// dna.js Template Cloner ~~ v0.3.6
// MIT/GPLv3 ~~ dnajs.org/license.html
// Copyright (c) 2013-2015 Center Key Software and other contributors
var dna = {
// API:
// dna.clone()
// dna.cloneSubTemplate()
// dna.createTemplate()
// dna.load()
// dna.getModel()
// dna.empty()
// dna.refresh()
// dna.refreshAll()
// dna.destroy()
// dna.getClone()
// dna.getClones()
// dna.getIndex()
// dna.bye()
// dna.registerInitializer()
// dna.clearInitializers()
// dna.info()
// See: http://dnajs.org/manual.html#api
clone: function(name, data, options) {
var settings = $.extend(
{ fade: false, top: false, container: null, empty: false, html: false, callback: null },
options);
var template = dna.store.getTemplate(name);
if (template.nested && !settings.container)
dna.core.berserk('Container missing for nested template: ' + name);
if (settings.empty)
dna.empty(name);
var list = data instanceof Array ? data : [data];
var clones = $();
for (var i = 0; i < list.length; i++)
clones = clones.add(dna.core.replicate(template, list[i], i, settings));
if (clones.first().closest('.dna-menu, .dna-panels').length)
dna.panels.refresh();
return clones;
},
cloneSubTemplate: function(holderClone, arrayField, data, options) {
var name = dna.compile.subTemplateName(holderClone, arrayField);
var selector = '.dna-contains-' + name;
var settings = $.extend({ container: holderClone.find(selector).addBack(selector) }, options);
dna.clone(name, data, settings);
var array = dna.getModel(holderClone)[arrayField];
function append() { array.push(this); }
$.each(data instanceof Array ? data : [data], append);
return holderClone;
},
createTemplate: function(name, html, holder) {
$(html).attr('id', name).addClass('dna-template').appendTo(holder);
return dna.store.getTemplate(name);
},
load: function(name, url, options) {
function processJson(data) { dna.core.unload(name, data, options); }
return $.getJSON(url, processJson);
},
getModel: function(elemOrName, options) {
function getOneModel(elem) {
return dna.getClone(elem, options).data('dnaModel');
}
function getAllModels(name) {
var model = [];
function addToModel() { model.push(dna.getModel($(this))); }
dna.getClones(name).each(addToModel);
return model;
}
return (elemOrName instanceof jQuery ? getOneModel : getAllModels)(elemOrName);
},
empty: function(name, options) {
var settings = $.extend({ fade: false }, options);
var clones = dna.store.getTemplate(name).container.find('.dna-clone');
return settings.fade ? dna.ui.slideFadeDelete(clones) : clones.remove();
},
refresh: function(clone, options) {
var settings = $.extend({ html: false }, options);
var elem = dna.getClone(clone, options);
var data = settings.data ? settings.data : dna.getModel(elem);
return dna.core.inject(elem, data, null, settings);
},
refreshAll: function(name) {
function refresh() { dna.refresh($(this)); }
return dna.getClones(name).each(refresh);
},
mutate: function(clone, data, options) { //DEPRECATED
return dna.refresh(clone, $.extend({ data: data }, options));
},
destroy: function(clone, options) {
var settings = $.extend({ fade: false }, options);
clone = dna.getClone(clone, options);
function removeArrayItem(field) {
dna.getModel(clone.parent())[field].splice(dna.getIndex(clone), 1);
}
if (clone.hasClass('dna-sub-clone'))
removeArrayItem(clone.data().dnaRules.array);
return settings.fade ? dna.ui.slideFadeDelete(clone) : clone.remove();
},
getClone: function(elem, options) {
var settings = $.extend({ main: false }, options);
var selector = settings.main ? '.dna-clone:not(.dna-sub-clone)' : '.dna-clone';
return elem instanceof jQuery ? elem.closest(selector) : $();
},
getClones: function(name) {
return dna.store.getTemplate(name).container.children().filter('.dna-clone');
},
getIndex: function(elem, options) {
var clone = dna.getClone(elem, options);
return clone.parent().children('.dna-clone').index(clone);
},
bye: function(elemOrEventOrIndex) {
return dna.destroy(dna.ui.toElem(elemOrEventOrIndex, this), { fade: true });
},
registerInitializer: function(func, options) {
var settings = $.extend({ onDocumentLoad: true }, options);
if (settings.onDocumentLoad)
dna.util.apply(func, [settings.selector ? $(settings.selector).not('.dna-template ' +
settings.selector).addClass('dna-initialized') : $(document)].concat(settings.params));
return dna.events.initializers.push(
{ func: func, selector: settings.selector, params: settings.params });
},
clearInitializers: function() {
dna.events.initializers = [];
},
info: function() {
var names = Object.keys(dna.store.templates);
console.log('~~ dna.js v0.3.6 ~~');
console.log('templates:', names.length);
console.log('names:', names);
console.log('store:', dna.store.templates);
console.log('initializers:', dna.events.initializers.length);
return navigator.appVersion;
}
};
dna.util = {
toCamel: function(codeStr) { //example: 'ready-set-go' ==> 'readySetGo'
function hump(match, char) { return char.toUpperCase(); }
return ('' + codeStr).replace(/\-(.)/g, hump);
},
toCode: function(camelCaseStr) { //example: 'readySetGo' ==> 'ready-set-go'
function dash(word) { return '-' + word.toLowerCase(); }
return ('' + camelCaseStr).replace(/([A-Z]+)/g, dash).replace(/\s|^-/g, '');
},
value: function(data, field) { //example: dna.util.value({ a: { b: 7 }}, 'a.b'); ==> 7
if (typeof field === 'string')
field = field.split('.');
return (data === null || data === undefined || field === undefined) ? null :
(field.length === 1 ? data[field[0]] : this.value(data[field[0]], field.slice(1)));
},
realTruth: function(value) { //returns a boolean
// Example true values: true, 7, '7', [5], 't', 'T', 'TRue', {}, 'Colbert'
// Example false values: false, 0, '0', [], 'f', 'F', 'faLSE', null, undefined, NaN
function falseyStr() { return /^(f|false|0)$/i.test(value); }
function emptyArray() { return value instanceof Array && value.length === 0; }
return value ? !emptyArray() && !falseyStr() : false;
},
apply: function(func, params) { //calls func (string name or actual function) passing in params
// Example: dna.util.apply('app.cart.buy', 7); ==> app.cart.buy(7);
var args = [].concat(params);
var elem = args[0], result;
function contextApply(obj, names) {
if (!obj || (names.length == 1 && typeof obj[names[0]] !== 'function'))
dna.core.berserk('Callback function not found: ' + func);
else if (names.length == 1)
result = obj[names[0]].apply(elem, args); //'app.cart.buy' ==> window['app']['cart']['buy']
else
contextApply(obj[names[0]], names.slice(1));
}
if (elem instanceof jQuery && elem.length === 0)
result = elem;
else if (typeof func === 'function')
result = func.apply(elem, args);
else if (elem && elem[func])
result = elem[func](args[1], args[2], args[3]);
else if (func === '' || { number: true, boolean: true}[typeof func])
dna.core.berserk('Invalid callback function: ' + func);
else if (typeof func === 'string' && func.length > 0)
contextApply(window, func.split('.'));
return result;
}
};
dna.ui = {
toElem: function(elemOrEventOrIndex, that) {
return elemOrEventOrIndex instanceof jQuery ? elemOrEventOrIndex :
elemOrEventOrIndex ? $(elemOrEventOrIndex.target) : $(that);
},
deleteElem: function(elemOrEventOrIndex) { //example: $('.box').fadeOut(dna.ui.deleteElem);
return dna.ui.toElem(elemOrEventOrIndex, this).remove();
},
slideFade: function(elem, callback, show) {
var obscure = { opacity: 0.0, transition: 'opacity 0s ease 0s' };
var easyIn = { opacity: 1.0, transition: 'opacity 0.4s ease-in' };
var easyOut = { opacity: 0.0, transition: 'opacity 0.4s ease-out' };
var reset = { transition: 'opacity 0s ease 0s' };
function clearOpacityTransition() { elem.css(reset); }
window.setTimeout(clearOpacityTransition, 1000); //keep clean for other animations
if (show)
elem.css(obscure).hide().slideDown({ complete: callback }).css(easyIn);
else
elem.css(easyOut).slideUp({ complete: callback });
return elem;
},
slideFadeIn: function(elem, callback) {
return dna.ui.slideFade(elem, callback, true);
},
slideFadeOut: function(elem, callback) {
return dna.ui.slideFade(elem, callback, false);
},
slideFadeToggle: function(elem, callback) {
return dna.ui.slideFade(elem, callback, elem.is(':hidden'));
},
slideFadeDelete: function(elem) {
return dna.ui.slideFadeOut(elem, dna.ui.deleteElem);
},
slidingFlasher: function(elem, callback) {
return elem.is(':hidden') ? dna.ui.slideFadeIn(elem, callback) : elem.hide().fadeIn();
}
};
dna.pageToken = {
// Page specific (url path) key/value temporary storage
put: function(key, value) {
// Example:
// dna.pageToken.put('favorite', 7); //saves 7
sessionStorage[key + window.location.pathname] = JSON.stringify(value);
return value;
},
get: function(key, defaultValue) {
// Example:
// dna.pageToken.get('favorite', 0); //returns 0 if not set
var value = sessionStorage[key + window.location.pathname];
return value === undefined ? defaultValue : JSON.parse(value);
}
};
dna.panels = {
// Each click of a menu item displays its corresponding panel and passes the panel
// element to the callback.
// Usage:
//
//
//
//
//
// Leave out the "data-hash" attribute to disable updating of the location bar.
key: function(menu) {
return '#' + menu.attr('id') + '-panels';
},
display: function(menu, loc) { //shows the panel at the given index (loc)
var panels, panel;
var key = dna.panels.key(menu);
if (loc === undefined)
loc = dna.pageToken.get(key, 0);
loc = Math.max(0, Math.min(loc, menu.children().length - 1));
menu.children().removeClass('selected').eq(loc).addClass('selected');
panels = $(key).children().hide().removeClass('displayed');
panel = panels.eq(loc).fadeIn().addClass('displayed');
function saveState() {
dna.pageToken.put(key, loc);
window.history.pushState(null, null, '#' + panel.data().hash);
}
if (panel.data().hash)
saveState();
dna.util.apply(menu.data().callback, panel);
},
rotate: function(event) { //moves to the selected panel
var item = $(event.target).closest('.menu-item');
dna.panels.display(item.parent(), item.index());
},
reload: function(name) { //refreshes the currently displayed panel
dna.panels.display($('#' + name));
},
init: function() {
var menu = $(this);
var key = dna.panels.key(menu);
var panels = $(key).children().addClass('panel');
var hash = window.location.hash.substring(1);
menu.children().addClass('menu-item');
function findPanelLoc() { return panels.filter('[data-hash=' + hash + ']').index(); }
function partOfTemplate(elems) { return elems.first().closest('.dna-template').length > 0; }
if (!partOfTemplate(panels) && !partOfTemplate(menu.children())) {
var loc = hash && panels.first().data().hash ? findPanelLoc() : dna.pageToken.get(key, 0);
dna.panels.display(menu, loc);
}
},
refresh: function() {
$('.dna-menu').each(dna.panels.init);
},
setup: function() {
dna.panels.refresh();
$(document).on('click', '.dna-menu >.menu-item', dna.panels.rotate);
}
};
$(dna.panels.setup);
dna.compile = {
// Pre-compile Example Post-compile class + data().dnaRules
// ----------- -------------------------------- ------------------------------------
// template class=dna-clone
// array
class=dna-nucleotide + array='tags'
// field
~~tag~~
class=dna-nucleotide + text='tag'
// attribute class=dna-nucleotide + attrs=['id', ['', 'num', '']]
// rule
class=dna-nucleotide + truthy='on'
// attr rule
class=dna-nucleotide + attrs=['src', ['', 'url', '']]
// prop rule class=dna-nucleotide + props=['checked', 'on']
// transform
class=dna-nucleotide + transform='app.enhance'
// callback
class=dna-nucleotide + callback='app.configure'
//
// Rules data().dnaRules
// ----------------------------------------- ---------------
// data-class=~~field,name-true,name-false~~ class=['field','name-true','name-false']
// data-attr-{NAME}=pre~~field~~post attrs=['{NAME}', ['pre', 'field', 'post']]
// data-prop-{NAME}=pre~~field~~post props=['{NAME}', 'field']
// data-require=~~field~~ require='field'
// data-missing=~~field~~ missing='field'
// data-truthy=~~field~~ truthy='field'
// data-falsey=~~field~~ falsey='field'
// data-transform=func transform='func'
// data-callback=func callback='func'
//
regexDnaField: /^[\s]*(~~|\{\{).*(~~|\}\})[\s]*$/, //example: ~~title~~
regexDnaBasePair: /~~|{{|}}/, //matches the '~~' string
regexDnaBasePairs: /~~|\{\{|\}\}/g, //matches the two '~~' strings so they can be removed
setupNucleotide: function(elem) {
if (elem.data().dnaRules === undefined)
elem.data().dnaRules = {};
return elem.addClass('dna-nucleotide');
},
isDnaField: function() {
var firstNode = $(this)[0].childNodes[0];
return firstNode && firstNode.nodeValue &&
firstNode.nodeValue.match(dna.compile.regexDnaField);
},
field: function() {
// Example:
//
~~name~~
==>
var elem = dna.compile.setupNucleotide($(this));
elem.data().dnaRules.text = $.trim(elem.text()).replace(dna.compile.regexDnaBasePairs, '');
return elem.empty();
},
propsAndAttrs: function() {
// Examples:
//