tat
Version:
Tiny custom tags
1,720 lines (1,584 loc) • 66.1 kB
JavaScript
/**
* _____ _ ____ _ _ _ ___ ___ ___
* |_ _|_ _| |_ / ___| (_) ___ _ __ | |_ __ __/ _ \ / _ \ / _ \
* | |/ _` | __| | | | | |/ _ \ '_ \| __| \ \ / / | | | | | | | | |
* | | (_| | |_ | |___| | | __/ | | | |_ \ V /| |_| | |_| | |_| |
* |_|\__,_|\__| \____|_|_|\___|_| |_|\__| \_/ \___(_)___(_)___/
*
*
* http://lighter.io/tat
* MIT License
*
* Source files:
* https://github.com/lighterio/tat/blob/master/scripts/tat-jymin.js
* https://github.com/lighterio/jymin/blob/master/scripts/ajax.js
* https://github.com/lighterio/jymin/blob/master/scripts/arrays.js
* https://github.com/lighterio/jymin/blob/master/scripts/cookies.js
* https://github.com/lighterio/jymin/blob/master/scripts/crypto.js
* https://github.com/lighterio/jymin/blob/master/scripts/dates.js
* https://github.com/lighterio/jymin/blob/master/scripts/dom.js
* https://github.com/lighterio/jymin/blob/master/scripts/emitter.js
* https://github.com/lighterio/jymin/blob/master/scripts/events.js
* https://github.com/lighterio/jymin/blob/master/scripts/forms.js
* https://github.com/lighterio/jymin/blob/master/scripts/functions.js
* https://github.com/lighterio/jymin/blob/master/scripts/history.js
* https://github.com/lighterio/jymin/blob/master/scripts/i18n.js
* https://github.com/lighterio/jymin/blob/master/scripts/json.js
* https://github.com/lighterio/jymin/blob/master/scripts/logging.js
* https://github.com/lighterio/jymin/blob/master/scripts/move.js
* https://github.com/lighterio/jymin/blob/master/scripts/numbers.js
* https://github.com/lighterio/jymin/blob/master/scripts/objects.js
* https://github.com/lighterio/jymin/blob/master/scripts/ready.js
* https://github.com/lighterio/jymin/blob/master/scripts/regexp.js
* https://github.com/lighterio/jymin/blob/master/scripts/storage.js
* https://github.com/lighterio/jymin/blob/master/scripts/strings.js
* https://github.com/lighterio/jymin/blob/master/scripts/timing.js
* https://github.com/lighterio/jymin/blob/master/scripts/types.js
* https://github.com/lighterio/jymin/blob/master/scripts/url.js
*/
/**
* This file is used in conjunction with Jymin to form the Tat client.
*
* If you're already using Jymin, you can use this file with it.
* Otherwise use ../d6-client.js which includes required Jymin functions.
*/
/**
* Tat is a function that accepts new tags.
*/
var Tat = window.Tat = function (newTags) {
var tags = Tat._tags = Tat._tags || {};
var tagNames = [];
Jymin.forIn(newTags, function (name, tag) {
tags[name] = tag;
tagNames = [];
});
var selector = tagNames.join(name);
Jymin.all(selector, function (element) {
});
if (!Tat.isReady) {
Tat._isReady = true;
Jymin.trigger(Tat, 'ready');
}
};
/**
* Empty handler.
* @type {function}
*/
Jymin.doNothing = function () {};
/**
* Default AJAX success handler function.
* @type {function}
*/
Jymin.responseSuccessFn = Jymin.doNothing;
/**
* Default AJAX failure handler function.
* @type {function}
*/
Jymin.responseFailureFn = Jymin.doNothing;
/**
* Name of the XMLHttpRequest object.
* @type {String}
*/
Jymin.XHR = 'XMLHttpRequest';
/**
* Get an XMLHttpRequest object (or ActiveX object in old IE).
*
* @return {XMLHttpRequest} The request object.
*/
Jymin.getXhr = function () {
var xhr;
//+browser:old
xhr = window.XMLHttpRequest ? new XMLHttpRequest() :
window.ActiveXObject ? new ActiveXObject('Microsoft.XMLHTTP') : // jshint ignore:line
false;
//-browser:old
//+browser:ok
xhr = new XMLHttpRequest();
//-browser:ok
return xhr;
};
/**
* Get an XMLHttpRequest upload object.
*
* @return {XMLHttpRequestUpload} The request upload object.
*/
Jymin.getUpload = function () {
var xhr = Jymin.getXhr();
return xhr ? xhr.upload : false;
};
/**
* Make an AJAX request, and handle it with success or failure.
*
* @param {string} url A URL from which to request a response.
* @param {string} body An optional query, which if provided, makes the request a POST.
* @param {function} onSuccess An optional function to run upon success.
* @param {function} onFailure An optional function to run upon failure.
* @return {boolean} True if AJAX is supported.
*/
Jymin.getResponse = function (url, body, onSuccess, onFailure) {
// If the optional body argument is omitted, shuffle it out.
if (Jymin.isFunction(body)) {
onFailure = onSuccess;
onSuccess = body;
body = 0;
}
var request = Jymin.getXhr();
if (request) {
onFailure = onFailure || Jymin.responseFailureFn;
onSuccess = onSuccess || Jymin.responseSuccessFn;
Jymin.bindReady(request, function () {
//+env:debug
Jymin.log('[Jymin] Received response from "' + url + '". (' + Jymin.getResponse._waiting + ' in progress).');
--Jymin.getResponse._waiting;
//-env:debug
var status = request.status;
var isSuccess = (status == 200);
var fn = isSuccess ?
onSuccess || Jymin.responseSuccessFn :
onFailure || Jymin.responseFailureFn;
var data = Jymin.parse(request.responseText) || {};
fn(data, request, status);
});
request.open(body ? 'POST' : 'GET', url, true);
if (body) {
request.setRequestHeader('content-type', 'application/x-www-form-urlencoded');
}
//+env:debug
// Record the original request URL.
request._url = url;
// If it's a post, record the post body.
if (body) {
request._body = body;
}
// Record the time the request was made.
request._time = Jymin.getTime();
// Allow applications to back off when too many requests are in progress.
Jymin.getResponse._waiting = (Jymin.getResponse._waiting || 0) + 1;
Jymin.log('[Jymin] Sending request to "' + url + '". (' + Jymin.getResponse._waiting + ' in progress).');
//-env:debug
request.send(body || null);
}
return true;
};
/**
* Iterate over an array-like collection, and call a function on each value, with
* the arguments: (value, index, array). Iteration stops if the function returns false.
*
* @param {Array|Object|string} array A collection, expected to have indexed items and a length.
* @param {Function} fn A function to call on each item.
* @return {Number} The number of items iterated over without breaking.
*/
Jymin.forEach = function (array, fn) {
if (array) {
array = Jymin.isString(array) ? Jymin.splitByCommas(array) : array;
for (var index = 0, length = Jymin.getLength(array); index < length; index++) {
var result = fn(array[index], index, array);
if (result === false) {
break;
}
}
return index;
}
};
/**
* Iterate over an array-like collection, and call a function on each value, with
* the arguments: (index, value, array). Iteration stops if the function returns false.
*
* @param {Array|Object|string} array A collection, expected to have indexed items and a length.
* @param {Function} fn A function to call on each item.
* @return {Number} The number of items iterated over without breaking.
*/
Jymin.each = function (array, fn) {
if (array) {
array = Jymin.isString(array) ? Jymin.splitByCommas(array) : array;
for (var index = 0, length = Jymin.getLength(array); index < length; index++) {
var result = fn(index, array[index], array);
if (result === false) {
break;
}
}
return index;
}
};
/**
* Get the length of an Array/Object/string/etc.
*
* @param {Array|Object|string} array A collection, expected to have a length.
* @return {Number} The length of the collection.
*/
Jymin.getLength = function (array) {
return (array || 0).length || 0;
};
/**
* Get the first item in an Array/Object/string/etc.
* @param {Array|Object|string} array A collection, expected to have index items.
* @return {Object} The first item in the collection.
*/
Jymin.getFirst = function (array) {
return (array || 0)[0];
};
/**
* Get the last item in an Array/Object/string/etc.
*
* @param {Array|Object|string} array A collection, expected to have indexed items and a length.
* @return {Object} The last item in the collection.
*/
Jymin.getLast = function (array) {
return (array || 0)[Jymin.getLength(array) - 1];
};
/**
* Check for the existence of more than one collection items.
*
* @param {Array|Object|string} array A collection, expected to have a length.
* @return {boolean} True if the collection has more than one item.
*/
Jymin.hasMany = function (array) {
return Jymin.getLength(array) > 1;
};
/**
* Push an item into an array.
*
* @param {Array} array An array to push an item into.
* @param {Object} item An item to push.
* @return {Object} The item that was pushed.
*/
Jymin.push = function (array, item) {
if (Jymin.isArray(array)) {
array.push(item);
}
return item;
};
/**
* Pop an item off an array.
*
* @param {Array} array An array to pop an item from.
* @return {Object} The item that was popped.
*/
Jymin.pop = function (array) {
if (Jymin.isArray(array)) {
return array.pop();
}
};
/**
* Merge one or more arrays into an array.
*
* @param {Array} array An array to merge into.
* @params {Array...} Items to merge into the array.
* @return {Array} The first array argument, with new items merged in.
*/
Jymin.merge = function (array) {
Jymin.forEach(arguments, function (items, index) {
if (index) {
Jymin.forEach(items, function (item) {
Jymin.push(array, item);
});
}
});
return array;
};
/**
* Push padding values onto an array up to a specified length.
*
* @return number:
* @param {Array} array An array to pad.
* @param {Number} padToLength A desired length for the array, after padding.
* @param {Object} paddingValue A value to use as padding.
* @return {Number} The number of padding values that were added.
*/
Jymin.padArray = function (array, padToLength, paddingValue) {
var countAdded = 0;
if (Jymin.isArray(array)) {
var startingLength = Jymin.getLength(array);
if (startingLength < length) {
paddingValue = Jymin.isUndefined(paddingValue) ? '' : paddingValue;
for (var index = startingLength; index < length; index++) {
Jymin.push(array, paddingValue);
countAdded++;
}
}
}
return countAdded;
};
/**
* Get all cookies from the document, and return a map.
*
* @return {Object} The map of cookie names and values.
*/
Jymin.getAllCookies = function () {
var obj = {};
var documentCookie = Jymin.trim(document.cookie);
if (documentCookie) {
var cookies = documentCookie.split(/\s*;\s*/);
Jymin.forEach(cookies, function (cookie) {
var pair = cookie.split(/\s*=\s*/);
obj[Jymin.unescape(pair[0])] = Jymin.unescape(pair[1]);
});
}
return obj;
};
/**
* Get a cookie by its name.
*
* @param {String} name A cookie name.
* @return {String} The cookie value.
*/
Jymin.getCookie = function (name) {
return Jymin.getAllCookies()[name];
};
/**
* Set or overwrite a cookie value.
*
* @param {String} name A cookie name, whose value is to be set.
* @param {Object} value A value to be set as a string.
* @param {Object} options Optional cookie options, including "maxage", "expires", "path", "domain" and "secure".
*/
Jymin.setCookie = function (name, value, options) {
options = options || {};
var str = Jymin.escape(name) + '=' + Jymin.unescape(value);
if (null === value) {
options.maxage = -1;
}
if (options.maxage) {
options.expires = new Date(+new Date() + options.maxage);
}
document.cookie = str +
(options.path ? ';path=' + options.path : '') +
(options.domain ? ';domain=' + options.domain : '') +
(options.expires ? ';expires=' + options.expires.toUTCString() : '') +
(options.secure ? ';secure' : '');
};
/**
* Delete a cookie by name.
*
* @param {String} name A cookie name, whose value is to be deleted.
*/
Jymin.deleteCookie = function (name) {
Jymin.setCookie(name, null);
};
/**
* Calculate an MD5 hash for a string (useful for things like Gravatars).
*
* @param {String} s A string to hash.
* @return {String} The MD5 hash for the given string.
*/
Jymin.md5 = function (str) {
// Encode as UTF-8.
str = decodeURIComponent(encodeURIComponent(str));
// Build an array of little-endian words.
var arr = new Array(str.length >> 2);
for (var idx = 0, len = arr.length; idx < len; idx += 1) {
arr[idx] = 0;
}
for (idx = 0, len = str.length * 8; idx < len; idx += 8) {
arr[idx >> 5] |= (str.charCodeAt(idx / 8) & 0xFF) << (idx % 32);
}
// Calculate the MD5 of an array of little-endian words.
arr[len >> 5] |= 0x80 << (len % 32);
arr[(((len + 64) >>> 9) << 4) + 14] = len;
var a = 1732584193;
var b = -271733879;
var c = -1732584194;
var d = 271733878;
len = arr.length;
idx = 0;
while (idx < len) {
var olda = a;
var oldb = b;
var oldc = c;
var oldd = d;
var e = arr[idx++];
var f = arr[idx++];
var g = arr[idx++];
var h = arr[idx++];
var i = arr[idx++];
var j = arr[idx++];
var k = arr[idx++];
var l = arr[idx++];
var m = arr[idx++];
var n = arr[idx++];
var o = arr[idx++];
var p = arr[idx++];
var q = arr[idx++];
var r = arr[idx++];
var s = arr[idx++];
var t = arr[idx++];
a = ff(a, b, c, d, e, 7, -680876936);
d = ff(d, a, b, c, f, 12, -389564586);
c = ff(c, d, a, b, g, 17, 606105819);
b = ff(b, c, d, a, h, 22, -1044525330);
a = ff(a, b, c, d, i, 7, -176418897);
d = ff(d, a, b, c, j, 12, 1200080426);
c = ff(c, d, a, b, k, 17, -1473231341);
b = ff(b, c, d, a, l, 22, -45705983);
a = ff(a, b, c, d, m, 7, 1770035416);
d = ff(d, a, b, c, n, 12, -1958414417);
c = ff(c, d, a, b, o, 17, -42063);
b = ff(b, c, d, a, p, 22, -1990404162);
a = ff(a, b, c, d, q, 7, 1804603682);
d = ff(d, a, b, c, r, 12, -40341101);
c = ff(c, d, a, b, s, 17, -1502002290);
b = ff(b, c, d, a, t, 22, 1236535329);
a = gg(a, b, c, d, f, 5, -165796510);
d = gg(d, a, b, c, k, 9, -1069501632);
c = gg(c, d, a, b, p, 14, 643717713);
b = gg(b, c, d, a, e, 20, -373897302);
a = gg(a, b, c, d, j, 5, -701558691);
d = gg(d, a, b, c, o, 9, 38016083);
c = gg(c, d, a, b, t, 14, -660478335);
b = gg(b, c, d, a, i, 20, -405537848);
a = gg(a, b, c, d, n, 5, 568446438);
d = gg(d, a, b, c, s, 9, -1019803690);
c = gg(c, d, a, b, h, 14, -187363961);
b = gg(b, c, d, a, m, 20, 1163531501);
a = gg(a, b, c, d, r, 5, -1444681467);
d = gg(d, a, b, c, g, 9, -51403784);
c = gg(c, d, a, b, l, 14, 1735328473);
b = gg(b, c, d, a, q, 20, -1926607734);
a = hh(a, b, c, d, j, 4, -378558);
d = hh(d, a, b, c, m, 11, -2022574463);
c = hh(c, d, a, b, p, 16, 1839030562);
b = hh(b, c, d, a, s, 23, -35309556);
a = hh(a, b, c, d, f, 4, -1530992060);
d = hh(d, a, b, c, i, 11, 1272893353);
c = hh(c, d, a, b, l, 16, -155497632);
b = hh(b, c, d, a, o, 23, -1094730640);
a = hh(a, b, c, d, r, 4, 681279174);
d = hh(d, a, b, c, e, 11, -358537222);
c = hh(c, d, a, b, h, 16, -722521979);
b = hh(b, c, d, a, k, 23, 76029189);
a = hh(a, b, c, d, n, 4, -640364487);
d = hh(d, a, b, c, q, 11, -421815835);
c = hh(c, d, a, b, t, 16, 530742520);
b = hh(b, c, d, a, g, 23, -995338651);
a = ii(a, b, c, d, e, 6, -198630844);
d = ii(d, a, b, c, l, 10, 1126891415);
c = ii(c, d, a, b, s, 15, -1416354905);
b = ii(b, c, d, a, j, 21, -57434055);
a = ii(a, b, c, d, q, 6, 1700485571);
d = ii(d, a, b, c, h, 10, -1894986606);
c = ii(c, d, a, b, o, 15, -1051523);
b = ii(b, c, d, a, f, 21, -2054922799);
a = ii(a, b, c, d, m, 6, 1873313359);
d = ii(d, a, b, c, t, 10, -30611744);
c = ii(c, d, a, b, k, 15, -1560198380);
b = ii(b, c, d, a, r, 21, 1309151649);
a = ii(a, b, c, d, i, 6, -145523070);
d = ii(d, a, b, c, p, 10, -1120210379);
c = ii(c, d, a, b, g, 15, 718787259);
b = ii(b, c, d, a, n, 21, -343485551);
a = add(a, olda);
b = add(b, oldb);
c = add(c, oldc);
d = add(d, oldd);
}
arr = [a, b, c, d];
// Build a string.
var hex = '0123456789abcdef';
str = '';
for (idx = 0, len = arr.length * 32; idx < len; idx += 8) {
var code = (arr[idx >> 5] >>> (idx % 32)) & 0xFF;
str += hex.charAt((code >>> 4) & 0x0F) + hex.charAt(code & 0x0F);
}
return str;
/**
* Add 32-bit integers, using 16-bit operations to mitigate JS interpreter bugs.
*/
function add(a, b) {
var lsw = (a & 0xFFFF) + (b & 0xFFFF);
var msw = (a >> 16) + (b >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
}
function cmn(q, a, b, x, s, t) {
a = add(add(a, q), add(x, t));
return add((a << s) | (a >>> (32 - s)), b);
}
function ff(a, b, c, d, x, s, t) {
return cmn((b & c) | ((~b) & d), a, b, x, s, t);
}
function gg(a, b, c, d, x, s, t) {
return cmn((b & d) | (c & (~d)), a, b, x, s, t);
}
function hh(a, b, c, d, x, s, t) {
return cmn(b ^ c ^ d, a, b, x, s, t);
}
function ii(a, b, c, d, x, s, t) {
return cmn(c ^ (b | (~d)), a, b, x, s, t);
}
};
/**
* Get Unix epoch milliseconds from a date.
*
* @param {Date} date An optional Date object (default: now).
* @return {Number} Epoch milliseconds.
*/
Jymin.getTime = function (date) {
return date ? date.getTime() : Date.now();
};
/**
* Get an ISO-standard date string.
*
* @param {Date} date Date object (default: now).
* @return {String} ISO date string.
*/
Jymin.getIsoDate = function (date) {
date = date || new Date();
//+browser:ok
date = date.toISOString();
//-browser:ok
//+browser:old
var utcPattern = /^.*?(\d+) (\w+) (\d+) ([\d:]+).*?$/;
date = date.toUTCString().replace(utcPattern, function (a, day, m, y, t) {
m = Jymin.zeroFill(date.getMonth(), 2);
t += '.' + Jymin.zeroFill(date.getMilliseconds(), 3);
return y + '-' + m + '-' + day + 'T' + t + 'Z';
});
//-browser:old
return date;
};
/**
* Take a date and return a formatted date string in long or short format:
* - Short: "8/26/14 7:42pm"
* - Long: "August 26, 2014 at 7:42pm"
*
* @param {Object} date An optional Date object or constructor argument.
* @param {Boolean} isLong Whether to output the short or long format.
* @param {Boolean} isTime Whether to append the time.
* @return {String} The formatted date string.
*/
Jymin.formatDate = function (date, isLong, isTime) {
if (!Jymin.isDate(date)) {
date = new Date(+date || date);
}
var m = date.getMonth();
var day = date.getDate();
var y = date.getFullYear();
if (isLong) {
m = Jymin.i18nMonths[m];
}
else {
m++;
y = ('' + y).substr(2);
}
var isAm = 1;
var hour = +date.getHours();
var minute = date.getMinutes();
minute = minute > 9 ? minute : '0' + minute;
if (!Jymin.i18n24Hour) {
if (hour > 12) {
isAm = 0;
hour -= 12;
}
else if (!hour) {
hour = 12;
}
}
var string;
if (Jymin.i18nDayMonthYear) {
string = m;
m = day;
day = string;
}
if (isLong) {
string = m + ' ' + day + ', ' + y;
}
else {
string = m + '/' + day + '/' + y;
}
if (isTime) {
if (isLong) {
string += ' ' + Jymin.i18nAt;
}
string += ' ' + hour + ':' + minute;
if (Jymin.i18n24Hour) {
string += (isAm ? 'am' : 'pm');
}
}
return string;
};
/**
* Taka a date object and return a formatted time string.
*
* @param {Object} date An optional Date object or constructor argument.
* @return {[type]}
*/
Jymin.formatTime = function (date) {
date = Jymin.formatDate(date).replace(/^.* /, '');
};
/**
* Get an element by its ID (if the argument is an ID).
* If you pass in an element, it just returns it.
* This can be used to ensure that you have an element.
*
* @param {HTMLElement} parentElement Optional element to call getElementById on (default: document).
* @param {string|HTMLElement} idOrElement ID of an element, or the element itself.
* @return {HTMLElement} The matching element, or undefined.
*/
Jymin.getElement = function (parentElement, idOrElement) {
if (!Jymin.hasMany(arguments)) {
idOrElement = parentElement;
parentElement = document;
}
return Jymin.isString(idOrElement) ? parentElement.getElementById(idOrElement) : idOrElement;
};
/**
* Get the parent of an element, or an ancestor with a specified tag name.
*
* @param {HTMLElement} element A element whose parent elements are being searched.
* @param {String} selector An optional selector to search up the tree.
* @return {HTMLElement} The parent or matching ancestor.
*/
Jymin.getParent = function (element, selector) {
return Jymin.getTrail(element, selector)[1];
};
/**
* Get the trail that leads back to the root, optionally filtered by a selector.
*
* @param {HTMLElement} element An element to start the trail.
* @param {String} selector An optional selector to filter the trail.
* @return {Array} The array of elements in the trail.
*/
Jymin.getTrail = function (element, selector) {
var trail = [element];
while (element = element.parentNode) { // jshint ignore:line
Jymin.push(trail, element);
}
if (selector) {
var set = trail;
trail = [];
Jymin.all(selector, function (element) {
if (set.indexOf(element) > -1) {
Jymin.push(trail, element);
}
});
}
return trail;
};
/**
* Get the children of a parent element.
*
* @param {HTMLElement} element A parent element who might have children.
* @return {HTMLCollection} The collection of children.
*/
Jymin.getChildren = function (element) {
return element.childNodes;
};
/**
* Get an element's index with respect to its parent.
*
* @param {HTMLElement} element An element with a parent, and potentially siblings.
* @return {Number} The element's index, or -1 if there's no matching element.
*/
Jymin.getIndex = function (element) {
var index = -1;
while (element) {
++index;
element = element.previousSibling;
}
return index;
};
/**
* Get an element's first child.
*
* @param {HTMLElement} element An element.
* @return {[type]} The element's first child.
*/
Jymin.getFirstChild = function (element) {
return element.firstChild;
};
/**
* Get an element's previous sibling.
*
* @param {HTMLElement} element An element.
* @return {HTMLElement} The element's previous sibling.
*/
Jymin.getPreviousSibling = function (element) {
return element.previousSibling;
};
/**
* Get an element's next sibling.
*
* @param {HTMLElement} element An element.
* @return {HTMLElement} The element's next sibling.
*/
Jymin.getNextSibling = function (element) {
return element.nextSibling;
};
/**
* Create a cloneable element with a specified tag name.
*
* @param {String} tagName An optional tag name (default: div).
* @return {HTMLElement} The newly-created DOM Element with the specified tag name.
*/
Jymin.createTag = function (tagName) {
tagName = tagName || 'div';
var isSvg = /^(svg|g|path|circle|line)$/.test(tagName);
var uri = 'http://www.w3.org/' + (isSvg ? '2000/svg' : '1999/xhtml');
return document.createElementNS(uri, tagName);
};
/**
* Create an element, given a specified tag identifier.
*
* Identifiers are of the form:
* tagName#id.class1.class2?attr1=value1&attr2=value2
*
* Each part of the identifier is optional.
*
* @param {HTMLElement|String} elementOrString An element or a string used to create an element (default: div).
* @param {String} innerHtml An optional string of HTML to populate the element.
* @return {HTMLElement} The existing or created element.
*/
Jymin.createElement = function (elementOrString, innerHtml) {
var element = elementOrString;
if (Jymin.isString(elementOrString)) {
var tagAndAttributes = elementOrString.split('?');
var tagAndClass = tagAndAttributes[0].split('.');
var className = tagAndClass.slice(1).join(' ');
var tagAndId = tagAndClass[0].split('#');
var tagName = tagAndId[0];
var id = tagAndId[1];
var attributes = tagAndAttributes[1];
var cachedElement = Jymin.createTag[tagName] || (Jymin.createTag[tagName] = Jymin.createTag(tagName));
element = cachedElement.cloneNode(true);
if (id) {
element.id = id;
}
if (className) {
element.className = className;
}
// TODO: Do something less janky than using query string syntax (Maybe like Ltl?).
if (attributes) {
attributes = attributes.split('&');
Jymin.forEach(attributes, function (attribute) {
var keyAndValue = attribute.split('=');
var key = Jymin.unescape(keyAndValue[0]);
var value = Jymin.unescape(keyAndValue[1]);
element[key] = value;
element.setAttribute(key, value);
});
}
if (innerHtml) {
Jymin.setHtml(element, innerHtml);
}
}
return element;
};
/**
* Add an element to a parent element, creating it first if necessary.
*
* @param {HTMLElement} parentElement An optional parent element (default: document).
* @param {HTMLElement|String} elementOrString An element or a string used to create an element (default: div).
* @param {String} innerHtml An optional string of HTML to populate the element.
* @return {HTMLElement} The element that was added.
*/
Jymin.addElement = function (parentElement, elementOrString, innerHtml) {
if (Jymin.isString(parentElement)) {
elementOrString = parentElement;
parentElement = document;
}
var element = Jymin.createElement(elementOrString, innerHtml);
parentElement.appendChild(element);
return element;
};
/**
* Insert a child element under a parent element, optionally before another element.
*
* @param {HTMLElement} parentElement An optional parent element (default: document).
* @param {HTMLElement|String} elementOrString An element or a string used to create an element (default: div).
* @param {HTMLElement} beforeSibling An optional child to insert the element before.
* @return {HTMLElement} The element that was inserted.
*/
Jymin.insertElement = function (parentElement, elementOrString, beforeSibling) {
if (Jymin.isString(parentElement)) {
beforeSibling = elementOrString;
elementOrString = parentElement;
parentElement = document;
}
var element = Jymin.createElement(elementOrString);
if (parentElement) {
// If the beforeSibling value is a number, get the (future) sibling at that index.
if (Jymin.isNumber(beforeSibling)) {
beforeSibling = Jymin.getChildren(parentElement)[beforeSibling];
}
// Insert the element, optionally before an existing sibling.
parentElement.insertBefore(element, beforeSibling || Jymin.getFirstChild(parentElement) || null);
}
return element;
};
/**
* Wrap an element with another element.
*
* @param {HTMLElement} innerElement An element to wrap with another element.
* @param {HTMLElement|String} outerElement An element or a string used to create an element (default: div).
* @return {HTMLElement} The element that was created as a wrapper.
*/
Jymin.wrapElement = function (innerElement, outerElement) {
var parentElement = Jymin.getParent(innerElement);
outerElement = Jymin.insertElement(parentElement, outerElement, innerElement);
Jymin.insertElement(outerElement, innerElement);
return outerElement;
};
/**
* Remove an element from its parent.
*
* @param {HTMLElement} element An element to remove.
*/
Jymin.removeElement = function (element) {
if (element) {
// Remove the element from its parent, provided that it has a parent.
var parentElement = Jymin.getParent(element);
if (parentElement) {
parentElement.removeChild(element);
}
}
};
/**
* Remove children from an element.
*
* @param {HTMLElement} element An element whose children should all be removed.
*/
Jymin.clearElement = function (element) {
Jymin.setHtml(element, '');
};
/**
* Get an element's inner HTML.
*
* @param {HTMLElement} element An element.
* @return {String} The element's HTML.
*/
Jymin.getHtml = function (element) {
return element.innerHTML;
};
/**
* Set an element's inner HTML.
*
* @param {HTMLElement} element An element.
* @param {String} html A string of HTML to set as the innerHTML.
*/
Jymin.setHtml = function (element, html) {
element.innerHTML = html;
};
/**
* Get an element's lowercase tag name.
*
* @param {HTMLElement} element An element.
* @return {String} The element's tag name.
*/
Jymin.getTag = function (element) {
return Jymin.lower(element.tagName);
};
/**
* Get an element's text.
*
* @param {HTMLElement} element An element.
* @return {String} The element's text content.
*/
Jymin.getText = function (element) {
return element.textContent || element.innerText;
};
/**
* Get an attribute from an element.
*
* @param {HTMLElement} element An element.
* @param {String} attributeName An attribute's name.
* @return {String} The value of the attribute.
*/
Jymin.getAttribute = function (element, attributeName) {
return element.getAttribute(attributeName);
};
/**
* Set an attribute on an element.
*
* @param {HTMLElement} element An element.
* @param {String} attributeName An attribute name.
* @param {String} value A value to set the attribute to.
*/
Jymin.setAttribute = function (element, attributeName, value) {
element.setAttribute(attributeName, value);
};
/**
* Get a data attribute from an element.
*
* @param {HTMLElement} element An element.
* @param {String} dataKey A data attribute's key.
* @return {String} The value of the data attribute.
*/
Jymin.getData = function (element, dataKey) {
return Jymin.getAttribute(element, 'data-' + dataKey);
};
/**
* Set a data attribute on an element.
*
* @param {HTMLElement} element An element.
* @param {String} dataKey A data attribute key.
* @param {String} value A value to set the data attribute to.
*/
Jymin.setData = function (element, dataKey, value) {
Jymin.setAttribute(element, 'data-' + dataKey, value);
};
/**
* Get an element's class name.
*
* @param {HTMLElement} element An element.
* @return {String} The element's class name.
*/
Jymin.getClass = function (element) {
var className = element.className || '';
return className.baseVal || className;
};
/**
* Get an element's class name as an array of classes.
*
* @param {HTMLElement} element An element.
* @return {Array} The element's class name classes.
*/
Jymin.getClasses = function (element) {
return Jymin.getClass(element).split(/\s+/);
};
/**
* Set an element's class name.
*
* @param {HTMLElement} element An element.
* @return {String} One or more space-delimited classes to set.
*/
Jymin.setClass = function (element, className) {
element.className = className;
};
/**
* Find out whether an element has a specified class.
*
* @param {HTMLElement} element An element.
* @param {String} className A class to search for.
* @return {boolean} True if the class was found.
*/
Jymin.hasClass = function (element, className) {
var classes = Jymin.getClasses(element);
return classes.indexOf(className) > -1;
};
/**
* Add a class to a given element.
*
* @param {HTMLElement} element An element.
* @param {String} A class to add if it's not already there.
*/
Jymin.addClass = function (element, className) {
if (!Jymin.hasClass(element, className)) {
element.className += ' ' + className;
}
};
/**
* Remove a class from a given element, assuming no duplication.
*
* @param {HTMLElement} element An element.
* @return {String} A class to remove.
*/
Jymin.removeClass = function (element, className) {
var classes = Jymin.getClasses(element);
var index = classes.indexOf(className);
if (index > -1) {
classes.splice(index, 1);
}
classes.join(' ');
Jymin.setClass(element, classes);
};
/**
* Turn a class on or off on a given element.
*
* @param {HTMLElement} element An element.
* @param {String} className A class to add or remove.
* @param {boolean} flipOn Whether to add, rather than removing.
*/
Jymin.flipClass = function (element, className, flipOn) {
var method = flipOn ? Jymin.addClass : Jymin.removeClass;
method(element, className);
};
/**
* Turn a class on if it's off, or off if it's on.
*
* @param {HTMLElement} element An element.
* @param {String} className A class to toggle.
* @return {boolean} True if the class was turned on.
*/
Jymin.toggleClass = function (element, className) {
var flipOn = !Jymin.hasClass(element, className);
Jymin.flipClass(element, className, flipOn);
return flipOn;
};
/**
* Find elements matching a selector, and return or run a function on them.
*
* Selectors are not fully querySelector compatible.
* Selectors only support commas, spaces, IDs, tags & classes.
*
* @param {HTMLElement} parentElement An optional element under which to find elements.
* @param {String} selector A simple selector for finding elements.
* @param {Function} fn An optional function to run on matching elements.
* @return {HTMLCollection} The matching elements (if any).
*/
Jymin.all = function (parentElement, selector, fn) {
if (!selector || Jymin.isFunction(selector)) {
fn = selector;
selector = parentElement;
parentElement = document;
}
var elements;
//+browser:old
elements = [];
if (Jymin.contains(selector, ',')) {
Jymin.forEach(selector, function (selector) {
Jymin.all(parentElement, selector, function (element) {
Jymin.push(elements, element);
});
});
}
else if (Jymin.contains(selector, ' ')) {
var pos = selector.indexOf(' ');
var preSelector = selector.substr(0, pos);
var postSelector = selector.substr(pos + 1);
elements = [];
Jymin.all(parentElement, preSelector, function (element) {
var children = Jymin.all(element, postSelector);
Jymin.merge(elements, children);
});
}
else if (selector[0] == '#') {
var id = selector.substr(1);
var child = Jymin.getElement(parentElement.ownerDocument || document, id);
if (child) {
var parent = Jymin.getParent(child);
while (parent) {
if (parent === parentElement) {
elements = [child];
break;
}
parent = Jymin.getParent(parent);
}
}
}
else {
selector = selector.split('.');
var tagName = selector[0];
var className = selector[1];
var tagElements = parentElement.getElementsByTagName(tagName);
Jymin.forEach(tagElements, function (element) {
if (!className || Jymin.hasClass(element, className)) {
Jymin.push(elements, element);
}
});
}
//-browser:old
//+browser:ok
elements = parentElement.querySelectorAll(selector);
//-browser:ok
if (fn) {
Jymin.forEach(elements, fn);
}
return elements;
};
/**
* Find an element matching a selector, optionally run a function on it, and return it.
*
* @param {HTMLElement} parentElement An optional element under which to find an element.
* @param {String} selector A simple selector for finding an element.
* @param {Function} fn An optional function to run on a matching element.
* @return {HTMLElement} The matching element (if any).
*/
Jymin.one = function (parentElement, selector, fn) {
if (!selector || Jymin.isFunction(selector)) {
fn = selector;
selector = parentElement;
parentElement = document;
}
var element;
//+browser:old
element = Jymin.all(parentElement, selector)[0];
//-browser:old
//+browser:ok
element = parentElement.querySelector(selector);
//-browser:ok
if (element && fn) {
fn(element);
}
return element;
};
/**
* Push new HTML into one or more selected elements.
*
* @param {String} html A string of HTML.
* @param {String} selector An optional selector (default: "body").
*/
Jymin.pushHtml = function (html, selector) {
// Grab the new page title if there is one.
var title = Jymin.getTagContents(html, 'title')[0];
// If there's no target, we're replacing the body contents.
if (!selector) {
selector = 'body';
html = Jymin.getTagContents(html, selector)[0];
}
// TODO: Implement a DOM diff.
Jymin.all(selector || 'body', function (element) {
// Set the HTML of an element.
Jymin.setHtml(element, html);
// If there's a title, set it.
if (title) {
document.title = title;
Jymin.scrollTop(0);
}
Jymin.ready(element);
});
// Execute any scripts that are found.
// TODO: Skip over JSX, etc.
Jymin.getTagContents(html, 'script', Jymin.execute);
};
/**
* Create an event emitter object, lazily loading its prototype.
*/
Jymin.Emitter = function () {
this._events = {};
if (!this._on) {
Jymin.decorateObject(Jymin.Emitter.prototype, Jymin.EmitterPrototype);
}
};
/**
* Expose Emitter methods which can be applied lazily.
*/
Jymin.EmitterPrototype = {
_on: function (event, fn) {
var self = this;
var events = self._events;
var listeners = events[event] || (events[event] = []);
listeners.push(fn);
return self;
},
_once: function (event, fn) {
var self = this;
function f() {
fn.apply(self, arguments);
self._removeListener(event, f);
}
self._on(event, f);
return self;
},
_emit: function (event) {
var self = this;
var listeners = self._listeners(event);
var args = Array.prototype.slice.call(arguments, 1);
Jymin.forEach(listeners, function (listener) {
listener.apply(self, args);
});
return self;
},
_listeners: function (event) {
var self = this;
var listeners = self._events[event] || [];
return listeners;
},
_removeListener: function (event, fn) {
var self = this;
var listeners = self._listeners(event);
var i = listeners.indexOf(fn);
if (i > -1) {
listeners.splice(i, 1);
}
return self;
},
_removeAllListeners: function (event) {
var self = this;
var events = self._events;
if (event) {
delete events[event];
}
else {
for (event in events) {
delete events[event];
}
}
return self;
}
};
Jymin.FOCUS = 'focus';
Jymin.BLUR = 'blur';
Jymin.CLICK = 'click';
Jymin.MOUSEDOWN = 'mousedown';
Jymin.MOUSEUP = 'mouseup';
Jymin.MOUSEOVER = 'mouseover';
Jymin.MOUSEOUT = 'mouseout';
Jymin.KEYDOWN = 'keydown';
Jymin.KEYUP = 'keyup';
Jymin.KEYPRESS = 'keypress';
Jymin.CANCEL_BUBBLE = 'cancelBubble';
Jymin.PREVENT_DEFAULT = 'preventDefault';
Jymin.STOP_PROPAGATION = 'stopPropagation';
Jymin.ADD_EVENT_LISTENER = 'addEventListener';
Jymin.ATTACH_EVENT = 'attachEvent';
Jymin.ON = 'on';
/**
* Bind an event listener for one or more events on an element.
*
* @param {HTMLElement} element An element to bind an event listener to.
* @param {string|Array} events An array or comma-delimited string of event names.
* @param {function} listener A function to run when the event occurs or is triggered: `listener(element, event, target)`.
*/
Jymin.bind = function (element, events, listener) {
Jymin.forEach(events, function (event) {
// Invoke the event listener with the event information and the target element.
var fn = function (event) {
// Fall back to window.event for IE.
event = event || window.event;
// Fall back to srcElement for IE.
var target = event.target || event.srcElement;
// Make sure this isn't a text node in Safari.
if (target.nodeType == 3) {
target = Jymin.getParent(target);
}
listener(element, event, target);
};
// Bind for emitting.
var events = (element._events = element._events || {});
var listeners = (events[event] = events[event] || []);
Jymin.push(listeners, listener);
// Bind using whatever method we can use.
var method = Jymin.ADD_EVENT_LISTENER;
var key;
if (element[method]) {
element[method](event, fn, true);
}
else {
method = Jymin.ATTACH_EVENT;
key = Jymin.ON + event;
if (element[method]) {
element[method](key, fn);
}
}
});
};
/**
* Bind a listener to an element to receive bubbled events from descendents matching a selector.
*
* @param {HTMLElement} element The element to bind a listener to.
* @param {String} selector The selector for descendents.
* @param {String|Array} events A list of events to listen for.
* @param {function} listener A function to call on an element, event and descendent.
*/
Jymin.on = function (element, selector, events, listener) {
if (Jymin.isFunction(events)) {
listener = events;
events = selector;
selector = element;
element = document;
}
Jymin.bind(element, events, function (element, event, target) {
var trail = Jymin.getTrail(target, selector);
Jymin.forEach(trail, function (element) {
listener(element, event, target);
return !event[Jymin.CANCEL_BUBBLE];
});
});
};
/**
* Trigger an event on an element, and bubble it up to parent elements.
*
* @param {HTMLElement} element Element to trigger an event on.
* @param {Event|string} event Event or event type to trigger.
* @param {HTMLElement} target Fake target.
*/
Jymin.trigger = function (element, event, target) {
if (element) {
var type = event.type;
event = type ? event : {type: (type = event)};
event._triggered = true;
target = target || element;
var listeners = (element._events || 0)[type];
Jymin.forEach(listeners, function (fn) {
fn(element, event, target);
});
if (!event[Jymin.CANCEL_BUBBLE]) {
Jymin.trigger(element.parentNode, event, target);
}
}
};
/**
* Stop an event from bubbling up the DOM.
*
* @param {Event} event Event to stop.
*/
Jymin.stopPropagation = function (event) {
(event || 0)[Jymin.CANCEL_BUBBLE] = true;
Jymin.apply(event, Jymin.STOP_PROPAGATION);
};
/**
* Prevent the default action for this event.
*
* @param {Event} event Event to prevent from doing its default action.
*/
Jymin.preventDefault = function (event) {
Jymin.apply(event, Jymin.PREVENT_DEFAULT);
};
/**
* Focus on a specified element.
*
* @param {HTMLElement} element The element to focus on.
*/
Jymin.focusElement = function (element) {
Jymin.apply(element, Jymin.FOCUS);
};
/**
* Get the value of a form element.
*
* @param {HTMLElement} input A form element.
* @return {String|Array} The value of the form element (or array of elements).
*/
Jymin.getValue = function (input) {
input = Jymin.getElement(input);
if (input) {
var type = input.type[0];
var value = input.value;
var checked = input.checked;
var options = input.options;
if (type == 'c' || type == 'r') {
value = checked ? value : null;
}
else if (input.multiple) {
value = [];
Jymin.forEach(options, function (option) {
if (option.selected) {
Jymin.push(value, option.value);
}
});
}
else if (options) {
value = Jymin.getValue(options[input.selectedIndex]);
}
return value;
}
};
/**
* Set the value of a form element.
*
* @param {HTMLElement} input A form element.
* @return {String|Array} A value or values to set on the form element.
*/
Jymin.setValue = function (input, value) {
input = Jymin.getElement(input);
if (input) {
var type = input.type[0];
var options = input.options;
if (type == 'c' || type == 'r') {
input.checked = value ? true : false;
}
else if (options) {
var selected = {};
if (input.multiple) {
Jymin.forEach(value, function (optionValue) {
selected[optionValue] = true;
});
}
else {
selected[value] = true;
}
value = Jymin.isArray(value) ? value : [value];
Jymin.forEach(options, function (option) {
option.selected = !!selected[option.value];
});
}
else {
input.value = value;
}
}
};
/**
* Apply arguments to an object method.
*
* @param {Object} object An object with methods.
* @param {string} methodName A method name, which may exist on the object.
* @param {Arguments|Array} args An arguments object or array to apply to the method.
* @return {Object} The result returned by the object method.
*/
Jymin.apply = function (object, methodName, args) {
return ((object || 0)[methodName] || Jymin.doNothing).apply(object, args);
};
/**
* Return a history object.
*/
Jymin.getHistory = function () {
var history = window.history || {};
Jymin.forEach(['Jymin.push', 'replace'], function (key) {
var fn = history[key + 'State'];
history[key] = function (href) {
if (fn) {
fn.apply(history, [null, null, href]);
} else {
// TODO: Create a backward compatible history push.
}
};
});
return history;
};
/**
* Push an item into the history.
*/
Jymin.historyPush = function (href) {
Jymin.getHistory().push(href);
};
/**
* Replace the current item in the history.
*/
Jymin.historyReplace = function (href) {
Jymin.getHistory().replace(href);
};
/**
* Go back.
*/
Jymin.historyPop = function () {
Jymin.getHistory().back();
};
/**
* Listen for a history change.
*/
Jymin.onHistoryPop = function (callback) {
Jymin.bind(window, 'Jymin.popstate', callback);
};
/**
* The values in this file can be overridden externally.
* The default locale is US. Sorry, World.
*/
/**
* Month names in English.
* @type {Array}
*/
Jymin.i18nMonths = [
'January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'
];
/**
* The word "at" in English (for separating date & time).
* @type {String}
*/
Jymin.i18nAt = 'at';
/**
* Whether to show dates in DD/MM/YYYY format.
* @type {Booly}
*/
Jymin.i18nDayMonthYear = 0;
/**
* Whether to show times in 24-hour format.
* @type {Booly}
*/
Jymin.i18n24Hour = 0;
/**
* Why oh why did I have to learn different units than the rest of the world?
* @type {String}
*/
Jymin.i18nTemperature = 'F';
/**
* Create a circular-safe JSON string.
*/
Jymin.safeStringify = function (data, stack) {
if (Jymin.isString(data)) {
data = '"' + data.replace(/\n\r"/g, function (c) {
return c == '\n' ? '\\n' : c == '\r' ? '\\r' : '\\"';
}) + '"';
}
else if (Jymin.isFunction(data) || Jymin.isUndefined(data) || (data === null)) {
return null;
}
else if (data && Jymin.isObject(data)) {
stack = stack || [];
var isCircular;
Jymin.forEach(stack, function (item) {
if (item == data) {
isCircular = 1;
}
});
if (isCircular) {
return null;
}
Jymin.push(stack, data);
var parts = [];
var before, after;
if (Jymin.isArray(data)) {
before = '[';
after = ']';
Jymin.forEach(data, function (value) {
Jymin.push(parts, Jymin.stringify(value, stack));
});
}
else {
before = '{';
after = '}';
Jymin.forIn(data, function (key, value) {
Jymin.push(parts, Jymin.stringify(key) + ':' + Jymin.stringify(value, stack));
});
}
Jymin.pop(stack);
data = before + parts.join(',') + after;
}
else {
data = '' + data;
}
return data;
};
/**
* Create a JSON string.
*/
Jymin.stringify = function (data) {
var json;
//+browser:old
json = Jymin.safeStringify(data);
//-browser:old
//+browser:ok
json = JSON.stringify(data);
//-browser:ok
};
/**
* Parse JavaScript and return a value.
*/
Jymin.parse = function (value) {
try {
var evil = window.eval; // jshint ignore:line
evil('eval.J=' + value);
return evil.J;
}
catch (e) {
//+env:debug
Jymin.error('[Jymin] Could not parse JS: ' + value);
//-env:debug
}
};
/**
* Execute JavaScript.
*/
Jymin.execute = function (text) {
Jymin.parse('0;' + text);
};
/**
* Parse a value and return a boolean no matter what.
*/
Jymin.parseBoolean = function (value, alternative) {
value = Jymin.parse(value);
return Jymin.isBoolean(value) ? value : (alternative || false);
};
/**
* Parse a value and return a number no matter what.
*/
Jymin.parseNumber = function (value, alternative) {
value = Jymin.parse(value);
return Jymin.isNumber(value) ? value : (alternative || 0);
};
/**
* Parse a value and return a string no matter what.
*/
Jymin.parseString = function (value, alternative) {
value = Jymin.parse(value);
return Jymin.isString(value) ? value : (alternative || '');
};
/**
* Parse a value and return an object no matter what.
*/
Jymin.parseObject = function (value, alternative) {
value = Jymin.parse(value);
return Jymin.isObject(value) ? value : (alternative || {});
};
/**
* Parse a value and return a number no matter what.
*/
Jymin.parseArray