Skip to content

Instantly share code, notes, and snippets.

@Error601
Last active January 28, 2016 20:06
Show Gist options
  • Save Error601/7a86fa6e34fbc98b31a6 to your computer and use it in GitHub Desktop.
Save Error601/7a86fa6e34fbc98b31a6 to your computer and use it in GitHub Desktop.
Generate HTML elements, returning a jQuery object for the outermost element
/*!
* Create HTML elements from a config object
* https://gist.github.com/Error601/7a86fa6e34fbc98b31a6
*/
if (typeof jQuery == 'undefined') {
throw new Error('jQuery is required');
}
(function($, undefined){
function isElement(it){
return it.nodeType && it.nodeType === 1;
}
function isFragment(it){
return it.nodeType && it.nodeType === 11;
}
// returns first defined argument
// useful for retrieving 'falsey' values
function firstDefined() {
var undefined, i = -1;
while (++i < arguments.length) {
if (arguments[i] !== undefined) {
return arguments[i];
}
}
return undefined;
}
// attach to global jQuery $ object
// (since it uses jQuery)
// usage:
// var $div1 = $.spawn('div|id:div1', {addClass: 'foo'}, {name: 'bar'}, [$p1, $p2, $p3]);
// var $div2 = $.spawn('div'), {}, {id:'div2'}, "Div2's HTML content")
/**
* Create a jQuery-wrapped DOM object
* @param tag - HTML tag
* @param opts - jQuery methods / child element(s) / HTML
* @param attr - native DOM attributes
* @param content - child element(s) / HTML
* @returns {object} - jQuery object
*/
$.spawn = function(tag, opts, attr, content){
var el, $el, parts, _opts={}, attrs, _attr={};
// return jQuery-wrapped fragment
// if called with no arguments
if (typeof tag === 'undefined') {
return $(document.createDocumentFragment());
}
// stop and return if it's
// already a jQuery object
if (tag && tag.jquery) {
return tag;
}
if ($.isPlainObject(tag)) {
opts = tag;
tag = firstDefined(opts.tag||undefined, null);
}
// trim outer white space and remove any trailing semicolons or commas
parts = tag.trim().replace(/(;|,)$/,'').split('|');
tag = parts[0].trim();
// pass empty string or #text as
// first argument to create fragment
if (tag === '' || tag === '#text'){
el = document.createDocumentFragment();
}
else {
try {
el = document.createElement(tag||'span');
}
catch(e){
if (console && console.log) console.log(e);
el = document.createDocumentFragment();
el = el.appendChild(document.createTextNode(tag||''));
}
}
// pass element attributes in 'tag' string, like:
// $.spawn('a|id="foo-link";href="foo";class="bar"');
// or (colons for separators, commas for delimeters, no quotes),:
// $.spawn('input|type:checkbox,id:foo-ckbx');
attrs = (parts[1]||'').split(/;|,/) || []; // allow ';' or ',' for attribute delimeter
attrs.forEach(function(att){
if (!att) return;
var sep = /:|=/; // allow ':' or '=' for key/value separator
var quotes = /^('|")|('|")$/g;
var key = att.split(sep)[0].trim();
var val = (att.split(sep)[1]||'').trim().replace(quotes, '') || key;
// add each attribute/property directly to DOM element
el[key] = val;
});
$el = $(el);
if (attr) {
if ($.isPlainObject(attr)){
// pull out the 'prop' properties
if (attr.prop){
$el.prop(attr.prop);
delete attr.prop;
}
}
try {
// could be an object map of multiple attributes
// or could be an array for a single attribute - ['name','foo']
$el.attr.apply($el, [].concat(attr));
}
catch(e){
if (console && console.log) console.log(e);
}
}
if (typeof opts == 'undefined') {
return $el;
}
opts = opts || {};
// just append an HTML string, jQuery object, element, or fragment
if (typeof opts == 'string' || opts.jquery || isElement(opts) || isFragment(opts)){
return $el.append(opts);
}
// if 'opts' is an array, they
// will be child elements
if ($.isArray(opts)) {
_opts.children = opts;
}
// otherwise it's a config object
if ($.isPlainObject(opts)) {
_opts = $.extend(true, {}, opts);
}
// a fourth argument can contain additional content
if (content){
_opts.content = [].concat(_opts.content||[], content);
}
$.each(_opts, function(prop, val){
// skip 'tag' property
if (prop === 'tag') return;
// special handling for 'id' property
if (prop === 'id'){
el.id = val;
return;
}
// special handling for 'class', 'className', or 'classes' property
// accepts a className string or array of separate class names
if (/^(class|classes|className|classNames)$/.test(prop)){
el.className = [].concat(val).join(' ').replace(/\s+/g, ' ').trim();
return;
}
// 'element' (or 'el') property contains
// items to attach natively
// to the new element
// (without jQuery)
if (/^(element|el)$/.test(prop)){
$.each(val, function(name, value){
el[name] = value;
});
return;
}
// 'children' property is
// an array of elements
// to be spawned
if (/^(children|content|contents)$/.test(prop)) {
$.each([].concat(val), function(i, child){
try {
// recursively append spawns as needed
$el.append(child); // each child must be an 'appendable' item
//$.spawn(child).appendTo($el);
//$el.append($.spawn.apply(null, [].concat(child)));
}
catch (e) {
if (console && console.log) console.log(e);
}
});
return;
}
// accept event handlers with varying
// number of arguments
if (/^(on|off)$/.test(prop)){
$.each(val, function(evt, args){
try {
$el[prop].apply($el, [evt].concat(args));
}
catch(e){
if (console && console.log) console.log(e);
}
});
return;
}
// otherwise root property names will be
// treated as jQuery methods
try {
$el[prop].apply($el, [].concat(val));
}
catch (e) {
if (console && console.log) console.log(e);
}
});
// raw element available in the
// 'element' property of returned
// jQuery object
$el.element = el;
return $el;
};
})(jQuery);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment