diff --git a/README.md b/README.md index 4ef8818..626b308 100644 --- a/README.md +++ b/README.md @@ -124,8 +124,6 @@ _But don't._ If you are using Crel in an environment that supports Proxies, you can also use the new API: ```javascript -let crel = require('crel').proxy; - let element = crel.div( crel.h1('Crello World!'), crel.p('This is crel'), @@ -133,6 +131,17 @@ let element = crel.div( ); ``` +If you want to transform tags to for example get dashes in them, you can define a `tagTransform` function: + +```javascript +// Adds dashes on camelCase, ex: `camelCase` -> `camel-case` +crel.tagTransform = key => key.replace(/([0-9a-z])([A-Z])/g, '$1-$2'); + +let element = crel.myTag('Crello World!'); + +console.log(element.tagName); // my-tag +``` + # Browser support Crel uses ES6 features, so it'll work in all evergreen browsers. diff --git a/crel.js b/crel.js index 9cba0f3..9a6e4a4 100644 --- a/crel.js +++ b/crel.js @@ -9,14 +9,15 @@ This might make it harder to read at times, but the code's intention should be t // IIFE our function ((exporter) => { - // Define our function and its properties // These strings are used multiple times, so this makes things smaller once compiled const func = 'function', isNodeString = 'isNode', + proxyString = 'proxy', + tagTransformString = 'tagTransform', d = document, // Helper functions used throughout the script isType = (object, type) => typeof object === type, - // Recursively appends children to given element. As a text node if not already an element + // Recursively appends children to given element if they're not `null`. As a text node if not already an element appendChild = (element, child) => { if (child !== null) { if (Array.isArray(child)) { // Support (deeply) nested child elements @@ -28,56 +29,59 @@ This might make it harder to read at times, but the code's intention should be t element.appendChild(child); } } - }; - // - function crel (element, settings) { - // Define all used variables / shortcuts here, to make things smaller once compiled - let args = arguments, // Note: assigned to a variable to assist compilers. - index = 1, - key, - attribute; - // If first argument is an element, use it as is, otherwise treat it as a tagname - element = crel.isElement(element) ? element : d.createElement(element); - // Check if second argument is a settings object - if (isType(settings, 'object') && !crel[isNodeString](settings) && !Array.isArray(settings)) { - // Don't treat settings as a child - index++; - // Go through settings / attributes object, if it exists - for (key in settings) { - // Store the attribute into a variable, before we potentially modify the key - attribute = settings[key]; - // Get mapped key / function, if one exists - key = crel.attrMap[key] || key; - // Note: We want to prioritise mapping over properties - if (isType(key, func)) { - key(element, attribute); - } else if (isType(attribute, func)) { // ex. onClick property - element[key] = attribute; - } else { - // Set the element attribute - element.setAttribute(key, attribute); + }, + // Define our function as a proxy interface + crel = new Proxy((element, ...children) => { + // Define all used variables / shortcuts here, to make things smaller once compiled + let settings = children[0], + key, + attribute; + // If first argument is an element, use it as is, otherwise treat it as a tagname + element = crel.isElement(element) ? element : d.createElement(element); + // Check if second argument is a settings object + if (isType(settings, 'object') && !crel[isNodeString](settings) && !Array.isArray(settings)) { + // Don't treat settings as a child + children.shift(); + // Go through settings / attributes object, if it exists + for (key in settings) { + // Store the attribute into a variable, before we potentially modify the key + attribute = settings[key]; + // Get mapped key / function, if one exists + key = crel.attrMap[key] || key; + // Note: We want to prioritise mapping over properties + if (isType(key, func)) { + key(element, attribute); + } else if (isType(attribute, func)) { // ex. onClick property + element[key] = attribute; + } else { + // Set the element attribute + element.setAttribute(key, attribute); + } } } - } - // Loop through all arguments, if any, and append them to our element if they're not `null` - for (; index < args.length; index++) { - appendChild(element, args[index]); - } - - return element; - } - - // Used for mapping attribute keys to supported versions in bad browsers, or to custom functionality + // Append remaining children to element and return it + appendChild(element, children); + return element; + }, {// Binds specific tagnames to crel function calls with that tag as the first argument + get: (target, key) => { + if (key in target) { + return target[key]; + } + key = target[tagTransformString](key); + if (!(key in target[proxyString])) { + target[proxyString][key] = target.bind(null, key); + } + return target[proxyString][key]; + } + }); + // Used for mapping attribute keys to custom functionality, or to supported versions in bad browsers crel.attrMap = {}; crel.isElement = object => object instanceof Element; crel[isNodeString] = node => node instanceof Node; - // Expose proxy interface - crel.proxy = new Proxy(crel, { - get: (target, key) => { - !(key in crel) && (crel[key] = crel.bind(null, key)); - return crel[key]; - } - }); + // Bound functions are "cached" here for legacy support and to keep Crels internal structure clean + crel[proxyString] = new Proxy({}, { get: (target, key) => target[key] || crel[key] }); + // Transforms tags on call, to for example allow dashes in tags + crel[tagTransformString] = key => key; // Export crel exporter(crel, func); })((product, func) => { diff --git a/crel.min.js b/crel.min.js index d17ea8c..98b8387 100644 --- a/crel.min.js +++ b/crel.min.js @@ -1 +1 @@ -(e=>{const t="function",n="isNode",r=document,o=(e,t)=>typeof e===t,i=(e,t)=>{null!==t&&(Array.isArray(t)?t.map(t=>i(e,t)):(a[n](t)||(t=r.createTextNode(t)),e.appendChild(t)))};function a(e,f){let l,d,s=arguments,c=1;if(e=a.isElement(e)?e:r.createElement(e),o(f,"object")&&!a[n](f)&&!Array.isArray(f))for(l in c++,f)d=f[l],l=a.attrMap[l]||l,o(l,t)?l(e,d):o(d,t)?e[l]=d:e.setAttribute(l,d);for(;ce instanceof Element),a[n]=(e=>e instanceof Node),a.proxy=new Proxy(a,{get:(e,t)=>(!(t in a)&&(a[t]=a.bind(null,t)),a[t])}),e(a,t)})((e,t)=>{"object"==typeof exports?module.exports=e:typeof define===t&&define.amd?define(e):this.crel=e}); \ No newline at end of file +(e=>{const t="function",n="isNode",r="proxy",o="tagTransform",i=document,a=(e,t)=>typeof e===t,s=(e,t)=>{null!==t&&(Array.isArray(t)?t.map(t=>s(e,t)):(f[n](t)||(t=i.createTextNode(t)),e.appendChild(t)))},f=new Proxy((e,...r)=>{let o,d,l=r[0];if(e=f.isElement(e)?e:i.createElement(e),a(l,"object")&&!f[n](l)&&!Array.isArray(l))for(o in r.shift(),l)d=l[o],a(o=f.attrMap[o]||o,t)?o(e,d):a(d,t)?e[o]=d:e.setAttribute(o,d);return s(e,r),e},{get:(e,t)=>t in e?e[t]:((t=e[o](t))in e[r]||(e[r][t]=e.bind(null,t)),e[r][t])});f.attrMap={},f.isElement=(e=>e instanceof Element),f[n]=(e=>e instanceof Node),f[r]=new Proxy({},{get:(e,t)=>e[t]||f[t]}),f[o]=(e=>e),e(f,t)})((e,t)=>{"object"==typeof exports?module.exports=e:typeof define===t&&define.amd?define(()=>e):this.crel=e}); \ No newline at end of file diff --git a/test/index.js b/test/index.js index 5671352..84ad205 100644 --- a/test/index.js +++ b/test/index.js @@ -222,32 +222,35 @@ test('Test that `isElement` is defined', function (t) { }); // -- Test the Proxy API -- -test('Test that the Proxy API is defined', function (t) { +test('Test that the Proxy API works', function (t) { if (typeof Proxy === 'undefined') { t.plan(1) t.pass('Proxies are not supported in the current environment'); } else { - var proxyCrel = crel.proxy; + // I'm not proficient with proxies, so + // TODO: Add #moar-tests + t.plan(4); - t.plan(proxyCrel ? 2 : 1); + var testElement = crel.proxy.div({'class': 'test'}, + crel.proxy.span('test')); - t.ok(proxyCrel, 'The Proxy API is defined'); + t.equal(testElement.className, 'test'); + t.equal(testElement.childNodes.length, 1); + t.equal(testElement.childNodes[0].tagName, 'SPAN'); + t.equal(testElement.childNodes[0].textContent, 'test'); + } +}); - if (proxyCrel) { - // Do further tests - t.test('Test that the Proxy API works', function (ts) { - // I'm not proficient with proxies, so - // TODO: Add #moar-tests - ts.plan(4); +// -- Test the Proxy APIs features -- +test('Test the proxy APIs tag transformations', (t) => { + t.plan(4); - var testElement = proxyCrel.div({'class': 'test'}, - proxyCrel.span('test')); + crel.tagTransform = (key) => key.replace(/([0-9a-z])([A-Z])/g, '$1-$2').toLowerCase(); + let testElement = crel.myTable(crel.span('test')); - ts.equal(testElement.className, 'test'); - ts.equal(testElement.childNodes.length, 1); - ts.equal(testElement.childNodes[0].tagName, 'SPAN'); - ts.equal(testElement.childNodes[0].textContent, 'test'); - }); - } - } + t.equal(testElement.tagName, 'MY-TABLE', + 'tagname had dashes added to it'); + t.equal(testElement.childNodes.length, 1); + t.equal(testElement.childNodes[0].tagName, 'SPAN'); + t.equal(testElement.childNodes[0].textContent, 'test'); }); diff --git a/test/test.html b/test/test.html index d6b99f3..cb90462 100644 --- a/test/test.html +++ b/test/test.html @@ -12,6 +12,7 @@ }; +