Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
299 lines
6.6 KiB
JavaScript
Executable file
299 lines
6.6 KiB
JavaScript
Executable file
;(function(define){'use strict';define(function(require,exports,module){
|
|
/*jshint esnext:true*/
|
|
|
|
var debug = 0 ? console.log.bind(console, '[poplar]') : function() {};
|
|
|
|
/**
|
|
* Stores references to Poplar elements.
|
|
* @type {WeakMap}
|
|
*/
|
|
var elements = new WeakMap();
|
|
|
|
/**
|
|
* Regexs used to parse HTML.
|
|
* @type {Object}
|
|
*/
|
|
var regex = {
|
|
content: />([^<]+)</g,
|
|
var: /\$\{([^\}]+)\}/g,
|
|
attrs: /(<[a-z\-]+ )(.+?)( ?\/>|>)/g,
|
|
attr: /([a-z\-]+)="([^\"]+\$\{[^\"]+|\$[^"]+)"/g
|
|
};
|
|
|
|
/**
|
|
* Exports
|
|
*/
|
|
|
|
module.exports = poplar;
|
|
|
|
/**
|
|
* Create a poplar element
|
|
* from an HTML string.
|
|
*
|
|
* @example
|
|
*
|
|
* var el = poplar('<h1>Hello ${name}</h1>');
|
|
* poplar.populate(el, { name: 'Wilson' });
|
|
* el.textContent; //=> 'Hello Wilson'
|
|
*
|
|
* @param {String} html
|
|
* @return {HTMLElement}
|
|
*/
|
|
function poplar(html) {
|
|
return poplar.create(poplar.parse(html));
|
|
}
|
|
|
|
/**
|
|
* Parse an HTML string and return
|
|
* a formatted element that can
|
|
* be passed to .create().
|
|
*
|
|
* @param {String} html
|
|
* @return {HTMLElement}
|
|
*/
|
|
poplar.parse = function(html) {
|
|
debug('parse', html);
|
|
|
|
var formatted = html
|
|
.replace(/\s*\n\s*/g, '')
|
|
.replace(/ +/g, '')
|
|
|
|
// attribute bindings
|
|
.replace(regex.attrs, function(match, start, content, end) {
|
|
var dynamic = [];
|
|
var simple = content.replace(regex.attr, function(match, name, prop) {
|
|
dynamic.push(name + '=' + encodeURIComponent(prop));
|
|
return '';
|
|
});
|
|
|
|
// Return original if no dynamic attrs were found
|
|
if (!dynamic.length) return match;
|
|
|
|
var dataBindAttrs = dynamic.join(' ');
|
|
var tag = start +
|
|
simple + ' data-poplar-attrs="' + dataBindAttrs + '"' +
|
|
end;
|
|
return tag.replace(/ +/g, ' ');
|
|
})
|
|
|
|
// textNode bindings
|
|
.replace(regex.content, function(m, content) {
|
|
return m.replace(regex.var, function(m, group) {
|
|
return '<span data-poplar-text="' + group + '"></span>';
|
|
});
|
|
});
|
|
|
|
return elementify(formatted);
|
|
};
|
|
|
|
/**
|
|
* Turns a formatted element into a final
|
|
* Poplar element that can we passed
|
|
* to .poplate() to fill with data.
|
|
*
|
|
* @param {HTMLElement} parent
|
|
* @return {HTMLElement}
|
|
*/
|
|
poplar.create = function(parent) {
|
|
debug('create');
|
|
|
|
var el = parent.firstElementChild;
|
|
|
|
elements.set(el, {
|
|
textNodes: replaceTextPlaceholders(parent),
|
|
attrs: replaceAttrPlaceholders(parent)
|
|
});
|
|
|
|
return el;
|
|
};
|
|
|
|
/**
|
|
* Populate the element with data.
|
|
*
|
|
* @param {HTMLElement} el poplar element
|
|
* @param {Object} data Data to fill
|
|
* @public
|
|
*/
|
|
poplar.populate = function(el, data) {
|
|
debug('poplate', el, data);
|
|
|
|
var bindings = elements.get(el);
|
|
if (!bindings) {
|
|
debug('unknown element');
|
|
return false;
|
|
}
|
|
|
|
var textNodes = bindings.textNodes;
|
|
var attrs = bindings.attrs;
|
|
var i = textNodes.length;
|
|
var j = attrs.length;
|
|
|
|
// text nodes
|
|
while (i--) {
|
|
var node = textNodes[i].node;
|
|
var newData = getProp(data, textNodes[i].key);
|
|
if (node.data !== newData) {
|
|
node.data = newData;
|
|
}
|
|
}
|
|
|
|
// attributes
|
|
while (j--) {
|
|
var item = attrs[j];
|
|
|
|
// If the variable is part of an
|
|
// attribute string it must be
|
|
// templated each time
|
|
var value = item.template
|
|
? interpolate(item.template, data)
|
|
: getProp(data, item.prop);
|
|
|
|
item.el.setAttribute(item.name, value);
|
|
}
|
|
|
|
debug('populated');
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Template a string.
|
|
*
|
|
* @example
|
|
*
|
|
* interpolate('foo ${bar}', { bar: 'bar'}); //=> 'foo bar'
|
|
*
|
|
* @param {String} string
|
|
* @param {Object} data
|
|
* @return {String}
|
|
*/
|
|
function interpolate(string, data) {
|
|
return string.replace(regex.var, function(match, group) {
|
|
return getProp(data, group);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Replace the <span> textNode placeholders
|
|
* with real textNodes and return a references.
|
|
*
|
|
* @param {HTMLElement} el
|
|
* @return {Array}
|
|
*/
|
|
function replaceTextPlaceholders(el) {
|
|
var placeholders = el.querySelectorAll('[data-poplar-text]');
|
|
var i = placeholders.length;
|
|
var result = [];
|
|
|
|
while (i--) {
|
|
var node = document.createTextNode('');
|
|
placeholders[i].parentNode.replaceChild(node, placeholders[i]);
|
|
result.push({
|
|
key: placeholders[i].dataset.poplarText,
|
|
node: node
|
|
});
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Remove the [data-poplar-attr] reference
|
|
* and return a list of all dynamic attributes.
|
|
*
|
|
* @param {HTMLElement} el
|
|
* @return {Array}
|
|
*/
|
|
function replaceAttrPlaceholders(el) {
|
|
var placeholders = el.querySelectorAll('[data-poplar-attrs]');
|
|
var i = placeholders.length;
|
|
var result = [];
|
|
|
|
while (i--) {
|
|
var attrs = placeholders[i].dataset.poplarAttrs.split(' ');
|
|
var j = attrs.length;
|
|
|
|
while (j--) {
|
|
var parts = attrs[j].split('=');
|
|
var value = decodeURIComponent(parts[1]);
|
|
var attr = {
|
|
name: parts[0],
|
|
el: placeholders[i]
|
|
};
|
|
|
|
// If the variable is *part* of the whole string
|
|
// we must interpolate the variable into the string
|
|
// each time .populate() is called. If the variable
|
|
// fills the entire string we can slam it straight in.
|
|
if (isPartial(value)) attr.template = value;
|
|
else attr.prop = value.replace(regex.var, '$1');
|
|
|
|
result.push(attr);
|
|
}
|
|
|
|
placeholders[i].removeAttribute('data-poplar-attrs');
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Detect if variable is part of
|
|
* a larger string.
|
|
*
|
|
* @example
|
|
*
|
|
* isPartial('before ${foo}') //=> true
|
|
* isPartial('${foo} after') //=> true
|
|
* isPartial('${foo}') //=> false
|
|
*
|
|
* @param {String} value
|
|
* @return {Boolean}
|
|
*/
|
|
function isPartial(value) {
|
|
return !/^\$\{.+\}$/.test(value);
|
|
}
|
|
|
|
/**
|
|
* Turn an HTML String into an element.
|
|
*
|
|
* @param {String} html
|
|
* @return {HTMLElement}
|
|
*/
|
|
function elementify(html) {
|
|
var div = document.createElement('div');
|
|
div.innerHTML = html;
|
|
return div;
|
|
}
|
|
|
|
/**
|
|
* Get a property from an object,
|
|
* supporting dot notation for
|
|
* deep properties.
|
|
*
|
|
* @example
|
|
*
|
|
* getProp({ foo: { bar: 1 }}, 'foo.bar') //=> 1
|
|
*
|
|
* @param {[type]} item [description]
|
|
* @param {[type]} path [description]
|
|
* @return {[type]} [description]
|
|
*/
|
|
function getProp(object, path) {
|
|
if (!path) return;
|
|
|
|
var parts = path.split('.');
|
|
|
|
// Fast paths
|
|
if (parts.length == 1) return object[parts[0]];
|
|
if (parts.length == 2) return object[parts[0]][parts[1]];
|
|
|
|
return (function getDeep(object, parts) {
|
|
var part = parts.shift();
|
|
return parts.length ? getDeep(object[part], parts) : object[part];
|
|
})(object, parts);
|
|
}
|
|
|
|
});})(typeof define=='function'&&define.amd?define
|
|
:(function(n,w){'use strict';return typeof module=='object'?function(c){
|
|
c(require,exports,module);}:function(c){var m={exports:{}};c(function(n){
|
|
return w[n];},m.exports,m);w[n]=m.exports;};})('poplar',this));
|