/*
TimelineJS - ver. 3.6.5 - 2019-05-02
Copyright (c) 2012-2016 Northwestern University
a project of the Northwestern University Knight Lab, originally created by Zach Wise
https://github.com/NUKnightLab/TimelineJS3
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/* **********************************************
Begin TL.js
********************************************** */
/*!
TL
*/
(function (root) {
root.TL = {
VERSION: '0.1',
_originalL: root.TL
};
}(this));
/* TL.Debug
Debug mode
================================================== */
TL.debug = false;
/* TL.Bind
================================================== */
TL.Bind = function (/*Function*/ fn, /*Object*/ obj) /*-> Object*/ {
return function () {
return fn.apply(obj, arguments);
};
};
/* Trace (console.log)
================================================== */
trace = function( msg ) {
if (TL.debug) {
if (window.console) {
console.log(msg);
} else if ( typeof( jsTrace ) != 'undefined' ) {
jsTrace.send( msg );
} else {
//alert(msg);
}
}
}
/* **********************************************
Begin TL.Error.js
********************************************** */
/* Timeline Error class */
function TL_Error(message_key, detail) {
this.name = 'TL.Error';
this.message = message_key || 'error';
this.message_key = this.message;
this.detail = detail || '';
// Grab stack?
var e = new Error();
if(e.hasOwnProperty('stack')) {
this.stack = e.stack;
}
}
TL_Error.prototype = Object.create(Error.prototype);
TL_Error.prototype.constructor = TL_Error;
TL.Error = TL_Error;
/* **********************************************
Begin TL.Util.js
********************************************** */
/* TL.Util
Class of utilities
================================================== */
TL.Util = {
mergeData: function(data_main, data_to_merge) {
var x;
for (x in data_to_merge) {
if (Object.prototype.hasOwnProperty.call(data_to_merge, x)) {
data_main[x] = data_to_merge[x];
}
}
return data_main;
},
// like TL.Util.mergeData but takes an arbitrarily long list of sources to merge.
extend: function (/*Object*/ dest) /*-> Object*/ { // merge src properties into dest
var sources = Array.prototype.slice.call(arguments, 1);
for (var j = 0, len = sources.length, src; j < len; j++) {
src = sources[j] || {};
TL.Util.mergeData(dest, src);
}
return dest;
},
isEven: function(n) {
return n == parseFloat(n)? !(n%2) : void 0;
},
isTrue: function(s) {
if (s == null) return false;
return s == true || String(s).toLowerCase() == 'true' || Number(s) == 1;
},
findArrayNumberByUniqueID: function(id, array, prop, defaultVal) {
var _n = defaultVal || 0;
for (var i = 0; i < array.length; i++) {
if (array[i].data[prop] == id) {
_n = i;
}
};
return _n;
},
convertUnixTime: function(str) {
var _date, _months, _year, _month, _day, _time, _date_array = [],
_date_str = {
ymd:"",
time:"",
time_array:[],
date_array:[],
full_array:[]
};
_date_str.ymd = str.split(" ")[0];
_date_str.time = str.split(" ")[1];
_date_str.date_array = _date_str.ymd.split("-");
_date_str.time_array = _date_str.time.split(":");
_date_str.full_array = _date_str.date_array.concat(_date_str.time_array)
for(var i = 0; i < _date_str.full_array.length; i++) {
_date_array.push( parseInt(_date_str.full_array[i]) )
}
_date = new Date(_date_array[0], _date_array[1], _date_array[2], _date_array[3], _date_array[4], _date_array[5]);
_months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
_year = _date.getFullYear();
_month = _months[_date.getMonth()];
_day = _date.getDate();
_time = _month + ', ' + _day + ' ' + _year;
return _time;
},
setData: function (obj, data) {
obj.data = TL.Util.extend({}, obj.data, data);
if (obj.data.unique_id === "") {
obj.data.unique_id = TL.Util.unique_ID(6);
}
},
stamp: (function () {
var lastId = 0, key = '_tl_id';
return function (/*Object*/ obj) {
obj[key] = obj[key] || ++lastId;
return obj[key];
};
}()),
isArray: (function () {
// Use compiler's own isArray when available
if (Array.isArray) {
return Array.isArray;
}
// Retain references to variables for performance
// optimization
var objectToStringFn = Object.prototype.toString,
arrayToStringResult = objectToStringFn.call([]);
return function (subject) {
return objectToStringFn.call(subject) === arrayToStringResult;
};
}()),
getRandomNumber: function(range) {
return Math.floor(Math.random() * range);
},
unique_ID: function(size, prefix) {
var getRandomNumber = function(range) {
return Math.floor(Math.random() * range);
};
var getRandomChar = function() {
var chars = "abcdefghijklmnopqurstuvwxyz";
return chars.substr( getRandomNumber(32), 1 );
};
var randomID = function(size) {
var str = "";
for(var i = 0; i < size; i++) {
str += getRandomChar();
}
return str;
};
if (prefix) {
return prefix + "-" + randomID(size);
} else {
return "tl-" + randomID(size);
}
},
ensureUniqueKey: function(obj, candidate) {
if (!candidate) { candidate = TL.Util.unique_ID(6); }
if (!(candidate in obj)) { return candidate; }
var root = candidate.match(/^(.+)(-\d+)?$/)[1];
var similar_ids = [];
// get an alternative
for (key in obj) {
if (key.match(/^(.+?)(-\d+)?$/)[1] == root) {
similar_ids.push(key);
}
}
candidate = root + "-" + (similar_ids.length + 1);
for (var counter = similar_ids.length; similar_ids.indexOf(candidate) != -1; counter++) {
candidate = root + '-' + counter;
}
return candidate;
},
htmlify: function(str) {
//if (str.match(/<\s*p[^>]*>([^<]*)<\s*\/\s*p\s*>/)) {
if (str.match(/
[\s\S]*?<\/p>/)) {
return str;
} else {
return "
" + str + "
";
}
},
unhtmlify: function(str) {
str = str.replace(/(<[^>]*>)+/g, '');
return str.replace('"', "'");
},
/* * Turns plain text links into real links
================================================== */
linkify: function(text,targets,is_touch) {
var make_link = function(url, link_text, prefix) {
if (!prefix) {
prefix = "";
}
var MAX_LINK_TEXT_LENGTH = 30;
if (link_text && link_text.length > MAX_LINK_TEXT_LENGTH) {
link_text = link_text.substring(0,MAX_LINK_TEXT_LENGTH) + "\u2026"; // unicode ellipsis
}
return prefix + "" + link_text + "";
}
// http://, https://, ftp://
var urlPattern = /\b(?:https?|ftp):\/\/([a-z0-9-+&@#\/%?=~_|!:,.;]*[a-z0-9-+&@#\/%=~_|])/gim;
// www. sans http:// or https://
var pseudoUrlPattern = /(^|[^\/>])(www\.[\S]+(\b|$))/gim;
// Email addresses
var emailAddressPattern = /([a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+)/gim;
return text
.replace(urlPattern, function(match, url_sans_protocol, offset, string) {
// Javascript doesn't support negative lookbehind assertions, so
// we need to handle risk of matching URLs in legit hrefs
if (offset > 0) {
var prechar = string[offset-1];
if (prechar == '"' || prechar == "'" || prechar == "=") {
return match;
}
}
return make_link(match, url_sans_protocol);
})
.replace(pseudoUrlPattern, function(match, beforePseudo, pseudoUrl, offset, string) {
return make_link('http://' + pseudoUrl, pseudoUrl, beforePseudo);
})
.replace(emailAddressPattern, function(match, email, offset, string) {
return make_link('mailto:' + email, email);
});
},
unlinkify: function(text) {
if(!text) return text;
text = text.replace(/]*>/i,"");
text = text.replace(/<\/a>/i, "");
return text;
},
getParamString: function (obj) {
var params = [];
for (var i in obj) {
if (obj.hasOwnProperty(i)) {
params.push(i + '=' + obj[i]);
}
}
return '?' + params.join('&');
},
formatNum: function (num, digits) {
var pow = Math.pow(10, digits || 5);
return Math.round(num * pow) / pow;
},
falseFn: function () {
return false;
},
requestAnimFrame: (function () {
function timeoutDefer(callback) {
window.setTimeout(callback, 1000 / 60);
}
var requestFn = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
timeoutDefer;
return function (callback, context, immediate, contextEl) {
callback = context ? TL.Util.bind(callback, context) : callback;
if (immediate && requestFn === timeoutDefer) {
callback();
} else {
requestFn(callback, contextEl);
}
};
}()),
bind: function (/*Function*/ fn, /*Object*/ obj) /*-> Object*/ {
return function () {
return fn.apply(obj, arguments);
};
},
template: function (str, data) {
return str.replace(/\{ *([\w_]+) *\}/g, function (str, key) {
var value = data[key];
if (!data.hasOwnProperty(key)) {
throw new TL.Error("template_value_err", str);
}
return value;
});
},
hexToRgb: function(hex) {
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
if (TL.Util.css_named_colors[hex.toLowerCase()]) {
hex = TL.Util.css_named_colors[hex.toLowerCase()];
}
var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
hex = hex.replace(shorthandRegex, function(m, r, g, b) {
return r + r + g + g + b + b;
});
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
},
// given an object with r, g, and b keys, or a string of the form 'rgb(mm,nn,ll)', return a CSS hex string including the leading '#' character
rgbToHex: function(rgb) {
var r,g,b;
if (typeof(rgb) == 'object') {
r = rgb.r;
g = rgb.g;
b = rgb.b;
} else if (typeof(rgb.match) == 'function'){
var parts = rgb.match(/^rgb\((\d+),(\d+),(\d+)\)$/);
if (parts) {
r = parts[1];
g = parts[2];
b = parts[3];
}
}
if (isNaN(r) || isNaN(b) || isNaN(g)) {
throw new TL.Error("invalid_rgb_err");
}
return "#" + TL.Util.intToHexString(r) + TL.Util.intToHexString(g) + TL.Util.intToHexString(b);
},
colorObjToHex: function(o) {
var parts = [o.r, o.g, o.b];
return TL.Util.rgbToHex("rgb(" + parts.join(',') + ")")
},
css_named_colors: {
"aliceblue": "#f0f8ff",
"antiquewhite": "#faebd7",
"aqua": "#00ffff",
"aquamarine": "#7fffd4",
"azure": "#f0ffff",
"beige": "#f5f5dc",
"bisque": "#ffe4c4",
"black": "#000000",
"blanchedalmond": "#ffebcd",
"blue": "#0000ff",
"blueviolet": "#8a2be2",
"brown": "#a52a2a",
"burlywood": "#deb887",
"cadetblue": "#5f9ea0",
"chartreuse": "#7fff00",
"chocolate": "#d2691e",
"coral": "#ff7f50",
"cornflowerblue": "#6495ed",
"cornsilk": "#fff8dc",
"crimson": "#dc143c",
"cyan": "#00ffff",
"darkblue": "#00008b",
"darkcyan": "#008b8b",
"darkgoldenrod": "#b8860b",
"darkgray": "#a9a9a9",
"darkgreen": "#006400",
"darkkhaki": "#bdb76b",
"darkmagenta": "#8b008b",
"darkolivegreen": "#556b2f",
"darkorange": "#ff8c00",
"darkorchid": "#9932cc",
"darkred": "#8b0000",
"darksalmon": "#e9967a",
"darkseagreen": "#8fbc8f",
"darkslateblue": "#483d8b",
"darkslategray": "#2f4f4f",
"darkturquoise": "#00ced1",
"darkviolet": "#9400d3",
"deeppink": "#ff1493",
"deepskyblue": "#00bfff",
"dimgray": "#696969",
"dodgerblue": "#1e90ff",
"firebrick": "#b22222",
"floralwhite": "#fffaf0",
"forestgreen": "#228b22",
"fuchsia": "#ff00ff",
"gainsboro": "#dcdcdc",
"ghostwhite": "#f8f8ff",
"gold": "#ffd700",
"goldenrod": "#daa520",
"gray": "#808080",
"green": "#008000",
"greenyellow": "#adff2f",
"honeydew": "#f0fff0",
"hotpink": "#ff69b4",
"indianred": "#cd5c5c",
"indigo": "#4b0082",
"ivory": "#fffff0",
"khaki": "#f0e68c",
"lavender": "#e6e6fa",
"lavenderblush": "#fff0f5",
"lawngreen": "#7cfc00",
"lemonchiffon": "#fffacd",
"lightblue": "#add8e6",
"lightcoral": "#f08080",
"lightcyan": "#e0ffff",
"lightgoldenrodyellow": "#fafad2",
"lightgray": "#d3d3d3",
"lightgreen": "#90ee90",
"lightpink": "#ffb6c1",
"lightsalmon": "#ffa07a",
"lightseagreen": "#20b2aa",
"lightskyblue": "#87cefa",
"lightslategray": "#778899",
"lightsteelblue": "#b0c4de",
"lightyellow": "#ffffe0",
"lime": "#00ff00",
"limegreen": "#32cd32",
"linen": "#faf0e6",
"magenta": "#ff00ff",
"maroon": "#800000",
"mediumaquamarine": "#66cdaa",
"mediumblue": "#0000cd",
"mediumorchid": "#ba55d3",
"mediumpurple": "#9370db",
"mediumseagreen": "#3cb371",
"mediumslateblue": "#7b68ee",
"mediumspringgreen": "#00fa9a",
"mediumturquoise": "#48d1cc",
"mediumvioletred": "#c71585",
"midnightblue": "#191970",
"mintcream": "#f5fffa",
"mistyrose": "#ffe4e1",
"moccasin": "#ffe4b5",
"navajowhite": "#ffdead",
"navy": "#000080",
"oldlace": "#fdf5e6",
"olive": "#808000",
"olivedrab": "#6b8e23",
"orange": "#ffa500",
"orangered": "#ff4500",
"orchid": "#da70d6",
"palegoldenrod": "#eee8aa",
"palegreen": "#98fb98",
"paleturquoise": "#afeeee",
"palevioletred": "#db7093",
"papayawhip": "#ffefd5",
"peachpuff": "#ffdab9",
"peru": "#cd853f",
"pink": "#ffc0cb",
"plum": "#dda0dd",
"powderblue": "#b0e0e6",
"purple": "#800080",
"rebeccapurple": "#663399",
"red": "#ff0000",
"rosybrown": "#bc8f8f",
"royalblue": "#4169e1",
"saddlebrown": "#8b4513",
"salmon": "#fa8072",
"sandybrown": "#f4a460",
"seagreen": "#2e8b57",
"seashell": "#fff5ee",
"sienna": "#a0522d",
"silver": "#c0c0c0",
"skyblue": "#87ceeb",
"slateblue": "#6a5acd",
"slategray": "#708090",
"snow": "#fffafa",
"springgreen": "#00ff7f",
"steelblue": "#4682b4",
"tan": "#d2b48c",
"teal": "#008080",
"thistle": "#d8bfd8",
"tomato": "#ff6347",
"turquoise": "#40e0d0",
"violet": "#ee82ee",
"wheat": "#f5deb3",
"white": "#ffffff",
"whitesmoke": "#f5f5f5",
"yellow": "#ffff00",
"yellowgreen": "#9acd32"
},
ratio: {
square: function(size) {
var s = {
w: 0,
h: 0
}
if (size.w > size.h && size.h > 0) {
s.h = size.h;
s.w = size.h;
} else {
s.w = size.w;
s.h = size.w;
}
return s;
},
r16_9: function(size) {
if (size.w !== null && size.w !== "") {
return Math.round((size.w / 16) * 9);
} else if (size.h !== null && size.h !== "") {
return Math.round((size.h / 9) * 16);
} else {
return 0;
}
},
r4_3: function(size) {
if (size.w !== null && size.w !== "") {
return Math.round((size.w / 4) * 3);
} else if (size.h !== null && size.h !== "") {
return Math.round((size.h / 3) * 4);
}
}
},
getObjectAttributeByIndex: function(obj, index) {
if(typeof obj != 'undefined') {
var i = 0;
for (var attr in obj){
if (index === i){
return obj[attr];
}
i++;
}
return "";
} else {
return "";
}
},
getUrlVars: function(string) {
var str,
vars = [],
hash,
hashes;
str = string.toString();
if (str.match('&')) {
str = str.replace("&", "&");
} else if (str.match('&')) {
str = str.replace("&", "&");
} else if (str.match('&')) {
str = str.replace("&", "&");
}
hashes = str.slice(str.indexOf('?') + 1).split('&');
for(var i = 0; i < hashes.length; i++) {
hash = hashes[i].split('=');
vars.push(hash[0]);
vars[hash[0]] = hash[1];
}
return vars;
},
/**
* Remove any leading or trailing whitespace from the given string.
* If `str` is undefined or does not have a `replace` function, return
* an empty string.
*/
trim: function(str) {
if (str && typeof(str.replace) == 'function') {
return str.replace(/^\s+|\s+$/g, '');
}
return "";
},
slugify: function(str) {
// borrowed from http://stackoverflow.com/a/5782563/102476
str = TL.Util.trim(str);
str = str.toLowerCase();
// remove accents, swap ñ for n, etc
var from = "ãà áäâẽèéëêìÃïîõòóöôùúüûñç·/_,:;";
var to = "aaaaaeeeeeiiiiooooouuuunc------";
for (var i=0, l=from.length ; i 1) {
var top = stack[stack.length - 1]
var bottom_idx = -1;
for (var j = 0; j < stack.length - 1; j++) {
if (stack[j][1] < top[0]) {
bottom_idx = j;
}
};
if (bottom_idx >= 0) {
stack = stack.slice(bottom_idx + 1);
}
}
if (stack.length > max_depth) {
max_depth = stack.length;
}
};
return max_depth;
},
pad: function (val, len) {
val = String(val);
len = len || 2;
while (val.length < len) val = "0" + val;
return val;
},
intToHexString: function(i) {
return TL.Util.pad(parseInt(i,10).toString(16));
},
findNextGreater: function(list, current, default_value) {
// given a sorted list and a current value which *might* be in the list,
// return the next greatest value if the current value is >= the last item in the list, return default,
// or if default is undefined, return input value
for (var i = 0; i < list.length; i++) {
if (current < list[i]) {
return list[i];
}
}
return (default_value) ? default_value : current;
},
findNextLesser: function(list, current, default_value) {
// given a sorted list and a current value which *might* be in the list,
// return the next lesser value if the current value is <= the last item in the list, return default,
// or if default is undefined, return input value
for (var i = list.length - 1; i >= 0; i--) {
if (current > list[i]) {
return list[i];
}
}
return (default_value) ? default_value : current;
},
isEmptyObject: function(o) {
var properties = []
if (Object.keys) {
properties = Object.keys(o);
} else { // all this to support IE 8
for (var p in o) if (Object.prototype.hasOwnProperty.call(o,p)) properties.push(p);
}
for (var i = 0; i < properties.length; i++) {
var k = properties[i];
if (o[k] != null && typeof o[k] != "string") return false;
if (TL.Util.trim(o[k]).length != 0) return false;
}
return true;
},
parseYouTubeTime: function(s) {
// given a YouTube start time string in a reasonable format, reduce it to a number of seconds as an integer.
if (typeof(s) == 'string') {
parts = s.match(/^\s*(\d+h)?(\d+m)?(\d+s)?\s*/i);
if (parts) {
var hours = parseInt(parts[1]) || 0;
var minutes = parseInt(parts[2]) || 0;
var seconds = parseInt(parts[3]) || 0;
return seconds + (minutes * 60) + (hours * 60 * 60);
}
} else if (typeof(s) == 'number') {
return s;
}
return 0;
},
/**
* Try to make seamless the process of interpreting a URL to a web page which embeds an image for sharing purposes
* as a direct image link. Some services have predictable transformations we can use rather than explain to people
* this subtlety.
*/
transformImageURL: function(url) {
return url.replace(/(.*)www.dropbox.com\/(.*)/, '$1dl.dropboxusercontent.com/$2')
},
base58: (function(alpha) {
var alphabet = alpha || '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ',
base = alphabet.length;
return {
encode: function(enc) {
if(typeof enc!=='number' || enc !== parseInt(enc))
throw '"encode" only accepts integers.';
var encoded = '';
while(enc) {
var remainder = enc % base;
enc = Math.floor(enc / base);
encoded = alphabet[remainder].toString() + encoded;
}
return encoded;
},
decode: function(dec) {
if(typeof dec!=='string')
throw '"decode" only accepts strings.';
var decoded = 0;
while(dec) {
var alphabetPosition = alphabet.indexOf(dec[0]);
if (alphabetPosition < 0)
throw '"decode" can\'t find "' + dec[0] + '" in the alphabet: "' + alphabet + '"';
var powerOf = dec.length - 1;
decoded += alphabetPosition * (Math.pow(base, powerOf));
dec = dec.substring(1);
}
return decoded;
}
};
})()
};
/* **********************************************
Begin TL.Data.js
********************************************** */
// Expects TL to be visible in scope
;(function(TL){
/* Zepto v1.1.2-15-g59d3fe5 - zepto event ajax form ie - zeptojs.com/license */
var Zepto = (function() {
var undefined, key, $, classList, emptyArray = [], slice = emptyArray.slice, filter = emptyArray.filter,
document = window.document,
elementDisplay = {}, classCache = {},
cssNumber = { 'column-count': 1, 'columns': 1, 'font-weight': 1, 'line-height': 1,'opacity': 1, 'z-index': 1, 'zoom': 1 },
fragmentRE = /^\s*<(\w+|!)[^>]*>/,
singleTagRE = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
tagExpanderRE = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
rootNodeRE = /^(?:body|html)$/i,
capitalRE = /([A-Z])/g,
// special attributes that should be get/set via method calls
methodAttributes = ['val', 'css', 'html', 'text', 'data', 'width', 'height', 'offset'],
adjacencyOperators = [ 'after', 'prepend', 'before', 'append' ],
table = document.createElement('table'),
tableRow = document.createElement('tr'),
containers = {
'tr': document.createElement('tbody'),
'tbody': table, 'thead': table, 'tfoot': table,
'td': tableRow, 'th': tableRow,
'*': document.createElement('div')
},
readyRE = /complete|loaded|interactive/,
classSelectorRE = /^\.([\w-]+)$/,
idSelectorRE = /^#([\w-]*)$/,
simpleSelectorRE = /^[\w-]*$/,
class2type = {},
toString = class2type.toString,
zepto = {},
camelize, uniq,
tempParent = document.createElement('div'),
propMap = {
'tabindex': 'tabIndex',
'readonly': 'readOnly',
'for': 'htmlFor',
'class': 'className',
'maxlength': 'maxLength',
'cellspacing': 'cellSpacing',
'cellpadding': 'cellPadding',
'rowspan': 'rowSpan',
'colspan': 'colSpan',
'usemap': 'useMap',
'frameborder': 'frameBorder',
'contenteditable': 'contentEditable'
},
isArray = Array.isArray ||
function(object){ return object instanceof Array }
zepto.matches = function(element, selector) {
if (!selector || !element || element.nodeType !== 1) return false
var matchesSelector = element.webkitMatchesSelector || element.mozMatchesSelector ||
element.oMatchesSelector || element.matchesSelector
if (matchesSelector) return matchesSelector.call(element, selector)
// fall back to performing a selector:
var match, parent = element.parentNode, temp = !parent
if (temp) (parent = tempParent).appendChild(element)
match = ~zepto.qsa(parent, selector).indexOf(element)
temp && tempParent.removeChild(element)
return match
}
function type(obj) {
return obj == null ? String(obj) :
class2type[toString.call(obj)] || "object"
}
function isFunction(value) { return type(value) == "function" }
function isWindow(obj) { return obj != null && obj == obj.window }
function isDocument(obj) { return obj != null && obj.nodeType == obj.DOCUMENT_NODE }
function isObject(obj) { return type(obj) == "object" }
function isPlainObject(obj) {
return isObject(obj) && !isWindow(obj) && Object.getPrototypeOf(obj) == Object.prototype
}
function likeArray(obj) { return typeof obj.length == 'number' }
function compact(array) { return filter.call(array, function(item){ return item != null }) }
function flatten(array) { return array.length > 0 ? $.fn.concat.apply([], array) : array }
camelize = function(str){ return str.replace(/-+(.)?/g, function(match, chr){ return chr ? chr.toUpperCase() : '' }) }
function dasherize(str) {
return str.replace(/::/g, '/')
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
.replace(/([a-z\d])([A-Z])/g, '$1_$2')
.replace(/_/g, '-')
.toLowerCase()
}
uniq = function(array){ return filter.call(array, function(item, idx){ return array.indexOf(item) == idx }) }
function classRE(name) {
return name in classCache ?
classCache[name] : (classCache[name] = new RegExp('(^|\\s)' + name + '(\\s|$)'))
}
function maybeAddPx(name, value) {
return (typeof value == "number" && !cssNumber[dasherize(name)]) ? value + "px" : value
}
function defaultDisplay(nodeName) {
var element, display
if (!elementDisplay[nodeName]) {
element = document.createElement(nodeName)
document.body.appendChild(element)
display = getComputedStyle(element, '').getPropertyValue("display")
element.parentNode.removeChild(element)
display == "none" && (display = "block")
elementDisplay[nodeName] = display
}
return elementDisplay[nodeName]
}
function children(element) {
return 'children' in element ?
slice.call(element.children) :
$.map(element.childNodes, function(node){ if (node.nodeType == 1) return node })
}
// `$.zepto.fragment` takes a html string and an optional tag name
// to generate DOM nodes nodes from the given html string.
// The generated DOM nodes are returned as an array.
// This function can be overriden in plugins for example to make
// it compatible with browsers that don't support the DOM fully.
zepto.fragment = function(html, name, properties) {
var dom, nodes, container
// A special case optimization for a single tag
if (singleTagRE.test(html)) dom = $(document.createElement(RegExp.$1))
if (!dom) {
if (html.replace) html = html.replace(tagExpanderRE, "<$1>$2>")
if (name === undefined) name = fragmentRE.test(html) && RegExp.$1
if (!(name in containers)) name = '*'
container = containers[name]
container.innerHTML = '' + html
dom = $.each(slice.call(container.childNodes), function(){
container.removeChild(this)
})
}
if (isPlainObject(properties)) {
nodes = $(dom)
$.each(properties, function(key, value) {
if (methodAttributes.indexOf(key) > -1) nodes[key](value)
else nodes.attr(key, value)
})
}
return dom
}
// `$.zepto.Z` swaps out the prototype of the given `dom` array
// of nodes with `$.fn` and thus supplying all the Zepto functions
// to the array. Note that `__proto__` is not supported on Internet
// Explorer. This method can be overriden in plugins.
zepto.Z = function(dom, selector) {
dom = dom || []
dom.__proto__ = $.fn
dom.selector = selector || ''
return dom
}
// `$.zepto.isZ` should return `true` if the given object is a Zepto
// collection. This method can be overriden in plugins.
zepto.isZ = function(object) {
return object instanceof zepto.Z
}
// `$.zepto.init` is Zepto's counterpart to jQuery's `$.fn.init` and
// takes a CSS selector and an optional context (and handles various
// special cases).
// This method can be overriden in plugins.
zepto.init = function(selector, context) {
var dom
// If nothing given, return an empty Zepto collection
if (!selector) return zepto.Z()
// Optimize for string selectors
else if (typeof selector == 'string') {
selector = selector.trim()
// If it's a html fragment, create nodes from it
// Note: In both Chrome 21 and Firefox 15, DOM error 12
// is thrown if the fragment doesn't begin with <
if (selector[0] == '<' && fragmentRE.test(selector))
dom = zepto.fragment(selector, RegExp.$1, context), selector = null
// If there's a context, create a collection on that context first, and select
// nodes from there
else if (context !== undefined) return $(context).find(selector)
// If it's a CSS selector, use it to select nodes.
else dom = zepto.qsa(document, selector)
}
// If a function is given, call it when the DOM is ready
else if (isFunction(selector)) return $(document).ready(selector)
// If a Zepto collection is given, just return it
else if (zepto.isZ(selector)) return selector
else {
// normalize array if an array of nodes is given
if (isArray(selector)) dom = compact(selector)
// Wrap DOM nodes.
else if (isObject(selector))
dom = [selector], selector = null
// If it's a html fragment, create nodes from it
else if (fragmentRE.test(selector))
dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null
// If there's a context, create a collection on that context first, and select
// nodes from there
else if (context !== undefined) return $(context).find(selector)
// And last but no least, if it's a CSS selector, use it to select nodes.
else dom = zepto.qsa(document, selector)
}
// create a new Zepto collection from the nodes found
return zepto.Z(dom, selector)
}
// `$` will be the base `Zepto` object. When calling this
// function just call `$.zepto.init, which makes the implementation
// details of selecting nodes and creating Zepto collections
// patchable in plugins.
$ = function(selector, context){
return zepto.init(selector, context)
}
function extend(target, source, deep) {
for (key in source)
if (deep && (isPlainObject(source[key]) || isArray(source[key]))) {
if (isPlainObject(source[key]) && !isPlainObject(target[key]))
target[key] = {}
if (isArray(source[key]) && !isArray(target[key]))
target[key] = []
extend(target[key], source[key], deep)
}
else if (source[key] !== undefined) target[key] = source[key]
}
// Copy all but undefined properties from one or more
// objects to the `target` object.
$.extend = function(target){
var deep, args = slice.call(arguments, 1)
if (typeof target == 'boolean') {
deep = target
target = args.shift()
}
args.forEach(function(arg){ extend(target, arg, deep) })
return target
}
// `$.zepto.qsa` is Zepto's CSS selector implementation which
// uses `document.querySelectorAll` and optimizes for some special cases, like `#id`.
// This method can be overriden in plugins.
zepto.qsa = function(element, selector){
var found,
maybeID = selector[0] == '#',
maybeClass = !maybeID && selector[0] == '.',
nameOnly = maybeID || maybeClass ? selector.slice(1) : selector, // Ensure that a 1 char tag name still gets checked
isSimple = simpleSelectorRE.test(nameOnly)
return (isDocument(element) && isSimple && maybeID) ?
( (found = element.getElementById(nameOnly)) ? [found] : [] ) :
(element.nodeType !== 1 && element.nodeType !== 9) ? [] :
slice.call(
isSimple && !maybeID ?
maybeClass ? element.getElementsByClassName(nameOnly) : // If it's simple, it could be a class
element.getElementsByTagName(selector) : // Or a tag
element.querySelectorAll(selector) // Or it's not simple, and we need to query all
)
}
function filtered(nodes, selector) {
return selector == null ? $(nodes) : $(nodes).filter(selector)
}
$.contains = function(parent, node) {
return parent !== node && parent.contains(node)
}
function funcArg(context, arg, idx, payload) {
return isFunction(arg) ? arg.call(context, idx, payload) : arg
}
function setAttribute(node, name, value) {
value == null ? node.removeAttribute(name) : node.setAttribute(name, value)
}
// access className property while respecting SVGAnimatedString
function className(node, value){
var klass = node.className,
svg = klass && klass.baseVal !== undefined
if (value === undefined) return svg ? klass.baseVal : klass
svg ? (klass.baseVal = value) : (node.className = value)
}
// "true" => true
// "false" => false
// "null" => null
// "42" => 42
// "42.5" => 42.5
// "08" => "08"
// JSON => parse if valid
// String => self
function deserializeValue(value) {
var num
try {
return value ?
value == "true" ||
( value == "false" ? false :
value == "null" ? null :
!/^0/.test(value) && !isNaN(num = Number(value)) ? num :
/^[\[\{]/.test(value) ? $.parseJSON(value) :
value )
: value
} catch(e) {
return value
}
}
$.type = type
$.isFunction = isFunction
$.isWindow = isWindow
$.isArray = isArray
$.isPlainObject = isPlainObject
$.isEmptyObject = function(obj) {
var name
for (name in obj) return false
return true
}
$.inArray = function(elem, array, i){
return emptyArray.indexOf.call(array, elem, i)
}
$.camelCase = camelize
$.trim = function(str) {
return str == null ? "" : String.prototype.trim.call(str)
}
// plugin compatibility
$.uuid = 0
$.support = { }
$.expr = { }
$.map = function(elements, callback){
var value, values = [], i, key
if (likeArray(elements))
for (i = 0; i < elements.length; i++) {
value = callback(elements[i], i)
if (value != null) values.push(value)
}
else
for (key in elements) {
value = callback(elements[key], key)
if (value != null) values.push(value)
}
return flatten(values)
}
$.each = function(elements, callback){
var i, key
if (likeArray(elements)) {
for (i = 0; i < elements.length; i++)
if (callback.call(elements[i], i, elements[i]) === false) return elements
} else {
for (key in elements)
if (callback.call(elements[key], key, elements[key]) === false) return elements
}
return elements
}
$.grep = function(elements, callback){
return filter.call(elements, callback)
}
if (window.JSON) $.parseJSON = JSON.parse
// Populate the class2type map
$.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
class2type[ "[object " + name + "]" ] = name.toLowerCase()
})
// Define methods that will be available on all
// Zepto collections
$.fn = {
// Because a collection acts like an array
// copy over these useful array functions.
forEach: emptyArray.forEach,
reduce: emptyArray.reduce,
push: emptyArray.push,
sort: emptyArray.sort,
indexOf: emptyArray.indexOf,
concat: emptyArray.concat,
// `map` and `slice` in the jQuery API work differently
// from their array counterparts
map: function(fn){
return $($.map(this, function(el, i){ return fn.call(el, i, el) }))
},
slice: function(){
return $(slice.apply(this, arguments))
},
ready: function(callback){
// need to check if document.body exists for IE as that browser reports
// document ready when it hasn't yet created the body element
if (readyRE.test(document.readyState) && document.body) callback($)
else document.addEventListener('DOMContentLoaded', function(){ callback($) }, false)
return this
},
get: function(idx){
return idx === undefined ? slice.call(this) : this[idx >= 0 ? idx : idx + this.length]
},
toArray: function(){ return this.get() },
size: function(){
return this.length
},
remove: function(){
return this.each(function(){
if (this.parentNode != null)
this.parentNode.removeChild(this)
})
},
each: function(callback){
emptyArray.every.call(this, function(el, idx){
return callback.call(el, idx, el) !== false
})
return this
},
filter: function(selector){
if (isFunction(selector)) return this.not(this.not(selector))
return $(filter.call(this, function(element){
return zepto.matches(element, selector)
}))
},
add: function(selector,context){
return $(uniq(this.concat($(selector,context))))
},
is: function(selector){
return this.length > 0 && zepto.matches(this[0], selector)
},
not: function(selector){
var nodes=[]
if (isFunction(selector) && selector.call !== undefined)
this.each(function(idx){
if (!selector.call(this,idx)) nodes.push(this)
})
else {
var excludes = typeof selector == 'string' ? this.filter(selector) :
(likeArray(selector) && isFunction(selector.item)) ? slice.call(selector) : $(selector)
this.forEach(function(el){
if (excludes.indexOf(el) < 0) nodes.push(el)
})
}
return $(nodes)
},
has: function(selector){
return this.filter(function(){
return isObject(selector) ?
$.contains(this, selector) :
$(this).find(selector).size()
})
},
eq: function(idx){
return idx === -1 ? this.slice(idx) : this.slice(idx, + idx + 1)
},
first: function(){
var el = this[0]
return el && !isObject(el) ? el : $(el)
},
last: function(){
var el = this[this.length - 1]
return el && !isObject(el) ? el : $(el)
},
find: function(selector){
var result, $this = this
if (typeof selector == 'object')
result = $(selector).filter(function(){
var node = this
return emptyArray.some.call($this, function(parent){
return $.contains(parent, node)
})
})
else if (this.length == 1) result = $(zepto.qsa(this[0], selector))
else result = this.map(function(){ return zepto.qsa(this, selector) })
return result
},
closest: function(selector, context){
var node = this[0], collection = false
if (typeof selector == 'object') collection = $(selector)
while (node && !(collection ? collection.indexOf(node) >= 0 : zepto.matches(node, selector)))
node = node !== context && !isDocument(node) && node.parentNode
return $(node)
},
parents: function(selector){
var ancestors = [], nodes = this
while (nodes.length > 0)
nodes = $.map(nodes, function(node){
if ((node = node.parentNode) && !isDocument(node) && ancestors.indexOf(node) < 0) {
ancestors.push(node)
return node
}
})
return filtered(ancestors, selector)
},
parent: function(selector){
return filtered(uniq(this.pluck('parentNode')), selector)
},
children: function(selector){
return filtered(this.map(function(){ return children(this) }), selector)
},
contents: function() {
return this.map(function() { return slice.call(this.childNodes) })
},
siblings: function(selector){
return filtered(this.map(function(i, el){
return filter.call(children(el.parentNode), function(child){ return child!==el })
}), selector)
},
empty: function(){
return this.each(function(){ this.innerHTML = '' })
},
// `pluck` is borrowed from Prototype.js
pluck: function(property){
return $.map(this, function(el){ return el[property] })
},
show: function(){
return this.each(function(){
this.style.display == "none" && (this.style.display = '')
if (getComputedStyle(this, '').getPropertyValue("display") == "none")
this.style.display = defaultDisplay(this.nodeName)
})
},
replaceWith: function(newContent){
return this.before(newContent).remove()
},
wrap: function(structure){
var func = isFunction(structure)
if (this[0] && !func)
var dom = $(structure).get(0),
clone = dom.parentNode || this.length > 1
return this.each(function(index){
$(this).wrapAll(
func ? structure.call(this, index) :
clone ? dom.cloneNode(true) : dom
)
})
},
wrapAll: function(structure){
if (this[0]) {
$(this[0]).before(structure = $(structure))
var children
// drill down to the inmost element
while ((children = structure.children()).length) structure = children.first()
$(structure).append(this)
}
return this
},
wrapInner: function(structure){
var func = isFunction(structure)
return this.each(function(index){
var self = $(this), contents = self.contents(),
dom = func ? structure.call(this, index) : structure
contents.length ? contents.wrapAll(dom) : self.append(dom)
})
},
unwrap: function(){
this.parent().each(function(){
$(this).replaceWith($(this).children())
})
return this
},
clone: function(){
return this.map(function(){ return this.cloneNode(true) })
},
hide: function(){
return this.css("display", "none")
},
toggle: function(setting){
return this.each(function(){
var el = $(this)
;(setting === undefined ? el.css("display") == "none" : setting) ? el.show() : el.hide()
})
},
prev: function(selector){ return $(this.pluck('previousElementSibling')).filter(selector || '*') },
next: function(selector){ return $(this.pluck('nextElementSibling')).filter(selector || '*') },
html: function(html){
return arguments.length === 0 ?
(this.length > 0 ? this[0].innerHTML : null) :
this.each(function(idx){
var originHtml = this.innerHTML
$(this).empty().append( funcArg(this, html, idx, originHtml) )
})
},
text: function(text){
return arguments.length === 0 ?
(this.length > 0 ? this[0].textContent : null) :
this.each(function(){ this.textContent = (text === undefined) ? '' : ''+text })
},
attr: function(name, value){
var result
return (typeof name == 'string' && value === undefined) ?
(this.length == 0 || this[0].nodeType !== 1 ? undefined :
(name == 'value' && this[0].nodeName == 'INPUT') ? this.val() :
(!(result = this[0].getAttribute(name)) && name in this[0]) ? this[0][name] : result
) :
this.each(function(idx){
if (this.nodeType !== 1) return
if (isObject(name)) for (key in name) setAttribute(this, key, name[key])
else setAttribute(this, name, funcArg(this, value, idx, this.getAttribute(name)))
})
},
removeAttr: function(name){
return this.each(function(){ this.nodeType === 1 && setAttribute(this, name) })
},
prop: function(name, value){
name = propMap[name] || name
return (value === undefined) ?
(this[0] && this[0][name]) :
this.each(function(idx){
this[name] = funcArg(this, value, idx, this[name])
})
},
data: function(name, value){
var data = this.attr('data-' + name.replace(capitalRE, '-$1').toLowerCase(), value)
return data !== null ? deserializeValue(data) : undefined
},
val: function(value){
return arguments.length === 0 ?
(this[0] && (this[0].multiple ?
$(this[0]).find('option').filter(function(){ return this.selected }).pluck('value') :
this[0].value)
) :
this.each(function(idx){
this.value = funcArg(this, value, idx, this.value)
})
},
offset: function(coordinates){
if (coordinates) return this.each(function(index){
var $this = $(this),
coords = funcArg(this, coordinates, index, $this.offset()),
parentOffset = $this.offsetParent().offset(),
props = {
top: coords.top - parentOffset.top,
left: coords.left - parentOffset.left
}
if ($this.css('position') == 'static') props['position'] = 'relative'
$this.css(props)
})
if (this.length==0) return null
var obj = this[0].getBoundingClientRect()
return {
left: obj.left + window.pageXOffset,
top: obj.top + window.pageYOffset,
width: Math.round(obj.width),
height: Math.round(obj.height)
}
},
css: function(property, value){
if (arguments.length < 2) {
var element = this[0], computedStyle = getComputedStyle(element, '')
if(!element) return
if (typeof property == 'string')
return element.style[camelize(property)] || computedStyle.getPropertyValue(property)
else if (isArray(property)) {
var props = {}
$.each(isArray(property) ? property: [property], function(_, prop){
props[prop] = (element.style[camelize(prop)] || computedStyle.getPropertyValue(prop))
})
return props
}
}
var css = ''
if (type(property) == 'string') {
if (!value && value !== 0)
this.each(function(){ this.style.removeProperty(dasherize(property)) })
else
css = dasherize(property) + ":" + maybeAddPx(property, value)
} else {
for (key in property)
if (!property[key] && property[key] !== 0)
this.each(function(){ this.style.removeProperty(dasherize(key)) })
else
css += dasherize(key) + ':' + maybeAddPx(key, property[key]) + ';'
}
return this.each(function(){ this.style.cssText += ';' + css })
},
index: function(element){
return element ? this.indexOf($(element)[0]) : this.parent().children().indexOf(this[0])
},
hasClass: function(name){
if (!name) return false
return emptyArray.some.call(this, function(el){
return this.test(className(el))
}, classRE(name))
},
addClass: function(name){
if (!name) return this
return this.each(function(idx){
classList = []
var cls = className(this), newName = funcArg(this, name, idx, cls)
newName.split(/\s+/g).forEach(function(klass){
if (!$(this).hasClass(klass)) classList.push(klass)
}, this)
classList.length && className(this, cls + (cls ? " " : "") + classList.join(" "))
})
},
removeClass: function(name){
return this.each(function(idx){
if (name === undefined) return className(this, '')
classList = className(this)
funcArg(this, name, idx, classList).split(/\s+/g).forEach(function(klass){
classList = classList.replace(classRE(klass), " ")
})
className(this, classList.trim())
})
},
toggleClass: function(name, when){
if (!name) return this
return this.each(function(idx){
var $this = $(this), names = funcArg(this, name, idx, className(this))
names.split(/\s+/g).forEach(function(klass){
(when === undefined ? !$this.hasClass(klass) : when) ?
$this.addClass(klass) : $this.removeClass(klass)
})
})
},
scrollTop: function(value){
if (!this.length) return
var hasScrollTop = 'scrollTop' in this[0]
if (value === undefined) return hasScrollTop ? this[0].scrollTop : this[0].pageYOffset
return this.each(hasScrollTop ?
function(){ this.scrollTop = value } :
function(){ this.scrollTo(this.scrollX, value) })
},
scrollLeft: function(value){
if (!this.length) return
var hasScrollLeft = 'scrollLeft' in this[0]
if (value === undefined) return hasScrollLeft ? this[0].scrollLeft : this[0].pageXOffset
return this.each(hasScrollLeft ?
function(){ this.scrollLeft = value } :
function(){ this.scrollTo(value, this.scrollY) })
},
position: function() {
if (!this.length) return
var elem = this[0],
// Get *real* offsetParent
offsetParent = this.offsetParent(),
// Get correct offsets
offset = this.offset(),
parentOffset = rootNodeRE.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset()
// Subtract element margins
// note: when an element has margin: auto the offsetLeft and marginLeft
// are the same in Safari causing offset.left to incorrectly be 0
offset.top -= parseFloat( $(elem).css('margin-top') ) || 0
offset.left -= parseFloat( $(elem).css('margin-left') ) || 0
// Add offsetParent borders
parentOffset.top += parseFloat( $(offsetParent[0]).css('border-top-width') ) || 0
parentOffset.left += parseFloat( $(offsetParent[0]).css('border-left-width') ) || 0
// Subtract the two offsets
return {
top: offset.top - parentOffset.top,
left: offset.left - parentOffset.left
}
},
offsetParent: function() {
return this.map(function(){
var parent = this.offsetParent || document.body
while (parent && !rootNodeRE.test(parent.nodeName) && $(parent).css("position") == "static")
parent = parent.offsetParent
return parent
})
}
}
// for now
$.fn.detach = $.fn.remove
// Generate the `width` and `height` functions
;['width', 'height'].forEach(function(dimension){
var dimensionProperty =
dimension.replace(/./, function(m){ return m[0].toUpperCase() })
$.fn[dimension] = function(value){
var offset, el = this[0]
if (value === undefined) return isWindow(el) ? el['inner' + dimensionProperty] :
isDocument(el) ? el.documentElement['scroll' + dimensionProperty] :
(offset = this.offset()) && offset[dimension]
else return this.each(function(idx){
el = $(this)
el.css(dimension, funcArg(this, value, idx, el[dimension]()))
})
}
})
function traverseNode(node, fun) {
fun(node)
for (var key in node.childNodes) traverseNode(node.childNodes[key], fun)
}
// Generate the `after`, `prepend`, `before`, `append`,
// `insertAfter`, `insertBefore`, `appendTo`, and `prependTo` methods.
adjacencyOperators.forEach(function(operator, operatorIndex) {
var inside = operatorIndex % 2 //=> prepend, append
$.fn[operator] = function(){
// arguments can be nodes, arrays of nodes, Zepto objects and HTML strings
var argType, nodes = $.map(arguments, function(arg) {
argType = type(arg)
return argType == "object" || argType == "array" || arg == null ?
arg : zepto.fragment(arg)
}),
parent, copyByClone = this.length > 1
if (nodes.length < 1) return this
return this.each(function(_, target){
parent = inside ? target : target.parentNode
// convert all methods to a "before" operation
target = operatorIndex == 0 ? target.nextSibling :
operatorIndex == 1 ? target.firstChild :
operatorIndex == 2 ? target :
null
nodes.forEach(function(node){
if (copyByClone) node = node.cloneNode(true)
else if (!parent) return $(node).remove()
traverseNode(parent.insertBefore(node, target), function(el){
if (el.nodeName != null && el.nodeName.toUpperCase() === 'SCRIPT' &&
(!el.type || el.type === 'text/javascript') && !el.src)
window['eval'].call(window, el.innerHTML)
})
})
})
}
// after => insertAfter
// prepend => prependTo
// before => insertBefore
// append => appendTo
$.fn[inside ? operator+'To' : 'insert'+(operatorIndex ? 'Before' : 'After')] = function(html){
$(html)[operator](this)
return this
}
})
zepto.Z.prototype = $.fn
// Export internal API functions in the `$.zepto` namespace
zepto.uniq = uniq
zepto.deserializeValue = deserializeValue
$.zepto = zepto
return $
})()
window.Zepto = Zepto
window.$ === undefined && (window.$ = Zepto)
;(function($){
var $$ = $.zepto.qsa, _zid = 1, undefined,
slice = Array.prototype.slice,
isFunction = $.isFunction,
isString = function(obj){ return typeof obj == 'string' },
handlers = {},
specialEvents={},
focusinSupported = 'onfocusin' in window,
focus = { focus: 'focusin', blur: 'focusout' },
hover = { mouseenter: 'mouseover', mouseleave: 'mouseout' }
specialEvents.click = specialEvents.mousedown = specialEvents.mouseup = specialEvents.mousemove = 'MouseEvents'
function zid(element) {
return element._zid || (element._zid = _zid++)
}
function findHandlers(element, event, fn, selector) {
event = parse(event)
if (event.ns) var matcher = matcherFor(event.ns)
return (handlers[zid(element)] || []).filter(function(handler) {
return handler
&& (!event.e || handler.e == event.e)
&& (!event.ns || matcher.test(handler.ns))
&& (!fn || zid(handler.fn) === zid(fn))
&& (!selector || handler.sel == selector)
})
}
function parse(event) {
var parts = ('' + event).split('.')
return {e: parts[0], ns: parts.slice(1).sort().join(' ')}
}
function matcherFor(ns) {
return new RegExp('(?:^| )' + ns.replace(' ', ' .* ?') + '(?: |$)')
}
function eventCapture(handler, captureSetting) {
return handler.del &&
(!focusinSupported && (handler.e in focus)) ||
!!captureSetting
}
function realEvent(type) {
return hover[type] || (focusinSupported && focus[type]) || type
}
function add(element, events, fn, data, selector, delegator, capture){
var id = zid(element), set = (handlers[id] || (handlers[id] = []))
events.split(/\s/).forEach(function(event){
if (event == 'ready') return $(document).ready(fn)
var handler = parse(event)
handler.fn = fn
handler.sel = selector
// emulate mouseenter, mouseleave
if (handler.e in hover) fn = function(e){
var related = e.relatedTarget
if (!related || (related !== this && !$.contains(this, related)))
return handler.fn.apply(this, arguments)
}
handler.del = delegator
var callback = delegator || fn
handler.proxy = function(e){
e = compatible(e)
if (e.isImmediatePropagationStopped()) return
e.data = data
var result = callback.apply(element, e._args == undefined ? [e] : [e].concat(e._args))
if (result === false) e.preventDefault(), e.stopPropagation()
return result
}
handler.i = set.length
set.push(handler)
if ('addEventListener' in element)
element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
})
}
function remove(element, events, fn, selector, capture){
var id = zid(element)
;(events || '').split(/\s/).forEach(function(event){
findHandlers(element, event, fn, selector).forEach(function(handler){
delete handlers[id][handler.i]
if ('removeEventListener' in element)
element.removeEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
})
})
}
$.event = { add: add, remove: remove }
$.proxy = function(fn, context) {
if (isFunction(fn)) {
var proxyFn = function(){ return fn.apply(context, arguments) }
proxyFn._zid = zid(fn)
return proxyFn
} else if (isString(context)) {
return $.proxy(fn[context], fn)
} else {
throw new TypeError("expected function")
}
}
$.fn.bind = function(event, data, callback){
return this.on(event, data, callback)
}
$.fn.unbind = function(event, callback){
return this.off(event, callback)
}
$.fn.one = function(event, selector, data, callback){
return this.on(event, selector, data, callback, 1)
}
var returnTrue = function(){return true},
returnFalse = function(){return false},
ignoreProperties = /^([A-Z]|returnValue$|layer[XY]$)/,
eventMethods = {
preventDefault: 'isDefaultPrevented',
stopImmediatePropagation: 'isImmediatePropagationStopped',
stopPropagation: 'isPropagationStopped'
}
function compatible(event, source) {
if (source || !event.isDefaultPrevented) {
source || (source = event)
$.each(eventMethods, function(name, predicate) {
var sourceMethod = source[name]
event[name] = function(){
this[predicate] = returnTrue
return sourceMethod && sourceMethod.apply(source, arguments)
}
event[predicate] = returnFalse
})
if (source.defaultPrevented !== undefined ? source.defaultPrevented :
'returnValue' in source ? source.returnValue === false :
source.getPreventDefault && source.getPreventDefault())
event.isDefaultPrevented = returnTrue
}
return event
}
function createProxy(event) {
var key, proxy = { originalEvent: event }
for (key in event)
if (!ignoreProperties.test(key) && event[key] !== undefined) proxy[key] = event[key]
return compatible(proxy, event)
}
$.fn.delegate = function(selector, event, callback){
return this.on(event, selector, callback)
}
$.fn.undelegate = function(selector, event, callback){
return this.off(event, selector, callback)
}
$.fn.live = function(event, callback){
$(document.body).delegate(this.selector, event, callback)
return this
}
$.fn.die = function(event, callback){
$(document.body).undelegate(this.selector, event, callback)
return this
}
$.fn.on = function(event, selector, data, callback, one){
var autoRemove, delegator, $this = this
if (event && !isString(event)) {
$.each(event, function(type, fn){
$this.on(type, selector, data, fn, one)
})
return $this
}
if (!isString(selector) && !isFunction(callback) && callback !== false)
callback = data, data = selector, selector = undefined
if (isFunction(data) || data === false)
callback = data, data = undefined
if (callback === false) callback = returnFalse
return $this.each(function(_, element){
if (one) autoRemove = function(e){
remove(element, e.type, callback)
return callback.apply(this, arguments)
}
if (selector) delegator = function(e){
var evt, match = $(e.target).closest(selector, element).get(0)
if (match && match !== element) {
evt = $.extend(createProxy(e), {currentTarget: match, liveFired: element})
return (autoRemove || callback).apply(match, [evt].concat(slice.call(arguments, 1)))
}
}
add(element, event, callback, data, selector, delegator || autoRemove)
})
}
$.fn.off = function(event, selector, callback){
var $this = this
if (event && !isString(event)) {
$.each(event, function(type, fn){
$this.off(type, selector, fn)
})
return $this
}
if (!isString(selector) && !isFunction(callback) && callback !== false)
callback = selector, selector = undefined
if (callback === false) callback = returnFalse
return $this.each(function(){
remove(this, event, callback, selector)
})
}
$.fn.trigger = function(event, args){
event = (isString(event) || $.isPlainObject(event)) ? $.Event(event) : compatible(event)
event._args = args
return this.each(function(){
// items in the collection might not be DOM elements
if('dispatchEvent' in this) this.dispatchEvent(event)
else $(this).triggerHandler(event, args)
})
}
// triggers event handlers on current element just as if an event occurred,
// doesn't trigger an actual event, doesn't bubble
$.fn.triggerHandler = function(event, args){
var e, result
this.each(function(i, element){
e = createProxy(isString(event) ? $.Event(event) : event)
e._args = args
e.target = element
$.each(findHandlers(element, event.type || event), function(i, handler){
result = handler.proxy(e)
if (e.isImmediatePropagationStopped()) return false
})
})
return result
}
// shortcut methods for `.bind(event, fn)` for each event type
;('focusin focusout load resize scroll unload click dblclick '+
'mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave '+
'change select keydown keypress keyup error').split(' ').forEach(function(event) {
$.fn[event] = function(callback) {
return callback ?
this.bind(event, callback) :
this.trigger(event)
}
})
;['focus', 'blur'].forEach(function(name) {
$.fn[name] = function(callback) {
if (callback) this.bind(name, callback)
else this.each(function(){
try { this[name]() }
catch(e) {}
})
return this
}
})
$.Event = function(type, props) {
if (!isString(type)) props = type, type = props.type
var event = document.createEvent(specialEvents[type] || 'Events'), bubbles = true
if (props) for (var name in props) (name == 'bubbles') ? (bubbles = !!props[name]) : (event[name] = props[name])
event.initEvent(type, bubbles, true)
return compatible(event)
}
})(Zepto)
;(function($){
var jsonpID = 0,
document = window.document,
key,
name,
rscript = /";
// API Call
this._el.content_item.innerHTML = content;
// After Loaded
this.onLoaded();
},
// Update Media Display
_updateMediaDisplay: function() {
this._el.content_item.style.height = this.options.height + "px";
}
});
/* **********************************************
Begin TL.Media.Text.js
********************************************** */
TL.Media.Text = TL.Class.extend({
includes: [TL.Events],
// DOM ELEMENTS
_el: {
container: {},
content_container: {},
content: {},
headline: {},
date: {}
},
// Data
data: {
unique_id: "",
headline: "headline",
text: "text"
},
// Options
options: {
title: false
},
/* Constructor
================================================== */
initialize: function(data, options, add_to_container) {
TL.Util.setData(this, data);
// Merge Options
TL.Util.mergeData(this.options, options);
this._el.container = TL.Dom.create("div", "tl-text");
this._el.container.id = this.data.unique_id;
this._initLayout();
if (add_to_container) {
add_to_container.appendChild(this._el.container);
};
},
/* Adding, Hiding, Showing etc
================================================== */
show: function() {
},
hide: function() {
},
addTo: function(container) {
container.appendChild(this._el.container);
//this.onAdd();
},
removeFrom: function(container) {
container.removeChild(this._el.container);
},
headlineHeight: function() {
return this._el.headline.offsetHeight + 40;
},
addDateText: function(str) {
this._el.date.innerHTML = str;
},
/* Events
================================================== */
onLoaded: function() {
this.fire("loaded", this.data);
},
onAdd: function() {
this.fire("added", this.data);
},
onRemove: function() {
this.fire("removed", this.data);
},
/* Private Methods
================================================== */
_initLayout: function () {
// Create Layout
this._el.content_container = TL.Dom.create("div", "tl-text-content-container", this._el.container);
// Date
this._el.date = TL.Dom.create("h3", "tl-headline-date", this._el.content_container);
// Headline
if (this.data.headline != "") {
var headline_class = "tl-headline";
if (this.options.title) {
headline_class = "tl-headline tl-headline-title";
}
this._el.headline = TL.Dom.create("h2", headline_class, this._el.content_container);
this._el.headline.innerHTML = this.data.headline;
}
// Text
if (this.data.text != "") {
var text_content = "";
text_content += TL.Util.htmlify(this.options.autolink == true ? TL.Util.linkify(this.data.text) : this.data.text);
trace(this.data.text);
this._el.content = TL.Dom.create("div", "tl-text-content", this._el.content_container);
this._el.content.innerHTML = text_content;
trace(text_content);
trace(this._el.content)
}
// Fire event that the slide is loaded
this.onLoaded();
}
});
/* **********************************************
Begin TL.Media.Twitter.js
********************************************** */
/* TL.Media.Twitter
Produces Twitter Display
================================================== */
TL.Media.Twitter = TL.Media.extend({
includes: [TL.Events],
/* Load the media
================================================== */
_loadMedia: function() {
var api_url,
self = this;
// Create Dom element
this._el.content_item = TL.Dom.create("div", "tl-media-twitter", this._el.content);
this._el.content_container.className = "tl-media-content-container tl-media-content-container-text";
// Get Media ID
if(this.data.url.match("^(https?:)?\/*(www.)?twitter\.com"))
{
if (this.data.url.match("status\/")) {
this.media_id = this.data.url.split("status\/")[1];
} else if (this.data.url.match("statuses\/")) {
this.media_id = this.data.url.split("statuses\/")[1];
} else {
this.media_id = "";
}
}
else if(this.data.url.match("";
tweetuser = d.author_url.split("twitter.com\/")[1];
tweet_status_temp = d.html.split("<\/p>\—")[1].split("")[0];
tweet_status_date = tweet_status_temp.split("\"\>")[1].split("<\/a>")[0];
// Open links in new window
tweet_text = tweet_text.replace(/";
tweet += "";
tweet += "" + "";
tweet += "";
tweet += "";
// Add to DOM
this._el.content_item.innerHTML = tweet;
// After Loaded
this.onLoaded();
}
},
updateMediaDisplay: function() {
},
_updateMediaDisplay: function() {
},
});
/* **********************************************
Begin TL.Media.TwitterEmbed.js
********************************************** */
/* TL.Media.TwitterEmbed
Produces Twitter Display
================================================== */
var mediaID;
TL.Media.TwitterEmbed = TL.Media.extend({
includes: [TL.Events],
/* Load the media
================================================== */
_loadMedia: function() {
var api_url,
self = this;
// Create Dom element
this._el.content_item = TL.Dom.create("div", "tl-media-twitter", this._el.content);
this._el.content_container.className = "tl-media-content-container tl-media-content-container-text";
// Get Media ID
var found = this.data.url.match(/(status|statuses)\/(\d+)/);
if (found && found.length > 2) {
this.media_id = found[2];
} else {
self.loadErrorDisplay(self._("twitterembed_invalidurl_err"));
return;
}
// API URL
api_url = "https://api.twitter.com/1/statuses/oembed.json?id=" + this.media_id + "&omit_script=true&include_entities=true&callback=?";
window.twttr = (function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0],
t = window.twttr || {};
if (d.getElementById(id)) return t;
js = d.createElement(s);
js.id = id;
js.src = "https://platform.twitter.com/widgets.js";
fjs.parentNode.insertBefore(js, fjs);
t._e = [];
t.ready = function(f) {
t._e.push(f);
};
return t;
}(document, "script", "twitter-wjs"));
mediaID = this.media_id;
// API Call
TL.ajax({
type: 'GET',
url: api_url,
dataType: 'json', //json data type
success: function(d){
self.createMedia(d);
},
error:function(xhr, type){
var error_text = "";
error_text += self._("twitter_load_err") + "
" + self.media_id + "
" + type;
self.loadErrorDisplay(error_text);
}
});
},
createMedia: function(d) {
trace("create_media")
var tweet = "",
tweet_text = "",
tweetuser = "",
tweet_status_temp = "",
tweet_status_url = "",
tweet_status_date = "";
// TWEET CONTENT
tweet_text = d.html.split("<\/p>\—")[0] + "";
console.log(tweet_text);
tweetuser = d.author_url.split("twitter.com\/")[1];
tweet_status_temp = d.html.split("<\/p>\—")[1].split("")[0];
tweet_status_date = tweet_status_temp.split("\"\>")[1].split("<\/a>")[0];
// Open links in new window
tweet_text = tweet_text.replace(/";
tweet += "";
tweet += "";
tweet += "";
// Add to DOM
this._el.content_item.innerHTML = tweet;
// After Loaded
this.onLoaded();
}
},
updateMediaDisplay: function() {
},
_updateMediaDisplay: function() {
}
});
/* **********************************************
Begin TL.Media.Vimeo.js
********************************************** */
/* TL.Media.Vimeo
================================================== */
TL.Media.Vimeo = TL.Media.extend({
includes: [TL.Events],
/* Load the media
================================================== */
_loadMedia: function() {
var api_url,
self = this;
// Create Dom element
this._el.content_item = TL.Dom.create("div", "tl-media-item tl-media-iframe tl-media-vimeo tl-media-shadow", this._el.content);
// Get Media ID
this.media_id = this.data.url.split(/video\/|\/\/vimeo\.com\//)[1].split(/[?&]/)[0];
var start_time = null;
// Get start time
if (this.data.url.match(/#t=([^&]+).*/)) {
start_time = this.data.url.match(/#t=([^&]+).*/)[1];
}
// API URL
api_url = "https://player.vimeo.com/video/" + this.media_id + "?api=1&title=0&byline=0&portrait=0&color=ffffff";
if (start_time) {
api_url = api_url += '&#t=' + start_time;
}
this.player = TL.Dom.create("iframe", "", this._el.content_item);
// Media Loaded Event
this.player.addEventListener('load', function(e) {
self.onMediaLoaded();
});
this.player.width = "100%";
this.player.height = "100%";
this.player.frameBorder = "0";
this.player.src = api_url;
this.player.setAttribute('allowfullscreen', '');
this.player.setAttribute('webkitallowfullscreen', '');
this.player.setAttribute('mozallowfullscreen', '');
// After Loaded
this.onLoaded();
},
// Update Media Display
_updateMediaDisplay: function() {
this._el.content_item.style.height = TL.Util.ratio.r16_9({w:this._el.content_item.offsetWidth}) + "px";
},
_stopMedia: function() {
try {
this.player.contentWindow.postMessage(JSON.stringify({method: "pause"}), "https://player.vimeo.com");
}
catch(err) {
trace(err);
}
}
});
/* **********************************************
Begin TL.Media.Vine.js
********************************************** */
/* TL.Media.Vine
================================================== */
TL.Media.Vine = TL.Media.extend({
includes: [TL.Events],
/* Load the media
================================================== */
_loadMedia: function() {
var api_url,
self = this;
// Create Dom element
this._el.content_item = TL.Dom.create("div", "tl-media-item tl-media-iframe tl-media-vine tl-media-shadow", this._el.content);
// Get Media ID
this.media_id = this.data.url.split("vine.co/v/")[1];
// API URL
api_url = "https://vine.co/v/" + this.media_id + "/embed/simple";
// API Call
this._el.content_item.innerHTML = ""
// After Loaded
this.onLoaded();
},
// Update Media Display
_updateMediaDisplay: function() {
var size = TL.Util.ratio.square({w:this._el.content_item.offsetWidth , h:this.options.height});
this._el.content_item.style.height = size.h + "px";
},
_stopMedia: function() {
this._el.content_item.querySelector("iframe").contentWindow.postMessage('pause', '*');
}
});
/* **********************************************
Begin TL.Media.Website.js
********************************************** */
/* TL.Media.Website
Uses Embedly
http://embed.ly/docs/api/extract/endpoints/1/extract
================================================== */
TL.Media.Website = TL.Media.extend({
includes: [TL.Events],
/* Load the media
================================================== */
_loadMedia: function() {
var self = this;
// Get Media ID
this.media_id = this.data.url.replace(/.*?:\/\//g, "");
if (this.options.api_key_embedly) {
// API URL
api_url = "https://api.embed.ly/1/extract?key=" + this.options.api_key_embedly + "&url=" + this.media_id + "&callback=?";
// API Call
TL.getJSON(api_url, function(d) {
self.createMedia(d);
});
} else {
this.createCardContent();
}
},
createCardContent: function() {
(function(w, d){
var id='embedly-platform', n = 'script';
if (!d.getElementById(id)){
w.embedly = w.embedly || function() {(w.embedly.q = w.embedly.q || []).push(arguments);};
var e = d.createElement(n); e.id = id; e.async=1;
e.src = ('https:' === document.location.protocol ? 'https' : 'http') + '://cdn.embedly.com/widgets/platform.js';
var s = d.getElementsByTagName(n)[0];
s.parentNode.insertBefore(e, s);
}
})(window, document);
var content = "" + this.data.url + "";
this._setContent(content);
},
createMedia: function(d) { // this costs API credits...
var content = "";
content += "";
if (d.images) {
if (d.images[0]) {
trace(d.images[0].url);
content += "";
}
}
if (d.favicon_url) {
content += "";
}
content += "" + d.provider_name + "
";
content += "" + d.description + "
";
this._setContent(content);
},
_setContent: function(content) {
// Create Dom element
this._el.content_item = TL.Dom.create("div", "tl-media-item tl-media-website", this._el.content);
this._el.content_container.className = "tl-media-content-container tl-media-content-container-text";
this._el.content_item.innerHTML = content;
// After Loaded
this.onLoaded();
},
updateMediaDisplay: function() {
},
_updateMediaDisplay: function() {
}
});
/* **********************************************
Begin TL.Media.Wikipedia.js
********************************************** */
/* TL.Media.Wikipedia
================================================== */
TL.Media.Wikipedia = TL.Media.extend({
includes: [TL.Events],
/* Load the media
================================================== */
_loadMedia: function() {
var api_url,
api_language,
self = this;
// Create Dom element
this._el.content_item = TL.Dom.create("div", "tl-media-item tl-media-wikipedia", this._el.content);
this._el.content_container.className = "tl-media-content-container tl-media-content-container-text";
// Get Media ID
this.media_id = this.data.url.split("wiki\/")[1].split("#")[0].replace("_", " ");
this.media_id = this.media_id.replace(" ", "%20");
api_language = this.data.url.split("//")[1].split(".wikipedia")[0];
// API URL
api_url = "https://" + api_language + ".wikipedia.org/w/api.php?action=query&prop=extracts|pageimages&redirects=&titles=" + this.media_id + "&exintro=1&format=json&callback=?";
// API Call
TL.ajax({
type: 'GET',
url: api_url,
dataType: 'json', //json data type
success: function(d){
self.createMedia(d);
},
error:function(xhr, type){
var error_text = "";
error_text += self._("wikipedia_load_err") + "
" + self.media_id + "
" + type;
self.loadErrorDisplay(error_text);
}
});
},
createMedia: function(d) {
var wiki = "";
if (d.query) {
var content = "",
wiki = {
entry: {},
title: "",
text: "",
extract: "",
paragraphs: 1,
page_image: "",
text_array: []
};
wiki.entry = TL.Util.getObjectAttributeByIndex(d.query.pages, 0);
wiki.extract = wiki.entry.extract;
wiki.title = wiki.entry.title;
wiki.page_image = wiki.entry.thumbnail;
if (wiki.extract.match("")) {
wiki.text_array = wiki.extract.split("
");
} else {
wiki.text_array.push(wiki.extract);
}
for(var i = 0; i < wiki.text_array.length; i++) {
if (i+1 <= wiki.paragraphs && i+1 < wiki.text_array.length) {
wiki.text += "
" + wiki.text_array[i+1];
}
}
content += "";
content += "
";
content += "
" + this._('wikipedia') + " ";
if (wiki.page_image) {
//content += "";
}
content += wiki.text;
if (wiki.extract.match("REDIRECT")) {
} else {
// Add to DOM
this._el.content_item.innerHTML = content;
// After Loaded
this.onLoaded();
}
}
},
updateMediaDisplay: function() {
},
_updateMediaDisplay: function() {
}
});
/* **********************************************
Begin TL.Media.Wistia.js
********************************************** */
/* TL.Media.Wistia
================================================== */
TL.Media.Wistia = TL.Media.extend({
includes: [TL.Events],
/* Load the media
================================================== */
_loadMedia: function() {
var api_url,
self = this;
// Create Dom element
this._el.content_item = TL.Dom.create("div", "tl-media-item tl-media-iframe tl-media-wistia tl-media-shadow", this._el.content);
// Get Media ID
this.media_id = this.data.url.split(/https?:\/\/(.+)?(wistia\.com|wi\.st)\/medias\/(.*)/)[3];
// API URL
api_url = "https://fast.wistia.com/embed/iframe/" + this.media_id + "?version=v1&controlsVisibleOnLoad=true&playerColor=aae3d8";
this.player = TL.Dom.create("iframe", "", this._el.content_item);
// Media Loaded Event
this.player.addEventListener('load', function(e) {
self.onMediaLoaded();
});
this.player.width = "100%";
this.player.height = "100%";
this.player.frameBorder = "0";
this.player.src = api_url;
this.player.setAttribute('allowfullscreen', '');
this.player.setAttribute('webkitallowfullscreen', '');
this.player.setAttribute('mozallowfullscreen', '');
// After Loaded
this.onLoaded();
},
// Update Media Display
_updateMediaDisplay: function() {
this._el.content_item.style.height = TL.Util.ratio.r16_9({w:this._el.content_item.offsetWidth}) + "px";
},
_stopMedia: function() {
try {
this.player.contentWindow.postMessage(JSON.stringify({method: "pause"}), "https://player.vimeo.com");
}
catch(err) {
trace(err);
}
}
});
/* **********************************************
Begin TL.Media.YouTube.js
********************************************** */
/* TL.Media.YouTube
================================================== */
TL.Media.YouTube = TL.Media.extend({
includes: [TL.Events],
/* Load the media
================================================== */
_loadMedia: function() {
var self = this,
url_vars;
this.youtube_loaded = false;
// Create Dom element
this._el.content_item = TL.Dom.create("div", "tl-media-item tl-media-youtube tl-media-shadow", this._el.content);
this._el.content_item.id = TL.Util.unique_ID(7)
// URL Vars
url_vars = TL.Util.getUrlVars(this.data.url);
// Get Media ID
this.media_id = {};
if (this.data.url.match('v=')) {
this.media_id.id = url_vars["v"];
} else if (this.data.url.match('\/embed\/')) {
this.media_id.id = this.data.url.split("embed\/")[1].split(/[?&]/)[0];
} else if (this.data.url.match(/v\/|v=|youtu\.be\//)){
this.media_id.id = this.data.url.split(/v\/|v=|youtu\.be\//)[1].split(/[?&]/)[0];
} else {
trace("YOUTUBE IN URL BUT NOT A VALID VIDEO");
}
// Get start second
if (this.data.url.match("start=")) {
this.media_id.start = parseInt(this.data.url.split("start=")[1], 10);
}
else if (this.data.url.match("t=")) {
this.media_id.start = parseInt(this.data.url.split("t=")[1], 10);
}
//Get end second
if (this.data.url.match("end=")) {
this.media_id.end = parseInt(this.data.url.split("end=")[1], 10);
}
this.media_id.hd = Boolean(typeof(url_vars["hd"]) != 'undefined');
// API Call
TL.Load.js('https://www.youtube.com/iframe_api', function() {
self.createMedia();
});
},
// Update Media Display
_updateMediaDisplay: function() {
//this.el.content_item = document.getElementById(this._el.content_item.id);
this._el.content_item.style.height = TL.Util.ratio.r16_9({w:this.options.width}) + "px";
this._el.content_item.style.width = this.options.width + "px";
},
_stopMedia: function() {
if (this.youtube_loaded) {
try {
if(this.player.getPlayerState() == YT.PlayerState.PLAYING) {
this.player.pauseVideo();
}
}
catch(err) {
trace(err);
}
}
},
createMedia: function() {
var self = this;
clearTimeout(this.timer);
if(typeof YT != 'undefined' && typeof YT.Player != 'undefined') {
// Create Player
this.player = new YT.Player(this._el.content_item.id, {
playerVars: {
enablejsapi: 1,
color: 'white',
controls: 1,
start: this.media_id.start,
end: this.media_id.end,
fs: 1
},
videoId: this.media_id.id,
events: {
onReady: function() {
self.onPlayerReady();
// After Loaded
self.onLoaded();
},
'onStateChange': self.onStateChange
}
});
} else {
this.timer = setTimeout(function() {
self.createMedia();
}, 1000);
}
},
/* Events
================================================== */
onPlayerReady: function(e) {
this.youtube_loaded = true;
this._el.content_item = document.getElementById(this._el.content_item.id);
this.onMediaLoaded();
},
onStateChange: function(e) {
if(e.data == YT.PlayerState.ENDED) {
e.target.seekTo(0);
e.target.pauseVideo();
}
}
});
/* **********************************************
Begin TL.Media.Audio.js
********************************************** */
/* TL.Media.Audio
Produces audio assets.
Takes a data object and populates a dom object
================================================== */
TL.Media.Audio = TL.Media.extend({
includes: [TL.Events],
/* Load the media
================================================== */
_loadMedia: function() {
// Loading Message
this.loadingMessage();
// Create media?
if(!this.options.background) {
this.createMedia();
}
// After loaded
this.onLoaded();
},
createMedia: function() {
var self = this,
audio_class = "tl-media-item tl-media-audio tl-media-shadow";
// Link
if (this.data.link) {
this._el.content_link = TL.Dom.create("a", "", this._el.content);
this._el.content_link.href = this.data.link;
this._el.content_link.target = "_blank";
this._el.content_item = TL.Dom.create("audio", audio_class, this._el.content_link);
} else {
this._el.content_item = TL.Dom.create("audio", audio_class, this._el.content);
}
this._el.content_item.controls = true;
this._el.source_item = TL.Dom.create("source", "", this._el.content_item);
// Media Loaded Event
this._el.content_item.addEventListener('load', function(e) {
self.onMediaLoaded();
});
this._el.source_item.src = this.data.url;
this._el.source_item.type = this._getType(this.data.url, this.data.mediatype.match_str);
this._el.content_item.innerHTML += "Your browser doesn't support HTML5 audio with " + this._el.source_item.type;
},
_updateMediaDisplay: function(layout) {
if(TL.Browser.firefox) {
this._el.content_item.style.width = "auto";
}
},
_getType: function(url, reg) {
var ext = url.match(reg);
var type = "audio/"
switch(ext[1]) {
case "mp3":
type += "mpeg";
break;
case "wav":
type += "wav";
break;
case "m4a":
type += "mp4";
break;
default:
type = "audio";
break;
}
return type
}
});
/* **********************************************
Begin TL.Media.Video.js
********************************************** */
/* TL.Media.Video
Produces video assets.
Takes a data object and populates a dom object
================================================== */
TL.Media.Video = TL.Media.extend({
includes: [TL.Events],
/* Load the media
================================================== */
_loadMedia: function() {
// Loading Message
this.loadingMessage();
// Create media?
if(!this.options.background) {
this.createMedia();
}
// After loaded
this.onLoaded();
},
createMedia: function() {
var self = this,
video_class = "tl-media-item tl-media-video tl-media-shadow";
// Link
if (this.data.link) {
this._el.content_link = TL.Dom.create("a", "", this._el.content);
this._el.content_link.href = this.data.link;
this._el.content_link.target = "_blank";
this._el.content_item = TL.Dom.create("video", video_class, this._el.content_link);
} else {
this._el.content_item = TL.Dom.create("video", video_class, this._el.content);
}
this._el.content_item.controls = true;
this._el.source_item = TL.Dom.create("source", "", this._el.content_item);
// Media Loaded Event
this._el.content_item.addEventListener('load', function(e) {
self.onMediaLoaded();
});
this._el.source_item.src = this.data.url;
this._el.source_item.type = this._getType(this.data.url, this.data.mediatype.match_str);
this._el.content_item.innerHTML += "Your browser doesn't support HTML5 video with " + this._el.source_item.type;
},
_updateMediaDisplay: function(layout) {
if(TL.Browser.firefox) {
this._el.content_item.style.width = "auto";
}
},
_getType: function(url, reg) {
var ext = url.match(reg);
var type = "video/"
switch(ext[1]) {
case "mp4":
type += "mp4";
break;
default:
type = "video";
break;
}
return type
}
});
/* **********************************************
Begin TL.Slide.js
********************************************** */
/* TL.Slide
Creates a slide. Takes a data object and
populates the slide with content.
================================================== */
TL.Slide = TL.Class.extend({
includes: [TL.Events, TL.DomMixins, TL.I18NMixins],
_el: {},
/* Constructor
================================================== */
initialize: function(data, options, title_slide) {
// DOM Elements
this._el = {
container: {},
scroll_container: {},
background: {},
content_container: {},
content: {}
};
// Components
this._media = null;
this._mediaclass = {};
this._text = {};
this._background_media = null;
// State
this._state = {
loaded: false
};
this.has = {
headline: false,
text: false,
media: false,
title: false,
background: {
image: false,
color: false,
color_value :""
}
}
this.has.title = title_slide;
// Data
this.data = {
unique_id: null,
background: null,
start_date: null,
end_date: null,
location: null,
text: null,
media: null,
autolink: true
};
// Options
this.options = {
// animation
duration: 1000,
slide_padding_lr: 40,
ease: TL.Ease.easeInSpline,
width: 600,
height: 600,
skinny_size: 650,
media_name: ""
};
// Actively Displaying
this.active = false;
// Animation Object
this.animator = {};
// Merge Data and Options
TL.Util.mergeData(this.options, options);
TL.Util.mergeData(this.data, data);
this._initLayout();
this._initEvents();
},
/* Adding, Hiding, Showing etc
================================================== */
show: function() {
this.animator = TL.Animate(this._el.slider_container, {
left: -(this._el.container.offsetWidth * n) + "px",
duration: this.options.duration,
easing: this.options.ease
});
},
hide: function() {
},
setActive: function(is_active) {
this.active = is_active;
if (this.active) {
if (this.data.background) {
this.fire("background_change", this.has.background);
}
this.loadMedia();
} else {
this.stopMedia();
}
},
addTo: function(container) {
container.appendChild(this._el.container);
//this.onAdd();
},
removeFrom: function(container) {
container.removeChild(this._el.container);
},
updateDisplay: function(w, h, l) {
this._updateDisplay(w, h, l);
},
loadMedia: function() {
var self = this;
if (this._media && !this._state.loaded) {
this._media.loadMedia();
this._state.loaded = true;
}
if(this._background_media && !this._background_media._state.loaded) {
this._background_media.on("loaded", function() {
self._updateBackgroundDisplay();
});
this._background_media.loadMedia();
}
},
stopMedia: function() {
if (this._media && this._state.loaded) {
this._media.stopMedia();
}
},
getBackground: function() {
return this.has.background;
},
scrollToTop: function() {
this._el.container.scrollTop = 0;
},
getFormattedDate: function() {
if (TL.Util.trim(this.data.display_date).length > 0) {
return this.data.display_date;
}
var date_text = "";
if(!this.has.title) {
if (this.data.end_date) {
date_text = " — " + this.data.end_date.getDisplayDate(this.getLanguage());
}
if (this.data.start_date) {
date_text = this.data.start_date.getDisplayDate(this.getLanguage()) + date_text;
}
}
return date_text;
},
/* Events
================================================== */
/* Private Methods
================================================== */
_initLayout: function () {
// Create Layout
this._el.container = TL.Dom.create("div", "tl-slide");
if (this.has.title) {
this._el.container.className = "tl-slide tl-slide-titleslide";
}
if (this.data.unique_id) {
this._el.container.id = this.data.unique_id;
}
this._el.scroll_container = TL.Dom.create("div", "tl-slide-scrollable-container", this._el.container);
this._el.content_container = TL.Dom.create("div", "tl-slide-content-container", this._el.scroll_container);
this._el.content = TL.Dom.create("div", "tl-slide-content", this._el.content_container);
this._el.background = TL.Dom.create("div", "tl-slide-background", this._el.container);
// Style Slide Background
if (this.data.background) {
if (this.data.background.url) {
var media_type = TL.MediaType(this.data.background, true);
if(media_type) {
this._background_media = new media_type.cls(this.data.background, {background: 1});
this.has.background.image = true;
this._el.container.className += ' tl-full-image-background';
this.has.background.color_value = "#000";
this._el.background.style.display = "block";
}
}
if (this.data.background.color) {
this.has.background.color = true;
this._el.container.className += ' tl-full-color-background';
this.has.background.color_value = this.data.background.color;
//this._el.container.style.backgroundColor = this.data.background.color;
//this._el.background.style.backgroundColor = this.data.background.color;
//this._el.background.style.display = "block";
}
if (this.data.background.text_background) {
this._el.container.className += ' tl-text-background';
}
}
// Determine Assets for layout and loading
if (this.data.media && this.data.media.url && this.data.media.url != "") {
this.has.media = true;
}
if (this.data.text && this.data.text.text) {
this.has.text = true;
}
if (this.data.text && this.data.text.headline) {
this.has.headline = true;
}
// Create Media
if (this.has.media) {
// Determine the media type
this.data.media.mediatype = TL.MediaType(this.data.media);
this.options.media_name = this.data.media.mediatype.name;
this.options.media_type = this.data.media.mediatype.type;
this.options.autolink = this.data.autolink;
// Create a media object using the matched class name
this._media = new this.data.media.mediatype.cls(this.data.media, this.options);
}
// Create Text
if (this.has.text || this.has.headline) {
this._text = new TL.Media.Text(this.data.text, {title:this.has.title,language: this.options.language, autolink: this.data.autolink });
this._text.addDateText(this.getFormattedDate());
}
// Add to DOM
if (!this.has.text && !this.has.headline && this.has.media) {
TL.DomUtil.addClass(this._el.container, 'tl-slide-media-only');
this._media.addTo(this._el.content);
} else if (this.has.headline && this.has.media && !this.has.text) {
TL.DomUtil.addClass(this._el.container, 'tl-slide-media-only');
this._text.addTo(this._el.content);
this._media.addTo(this._el.content);
} else if (this.has.text && this.has.media) {
this._media.addTo(this._el.content);
this._text.addTo(this._el.content);
} else if (this.has.text || this.has.headline) {
TL.DomUtil.addClass(this._el.container, 'tl-slide-text-only');
this._text.addTo(this._el.content);
}
// Fire event that the slide is loaded
this.onLoaded();
},
_initEvents: function() {
},
// Update Display
_updateDisplay: function(width, height, layout) {
var content_width,
content_padding_left = this.options.slide_padding_lr,
content_padding_right = this.options.slide_padding_lr;
if (width) {
this.options.width = width;
} else {
this.options.width = this._el.container.offsetWidth;
}
content_width = this.options.width - (this.options.slide_padding_lr * 2);
if(TL.Browser.mobile && (this.options.width <= this.options.skinny_size)) {
content_padding_left = 0;
content_padding_right = 0;
content_width = this.options.width;
} else if (layout == "landscape") {
} else if (this.options.width <= this.options.skinny_size) {
content_padding_left = 50;
content_padding_right = 50;
content_width = this.options.width - content_padding_left - content_padding_right;
} else {
}
this._el.content.style.paddingLeft = content_padding_left + "px";
this._el.content.style.paddingRight = content_padding_right + "px";
this._el.content.style.width = content_width + "px";
if (height) {
this.options.height = height;
//this._el.scroll_container.style.height = this.options.height + "px";
} else {
this.options.height = this._el.container.offsetHeight;
}
if (this._media) {
if (!this.has.text && this.has.headline) {
this._media.updateDisplay(content_width, (this.options.height - this._text.headlineHeight()), layout);
} else if (!this.has.text && !this.has.headline) {
this._media.updateDisplay(content_width, this.options.height, layout);
} else if (this.options.width <= this.options.skinny_size) {
this._media.updateDisplay(content_width, this.options.height, layout);
} else {
this._media.updateDisplay(content_width/2, this.options.height, layout);
}
}
this._updateBackgroundDisplay();
},
_updateBackgroundDisplay: function() {
if(this._background_media && this._background_media._state.loaded) {
this._el.background.style.backgroundImage = "url('" + this._background_media.getImageURL(this.options.width, this.options.height) + "')";
}
}
});
/* **********************************************
Begin TL.SlideNav.js
********************************************** */
/* TL.SlideNav
encapsulate DOM display/events for the
'next' and 'previous' buttons on a slide.
================================================== */
// TODO null out data
TL.SlideNav = TL.Class.extend({
includes: [TL.Events, TL.DomMixins],
_el: {},
/* Constructor
================================================== */
initialize: function(data, options, add_to_container) {
// DOM ELEMENTS
this._el = {
container: {},
content_container: {},
icon: {},
title: {},
description: {}
};
// Media Type
this.mediatype = {};
// Data
this.data = {
title: "Navigation",
description: "Description",
date: "Date"
};
//Options
this.options = {
direction: "previous"
};
this.animator = null;
// Merge Data and Options
TL.Util.mergeData(this.options, options);
TL.Util.mergeData(this.data, data);
this._el.container = TL.Dom.create("div", "tl-slidenav-" + this.options.direction);
if (TL.Browser.mobile) {
this._el.container.setAttribute("ontouchstart"," ");
}
this._initLayout();
this._initEvents();
if (add_to_container) {
add_to_container.appendChild(this._el.container);
};
},
/* Update Content
================================================== */
update: function(slide) {
var d = {
title: "",
description: "",
date: slide.getFormattedDate()
};
if (slide.data.text) {
if (slide.data.text.headline) {
d.title = slide.data.text.headline;
}
}
this._update(d);
},
/* Color
================================================== */
setColor: function(inverted) {
if (inverted) {
this._el.content_container.className = 'tl-slidenav-content-container tl-slidenav-inverted';
} else {
this._el.content_container.className = 'tl-slidenav-content-container';
}
},
/* Events
================================================== */
_onMouseClick: function() {
this.fire("clicked", this.options);
},
/* Private Methods
================================================== */
_update: function(d) {
// update data
this.data = TL.Util.mergeData(this.data, d);
// Title
this._el.title.innerHTML = TL.Util.unlinkify(this.data.title);
// Date
this._el.description.innerHTML = TL.Util.unlinkify(this.data.date);
},
_initLayout: function () {
// Create Layout
this._el.content_container = TL.Dom.create("div", "tl-slidenav-content-container", this._el.container);
this._el.icon = TL.Dom.create("div", "tl-slidenav-icon", this._el.content_container);
this._el.title = TL.Dom.create("div", "tl-slidenav-title", this._el.content_container);
this._el.description = TL.Dom.create("div", "tl-slidenav-description", this._el.content_container);
this._el.icon.innerHTML = " "
this._update();
},
_initEvents: function () {
TL.DomEvent.addListener(this._el.container, 'click', this._onMouseClick, this);
}
});
/* **********************************************
Begin TL.StorySlider.js
********************************************** */
/* StorySlider
is the central class of the API - it is used to create a StorySlider
Events:
nav_next
nav_previous
slideDisplayUpdate
loaded
slideAdded
slideLoaded
slideRemoved
================================================== */
TL.StorySlider = TL.Class.extend({
includes: [TL.Events, TL.I18NMixins],
/* Private Methods
================================================== */
initialize: function (elem, data, options, init) {
// DOM ELEMENTS
this._el = {
container: {},
background: {},
slider_container_mask: {},
slider_container: {},
slider_item_container: {}
};
this._nav = {};
this._nav.previous = {};
this._nav.next = {};
// Slide Spacing
this.slide_spacing = 0;
// Slides Array
this._slides = [];
// Swipe Object
this._swipable;
// Preload Timer
this.preloadTimer;
// Message
this._message;
// Current Slide
this.current_id = '';
// Data Object
this.data = {};
this.options = {
id: "",
layout: "portrait",
width: 600,
height: 600,
default_bg_color: {r:255, g:255, b:255},
slide_padding_lr: 40, // padding on slide of slide
start_at_slide: 1,
slide_default_fade: "0%", // landscape fade
// animation
duration: 1000,
ease: TL.Ease.easeInOutQuint,
// interaction
dragging: true,
trackResize: true
};
// Main element ID
if (typeof elem === 'object') {
this._el.container = elem;
this.options.id = TL.Util.unique_ID(6, "tl");
} else {
this.options.id = elem;
this._el.container = TL.Dom.get(elem);
}
if (!this._el.container.id) {
this._el.container.id = this.options.id;
}
// Animation Object
this.animator = null;
// Merge Data and Options
TL.Util.mergeData(this.options, options);
TL.Util.mergeData(this.data, data);
if (init) {
this.init();
}
},
init: function() {
this._initLayout();
this._initEvents();
this._initData();
this._updateDisplay();
// Go to initial slide
this.goTo(this.options.start_at_slide);
this._onLoaded();
},
/* Slides
================================================== */
_addSlide:function(slide) {
slide.addTo(this._el.slider_item_container);
slide.on('added', this._onSlideAdded, this);
slide.on('background_change', this._onBackgroundChange, this);
},
_createSlide: function(d, title_slide, n) {
var slide = new TL.Slide(d, this.options, title_slide);
this._addSlide(slide);
if(n < 0) {
this._slides.push(slide);
} else {
this._slides.splice(n, 0, slide);
}
},
_createSlides: function(array) {
for (var i = 0; i < array.length; i++) {
if (array[i].unique_id == "") {
array[i].unique_id = TL.Util.unique_ID(6, "tl-slide");
}
this._createSlide(array[i], false, -1);
}
},
_removeSlide: function(slide) {
slide.removeFrom(this._el.slider_item_container);
slide.off('added', this._onSlideRemoved, this);
slide.off('background_change', this._onBackgroundChange);
},
_destroySlide: function(n) {
this._removeSlide(this._slides[n]);
this._slides.splice(n, 1);
},
_findSlideIndex: function(n) {
var _n = n;
if (typeof n == 'string' || n instanceof String) {
_n = TL.Util.findArrayNumberByUniqueID(n, this._slides, "unique_id");
}
return _n;
},
/* Public
================================================== */
updateDisplay: function(w, h, a, l) {
this._updateDisplay(w, h, a, l);
},
// Create a slide
createSlide: function(d, n) {
this._createSlide(d, false, n);
},
// Create Many Slides from an array
createSlides: function(array) {
this._createSlides(array);
},
// Destroy slide by index
destroySlide: function(n) {
this._destroySlide(n);
},
// Destroy slide by id
destroySlideId: function(id) {
this.destroySlide(this._findSlideIndex(id));
},
/* Navigation
================================================== */
goTo: function(n, fast, displayupdate) {
n = parseInt(n);
if (isNaN(n)) n = 0;
var self = this;
this.changeBackground({color_value:"", image:false});
// Clear Preloader Timer
if (this.preloadTimer) {
clearTimeout(this.preloadTimer);
}
// Set Slide Active State
for (var i = 0; i < this._slides.length; i++) {
this._slides[i].setActive(false);
}
if (n < this._slides.length && n >= 0) {
this.current_id = this._slides[n].data.unique_id;
// Stop animation
if (this.animator) {
this.animator.stop();
}
if (this._swipable) {
this._swipable.stopMomentum();
}
if (fast) {
this._el.slider_container.style.left = -(this.slide_spacing * n) + "px";
this._onSlideChange(displayupdate);
} else {
this.animator = TL.Animate(this._el.slider_container, {
left: -(this.slide_spacing * n) + "px",
duration: this.options.duration,
easing: this.options.ease,
complete: this._onSlideChange(displayupdate)
});
}
// Set Slide Active State
this._slides[n].setActive(true);
// Update Navigation and Info
if (this._slides[n + 1]) {
this.showNav(this._nav.next, true);
this._nav.next.update(this._slides[n + 1]);
} else {
this.showNav(this._nav.next, false);
}
if (this._slides[n - 1]) {
this.showNav(this._nav.previous, true);
this._nav.previous.update(this._slides[n - 1]);
} else {
this.showNav(this._nav.previous, false);
}
// Preload Slides
this.preloadTimer = setTimeout(function() {
self.preloadSlides(n);
}, this.options.duration);
}
},
goToId: function(id, fast, displayupdate) {
this.goTo(this._findSlideIndex(id), fast, displayupdate);
},
preloadSlides: function(n) {
if (this._slides[n + 1]) {
this._slides[n + 1].loadMedia();
this._slides[n + 1].scrollToTop();
}
if (this._slides[n + 2]) {
this._slides[n + 2].loadMedia();
this._slides[n + 2].scrollToTop();
}
if (this._slides[n - 1]) {
this._slides[n - 1].loadMedia();
this._slides[n - 1].scrollToTop();
}
if (this._slides[n - 2]) {
this._slides[n - 2].loadMedia();
this._slides[n - 2].scrollToTop();
}
},
next: function() {
var n = this._findSlideIndex(this.current_id);
if ((n + 1) < (this._slides.length)) {
this.goTo(n + 1);
} else {
this.goTo(n);
}
},
previous: function() {
var n = this._findSlideIndex(this.current_id);
if (n - 1 >= 0) {
this.goTo(n - 1);
} else {
this.goTo(n);
}
},
showNav: function(nav_obj, show) {
if (this.options.width <= 500 && TL.Browser.mobile) {
} else {
if (show) {
nav_obj.show();
} else {
nav_obj.hide();
}
}
},
changeBackground: function(bg) {
var bg_color = {r:256, g:256, b:256},
bg_color_rgb;
if (bg.color_value && bg.color_value != "") {
bg_color = TL.Util.hexToRgb(bg.color_value);
if (!bg_color) {
trace("Invalid color value " + bg.color_value);
bg_color = this.options.default_bg_color;
}
} else {
bg_color = this.options.default_bg_color;
bg.color_value = "rgb(" + bg_color.r + " , " + bg_color.g + ", " + bg_color.b + ")";
}
bg_color_rgb = bg_color.r + "," + bg_color.g + "," + bg_color.b;
this._el.background.style.backgroundImage = "none";
if (bg.color_value) {
this._el.background.style.backgroundColor = bg.color_value;
} else {
this._el.background.style.backgroundColor = "transparent";
}
if (bg_color.r < 255 || bg_color.g < 255 || bg_color.b < 255 || bg.image) {
this._nav.next.setColor(true);
this._nav.previous.setColor(true);
} else {
this._nav.next.setColor(false);
this._nav.previous.setColor(false);
}
},
/* Private Methods
================================================== */
// Update Display
_updateDisplay: function(width, height, animate, layout) {
var nav_pos, _layout;
if(typeof layout === 'undefined'){
_layout = this.options.layout;
} else {
_layout = layout;
}
this.options.layout = _layout;
this.slide_spacing = this.options.width*2;
if (width) {
this.options.width = width;
} else {
this.options.width = this._el.container.offsetWidth;
}
if (height) {
this.options.height = height;
} else {
this.options.height = this._el.container.offsetHeight;
}
//this._el.container.style.height = this.options.height;
// position navigation
nav_pos = (this.options.height/2);
this._nav.next.setPosition({top:nav_pos});
this._nav.previous.setPosition({top:nav_pos});
// Position slides
for (var i = 0; i < this._slides.length; i++) {
this._slides[i].updateDisplay(this.options.width, this.options.height, _layout);
this._slides[i].setPosition({left:(this.slide_spacing * i), top:0});
};
// Go to the current slide
this.goToId(this.current_id, true, true);
},
// Reposition and redraw slides
_updateDrawSlides: function() {
var _layout = this.options.layout;
for (var i = 0; i < this._slides.length; i++) {
this._slides[i].updateDisplay(this.options.width, this.options.height, _layout);
this._slides[i].setPosition({left:(this.slide_spacing * i), top:0});
};
this.goToId(this.current_id, true, false);
},
/* Init
================================================== */
_initLayout: function () {
TL.DomUtil.addClass(this._el.container, 'tl-storyslider');
// Create Layout
this._el.slider_container_mask = TL.Dom.create('div', 'tl-slider-container-mask', this._el.container);
this._el.background = TL.Dom.create('div', 'tl-slider-background tl-animate', this._el.container);
this._el.slider_container = TL.Dom.create('div', 'tl-slider-container tlanimate', this._el.slider_container_mask);
this._el.slider_item_container = TL.Dom.create('div', 'tl-slider-item-container', this._el.slider_container);
// Update Size
this.options.width = this._el.container.offsetWidth;
this.options.height = this._el.container.offsetHeight;
// Create Navigation
this._nav.previous = new TL.SlideNav({title: "Previous", description: "description"}, {direction:"previous"});
this._nav.next = new TL.SlideNav({title: "Next",description: "description"}, {direction:"next"});
// add the navigation to the dom
this._nav.next.addTo(this._el.container);
this._nav.previous.addTo(this._el.container);
this._el.slider_container.style.left="0px";
if (TL.Browser.touch) {
//this._el.slider_touch_mask = TL.Dom.create('div', 'tl-slider-touch-mask', this._el.slider_container_mask);
this._swipable = new TL.Swipable(this._el.slider_container_mask, this._el.slider_container, {
enable: {x:true, y:false},
snap: true
});
this._swipable.enable();
// Message
this._message = new TL.Message({}, {
message_class: "tl-message-full",
message_icon_class: "tl-icon-swipe-left"
});
this._message.updateMessage(this._("swipe_to_navigate"));
this._message.addTo(this._el.container);
}
},
_initEvents: function () {
this._nav.next.on('clicked', this._onNavigation, this);
this._nav.previous.on('clicked', this._onNavigation, this);
if (this._message) {
this._message.on('clicked', this._onMessageClick, this);
}
if (this._swipable) {
this._swipable.on('swipe_left', this._onNavigation, this);
this._swipable.on('swipe_right', this._onNavigation, this);
this._swipable.on('swipe_nodirection', this._onSwipeNoDirection, this);
}
},
_initData: function() {
if(this.data.title) {
this._createSlide(this.data.title, true, -1);
}
this._createSlides(this.data.events);
},
/* Events
================================================== */
_onBackgroundChange: function(e) {
var n = this._findSlideIndex(this.current_id);
var slide_background = this._slides[n].getBackground();
this.changeBackground(e);
this.fire("colorchange", slide_background);
},
_onMessageClick: function(e) {
this._message.hide();
},
_onSwipeNoDirection: function(e) {
this.goToId(this.current_id);
},
_onNavigation: function(e) {
if (e.direction == "next" || e.direction == "left") {
this.next();
} else if (e.direction == "previous" || e.direction == "right") {
this.previous();
}
this.fire("nav_" + e.direction, this.data);
},
_onSlideAdded: function(e) {
trace("slideadded")
this.fire("slideAdded", this.data);
},
_onSlideRemoved: function(e) {
this.fire("slideRemoved", this.data);
},
_onSlideChange: function(displayupdate) {
if (!displayupdate) {
this.fire("change", {unique_id: this.current_id});
}
},
_onMouseClick: function(e) {
},
_fireMouseEvent: function (e) {
if (!this._loaded) {
return;
}
var type = e.type;
type = (type === 'mouseenter' ? 'mouseover' : (type === 'mouseleave' ? 'mouseout' : type));
if (!this.hasEventListeners(type)) {
return;
}
if (type === 'contextmenu') {
TL.DomEvent.preventDefault(e);
}
this.fire(type, {
latlng: "something", //this.mouseEventToLatLng(e),
layerPoint: "something else" //this.mouseEventToLayerPoint(e)
});
},
_onLoaded: function() {
this.fire("loaded", this.data);
}
});
/* **********************************************
Begin TL.TimeNav.js
********************************************** */
/* TL.TimeNav
================================================== */
TL.TimeNav = TL.Class.extend({
includes: [TL.Events, TL.DomMixins],
_el: {},
/* Constructor
================================================== */
initialize: function (elem, timeline_config, options, init) {
// DOM ELEMENTS
this._el = {
parent: {},
container: {},
slider: {},
slider_background: {},
line: {},
marker_container_mask: {},
marker_container: {},
marker_item_container: {},
timeaxis: {},
timeaxis_background: {},
attribution: {}
};
this.collapsed = false;
if (typeof elem === 'object') {
this._el.container = elem;
} else {
this._el.container = TL.Dom.get(elem);
}
this.config = timeline_config;
//Options
this.options = {
width: 600,
height: 600,
duration: 1000,
ease: TL.Ease.easeInOutQuint,
has_groups: false,
optimal_tick_width: 50,
scale_factor: 2, // How many screen widths wide should the timeline be
marker_padding: 5,
timenav_height_min: 150, // Minimum timenav height
marker_height_min: 30, // Minimum Marker Height
marker_width_min: 100, // Minimum Marker Width
zoom_sequence: [0.5, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89] // Array of Fibonacci numbers for TimeNav zoom levels http://www.maths.surrey.ac.uk/hosted-sites/R.Knott/Fibonacci/fibtable.html
};
// Animation
this.animator = null;
// Ready state
this.ready = false;
// Markers Array
this._markers = [];
// Eras Array
this._eras = [];
this.has_eras = false;
// Groups Array
this._groups = [];
// Row Height
this._calculated_row_height = 100;
// Current Marker
this.current_id = "";
// TimeScale
this.timescale = {};
// TimeAxis
this.timeaxis = {};
this.axishelper = {};
// Max Rows
this.max_rows = 6;
// Animate CSS
this.animate_css = false;
// Swipe Object
this._swipable;
// Merge Data and Options
TL.Util.mergeData(this.options, options);
if (init) {
this.init();
}
},
init: function() {
this._initLayout();
this._initEvents();
this._initData();
this._updateDisplay();
this._onLoaded();
},
/* Public
================================================== */
positionMarkers: function() {
this._positionMarkers();
},
/* Update Display
================================================== */
updateDisplay: function(w, h, a, l) {
this._updateDisplay(w, h, a, l);
},
/* TimeScale
================================================== */
_getTimeScale: function() {
/* maybe the establishing config values (marker_height_min and max_rows) should be
separated from making a TimeScale object, which happens in another spot in this file with duplicate mapping of properties of this TimeNav into the TimeScale options object? */
// Set Max Rows
var marker_height_min = 0;
try {
marker_height_min = parseInt(this.options.marker_height_min);
} catch(e) {
trace("Invalid value for marker_height_min option.");
marker_height_min = 30;
}
if (marker_height_min == 0) {
trace("marker_height_min option must not be zero.")
marker_height_min = 30;
}
this.max_rows = Math.round((this.options.height - this._el.timeaxis_background.offsetHeight - (this.options.marker_padding)) / marker_height_min);
if (this.max_rows < 1) {
this.max_rows = 1;
}
return new TL.TimeScale(this.config, {
display_width: this._el.container.offsetWidth,
screen_multiplier: this.options.scale_factor,
max_rows: this.max_rows
});
},
_updateTimeScale: function(new_scale) {
this.options.scale_factor = new_scale;
this._updateDrawTimeline();
},
zoomIn: function() { // move the the next "higher" scale factor
var new_scale = TL.Util.findNextGreater(this.options.zoom_sequence, this.options.scale_factor);
this.setZoomFactor(new_scale);
},
zoomOut: function() { // move the the next "lower" scale factor
var new_scale = TL.Util.findNextLesser(this.options.zoom_sequence, this.options.scale_factor);
this.setZoomFactor(new_scale);
},
setZoom: function(level) {
var zoom_factor = this.options.zoom_sequence[level];
if (typeof(zoom_factor) == 'number') {
this.setZoomFactor(zoom_factor);
} else {
console.warn("Invalid zoom level. Please use an index number between 0 and " + (this.options.zoom_sequence.length - 1));
}
},
setZoomFactor: function(factor) {
if (factor <= this.options.zoom_sequence[0]) {
this.fire("zoomtoggle", {zoom:"out", show:false});
} else {
this.fire("zoomtoggle", {zoom:"out", show:true});
}
if (factor >= this.options.zoom_sequence[this.options.zoom_sequence.length-1]) {
this.fire("zoomtoggle", {zoom:"in", show:false});
} else {
this.fire("zoomtoggle", {zoom:"in", show:true});
}
if (factor == 0) {
console.warn("Zoom factor must be greater than zero. Using 0.1");
factor = 0.1;
}
this.options.scale_factor = factor;
//this._updateDrawTimeline(true);
this.goToId(this.current_id, !this._updateDrawTimeline(true), true);
},
/* Groups
================================================== */
_createGroups: function() {
this._groups = [];
var group_labels = this.timescale.getGroupLabels();
if (group_labels) {
this.options.has_groups = true;
for (var i = 0; i < group_labels.length; i++) {
this._createGroup(group_labels[i]);
}
}
},
_createGroup: function(group_label) {
var group = new TL.TimeGroup(group_label);
this._addGroup(group);
this._groups.push(group);
},
_addGroup:function(group) {
group.addTo(this._el.container);
},
_positionGroups: function() {
if (this.options.has_groups) {
var available_height = (this.options.height - this._el.timeaxis_background.offsetHeight ),
group_height = Math.floor((available_height /this.timescale.getNumberOfRows()) - this.options.marker_padding),
group_labels = this.timescale.getGroupLabels();
for (var i = 0, group_rows = 0; i < this._groups.length; i++) {
var group_y = Math.floor(group_rows * (group_height + this.options.marker_padding));
var group_hide = false;
if (group_y > (available_height- this.options.marker_padding)) {
group_hide = true;
}
this._groups[i].setRowPosition(group_y, this._calculated_row_height + this.options.marker_padding/2);
this._groups[i].setAlternateRowColor(TL.Util.isEven(i), group_hide);
group_rows += this._groups[i].data.rows; // account for groups spanning multiple rows
}
}
},
/* Markers
================================================== */
_addMarker:function(marker) {
marker.addTo(this._el.marker_item_container);
marker.on('markerclick', this._onMarkerClick, this);
marker.on('added', this._onMarkerAdded, this);
},
_createMarker: function(data, n) {
var marker = new TL.TimeMarker(data, this.options);
this._addMarker(marker);
if(n < 0) {
this._markers.push(marker);
} else {
this._markers.splice(n, 0, marker);
}
},
_createMarkers: function(array) {
for (var i = 0; i < array.length; i++) {
this._createMarker(array[i], -1);
}
},
_removeMarker: function(marker) {
marker.removeFrom(this._el.marker_item_container);
//marker.off('added', this._onMarkerRemoved, this);
},
_destroyMarker: function(n) {
this._removeMarker(this._markers[n]);
this._markers.splice(n, 1);
},
_positionMarkers: function(fast) {
// POSITION X
for (var i = 0; i < this._markers.length; i++) {
var pos = this.timescale.getPositionInfo(i);
if (fast) {
this._markers[i].setClass("tl-timemarker tl-timemarker-fast");
} else {
this._markers[i].setClass("tl-timemarker");
}
this._markers[i].setPosition({left:pos.start});
this._markers[i].setWidth(pos.width);
};
},
_calculateMarkerHeight: function(h) {
return ((h /this.timescale.getNumberOfRows()) - this.options.marker_padding);
},
_calculateRowHeight: function(h) {
return (h /this.timescale.getNumberOfRows());
},
_calculateAvailableHeight: function() {
return (this.options.height - this._el.timeaxis_background.offsetHeight - (this.options.marker_padding));
},
_calculateMinimumTimeNavHeight: function() {
return (this.timescale.getNumberOfRows() * this.options.marker_height_min) + this._el.timeaxis_background.offsetHeight + (this.options.marker_padding);
},
getMinimumHeight: function() {
return this._calculateMinimumTimeNavHeight();
},
_assignRowsToMarkers: function() {
var available_height = this._calculateAvailableHeight(),
marker_height = this._calculateMarkerHeight(available_height);
this._positionGroups();
this._calculated_row_height = this._calculateRowHeight(available_height);
for (var i = 0; i < this._markers.length; i++) {
// Set Height
this._markers[i].setHeight(marker_height);
//Position by Row
var row = this.timescale.getPositionInfo(i).row;
var marker_y = Math.floor(row * (marker_height + this.options.marker_padding)) + this.options.marker_padding;
var remainder_height = available_height - marker_y + this.options.marker_padding;
this._markers[i].setRowPosition(marker_y, remainder_height);
};
},
_resetMarkersActive: function() {
for (var i = 0; i < this._markers.length; i++) {
this._markers[i].setActive(false);
};
},
_findMarkerIndex: function(n) {
var _n = -1;
if (typeof n == 'string' || n instanceof String) {
_n = TL.Util.findArrayNumberByUniqueID(n, this._markers, "unique_id", _n);
}
return _n;
},
/* ERAS
================================================== */
_createEras: function(array) {
for (var i = 0; i < array.length; i++) {
this._createEra(array[i], -1);
}
},
_createEra: function(data, n) {
var era = new TL.TimeEra(data, this.options);
this._addEra(era);
if(n < 0) {
this._eras.push(era);
} else {
this._eras.splice(n, 0, era);
}
},
_addEra:function(era) {
era.addTo(this._el.marker_item_container);
era.on('added', this._onEraAdded, this);
},
_removeEra: function(era) {
era.removeFrom(this._el.marker_item_container);
//marker.off('added', this._onMarkerRemoved, this);
},
_destroyEra: function(n) {
this._removeEra(this._eras[n]);
this._eras.splice(n, 1);
},
_positionEras: function(fast) {
var era_color = 0;
// POSITION X
for (var i = 0; i < this._eras.length; i++) {
var pos = {
start:0,
end:0,
width:0
};
pos.start = this.timescale.getPosition(this._eras[i].data.start_date.getTime());
pos.end = this.timescale.getPosition(this._eras[i].data.end_date.getTime());
pos.width = pos.end - pos.start;
if (fast) {
this._eras[i].setClass("tl-timeera tl-timeera-fast");
} else {
this._eras[i].setClass("tl-timeera");
}
this._eras[i].setPosition({left:pos.start});
this._eras[i].setWidth(pos.width);
era_color++;
if (era_color > 5) {
era_color = 0;
}
this._eras[i].setColor(era_color);
};
},
/* Public
================================================== */
// Create a marker
createMarker: function(d, n) {
this._createMarker(d, n);
},
// Create many markers from an array
createMarkers: function(array) {
this._createMarkers(array);
},
// Destroy marker by index
destroyMarker: function(n) {
this._destroyMarker(n);
},
// Destroy marker by id
destroyMarkerId: function(id) {
this.destroyMarker(this._findMarkerIndex(id));
},
/* Navigation
================================================== */
goTo: function(n, fast, css_animation) {
var self = this,
_ease = this.options.ease,
_duration = this.options.duration,
_n = (n < 0) ? 0 : n;
// Set Marker active state
this._resetMarkersActive();
if(n >= 0 && n < this._markers.length) {
this._markers[n].setActive(true);
}
// Stop animation
if (this.animator) {
this.animator.stop();
}
if (fast) {
this._el.slider.className = "tl-timenav-slider";
this._el.slider.style.left = -this._markers[_n].getLeft() + (this.options.width/2) + "px";
} else {
if (css_animation) {
this._el.slider.className = "tl-timenav-slider tl-timenav-slider-animate";
this.animate_css = true;
this._el.slider.style.left = -this._markers[_n].getLeft() + (this.options.width/2) + "px";
} else {
this._el.slider.className = "tl-timenav-slider";
this.animator = TL.Animate(this._el.slider, {
left: -this._markers[_n].getLeft() + (this.options.width/2) + "px",
duration: _duration,
easing: _ease
});
}
}
if(n >= 0 && n < this._markers.length) {
this.current_id = this._markers[n].data.unique_id;
} else {
this.current_id = '';
}
},
goToId: function(id, fast, css_animation) {
this.goTo(this._findMarkerIndex(id), fast, css_animation);
},
/* Events
================================================== */
_onLoaded: function() {
this.ready = true;
this.fire("loaded", this.config);
},
_onMarkerAdded: function(e) {
this.fire("dateAdded", this.config);
},
_onEraAdded: function(e) {
this.fire("eraAdded", this.config);
},
_onMarkerRemoved: function(e) {
this.fire("dateRemoved", this.config);
},
_onMarkerClick: function(e) {
// Go to the clicked marker
this.goToId(e.unique_id);
this.fire("change", {unique_id: e.unique_id});
},
_onMouseScroll: function(e) {
var delta = 0,
scroll_to = 0,
constraint = {
right: -(this.timescale.getPixelWidth() - (this.options.width/2)),
left: this.options.width/2
};
if (!e) {
e = window.event;
}
if (e.originalEvent) {
e = e.originalEvent;
}
// Webkit and browsers able to differntiate between up/down and left/right scrolling
if (typeof e.wheelDeltaX != 'undefined' ) {
delta = e.wheelDeltaY/6;
if (Math.abs(e.wheelDeltaX) > Math.abs(e.wheelDeltaY)) {
delta = e.wheelDeltaX/6;
} else {
//delta = e.wheelDeltaY/6;
delta = 0;
}
}
if (delta) {
if (e.preventDefault) {
e.preventDefault();
}
e.returnValue = false;
}
// Stop from scrolling too far
scroll_to = parseInt(this._el.slider.style.left.replace("px", "")) + delta;
if (scroll_to > constraint.left) {
scroll_to = constraint.left;
} else if (scroll_to < constraint.right) {
scroll_to = constraint.right;
}
if (this.animate_css) {
this._el.slider.className = "tl-timenav-slider";
this.animate_css = false;
}
this._el.slider.style.left = scroll_to + "px";
},
_onDragMove: function(e) {
if (this.animate_css) {
this._el.slider.className = "tl-timenav-slider";
this.animate_css = false;
}
},
/* Private Methods
================================================== */
// Update Display
_updateDisplay: function(width, height, animate) {
if (width) {
this.options.width = width;
}
if (height && height != this.options.height) {
this.options.height = height;
this.timescale = this._getTimeScale();
}
// Size Markers
this._assignRowsToMarkers();
// Size swipable area
this._el.slider_background.style.width = this.timescale.getPixelWidth() + this.options.width + "px";
this._el.slider_background.style.left = -(this.options.width/2) + "px";
this._el.slider.style.width = this.timescale.getPixelWidth() + this.options.width + "px";
// Update Swipable constraint
this._swipable.updateConstraint({top: false,bottom: false,left: (this.options.width/2),right: -(this.timescale.getPixelWidth() - (this.options.width/2))});
// Go to the current slide
this.goToId(this.current_id, true);
},
_drawTimeline: function(fast) {
this.timescale = this._getTimeScale();
this.timeaxis.drawTicks(this.timescale, this.options.optimal_tick_width);
this._positionMarkers(fast);
this._assignRowsToMarkers();
this._createGroups();
this._positionGroups();
if (this.has_eras) {
this._positionEras(fast);
}
},
_updateDrawTimeline: function(check_update) {
var do_update = false;
// Check to see if redraw is needed
if (check_update) {
/* keep this aligned with _getTimeScale or reduce code duplication */
var temp_timescale = new TL.TimeScale(this.config, {
display_width: this._el.container.offsetWidth,
screen_multiplier: this.options.scale_factor,
max_rows: this.max_rows
});
if (this.timescale.getMajorScale() == temp_timescale.getMajorScale()
&& this.timescale.getMinorScale() == temp_timescale.getMinorScale()) {
do_update = true;
}
} else {
do_update = true;
}
// Perform update or redraw
if (do_update) {
this.timescale = this._getTimeScale();
this.timeaxis.positionTicks(this.timescale, this.options.optimal_tick_width);
this._positionMarkers();
this._assignRowsToMarkers();
this._positionGroups();
if (this.has_eras) {
this._positionEras();
}
this._updateDisplay();
} else {
this._drawTimeline(true);
}
return do_update;
},
/* Init
================================================== */
_initLayout: function () {
// Create Layout
this._el.attribution = TL.Dom.create('div', 'tl-attribution', this._el.container);
this._el.line = TL.Dom.create('div', 'tl-timenav-line', this._el.container);
this._el.slider = TL.Dom.create('div', 'tl-timenav-slider', this._el.container);
this._el.slider_background = TL.Dom.create('div', 'tl-timenav-slider-background', this._el.slider);
this._el.marker_container_mask = TL.Dom.create('div', 'tl-timenav-container-mask', this._el.slider);
this._el.marker_container = TL.Dom.create('div', 'tl-timenav-container', this._el.marker_container_mask);
this._el.marker_item_container = TL.Dom.create('div', 'tl-timenav-item-container', this._el.marker_container);
this._el.timeaxis = TL.Dom.create('div', 'tl-timeaxis', this._el.slider);
this._el.timeaxis_background = TL.Dom.create('div', 'tl-timeaxis-background', this._el.container);
// Knight Lab Logo
this._el.attribution.innerHTML = "Timeline JS"
// Time Axis
this.timeaxis = new TL.TimeAxis(this._el.timeaxis, this.options);
// Swipable
this._swipable = new TL.Swipable(this._el.slider_background, this._el.slider, {
enable: {x:true, y:false},
constraint: {top: false,bottom: false,left: (this.options.width/2),right: false},
snap: false
});
this._swipable.enable();
},
_initEvents: function () {
// Drag Events
this._swipable.on('dragmove', this._onDragMove, this);
// Scroll Events
TL.DomEvent.addListener(this._el.container, 'mousewheel', this._onMouseScroll, this);
TL.DomEvent.addListener(this._el.container, 'DOMMouseScroll', this._onMouseScroll, this);
},
_initData: function() {
// Create Markers and then add them
this._createMarkers(this.config.events);
if (this.config.eras) {
this.has_eras = true;
this._createEras(this.config.eras);
}
this._drawTimeline();
}
});
/* **********************************************
Begin TL.TimeMarker.js
********************************************** */
/* TL.TimeMarker
================================================== */
TL.TimeMarker = TL.Class.extend({
includes: [TL.Events, TL.DomMixins],
_el: {},
/* Constructor
================================================== */
initialize: function(data, options) {
// DOM Elements
this._el = {
container: {},
content_container: {},
media_container: {},
timespan: {},
line_left: {},
line_right: {},
content: {},
text: {},
media: {},
};
// Components
this._text = {};
// State
this._state = {
loaded: false
};
// Data
this.data = {
unique_id: "",
background: null,
date: {
year: 0,
month: 0,
day: 0,
hour: 0,
minute: 0,
second: 0,
millisecond: 0,
thumbnail: "",
format: ""
},
text: {
headline: "",
text: ""
},
media: null
};
// Options
this.options = {
duration: 1000,
ease: TL.Ease.easeInSpline,
width: 600,
height: 600,
marker_width_min: 100 // Minimum Marker Width
};
// Actively Displaying
this.active = false;
// Animation Object
this.animator = {};
// End date
this.has_end_date = false;
// Merge Data and Options
TL.Util.mergeData(this.options, options);
TL.Util.mergeData(this.data, data);
this._initLayout();
this._initEvents();
},
/* Adding, Hiding, Showing etc
================================================== */
show: function() {
},
hide: function() {
},
setActive: function(is_active) {
this.active = is_active;
if (this.active && this.has_end_date) {
this._el.container.className = 'tl-timemarker tl-timemarker-with-end tl-timemarker-active';
} else if (this.active){
this._el.container.className = 'tl-timemarker tl-timemarker-active';
} else if (this.has_end_date){
this._el.container.className = 'tl-timemarker tl-timemarker-with-end';
} else {
this._el.container.className = 'tl-timemarker';
}
},
addTo: function(container) {
container.appendChild(this._el.container);
},
removeFrom: function(container) {
container.removeChild(this._el.container);
},
updateDisplay: function(w, h) {
this._updateDisplay(w, h);
},
loadMedia: function() {
if (this._media && !this._state.loaded) {
this._media.loadMedia();
this._state.loaded = true;
}
},
stopMedia: function() {
if (this._media && this._state.loaded) {
this._media.stopMedia();
}
},
getLeft: function() {
return this._el.container.style.left.slice(0, -2);
},
getTime: function() { // TODO does this need to know about the end date?
return this.data.start_date.getTime();
},
getEndTime: function() {
if (this.data.end_date) {
return this.data.end_date.getTime();
} else {
return false;
}
},
setHeight: function(h) {
var text_line_height = 12,
text_lines = 1;
this._el.content_container.style.height = h + "px";
this._el.timespan_content.style.height = h + "px";
// Handle Line height for better display of text
if (h <= 30) {
this._el.content.className = "tl-timemarker-content tl-timemarker-content-small";
} else {
this._el.content.className = "tl-timemarker-content";
}
if (h <= 56) {
TL.DomUtil.addClass(this._el.content_container, "tl-timemarker-content-container-small");
} else {
TL.DomUtil.removeClass(this._el.content_container, "tl-timemarker-content-container-small");
}
// Handle number of lines visible vertically
if (TL.Browser.webkit) {
text_lines = Math.floor(h / (text_line_height + 2));
if (text_lines < 1) {
text_lines = 1;
}
this._text.className = "tl-headline";
this._text.style.webkitLineClamp = text_lines;
} else {
text_lines = h / text_line_height;
if (text_lines > 1) {
this._text.className = "tl-headline tl-headline-fadeout";
} else {
this._text.className = "tl-headline";
}
this._text.style.height = (text_lines * text_line_height) + "px";
}
},
setWidth: function(w) {
if (this.data.end_date) {
this._el.container.style.width = w + "px";
if (w > this.options.marker_width_min) {
this._el.content_container.style.width = w + "px";
this._el.content_container.className = "tl-timemarker-content-container tl-timemarker-content-container-long";
} else {
this._el.content_container.style.width = this.options.marker_width_min + "px";
this._el.content_container.className = "tl-timemarker-content-container";
}
}
},
setClass: function(n) {
this._el.container.className = n;
},
setRowPosition: function(n, remainder) {
this.setPosition({top:n});
this._el.timespan.style.height = remainder + "px";
if (remainder < 56) {
//TL.DomUtil.removeClass(this._el.content_container, "tl-timemarker-content-container-small");
}
},
/* Events
================================================== */
_onMarkerClick: function(e) {
this.fire("markerclick", {unique_id:this.data.unique_id});
},
/* Private Methods
================================================== */
_initLayout: function () {
//trace(this.data)
// Create Layout
this._el.container = TL.Dom.create("div", "tl-timemarker");
if (this.data.unique_id) {
this._el.container.id = this.data.unique_id + "-marker";
}
if (this.data.end_date) {
this.has_end_date = true;
this._el.container.className = 'tl-timemarker tl-timemarker-with-end';
}
this._el.timespan = TL.Dom.create("div", "tl-timemarker-timespan", this._el.container);
this._el.timespan_content = TL.Dom.create("div", "tl-timemarker-timespan-content", this._el.timespan);
this._el.content_container = TL.Dom.create("div", "tl-timemarker-content-container", this._el.container);
this._el.content = TL.Dom.create("div", "tl-timemarker-content", this._el.content_container);
this._el.line_left = TL.Dom.create("div", "tl-timemarker-line-left", this._el.timespan);
this._el.line_right = TL.Dom.create("div", "tl-timemarker-line-right", this._el.timespan);
// Thumbnail or Icon
if (this.data.media) {
this._el.media_container = TL.Dom.create("div", "tl-timemarker-media-container", this._el.content);
// ugh. needs an overhaul
var mtd = {url: this.data.media.thumbnail};
var thumbnail_media_type = (this.data.media.thumbnail) ? TL.MediaType(mtd, true) : null;
if (thumbnail_media_type) {
var thumbnail_media = new thumbnail_media_type.cls(mtd);
thumbnail_media.on("loaded", function() {
this._el.media = TL.Dom.create("img", "tl-timemarker-media", this._el.media_container);
this._el.media.src = thumbnail_media.getImageURL();
}.bind(this));
thumbnail_media.loadMedia();
} else {
var media_type = TL.MediaType(this.data.media).type;
this._el.media = TL.Dom.create("span", "tl-icon-" + media_type, this._el.media_container);
}
}
// Text
this._el.text = TL.Dom.create("div", "tl-timemarker-text", this._el.content);
this._text = TL.Dom.create("h2", "tl-headline", this._el.text);
if (this.data.text.headline && this.data.text.headline != "") {
this._text.innerHTML = TL.Util.unlinkify(this.data.text.headline);
} else if (this.data.text.text && this.data.text.text != "") {
this._text.innerHTML = TL.Util.unlinkify(this.data.text.text);
} else if (this.data.media && this.data.media.caption && this.data.media.caption != "") {
this._text.innerHTML = TL.Util.unlinkify(this.data.media.caption);
}
// Fire event that the slide is loaded
this.onLoaded();
},
_initEvents: function() {
TL.DomEvent.addListener(this._el.container, 'click', this._onMarkerClick, this);
},
// Update Display
_updateDisplay: function(width, height, layout) {
if (width) {
this.options.width = width;
}
if (height) {
this.options.height = height;
}
}
});
/* **********************************************
Begin TL.TimeEra.js
********************************************** */
/* TL.TimeMarker
================================================== */
TL.TimeEra = TL.Class.extend({
includes: [TL.Events, TL.DomMixins],
_el: {},
/* Constructor
================================================== */
initialize: function(data, options) {
// DOM Elements
this._el = {
container: {},
background: {},
content_container: {},
content: {},
text: {}
};
// Components
this._text = {};
// State
this._state = {
loaded: false
};
// Data
this.data = {
unique_id: "",
date: {
year: 0,
month: 0,
day: 0,
hour: 0,
minute: 0,
second: 0,
millisecond: 0,
thumbnail: "",
format: ""
},
text: {
headline: "",
text: ""
}
};
// Options
this.options = {
duration: 1000,
ease: TL.Ease.easeInSpline,
width: 600,
height: 600,
marker_width_min: 100 // Minimum Marker Width
};
// Actively Displaying
this.active = false;
// Animation Object
this.animator = {};
// End date
this.has_end_date = false;
// Merge Data and Options
TL.Util.mergeData(this.options, options);
TL.Util.mergeData(this.data, data);
this._initLayout();
this._initEvents();
},
/* Adding, Hiding, Showing etc
================================================== */
show: function() {
},
hide: function() {
},
setActive: function(is_active) {
},
addTo: function(container) {
container.appendChild(this._el.container);
},
removeFrom: function(container) {
container.removeChild(this._el.container);
},
updateDisplay: function(w, h) {
this._updateDisplay(w, h);
},
getLeft: function() {
return this._el.container.style.left.slice(0, -2);
},
getTime: function() { // TODO does this need to know about the end date?
return this.data.start_date.getTime();
},
getEndTime: function() {
if (this.data.end_date) {
return this.data.end_date.getTime();
} else {
return false;
}
},
setHeight: function(h) {
var text_line_height = 12,
text_lines = 1;
this._el.content_container.style.height = h + "px";
this._el.content.className = "tl-timeera-content";
// Handle number of lines visible vertically
if (TL.Browser.webkit) {
text_lines = Math.floor(h / (text_line_height + 2));
if (text_lines < 1) {
text_lines = 1;
}
this._text.className = "tl-headline";
this._text.style.webkitLineClamp = text_lines;
} else {
text_lines = h / text_line_height;
if (text_lines > 1) {
this._text.className = "tl-headline tl-headline-fadeout";
} else {
this._text.className = "tl-headline";
}
this._text.style.height = (text_lines * text_line_height) + "px";
}
},
setWidth: function(w) {
if (this.data.end_date) {
this._el.container.style.width = w + "px";
if (w > this.options.marker_width_min) {
this._el.content_container.style.width = w + "px";
this._el.content_container.className = "tl-timeera-content-container tl-timeera-content-container-long";
} else {
this._el.content_container.style.width = this.options.marker_width_min + "px";
this._el.content_container.className = "tl-timeera-content-container";
}
}
},
setClass: function(n) {
this._el.container.className = n;
},
setRowPosition: function(n, remainder) {
this.setPosition({top:n});
if (remainder < 56) {
//TL.DomUtil.removeClass(this._el.content_container, "tl-timeera-content-container-small");
}
},
setColor: function(color_num) {
this._el.container.className = 'tl-timeera tl-timeera-color' + color_num;
},
/* Events
================================================== */
/* Private Methods
================================================== */
_initLayout: function () {
//trace(this.data)
// Create Layout
this._el.container = TL.Dom.create("div", "tl-timeera");
if (this.data.unique_id) {
this._el.container.id = this.data.unique_id + "-era";
}
if (this.data.end_date) {
this.has_end_date = true;
this._el.container.className = 'tl-timeera tl-timeera-with-end';
}
this._el.content_container = TL.Dom.create("div", "tl-timeera-content-container", this._el.container);
this._el.background = TL.Dom.create("div", "tl-timeera-background", this._el.content_container);
this._el.content = TL.Dom.create("div", "tl-timeera-content", this._el.content_container);
// Text
this._el.text = TL.Dom.create("div", "tl-timeera-text", this._el.content);
this._text = TL.Dom.create("h2", "tl-headline", this._el.text);
if (this.data.text.headline && this.data.text.headline != "") {
this._text.innerHTML = TL.Util.unlinkify(this.data.text.headline);
}
// Fire event that the slide is loaded
this.onLoaded();
},
_initEvents: function() {
},
// Update Display
_updateDisplay: function(width, height, layout) {
if (width) {
this.options.width = width;
}
if (height) {
this.options.height = height;
}
}
});
/* **********************************************
Begin TL.TimeGroup.js
********************************************** */
/* TL.TimeGroup
================================================== */
TL.TimeGroup = TL.Class.extend({
includes: [TL.Events, TL.DomMixins],
_el: {},
/* Constructor
================================================== */
initialize: function(data) {
// DOM ELEMENTS
this._el = {
parent: {},
container: {},
message: {}
};
//Options
this.options = {
width: 600,
height: 600
};
// Data
this.data = {
label: "",
rows: 1
};
this._el.container = TL.Dom.create("div", "tl-timegroup");
// Merge Data
TL.Util.mergeData(this.data, data);
// Animation
this.animator = {};
this._initLayout();
this._initEvents();
},
/* Public
================================================== */
/* Update Display
================================================== */
updateDisplay: function(w, h) {
},
setRowPosition: function(n, h) {
// trace(n);
// trace(this._el.container)
this.options.height = h * this.data.rows;
this.setPosition({top:n});
this._el.container.style.height = this.options.height + "px";
},
setAlternateRowColor: function(alternate, hide) {
var class_name = "tl-timegroup";
if (alternate) {
class_name += " tl-timegroup-alternate";
}
if (hide) {
class_name += " tl-timegroup-hidden";
}
this._el.container.className = class_name;
},
/* Events
================================================== */
_onMouseClick: function() {
this.fire("clicked", this.options);
},
/* Private Methods
================================================== */
_initLayout: function () {
// Create Layout
this._el.message = TL.Dom.create("div", "tl-timegroup-message", this._el.container);
this._el.message.innerHTML = this.data.label;
},
_initEvents: function () {
TL.DomEvent.addListener(this._el.container, 'click', this._onMouseClick, this);
},
// Update Display
_updateDisplay: function(width, height, animate) {
}
});
/* **********************************************
Begin TL.TimeScale.js
********************************************** */
/* TL.TimeScale
Strategies for laying out the timenav
make a new one if the slides change
TODOS: deal with clustering
================================================== */
TL.TimeScale = TL.Class.extend({
initialize: function (timeline_config, options) {
var slides = timeline_config.events;
this._scale = timeline_config.scale;
options = TL.Util.mergeData({ // establish defaults
display_width: 500,
screen_multiplier: 3,
max_rows: null
}, options);
this._display_width = options.display_width;
this._screen_multiplier = options.screen_multiplier;
this._pixel_width = this._screen_multiplier * this._display_width;
this._group_labels = undefined;
this._positions = [];
this._pixels_per_milli = 0;
this._earliest = timeline_config.getEarliestDate().getTime();
this._latest = timeline_config.getLatestDate().getTime();
this._span_in_millis = this._latest - this._earliest;
if (this._span_in_millis <= 0) {
this._span_in_millis = this._computeDefaultSpan(timeline_config);
}
this._average = (this._span_in_millis)/slides.length;
this._pixels_per_milli = this.getPixelWidth() / this._span_in_millis;
this._axis_helper = TL.AxisHelper.getBestHelper(this);
this._scaled_padding = (1/this.getPixelsPerTick()) * (this._display_width/2)
this._computePositionInfo(slides, options.max_rows);
},
_computeDefaultSpan: function(timeline_config) {
// this gets called when all events are at the same instant,
// or maybe when the span_in_millis is > 0 but still below a desired threshold
// TODO: does this need smarts about eras?
if (timeline_config.scale == 'human') {
var formats = {}
for (var i = 0; i < timeline_config.events.length; i++) {
var fmt = timeline_config.events[i].start_date.findBestFormat();
formats[fmt] = (formats[fmt]) ? formats[fmt] + 1 : 1;
};
for (var i = TL.Date.SCALES.length - 1; i >= 0; i--) {
if (formats.hasOwnProperty(TL.Date.SCALES[i][0])) {
var scale = TL.Date.SCALES[TL.Date.SCALES.length - 1]; // default
if (TL.Date.SCALES[i+1]) {
scale = TL.Date.SCALES[i+1]; // one larger than the largest in our data
}
return scale[1]
}
};
return 365 * 24 * 60 * 60 * 1000; // default to a year?
}
return 200000; // what is the right handling for cosmo dates?
},
getGroupLabels: function() { /*
return an array of objects, one per group, in the order (top to bottom) that the groups are expected to appear. Each object will have two properties:
* label (the string as specified in one or more 'group' properties of events in the configuration)
* rows (the number of rows occupied by events associated with the label. )
*/
return (this._group_labels || []);
},
getScale: function() {
return this._scale;
},
getNumberOfRows: function() {
return this._number_of_rows
},
getPixelWidth: function() {
return this._pixel_width;
},
getPosition: function(time_in_millis) {
// be careful using millis, as they won't scale to cosmological time.
// however, we're moving to make the arg to this whatever value
// comes from TL.Date.getTime() which could be made smart about that --
// so it may just be about the naming.
return ( time_in_millis - this._earliest ) * this._pixels_per_milli
},
getPositionInfo: function(idx) {
return this._positions[idx];
},
getPixelsPerTick: function() {
return this._axis_helper.getPixelsPerTick(this._pixels_per_milli);
},
getTicks: function() {
return {
major: this._axis_helper.getMajorTicks(this),
minor: this._axis_helper.getMinorTicks(this) }
},
getDateFromTime: function(t) {
if(this._scale == 'human') {
return new TL.Date(t);
} else if(this._scale == 'cosmological') {
return new TL.BigDate(new TL.BigYear(t));
}
throw new TL.Error("time_scale_scale_err", this._scale);
},
getMajorScale: function() {
return this._axis_helper.major.name;
},
getMinorScale: function() {
return this._axis_helper.minor.name;
},
_assessGroups: function(slides) {
var groups = [];
var empty_group = false;
for (var i = 0; i < slides.length; i++) {
if(slides[i].group) {
if(groups.indexOf(slides[i].group) < 0) {
groups.push(slides[i].group);
} else {
empty_group = true;
}
}
};
if (groups.length && empty_group) {
groups.push('');
}
return groups;
},
/* Compute the marker row positions, minimizing the number of
overlaps.
@positions = list of objects from this._positions
@rows_left = number of rows available (assume > 0)
*/
_computeRowInfo: function(positions, rows_left) {
var lasts_in_row = [];
var n_overlaps = 0;
for (var i = 0; i < positions.length; i++) {
var pos_info = positions[i];
var overlaps = [];
// See if we can add item to an existing row without
// overlapping the previous item in that row
delete pos_info.row;
for (var j = 0; j < lasts_in_row.length; j++) {
overlaps.push(lasts_in_row[j].end - pos_info.start);
if(overlaps[j] <= 0) {
pos_info.row = j;
lasts_in_row[j] = pos_info;
break;
}
}
// If we couldn't add to an existing row without overlap...
if (typeof(pos_info.row) == 'undefined') {
if (rows_left === null) {
// Make a new row
pos_info.row = lasts_in_row.length;
lasts_in_row.push(pos_info);
} else if (rows_left > 0) {
// Make a new row
pos_info.row = lasts_in_row.length;
lasts_in_row.push(pos_info);
rows_left--;
} else {
// Add to existing row with minimum overlap.
var min_overlap = Math.min.apply(null, overlaps);
var idx = overlaps.indexOf(min_overlap);
pos_info.row = idx;
if (pos_info.end > lasts_in_row[idx].end) {
lasts_in_row[idx] = pos_info;
}
n_overlaps++;
}
}
}
return {n_rows: lasts_in_row.length, n_overlaps: n_overlaps};
},
/* Compute marker positions. If using groups, this._number_of_rows
will never be less than the number of groups.
@max_rows = total number of available rows
@default_marker_width should be in pixels
*/
_computePositionInfo: function(slides, max_rows, default_marker_width) {
default_marker_width = default_marker_width || 100;
var groups = [];
var empty_group = false;
// Set start/end/width; enumerate groups
for (var i = 0; i < slides.length; i++) {
var pos_info = {
start: this.getPosition(slides[i].start_date.getTime())
};
this._positions.push(pos_info);
if (typeof(slides[i].end_date) != 'undefined') {
var end_pos = this.getPosition(slides[i].end_date.getTime());
pos_info.width = end_pos - pos_info.start;
if (pos_info.width > default_marker_width) {
pos_info.end = pos_info.start + pos_info.width;
} else {
pos_info.end = pos_info.start + default_marker_width;
}
} else {
pos_info.width = default_marker_width;
pos_info.end = pos_info.start + default_marker_width;
}
if(slides[i].group) {
if(groups.indexOf(slides[i].group) < 0) {
groups.push(slides[i].group);
}
} else {
empty_group = true;
}
}
if(!(groups.length)) {
var result = this._computeRowInfo(this._positions, max_rows);
this._number_of_rows = result.n_rows;
} else {
if(empty_group) {
groups.push("");
}
// Init group info
var group_info = [];
for(var i = 0; i < groups.length; i++) {
group_info[i] = {
label: groups[i],
idx: i,
positions: [],
n_rows: 1, // default
n_overlaps: 0
};
}
for(var i = 0; i < this._positions.length; i++) {
var pos_info = this._positions[i];
pos_info.group = groups.indexOf(slides[i].group || "");
pos_info.row = 0;
var gi = group_info[pos_info.group];
for(var j = gi.positions.length - 1; j >= 0; j--) {
if(gi.positions[j].end > pos_info.start) {
gi.n_overlaps++;
}
}
gi.positions.push(pos_info);
}
var n_rows = groups.length; // start with 1 row per group
while(true) {
// Count free rows available
var rows_left = Math.max(0, max_rows - n_rows);
if(!rows_left) {
break; // no free rows, nothing to do
}
// Sort by # overlaps, idx
group_info.sort(function(a, b) {
if(a.n_overlaps > b.n_overlaps) {
return -1;
} else if(a.n_overlaps < b.n_overlaps) {
return 1;
}
return a.idx - b.idx;
});
if(!group_info[0].n_overlaps) {
break; // no overlaps, nothing to do
}
// Distribute free rows among groups with overlaps
var n_rows = 0;
for(var i = 0; i < group_info.length; i++) {
var gi = group_info[i];
if(gi.n_overlaps && rows_left) {
var res = this._computeRowInfo(gi.positions, gi.n_rows + 1);
gi.n_rows = res.n_rows; // update group info
gi.n_overlaps = res.n_overlaps;
rows_left--; // update rows left
}
n_rows += gi.n_rows; // update rows used
}
}
// Set number of rows
this._number_of_rows = n_rows;
// Set group labels; offset row positions
this._group_labels = [];
group_info.sort(function(a, b) {return a.idx - b.idx; });
for(var i = 0, row_offset = 0; i < group_info.length; i++) {
this._group_labels.push({
label: group_info[i].label,
rows: group_info[i].n_rows
});
for(var j = 0; j < group_info[i].positions.length; j++) {
var pos_info = group_info[i].positions[j];
pos_info.row += row_offset;
}
row_offset += group_info[i].n_rows;
}
}
}
});
/* **********************************************
Begin TL.TimeAxis.js
********************************************** */
/* TL.TimeAxis
Display element for showing timescale ticks
================================================== */
TL.TimeAxis = TL.Class.extend({
includes: [TL.Events, TL.DomMixins, TL.I18NMixins],
_el: {},
/* Constructor
================================================== */
initialize: function(elem, options) {
// DOM Elements
this._el = {
container: {},
content_container: {},
major: {},
minor: {},
};
// Components
this._text = {};
// State
this._state = {
loaded: false
};
// Data
this.data = {};
// Options
this.options = {
duration: 1000,
ease: TL.Ease.easeInSpline,
width: 600,
height: 600
};
// Actively Displaying
this.active = false;
// Animation Object
this.animator = {};
// Axis Helper
this.axis_helper = {};
// Minor tick dom element array
this.minor_ticks = [];
// Minor tick dom element array
this.major_ticks = [];
// Date Format Lookup, map TL.Date.SCALES names to...
this.dateformat_lookup = {
millisecond: 'time_milliseconds', // ...TL.Language..dateformats
second: 'time_short',
minute: 'time_no_seconds_short',
hour: 'time_no_minutes_short',
day: 'full_short',
month: 'month_short',
year: 'year',
decade: 'year',
century: 'year',
millennium: 'year',
age: 'compact', // ...TL.Language..bigdateformats
epoch: 'compact',
era: 'compact',
eon: 'compact',
eon2: 'compact'
}
// Main element
if (typeof elem === 'object') {
this._el.container = elem;
} else {
this._el.container = TL.Dom.get(elem);
}
// Merge Data and Options
TL.Util.mergeData(this.options, options);
this._initLayout();
this._initEvents();
},
/* Adding, Hiding, Showing etc
================================================== */
show: function() {
},
hide: function() {
},
addTo: function(container) {
container.appendChild(this._el.container);
},
removeFrom: function(container) {
container.removeChild(this._el.container);
},
updateDisplay: function(w, h) {
this._updateDisplay(w, h);
},
getLeft: function() {
return this._el.container.style.left.slice(0, -2);
},
drawTicks: function(timescale, optimal_tick_width) {
var ticks = timescale.getTicks();
var controls = {
minor: {
el: this._el.minor,
dateformat: this.dateformat_lookup[ticks['minor'].name],
ts_ticks: ticks['minor'].ticks,
tick_elements: this.minor_ticks
},
major: {
el: this._el.major,
dateformat: this.dateformat_lookup[ticks['major'].name],
ts_ticks: ticks['major'].ticks,
tick_elements: this.major_ticks
}
}
// FADE OUT
this._el.major.className = "tl-timeaxis-major";
this._el.minor.className = "tl-timeaxis-minor";
this._el.major.style.opacity = 0;
this._el.minor.style.opacity = 0;
// CREATE MAJOR TICKS
this.major_ticks = this._createTickElements(
ticks['major'].ticks,
this._el.major,
this.dateformat_lookup[ticks['major'].name]
);
// CREATE MINOR TICKS
this.minor_ticks = this._createTickElements(
ticks['minor'].ticks,
this._el.minor,
this.dateformat_lookup[ticks['minor'].name],
ticks['major'].ticks
);
this.positionTicks(timescale, optimal_tick_width, true);
// FADE IN
this._el.major.className = "tl-timeaxis-major tl-animate-opacity tl-timeaxis-animate-opacity";
this._el.minor.className = "tl-timeaxis-minor tl-animate-opacity tl-timeaxis-animate-opacity";
this._el.major.style.opacity = 1;
this._el.minor.style.opacity = 1;
},
_createTickElements: function(ts_ticks,tick_element,dateformat,ticks_to_skip) {
tick_element.innerHTML = "";
var skip_times = {};
var yearZero = new Date(-1,13,-30);
skip_times[yearZero.getTime()] = true;
if (ticks_to_skip){
for (var i = 0; i < ticks_to_skip.length; i++) {
skip_times[ticks_to_skip[i].getTime()] = true;
}
}
var tick_elements = []
for (var i = 0; i < ts_ticks.length; i++) {
var ts_tick = ts_ticks[i];
if (!(ts_tick.getTime() in skip_times)) {
var tick = TL.Dom.create("div", "tl-timeaxis-tick", tick_element),
tick_text = TL.Dom.create("span", "tl-timeaxis-tick-text tl-animate-opacity", tick);
tick_text.innerHTML = ts_tick.getDisplayDate(this.getLanguage(), dateformat);
tick_elements.push({
tick:tick,
tick_text:tick_text,
display_date:ts_tick.getDisplayDate(this.getLanguage(), dateformat),
date:ts_tick
});
}
}
return tick_elements;
},
positionTicks: function(timescale, optimal_tick_width, no_animate) {
// Handle Animation
if (no_animate) {
this._el.major.className = "tl-timeaxis-major";
this._el.minor.className = "tl-timeaxis-minor";
} else {
this._el.major.className = "tl-timeaxis-major tl-timeaxis-animate";
this._el.minor.className = "tl-timeaxis-minor tl-timeaxis-animate";
}
this._positionTickArray(this.major_ticks, timescale, optimal_tick_width);
this._positionTickArray(this.minor_ticks, timescale, optimal_tick_width);
},
_positionTickArray: function(tick_array, timescale, optimal_tick_width) {
// Poition Ticks & Handle density of ticks
if (tick_array[1] && tick_array[0]) {
var distance = ( timescale.getPosition(tick_array[1].date.getMillisecond()) - timescale.getPosition(tick_array[0].date.getMillisecond()) ),
fraction_of_array = 1;
if (distance < optimal_tick_width) {
fraction_of_array = Math.round(optimal_tick_width/timescale.getPixelsPerTick());
}
var show = 1;
for (var i = 0; i < tick_array.length; i++) {
var tick = tick_array[i];
// Poition Ticks
tick.tick.style.left = timescale.getPosition(tick.date.getMillisecond()) + "px";
tick.tick_text.innerHTML = tick.display_date;
// Handle density of ticks
if (fraction_of_array > 1) {
if (show >= fraction_of_array) {
show = 1;
tick.tick_text.style.opacity = 1;
tick.tick.className = "tl-timeaxis-tick";
} else {
show++;
tick.tick_text.style.opacity = 0;
tick.tick.className = "tl-timeaxis-tick tl-timeaxis-tick-hidden";
}
} else {
tick.tick_text.style.opacity = 1;
tick.tick.className = "tl-timeaxis-tick";
}
};
}
},
/* Events
================================================== */
/* Private Methods
================================================== */
_initLayout: function () {
this._el.content_container = TL.Dom.create("div", "tl-timeaxis-content-container", this._el.container);
this._el.major = TL.Dom.create("div", "tl-timeaxis-major", this._el.content_container);
this._el.minor = TL.Dom.create("div", "tl-timeaxis-minor", this._el.content_container);
// Fire event that the slide is loaded
this.onLoaded();
},
_initEvents: function() {
},
// Update Display
_updateDisplay: function(width, height, layout) {
if (width) {
this.options.width = width;
}
if (height) {
this.options.height = height;
}
}
});
/* **********************************************
Begin TL.AxisHelper.js
********************************************** */
/* TL.AxisHelper
Strategies for laying out the timenav
markers and time axis
Intended as a private class -- probably only known to TimeScale
================================================== */
TL.AxisHelper = TL.Class.extend({
initialize: function (options) {
if (options) {
this.scale = options.scale;
this.minor = options.minor;
this.major = options.major;
} else {
throw new TL.Error("axis_helper_no_options_err")
}
},
getPixelsPerTick: function(pixels_per_milli) {
return pixels_per_milli * this.minor.factor;
},
getMajorTicks: function(timescale) {
return this._getTicks(timescale, this.major)
},
getMinorTicks: function(timescale) {
return this._getTicks(timescale, this.minor)
},
_getTicks: function(timescale, option) {
var factor_scale = timescale._scaled_padding * option.factor;
var first_tick_time = timescale._earliest - factor_scale;
var last_tick_time = timescale._latest + factor_scale;
var ticks = []
for (var i = first_tick_time; i < last_tick_time; i += option.factor) {
ticks.push(timescale.getDateFromTime(i).floor(option.name));
}
return {
name: option.name,
ticks: ticks
}
}
});
(function(cls){ // add some class-level behavior
var HELPERS = {};
var setHelpers = function(scale_type, scales) {
HELPERS[scale_type] = [];
for (var idx = 0; idx < scales.length - 1; idx++) {
var minor = scales[idx];
var major = scales[idx+1];
HELPERS[scale_type].push(new cls({
scale: minor[3],
minor: { name: minor[0], factor: minor[1]},
major: { name: major[0], factor: major[1]}
}));
}
};
setHelpers('human', TL.Date.SCALES);
setHelpers('cosmological', TL.BigDate.SCALES);
cls.HELPERS = HELPERS;
cls.getBestHelper = function(ts,optimal_tick_width) {
if (typeof(optimal_tick_width) != 'number' ) {
optimal_tick_width = 100;
}
var ts_scale = ts.getScale();
var helpers = HELPERS[ts_scale];
if (!helpers) {
throw new TL.Error("axis_helper_scale_err", ts_scale);
}
var prev = null;
for (var idx = 0; idx < helpers.length; idx++) {
var curr = helpers[idx];
var pixels_per_tick = curr.getPixelsPerTick(ts._pixels_per_milli);
if (pixels_per_tick > optimal_tick_width) {
if (prev == null) return curr;
var curr_dist = Math.abs(optimal_tick_width - pixels_per_tick);
var prev_dist = Math.abs(optimal_tick_width - pixels_per_tick);
if (curr_dist < prev_dist) {
return curr;
} else {
return prev;
}
}
prev = curr;
}
return helpers[helpers.length - 1]; // last resort
}
})(TL.AxisHelper);
/* **********************************************
Begin TL.Timeline.js
********************************************** */
/* TimelineJS
Designed and built by Zach Wise at KnightLab
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at https://mozilla.org/MPL/2.0/.
================================================== */
/*
TODO
*/
/* Required Files
CodeKit Import
https://incident57.com/codekit/
================================================== */
// CORE
// @codekit-prepend "core/TL.js";
// @codekit-prepend "core/TL.Error.js";
// @codekit-prepend "core/TL.Util.js";
// @codekit-prepend "data/TL.Data.js";
// @codekit-prepend "core/TL.Class.js";
// @codekit-prepend "core/TL.Events.js";
// @codekit-prepend "core/TL.Browser.js";
// @codekit-prepend "core/TL.Load.js";
// @codekit-prepend "core/TL.TimelineConfig.js";
// @codekit-prepend "core/TL.ConfigFactory.js";
// LANGUAGE
// @codekit-prepend "language/TL.Language.js";
// @codekit-prepend "language/TL.I18NMixins.js";
// ANIMATION
// @codekit-prepend "animation/TL.Ease.js";
// @codekit-prepend "animation/TL.Animate.js";
// DOM
// @codekit-prepend "dom/TL.Point.js";
// @codekit-prepend "dom/TL.DomMixins.js";
// @codekit-prepend "dom/TL.Dom.js";
// @codekit-prepend "dom/TL.DomUtil.js";
// @codekit-prepend "dom/TL.DomEvent.js";
// @codekit-prepend "dom/TL.StyleSheet.js";
// Date
// @codekit-prepend "date/TL.Date.js";
// @codekit-prepend "date/TL.DateUtil.js";
// UI
// @codekit-prepend "ui/TL.Draggable.js";
// @codekit-prepend "ui/TL.Swipable.js";
// @codekit-prepend "ui/TL.MenuBar.js";
// @codekit-prepend "ui/TL.Message.js";
// MEDIA
// @codekit-prepend "media/TL.MediaType.js";
// @codekit-prepend "media/TL.Media.js";
// MEDIA TYPES
// @codekit-prepend "media/types/TL.Media.Blockquote.js";
// @codekit-prepend "media/types/TL.Media.DailyMotion.js";
// @codekit-prepend "media/types/TL.Media.DocumentCloud.js";
// @codekit-prepend "media/types/TL.Media.Flickr.js";
// @codekit-prepend "media/types/TL.Media.GoogleDoc.js";
// @codekit-prepend "media/types/TL.Media.GooglePlus.js";
// @codekit-prepend "media/types/TL.Media.IFrame.js";
// @codekit-prepend "media/types/TL.Media.Image.js";
// @codekit-prepend "media/types/TL.Media.Imgur.js";
// @codekit-prepend "media/types/TL.Media.Instagram.js";
// @codekit-prepend "media/types/TL.Media.GoogleMap.js";
// @codekit-prepend "media/types/TL.Media.PDF.js";
// @codekit-prepend "media/types/TL.Media.Profile.js";
// @codekit-prepend "media/types/TL.Media.Slider.js";
// @codekit-prepend "media/types/TL.Media.SoundCloud.js";
// @codekit-prepend "media/types/TL.Media.Spotify.js";
// @codekit-prepend "media/types/TL.Media.Storify.js";
// @codekit-prepend "media/types/TL.Media.Text.js";
// @codekit-prepend "media/types/TL.Media.Twitter.js";
// @codekit-prepend "media/types/TL.Media.TwitterEmbed.js";
// @codekit-prepend "media/types/TL.Media.Vimeo.js";
// @codekit-prepend "media/types/TL.Media.Vine.js";
// @codekit-prepend "media/types/TL.Media.Website.js";
// @codekit-prepend "media/types/TL.Media.Wikipedia.js";
// @codekit-prepend "media/types/TL.Media.Wistia.js";
// @codekit-prepend "media/types/TL.Media.YouTube.js";
// @codekit-prepend "media/types/TL.Media.Audio.js";
// @codekit-prepend "media/types/TL.Media.Video.js";
// STORYSLIDER
// @codekit-prepend "slider/TL.Slide.js";
// @codekit-prepend "slider/TL.SlideNav.js";
// @codekit-prepend "slider/TL.StorySlider.js";
// TIMENAV
// @codekit-prepend "timenav/TL.TimeNav.js";
// @codekit-prepend "timenav/TL.TimeMarker.js";
// @codekit-prepend "timenav/TL.TimeEra.js";
// @codekit-prepend "timenav/TL.TimeGroup.js";
// @codekit-prepend "timenav/TL.TimeScale.js";
// @codekit-prepend "timenav/TL.TimeAxis.js";
// @codekit-prepend "timenav/TL.AxisHelper.js";
TL.Timeline = TL.Class.extend({
includes: [TL.Events, TL.I18NMixins],
/* Private Methods
================================================== */
initialize: function (elem, data, options) {
var self = this;
if (!options) { options = {}};
// Version
this.version = "3.2.6";
// Ready
this.ready = false;
// DOM ELEMENTS
this._el = {
container: {},
storyslider: {},
timenav: {},
menubar: {}
};
// Determine Container Element
if (typeof elem === 'object') {
this._el.container = elem;
} else {
this._el.container = TL.Dom.get(elem);
}
// Slider
this._storyslider = {};
// Style Sheet
this._style_sheet = new TL.StyleSheet();
// TimeNav
this._timenav = {};
// Menu Bar
this._menubar = {};
// Loaded State
this._loaded = {storyslider:false, timenav:false};
// Data Object
this.config = null;
this.options = {
script_path: "",
height: this._el.container.offsetHeight,
width: this._el.container.offsetWidth,
debug: false,
is_embed: false,
is_full_embed: false,
hash_bookmark: false,
default_bg_color: {r:255, g:255, b:255},
scale_factor: 2, // How many screen widths wide should the timeline be
layout: "landscape", // portrait or landscape
timenav_position: "bottom", // timeline on top or bottom
optimal_tick_width: 60, // optimal distance (in pixels) between ticks on axis
base_class: "tl-timeline", // removing tl-timeline will break all default stylesheets...
timenav_height: null,
timenav_height_percentage: 25, // Overrides timenav height as a percentage of the screen
timenav_mobile_height_percentage: 40, // timenav height as a percentage on mobile devices
timenav_height_min: 175, // Minimum timenav height
marker_height_min: 30, // Minimum Marker Height
marker_width_min: 100, // Minimum Marker Width
marker_padding: 5, // Top Bottom Marker Padding
start_at_slide: 0,
start_at_end: false,
menubar_height: 0,
skinny_size: 650,
medium_size: 800,
relative_date: false, // Use momentjs to show a relative date from the slide.text.date.created_time field
use_bc: false, // Use declared suffix on dates earlier than 0
// animation
duration: 1000,
ease: TL.Ease.easeInOutQuint,
// interaction
dragging: true,
trackResize: true,
map_type: "stamen:toner-lite",
slide_padding_lr: 100, // padding on slide of slide
slide_default_fade: "0%", // landscape fade
zoom_sequence: [0.5, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89], // Array of Fibonacci numbers for TimeNav zoom levels
language: "en",
ga_property_id: null,
track_events: ['back_to_start','nav_next','nav_previous','zoom_in','zoom_out' ]
};
// Animation Objects
this.animator_timenav = null;
this.animator_storyslider = null;
this.animator_menubar = null;
// Add message to DOM
this.message = new TL.Message({}, {message_class: "tl-message-full"}, this._el.container);
// Merge Options
if (typeof(options.default_bg_color) == "string") {
var parsed = TL.Util.hexToRgb(options.default_bg_color); // will clear it out if its invalid
if (parsed) {
options.default_bg_color = parsed;
} else {
delete options.default_bg_color
trace("Invalid default background color. Ignoring.");
}
}
TL.Util.mergeData(this.options, options);
window.addEventListener("resize", function(e){
self.updateDisplay();
});
// Set Debug Mode
TL.debug = this.options.debug;
// Apply base class to container
TL.DomUtil.addClass(this._el.container, 'tl-timeline');
if (this.options.is_embed) {
TL.DomUtil.addClass(this._el.container, 'tl-timeline-embed');
}
if (this.options.is_full_embed) {
TL.DomUtil.addClass(this._el.container, 'tl-timeline-full-embed');
}
document.addEventListener("keydown", function(event) {
var keyName = event.key;
var currentSlide = self._getSlideIndex(self.current_id);
var _n = self.config.events.length - 1;
var lastSlide = self.config.title ? _n + 1 : _n;
var firstSlide = 0;
if (keyName == 'ArrowLeft'){
if (currentSlide!=firstSlide){
self.goToPrev();
}
}
else if (keyName == 'ArrowRight'){
if (currentSlide!=lastSlide){
self.goToNext();
}
}
});
// Use Relative Date Calculations
// NOT YET IMPLEMENTED
if(this.options.relative_date) {
if (typeof(moment) !== 'undefined') {
self._loadLanguage(data);
} else {
TL.Load.js(this.options.script_path + "/library/moment.js", function() {
self._loadLanguage(data);
trace("LOAD MOMENTJS")
});
}
} else {
self._loadLanguage(data);
}
},
_translateError: function(e) {
if(e.hasOwnProperty('stack')) {
trace(e.stack);
}
if(e.message_key) {
return this._(e.message_key) + (e.detail ? ' [' + e.detail +']' : '')
}
return e;
},
/* Load Language
================================================== */
_loadLanguage: function(data) {
try {
this.options.language = new TL.Language(this.options);
this._initData(data);
} catch(e) {
this.showMessage(this._translateError(e));
}
},
/* Navigation
================================================== */
// Goto slide with id
goToId: function(id) {
if (this.current_id != id) {
this.current_id = id;
this._timenav.goToId(this.current_id);
this._storyslider.goToId(this.current_id, false, true);
this.fire("change", {unique_id: this.current_id}, this);
}
},
// Goto slide n
goTo: function(n) {
if(this.config.title) {
if(n == 0) {
this.goToId(this.config.title.unique_id);
} else {
this.goToId(this.config.events[n - 1].unique_id);
}
} else {
this.goToId(this.config.events[n].unique_id);
}
},
// Goto first slide
goToStart: function() {
this.goTo(0);
},
// Goto last slide
goToEnd: function() {
var _n = this.config.events.length - 1;
this.goTo(this.config.title ? _n + 1 : _n);
},
// Goto previous slide
goToPrev: function() {
this.goTo(this._getSlideIndex(this.current_id) - 1);
},
// Goto next slide
goToNext: function() {
this.goTo(this._getSlideIndex(this.current_id) + 1);
},
/* Event maniupluation
================================================== */
// Add an event
add: function(data) {
var unique_id = this.config.addEvent(data);
var n = this._getEventIndex(unique_id);
var d = this.config.events[n];
this._storyslider.createSlide(d, this.config.title ? n+1 : n);
this._storyslider._updateDrawSlides();
this._timenav.createMarker(d, n);
this._timenav._updateDrawTimeline(false);
this.fire("added", {unique_id: unique_id});
},
// Remove an event
remove: function(n) {
if(n >= 0 && n < this.config.events.length) {
// If removing the current, nav to new one first
if(this.config.events[n].unique_id == this.current_id) {
if(n < this.config.events.length - 1) {
this.goTo(n + 1);
} else {
this.goTo(n - 1);
}
}
var event = this.config.events.splice(n, 1);
delete this.config.event_dict[event[0].unique_id];
this._storyslider.destroySlide(this.config.title ? n+1 : n);
this._storyslider._updateDrawSlides();
this._timenav.destroyMarker(n);
this._timenav._updateDrawTimeline(false);
this.fire("removed", {unique_id: event[0].unique_id});
}
},
removeId: function(id) {
this.remove(this._getEventIndex(id));
},
/* Get slide data
================================================== */
getData: function(n) {
if(this.config.title) {
if(n == 0) {
return this.config.title;
} else if(n > 0 && n <= this.config.events.length) {
return this.config.events[n - 1];
}
} else if(n >= 0 && n < this.config.events.length) {
return this.config.events[n];
}
return null;
},
getDataById: function(id) {
return this.getData(this._getSlideIndex(id));
},
/* Get slide object
================================================== */
getSlide: function(n) {
if(n >= 0 && n < this._storyslider._slides.length) {
return this._storyslider._slides[n];
}
return null;
},
getSlideById: function(id) {
return this.getSlide(this._getSlideIndex(id));
},
getCurrentSlide: function() {
return this.getSlideById(this.current_id);
},
/* Display
================================================== */
updateDisplay: function() {
if (this.ready) {
this._updateDisplay();
}
},
/*
Compute the height of the navigation section of the Timeline, taking into account
the possibility of an explicit height or height percentage, but also honoring the
`timenav_height_min` option value. If `timenav_height` is specified it takes precedence over `timenav_height_percentage` but in either case, if the resultant pixel height is less than `options.timenav_height_min` then the value of `options.timenav_height_min` will be returned. (A minor adjustment is made to the returned value to account for marker padding.)
Arguments:
@timenav_height (optional): an integer value for the desired height in pixels
@timenav_height_percentage (optional): an integer between 1 and 100
*/
_calculateTimeNavHeight: function(timenav_height, timenav_height_percentage) {
var height = 0;
if (timenav_height) {
height = timenav_height;
} else {
if (this.options.timenav_height_percentage || timenav_height_percentage) {
if (timenav_height_percentage) {
height = Math.round((this.options.height/100)*timenav_height_percentage);
} else {
height = Math.round((this.options.height/100)*this.options.timenav_height_percentage);
}
}
}
// Set new minimum based on how many rows needed
if (this._timenav.ready) {
if (this.options.timenav_height_min < this._timenav.getMinimumHeight()) {
this.options.timenav_height_min = this._timenav.getMinimumHeight();
}
}
// If height is less than minimum set it to minimum
if (height < this.options.timenav_height_min) {
height = this.options.timenav_height_min;
}
height = height - (this.options.marker_padding * 2);
return height;
},
/* Private Methods
================================================== */
// Update View
_updateDisplay: function(timenav_height, animate, d) {
var duration = this.options.duration,
display_class = this.options.base_class,
menu_position = 0,
self = this;
if (d) {
duration = d;
}
// Update width and height
this.options.width = this._el.container.offsetWidth;
this.options.height = this._el.container.offsetHeight;
// Check if skinny
if (this.options.width <= this.options.skinny_size) {
display_class += " tl-skinny";
this.options.layout = "portrait";
} else if (this.options.width <= this.options.medium_size) {
display_class += " tl-medium";
this.options.layout = "landscape";
} else {
this.options.layout = "landscape";
}
// Detect Mobile and Update Orientation on Touch devices
if (TL.Browser.touch) {
this.options.layout = TL.Browser.orientation();
}
if (TL.Browser.mobile) {
display_class += " tl-mobile";
// Set TimeNav Height
this.options.timenav_height = this._calculateTimeNavHeight(timenav_height, this.options.timenav_mobile_height_percentage);
} else {
// Set TimeNav Height
this.options.timenav_height = this._calculateTimeNavHeight(timenav_height);
}
// LAYOUT
if (this.options.layout == "portrait") {
// Portrait
display_class += " tl-layout-portrait";
} else {
// Landscape
display_class += " tl-layout-landscape";
}
// Set StorySlider Height
this.options.storyslider_height = (this.options.height - this.options.timenav_height);
// Positon Menu
if (this.options.timenav_position == "top") {
menu_position = ( Math.ceil(this.options.timenav_height)/2 ) - (this._el.menubar.offsetHeight/2) - (39/2) ;
} else {
menu_position = Math.round(this.options.storyslider_height + 1 + ( Math.ceil(this.options.timenav_height)/2 ) - (this._el.menubar.offsetHeight/2) - (35/2));
}
if (animate) {
// Animate TimeNav
/*
if (this.animator_timenav) {
this.animator_timenav.stop();
}
this.animator_timenav = TL.Animate(this._el.timenav, {
height: (this.options.timenav_height) + "px",
duration: duration/4,
easing: TL.Ease.easeOutStrong,
complete: function () {
//self._map.updateDisplay(self.options.width, self.options.timenav_height, animate, d, self.options.menubar_height);
}
});
*/
this._el.timenav.style.height = Math.ceil(this.options.timenav_height) + "px";
// Animate StorySlider
if (this.animator_storyslider) {
this.animator_storyslider.stop();
}
this.animator_storyslider = TL.Animate(this._el.storyslider, {
height: this.options.storyslider_height + "px",
duration: duration/2,
easing: TL.Ease.easeOutStrong
});
// Animate Menubar
if (this.animator_menubar) {
this.animator_menubar.stop();
}
this.animator_menubar = TL.Animate(this._el.menubar, {
top: menu_position + "px",
duration: duration/2,
easing: TL.Ease.easeOutStrong
});
} else {
// TimeNav
this._el.timenav.style.height = Math.ceil(this.options.timenav_height) + "px";
// StorySlider
this._el.storyslider.style.height = this.options.storyslider_height + "px";
// Menubar
this._el.menubar.style.top = menu_position + "px";
}
if (this.message) {
this.message.updateDisplay(this.options.width, this.options.height);
}
// Update Component Displays
this._timenav.updateDisplay(this.options.width, this.options.timenav_height, animate);
this._storyslider.updateDisplay(this.options.width, this.options.storyslider_height, animate, this.options.layout);
if (this.options.language.direction == 'rtl') {
display_class += ' tl-rtl';
}
// Apply class
this._el.container.className = display_class;
},
// Update hashbookmark in the url bar
_updateHashBookmark: function(id) {
var hash = "#" + "event-" + id.toString();
if (window.location.protocol != 'file:') {
window.history.replaceState(null, "Browsing TimelineJS", hash);
}
this.fire("hash_updated", {unique_id:this.current_id, hashbookmark:"#" + "event-" + id.toString()}, this);
},
/* Init
================================================== */
// Initialize the data
_initData: function(data) {
var self = this;
if (typeof data == 'string') {
var self = this;
TL.ConfigFactory.makeConfig(data, function(config) {
self.setConfig(config);
});
} else if (TL.TimelineConfig == data.constructor) {
this.setConfig(data);
} else {
this.setConfig(new TL.TimelineConfig(data));
}
},
setConfig: function(config) {
this.config = config;
this.config.validate();
this._validateOptions();
if (this.config.isValid()) {
try {
this._onDataLoaded();
} catch(e) {
this.showMessage(""+ this._('error') +": " + this._translateError(e));
}
} else {
var translated_errs = [];
for(var i = 0, errs = this.config.getErrors(); i < errs.length; i++) {
translated_errs.push(this._translateError(errs[i]));
}
this.showMessage(""+ this._('error') +": " + translated_errs.join('
'));
// should we set 'self.ready'? if not, it won't resize,
// but most resizing would only work
// if more setup happens
}
},
_validateOptions: function() {
// assumes that this.options and this.config have been set.
var INTEGER_PROPERTIES = ['timenav_height', 'timenav_height_min', 'marker_height_min', 'marker_width_min', 'marker_padding', 'start_at_slide', 'slide_padding_lr' ];
for (var i = 0; i < INTEGER_PROPERTIES.length; i++) {
var opt = INTEGER_PROPERTIES[i];
var value = this.options[opt];
valid = true;
if (typeof(value) == 'number') {
valid = (value == parseInt(value))
} else if (typeof(value) == "string") {
valid = (value.match(/^\s*(\-?\d+)?\s*$/));
}
if (!valid) {
this.config.logError({ message_key: 'invalid_integer_option', detail: opt });
}
}
},
// Initialize the layout
_initLayout: function () {
var self = this;
this.message.removeFrom(this._el.container);
this._el.container.innerHTML = "";
// Create Layout
if (this.options.timenav_position == "top") {
this._el.timenav = TL.Dom.create('div', 'tl-timenav', this._el.container);
this._el.storyslider = TL.Dom.create('div', 'tl-storyslider', this._el.container);
} else {
this._el.storyslider = TL.Dom.create('div', 'tl-storyslider', this._el.container);
this._el.timenav = TL.Dom.create('div', 'tl-timenav', this._el.container);
}
this._el.menubar = TL.Dom.create('div', 'tl-menubar', this._el.container);
// Initial Default Layout
this.options.width = this._el.container.offsetWidth;
this.options.height = this._el.container.offsetHeight;
// this._el.storyslider.style.top = "1px";
// Set TimeNav Height
this.options.timenav_height = this._calculateTimeNavHeight(this.options.timenav_height);
// Create TimeNav
this._timenav = new TL.TimeNav(this._el.timenav, this.config, this.options);
this._timenav.on('loaded', this._onTimeNavLoaded, this);
this._timenav.on('update_timenav_min', this._updateTimeNavHeightMin, this);
this._timenav.options.height = this.options.timenav_height;
this._timenav.init();
// intial_zoom cannot be applied before the timenav has been created
if (this.options.initial_zoom) {
// at this point, this.options refers to the merged set of options
this.setZoom(this.options.initial_zoom);
}
// Create StorySlider
this._storyslider = new TL.StorySlider(this._el.storyslider, this.config, this.options);
this._storyslider.on('loaded', this._onStorySliderLoaded, this);
this._storyslider.init();
// Create Menu Bar
this._menubar = new TL.MenuBar(this._el.menubar, this._el.container, this.options);
// LAYOUT
if (this.options.layout == "portrait") {
this.options.storyslider_height = (this.options.height - this.options.timenav_height - 1);
} else {
this.options.storyslider_height = (this.options.height - 1);
}
// Update Display
this._updateDisplay(this._timenav.options.height, true, 2000);
},
/* Depends upon _initLayout because these events are on things the layout initializes */
_initEvents: function () {
// TimeNav Events
this._timenav.on('change', this._onTimeNavChange, this);
this._timenav.on('zoomtoggle', this._onZoomToggle, this);
// StorySlider Events
this._storyslider.on('change', this._onSlideChange, this);
this._storyslider.on('colorchange', this._onColorChange, this);
this._storyslider.on('nav_next', this._onStorySliderNext, this);
this._storyslider.on('nav_previous', this._onStorySliderPrevious, this);
// Menubar Events
this._menubar.on('zoom_in', this._onZoomIn, this);
this._menubar.on('zoom_out', this._onZoomOut, this);
this._menubar.on('back_to_start', this._onBackToStart, this);
},
/* Analytics
================================================== */
_initGoogleAnalytics: function() {
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', this.options.ga_property_id, 'auto');
ga('set', 'anonymizeIp', true);
},
_initAnalytics: function() {
if (this.options.ga_property_id === null) { return; }
this._initGoogleAnalytics();
ga('send', 'pageview');
var events = this.options.track_events;
for (i=0; i < events.length; i++) {
var event_ = events[i];
this.addEventListener(event_, function(e) {
ga('send', 'event', e.type, 'clicked');
});
}
},
_onZoomToggle: function(e) {
if (e.zoom == "in") {
this._menubar.toogleZoomIn(e.show);
} else if (e.zoom == "out") {
this._menubar.toogleZoomOut(e.show);
}
},
/* Get index of event by id
================================================== */
_getEventIndex: function(id) {
for(var i = 0; i < this.config.events.length; i++) {
if(id == this.config.events[i].unique_id) {
return i;
}
}
return -1;
},
/* Get index of slide by id
================================================== */
_getSlideIndex: function(id) {
if(this.config.title && this.config.title.unique_id == id) {
return 0;
}
for(var i = 0; i < this.config.events.length; i++) {
if(id == this.config.events[i].unique_id) {
return this.config.title ? i+1 : i;
}
}
return -1;
},
/* Events
================================================== */
_onDataLoaded: function(e) {
this.fire("dataloaded");
this._initLayout();
this._initEvents();
this._initAnalytics();
if (this.message) {
this.message.hide();
}
this.ready = true;
},
showMessage: function(msg) {
if (this.message) {
this.message.updateMessage(msg);
} else {
trace("No message display available.")
trace(msg);
}
},
_onColorChange: function(e) {
this.fire("color_change", {unique_id:this.current_id}, this);
if (e.color || e.image) {
} else {
}
},
_onSlideChange: function(e) {
if (this.current_id != e.unique_id) {
this.current_id = e.unique_id;
this._timenav.goToId(this.current_id);
this._onChange(e);
}
},
_onTimeNavChange: function(e) {
if (this.current_id != e.unique_id) {
this.current_id = e.unique_id;
this._storyslider.goToId(this.current_id);
this._onChange(e);
}
},
_onChange: function(e) {
this.fire("change", {unique_id:this.current_id}, this);
if (this.options.hash_bookmark && this.current_id) {
this._updateHashBookmark(this.current_id);
}
},
_onBackToStart: function(e) {
this._storyslider.goTo(0);
this.fire("back_to_start", {unique_id:this.current_id}, this);
},
/**
* Zoom in and zoom out should be part of the public API.
*/
zoomIn: function() {
this._timenav.zoomIn();
},
zoomOut: function() {
this._timenav.zoomOut();
},
setZoom: function(level) {
this._timenav.setZoom(level);
},
_onZoomIn: function(e) {
this._timenav.zoomIn();
this.fire("zoom_in", {zoom_level:this._timenav.options.scale_factor}, this);
},
_onZoomOut: function(e) {
this._timenav.zoomOut();
this.fire("zoom_out", {zoom_level:this._timenav.options.scale_factor}, this);
},
_onTimeNavLoaded: function() {
this._loaded.timenav = true;
this._onLoaded();
},
_onStorySliderLoaded: function() {
this._loaded.storyslider = true;
this._onLoaded();
},
_onStorySliderNext: function(e) {
this.fire("nav_next", e);
},
_onStorySliderPrevious: function(e) {
this.fire("nav_previous", e);
},
_onLoaded: function() {
if (this._loaded.storyslider && this._loaded.timenav) {
this.fire("loaded", this.config);
// Go to proper slide
if (this.options.hash_bookmark && window.location.hash != "") {
this.goToId(window.location.hash.replace("#event-", ""));
} else {
if( TL.Util.isTrue(this.options.start_at_end) || this.options.start_at_slide > this.config.events.length ) {
this.goToEnd();
} else {
this.goTo(this.options.start_at_slide);
}
if (this.options.hash_bookmark ) {
this._updateHashBookmark(this.current_id);
}
}
}
}
});
TL.Timeline.source_path = (function() {
var script_tags = document.getElementsByTagName('script');
var src = script_tags[script_tags.length-1].src;
return src.substr(0,src.lastIndexOf('/'));
})();