/*! * Spawn UI elements using the Spawner service */ var XNAT = getObject(XNAT); (function(factory){ if (typeof define === 'function' && define.amd) { define(factory); } else if (typeof exports === 'object') { module.exports = factory(); } else { return factory(); } }(function(){ var undefined, ui, spawner, NAMESPACE = 'XNAT.ui', $ = jQuery || null, // check and localize hasConsole = console && console.log; XNAT.ui = getObject(XNAT.ui || {}); XNAT.spawner = spawner = getObject(XNAT.spawner || {}); spawner.counter = 0; // keep track of items that spawned spawner.spawnedElements = []; // keep track of items that didn't spawn spawner.notSpawned = []; function setRoot(url){ url = url.replace(/^([*~.]\/*)/, '/'); return XNAT.url.rootUrl(url) } // ================================================== // MAIN FUNCTION spawner.spawn = spawner.init = function _spawn(obj){ var frag = document.createDocumentFragment(), $frag = $(frag), callbacks = [], undefined; spawner.counter++; forOwn(obj, function(item, prop){ var kind, element, method, spawnedElement, $spawnedElement; // 'prop' can be a new or existing DOM element if (prop instanceof Element) { element = prop; prop = { kind: 'element', element: element }; } else { // save the config properties in a new object prop = cloneObject(prop); } // add this for proper handling in 'universal' widgets prop.spawnerElement = true; prop.element = prop.element || prop.config || {}; // use 'name' property in element or config // then look for 'name' at object root // lastly use the object's own name prop.name = prop.name || item; // auto-generate IDs if not specified // I really don't like doing this here. prop.id = prop.id || prop.element.id || toDashed(prop.name); // accept 'kind' or 'type' property name // but 'kind' will take priority // with a fallback to a generic div kind = prop.kind || prop.type || null; // make 'href' 'src' and 'action' properties // start at the site root if starting with '/' if (prop.element.href) { prop.element.href = setRoot(prop.element.href) } if (prop.element.src) { prop.element.src = setRoot(prop.element.src) } if (prop.element.action) { prop.element.action = setRoot(prop.element.action) } // do a raw spawn() if 'kind' is 'element' // or if there's a tag property if (kind === 'element' || prop.tag || prop.element.tag) { // pass 'content' (not contentS) property to add // stuff directly to spawned element prop.content = prop.content || prop.children || ''; try { // if setting up Spawner elements in JS, allow a // DOM element to be passed in the 'element' property if (prop.element instanceof Element) { spawnedElement = prop.element; } else { spawnedElement = spawn(prop.tag || prop.element.tag || 'span', prop.element, prop.content); } // convert relative URIs for href, src, and action attributes if (spawnedElement.href) { spawnedElement.href = setRoot(spawnedElement.getAttribute('href')) } if (spawnedElement.src) { spawnedElement.src = setRoot(spawnedElement.getAttribute('src')) } if (spawnedElement.action) { spawnedElement.action = setRoot(spawnedElement.getAttribute('action')) } // jQuery's .append() method is // MUCH more robust and forgiving // than element.appendChild() $frag.append(spawnedElement); spawner.spawnedElements.push(spawnedElement); } catch (e) { if (hasConsole) console.log(e); spawner.notSpawned.push(prop); } } else if (/^(text|html)$/i.test(kind)) { $frag.append(prop.content||prop.html||prop.text) } else { // check for a matching XNAT.ui method to call: method = // XNAT.kind.init() lookupObjectValue(XNAT, kind + '.init') || // XNAT.kind() lookupObjectValue(XNAT, kind) || // XNAT.ui.kind.init() lookupObjectValue(NAMESPACE + '.' + kind + '.init') || // XNAT.ui.kind() lookupObjectValue(NAMESPACE + '.' + kind) || // XNAT.element.kind() lookupObjectValue(XNAT, 'element.' + kind) || // kind.init() lookupObjectValue(kind + '.init') || // kind() lookupObjectValue(kind) || null; // only spawn elements with defined methods if (isFunction(method)) { // 'spawnedElement' item will be an // object with an 'element' property // or a .get() method that will retrieve // the spawned item spawnedElement = method(prop); // add spawnedElement to the master frag $frag.append(spawnedElement.element); // save a reference to spawnedElement spawner.spawnedElements.push(spawnedElement.element); } else { if (hasConsole) console.log('not spawned: ' + prop); spawner.notSpawned.push(prop); } } // give up if no spawnedElement if (!spawnedElement) return; // spawn child elements from... // 'contents' or 'content' or 'children' or // a property matching the value of either 'contains' or 'kind' if (prop.contains || prop.contents || prop[prop.kind]) { prop.contents = prop[prop.contains] || prop.contents || prop[prop.kind]; // if there's a 'target' property, put contents in there if (spawnedElement.target || spawnedElement.inner) { $spawnedElement = $(spawnedElement.target || spawnedElement.inner); } else { $spawnedElement = $(spawnedElement.element); } // if a string, number, or boolean is passed as 'contents' // just append that as-is (as a string) if (stringable(prop.contents)) { $spawnedElement.append(prop.contents+''); } else { $spawnedElement.append(_spawn(prop.contents).get()); } } // Treat 'before' and 'after' just like 'contents' // but insert the items 'before' or 'after' the main // spawned (outer) element. This may have unintended // consequences depending on the HTML structure of the // spawned widget that has things 'before' or 'after' it. if (prop.after) { if (stringable(prop.after) || Array.isArray(prop.after)) { $frag.append(prop.after) } else if (isPlainObject(prop.after)) { $frag.append(_spawn(prop.after).get()) } } if (prop.before) { if (stringable(prop.before) || Array.isArray(prop.before)) { $frag.prepend(prop.before) } else if (isPlainObject(prop.before)) { $frag.prepend(_spawn(prop.before).get()) } } // if there's a .load() method, fire that if (isFunction(spawnedElement.load||null)) { spawnedElement.load.call(spawnedElement); } // if there's an .onRender() method, queue it if (isFunction(spawnedElement.onRender||null)) { callbacks.push({ onRender: spawnedElement.onRender, spawned: spawnedElement, $element: $spawnedElement }); } }); _spawn.spawned = frag; _spawn.element = frag; _spawn.children = frag.children; _spawn.get = function(){ return frag; }; _spawn.getContents = function(){ return $frag.contents(); }; _spawn.done = function(callback){ if (isFunction(callback)) { callback(_spawn) } return _spawn; }; _spawn.render = function(container, wait, callback){ var $container = $$(container).hide(); wait = wait !== undefined ? wait : 100; $container.append(frag); $container.fadeIn(wait); // fire collected callbacks callbacks.forEach(function(obj){ try { obj.onRender.call(obj.spawned, obj.$element, obj); } catch(e) { console.log(e) } }); setTimeout(function(){ if (isFunction(callback)) { callback() } }, wait/2); return _spawn; }; _spawn.foo = '(spawn.foo)'; return _spawn; }; // ================================================== // given a container and spawner object, // spawn the elements into the container spawner.render = function(container, obj){ return spawner.spawn(obj).render($$(container)) }; // spawn elements with only the namespace/element path, // container/selector, and an optional AJAX config object // XNAT.spawner.resolve('siteAdmin/adminPage').render('#page-container'); // or assign it to a variable and render later. // var adminPage = XNAT.spawner.resolve('siteAdmin/adminPage'); // adminPage.render('#page-container'); // and methods from the AJAX request will be in .get.done(), .get.fail(), etc. spawner.resolve = function(nsPath, opts) { // you can pass a config object as the only argument opts = cloneObject(firstDefined(opts, getObject(nsPath))); console.log(opts); var url = opts.url || XNAT.url.restUrl('/xapi/spawner/resolve/' + nsPath); var request = XNAT.xhr.getJSON(extend(true, { url: url }, opts)); function spawnRender(){ var renderArgs = arguments; return request.done(function(obj){ spawner.spawn(obj).render.apply(request, renderArgs); return request; }); } return { done: request.done, spawn: spawnRender, render: spawnRender }; }; spawner.testSpawn = function(){ var jsonUrl = XNAT.url.rootUrl('/page/admin/data/config/site-admin-sample-new.json'); return $.getJSON({ url: jsonUrl, success: function(data){ spawner.spawn(data); } }); }; // this script has loaded spawner.loaded = true; return XNAT.spawner = spawner; }));