Newer
Older
Mark M. Florida
committed
/*!
* 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,
Mark M. Florida
committed
ui, spawner,
NAMESPACE = 'XNAT.ui',
$ = jQuery || null, // check and localize
Mark M. Florida
committed
hasConsole = console && console.log;
Mark M. Florida
committed
XNAT.ui =
getObject(XNAT.ui || {});
XNAT.spawner = spawner =
getObject(XNAT.spawner || {});
Mark M. Florida
committed
Mark M. Florida
committed
spawner.counter = 0;
Mark M. Florida
committed
// keep track of items that spawned
spawner.spawnedElements = [];
Mark M. Florida
committed
// keep track of items that didn't spawn
spawner.notSpawned = [];
Mark M. Florida
committed
Mark M. Florida
committed
function setRoot(url){
Mark M. Florida
committed
url = url.replace(/^([*~.]\/*)/, '/');
Mark M. Florida
committed
return XNAT.url.rootUrl(url)
}
Mark M. Florida
committed
Mark M. Florida
committed
// ==================================================
// MAIN FUNCTION
Mark M. Florida
committed
spawner.spawn = spawner.init = function _spawn(obj){
var frag = document.createDocumentFragment(),
Mark M. Florida
committed
$frag = $(frag),
Mark M. Florida
committed
callbacks = [],
Mark M. Florida
committed
undefined;
Mark M. Florida
committed
Mark M. Florida
committed
spawner.counter++;
forOwn(obj, function(item, prop){
Mark M. Florida
committed
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;
Mark M. Florida
committed
prop.element = prop.element || prop.config || {};
Mark M. Florida
committed
// 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;
Mark M. Florida
committed
// auto-generate IDs if not specified
// I really don't like doing this here.
prop.id = prop.id || prop.element.id || toDashed(prop.name);
Mark M. Florida
committed
// accept 'kind' or 'type' property name
// but 'kind' will take priority
// with a fallback to a generic div
kind = prop.kind || prop.type || null;
Mark M. Florida
committed
Mark M. Florida
committed
// make 'href' 'src' and 'action' properties
Mark M. Florida
committed
// start at the site root if starting with '/'
if (prop.element.href) {
prop.element.href = setRoot(prop.element.href)
Mark M. Florida
committed
}
Mark M. Florida
committed
if (prop.element.src) {
prop.element.src = setRoot(prop.element.src)
Mark M. Florida
committed
if (prop.element.action) {
prop.element.action = setRoot(prop.element.action)
Mark M. Florida
committed
Mark M. Florida
committed
// do a raw spawn() if 'kind' is 'element'
// or if there's a tag property
Mark M. Florida
committed
if (kind === 'element' || prop.tag || prop.element.tag) {
// pass 'content' (not contentS) property to add
// stuff directly to spawned element
Mark M. Florida
committed
prop.content = prop.content || prop.children || '';
Mark M. Florida
committed
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'))
}
Mark M. Florida
committed
// jQuery's .append() method is
// MUCH more robust and forgiving
// than element.appendChild()
$frag.append(spawnedElement);
spawner.spawnedElements.push(spawnedElement);
Mark M. Florida
committed
}
catch (e) {
if (hasConsole) console.log(e);
spawner.notSpawned.push(prop);
Mark M. Florida
committed
}
}
else if (/^(text|html)$/i.test(kind)) {
$frag.append(prop.content||prop.html||prop.text)
}
Mark M. Florida
committed
else {
Mark M. Florida
committed
Mark M. Florida
committed
// check for a matching XNAT.ui method to call:
method =
// XNAT.kind.init()
Mark M. Florida
committed
lookupObjectValue(XNAT, kind + '.init') ||
// XNAT.kind()
Mark M. Florida
committed
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()
Mark M. Florida
committed
lookupObjectValue(kind + '.init') ||
// kind()
Mark M. Florida
committed
lookupObjectValue(kind) ||
Mark M. Florida
committed
null;
Mark M. Florida
committed
// only spawn elements with defined methods
if (isFunction(method)) {
Mark M. Florida
committed
// object with an 'element' property
// or a .get() method that will retrieve
// the spawned item
spawnedElement = method(prop);
Mark M. Florida
committed
// add spawnedElement to the master frag
$frag.append(spawnedElement.element);
Mark M. Florida
committed
// save a reference to spawnedElement
spawner.spawnedElements.push(spawnedElement.element);
Mark M. Florida
committed
}
else {
if (hasConsole) console.log('not spawned: ' + prop);
spawner.notSpawned.push(prop);
Mark M. Florida
committed
}
}
Mark M. Florida
committed
// 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
Mark M. Florida
committed
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());
}
Mark M. Florida
committed
}
// 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())
}
}
Mark M. Florida
committed
// if there's a .load() method, fire that
if (isFunction(spawnedElement.load||null)) {
Mark M. Florida
committed
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
});
Mark M. Florida
committed
}
Mark M. Florida
committed
});
_spawn.spawned = frag;
_spawn.element = frag;
Mark M. Florida
committed
Mark M. Florida
committed
_spawn.get = function(){
Mark M. Florida
committed
return frag;
};
Mark M. Florida
committed
_spawn.getContents = function(){
return $frag.contents();
};
Mark M. Florida
committed
Mark M. Florida
committed
_spawn.done = function(callback){
if (isFunction(callback)) {
Mark M. Florida
committed
callback(_spawn)
Mark M. Florida
committed
}
Mark M. Florida
committed
return _spawn;
Mark M. Florida
committed
};
Mark M. Florida
committed
_spawn.render = function(container, wait, callback){
var $container = $$(container).hide();
Mark M. Florida
committed
wait = wait !== undefined ? wait : 100;
$container.append(frag);
Mark M. Florida
committed
$container.fadeIn(wait);
Mark M. Florida
committed
// fire collected callbacks
callbacks.forEach(function(obj){
try {
obj.onRender.call(obj.spawned, obj.$element, obj);
}
catch(e) {
console.log(e)
}
});
setTimeout(function(){
Mark M. Florida
committed
if (isFunction(callback)) {
callback()
}
Mark M. Florida
committed
}, wait/2);
Mark M. Florida
committed
return _spawn;
};
_spawn.foo = '(spawn.foo)';
return _spawn;
Mark M. Florida
committed
};
Mark M. Florida
committed
// ==================================================
Mark M. Florida
committed
Mark M. Florida
committed
Mark M. Florida
committed
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
// 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));
Mark M. Florida
committed
function spawnRender(){
var renderArgs = arguments;
Mark M. Florida
committed
return request.done(function(obj){
Mark M. Florida
committed
spawner.spawn(obj).render.apply(request, renderArgs);
return request;
Mark M. Florida
committed
});
}
return {
done: request.done,
spawn: spawnRender,
render: spawnRender
};
};
Mark M. Florida
committed
spawner.testSpawn = function(){
var jsonUrl =
Mark M. Florida
committed
XNAT.url.rootUrl('/page/admin/data/config/site-admin-sample-new.json');
return $.getJSON({
url: jsonUrl,
success: function(data){
spawner.spawn(data);
}
});
Mark M. Florida
committed
};
Mark M. Florida
committed
// this script has loaded
spawner.loaded = true;
Mark M. Florida
committed
return XNAT.spawner = spawner;
}));