Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wb-config - Configuration web component #9676

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/base/_wb-config.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
*
* Web Experience Toolkit (WET) / Boîte à outils de l'expérience Web (BOEW)
* wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
*
*/

/* wb-config web component style */
wb-config {
@extend %global-display-none;
}
1 change: 1 addition & 0 deletions src/base/_wet-boew.scss
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
@import "bootstrap-sass/assets/stylesheets/bootstrap/glyphicons";

/*! Core - HTML */
@import "wb-config";
@import "bootstrap-sass/assets/stylesheets/bootstrap/scaffolding";
@import "bootstrap-sass/assets/stylesheets/bootstrap/type";
@import "bootstrap-overrides/core-heading";
Expand Down
9 changes: 8 additions & 1 deletion src/core/dep/jquery-fix.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,14 +176,21 @@ var localParseHTML = jQuery.parseHTML,
"<td />",
"<td/>"
],
sanitizeOptions = {
CUSTOM_ELEMENT_HANDLING: {
tagNameCheck: /^wb-config$/, // only allow the custom element we do have, eventually we might allow all tags starting with "<wb-*>" ex: /^wb-/
attributeNameCheck: /.+/, // allow all attributes
allowCustomizedBuiltInElements: false // customized built-ins are not allowed yet
}
},
sanitize = function( html ) {

// Add an exception for DataTable plugin
if ( window.DataTable && dataTableAllowedTag.indexOf( html ) !== -1 ) {
return html;
}

return DOMPurify.sanitize( html );
return DOMPurify.sanitize( html, sanitizeOptions );
};

jQuery.parseHTML = function( data, context, keepScripts ) {
Expand Down
178 changes: 175 additions & 3 deletions src/core/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,196 @@
*/
( function( $, wb ) {

wb.getData = function( element, dataName ) {
wb.getData = function( element, dataName, parseOptions ) {
var elm = !element.jquery ? element : element[ 0 ],
dataAttr = elm.getAttribute( "data-" + dataName ),
dataObj;
dataObj = {},
configElm, dataConfig;

if ( dataAttr ) {
try {
dataObj = JSON.parse( dataAttr );
$.data( elm, dataName, dataObj );
} catch ( error ) {
console.info( elm );
$.error( "Bad JSON array in data-" + dataName + " attribute" );
console.error( "Bad JSON array in data-" + dataName + " attribute" );
}
}

// Check if there is web-component wb-config
configElm = elm.querySelector( ":scope wb-config[wb-plugin=" + dataName + "]" ) || document.getElementById( elm.getAttribute( "data-" + dataName + "-config" ) );

if ( configElm ) {

// If wb-ignore are not define, set it by default to: wb-ignore="id"
if ( !configElm.hasAttribute( "wb-ignore" ) ) {
configElm.setAttribute( "wb-ignore", "id" );
}

// Get the setting from wb-config element
dataConfig = getWbConfig( configElm, parseOptions ).parsed;

if ( !dataAttr ) {
dataObj = dataConfig;
} else if ( Array.isArray( dataConfig ) && Array.isArray( dataObj ) ) {
dataObj = dataObj.concat( dataConfig );
} else if ( !Array.isArray( dataConfig ) && !Array.isArray( dataObj ) ) {
dataObj = $.extend( true, {}, dataObj, dataConfig );
} else {
console.info( elm );
console.error( "Incompatible setting, array and object can not be merged" );

// Let's use the dataConfig
dataObj = dataConfig;
}
}

return dataObj;
};

function getWbConfig( elm, parseOptions ) {

var context = {},
configuration = {},
i, i_len = elm.attributes.length,
attr, attrName, attrValue,
innerConfig = elm.querySelectorAll( ":scope > wb-config" ),
innerConfigArray = [],
lstIgnoreProp,
configParsed,
parsedValue;

// Validate parse Options
parseOptions = parseOptions || {};

// Get all attribute and triage aside the contextual ones prefixed with "wb-"
for ( i = 0; i !== i_len; i++ ) {
attr = elm.attributes[ i ];
attrName = attr.nodeName;
attrValue = attr.value;

// Is it a contextual setting
if ( attrName.startsWith( "wb-" ) ) {
context[ attrName.substring( 3 ) ] = attrValue;
continue;
}

// Exclude any data-* attribute
if ( attrName.startsWith( "data-" ) ) {
continue;
}

configuration[ attrName ] = parseConfigValue( attrName, attrValue, parseOptions );
}

// Is there any property to remove/cleanup?
if ( context.ignore ) {
lstIgnoreProp = context.ignore.split( " " );
i_len = lstIgnoreProp.length;
for ( i = 0; i !== i_len; i++ ) {
if ( configuration[ lstIgnoreProp[ i ] ] !== undefined ) {
delete configuration[ lstIgnoreProp[ i ] ];
}
}
}

// Is there any wb-config children?
i_len = innerConfig.length;
for ( i = 0; i !== i_len; i++ ) {
configParsed = getWbConfig( innerConfig[ i ], parseOptions );

if ( configParsed.context.prop ) {
configuration[ configParsed.context.prop ] = configParsed.parsed;
} else {
innerConfigArray.push( configParsed.parsed );
}
}

// Check if the data type enforced for textContent are JSON
if ( !context.value ) {
context.value = elm.textContent;
} else if ( !context.type && context.value ) {
context.type = "json";
}

// Define the true value of the configuration
if ( !innerConfig.length && ( context.prop || context.value ) ) {

parsedValue = {};

// Map the text content value
if ( context.type === "json" ) {
try {
parsedValue = JSON.parse( context.value );
} catch ( error ) {
console.info( elm );
console.error( "Bad JSON in wb-value attribute" );
}
} else if ( context.type === "bool" ) {
parsedValue = parseConfigValue( context.prop, context.value, { expectBool: [ context.prop ] } );
} else if ( context.type === "number" ) {
parsedValue = parseConfigValue( context.prop, context.value, { expectNb: [ context.prop ] } );
} else if ( context.type === "pre" ) {
parsedValue = parseConfigValue( context.prop, context.value, parseOptions );
} else { // string
parsedValue = parseConfigValue( context.prop, context.value.trim(), parseOptions );
}

// Add the property value as sibling prop
if ( Object.keys( configuration ).length ) {
if ( !parsedValue === "" || context.type === "pre" ) {
configuration[ context.prop ] = parsedValue;
}
} else {
configuration = parsedValue;
}

} else if ( innerConfigArray.length && ( !Object.keys( configuration ).length ) ) {

// The value are an array of object
configuration = innerConfigArray;
} else if ( innerConfigArray.length && Object.keys( configuration ).length ) {

// We can't mix key/value items with anonymous object
console.error( elm );
console.error( "Error in the configuration, Unnamed property or incompatible config type where array must not be intermixed with key/value.\nThe following config are ignored" );
console.error( innerConfigArray );
}

return {
context: context,
parsed: configuration
};
}

function parseConfigValue( propName, propValue, options ) {

var expectedBooleanValue = options.expectBool || [],
expectedNumberValue = options.expectNb || [];

// Adjust the value based on plugin configuration expectation
if ( expectedBooleanValue.indexOf( propName ) !== -1 ) {
switch ( propValue ) {

case "false":
case "0":
propValue = false;
break;

case "true":
default:
propValue = true;
break;
}
}

if ( expectedNumberValue.indexOf( propName ) !== -1 ) {
propValue = Number.parseFloat( propValue );
}

return propValue;
}

/*
* Initiate an in-browser download from a blob
* @param blob: a reference to a blob object
Expand Down