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

Proxy touchup #46

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,15 +124,24 @@ _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'),
crel.input({ type: 'number' })
);
```

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.
Expand Down
98 changes: 51 additions & 47 deletions crel.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) => {
Expand Down
2 changes: 1 addition & 1 deletion crel.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 22 additions & 19 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
1 change: 1 addition & 0 deletions test/test.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
};
</script>
<script src="index.browser.js"></script>
<script src="../crel.js"></script>
</head>
<body></body>
</html>