// dna.js Semantic Templates ~~ v1.2.0
// MIT ~~ dnajs.org/license
// Copyright (c) 2013-2017 individual contributors
window.dna = {
// API:
// dna.clone()
// dna.cloneSubTemplate()
// dna.createTemplate()
// dna.rest.get()
// dna.getModel()
// dna.empty()
// dna.refresh()
// dna.refreshAll()
// dna.destroy()
// dna.getClone()
// dna.getClones()
// dna.getIndex()
// dna.up()
// dna.down()
// dna.bye()
// dna.registerInitializer()
// dna.clearInitializers()
// dna.info()
// See: http://dnajs.org/docs/#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));
dna.placeholder.setup(); //TODO: optimize
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);
},
rest: {
get: function(name, url, options) {
function processJson(data) {
if (!data.error)
dna.clone(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 $ ? 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) : dna.core.remove(clones);
},
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);
},
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) : dna.core.remove(clone);
},
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 $ ? 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);
},
up: function(elemOrEventOrIndex) {
return dna.ui.smoothMove(dna.getClone(dna.ui.toElem(elemOrEventOrIndex, this)), true);
},
down: function(elemOrEventOrIndex) {
return dna.ui.smoothMove(dna.getClone(dna.ui.toElem(elemOrEventOrIndex, this)), false);
},
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);
function addToSum(sum, name) { return sum + dna.store.templates[name].clones; }
return {
version: '1.2.0',
templates: names.length,
clones: names.reduce(addToSum, 0),
names: names,
store: dna.store.templates,
initializers: dna.events.initializers
};
}
};
dna.array = {
find: function(array, code) {
function found(obj) { return obj.code === code; }
var objs = array.filter(found);
return objs.length ? objs[0] : null;
},
last: function(array) {
// Example:
// dna.array.last([3, 21, 7]) === 7;
return array && array.length ? array[array.length - 1] : undefined;
},
toMap: function(array, key) {
// Converts an array of objects into an object (hash map)
// var array = [{ code: 'a', word: 'Ant' }, { code: 'b', word: 'Bat' }];
// var map = dna.array.toMap(array, 'code');
// ==> { a: { word: 'Ant' }, b: { word: 'Bat' } }
key = key ? key : 'code';
var map = {};
function addObj(obj) { map[obj[key]] = obj; }
array.forEach(addObj);
return map;
}
};
dna.browser = {
getParams: function() {
// Example:
// http://example.com?lang=jp&code=7 ==> { lang: 'jp', code: 7 }
var params = {};
function addParam(pair) { params[pair.split('=')[0]] = pair.split('=')[1]; }
window.location.search.slice(1).split('&').forEach(addParam);
return params;
}
};
dna.util = {
toCamel: function(kebabStr) { //example: 'ready-set-go' ==> 'readySetGo'
function hump(match, char) { return char.toUpperCase(); }
return ('' + kebabStr).replace(/\-(.)/g, hump);
},
toKebab: function(camelStr) { //example: 'readySetGo' ==> 'ready-set-go'
function dash(word) { return '-' + word.toLowerCase(); }
return ('' + camelStr).replace(/([A-Z]+)/g, dash).replace(/\s|^-/g, '');
},
toCode: function(camelStr) { return dna.util.toKebab(camelStr); }, //DEPRECATED
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;
},
printf: function(format) {
// Usage:
// dna.util.printf('%s: %s', 'Lives', 3) === 'Lives: 3';
var values = Array.prototype.slice.call(arguments, 1);
function insert(str, val) { return str.replace(/%s/, val); }
return values.reduce(insert, format);
},
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 = params === undefined ? [] : [].concat(params);
var elem = args[0];
var 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 $ && 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 $ ? elemOrEventOrIndex :
elemOrEventOrIndex ? $(elemOrEventOrIndex.target) : $(that);
},
deleteElem: function(elemOrEventOrIndex) { //example: $('.box').fadeOut(dna.ui.deleteElem);
var elem = dna.ui.toElem(elemOrEventOrIndex, this);
dna.core.remove(elem);
return elem;
},
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();
},
smoothMove: function(elem, up) {
function move() {
var ghostElem = submissiveElem.clone();
if (up)
elem.after(submissiveElem.hide()).before(ghostElem);
else
elem.before(submissiveElem.hide()).after(ghostElem);
dna.ui.slideFadeIn(submissiveElem);
dna.ui.slideFadeDelete(ghostElem);
}
var submissiveElem = up ? elem.prev() : elem.next();
if (submissiveElem.length)
move();
},
focus: function(elem) {
return elem.focus();
}
};
dna.placeholder = { //TODO: optimize
setup: function() {
function fade() {
var elem = $(this).stop();
return dna.getClones(elem.data().placeholder).length ? elem.fadeOut() : elem.fadeIn();
}
$('[data-placeholder]').each(fade);
}
};
$(dna.placeholder.setup);
dna.pageToken = {
// Page specific (url path) key/value temporary storage
put: function(key, value) {
// Example:
// dna.pageToken.put('favorite', 7); //saves 7
window.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 = window.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, updateUrl) { //shows the panel at the given index (loc)
var panels, panel;
var key = dna.panels.key(menu);
var menuItems = menu.find('.menu-item');
if (loc === undefined)
loc = dna.pageToken.get(key, 0);
loc = Math.max(0, Math.min(loc, menuItems.length - 1));
menuItems.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);
if (updateUrl && panel.data().hash)
window.history.pushState(null, null, '#' + 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');
var menu = item.closest('.dna-menu');
dna.panels.display(menu, menu.find('.menu-item').index(item), true);
},
reload: function(name) { //refreshes the currently displayed panel
dna.panels.display($('#' + name));
},
refresh: function() {
var hash = window.location.hash.slice(1);
function findPanelLoc(panels) { return panels.filter('[data-hash=' + hash + ']').index(); }
function partOfTemplate(elems) { return elems.first().closest('.dna-template').length > 0; }
function init() {
var menu = $(this);
var key = dna.panels.key(menu);
var panels = $(key).children().addClass('panel');
if (menu.find('.menu-item').length === 0)
menu.children().addClass('menu-item');
if (!partOfTemplate(panels) && !partOfTemplate(menu.children())) {
var loc = hash && panels.first().data().hash ?
findPanelLoc(panels) : dna.pageToken.get(key, 0);
dna.panels.display(menu, loc);
}
}
$('.dna-menu').each(init);
},
setup: function() {
dna.panels.refresh();
$(document).on({ click: dna.panels.rotate }, '.dna-menu .menu-item');
}
};
$(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']
// select rule