// dna.js v1.3.1
// MIT ~~ dnajs.org/license
// Copyright (c) 2013-2017 individual contributors to dna.js
var dna = {
// API:
// dna.clone()
// dna.cloneSub()
// 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.registerContext()
// dna.info()
// See: http://dnajs.org/docs/#api
clone: function(name, data, options) {
var defaults = {
fade: false,
top: false,
container: null,
empty: false,
clones: 1,
html: false,
transform: null,
callback: null
};
var settings = $.extend(defaults, 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 = [];
while (settings.clones--)
list = list.concat(data);
var clones = $();
function addClone(i, d) { clones = clones.add(dna.core.replicate(template, d, i, settings)); }
$.each(list, addClone);
dna.placeholder.setup(); //TODO: optimize
var first = clones.first();
first.closest('.dna-menu, .dna-panels').each(dna.panels.refresh);
first.parents('.dna-hide').removeClass('dna-hide').addClass('dna-unhide');
return clones;
},
cloneSub: 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(i, value) { array.push(value); }
$.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: { //experimental -- currently very limited function
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(i, elem) { model.push(dna.getModel($(elem))); }
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(i, elem) { dna.refresh($(elem)); }
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') :
$(window.document)].concat(settings.params));
return dna.events.initializers.push(
{ func: func, selector: settings.selector, params: settings.params });
},
clearInitializers: function() {
dna.events.initializers = [];
},
registerContext: function(contextName, contextObjectOrFunction) {
dna.events.context[contextName] = contextObjectOrFunction;
return dna.events.context;
},
info: function() {
var names = Object.keys(dna.store.templates);
function addToSum(sum, name) { return sum + dna.store.templates[name].clones; }
return {
version: '1.3.1',
templates: names.length,
clones: names.reduce(addToSum, 0),
names: names,
store: dna.store.templates,
initializers: dna.events.initializers
};
}
};
dna.cloneSubTemplate = dna.cloneSub; //DEPRECATED
dna.array = {
last: function(array) {
// Example:
// dna.array.last([3, 21, 7]) === 7;
return array && array.length ? array[array.length - 1] : undefined;
},
find: function(array, value, key) {
// Example:
// var array = [{ code: 'a', word: 'Ant' }, { code: 'b', word: 'Bat' }];
// dna.array.find(array, 'b').word === 'Bat';
key = key || 'code';
function found(obj) { return obj[key] === value; }
var objs = array.filter(found);
return objs.length ? objs[0] : null;
},
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 || 'code';
var map = {};
function addObj(obj) { map[obj[key]] = obj; }
array.forEach(addObj);
return map;
},
fromMap: function(map, key) {
// Converts an object (hash map) into an array of objects
// var map = { a: { word: 'Ant' }, b: { word: 'Bat' } };
// var array = dna.array.fromMap(map, 'code');
// ==> [{ code: 'a', word: 'Ant' }, { code: 'b', word: 'Bat' }]
key = key || 'code';
var array = [];
function toObj(item) { return item instanceof Object ? item : { value: item }; }
for (var property in map)
array[array.push(toObj(map[property])) - 1][key] = property;
return array;
}
};
dna.browser = {
iOS: function() {
return /iPad|iPhone|iPod/.test(window.navigator.userAgent) &&
/Apple/.test(window.navigator.vendor);
},
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) {
// Converts a kebab-case string (a code made of lowercase letters and dashes) to camelCase.
// Example:
// dna.util.toCamel('ready-set-go') === 'readySetGo'
function hump(match, char) { return char.toUpperCase(); }
return ('' + kebabStr).replace(/\-(.)/g, hump);
},
toKebab: function(camelStr) {
// Converts a camelCase string to kebab-case (a code made of lowercase letters and dashes).
// Example:
// dna.util.toKebab('readySetGo') === 'ready-set-go'
function dash(word) { return '-' + word.toLowerCase(); }
return ('' + camelStr).replace(/([A-Z]+)/g, dash).replace(/\s|^-/g, '');
},
value: function(data, field) {
// Returns the value of the field from the data object.
// 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 the "real" boolean truth of a value.
// Examples:
// true values ==> true, 7, '7', [5], 't', 'T', 'TRue', {}, 'Colbert'
// 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) {
// Builds a formatted string by replacing the format specifiers with the supplied arguments.
// 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(fn, params) {
// Calls fn (string name or actual function) passing in params.
// Usage:
// 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(context, names) {
if (!context || (names.length === 1 && typeof context[names[0]] !== 'function'))
dna.core.berserk('Callback function not found: ' + fn);
else if (names.length === 1)
result = context[names[0]].apply(elem, args); //'app.cart.buy' ==> window['app']['cart']['buy']
else
contextApply(context[names[0]], names.slice(1));
}
function findFn(names) {
if (elem instanceof $)
args.push(dna.ui.getComponent(elem));
contextApply(dna.events.context[names[0]] ? dna.events.context : window, names);
}
if (elem instanceof $ && elem.length === 0) //noop for emply list of elems
result = elem;
else if (typeof fn === 'function') //run regular function with supplied arguments
result = fn.apply(elem, args);
else if (elem && elem[fn]) //run element's jQuery function
result = elem[fn](args[1], args[2], args[3]);
else if (fn === '' || { number: true, boolean: true}[typeof fn])
dna.core.berserk('Invalid callback function: ' + fn);
else if (typeof fn === 'string' && fn.length > 0)
findFn(fn.split('.'));
return result;
}
};
dna.ui = {
toElem: function(elemOrEventOrIndex, that) {
return elemOrEventOrIndex instanceof $ ? elemOrEventOrIndex :
$(elemOrEventOrIndex ? elemOrEventOrIndex.target : that);
},
getComponent: function(elem) {
return elem.closest('[data-component]');
},
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();
},
smoothHeightSetBaseline: function(container) {
dna.ui.$container = container = container || $('body');
var height = container.outerHeight();
return container.css({ minHeight: height, maxHeight: height, overflow: 'hidden' });
},
smoothHeightAnimate: function(container) {
container = container || dna.ui.$container;
function animate() {
container.css({ minHeight: 0, maxHeight: '100vh' });
function turnOffTransition() { container.css({ transition: 'none' }); }
window.setTimeout(turnOffTransition, 1000); //allow 1s transition to finish
}
window.setTimeout(animate, 500); //allow container time to draw (default fadeIn() time + 100)
return container.css({ transition: 'all 1s' });
},
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();
return elem;
},
focus: function(elem) {
return elem.focus();
}
};
dna.placeholder = { //TODO: optimize
setup: function() {
$('option.dna-template').closest('select').addClass('dna-hide');
function fade(i, elem) {
var input = $(elem).stop();
return dna.getClones(input.data().placeholder).length ? input.fadeOut() : input.fadeIn();
}
$('[data-placeholder]').each(fade);
}
};
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 optionally passes the panel
// element and hash to the function specified by the "data-callback" attribute.
// Usage:
//
//
// The X1
// The X2
//
// The optional "data-hash" attribute specifies the hash (URL fragment ID) and updates the
// location bar.
display: function(menu, loc, updateUrl) { //shows the panel at the given index (loc)
var panels, panel;
var key = menu.data().dnaKey;
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));
menu[0].selectedIndex = loc; //case where menu is a drop-down elem (