/*! * https://github.com/es-shims/es5-shim * @license es5-shim Copyright 2009-2014 by contributors, MIT License * see https://github.com/es-shims/es5-shim/blob/master/LICENSE */ // vim: ts=4 sts=4 sw=4 expandtab //Add semicolon to prevent IIFE from being passed as argument to concated code. ; // UMD (Universal Module Definition) // see https://github.com/umdjs/umd/blob/master/returnExports.js (function (root, factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define(factory); } else if (typeof exports === 'object') { // Node. Does not work with strict CommonJS, but // only CommonJS-like enviroments that support module.exports, // like Node. module.exports = factory(); } else { // Browser globals (root is window) root.returnExports = factory(); } }(this, function () { /** * Brings an environment as close to ECMAScript 5 compliance * as is possible with the facilities of erstwhile engines. * * Annotated ES5: http://es5.github.com/ (specific links below) * ES5 Spec: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf * Required reading: http://javascriptweblog.wordpress.com/2011/12/05/extending-javascript-natives/ */ // Shortcut to an often accessed properties, in order to avoid multiple // dereference that costs universally. var ArrayPrototype = Array.prototype; var ObjectPrototype = Object.prototype; var FunctionPrototype = Function.prototype; var StringPrototype = String.prototype; var NumberPrototype = Number.prototype; var _Array_slice_ = ArrayPrototype.slice; var array_splice = ArrayPrototype.splice; var array_push = ArrayPrototype.push; var array_unshift = ArrayPrototype.unshift; var call = FunctionPrototype.call; // Having a toString local variable name breaks in Opera so use _toString. var _toString = ObjectPrototype.toString; var isFunction = function (val) { return ObjectPrototype.toString.call(val) === '[object Function]'; }; var isRegex = function (val) { return ObjectPrototype.toString.call(val) === '[object RegExp]'; }; var isArray = function isArray(obj) { return _toString.call(obj) === "[object Array]"; }; var isString = function isString(obj) { return _toString.call(obj) === "[object String]"; }; var isArguments = function isArguments(value) { var str = _toString.call(value); var isArgs = str === '[object Arguments]'; if (!isArgs) { isArgs = !isArray(value) && value !== null && typeof value === 'object' && typeof value.length === 'number' && value.length >= 0 && isFunction(value.callee); } return isArgs; }; var supportsDescriptors = Object.defineProperty && (function () { try { Object.defineProperty({}, 'x', {}); return true; } catch (e) { /* this is ES3 */ return false; } }()); // Define configurable, writable and non-enumerable props // if they don't exist. var defineProperty; if (supportsDescriptors) { defineProperty = function (object, name, method, forceAssign) { if (!forceAssign && (name in object)) { return; } Object.defineProperty(object, name, { configurable: true, enumerable: false, writable: true, value: method }); }; } else { defineProperty = function (object, name, method, forceAssign) { if (!forceAssign && (name in object)) { return; } object[name] = method; }; } var defineProperties = function (object, map, forceAssign) { for (var name in map) { if (ObjectPrototype.hasOwnProperty.call(map, name)) { defineProperty(object, name, map[name], forceAssign); } } }; // // Util // ====== // // ES5 9.4 // http://es5.github.com/#x9.4 // http://jsperf.com/to-integer function toInteger(n) { n = +n; if (n !== n) { // isNaN n = 0; } else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) { n = (n > 0 || -1) * Math.floor(Math.abs(n)); } return n; } function isPrimitive(input) { var type = typeof input; return ( input === null || type === "undefined" || type === "boolean" || type === "number" || type === "string" ); } function toPrimitive(input) { var val, valueOf, toStr; if (isPrimitive(input)) { return input; } valueOf = input.valueOf; if (isFunction(valueOf)) { val = valueOf.call(input); if (isPrimitive(val)) { return val; } } toStr = input.toString; if (isFunction(toStr)) { val = toStr.call(input); if (isPrimitive(val)) { return val; } } throw new TypeError(); } // ES5 9.9 // http://es5.github.com/#x9.9 var toObject = function (o) { if (o == null) { // this matches both null and undefined throw new TypeError("can't convert " + o + " to object"); } return Object(o); }; var ToUint32 = function ToUint32(x) { return x >>> 0; }; // // Function // ======== // // ES-5 15.3.4.5 // http://es5.github.com/#x15.3.4.5 function Empty() {} defineProperties(FunctionPrototype, { bind: function bind(that) { // .length is 1 // 1. Let Target be the this value. var target = this; // 2. If IsCallable(Target) is false, throw a TypeError exception. if (!isFunction(target)) { throw new TypeError("Function.prototype.bind called on incompatible " + target); } // 3. Let A be a new (possibly empty) internal list of all of the // argument values provided after thisArg (arg1, arg2 etc), in order. // XXX slicedArgs will stand in for "A" if used var args = _Array_slice_.call(arguments, 1); // for normal call // 4. Let F be a new native ECMAScript object. // 11. Set the [[Prototype]] internal property of F to the standard // built-in Function prototype object as specified in 15.3.3.1. // 12. Set the [[Call]] internal property of F as described in // 15.3.4.5.1. // 13. Set the [[Construct]] internal property of F as described in // 15.3.4.5.2. // 14. Set the [[HasInstance]] internal property of F as described in // 15.3.4.5.3. var binder = function () { if (this instanceof bound) { // 15.3.4.5.2 [[Construct]] // When the [[Construct]] internal method of a function object, // F that was created using the bind function is called with a // list of arguments ExtraArgs, the following steps are taken: // 1. Let target be the value of F's [[TargetFunction]] // internal property. // 2. If target has no [[Construct]] internal method, a // TypeError exception is thrown. // 3. Let boundArgs be the value of F's [[BoundArgs]] internal // property. // 4. Let args be a new list containing the same values as the // list boundArgs in the same order followed by the same // values as the list ExtraArgs in the same order. // 5. Return the result of calling the [[Construct]] internal // method of target providing args as the arguments. var result = target.apply( this, args.concat(_Array_slice_.call(arguments)) ); if (Object(result) === result) { return result; } return this; } else { // 15.3.4.5.1 [[Call]] // When the [[Call]] internal method of a function object, F, // which was created using the bind function is called with a // this value and a list of arguments ExtraArgs, the following // steps are taken: // 1. Let boundArgs be the value of F's [[BoundArgs]] internal // property. // 2. Let boundThis be the value of F's [[BoundThis]] internal // property. // 3. Let target be the value of F's [[TargetFunction]] internal // property. // 4. Let args be a new list containing the same values as the // list boundArgs in the same order followed by the same // values as the list ExtraArgs in the same order. // 5. Return the result of calling the [[Call]] internal method // of target providing boundThis as the this value and // providing args as the arguments. // equiv: target.call(this, ...boundArgs, ...args) return target.apply( that, args.concat(_Array_slice_.call(arguments)) ); } }; // 15. If the [[Class]] internal property of Target is "Function", then // a. Let L be the length property of Target minus the length of A. // b. Set the length own property of F to either 0 or L, whichever is // larger. // 16. Else set the length own property of F to 0. var boundLength = Math.max(0, target.length - args.length); // 17. Set the attributes of the length own property of F to the values // specified in 15.3.5.1. var boundArgs = []; for (var i = 0; i < boundLength; i++) { boundArgs.push("$" + i); } // XXX Build a dynamic function with desired amount of arguments is the only // way to set the length property of a function. // In environments where Content Security Policies enabled (Chrome extensions, // for ex.) all use of eval or Function costructor throws an exception. // However in all of these environments Function.prototype.bind exists // and so this code will never be executed. var bound = Function("binder", "return function (" + boundArgs.join(",") + "){return binder.apply(this,arguments)}")(binder); if (target.prototype) { Empty.prototype = target.prototype; bound.prototype = new Empty(); // Clean up dangling references. Empty.prototype = null; } // TODO // 18. Set the [[Extensible]] internal property of F to true. // TODO // 19. Let thrower be the [[ThrowTypeError]] function Object (13.2.3). // 20. Call the [[DefineOwnProperty]] internal method of F with // arguments "caller", PropertyDescriptor {[[Get]]: thrower, [[Set]]: // thrower, [[Enumerable]]: false, [[Configurable]]: false}, and // false. // 21. Call the [[DefineOwnProperty]] internal method of F with // arguments "arguments", PropertyDescriptor {[[Get]]: thrower, // [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false}, // and false. // TODO // NOTE Function objects created using Function.prototype.bind do not // have a prototype property or the [[Code]], [[FormalParameters]], and // [[Scope]] internal properties. // XXX can't delete prototype in pure-js. // 22. Return F. return bound; } }); // _Please note: Shortcuts are defined after `Function.prototype.bind` as we // us it in defining shortcuts. var owns = call.bind(ObjectPrototype.hasOwnProperty); // If JS engine supports accessors creating shortcuts. var defineGetter; var defineSetter; var lookupGetter; var lookupSetter; var supportsAccessors; if ((supportsAccessors = owns(ObjectPrototype, "__defineGetter__"))) { defineGetter = call.bind(ObjectPrototype.__defineGetter__); defineSetter = call.bind(ObjectPrototype.__defineSetter__); lookupGetter = call.bind(ObjectPrototype.__lookupGetter__); lookupSetter = call.bind(ObjectPrototype.__lookupSetter__); } // // Array // ===== // // ES5 15.4.4.12 // http://es5.github.com/#x15.4.4.12 var spliceNoopReturnsEmptyArray = (function () { var a = [1, 2]; var result = a.splice(); return a.length === 2 && isArray(result) && result.length === 0; }()); defineProperties(ArrayPrototype, { // Safari 5.0 bug where .splice() returns undefined splice: function splice(start, deleteCount) { if (arguments.length === 0) { return []; } else { return array_splice.apply(this, arguments); } } }, spliceNoopReturnsEmptyArray); var spliceWorksWithEmptyObject = (function () { var obj = {}; ArrayPrototype.splice.call(obj, 0, 0, 1); return obj.length === 1; }()); defineProperties(ArrayPrototype, { splice: function splice(start, deleteCount) { if (arguments.length === 0) { return []; } var args = arguments; this.length = Math.max(toInteger(this.length), 0); if (arguments.length > 0 && typeof deleteCount !== 'number') { args = _Array_slice_.call(arguments); if (args.length < 2) { args.push(this.length - start); } else { args[1] = toInteger(deleteCount); } } return array_splice.apply(this, args); } }, !spliceWorksWithEmptyObject); // ES5 15.4.4.12 // http://es5.github.com/#x15.4.4.13 // Return len+argCount. // [bugfix, ielt8] // IE < 8 bug: [].unshift(0) === undefined but should be "1" var hasUnshiftReturnValueBug = [].unshift(0) !== 1; defineProperties(ArrayPrototype, { unshift: function () { array_unshift.apply(this, arguments); return this.length; } }, hasUnshiftReturnValueBug); // ES5 15.4.3.2 // http://es5.github.com/#x15.4.3.2 // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/isArray defineProperties(Array, { isArray: isArray }); // The IsCallable() check in the Array functions // has been replaced with a strict check on the // internal class of the object to trap cases where // the provided function was actually a regular // expression literal, which in V8 and // JavaScriptCore is a typeof "function". Only in // V8 are regular expression literals permitted as // reduce parameters, so it is desirable in the // general case for the shim to match the more // strict and common behavior of rejecting regular // expressions. // ES5 15.4.4.18 // http://es5.github.com/#x15.4.4.18 // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/forEach // Check failure of by-index access of string characters (IE < 9) // and failure of `0 in boxedString` (Rhino) var boxedString = Object("a"); var splitString = boxedString[0] !== "a" || !(0 in boxedString); var properlyBoxesContext = function properlyBoxed(method) { // Check node 0.6.21 bug where third parameter is not boxed var properlyBoxesNonStrict = true; var properlyBoxesStrict = true; if (method) { method.call('foo', function (_, __, context) { if (typeof context !== 'object') { properlyBoxesNonStrict = false; } }); method.call([1], function () { 'use strict'; properlyBoxesStrict = typeof this === 'string'; }, 'x'); } return !!method && properlyBoxesNonStrict && properlyBoxesStrict; }; defineProperties(ArrayPrototype, { forEach: function forEach(fun /*, thisp*/) { var object = toObject(this), self = splitString && isString(this) ? this.split('') : object, thisp = arguments[1], i = -1, length = self.length >>> 0; // If no callback function or if callback is not a callable function if (!isFunction(fun)) { throw new TypeError(); // TODO message } while (++i < length) { if (i in self) { // Invoke the callback function with call, passing arguments: // context, property value, property key, thisArg object // context fun.call(thisp, self[i], i, object); } } } }, !properlyBoxesContext(ArrayPrototype.forEach)); // ES5 15.4.4.19 // http://es5.github.com/#x15.4.4.19 // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/map defineProperties(ArrayPrototype, { map: function map(fun /*, thisp*/) { var object = toObject(this), self = splitString && isString(this) ? this.split('') : object, length = self.length >>> 0, result = Array(length), thisp = arguments[1]; // If no callback function or if callback is not a callable function if (!isFunction(fun)) { throw new TypeError(fun + " is not a function"); } for (var i = 0; i < length; i++) { if (i in self) { result[i] = fun.call(thisp, self[i], i, object); } } return result; } }, !properlyBoxesContext(ArrayPrototype.map)); // ES5 15.4.4.20 // http://es5.github.com/#x15.4.4.20 // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/filter defineProperties(ArrayPrototype, { filter: function filter(fun /*, thisp */) { var object = toObject(this), self = splitString && isString(this) ? this.split('') : object, length = self.length >>> 0, result = [], value, thisp = arguments[1]; // If no callback function or if callback is not a callable function if (!isFunction(fun)) { throw new TypeError(fun + " is not a function"); } for (var i = 0; i < length; i++) { if (i in self) { value = self[i]; if (fun.call(thisp, value, i, object)) { result.push(value); } } } return result; } }, !properlyBoxesContext(ArrayPrototype.filter)); // ES5 15.4.4.16 // http://es5.github.com/#x15.4.4.16 // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/every defineProperties(ArrayPrototype, { every: function every(fun /*, thisp */) { var object = toObject(this), self = splitString && isString(this) ? this.split('') : object, length = self.length >>> 0, thisp = arguments[1]; // If no callback function or if callback is not a callable function if (!isFunction(fun)) { throw new TypeError(fun + " is not a function"); } for (var i = 0; i < length; i++) { if (i in self && !fun.call(thisp, self[i], i, object)) { return false; } } return true; } }, !properlyBoxesContext(ArrayPrototype.every)); // ES5 15.4.4.17 // http://es5.github.com/#x15.4.4.17 // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/some defineProperties(ArrayPrototype, { some: function some(fun /*, thisp */) { var object = toObject(this), self = splitString && isString(this) ? this.split('') : object, length = self.length >>> 0, thisp = arguments[1]; // If no callback function or if callback is not a callable function if (!isFunction(fun)) { throw new TypeError(fun + " is not a function"); } for (var i = 0; i < length; i++) { if (i in self && fun.call(thisp, self[i], i, object)) { return true; } } return false; } }, !properlyBoxesContext(ArrayPrototype.some)); // ES5 15.4.4.21 // http://es5.github.com/#x15.4.4.21 // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduce var reduceCoercesToObject = false; if (ArrayPrototype.reduce) { reduceCoercesToObject = typeof ArrayPrototype.reduce.call('es5', function (_, __, ___, list) { return list; }) === 'object'; } defineProperties(ArrayPrototype, { reduce: function reduce(fun /*, initial*/) { var object = toObject(this), self = splitString && isString(this) ? this.split('') : object, length = self.length >>> 0; // If no callback function or if callback is not a callable function if (!isFunction(fun)) { throw new TypeError(fun + " is not a function"); } // no value to return if no initial value and an empty array if (!length && arguments.length === 1) { throw new TypeError("reduce of empty array with no initial value"); } var i = 0; var result; if (arguments.length >= 2) { result = arguments[1]; } else { do { if (i in self) { result = self[i++]; break; } // if array contains no values, no initial value to return if (++i >= length) { throw new TypeError("reduce of empty array with no initial value"); } } while (true); } for (; i < length; i++) { if (i in self) { result = fun.call(void 0, result, self[i], i, object); } } return result; } }, !reduceCoercesToObject); // ES5 15.4.4.22 // http://es5.github.com/#x15.4.4.22 // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduceRight var reduceRightCoercesToObject = false; if (ArrayPrototype.reduceRight) { reduceRightCoercesToObject = typeof ArrayPrototype.reduceRight.call('es5', function (_, __, ___, list) { return list; }) === 'object'; } defineProperties(ArrayPrototype, { reduceRight: function reduceRight(fun /*, initial*/) { var object = toObject(this), self = splitString && isString(this) ? this.split('') : object, length = self.length >>> 0; // If no callback function or if callback is not a callable function if (!isFunction(fun)) { throw new TypeError(fun + " is not a function"); } // no value to return if no initial value, empty array if (!length && arguments.length === 1) { throw new TypeError("reduceRight of empty array with no initial value"); } var result, i = length - 1; if (arguments.length >= 2) { result = arguments[1]; } else { do { if (i in self) { result = self[i--]; break; } // if array contains no values, no initial value to return if (--i < 0) { throw new TypeError("reduceRight of empty array with no initial value"); } } while (true); } if (i < 0) { return result; } do { if (i in self) { result = fun.call(void 0, result, self[i], i, object); } } while (i--); return result; } }, !reduceRightCoercesToObject); // ES5 15.4.4.14 // http://es5.github.com/#x15.4.4.14 // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf var hasFirefox2IndexOfBug = Array.prototype.indexOf && [0, 1].indexOf(1, 2) !== -1; defineProperties(ArrayPrototype, { indexOf: function indexOf(sought /*, fromIndex */ ) { var self = splitString && isString(this) ? this.split('') : toObject(this), length = self.length >>> 0; if (!length) { return -1; } var i = 0; if (arguments.length > 1) { i = toInteger(arguments[1]); } // handle negative indices i = i >= 0 ? i : Math.max(0, length + i); for (; i < length; i++) { if (i in self && self[i] === sought) { return i; } } return -1; } }, hasFirefox2IndexOfBug); // ES5 15.4.4.15 // http://es5.github.com/#x15.4.4.15 // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/lastIndexOf var hasFirefox2LastIndexOfBug = Array.prototype.lastIndexOf && [0, 1].lastIndexOf(0, -3) !== -1; defineProperties(ArrayPrototype, { lastIndexOf: function lastIndexOf(sought /*, fromIndex */) { var self = splitString && isString(this) ? this.split('') : toObject(this), length = self.length >>> 0; if (!length) { return -1; } var i = length - 1; if (arguments.length > 1) { i = Math.min(i, toInteger(arguments[1])); } // handle negative indices i = i >= 0 ? i : length - Math.abs(i); for (; i >= 0; i--) { if (i in self && sought === self[i]) { return i; } } return -1; } }, hasFirefox2LastIndexOfBug); // // Object // ====== // // ES5 15.2.3.14 // http://es5.github.com/#x15.2.3.14 // http://whattheheadsaid.com/2010/10/a-safer-object-keys-compatibility-implementation var hasDontEnumBug = !({'toString': null}).propertyIsEnumerable('toString'), hasProtoEnumBug = (function () {}).propertyIsEnumerable('prototype'), dontEnums = [ "toString", "toLocaleString", "valueOf", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "constructor" ], dontEnumsLength = dontEnums.length; defineProperties(Object, { keys: function keys(object) { var isFn = isFunction(object), isArgs = isArguments(object), isObject = object !== null && typeof object === 'object', isStr = isObject && isString(object); if (!isObject && !isFn && !isArgs) { throw new TypeError("Object.keys called on a non-object"); } var theKeys = []; var skipProto = hasProtoEnumBug && isFn; if (isStr || isArgs) { for (var i = 0; i < object.length; ++i) { theKeys.push(String(i)); } } else { for (var name in object) { if (!(skipProto && name === 'prototype') && owns(object, name)) { theKeys.push(String(name)); } } } if (hasDontEnumBug) { var ctor = object.constructor, skipConstructor = ctor && ctor.prototype === object; for (var j = 0; j < dontEnumsLength; j++) { var dontEnum = dontEnums[j]; if (!(skipConstructor && dontEnum === 'constructor') && owns(object, dontEnum)) { theKeys.push(dontEnum); } } } return theKeys; } }); var keysWorksWithArguments = Object.keys && (function () { // Safari 5.0 bug return Object.keys(arguments).length === 2; }(1, 2)); var originalKeys = Object.keys; defineProperties(Object, { keys: function keys(object) { if (isArguments(object)) { return originalKeys(ArrayPrototype.slice.call(object)); } else { return originalKeys(object); } } }, !keysWorksWithArguments); // // Date // ==== // // ES5 15.9.5.43 // http://es5.github.com/#x15.9.5.43 // This function returns a String value represent the instance in time // represented by this Date object. The format of the String is the Date Time // string format defined in 15.9.1.15. All fields are present in the String. // The time zone is always UTC, denoted by the suffix Z. If the time value of // this object is not a finite Number a RangeError exception is thrown. var negativeDate = -62198755200000; var negativeYearString = "-000001"; var hasNegativeDateBug = Date.prototype.toISOString && new Date(negativeDate).toISOString().indexOf(negativeYearString) === -1; defineProperties(Date.prototype, { toISOString: function toISOString() { var result, length, value, year, month; if (!isFinite(this)) { throw new RangeError("Date.prototype.toISOString called on non-finite value."); } year = this.getUTCFullYear(); month = this.getUTCMonth(); // see https://github.com/es-shims/es5-shim/issues/111 year += Math.floor(month / 12); month = (month % 12 + 12) % 12; // the date time string format is specified in 15.9.1.15. result = [month + 1, this.getUTCDate(), this.getUTCHours(), this.getUTCMinutes(), this.getUTCSeconds()]; year = ( (year < 0 ? "-" : (year > 9999 ? "+" : "")) + ("00000" + Math.abs(year)).slice(0 <= year && year <= 9999 ? -4 : -6) ); length = result.length; while (length--) { value = result[length]; // pad months, days, hours, minutes, and seconds to have two // digits. if (value < 10) { result[length] = "0" + value; } } // pad milliseconds to have three digits. return ( year + "-" + result.slice(0, 2).join("-") + "T" + result.slice(2).join(":") + "." + ("000" + this.getUTCMilliseconds()).slice(-3) + "Z" ); } }, hasNegativeDateBug); // ES5 15.9.5.44 // http://es5.github.com/#x15.9.5.44 // This function provides a String representation of a Date object for use by // JSON.stringify (15.12.3). var dateToJSONIsSupported = false; try { dateToJSONIsSupported = ( Date.prototype.toJSON && new Date(NaN).toJSON() === null && new Date(negativeDate).toJSON().indexOf(negativeYearString) !== -1 && Date.prototype.toJSON.call({ // generic toISOString: function () { return true; } }) ); } catch (e) { } if (!dateToJSONIsSupported) { Date.prototype.toJSON = function toJSON(key) { // When the toJSON method is called with argument key, the following // steps are taken: // 1. Let O be the result of calling ToObject, giving it the this // value as its argument. // 2. Let tv be toPrimitive(O, hint Number). var o = Object(this), tv = toPrimitive(o), toISO; // 3. If tv is a Number and is not finite, return null. if (typeof tv === "number" && !isFinite(tv)) { return null; } // 4. Let toISO be the result of calling the [[Get]] internal method of // O with argument "toISOString". toISO = o.toISOString; // 5. If IsCallable(toISO) is false, throw a TypeError exception. if (typeof toISO !== "function") { throw new TypeError("toISOString property is not callable"); } // 6. Return the result of calling the [[Call]] internal method of // toISO with O as the this value and an empty argument list. return toISO.call(o); // NOTE 1 The argument is ignored. // NOTE 2 The toJSON function is intentionally generic; it does not // require that its this value be a Date object. Therefore, it can be // transferred to other kinds of objects for use as a method. However, // it does require that any such object have a toISOString method. An // object is free to use the argument key to filter its // stringification. }; } // ES5 15.9.4.2 // http://es5.github.com/#x15.9.4.2 // based on work shared by Daniel Friesen (dantman) // http://gist.github.com/303249 var supportsExtendedYears = Date.parse('+033658-09-27T01:46:40.000Z') === 1e15; var acceptsInvalidDates = !isNaN(Date.parse('2012-04-04T24:00:00.500Z')) || !isNaN(Date.parse('2012-11-31T23:59:59.000Z')); var doesNotParseY2KNewYear = isNaN(Date.parse("2000-01-01T00:00:00.000Z")); if (!Date.parse || doesNotParseY2KNewYear || acceptsInvalidDates || !supportsExtendedYears) { // XXX global assignment won't work in embeddings that use // an alternate object for the context. Date = (function (NativeDate) { // Date.length === 7 function Date(Y, M, D, h, m, s, ms) { var length = arguments.length; if (this instanceof NativeDate) { var date = length === 1 && String(Y) === Y ? // isString(Y) // We explicitly pass it through parse: new NativeDate(Date.parse(Y)) : // We have to manually make calls depending on argument // length here length >= 7 ? new NativeDate(Y, M, D, h, m, s, ms) : length >= 6 ? new NativeDate(Y, M, D, h, m, s) : length >= 5 ? new NativeDate(Y, M, D, h, m) : length >= 4 ? new NativeDate(Y, M, D, h) : length >= 3 ? new NativeDate(Y, M, D) : length >= 2 ? new NativeDate(Y, M) : length >= 1 ? new NativeDate(Y) : new NativeDate(); // Prevent mixups with unfixed Date object date.constructor = Date; return date; } return NativeDate.apply(this, arguments); } // 15.9.1.15 Date Time String Format. var isoDateExpression = new RegExp("^" + "(\\d{4}|[\+\-]\\d{6})" + // four-digit year capture or sign + // 6-digit extended year "(?:-(\\d{2})" + // optional month capture "(?:-(\\d{2})" + // optional day capture "(?:" + // capture hours:minutes:seconds.milliseconds "T(\\d{2})" + // hours capture ":(\\d{2})" + // minutes capture "(?:" + // optional :seconds.milliseconds ":(\\d{2})" + // seconds capture "(?:(\\.\\d{1,}))?" + // milliseconds capture ")?" + "(" + // capture UTC offset component "Z|" + // UTC capture "(?:" + // offset specifier +/-hours:minutes "([-+])" + // sign capture "(\\d{2})" + // hours offset capture ":(\\d{2})" + // minutes offset capture ")" + ")?)?)?)?" + "$"); var months = [ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 ]; function dayFromMonth(year, month) { var t = month > 1 ? 1 : 0; return ( months[month] + Math.floor((year - 1969 + t) / 4) - Math.floor((year - 1901 + t) / 100) + Math.floor((year - 1601 + t) / 400) + 365 * (year - 1970) ); } function toUTC(t) { return Number(new NativeDate(1970, 0, 1, 0, 0, 0, t)); } // Copy any custom methods a 3rd party library may have added for (var key in NativeDate) { Date[key] = NativeDate[key]; } // Copy "native" methods explicitly; they may be non-enumerable Date.now = NativeDate.now; Date.UTC = NativeDate.UTC; Date.prototype = NativeDate.prototype; Date.prototype.constructor = Date; // Upgrade Date.parse to handle simplified ISO 8601 strings Date.parse = function parse(string) { var match = isoDateExpression.exec(string); if (match) { // parse months, days, hours, minutes, seconds, and milliseconds // provide default values if necessary // parse the UTC offset component var year = Number(match[1]), month = Number(match[2] || 1) - 1, day = Number(match[3] || 1) - 1, hour = Number(match[4] || 0), minute = Number(match[5] || 0), second = Number(match[6] || 0), millisecond = Math.floor(Number(match[7] || 0) * 1000), // When time zone is missed, local offset should be used // (ES 5.1 bug) // see https://bugs.ecmascript.org/show_bug.cgi?id=112 isLocalTime = Boolean(match[4] && !match[8]), signOffset = match[9] === "-" ? 1 : -1, hourOffset = Number(match[10] || 0), minuteOffset = Number(match[11] || 0), result; if ( hour < ( minute > 0 || second > 0 || millisecond > 0 ? 24 : 25 ) && minute < 60 && second < 60 && millisecond < 1000 && month > -1 && month < 12 && hourOffset < 24 && minuteOffset < 60 && // detect invalid offsets day > -1 && day < ( dayFromMonth(year, month + 1) - dayFromMonth(year, month) ) ) { result = ( (dayFromMonth(year, month) + day) * 24 + hour + hourOffset * signOffset ) * 60; result = ( (result + minute + minuteOffset * signOffset) * 60 + second ) * 1000 + millisecond; if (isLocalTime) { result = toUTC(result); } if (-8.64e15 <= result && result <= 8.64e15) { return result; } } return NaN; } return NativeDate.parse.apply(this, arguments); }; return Date; })(Date); } // ES5 15.9.4.4 // http://es5.github.com/#x15.9.4.4 if (!Date.now) { Date.now = function now() { return new Date().getTime(); }; } // // Number // ====== // // ES5.1 15.7.4.5 // http://es5.github.com/#x15.7.4.5 var hasToFixedBugs = NumberPrototype.toFixed && ( (0.00008).toFixed(3) !== '0.000' || (0.9).toFixed(0) !== '1' || (1.255).toFixed(2) !== '1.25' || (1000000000000000128).toFixed(0) !== "1000000000000000128" ); var toFixedHelpers = { base: 1e7, size: 6, data: [0, 0, 0, 0, 0, 0], multiply: function multiply(n, c) { var i = -1; while (++i < toFixedHelpers.size) { c += n * toFixedHelpers.data[i]; toFixedHelpers.data[i] = c % toFixedHelpers.base; c = Math.floor(c / toFixedHelpers.base); } }, divide: function divide(n) { var i = toFixedHelpers.size, c = 0; while (--i >= 0) { c += toFixedHelpers.data[i]; toFixedHelpers.data[i] = Math.floor(c / n); c = (c % n) * toFixedHelpers.base; } }, numToString: function numToString() { var i = toFixedHelpers.size; var s = ''; while (--i >= 0) { if (s !== '' || i === 0 || toFixedHelpers.data[i] !== 0) { var t = String(toFixedHelpers.data[i]); if (s === '') { s = t; } else { s += '0000000'.slice(0, 7 - t.length) + t; } } } return s; }, pow: function pow(x, n, acc) { return (n === 0 ? acc : (n % 2 === 1 ? pow(x, n - 1, acc * x) : pow(x * x, n / 2, acc))); }, log: function log(x) { var n = 0; while (x >= 4096) { n += 12; x /= 4096; } while (x >= 2) { n += 1; x /= 2; } return n; } }; defineProperties(NumberPrototype, { toFixed: function toFixed(fractionDigits) { var f, x, s, m, e, z, j, k; // Test for NaN and round fractionDigits down f = Number(fractionDigits); f = f !== f ? 0 : Math.floor(f); if (f < 0 || f > 20) { throw new RangeError("Number.toFixed called with invalid number of decimals"); } x = Number(this); // Test for NaN if (x !== x) { return "NaN"; } // If it is too big or small, return the string value of the number if (x <= -1e21 || x >= 1e21) { return String(x); } s = ""; if (x < 0) { s = "-"; x = -x; } m = "0"; if (x > 1e-21) { // 1e-21 < x < 1e21 // -70 < log2(x) < 70 e = toFixedHelpers.log(x * toFixedHelpers.pow(2, 69, 1)) - 69; z = (e < 0 ? x * toFixedHelpers.pow(2, -e, 1) : x / toFixedHelpers.pow(2, e, 1)); z *= 0x10000000000000; // Math.pow(2, 52); e = 52 - e; // -18 < e < 122 // x = z / 2 ^ e if (e > 0) { toFixedHelpers.multiply(0, z); j = f; while (j >= 7) { toFixedHelpers.multiply(1e7, 0); j -= 7; } toFixedHelpers.multiply(toFixedHelpers.pow(10, j, 1), 0); j = e - 1; while (j >= 23) { toFixedHelpers.divide(1 << 23); j -= 23; } toFixedHelpers.divide(1 << j); toFixedHelpers.multiply(1, 1); toFixedHelpers.divide(2); m = toFixedHelpers.numToString(); } else { toFixedHelpers.multiply(0, z); toFixedHelpers.multiply(1 << (-e), 0); m = toFixedHelpers.numToString() + '0.00000000000000000000'.slice(2, 2 + f); } } if (f > 0) { k = m.length; if (k <= f) { m = s + '0.0000000000000000000'.slice(0, f - k + 2) + m; } else { m = s + m.slice(0, k - f) + '.' + m.slice(k - f); } } else { m = s + m; } return m; } }, hasToFixedBugs); // // String // ====== // // ES5 15.5.4.14 // http://es5.github.com/#x15.5.4.14 // [bugfix, IE lt 9, firefox 4, Konqueror, Opera, obscure browsers] // Many browsers do not split properly with regular expressions or they // do not perform the split correctly under obscure conditions. // See http://blog.stevenlevithan.com/archives/cross-browser-split // I've tested in many browsers and this seems to cover the deviant ones: // 'ab'.split(/(?:ab)*/) should be ["", ""], not [""] // '.'.split(/(.?)(.?)/) should be ["", ".", "", ""], not ["", ""] // 'tesst'.split(/(s)*/) should be ["t", undefined, "e", "s", "t"], not // [undefined, "t", undefined, "e", ...] // ''.split(/.?/) should be [], not [""] // '.'.split(/()()/) should be ["."], not ["", "", "."] var string_split = StringPrototype.split; if ( 'ab'.split(/(?:ab)*/).length !== 2 || '.'.split(/(.?)(.?)/).length !== 4 || 'tesst'.split(/(s)*/)[1] === "t" || 'test'.split(/(?:)/, -1).length !== 4 || ''.split(/.?/).length || '.'.split(/()()/).length > 1 ) { (function () { var compliantExecNpcg = /()??/.exec("")[1] === void 0; // NPCG: nonparticipating capturing group StringPrototype.split = function (separator, limit) { var string = this; if (separator === void 0 && limit === 0) { return []; } // If `separator` is not a regex, use native split if (_toString.call(separator) !== "[object RegExp]") { return string_split.call(this, separator, limit); } var output = [], flags = (separator.ignoreCase ? "i" : "") + (separator.multiline ? "m" : "") + (separator.extended ? "x" : "") + // Proposed for ES6 (separator.sticky ? "y" : ""), // Firefox 3+ lastLastIndex = 0, // Make `global` and avoid `lastIndex` issues by working with a copy separator2, match, lastIndex, lastLength; separator = new RegExp(separator.source, flags + "g"); string += ""; // Type-convert if (!compliantExecNpcg) { // Doesn't need flags gy, but they don't hurt separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags); } /* Values for `limit`, per the spec: * If undefined: 4294967295 // Math.pow(2, 32) - 1 * If 0, Infinity, or NaN: 0 * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296; * If negative number: 4294967296 - Math.floor(Math.abs(limit)) * If other: Type-convert, then use the above rules */ limit = limit === void 0 ? -1 >>> 0 : // Math.pow(2, 32) - 1 ToUint32(limit); while (match = separator.exec(string)) { // `separator.lastIndex` is not reliable cross-browser lastIndex = match.index + match[0].length; if (lastIndex > lastLastIndex) { output.push(string.slice(lastLastIndex, match.index)); // Fix browsers whose `exec` methods don't consistently return `undefined` for // nonparticipating capturing groups if (!compliantExecNpcg && match.length > 1) { match[0].replace(separator2, function () { for (var i = 1; i < arguments.length - 2; i++) { if (arguments[i] === void 0) { match[i] = void 0; } } }); } if (match.length > 1 && match.index < string.length) { ArrayPrototype.push.apply(output, match.slice(1)); } lastLength = match[0].length; lastLastIndex = lastIndex; if (output.length >= limit) { break; } } if (separator.lastIndex === match.index) { separator.lastIndex++; // Avoid an infinite loop } } if (lastLastIndex === string.length) { if (lastLength || !separator.test("")) { output.push(""); } } else { output.push(string.slice(lastLastIndex)); } return output.length > limit ? output.slice(0, limit) : output; }; }()); // [bugfix, chrome] // If separator is undefined, then the result array contains just one String, // which is the this value (converted to a String). If limit is not undefined, // then the output array is truncated so that it contains no more than limit // elements. // "0".split(undefined, 0) -> [] } else if ("0".split(void 0, 0).length) { StringPrototype.split = function split(separator, limit) { if (separator === void 0 && limit === 0) { return []; } return string_split.call(this, separator, limit); }; } var str_replace = StringPrototype.replace; var replaceReportsGroupsCorrectly = (function () { var groups = []; 'x'.replace(/x(.)?/g, function (match, group) { groups.push(group); }); return groups.length === 1 && typeof groups[0] === 'undefined'; }()); if (!replaceReportsGroupsCorrectly) { StringPrototype.replace = function replace(searchValue, replaceValue) { var isFn = isFunction(replaceValue); var hasCapturingGroups = isRegex(searchValue) && (/\)[*?]/).test(searchValue.source); if (!isFn || !hasCapturingGroups) { return str_replace.call(this, searchValue, replaceValue); } else { var wrappedReplaceValue = function (match) { var length = arguments.length; var originalLastIndex = searchValue.lastIndex; searchValue.lastIndex = 0; var args = searchValue.exec(match); searchValue.lastIndex = originalLastIndex; args.push(arguments[length - 2], arguments[length - 1]); return replaceValue.apply(this, args); }; return str_replace.call(this, searchValue, wrappedReplaceValue); } }; } // ECMA-262, 3rd B.2.3 // Not an ECMAScript standard, although ECMAScript 3rd Edition has a // non-normative section suggesting uniform semantics and it should be // normalized across all browsers // [bugfix, IE lt 9] IE < 9 substr() with negative value not working in IE var string_substr = StringPrototype.substr; var hasNegativeSubstrBug = "".substr && "0b".substr(-1) !== "b"; defineProperties(StringPrototype, { substr: function substr(start, length) { return string_substr.call( this, start < 0 ? ((start = this.length + start) < 0 ? 0 : start) : start, length ); } }, hasNegativeSubstrBug); // ES5 15.5.4.20 // whitespace from: http://es5.github.io/#x15.5.4.20 var ws = "\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003" + "\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028" + "\u2029\uFEFF"; var zeroWidth = '\u200b'; var wsRegexChars = "[" + ws + "]"; var trimBeginRegexp = new RegExp("^" + wsRegexChars + wsRegexChars + "*"); var trimEndRegexp = new RegExp(wsRegexChars + wsRegexChars + "*$"); var hasTrimWhitespaceBug = StringPrototype.trim && (ws.trim() || !zeroWidth.trim()); defineProperties(StringPrototype, { // http://blog.stevenlevithan.com/archives/faster-trim-javascript // http://perfectionkills.com/whitespace-deviations/ trim: function trim() { if (this === void 0 || this === null) { throw new TypeError("can't convert " + this + " to object"); } return String(this).replace(trimBeginRegexp, "").replace(trimEndRegexp, ""); } }, hasTrimWhitespaceBug); // ES-5 15.1.2.2 if (parseInt(ws + '08') !== 8 || parseInt(ws + '0x16') !== 22) { parseInt = (function (origParseInt) { var hexRegex = /^0[xX]/; return function parseIntES5(str, radix) { str = String(str).trim(); if (!Number(radix)) { radix = hexRegex.test(str) ? 16 : 10; } return origParseInt(str, radix); }; }(parseInt)); } })); // Copyright 2006 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Known Issues: // // * Patterns only support repeat. // * Radial gradient are not implemented. The VML version of these look very // different from the canvas one. // * Clipping paths are not implemented. // * Coordsize. The width and height attribute have higher priority than the // width and height style values which isn't correct. // * Painting mode isn't implemented. // * Canvas width/height should is using content-box by default. IE in // Quirks mode will draw the canvas using border-box. Either change your // doctype to HTML5 // (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype) // or use Box Sizing Behavior from WebFX // (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html) // * Non uniform scaling does not correctly scale strokes. // * Optimize. There is always room for speed improvements. // Only add this code if we do not already have a canvas implementation if (!document.createElement('canvas').getContext) { (function() { // alias some functions to make (compiled) code shorter var m = Math; var mr = m.round; var ms = m.sin; var mc = m.cos; var abs = m.abs; var sqrt = m.sqrt; // this is used for sub pixel precision var Z = 10; var Z2 = Z / 2; var IE_VERSION = +navigator.userAgent.match(/MSIE ([\d.]+)?/)[1]; /** * This funtion is assigned to the elements as element.getContext(). * @this {HTMLElement} * @return {CanvasRenderingContext2D_} */ function getContext() { return this.context_ || (this.context_ = new CanvasRenderingContext2D_(this)); } var slice = Array.prototype.slice; /** * Binds a function to an object. The returned function will always use the * passed in {@code obj} as {@code this}. * * Example: * * g = bind(f, obj, a, b) * g(c, d) // will do f.call(obj, a, b, c, d) * * @param {Function} f The function to bind the object to * @param {Object} obj The object that should act as this when the function * is called * @param {*} var_args Rest arguments that will be used as the initial * arguments when the function is called * @return {Function} A new function that has bound this */ function bind(f, obj, var_args) { var a = slice.call(arguments, 2); return function() { return f.apply(obj, a.concat(slice.call(arguments))); }; } function encodeHtmlAttribute(s) { return String(s).replace(/&/g, '&').replace(/"/g, '"'); } function addNamespace(doc, prefix, urn) { if (!doc.namespaces[prefix]) { doc.namespaces.add(prefix, urn, '#default#VML'); } } function addNamespacesAndStylesheet(doc) { addNamespace(doc, 'g_vml_', 'urn:schemas-microsoft-com:vml'); addNamespace(doc, 'g_o_', 'urn:schemas-microsoft-com:office:office'); // Setup default CSS. Only add one style sheet per document if (!doc.styleSheets['ex_canvas_']) { var ss = doc.createStyleSheet(); ss.owningElement.id = 'ex_canvas_'; ss.cssText = 'canvas{display:inline-block;overflow:hidden;' + // default size is 300x150 in Gecko and Opera 'text-align:left;width:300px;height:150px}'; } } // Add namespaces and stylesheet at startup. addNamespacesAndStylesheet(document); var G_vmlCanvasManager_ = { init: function(opt_doc) { var doc = opt_doc || document; // Create a dummy element so that IE will allow canvas elements to be // recognized. doc.createElement('canvas'); doc.attachEvent('onreadystatechange', bind(this.init_, this, doc)); }, init_: function(doc) { // find all canvas elements var els = doc.getElementsByTagName('canvas'); for (var i = 0; i < els.length; i++) { this.initElement(els[i]); } }, /** * Public initializes a canvas element so that it can be used as canvas * element from now on. This is called automatically before the page is * loaded but if you are creating elements using createElement you need to * make sure this is called on the element. * @param {HTMLElement} el The canvas element to initialize. * @return {HTMLElement} the element that was created. */ initElement: function(el) { if (!el.getContext) { el.getContext = getContext; // Add namespaces and stylesheet to document of the element. addNamespacesAndStylesheet(el.ownerDocument); // Remove fallback content. There is no way to hide text nodes so we // just remove all childNodes. We could hide all elements and remove // text nodes but who really cares about the fallback content. el.innerHTML = ''; // do not use inline function because that will leak memory el.attachEvent('onpropertychange', onPropertyChange); el.attachEvent('onresize', onResize); var attrs = el.attributes; if (attrs.width && attrs.width.specified) { // TODO: use runtimeStyle and coordsize // el.getContext().setWidth_(attrs.width.nodeValue); el.style.width = attrs.width.nodeValue + 'px'; } else { el.width = el.clientWidth; } if (attrs.height && attrs.height.specified) { // TODO: use runtimeStyle and coordsize // el.getContext().setHeight_(attrs.height.nodeValue); el.style.height = attrs.height.nodeValue + 'px'; } else { el.height = el.clientHeight; } //el.getContext().setCoordsize_() } return el; } }; function onPropertyChange(e) { var el = e.srcElement; switch (e.propertyName) { case 'width': el.getContext().clearRect(); el.style.width = el.attributes.width.nodeValue + 'px'; // In IE8 this does not trigger onresize. el.firstChild.style.width = el.clientWidth + 'px'; break; case 'height': el.getContext().clearRect(); el.style.height = el.attributes.height.nodeValue + 'px'; el.firstChild.style.height = el.clientHeight + 'px'; break; } } function onResize(e) { var el = e.srcElement; if (el.firstChild) { el.firstChild.style.width = el.clientWidth + 'px'; el.firstChild.style.height = el.clientHeight + 'px'; } } G_vmlCanvasManager_.init(); // precompute "00" to "FF" var decToHex = []; for (var i = 0; i < 16; i++) { for (var j = 0; j < 16; j++) { decToHex[i * 16 + j] = i.toString(16) + j.toString(16); } } function createMatrixIdentity() { return [ [1, 0, 0], [0, 1, 0], [0, 0, 1] ]; } function matrixMultiply(m1, m2) { var result = createMatrixIdentity(); for (var x = 0; x < 3; x++) { for (var y = 0; y < 3; y++) { var sum = 0; for (var z = 0; z < 3; z++) { sum += m1[x][z] * m2[z][y]; } result[x][y] = sum; } } return result; } function copyState(o1, o2) { o2.fillStyle = o1.fillStyle; o2.lineCap = o1.lineCap; o2.lineJoin = o1.lineJoin; o2.lineWidth = o1.lineWidth; o2.miterLimit = o1.miterLimit; o2.shadowBlur = o1.shadowBlur; o2.shadowColor = o1.shadowColor; o2.shadowOffsetX = o1.shadowOffsetX; o2.shadowOffsetY = o1.shadowOffsetY; o2.strokeStyle = o1.strokeStyle; o2.globalAlpha = o1.globalAlpha; o2.font = o1.font; o2.textAlign = o1.textAlign; o2.textBaseline = o1.textBaseline; o2.arcScaleX_ = o1.arcScaleX_; o2.arcScaleY_ = o1.arcScaleY_; o2.lineScale_ = o1.lineScale_; } var colorData = { aliceblue: '#F0F8FF', antiquewhite: '#FAEBD7', aquamarine: '#7FFFD4', azure: '#F0FFFF', beige: '#F5F5DC', bisque: '#FFE4C4', black: '#000000', blanchedalmond: '#FFEBCD', 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', darkgrey: '#A9A9A9', darkkhaki: '#BDB76B', darkmagenta: '#8B008B', darkolivegreen: '#556B2F', darkorange: '#FF8C00', darkorchid: '#9932CC', darkred: '#8B0000', darksalmon: '#E9967A', darkseagreen: '#8FBC8F', darkslateblue: '#483D8B', darkslategray: '#2F4F4F', darkslategrey: '#2F4F4F', darkturquoise: '#00CED1', darkviolet: '#9400D3', deeppink: '#FF1493', deepskyblue: '#00BFFF', dimgray: '#696969', dimgrey: '#696969', dodgerblue: '#1E90FF', firebrick: '#B22222', floralwhite: '#FFFAF0', forestgreen: '#228B22', gainsboro: '#DCDCDC', ghostwhite: '#F8F8FF', gold: '#FFD700', goldenrod: '#DAA520', grey: '#808080', 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', lightgreen: '#90EE90', lightgrey: '#D3D3D3', lightpink: '#FFB6C1', lightsalmon: '#FFA07A', lightseagreen: '#20B2AA', lightskyblue: '#87CEFA', lightslategray: '#778899', lightslategrey: '#778899', lightsteelblue: '#B0C4DE', lightyellow: '#FFFFE0', limegreen: '#32CD32', linen: '#FAF0E6', magenta: '#FF00FF', 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', oldlace: '#FDF5E6', 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', rosybrown: '#BC8F8F', royalblue: '#4169E1', saddlebrown: '#8B4513', salmon: '#FA8072', sandybrown: '#F4A460', seagreen: '#2E8B57', seashell: '#FFF5EE', sienna: '#A0522D', skyblue: '#87CEEB', slateblue: '#6A5ACD', slategray: '#708090', slategrey: '#708090', snow: '#FFFAFA', springgreen: '#00FF7F', steelblue: '#4682B4', tan: '#D2B48C', thistle: '#D8BFD8', tomato: '#FF6347', turquoise: '#40E0D0', violet: '#EE82EE', wheat: '#F5DEB3', whitesmoke: '#F5F5F5', yellowgreen: '#9ACD32' }; function getRgbHslContent(styleString) { var start = styleString.indexOf('(', 3); var end = styleString.indexOf(')', start + 1); var parts = styleString.substring(start + 1, end).split(','); // add alpha if needed if (parts.length != 4 || styleString.charAt(3) != 'a') { parts[3] = 1; } return parts; } function percent(s) { return parseFloat(s) / 100; } function clamp(v, min, max) { return Math.min(max, Math.max(min, v)); } function hslToRgb(parts){ var r, g, b, h, s, l; h = parseFloat(parts[0]) / 360 % 360; if (h < 0) h++; s = clamp(percent(parts[1]), 0, 1); l = clamp(percent(parts[2]), 0, 1); if (s == 0) { r = g = b = l; // achromatic } else { var q = l < 0.5 ? l * (1 + s) : l + s - l * s; var p = 2 * l - q; r = hueToRgb(p, q, h + 1 / 3); g = hueToRgb(p, q, h); b = hueToRgb(p, q, h - 1 / 3); } return '#' + decToHex[Math.floor(r * 255)] + decToHex[Math.floor(g * 255)] + decToHex[Math.floor(b * 255)]; } function hueToRgb(m1, m2, h) { if (h < 0) h++; if (h > 1) h--; if (6 * h < 1) return m1 + (m2 - m1) * 6 * h; else if (2 * h < 1) return m2; else if (3 * h < 2) return m1 + (m2 - m1) * (2 / 3 - h) * 6; else return m1; } var processStyleCache = {}; function processStyle(styleString) { if (styleString in processStyleCache) { return processStyleCache[styleString]; } var str, alpha = 1; styleString = String(styleString); if (styleString.charAt(0) == '#') { str = styleString; } else if (/^rgb/.test(styleString)) { var parts = getRgbHslContent(styleString); var str = '#', n; for (var i = 0; i < 3; i++) { if (parts[i].indexOf('%') != -1) { n = Math.floor(percent(parts[i]) * 255); } else { n = +parts[i]; } str += decToHex[clamp(n, 0, 255)]; } alpha = +parts[3]; } else if (/^hsl/.test(styleString)) { var parts = getRgbHslContent(styleString); str = hslToRgb(parts); alpha = parts[3]; } else { str = colorData[styleString] || styleString; } return processStyleCache[styleString] = {color: str, alpha: alpha}; } var DEFAULT_STYLE = { style: 'normal', variant: 'normal', weight: 'normal', size: 10, family: 'sans-serif' }; // Internal text style cache var fontStyleCache = {}; function processFontStyle(styleString) { if (fontStyleCache[styleString]) { return fontStyleCache[styleString]; } var el = document.createElement('div'); var style = el.style; try { style.font = styleString; } catch (ex) { // Ignore failures to set to invalid font. } return fontStyleCache[styleString] = { style: style.fontStyle || DEFAULT_STYLE.style, variant: style.fontVariant || DEFAULT_STYLE.variant, weight: style.fontWeight || DEFAULT_STYLE.weight, size: style.fontSize || DEFAULT_STYLE.size, family: style.fontFamily || DEFAULT_STYLE.family }; } function getComputedStyle(style, element) { var computedStyle = {}; for (var p in style) { computedStyle[p] = style[p]; } // Compute the size var canvasFontSize = parseFloat(element.currentStyle.fontSize), fontSize = parseFloat(style.size); if (typeof style.size == 'number') { computedStyle.size = style.size; } else if (style.size.indexOf('px') != -1) { computedStyle.size = fontSize; } else if (style.size.indexOf('em') != -1) { computedStyle.size = canvasFontSize * fontSize; } else if(style.size.indexOf('%') != -1) { computedStyle.size = (canvasFontSize / 100) * fontSize; } else if (style.size.indexOf('pt') != -1) { computedStyle.size = fontSize / .75; } else { computedStyle.size = canvasFontSize; } // Different scaling between normal text and VML text. This was found using // trial and error to get the same size as non VML text. computedStyle.size *= 0.981; return computedStyle; } function buildStyle(style) { return style.style + ' ' + style.variant + ' ' + style.weight + ' ' + style.size + 'px ' + style.family; } var lineCapMap = { 'butt': 'flat', 'round': 'round' }; function processLineCap(lineCap) { return lineCapMap[lineCap] || 'square'; } /** * This class implements CanvasRenderingContext2D interface as described by * the WHATWG. * @param {HTMLElement} canvasElement The element that the 2D context should * be associated with */ function CanvasRenderingContext2D_(canvasElement) { this.m_ = createMatrixIdentity(); this.mStack_ = []; this.aStack_ = []; this.currentPath_ = []; // Canvas context properties this.strokeStyle = '#000'; this.fillStyle = '#000'; this.lineWidth = 1; this.lineJoin = 'miter'; this.lineCap = 'butt'; this.miterLimit = Z * 1; this.globalAlpha = 1; this.font = '10px sans-serif'; this.textAlign = 'left'; this.textBaseline = 'alphabetic'; this.canvas = canvasElement; var cssText = 'width:' + canvasElement.clientWidth + 'px;height:' + canvasElement.clientHeight + 'px;overflow:hidden;position:absolute'; var el = canvasElement.ownerDocument.createElement('div'); el.style.cssText = cssText; canvasElement.appendChild(el); var overlayEl = el.cloneNode(false); // Use a non transparent background. overlayEl.style.backgroundColor = 'red'; overlayEl.style.filter = 'alpha(opacity=0)'; canvasElement.appendChild(overlayEl); this.element_ = el; this.arcScaleX_ = 1; this.arcScaleY_ = 1; this.lineScale_ = 1; } var contextPrototype = CanvasRenderingContext2D_.prototype; contextPrototype.clearRect = function() { if (this.textMeasureEl_) { this.textMeasureEl_.removeNode(true); this.textMeasureEl_ = null; } this.element_.innerHTML = ''; }; contextPrototype.beginPath = function() { // TODO: Branch current matrix so that save/restore has no effect // as per safari docs. this.currentPath_ = []; }; contextPrototype.moveTo = function(aX, aY) { var p = getCoords(this, aX, aY); this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y}); this.currentX_ = p.x; this.currentY_ = p.y; }; contextPrototype.lineTo = function(aX, aY) { var p = getCoords(this, aX, aY); this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y}); this.currentX_ = p.x; this.currentY_ = p.y; }; contextPrototype.bezierCurveTo = function(aCP1x, aCP1y, aCP2x, aCP2y, aX, aY) { var p = getCoords(this, aX, aY); var cp1 = getCoords(this, aCP1x, aCP1y); var cp2 = getCoords(this, aCP2x, aCP2y); bezierCurveTo(this, cp1, cp2, p); }; // Helper function that takes the already fixed cordinates. function bezierCurveTo(self, cp1, cp2, p) { self.currentPath_.push({ type: 'bezierCurveTo', cp1x: cp1.x, cp1y: cp1.y, cp2x: cp2.x, cp2y: cp2.y, x: p.x, y: p.y }); self.currentX_ = p.x; self.currentY_ = p.y; } contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) { // the following is lifted almost directly from // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes var cp = getCoords(this, aCPx, aCPy); var p = getCoords(this, aX, aY); var cp1 = { x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_), y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_) }; var cp2 = { x: cp1.x + (p.x - this.currentX_) / 3.0, y: cp1.y + (p.y - this.currentY_) / 3.0 }; bezierCurveTo(this, cp1, cp2, p); }; contextPrototype.arc = function(aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise) { aRadius *= Z; var arcType = aClockwise ? 'at' : 'wa'; var xStart = aX + mc(aStartAngle) * aRadius - Z2; var yStart = aY + ms(aStartAngle) * aRadius - Z2; var xEnd = aX + mc(aEndAngle) * aRadius - Z2; var yEnd = aY + ms(aEndAngle) * aRadius - Z2; // IE won't render arches drawn counter clockwise if xStart == xEnd. if (xStart == xEnd && !aClockwise) { xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something // that can be represented in binary } var p = getCoords(this, aX, aY); var pStart = getCoords(this, xStart, yStart); var pEnd = getCoords(this, xEnd, yEnd); this.currentPath_.push({type: arcType, x: p.x, y: p.y, radius: aRadius, xStart: pStart.x, yStart: pStart.y, xEnd: pEnd.x, yEnd: pEnd.y}); }; contextPrototype.rect = function(aX, aY, aWidth, aHeight) { this.moveTo(aX, aY); this.lineTo(aX + aWidth, aY); this.lineTo(aX + aWidth, aY + aHeight); this.lineTo(aX, aY + aHeight); this.closePath(); }; contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) { var oldPath = this.currentPath_; this.beginPath(); this.moveTo(aX, aY); this.lineTo(aX + aWidth, aY); this.lineTo(aX + aWidth, aY + aHeight); this.lineTo(aX, aY + aHeight); this.closePath(); this.stroke(); this.currentPath_ = oldPath; }; contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) { var oldPath = this.currentPath_; this.beginPath(); this.moveTo(aX, aY); this.lineTo(aX + aWidth, aY); this.lineTo(aX + aWidth, aY + aHeight); this.lineTo(aX, aY + aHeight); this.closePath(); this.fill(); this.currentPath_ = oldPath; }; contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) { var gradient = new CanvasGradient_('gradient'); gradient.x0_ = aX0; gradient.y0_ = aY0; gradient.x1_ = aX1; gradient.y1_ = aY1; return gradient; }; contextPrototype.createRadialGradient = function(aX0, aY0, aR0, aX1, aY1, aR1) { var gradient = new CanvasGradient_('gradientradial'); gradient.x0_ = aX0; gradient.y0_ = aY0; gradient.r0_ = aR0; gradient.x1_ = aX1; gradient.y1_ = aY1; gradient.r1_ = aR1; return gradient; }; contextPrototype.drawImage = function(image, var_args) { if (image.getContext) { // draw canvas this.element_.innerHTML += image.getContext("2d").element_.innerHTML; } var dx, dy, dw, dh, sx, sy, sw, sh; // to find the original width we overide the width and height var oldRuntimeWidth = image.runtimeStyle.width; var oldRuntimeHeight = image.runtimeStyle.height; image.runtimeStyle.width = 'auto'; image.runtimeStyle.height = 'auto'; // get the original size var w = image.width; var h = image.height; // and remove overides image.runtimeStyle.width = oldRuntimeWidth; image.runtimeStyle.height = oldRuntimeHeight; if (arguments.length == 3) { dx = arguments[1]; dy = arguments[2]; sx = sy = 0; sw = dw = w; sh = dh = h; } else if (arguments.length == 5) { dx = arguments[1]; dy = arguments[2]; dw = arguments[3]; dh = arguments[4]; sx = sy = 0; sw = w; sh = h; } else if (arguments.length == 9) { sx = arguments[1]; sy = arguments[2]; sw = arguments[3]; sh = arguments[4]; dx = arguments[5]; dy = arguments[6]; dw = arguments[7]; dh = arguments[8]; } else { throw Error('Invalid number of arguments'); } var d = getCoords(this, dx, dy); var w2 = sw / 2; var h2 = sh / 2; var vmlStr = []; var W = 10; var H = 10; // For some reason that I've now forgotten, using divs didn't work vmlStr.push(' ' , '', ''); this.element_.insertAdjacentHTML('BeforeEnd', vmlStr.join('')); }; contextPrototype.stroke = function(aFill) { var lineStr = []; var lineOpen = false; var W = 10; var H = 10; lineStr.push(''); if (!aFill) { appendStroke(this, lineStr); } else { appendFill(this, lineStr, min, max); } lineStr.push(''); this.element_.insertAdjacentHTML('beforeEnd', lineStr.join('')); }; function appendStroke(ctx, lineStr) { var a = processStyle(ctx.strokeStyle); var color = a.color; var opacity = a.alpha * ctx.globalAlpha; var lineWidth = ctx.lineScale_ * ctx.lineWidth; // VML cannot correctly render a line if the width is less than 1px. // In that case, we dilute the color to make the line look thinner. if (lineWidth < 1) { opacity *= lineWidth; } lineStr.push( '' ); } function appendFill(ctx, lineStr, min, max) { var fillStyle = ctx.fillStyle; var arcScaleX = ctx.arcScaleX_; var arcScaleY = ctx.arcScaleY_; var width = max.x - min.x; var height = max.y - min.y; if (fillStyle instanceof CanvasGradient_) { // TODO: Gradients transformed with the transformation matrix. var angle = 0; var focus = {x: 0, y: 0}; // additional offset var shift = 0; // scale factor for offset var expansion = 1; if (fillStyle.type_ == 'gradient') { var x0 = fillStyle.x0_ / arcScaleX; var y0 = fillStyle.y0_ / arcScaleY; var x1 = fillStyle.x1_ / arcScaleX; var y1 = fillStyle.y1_ / arcScaleY; var p0 = getCoords(ctx, x0, y0); var p1 = getCoords(ctx, x1, y1); var dx = p1.x - p0.x; var dy = p1.y - p0.y; angle = Math.atan2(dx, dy) * 180 / Math.PI; // The angle should be a non-negative number. if (angle < 0) { angle += 360; } // Very small angles produce an unexpected result because they are // converted to a scientific notation string. if (angle < 1e-6) { angle = 0; } } else { var p0 = getCoords(ctx, fillStyle.x0_, fillStyle.y0_); focus = { x: (p0.x - min.x) / width, y: (p0.y - min.y) / height }; width /= arcScaleX * Z; height /= arcScaleY * Z; var dimension = m.max(width, height); shift = 2 * fillStyle.r0_ / dimension; expansion = 2 * fillStyle.r1_ / dimension - shift; } // We need to sort the color stops in ascending order by offset, // otherwise IE won't interpret it correctly. var stops = fillStyle.colors_; stops.sort(function(cs1, cs2) { return cs1.offset - cs2.offset; }); var length = stops.length; var color1 = stops[0].color; var color2 = stops[length - 1].color; var opacity1 = stops[0].alpha * ctx.globalAlpha; var opacity2 = stops[length - 1].alpha * ctx.globalAlpha; var colors = []; for (var i = 0; i < length; i++) { var stop = stops[i]; colors.push(stop.offset * expansion + shift + ' ' + stop.color); } // When colors attribute is used, the meanings of opacity and o:opacity2 // are reversed. lineStr.push(''); } else if (fillStyle instanceof CanvasPattern_) { if (width && height) { var deltaLeft = -min.x; var deltaTop = -min.y; lineStr.push(''); } } else { var a = processStyle(ctx.fillStyle); var color = a.color; var opacity = a.alpha * ctx.globalAlpha; lineStr.push(''); } } contextPrototype.fill = function() { this.stroke(true); }; contextPrototype.closePath = function() { this.currentPath_.push({type: 'close'}); }; function getCoords(ctx, aX, aY) { var m = ctx.m_; return { x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2, y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2 }; }; contextPrototype.save = function() { var o = {}; copyState(this, o); this.aStack_.push(o); this.mStack_.push(this.m_); this.m_ = matrixMultiply(createMatrixIdentity(), this.m_); }; contextPrototype.restore = function() { if (this.aStack_.length) { copyState(this.aStack_.pop(), this); this.m_ = this.mStack_.pop(); } }; function matrixIsFinite(m) { return isFinite(m[0][0]) && isFinite(m[0][1]) && isFinite(m[1][0]) && isFinite(m[1][1]) && isFinite(m[2][0]) && isFinite(m[2][1]); } function setM(ctx, m, updateLineScale) { if (!matrixIsFinite(m)) { return; } ctx.m_ = m; if (updateLineScale) { // Get the line scale. // Determinant of this.m_ means how much the area is enlarged by the // transformation. So its square root can be used as a scale factor // for width. var det = m[0][0] * m[1][1] - m[0][1] * m[1][0]; ctx.lineScale_ = sqrt(abs(det)); } } contextPrototype.translate = function(aX, aY) { var m1 = [ [1, 0, 0], [0, 1, 0], [aX, aY, 1] ]; setM(this, matrixMultiply(m1, this.m_), false); }; contextPrototype.rotate = function(aRot) { var c = mc(aRot); var s = ms(aRot); var m1 = [ [c, s, 0], [-s, c, 0], [0, 0, 1] ]; setM(this, matrixMultiply(m1, this.m_), false); }; contextPrototype.scale = function(aX, aY) { this.arcScaleX_ *= aX; this.arcScaleY_ *= aY; var m1 = [ [aX, 0, 0], [0, aY, 0], [0, 0, 1] ]; setM(this, matrixMultiply(m1, this.m_), true); }; contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) { var m1 = [ [m11, m12, 0], [m21, m22, 0], [dx, dy, 1] ]; setM(this, matrixMultiply(m1, this.m_), true); }; contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) { var m = [ [m11, m12, 0], [m21, m22, 0], [dx, dy, 1] ]; setM(this, m, true); }; /** * The text drawing function. * The maxWidth argument isn't taken in account, since no browser supports * it yet. */ contextPrototype.drawText_ = function(text, x, y, maxWidth, stroke) { var m = this.m_, delta = 1000, left = 0, right = delta, offset = {x: 0, y: 0}, lineStr = []; var fontStyle = getComputedStyle(processFontStyle(this.font), this.element_); var fontStyleString = buildStyle(fontStyle); var elementStyle = this.element_.currentStyle; var textAlign = this.textAlign.toLowerCase(); switch (textAlign) { case 'left': case 'center': case 'right': break; case 'end': textAlign = elementStyle.direction == 'ltr' ? 'right' : 'left'; break; case 'start': textAlign = elementStyle.direction == 'rtl' ? 'right' : 'left'; break; default: textAlign = 'left'; } // 1.75 is an arbitrary number, as there is no info about the text baseline switch (this.textBaseline) { case 'hanging': case 'top': offset.y = fontStyle.size / 1.75; break; case 'middle': break; default: case null: case 'alphabetic': case 'ideographic': case 'bottom': offset.y = -fontStyle.size / 2.25; break; } switch(textAlign) { case 'right': left = delta; right = 0.05; break; case 'center': left = right = delta / 2; break; } var d = getCoords(this, x + offset.x, y + offset.y); lineStr.push(''); if (stroke) { appendStroke(this, lineStr); } else { // TODO: Fix the min and max params. appendFill(this, lineStr, {x: -left, y: 0}, {x: right, y: fontStyle.size}); } var skewM = m[0][0].toFixed(3) + ',' + m[1][0].toFixed(3) + ',' + m[0][1].toFixed(3) + ',' + m[1][1].toFixed(3) + ',0,0'; var skewOffset = mr(d.x / Z) + ',' + mr(d.y / Z); lineStr.push('', '', ''); this.element_.insertAdjacentHTML('beforeEnd', lineStr.join('')); }; contextPrototype.fillText = function(text, x, y, maxWidth) { this.drawText_(text, x, y, maxWidth, false); }; contextPrototype.strokeText = function(text, x, y, maxWidth) { this.drawText_(text, x, y, maxWidth, true); }; contextPrototype.measureText = function(text) { if (!this.textMeasureEl_) { var s = ''; this.element_.insertAdjacentHTML('beforeEnd', s); this.textMeasureEl_ = this.element_.lastChild; } var doc = this.element_.ownerDocument; this.textMeasureEl_.innerHTML = ''; this.textMeasureEl_.style.font = this.font; // Don't use innerHTML or innerText because they allow markup/whitespace. this.textMeasureEl_.appendChild(doc.createTextNode(text)); return {width: this.textMeasureEl_.offsetWidth}; }; /******** STUBS ********/ contextPrototype.clip = function() { // TODO: Implement }; contextPrototype.arcTo = function() { // TODO: Implement }; contextPrototype.createPattern = function(image, repetition) { return new CanvasPattern_(image, repetition); }; // Gradient / Pattern Stubs function CanvasGradient_(aType) { this.type_ = aType; this.x0_ = 0; this.y0_ = 0; this.r0_ = 0; this.x1_ = 0; this.y1_ = 0; this.r1_ = 0; this.colors_ = []; } CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) { aColor = processStyle(aColor); this.colors_.push({offset: aOffset, color: aColor.color, alpha: aColor.alpha}); }; function CanvasPattern_(image, repetition) { assertImageIsValid(image); switch (repetition) { case 'repeat': case null: case '': this.repetition_ = 'repeat'; break case 'repeat-x': case 'repeat-y': case 'no-repeat': this.repetition_ = repetition; break; default: throwException('SYNTAX_ERR'); } this.src_ = image.src; this.width_ = image.width; this.height_ = image.height; } function throwException(s) { throw new DOMException_(s); } function assertImageIsValid(img) { if (!img || img.nodeType != 1 || img.tagName != 'IMG') { throwException('TYPE_MISMATCH_ERR'); } if (img.readyState != 'complete') { throwException('INVALID_STATE_ERR'); } } function DOMException_(s) { this.code = this[s]; this.message = s +': DOM Exception ' + this.code; } var p = DOMException_.prototype = new Error; p.INDEX_SIZE_ERR = 1; p.DOMSTRING_SIZE_ERR = 2; p.HIERARCHY_REQUEST_ERR = 3; p.WRONG_DOCUMENT_ERR = 4; p.INVALID_CHARACTER_ERR = 5; p.NO_DATA_ALLOWED_ERR = 6; p.NO_MODIFICATION_ALLOWED_ERR = 7; p.NOT_FOUND_ERR = 8; p.NOT_SUPPORTED_ERR = 9; p.INUSE_ATTRIBUTE_ERR = 10; p.INVALID_STATE_ERR = 11; p.SYNTAX_ERR = 12; p.INVALID_MODIFICATION_ERR = 13; p.NAMESPACE_ERR = 14; p.INVALID_ACCESS_ERR = 15; p.VALIDATION_ERR = 16; p.TYPE_MISMATCH_ERR = 17; // set up externs G_vmlCanvasManager = G_vmlCanvasManager_; CanvasRenderingContext2D = CanvasRenderingContext2D_; CanvasGradient = CanvasGradient_; CanvasPattern = CanvasPattern_; DOMException = DOMException_; })(); } // if ; /*! Holder - 2.3.2 - client side image placeholders (c) 2012-2014 Ivan Malopinsky / http://imsky.co Provided under the MIT License. Commercial use requires attribution. */ var Holder = Holder || {}; (function (app, win) { var system_config = { use_svg: false, use_canvas: false, use_fallback: false }; var instance_config = {}; var preempted = false; canvas = document.createElement('canvas'); var dpr = 1, bsr = 1; var resizable_images = []; if (!canvas.getContext) { system_config.use_fallback = true; } else { if (canvas.toDataURL("image/png") .indexOf("data:image/png") < 0) { //Android doesn't support data URI system_config.use_fallback = true; } else { var ctx = canvas.getContext("2d"); } } if(!!document.createElementNS && !!document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGRect){ system_config.use_svg = true; system_config.use_canvas = false; } if(!system_config.use_fallback){ dpr = window.devicePixelRatio || 1, bsr = ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1; } var ratio = dpr / bsr; var settings = { domain: "holder.js", images: "img", bgnodes: ".holderjs", themes: { "gray": { background: "#eee", foreground: "#aaa", size: 12 }, "social": { background: "#3a5a97", foreground: "#fff", size: 12 }, "industrial": { background: "#434A52", foreground: "#C2F200", size: 12 }, "sky": { background: "#0D8FDB", foreground: "#fff", size: 12 }, "vine": { background: "#39DBAC", foreground: "#1E292C", size: 12 }, "lava": { background: "#F8591A", foreground: "#1C2846", size: 12 } }, stylesheet: "" }; app.flags = { dimensions: { regex: /^(\d+)x(\d+)$/, output: function (val) { var exec = this.regex.exec(val); return { width: +exec[1], height: +exec[2] } } }, fluid: { regex: /^([0-9%]+)x([0-9%]+)$/, output: function (val) { var exec = this.regex.exec(val); return { width: exec[1], height: exec[2] } } }, colors: { regex: /#([0-9a-f]{3,})\:#([0-9a-f]{3,})/i, output: function (val) { var exec = this.regex.exec(val); return { size: settings.themes.gray.size, foreground: "#" + exec[2], background: "#" + exec[1] } } }, text: { regex: /text\:(.*)/, output: function (val) { return this.regex.exec(val)[1]; } }, font: { regex: /font\:(.*)/, output: function (val) { return this.regex.exec(val)[1]; } }, auto: { regex: /^auto$/ }, textmode: { regex: /textmode\:(.*)/, output: function(val){ return this.regex.exec(val)[1]; } } } function text_size(width, height, template) { height = parseInt(height, 10); width = parseInt(width, 10); var bigSide = Math.max(height, width) var smallSide = Math.min(height, width) var scale = 1 / 12; var newHeight = Math.min(smallSide * 0.75, 0.75 * bigSide * scale); return { height: Math.round(Math.max(template.size, newHeight)) } } var svg_el = (function(){ //Prevent IE <9 from initializing SVG renderer if(!window.XMLSerializer) return; var serializer = new XMLSerializer(); var svg_ns = "http://www.w3.org/2000/svg" var svg = document.createElementNS(svg_ns, "svg"); //IE throws an exception if this is set and Chrome requires it to be set if(svg.webkitMatchesSelector){ svg.setAttribute("xmlns", "http://www.w3.org/2000/svg") } var bg_el = document.createElementNS(svg_ns, "rect") var text_el = document.createElementNS(svg_ns, "text") var textnode_el = document.createTextNode(null) text_el.setAttribute("text-anchor", "middle") text_el.appendChild(textnode_el) svg.appendChild(bg_el) svg.appendChild(text_el) return function(props){ svg.setAttribute("width",props.width); svg.setAttribute("height", props.height); bg_el.setAttribute("width", props.width); bg_el.setAttribute("height", props.height); bg_el.setAttribute("fill", props.template.background); text_el.setAttribute("x", props.width/2) text_el.setAttribute("y", props.height/2) textnode_el.nodeValue=props.text text_el.setAttribute("style", css_properties({ "fill": props.template.foreground, "font-weight": "bold", "font-size": props.text_height+"px", "font-family":props.font, "dominant-baseline":"central" })) return serializer.serializeToString(svg) } })() function css_properties(props){ var ret = []; for(p in props){ if(props.hasOwnProperty(p)){ ret.push(p+":"+props[p]) } } return ret.join(";") } function draw_canvas(args) { var ctx = args.ctx, dimensions = args.dimensions, template = args.template, ratio = args.ratio, holder = args.holder, literal = holder.textmode == "literal", exact = holder.textmode == "exact"; var ts = text_size(dimensions.width, dimensions.height, template); var text_height = ts.height; var width = dimensions.width * ratio, height = dimensions.height * ratio; var font = template.font ? template.font : "Arial,Helvetica,sans-serif"; canvas.width = width; canvas.height = height; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillStyle = template.background; ctx.fillRect(0, 0, width, height); ctx.fillStyle = template.foreground; ctx.font = "bold " + text_height + "px " + font; var text = template.text ? template.text : (Math.floor(dimensions.width) + "x" + Math.floor(dimensions.height)); if (literal) { var dimensions = holder.dimensions; text = dimensions.width + "x" + dimensions.height; } else if(exact && holder.exact_dimensions){ var dimensions = holder.exact_dimensions; text = (Math.floor(dimensions.width) + "x" + Math.floor(dimensions.height)); } var text_width = ctx.measureText(text).width; if (text_width / width >= 0.75) { text_height = Math.floor(text_height * 0.75 * (width / text_width)); } //Resetting font size if necessary ctx.font = "bold " + (text_height * ratio) + "px " + font; ctx.fillText(text, (width / 2), (height / 2), width); return canvas.toDataURL("image/png"); } function draw_svg(args){ var dimensions = args.dimensions, template = args.template, holder = args.holder, literal = holder.textmode == "literal", exact = holder.textmode == "exact"; var ts = text_size(dimensions.width, dimensions.height, template); var text_height = ts.height; var width = dimensions.width, height = dimensions.height; var font = template.font ? template.font : "Arial,Helvetica,sans-serif"; var text = template.text ? template.text : (Math.floor(dimensions.width) + "x" + Math.floor(dimensions.height)); if (literal) { var dimensions = holder.dimensions; text = dimensions.width + "x" + dimensions.height; } else if(exact && holder.exact_dimensions){ var dimensions = holder.exact_dimensions; text = (Math.floor(dimensions.width) + "x" + Math.floor(dimensions.height)); } var string = svg_el({ text: text, width:width, height:height, text_height:text_height, font:font, template:template }) return "data:image/svg+xml;base64,"+btoa(unescape(encodeURIComponent(string))); } function draw(args) { if(instance_config.use_canvas && !instance_config.use_svg){ return draw_canvas(args); } else{ return draw_svg(args); } } function render(mode, el, holder, src) { var dimensions = holder.dimensions, theme = holder.theme, text = holder.text ? decodeURIComponent(holder.text) : holder.text; var dimensions_caption = dimensions.width + "x" + dimensions.height; theme = (text ? extend(theme, { text: text }) : theme); theme = (holder.font ? extend(theme, { font: holder.font }) : theme); el.setAttribute("data-src", src); holder.theme = theme; el.holder_data = holder; if (mode == "image") { el.setAttribute("alt", text ? text : theme.text ? theme.text + " [" + dimensions_caption + "]" : dimensions_caption); if (instance_config.use_fallback || !holder.auto) { el.style.width = dimensions.width + "px"; el.style.height = dimensions.height + "px"; } if (instance_config.use_fallback) { el.style.backgroundColor = theme.background; } else { el.setAttribute("src", draw({ctx: ctx, dimensions: dimensions, template: theme, ratio:ratio, holder: holder})); if(holder.textmode && holder.textmode == "exact"){ resizable_images.push(el); resizable_update(el); } } } else if (mode == "background") { if (!instance_config.use_fallback) { el.style.backgroundImage = "url(" + draw({ctx:ctx, dimensions: dimensions, template: theme, ratio: ratio, holder: holder}) + ")"; el.style.backgroundSize = dimensions.width + "px " + dimensions.height + "px"; } } else if (mode == "fluid") { el.setAttribute("alt", text ? text : theme.text ? theme.text + " [" + dimensions_caption + "]" : dimensions_caption); if (dimensions.height.slice(-1) == "%") { el.style.height = dimensions.height } else if(holder.auto == null || !holder.auto){ el.style.height = dimensions.height + "px" } if (dimensions.width.slice(-1) == "%") { el.style.width = dimensions.width } else if(holder.auto == null || !holder.auto){ el.style.width = dimensions.width + "px" } if (el.style.display == "inline" || el.style.display === "" || el.style.display == "none") { el.style.display = "block"; } set_initial_dimensions(el) if (instance_config.use_fallback) { el.style.backgroundColor = theme.background; } else { resizable_images.push(el); resizable_update(el); } } } function dimension_check(el, callback) { var dimensions = { height: el.clientHeight, width: el.clientWidth }; if (!dimensions.height && !dimensions.width) { el.setAttribute("data-holder-invisible", true) callback.call(this, el) } else{ el.removeAttribute("data-holder-invisible") return dimensions; } } function set_initial_dimensions(el){ if(el.holder_data){ var dimensions = dimension_check(el, app.invisible_error_fn( set_initial_dimensions)) if(dimensions){ var holder = el.holder_data; holder.initial_dimensions = dimensions; holder.fluid_data = { fluid_height: holder.dimensions.height.slice(-1) == "%", fluid_width: holder.dimensions.width.slice(-1) == "%", mode: null } if(holder.fluid_data.fluid_width && !holder.fluid_data.fluid_height){ holder.fluid_data.mode = "width" holder.fluid_data.ratio = holder.initial_dimensions.width / parseFloat(holder.dimensions.height) } else if(!holder.fluid_data.fluid_width && holder.fluid_data.fluid_height){ holder.fluid_data.mode = "height"; holder.fluid_data.ratio = parseFloat(holder.dimensions.width) / holder.initial_dimensions.height } } } } function resizable_update(element) { var images; if (element.nodeType == null) { images = resizable_images; } else { images = [element] } for (var i in images) { if (!images.hasOwnProperty(i)) { continue; } var el = images[i] if (el.holder_data) { var holder = el.holder_data; var dimensions = dimension_check(el, app.invisible_error_fn( resizable_update)) if(dimensions){ if(holder.fluid){ if(holder.auto){ switch(holder.fluid_data.mode){ case "width": dimensions.height = dimensions.width / holder.fluid_data.ratio; break; case "height": dimensions.width = dimensions.height * holder.fluid_data.ratio; break; } } el.setAttribute("src", draw({ ctx: ctx, dimensions: dimensions, template: holder.theme, ratio: ratio, holder: holder })) } if(holder.textmode && holder.textmode == "exact"){ holder.exact_dimensions = dimensions; el.setAttribute("src", draw({ ctx: ctx, dimensions: holder.dimensions, template: holder.theme, ratio: ratio, holder: holder })) } } } } } function parse_flags(flags, options) { var ret = { theme: extend(settings.themes.gray, {}) }; var render = false; for (var fl = flags.length, j = 0; j < fl; j++) { var flag = flags[j]; if (app.flags.dimensions.match(flag)) { render = true; ret.dimensions = app.flags.dimensions.output(flag); } else if (app.flags.fluid.match(flag)) { render = true; ret.dimensions = app.flags.fluid.output(flag); ret.fluid = true; } else if (app.flags.textmode.match(flag)) { ret.textmode = app.flags.textmode.output(flag) } else if (app.flags.colors.match(flag)) { ret.theme = app.flags.colors.output(flag); } else if (options.themes[flag]) { //If a theme is specified, it will override custom colors if(options.themes.hasOwnProperty(flag)){ ret.theme = extend(options.themes[flag], {}); } } else if (app.flags.font.match(flag)) { ret.font = app.flags.font.output(flag); } else if (app.flags.auto.match(flag)) { ret.auto = true; } else if (app.flags.text.match(flag)) { ret.text = app.flags.text.output(flag); } } return render ? ret : false; } for (var flag in app.flags) { if (!app.flags.hasOwnProperty(flag)) continue; app.flags[flag].match = function (val) { return val.match(this.regex) } } app.invisible_error_fn = function(fn){ return function(el){ if(el.hasAttribute("data-holder-invisible")){ throw new Error("Holder: invisible placeholder") } } } app.add_theme = function (name, theme) { name != null && theme != null && (settings.themes[name] = theme); return app; }; app.add_image = function (src, el) { var node = selector(el); if (node.length) { for (var i = 0, l = node.length; i < l; i++) { var img = document.createElement("img") img.setAttribute("data-src", src); node[i].appendChild(img); } } return app; }; app.run = function (o) { instance_config = extend({}, system_config) preempted = true; var options = extend(settings, o), images = [], imageNodes = [], bgnodes = []; if(options.use_canvas != null && options.use_canvas){ instance_config.use_canvas = true; instance_config.use_svg = false; } if (typeof (options.images) == "string") { imageNodes = selector(options.images); } else if (window.NodeList && options.images instanceof window.NodeList) { imageNodes = options.images; } else if (window.Node && options.images instanceof window.Node) { imageNodes = [options.images]; } else if(window.HTMLCollection && options.images instanceof window.HTMLCollection){ imageNodes = options.images } if (typeof (options.bgnodes) == "string") { bgnodes = selector(options.bgnodes); } else if (window.NodeList && options.elements instanceof window.NodeList) { bgnodes = options.bgnodes; } else if (window.Node && options.bgnodes instanceof window.Node) { bgnodes = [options.bgnodes]; } for (i = 0, l = imageNodes.length; i < l; i++) images.push(imageNodes[i]); var holdercss = document.getElementById("holderjs-style"); if (!holdercss) { holdercss = document.createElement("style"); holdercss.setAttribute("id", "holderjs-style"); holdercss.type = "text/css"; document.getElementsByTagName("head")[0].appendChild(holdercss); } if (!options.nocss) { if (holdercss.styleSheet) { holdercss.styleSheet.cssText += options.stylesheet; } else { if(options.stylesheet.length){ holdercss.appendChild(document.createTextNode(options.stylesheet)); } } } var cssregex = new RegExp(options.domain + "\/(.*?)\"?\\)"); for (var l = bgnodes.length, i = 0; i < l; i++) { var src = window.getComputedStyle(bgnodes[i], null) .getPropertyValue("background-image"); var flags = src.match(cssregex); var bgsrc = bgnodes[i].getAttribute("data-background-src"); if (flags) { var holder = parse_flags(flags[1].split("/"), options); if (holder) { render("background", bgnodes[i], holder, src); } } else if (bgsrc != null) { var holder = parse_flags(bgsrc.substr(bgsrc.lastIndexOf(options.domain) + options.domain.length + 1) .split("/"), options); if (holder) { render("background", bgnodes[i], holder, src); } } } for (l = images.length, i = 0; i < l; i++) { var attr_data_src, attr_src; attr_src = attr_data_src = src = null; try { attr_src = images[i].getAttribute("src"); attr_datasrc = images[i].getAttribute("data-src"); } catch (e) {} if (attr_datasrc == null && !! attr_src && attr_src.indexOf(options.domain) >= 0) { src = attr_src; } else if ( !! attr_datasrc && attr_datasrc.indexOf(options.domain) >= 0) { src = attr_datasrc; } if (src) { var holder = parse_flags(src.substr(src.lastIndexOf(options.domain) + options.domain.length + 1).split("/"), options); if (holder) { if (holder.fluid) { render("fluid", images[i], holder, src) } else { render("image", images[i], holder, src); } } } } return app; }; contentLoaded(win, function () { if (window.addEventListener) { window.addEventListener("resize", resizable_update, false); window.addEventListener("orientationchange", resizable_update, false); } else { window.attachEvent("onresize", resizable_update) } preempted || app.run({}); if (typeof window.Turbolinks === "object") { document.addEventListener("page:change", function() { app.run({}) }) } }); if (typeof define === "function" && define.amd) { define([], function () { return app; }); } //github.com/davidchambers/Base64.js (function(){function t(t){this.message=t}var e="undefined"!=typeof exports?exports:this,r="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";t.prototype=Error(),t.prototype.name="InvalidCharacterError",e.btoa||(e.btoa=function(e){for(var o,n,a=0,i=r,c="";e.charAt(0|a)||(i="=",a%1);c+=i.charAt(63&o>>8-8*(a%1))){if(n=e.charCodeAt(a+=.75),n>255)throw new t("'btoa' failed");o=o<<8|n}return c}),e.atob||(e.atob=function(e){if(e=e.replace(/=+$/,""),1==e.length%4)throw new t("'atob' failed");for(var o,n,a=0,i=0,c="";n=e.charAt(i++);~n&&(o=a%4?64*o+n:n,a++%4)?c+=String.fromCharCode(255&o>>(6&-2*a)):0)n=r.indexOf(n);return c})})(); //getElementsByClassName polyfill document.getElementsByClassName||(document.getElementsByClassName=function(e){var t=document,n,r,i,s=[];if(t.querySelectorAll)return t.querySelectorAll("."+e);if(t.evaluate){r=".//*[contains(concat(' ', @class, ' '), ' "+e+" ')]",n=t.evaluate(r,t,null,0,null);while(i=n.iterateNext())s.push(i)}else{n=t.getElementsByTagName("*"),r=new RegExp("(^|\\s)"+e+"(\\s|$)");for(i=0;i ['10', '00'] or '-1530' > ['-15', '30'] parseTimezoneChunker = /([\+\-]|\d\d)/gi, // getter and setter names proxyGettersAndSetters = 'Date|Hours|Minutes|Seconds|Milliseconds'.split('|'), unitMillisecondFactors = { 'Milliseconds' : 1, 'Seconds' : 1e3, 'Minutes' : 6e4, 'Hours' : 36e5, 'Days' : 864e5, 'Months' : 2592e6, 'Years' : 31536e6 }, unitAliases = { ms : 'millisecond', s : 'second', m : 'minute', h : 'hour', d : 'day', D : 'date', w : 'week', W : 'isoWeek', M : 'month', Q : 'quarter', y : 'year', DDD : 'dayOfYear', e : 'weekday', E : 'isoWeekday', gg: 'weekYear', GG: 'isoWeekYear' }, camelFunctions = { dayofyear : 'dayOfYear', isoweekday : 'isoWeekday', isoweek : 'isoWeek', weekyear : 'weekYear', isoweekyear : 'isoWeekYear' }, // format function strings formatFunctions = {}, // default relative time thresholds relativeTimeThresholds = { s: 45, // seconds to minute m: 45, // minutes to hour h: 22, // hours to day d: 26, // days to month M: 11 // months to year }, // tokens to ordinalize and pad ordinalizeTokens = 'DDD w W M D d'.split(' '), paddedTokens = 'M D H h m s w W'.split(' '), formatTokenFunctions = { M : function () { return this.month() + 1; }, MMM : function (format) { return this.localeData().monthsShort(this, format); }, MMMM : function (format) { return this.localeData().months(this, format); }, D : function () { return this.date(); }, DDD : function () { return this.dayOfYear(); }, d : function () { return this.day(); }, dd : function (format) { return this.localeData().weekdaysMin(this, format); }, ddd : function (format) { return this.localeData().weekdaysShort(this, format); }, dddd : function (format) { return this.localeData().weekdays(this, format); }, w : function () { return this.week(); }, W : function () { return this.isoWeek(); }, YY : function () { return leftZeroFill(this.year() % 100, 2); }, YYYY : function () { return leftZeroFill(this.year(), 4); }, YYYYY : function () { return leftZeroFill(this.year(), 5); }, YYYYYY : function () { var y = this.year(), sign = y >= 0 ? '+' : '-'; return sign + leftZeroFill(Math.abs(y), 6); }, gg : function () { return leftZeroFill(this.weekYear() % 100, 2); }, gggg : function () { return leftZeroFill(this.weekYear(), 4); }, ggggg : function () { return leftZeroFill(this.weekYear(), 5); }, GG : function () { return leftZeroFill(this.isoWeekYear() % 100, 2); }, GGGG : function () { return leftZeroFill(this.isoWeekYear(), 4); }, GGGGG : function () { return leftZeroFill(this.isoWeekYear(), 5); }, e : function () { return this.weekday(); }, E : function () { return this.isoWeekday(); }, a : function () { return this.localeData().meridiem(this.hours(), this.minutes(), true); }, A : function () { return this.localeData().meridiem(this.hours(), this.minutes(), false); }, H : function () { return this.hours(); }, h : function () { return this.hours() % 12 || 12; }, m : function () { return this.minutes(); }, s : function () { return this.seconds(); }, S : function () { return toInt(this.milliseconds() / 100); }, SS : function () { return leftZeroFill(toInt(this.milliseconds() / 10), 2); }, SSS : function () { return leftZeroFill(this.milliseconds(), 3); }, SSSS : function () { return leftZeroFill(this.milliseconds(), 3); }, Z : function () { var a = -this.zone(), b = '+'; if (a < 0) { a = -a; b = '-'; } return b + leftZeroFill(toInt(a / 60), 2) + ':' + leftZeroFill(toInt(a) % 60, 2); }, ZZ : function () { var a = -this.zone(), b = '+'; if (a < 0) { a = -a; b = '-'; } return b + leftZeroFill(toInt(a / 60), 2) + leftZeroFill(toInt(a) % 60, 2); }, z : function () { return this.zoneAbbr(); }, zz : function () { return this.zoneName(); }, x : function () { return this.valueOf(); }, X : function () { return this.unix(); }, Q : function () { return this.quarter(); } }, deprecations = {}, lists = ['months', 'monthsShort', 'weekdays', 'weekdaysShort', 'weekdaysMin']; // Pick the first defined of two or three arguments. dfl comes from // default. function dfl(a, b, c) { switch (arguments.length) { case 2: return a != null ? a : b; case 3: return a != null ? a : b != null ? b : c; default: throw new Error('Implement me'); } } function hasOwnProp(a, b) { return hasOwnProperty.call(a, b); } function defaultParsingFlags() { // We need to deep clone this object, and es5 standard is not very // helpful. return { empty : false, unusedTokens : [], unusedInput : [], overflow : -2, charsLeftOver : 0, nullInput : false, invalidMonth : null, invalidFormat : false, userInvalidated : false, iso: false }; } function printMsg(msg) { if (moment.suppressDeprecationWarnings === false && typeof console !== 'undefined' && console.warn) { console.warn('Deprecation warning: ' + msg); } } function deprecate(msg, fn) { var firstTime = true; return extend(function () { if (firstTime) { printMsg(msg); firstTime = false; } return fn.apply(this, arguments); }, fn); } function deprecateSimple(name, msg) { if (!deprecations[name]) { printMsg(msg); deprecations[name] = true; } } function padToken(func, count) { return function (a) { return leftZeroFill(func.call(this, a), count); }; } function ordinalizeToken(func, period) { return function (a) { return this.localeData().ordinal(func.call(this, a), period); }; } while (ordinalizeTokens.length) { i = ordinalizeTokens.pop(); formatTokenFunctions[i + 'o'] = ordinalizeToken(formatTokenFunctions[i], i); } while (paddedTokens.length) { i = paddedTokens.pop(); formatTokenFunctions[i + i] = padToken(formatTokenFunctions[i], 2); } formatTokenFunctions.DDDD = padToken(formatTokenFunctions.DDD, 3); /************************************ Constructors ************************************/ function Locale() { } // Moment prototype object function Moment(config, skipOverflow) { if (skipOverflow !== false) { checkOverflow(config); } copyConfig(this, config); this._d = new Date(+config._d); } // Duration Constructor function Duration(duration) { var normalizedInput = normalizeObjectUnits(duration), years = normalizedInput.year || 0, quarters = normalizedInput.quarter || 0, months = normalizedInput.month || 0, weeks = normalizedInput.week || 0, days = normalizedInput.day || 0, hours = normalizedInput.hour || 0, minutes = normalizedInput.minute || 0, seconds = normalizedInput.second || 0, milliseconds = normalizedInput.millisecond || 0; // representation for dateAddRemove this._milliseconds = +milliseconds + seconds * 1e3 + // 1000 minutes * 6e4 + // 1000 * 60 hours * 36e5; // 1000 * 60 * 60 // Because of dateAddRemove treats 24 hours as different from a // day when working around DST, we need to store them separately this._days = +days + weeks * 7; // It is impossible translate months into days without knowing // which months you are are talking about, so we have to store // it separately. this._months = +months + quarters * 3 + years * 12; this._data = {}; this._locale = moment.localeData(); this._bubble(); } /************************************ Helpers ************************************/ function extend(a, b) { for (var i in b) { if (hasOwnProp(b, i)) { a[i] = b[i]; } } if (hasOwnProp(b, 'toString')) { a.toString = b.toString; } if (hasOwnProp(b, 'valueOf')) { a.valueOf = b.valueOf; } return a; } function copyConfig(to, from) { var i, prop, val; if (typeof from._isAMomentObject !== 'undefined') { to._isAMomentObject = from._isAMomentObject; } if (typeof from._i !== 'undefined') { to._i = from._i; } if (typeof from._f !== 'undefined') { to._f = from._f; } if (typeof from._l !== 'undefined') { to._l = from._l; } if (typeof from._strict !== 'undefined') { to._strict = from._strict; } if (typeof from._tzm !== 'undefined') { to._tzm = from._tzm; } if (typeof from._isUTC !== 'undefined') { to._isUTC = from._isUTC; } if (typeof from._offset !== 'undefined') { to._offset = from._offset; } if (typeof from._pf !== 'undefined') { to._pf = from._pf; } if (typeof from._locale !== 'undefined') { to._locale = from._locale; } if (momentProperties.length > 0) { for (i in momentProperties) { prop = momentProperties[i]; val = from[prop]; if (typeof val !== 'undefined') { to[prop] = val; } } } return to; } function absRound(number) { if (number < 0) { return Math.ceil(number); } else { return Math.floor(number); } } // left zero fill a number // see http://jsperf.com/left-zero-filling for performance comparison function leftZeroFill(number, targetLength, forceSign) { var output = '' + Math.abs(number), sign = number >= 0; while (output.length < targetLength) { output = '0' + output; } return (sign ? (forceSign ? '+' : '') : '-') + output; } function positiveMomentsDifference(base, other) { var res = {milliseconds: 0, months: 0}; res.months = other.month() - base.month() + (other.year() - base.year()) * 12; if (base.clone().add(res.months, 'M').isAfter(other)) { --res.months; } res.milliseconds = +other - +(base.clone().add(res.months, 'M')); return res; } function momentsDifference(base, other) { var res; other = makeAs(other, base); if (base.isBefore(other)) { res = positiveMomentsDifference(base, other); } else { res = positiveMomentsDifference(other, base); res.milliseconds = -res.milliseconds; res.months = -res.months; } return res; } // TODO: remove 'name' arg after deprecation is removed function createAdder(direction, name) { return function (val, period) { var dur, tmp; //invert the arguments, but complain about it if (period !== null && !isNaN(+period)) { deprecateSimple(name, 'moment().' + name + '(period, number) is deprecated. Please use moment().' + name + '(number, period).'); tmp = val; val = period; period = tmp; } val = typeof val === 'string' ? +val : val; dur = moment.duration(val, period); addOrSubtractDurationFromMoment(this, dur, direction); return this; }; } function addOrSubtractDurationFromMoment(mom, duration, isAdding, updateOffset) { var milliseconds = duration._milliseconds, days = duration._days, months = duration._months; updateOffset = updateOffset == null ? true : updateOffset; if (milliseconds) { mom._d.setTime(+mom._d + milliseconds * isAdding); } if (days) { rawSetter(mom, 'Date', rawGetter(mom, 'Date') + days * isAdding); } if (months) { rawMonthSetter(mom, rawGetter(mom, 'Month') + months * isAdding); } if (updateOffset) { moment.updateOffset(mom, days || months); } } // check if is an array function isArray(input) { return Object.prototype.toString.call(input) === '[object Array]'; } function isDate(input) { return Object.prototype.toString.call(input) === '[object Date]' || input instanceof Date; } // compare two arrays, return the number of differences function compareArrays(array1, array2, dontConvert) { var len = Math.min(array1.length, array2.length), lengthDiff = Math.abs(array1.length - array2.length), diffs = 0, i; for (i = 0; i < len; i++) { if ((dontConvert && array1[i] !== array2[i]) || (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) { diffs++; } } return diffs + lengthDiff; } function normalizeUnits(units) { if (units) { var lowered = units.toLowerCase().replace(/(.)s$/, '$1'); units = unitAliases[units] || camelFunctions[lowered] || lowered; } return units; } function normalizeObjectUnits(inputObject) { var normalizedInput = {}, normalizedProp, prop; for (prop in inputObject) { if (hasOwnProp(inputObject, prop)) { normalizedProp = normalizeUnits(prop); if (normalizedProp) { normalizedInput[normalizedProp] = inputObject[prop]; } } } return normalizedInput; } function makeList(field) { var count, setter; if (field.indexOf('week') === 0) { count = 7; setter = 'day'; } else if (field.indexOf('month') === 0) { count = 12; setter = 'month'; } else { return; } moment[field] = function (format, index) { var i, getter, method = moment._locale[field], results = []; if (typeof format === 'number') { index = format; format = undefined; } getter = function (i) { var m = moment().utc().set(setter, i); return method.call(moment._locale, m, format || ''); }; if (index != null) { return getter(index); } else { for (i = 0; i < count; i++) { results.push(getter(i)); } return results; } }; } function toInt(argumentForCoercion) { var coercedNumber = +argumentForCoercion, value = 0; if (coercedNumber !== 0 && isFinite(coercedNumber)) { if (coercedNumber >= 0) { value = Math.floor(coercedNumber); } else { value = Math.ceil(coercedNumber); } } return value; } function daysInMonth(year, month) { return new Date(Date.UTC(year, month + 1, 0)).getUTCDate(); } function weeksInYear(year, dow, doy) { return weekOfYear(moment([year, 11, 31 + dow - doy]), dow, doy).week; } function daysInYear(year) { return isLeapYear(year) ? 366 : 365; } function isLeapYear(year) { return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; } function checkOverflow(m) { var overflow; if (m._a && m._pf.overflow === -2) { overflow = m._a[MONTH] < 0 || m._a[MONTH] > 11 ? MONTH : m._a[DATE] < 1 || m._a[DATE] > daysInMonth(m._a[YEAR], m._a[MONTH]) ? DATE : m._a[HOUR] < 0 || m._a[HOUR] > 24 || (m._a[HOUR] === 24 && (m._a[MINUTE] !== 0 || m._a[SECOND] !== 0 || m._a[MILLISECOND] !== 0)) ? HOUR : m._a[MINUTE] < 0 || m._a[MINUTE] > 59 ? MINUTE : m._a[SECOND] < 0 || m._a[SECOND] > 59 ? SECOND : m._a[MILLISECOND] < 0 || m._a[MILLISECOND] > 999 ? MILLISECOND : -1; if (m._pf._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) { overflow = DATE; } m._pf.overflow = overflow; } } function isValid(m) { if (m._isValid == null) { m._isValid = !isNaN(m._d.getTime()) && m._pf.overflow < 0 && !m._pf.empty && !m._pf.invalidMonth && !m._pf.nullInput && !m._pf.invalidFormat && !m._pf.userInvalidated; if (m._strict) { m._isValid = m._isValid && m._pf.charsLeftOver === 0 && m._pf.unusedTokens.length === 0 && m._pf.bigHour === undefined; } } return m._isValid; } function normalizeLocale(key) { return key ? key.toLowerCase().replace('_', '-') : key; } // pick the locale from the array // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root function chooseLocale(names) { var i = 0, j, next, locale, split; while (i < names.length) { split = normalizeLocale(names[i]).split('-'); j = split.length; next = normalizeLocale(names[i + 1]); next = next ? next.split('-') : null; while (j > 0) { locale = loadLocale(split.slice(0, j).join('-')); if (locale) { return locale; } if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) { //the next array item is better than a shallower substring of this one break; } j--; } i++; } return null; } function loadLocale(name) { var oldLocale = null; if (!locales[name] && hasModule) { try { oldLocale = moment.locale(); require('./locale/' + name); // because defineLocale currently also sets the global locale, we want to undo that for lazy loaded locales moment.locale(oldLocale); } catch (e) { } } return locales[name]; } // Return a moment from input, that is local/utc/zone equivalent to model. function makeAs(input, model) { var res, diff; if (model._isUTC) { res = model.clone(); diff = (moment.isMoment(input) || isDate(input) ? +input : +moment(input)) - (+res); // Use low-level api, because this fn is low-level api. res._d.setTime(+res._d + diff); moment.updateOffset(res, false); return res; } else { return moment(input).local(); } } /************************************ Locale ************************************/ extend(Locale.prototype, { set : function (config) { var prop, i; for (i in config) { prop = config[i]; if (typeof prop === 'function') { this[i] = prop; } else { this['_' + i] = prop; } } // Lenient ordinal parsing accepts just a number in addition to // number + (possibly) stuff coming from _ordinalParseLenient. this._ordinalParseLenient = new RegExp(this._ordinalParse.source + '|' + /\d{1,2}/.source); }, _months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'), months : function (m) { return this._months[m.month()]; }, _monthsShort : 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'), monthsShort : function (m) { return this._monthsShort[m.month()]; }, monthsParse : function (monthName, format, strict) { var i, mom, regex; if (!this._monthsParse) { this._monthsParse = []; this._longMonthsParse = []; this._shortMonthsParse = []; } for (i = 0; i < 12; i++) { // make the regex if we don't have it already mom = moment.utc([2000, i]); if (strict && !this._longMonthsParse[i]) { this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i'); this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i'); } if (!strict && !this._monthsParse[i]) { regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); } // test the regex if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) { return i; } else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) { return i; } else if (!strict && this._monthsParse[i].test(monthName)) { return i; } } }, _weekdays : 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'), weekdays : function (m) { return this._weekdays[m.day()]; }, _weekdaysShort : 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'), weekdaysShort : function (m) { return this._weekdaysShort[m.day()]; }, _weekdaysMin : 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'), weekdaysMin : function (m) { return this._weekdaysMin[m.day()]; }, weekdaysParse : function (weekdayName) { var i, mom, regex; if (!this._weekdaysParse) { this._weekdaysParse = []; } for (i = 0; i < 7; i++) { // make the regex if we don't have it already if (!this._weekdaysParse[i]) { mom = moment([2000, 1]).day(i); regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, ''); this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i'); } // test the regex if (this._weekdaysParse[i].test(weekdayName)) { return i; } } }, _longDateFormat : { LTS : 'h:mm:ss A', LT : 'h:mm A', L : 'MM/DD/YYYY', LL : 'MMMM D, YYYY', LLL : 'MMMM D, YYYY LT', LLLL : 'dddd, MMMM D, YYYY LT' }, longDateFormat : function (key) { var output = this._longDateFormat[key]; if (!output && this._longDateFormat[key.toUpperCase()]) { output = this._longDateFormat[key.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (val) { return val.slice(1); }); this._longDateFormat[key] = output; } return output; }, isPM : function (input) { // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays // Using charAt should be more compatible. return ((input + '').toLowerCase().charAt(0) === 'p'); }, _meridiemParse : /[ap]\.?m?\.?/i, meridiem : function (hours, minutes, isLower) { if (hours > 11) { return isLower ? 'pm' : 'PM'; } else { return isLower ? 'am' : 'AM'; } }, _calendar : { sameDay : '[Today at] LT', nextDay : '[Tomorrow at] LT', nextWeek : 'dddd [at] LT', lastDay : '[Yesterday at] LT', lastWeek : '[Last] dddd [at] LT', sameElse : 'L' }, calendar : function (key, mom, now) { var output = this._calendar[key]; return typeof output === 'function' ? output.apply(mom, [now]) : output; }, _relativeTime : { future : 'in %s', past : '%s ago', s : 'a few seconds', m : 'a minute', mm : '%d minutes', h : 'an hour', hh : '%d hours', d : 'a day', dd : '%d days', M : 'a month', MM : '%d months', y : 'a year', yy : '%d years' }, relativeTime : function (number, withoutSuffix, string, isFuture) { var output = this._relativeTime[string]; return (typeof output === 'function') ? output(number, withoutSuffix, string, isFuture) : output.replace(/%d/i, number); }, pastFuture : function (diff, output) { var format = this._relativeTime[diff > 0 ? 'future' : 'past']; return typeof format === 'function' ? format(output) : format.replace(/%s/i, output); }, ordinal : function (number) { return this._ordinal.replace('%d', number); }, _ordinal : '%d', _ordinalParse : /\d{1,2}/, preparse : function (string) { return string; }, postformat : function (string) { return string; }, week : function (mom) { return weekOfYear(mom, this._week.dow, this._week.doy).week; }, _week : { dow : 0, // Sunday is the first day of the week. doy : 6 // The week that contains Jan 1st is the first week of the year. }, _invalidDate: 'Invalid date', invalidDate: function () { return this._invalidDate; } }); /************************************ Formatting ************************************/ function removeFormattingTokens(input) { if (input.match(/\[[\s\S]/)) { return input.replace(/^\[|\]$/g, ''); } return input.replace(/\\/g, ''); } function makeFormatFunction(format) { var array = format.match(formattingTokens), i, length; for (i = 0, length = array.length; i < length; i++) { if (formatTokenFunctions[array[i]]) { array[i] = formatTokenFunctions[array[i]]; } else { array[i] = removeFormattingTokens(array[i]); } } return function (mom) { var output = ''; for (i = 0; i < length; i++) { output += array[i] instanceof Function ? array[i].call(mom, format) : array[i]; } return output; }; } // format date using native date object function formatMoment(m, format) { if (!m.isValid()) { return m.localeData().invalidDate(); } format = expandFormat(format, m.localeData()); if (!formatFunctions[format]) { formatFunctions[format] = makeFormatFunction(format); } return formatFunctions[format](m); } function expandFormat(format, locale) { var i = 5; function replaceLongDateFormatTokens(input) { return locale.longDateFormat(input) || input; } localFormattingTokens.lastIndex = 0; while (i >= 0 && localFormattingTokens.test(format)) { format = format.replace(localFormattingTokens, replaceLongDateFormatTokens); localFormattingTokens.lastIndex = 0; i -= 1; } return format; } /************************************ Parsing ************************************/ // get the regex to find the next token function getParseRegexForToken(token, config) { var a, strict = config._strict; switch (token) { case 'Q': return parseTokenOneDigit; case 'DDDD': return parseTokenThreeDigits; case 'YYYY': case 'GGGG': case 'gggg': return strict ? parseTokenFourDigits : parseTokenOneToFourDigits; case 'Y': case 'G': case 'g': return parseTokenSignedNumber; case 'YYYYYY': case 'YYYYY': case 'GGGGG': case 'ggggg': return strict ? parseTokenSixDigits : parseTokenOneToSixDigits; case 'S': if (strict) { return parseTokenOneDigit; } /* falls through */ case 'SS': if (strict) { return parseTokenTwoDigits; } /* falls through */ case 'SSS': if (strict) { return parseTokenThreeDigits; } /* falls through */ case 'DDD': return parseTokenOneToThreeDigits; case 'MMM': case 'MMMM': case 'dd': case 'ddd': case 'dddd': return parseTokenWord; case 'a': case 'A': return config._locale._meridiemParse; case 'x': return parseTokenOffsetMs; case 'X': return parseTokenTimestampMs; case 'Z': case 'ZZ': return parseTokenTimezone; case 'T': return parseTokenT; case 'SSSS': return parseTokenDigits; case 'MM': case 'DD': case 'YY': case 'GG': case 'gg': case 'HH': case 'hh': case 'mm': case 'ss': case 'ww': case 'WW': return strict ? parseTokenTwoDigits : parseTokenOneOrTwoDigits; case 'M': case 'D': case 'd': case 'H': case 'h': case 'm': case 's': case 'w': case 'W': case 'e': case 'E': return parseTokenOneOrTwoDigits; case 'Do': return strict ? config._locale._ordinalParse : config._locale._ordinalParseLenient; default : a = new RegExp(regexpEscape(unescapeFormat(token.replace('\\', '')), 'i')); return a; } } function timezoneMinutesFromString(string) { string = string || ''; var possibleTzMatches = (string.match(parseTokenTimezone) || []), tzChunk = possibleTzMatches[possibleTzMatches.length - 1] || [], parts = (tzChunk + '').match(parseTimezoneChunker) || ['-', 0, 0], minutes = +(parts[1] * 60) + toInt(parts[2]); return parts[0] === '+' ? -minutes : minutes; } // function to convert string input to date function addTimeToArrayFromToken(token, input, config) { var a, datePartArray = config._a; switch (token) { // QUARTER case 'Q': if (input != null) { datePartArray[MONTH] = (toInt(input) - 1) * 3; } break; // MONTH case 'M' : // fall through to MM case 'MM' : if (input != null) { datePartArray[MONTH] = toInt(input) - 1; } break; case 'MMM' : // fall through to MMMM case 'MMMM' : a = config._locale.monthsParse(input, token, config._strict); // if we didn't find a month name, mark the date as invalid. if (a != null) { datePartArray[MONTH] = a; } else { config._pf.invalidMonth = input; } break; // DAY OF MONTH case 'D' : // fall through to DD case 'DD' : if (input != null) { datePartArray[DATE] = toInt(input); } break; case 'Do' : if (input != null) { datePartArray[DATE] = toInt(parseInt( input.match(/\d{1,2}/)[0], 10)); } break; // DAY OF YEAR case 'DDD' : // fall through to DDDD case 'DDDD' : if (input != null) { config._dayOfYear = toInt(input); } break; // YEAR case 'YY' : datePartArray[YEAR] = moment.parseTwoDigitYear(input); break; case 'YYYY' : case 'YYYYY' : case 'YYYYYY' : datePartArray[YEAR] = toInt(input); break; // AM / PM case 'a' : // fall through to A case 'A' : config._isPm = config._locale.isPM(input); break; // HOUR case 'h' : // fall through to hh case 'hh' : config._pf.bigHour = true; /* falls through */ case 'H' : // fall through to HH case 'HH' : datePartArray[HOUR] = toInt(input); break; // MINUTE case 'm' : // fall through to mm case 'mm' : datePartArray[MINUTE] = toInt(input); break; // SECOND case 's' : // fall through to ss case 'ss' : datePartArray[SECOND] = toInt(input); break; // MILLISECOND case 'S' : case 'SS' : case 'SSS' : case 'SSSS' : datePartArray[MILLISECOND] = toInt(('0.' + input) * 1000); break; // UNIX OFFSET (MILLISECONDS) case 'x': config._d = new Date(toInt(input)); break; // UNIX TIMESTAMP WITH MS case 'X': config._d = new Date(parseFloat(input) * 1000); break; // TIMEZONE case 'Z' : // fall through to ZZ case 'ZZ' : config._useUTC = true; config._tzm = timezoneMinutesFromString(input); break; // WEEKDAY - human case 'dd': case 'ddd': case 'dddd': a = config._locale.weekdaysParse(input); // if we didn't get a weekday name, mark the date as invalid if (a != null) { config._w = config._w || {}; config._w['d'] = a; } else { config._pf.invalidWeekday = input; } break; // WEEK, WEEK DAY - numeric case 'w': case 'ww': case 'W': case 'WW': case 'd': case 'e': case 'E': token = token.substr(0, 1); /* falls through */ case 'gggg': case 'GGGG': case 'GGGGG': token = token.substr(0, 2); if (input) { config._w = config._w || {}; config._w[token] = toInt(input); } break; case 'gg': case 'GG': config._w = config._w || {}; config._w[token] = moment.parseTwoDigitYear(input); } } function dayOfYearFromWeekInfo(config) { var w, weekYear, week, weekday, dow, doy, temp; w = config._w; if (w.GG != null || w.W != null || w.E != null) { dow = 1; doy = 4; // TODO: We need to take the current isoWeekYear, but that depends on // how we interpret now (local, utc, fixed offset). So create // a now version of current config (take local/utc/offset flags, and // create now). weekYear = dfl(w.GG, config._a[YEAR], weekOfYear(moment(), 1, 4).year); week = dfl(w.W, 1); weekday = dfl(w.E, 1); } else { dow = config._locale._week.dow; doy = config._locale._week.doy; weekYear = dfl(w.gg, config._a[YEAR], weekOfYear(moment(), dow, doy).year); week = dfl(w.w, 1); if (w.d != null) { // weekday -- low day numbers are considered next week weekday = w.d; if (weekday < dow) { ++week; } } else if (w.e != null) { // local weekday -- counting starts from begining of week weekday = w.e + dow; } else { // default to begining of week weekday = dow; } } temp = dayOfYearFromWeeks(weekYear, week, weekday, doy, dow); config._a[YEAR] = temp.year; config._dayOfYear = temp.dayOfYear; } // convert an array to a date. // the array should mirror the parameters below // note: all values past the year are optional and will default to the lowest possible value. // [year, month, day , hour, minute, second, millisecond] function dateFromConfig(config) { var i, date, input = [], currentDate, yearToUse; if (config._d) { return; } currentDate = currentDateArray(config); //compute day of the year from weeks and weekdays if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { dayOfYearFromWeekInfo(config); } //if the day of the year is set, figure out what it is if (config._dayOfYear) { yearToUse = dfl(config._a[YEAR], currentDate[YEAR]); if (config._dayOfYear > daysInYear(yearToUse)) { config._pf._overflowDayOfYear = true; } date = makeUTCDate(yearToUse, 0, config._dayOfYear); config._a[MONTH] = date.getUTCMonth(); config._a[DATE] = date.getUTCDate(); } // Default to current date. // * if no year, month, day of month are given, default to today // * if day of month is given, default month and year // * if month is given, default only year // * if year is given, don't default anything for (i = 0; i < 3 && config._a[i] == null; ++i) { config._a[i] = input[i] = currentDate[i]; } // Zero out whatever was not defaulted, including time for (; i < 7; i++) { config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i]; } // Check for 24:00:00.000 if (config._a[HOUR] === 24 && config._a[MINUTE] === 0 && config._a[SECOND] === 0 && config._a[MILLISECOND] === 0) { config._nextDay = true; config._a[HOUR] = 0; } config._d = (config._useUTC ? makeUTCDate : makeDate).apply(null, input); // Apply timezone offset from input. The actual zone can be changed // with parseZone. if (config._tzm != null) { config._d.setUTCMinutes(config._d.getUTCMinutes() + config._tzm); } if (config._nextDay) { config._a[HOUR] = 24; } } function dateFromObject(config) { var normalizedInput; if (config._d) { return; } normalizedInput = normalizeObjectUnits(config._i); config._a = [ normalizedInput.year, normalizedInput.month, normalizedInput.day || normalizedInput.date, normalizedInput.hour, normalizedInput.minute, normalizedInput.second, normalizedInput.millisecond ]; dateFromConfig(config); } function currentDateArray(config) { var now = new Date(); if (config._useUTC) { return [ now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() ]; } else { return [now.getFullYear(), now.getMonth(), now.getDate()]; } } // date from string and format string function makeDateFromStringAndFormat(config) { if (config._f === moment.ISO_8601) { parseISO(config); return; } config._a = []; config._pf.empty = true; // This array is used to make a Date, either with `new Date` or `Date.UTC` var string = '' + config._i, i, parsedInput, tokens, token, skipped, stringLength = string.length, totalParsedInputLength = 0; tokens = expandFormat(config._f, config._locale).match(formattingTokens) || []; for (i = 0; i < tokens.length; i++) { token = tokens[i]; parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0]; if (parsedInput) { skipped = string.substr(0, string.indexOf(parsedInput)); if (skipped.length > 0) { config._pf.unusedInput.push(skipped); } string = string.slice(string.indexOf(parsedInput) + parsedInput.length); totalParsedInputLength += parsedInput.length; } // don't parse if it's not a known token if (formatTokenFunctions[token]) { if (parsedInput) { config._pf.empty = false; } else { config._pf.unusedTokens.push(token); } addTimeToArrayFromToken(token, parsedInput, config); } else if (config._strict && !parsedInput) { config._pf.unusedTokens.push(token); } } // add remaining unparsed input length to the string config._pf.charsLeftOver = stringLength - totalParsedInputLength; if (string.length > 0) { config._pf.unusedInput.push(string); } // clear _12h flag if hour is <= 12 if (config._pf.bigHour === true && config._a[HOUR] <= 12) { config._pf.bigHour = undefined; } // handle am pm if (config._isPm && config._a[HOUR] < 12) { config._a[HOUR] += 12; } // if is 12 am, change hours to 0 if (config._isPm === false && config._a[HOUR] === 12) { config._a[HOUR] = 0; } dateFromConfig(config); checkOverflow(config); } function unescapeFormat(s) { return s.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) { return p1 || p2 || p3 || p4; }); } // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript function regexpEscape(s) { return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); } // date from string and array of format strings function makeDateFromStringAndArray(config) { var tempConfig, bestMoment, scoreToBeat, i, currentScore; if (config._f.length === 0) { config._pf.invalidFormat = true; config._d = new Date(NaN); return; } for (i = 0; i < config._f.length; i++) { currentScore = 0; tempConfig = copyConfig({}, config); if (config._useUTC != null) { tempConfig._useUTC = config._useUTC; } tempConfig._pf = defaultParsingFlags(); tempConfig._f = config._f[i]; makeDateFromStringAndFormat(tempConfig); if (!isValid(tempConfig)) { continue; } // if there is any input that was not parsed add a penalty for that format currentScore += tempConfig._pf.charsLeftOver; //or tokens currentScore += tempConfig._pf.unusedTokens.length * 10; tempConfig._pf.score = currentScore; if (scoreToBeat == null || currentScore < scoreToBeat) { scoreToBeat = currentScore; bestMoment = tempConfig; } } extend(config, bestMoment || tempConfig); } // date from iso format function parseISO(config) { var i, l, string = config._i, match = isoRegex.exec(string); if (match) { config._pf.iso = true; for (i = 0, l = isoDates.length; i < l; i++) { if (isoDates[i][1].exec(string)) { // match[5] should be 'T' or undefined config._f = isoDates[i][0] + (match[6] || ' '); break; } } for (i = 0, l = isoTimes.length; i < l; i++) { if (isoTimes[i][1].exec(string)) { config._f += isoTimes[i][0]; break; } } if (string.match(parseTokenTimezone)) { config._f += 'Z'; } makeDateFromStringAndFormat(config); } else { config._isValid = false; } } // date from iso format or fallback function makeDateFromString(config) { parseISO(config); if (config._isValid === false) { delete config._isValid; moment.createFromInputFallback(config); } } function map(arr, fn) { var res = [], i; for (i = 0; i < arr.length; ++i) { res.push(fn(arr[i], i)); } return res; } function makeDateFromInput(config) { var input = config._i, matched; if (input === undefined) { config._d = new Date(); } else if (isDate(input)) { config._d = new Date(+input); } else if ((matched = aspNetJsonRegex.exec(input)) !== null) { config._d = new Date(+matched[1]); } else if (typeof input === 'string') { makeDateFromString(config); } else if (isArray(input)) { config._a = map(input.slice(0), function (obj) { return parseInt(obj, 10); }); dateFromConfig(config); } else if (typeof(input) === 'object') { dateFromObject(config); } else if (typeof(input) === 'number') { // from milliseconds config._d = new Date(input); } else { moment.createFromInputFallback(config); } } function makeDate(y, m, d, h, M, s, ms) { //can't just apply() to create a date: //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply var date = new Date(y, m, d, h, M, s, ms); //the date constructor doesn't accept years < 1970 if (y < 1970) { date.setFullYear(y); } return date; } function makeUTCDate(y) { var date = new Date(Date.UTC.apply(null, arguments)); if (y < 1970) { date.setUTCFullYear(y); } return date; } function parseWeekday(input, locale) { if (typeof input === 'string') { if (!isNaN(input)) { input = parseInt(input, 10); } else { input = locale.weekdaysParse(input); if (typeof input !== 'number') { return null; } } } return input; } /************************************ Relative Time ************************************/ // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) { return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture); } function relativeTime(posNegDuration, withoutSuffix, locale) { var duration = moment.duration(posNegDuration).abs(), seconds = round(duration.as('s')), minutes = round(duration.as('m')), hours = round(duration.as('h')), days = round(duration.as('d')), months = round(duration.as('M')), years = round(duration.as('y')), args = seconds < relativeTimeThresholds.s && ['s', seconds] || minutes === 1 && ['m'] || minutes < relativeTimeThresholds.m && ['mm', minutes] || hours === 1 && ['h'] || hours < relativeTimeThresholds.h && ['hh', hours] || days === 1 && ['d'] || days < relativeTimeThresholds.d && ['dd', days] || months === 1 && ['M'] || months < relativeTimeThresholds.M && ['MM', months] || years === 1 && ['y'] || ['yy', years]; args[2] = withoutSuffix; args[3] = +posNegDuration > 0; args[4] = locale; return substituteTimeAgo.apply({}, args); } /************************************ Week of Year ************************************/ // firstDayOfWeek 0 = sun, 6 = sat // the day of the week that starts the week // (usually sunday or monday) // firstDayOfWeekOfYear 0 = sun, 6 = sat // the first week is the week that contains the first // of this day of the week // (eg. ISO weeks use thursday (4)) function weekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) { var end = firstDayOfWeekOfYear - firstDayOfWeek, daysToDayOfWeek = firstDayOfWeekOfYear - mom.day(), adjustedMoment; if (daysToDayOfWeek > end) { daysToDayOfWeek -= 7; } if (daysToDayOfWeek < end - 7) { daysToDayOfWeek += 7; } adjustedMoment = moment(mom).add(daysToDayOfWeek, 'd'); return { week: Math.ceil(adjustedMoment.dayOfYear() / 7), year: adjustedMoment.year() }; } //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday function dayOfYearFromWeeks(year, week, weekday, firstDayOfWeekOfYear, firstDayOfWeek) { var d = makeUTCDate(year, 0, 1).getUTCDay(), daysToAdd, dayOfYear; d = d === 0 ? 7 : d; weekday = weekday != null ? weekday : firstDayOfWeek; daysToAdd = firstDayOfWeek - d + (d > firstDayOfWeekOfYear ? 7 : 0) - (d < firstDayOfWeek ? 7 : 0); dayOfYear = 7 * (week - 1) + (weekday - firstDayOfWeek) + daysToAdd + 1; return { year: dayOfYear > 0 ? year : year - 1, dayOfYear: dayOfYear > 0 ? dayOfYear : daysInYear(year - 1) + dayOfYear }; } /************************************ Top Level Functions ************************************/ function makeMoment(config) { var input = config._i, format = config._f, res; config._locale = config._locale || moment.localeData(config._l); if (input === null || (format === undefined && input === '')) { return moment.invalid({nullInput: true}); } if (typeof input === 'string') { config._i = input = config._locale.preparse(input); } if (moment.isMoment(input)) { return new Moment(input, true); } else if (format) { if (isArray(format)) { makeDateFromStringAndArray(config); } else { makeDateFromStringAndFormat(config); } } else { makeDateFromInput(config); } res = new Moment(config); if (res._nextDay) { // Adding is smart enough around DST res.add(1, 'd'); res._nextDay = undefined; } return res; } moment = function (input, format, locale, strict) { var c; if (typeof(locale) === 'boolean') { strict = locale; locale = undefined; } // object construction must be done this way. // https://github.com/moment/moment/issues/1423 c = {}; c._isAMomentObject = true; c._i = input; c._f = format; c._l = locale; c._strict = strict; c._isUTC = false; c._pf = defaultParsingFlags(); return makeMoment(c); }; moment.suppressDeprecationWarnings = false; moment.createFromInputFallback = deprecate( 'moment construction falls back to js Date. This is ' + 'discouraged and will be removed in upcoming major ' + 'release. Please refer to ' + 'https://github.com/moment/moment/issues/1407 for more info.', function (config) { config._d = new Date(config._i + (config._useUTC ? ' UTC' : '')); } ); // Pick a moment m from moments so that m[fn](other) is true for all // other. This relies on the function fn to be transitive. // // moments should either be an array of moment objects or an array, whose // first element is an array of moment objects. function pickBy(fn, moments) { var res, i; if (moments.length === 1 && isArray(moments[0])) { moments = moments[0]; } if (!moments.length) { return moment(); } res = moments[0]; for (i = 1; i < moments.length; ++i) { if (moments[i][fn](res)) { res = moments[i]; } } return res; } moment.min = function () { var args = [].slice.call(arguments, 0); return pickBy('isBefore', args); }; moment.max = function () { var args = [].slice.call(arguments, 0); return pickBy('isAfter', args); }; // creating with utc moment.utc = function (input, format, locale, strict) { var c; if (typeof(locale) === 'boolean') { strict = locale; locale = undefined; } // object construction must be done this way. // https://github.com/moment/moment/issues/1423 c = {}; c._isAMomentObject = true; c._useUTC = true; c._isUTC = true; c._l = locale; c._i = input; c._f = format; c._strict = strict; c._pf = defaultParsingFlags(); return makeMoment(c).utc(); }; // creating with unix timestamp (in seconds) moment.unix = function (input) { return moment(input * 1000); }; // duration moment.duration = function (input, key) { var duration = input, // matching against regexp is expensive, do it on demand match = null, sign, ret, parseIso, diffRes; if (moment.isDuration(input)) { duration = { ms: input._milliseconds, d: input._days, M: input._months }; } else if (typeof input === 'number') { duration = {}; if (key) { duration[key] = input; } else { duration.milliseconds = input; } } else if (!!(match = aspNetTimeSpanJsonRegex.exec(input))) { sign = (match[1] === '-') ? -1 : 1; duration = { y: 0, d: toInt(match[DATE]) * sign, h: toInt(match[HOUR]) * sign, m: toInt(match[MINUTE]) * sign, s: toInt(match[SECOND]) * sign, ms: toInt(match[MILLISECOND]) * sign }; } else if (!!(match = isoDurationRegex.exec(input))) { sign = (match[1] === '-') ? -1 : 1; parseIso = function (inp) { // We'd normally use ~~inp for this, but unfortunately it also // converts floats to ints. // inp may be undefined, so careful calling replace on it. var res = inp && parseFloat(inp.replace(',', '.')); // apply sign while we're at it return (isNaN(res) ? 0 : res) * sign; }; duration = { y: parseIso(match[2]), M: parseIso(match[3]), d: parseIso(match[4]), h: parseIso(match[5]), m: parseIso(match[6]), s: parseIso(match[7]), w: parseIso(match[8]) }; } else if (typeof duration === 'object' && ('from' in duration || 'to' in duration)) { diffRes = momentsDifference(moment(duration.from), moment(duration.to)); duration = {}; duration.ms = diffRes.milliseconds; duration.M = diffRes.months; } ret = new Duration(duration); if (moment.isDuration(input) && hasOwnProp(input, '_locale')) { ret._locale = input._locale; } return ret; }; // version number moment.version = VERSION; // default format moment.defaultFormat = isoFormat; // constant that refers to the ISO standard moment.ISO_8601 = function () {}; // Plugins that add properties should also add the key here (null value), // so we can properly clone ourselves. moment.momentProperties = momentProperties; // This function will be called whenever a moment is mutated. // It is intended to keep the offset in sync with the timezone. moment.updateOffset = function () {}; // This function allows you to set a threshold for relative time strings moment.relativeTimeThreshold = function (threshold, limit) { if (relativeTimeThresholds[threshold] === undefined) { return false; } if (limit === undefined) { return relativeTimeThresholds[threshold]; } relativeTimeThresholds[threshold] = limit; return true; }; moment.lang = deprecate( 'moment.lang is deprecated. Use moment.locale instead.', function (key, value) { return moment.locale(key, value); } ); // This function will load locale and then set the global locale. If // no arguments are passed in, it will simply return the current global // locale key. moment.locale = function (key, values) { var data; if (key) { if (typeof(values) !== 'undefined') { data = moment.defineLocale(key, values); } else { data = moment.localeData(key); } if (data) { moment.duration._locale = moment._locale = data; } } return moment._locale._abbr; }; moment.defineLocale = function (name, values) { if (values !== null) { values.abbr = name; if (!locales[name]) { locales[name] = new Locale(); } locales[name].set(values); // backwards compat for now: also set the locale moment.locale(name); return locales[name]; } else { // useful for testing delete locales[name]; return null; } }; moment.langData = deprecate( 'moment.langData is deprecated. Use moment.localeData instead.', function (key) { return moment.localeData(key); } ); // returns locale data moment.localeData = function (key) { var locale; if (key && key._locale && key._locale._abbr) { key = key._locale._abbr; } if (!key) { return moment._locale; } if (!isArray(key)) { //short-circuit everything else locale = loadLocale(key); if (locale) { return locale; } key = [key]; } return chooseLocale(key); }; // compare moment object moment.isMoment = function (obj) { return obj instanceof Moment || (obj != null && hasOwnProp(obj, '_isAMomentObject')); }; // for typechecking Duration objects moment.isDuration = function (obj) { return obj instanceof Duration; }; for (i = lists.length - 1; i >= 0; --i) { makeList(lists[i]); } moment.normalizeUnits = function (units) { return normalizeUnits(units); }; moment.invalid = function (flags) { var m = moment.utc(NaN); if (flags != null) { extend(m._pf, flags); } else { m._pf.userInvalidated = true; } return m; }; moment.parseZone = function () { return moment.apply(null, arguments).parseZone(); }; moment.parseTwoDigitYear = function (input) { return toInt(input) + (toInt(input) > 68 ? 1900 : 2000); }; /************************************ Moment Prototype ************************************/ extend(moment.fn = Moment.prototype, { clone : function () { return moment(this); }, valueOf : function () { return +this._d + ((this._offset || 0) * 60000); }, unix : function () { return Math.floor(+this / 1000); }, toString : function () { return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ'); }, toDate : function () { return this._offset ? new Date(+this) : this._d; }, toISOString : function () { var m = moment(this).utc(); if (0 < m.year() && m.year() <= 9999) { if ('function' === typeof Date.prototype.toISOString) { // native implementation is ~50x faster, use it when we can return this.toDate().toISOString(); } else { return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); } } else { return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); } }, toArray : function () { var m = this; return [ m.year(), m.month(), m.date(), m.hours(), m.minutes(), m.seconds(), m.milliseconds() ]; }, isValid : function () { return isValid(this); }, isDSTShifted : function () { if (this._a) { return this.isValid() && compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray()) > 0; } return false; }, parsingFlags : function () { return extend({}, this._pf); }, invalidAt: function () { return this._pf.overflow; }, utc : function (keepLocalTime) { return this.zone(0, keepLocalTime); }, local : function (keepLocalTime) { if (this._isUTC) { this.zone(0, keepLocalTime); this._isUTC = false; if (keepLocalTime) { this.add(this._dateTzOffset(), 'm'); } } return this; }, format : function (inputString) { var output = formatMoment(this, inputString || moment.defaultFormat); return this.localeData().postformat(output); }, add : createAdder(1, 'add'), subtract : createAdder(-1, 'subtract'), diff : function (input, units, asFloat) { var that = makeAs(input, this), zoneDiff = (this.zone() - that.zone()) * 6e4, diff, output, daysAdjust; units = normalizeUnits(units); if (units === 'year' || units === 'month') { // average number of days in the months in the given dates diff = (this.daysInMonth() + that.daysInMonth()) * 432e5; // 24 * 60 * 60 * 1000 / 2 // difference in months output = ((this.year() - that.year()) * 12) + (this.month() - that.month()); // adjust by taking difference in days, average number of days // and dst in the given months. daysAdjust = (this - moment(this).startOf('month')) - (that - moment(that).startOf('month')); // same as above but with zones, to negate all dst daysAdjust -= ((this.zone() - moment(this).startOf('month').zone()) - (that.zone() - moment(that).startOf('month').zone())) * 6e4; output += daysAdjust / diff; if (units === 'year') { output = output / 12; } } else { diff = (this - that); output = units === 'second' ? diff / 1e3 : // 1000 units === 'minute' ? diff / 6e4 : // 1000 * 60 units === 'hour' ? diff / 36e5 : // 1000 * 60 * 60 units === 'day' ? (diff - zoneDiff) / 864e5 : // 1000 * 60 * 60 * 24, negate dst units === 'week' ? (diff - zoneDiff) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst diff; } return asFloat ? output : absRound(output); }, from : function (time, withoutSuffix) { return moment.duration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix); }, fromNow : function (withoutSuffix) { return this.from(moment(), withoutSuffix); }, calendar : function (time) { // We want to compare the start of today, vs this. // Getting start-of-today depends on whether we're zone'd or not. var now = time || moment(), sod = makeAs(now, this).startOf('day'), diff = this.diff(sod, 'days', true), format = diff < -6 ? 'sameElse' : diff < -1 ? 'lastWeek' : diff < 0 ? 'lastDay' : diff < 1 ? 'sameDay' : diff < 2 ? 'nextDay' : diff < 7 ? 'nextWeek' : 'sameElse'; return this.format(this.localeData().calendar(format, this, moment(now))); }, isLeapYear : function () { return isLeapYear(this.year()); }, isDST : function () { return (this.zone() < this.clone().month(0).zone() || this.zone() < this.clone().month(5).zone()); }, day : function (input) { var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); if (input != null) { input = parseWeekday(input, this.localeData()); return this.add(input - day, 'd'); } else { return day; } }, month : makeAccessor('Month', true), startOf : function (units) { units = normalizeUnits(units); // the following switch intentionally omits break keywords // to utilize falling through the cases. switch (units) { case 'year': this.month(0); /* falls through */ case 'quarter': case 'month': this.date(1); /* falls through */ case 'week': case 'isoWeek': case 'day': this.hours(0); /* falls through */ case 'hour': this.minutes(0); /* falls through */ case 'minute': this.seconds(0); /* falls through */ case 'second': this.milliseconds(0); /* falls through */ } // weeks are a special case if (units === 'week') { this.weekday(0); } else if (units === 'isoWeek') { this.isoWeekday(1); } // quarters are also special if (units === 'quarter') { this.month(Math.floor(this.month() / 3) * 3); } return this; }, endOf: function (units) { units = normalizeUnits(units); if (units === undefined || units === 'millisecond') { return this; } return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms'); }, isAfter: function (input, units) { var inputMs; units = normalizeUnits(typeof units !== 'undefined' ? units : 'millisecond'); if (units === 'millisecond') { input = moment.isMoment(input) ? input : moment(input); return +this > +input; } else { inputMs = moment.isMoment(input) ? +input : +moment(input); return inputMs < +this.clone().startOf(units); } }, isBefore: function (input, units) { var inputMs; units = normalizeUnits(typeof units !== 'undefined' ? units : 'millisecond'); if (units === 'millisecond') { input = moment.isMoment(input) ? input : moment(input); return +this < +input; } else { inputMs = moment.isMoment(input) ? +input : +moment(input); return +this.clone().endOf(units) < inputMs; } }, isSame: function (input, units) { var inputMs; units = normalizeUnits(units || 'millisecond'); if (units === 'millisecond') { input = moment.isMoment(input) ? input : moment(input); return +this === +input; } else { inputMs = +moment(input); return +(this.clone().startOf(units)) <= inputMs && inputMs <= +(this.clone().endOf(units)); } }, min: deprecate( 'moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548', function (other) { other = moment.apply(null, arguments); return other < this ? this : other; } ), max: deprecate( 'moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548', function (other) { other = moment.apply(null, arguments); return other > this ? this : other; } ), // keepLocalTime = true means only change the timezone, without // affecting the local hour. So 5:31:26 +0300 --[zone(2, true)]--> // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist int zone // +0200, so we adjust the time as needed, to be valid. // // Keeping the time actually adds/subtracts (one hour) // from the actual represented time. That is why we call updateOffset // a second time. In case it wants us to change the offset again // _changeInProgress == true case, then we have to adjust, because // there is no such time in the given timezone. zone : function (input, keepLocalTime) { var offset = this._offset || 0, localAdjust; if (input != null) { if (typeof input === 'string') { input = timezoneMinutesFromString(input); } if (Math.abs(input) < 16) { input = input * 60; } if (!this._isUTC && keepLocalTime) { localAdjust = this._dateTzOffset(); } this._offset = input; this._isUTC = true; if (localAdjust != null) { this.subtract(localAdjust, 'm'); } if (offset !== input) { if (!keepLocalTime || this._changeInProgress) { addOrSubtractDurationFromMoment(this, moment.duration(offset - input, 'm'), 1, false); } else if (!this._changeInProgress) { this._changeInProgress = true; moment.updateOffset(this, true); this._changeInProgress = null; } } } else { return this._isUTC ? offset : this._dateTzOffset(); } return this; }, zoneAbbr : function () { return this._isUTC ? 'UTC' : ''; }, zoneName : function () { return this._isUTC ? 'Coordinated Universal Time' : ''; }, parseZone : function () { if (this._tzm) { this.zone(this._tzm); } else if (typeof this._i === 'string') { this.zone(this._i); } return this; }, hasAlignedHourOffset : function (input) { if (!input) { input = 0; } else { input = moment(input).zone(); } return (this.zone() - input) % 60 === 0; }, daysInMonth : function () { return daysInMonth(this.year(), this.month()); }, dayOfYear : function (input) { var dayOfYear = round((moment(this).startOf('day') - moment(this).startOf('year')) / 864e5) + 1; return input == null ? dayOfYear : this.add((input - dayOfYear), 'd'); }, quarter : function (input) { return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3); }, weekYear : function (input) { var year = weekOfYear(this, this.localeData()._week.dow, this.localeData()._week.doy).year; return input == null ? year : this.add((input - year), 'y'); }, isoWeekYear : function (input) { var year = weekOfYear(this, 1, 4).year; return input == null ? year : this.add((input - year), 'y'); }, week : function (input) { var week = this.localeData().week(this); return input == null ? week : this.add((input - week) * 7, 'd'); }, isoWeek : function (input) { var week = weekOfYear(this, 1, 4).week; return input == null ? week : this.add((input - week) * 7, 'd'); }, weekday : function (input) { var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7; return input == null ? weekday : this.add(input - weekday, 'd'); }, isoWeekday : function (input) { // behaves the same as moment#day except // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) // as a setter, sunday should belong to the previous week. return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7); }, isoWeeksInYear : function () { return weeksInYear(this.year(), 1, 4); }, weeksInYear : function () { var weekInfo = this.localeData()._week; return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy); }, get : function (units) { units = normalizeUnits(units); return this[units](); }, set : function (units, value) { units = normalizeUnits(units); if (typeof this[units] === 'function') { this[units](value); } return this; }, // If passed a locale key, it will set the locale for this // instance. Otherwise, it will return the locale configuration // variables for this instance. locale : function (key) { var newLocaleData; if (key === undefined) { return this._locale._abbr; } else { newLocaleData = moment.localeData(key); if (newLocaleData != null) { this._locale = newLocaleData; } return this; } }, lang : deprecate( 'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.', function (key) { if (key === undefined) { return this.localeData(); } else { return this.locale(key); } } ), localeData : function () { return this._locale; }, _dateTzOffset : function () { // On Firefox.24 Date#getTimezoneOffset returns a floating point. // https://github.com/moment/moment/pull/1871 return Math.round(this._d.getTimezoneOffset() / 15) * 15; } }); function rawMonthSetter(mom, value) { var dayOfMonth; // TODO: Move this out of here! if (typeof value === 'string') { value = mom.localeData().monthsParse(value); // TODO: Another silent failure? if (typeof value !== 'number') { return mom; } } dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value)); mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth); return mom; } function rawGetter(mom, unit) { return mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit](); } function rawSetter(mom, unit, value) { if (unit === 'Month') { return rawMonthSetter(mom, value); } else { return mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value); } } function makeAccessor(unit, keepTime) { return function (value) { if (value != null) { rawSetter(this, unit, value); moment.updateOffset(this, keepTime); return this; } else { return rawGetter(this, unit); } }; } moment.fn.millisecond = moment.fn.milliseconds = makeAccessor('Milliseconds', false); moment.fn.second = moment.fn.seconds = makeAccessor('Seconds', false); moment.fn.minute = moment.fn.minutes = makeAccessor('Minutes', false); // Setting the hour should keep the time, because the user explicitly // specified which hour he wants. So trying to maintain the same hour (in // a new timezone) makes sense. Adding/subtracting hours does not follow // this rule. moment.fn.hour = moment.fn.hours = makeAccessor('Hours', true); // moment.fn.month is defined separately moment.fn.date = makeAccessor('Date', true); moment.fn.dates = deprecate('dates accessor is deprecated. Use date instead.', makeAccessor('Date', true)); moment.fn.year = makeAccessor('FullYear', true); moment.fn.years = deprecate('years accessor is deprecated. Use year instead.', makeAccessor('FullYear', true)); // add plural methods moment.fn.days = moment.fn.day; moment.fn.months = moment.fn.month; moment.fn.weeks = moment.fn.week; moment.fn.isoWeeks = moment.fn.isoWeek; moment.fn.quarters = moment.fn.quarter; // add aliased format methods moment.fn.toJSON = moment.fn.toISOString; /************************************ Duration Prototype ************************************/ function daysToYears (days) { // 400 years have 146097 days (taking into account leap year rules) return days * 400 / 146097; } function yearsToDays (years) { // years * 365 + absRound(years / 4) - // absRound(years / 100) + absRound(years / 400); return years * 146097 / 400; } extend(moment.duration.fn = Duration.prototype, { _bubble : function () { var milliseconds = this._milliseconds, days = this._days, months = this._months, data = this._data, seconds, minutes, hours, years = 0; // The following code bubbles up values, see the tests for // examples of what that means. data.milliseconds = milliseconds % 1000; seconds = absRound(milliseconds / 1000); data.seconds = seconds % 60; minutes = absRound(seconds / 60); data.minutes = minutes % 60; hours = absRound(minutes / 60); data.hours = hours % 24; days += absRound(hours / 24); // Accurately convert days to years, assume start from year 0. years = absRound(daysToYears(days)); days -= absRound(yearsToDays(years)); // 30 days to a month // TODO (iskren): Use anchor date (like 1st Jan) to compute this. months += absRound(days / 30); days %= 30; // 12 months -> 1 year years += absRound(months / 12); months %= 12; data.days = days; data.months = months; data.years = years; }, abs : function () { this._milliseconds = Math.abs(this._milliseconds); this._days = Math.abs(this._days); this._months = Math.abs(this._months); this._data.milliseconds = Math.abs(this._data.milliseconds); this._data.seconds = Math.abs(this._data.seconds); this._data.minutes = Math.abs(this._data.minutes); this._data.hours = Math.abs(this._data.hours); this._data.months = Math.abs(this._data.months); this._data.years = Math.abs(this._data.years); return this; }, weeks : function () { return absRound(this.days() / 7); }, valueOf : function () { return this._milliseconds + this._days * 864e5 + (this._months % 12) * 2592e6 + toInt(this._months / 12) * 31536e6; }, humanize : function (withSuffix) { var output = relativeTime(this, !withSuffix, this.localeData()); if (withSuffix) { output = this.localeData().pastFuture(+this, output); } return this.localeData().postformat(output); }, add : function (input, val) { // supports only 2.0-style add(1, 's') or add(moment) var dur = moment.duration(input, val); this._milliseconds += dur._milliseconds; this._days += dur._days; this._months += dur._months; this._bubble(); return this; }, subtract : function (input, val) { var dur = moment.duration(input, val); this._milliseconds -= dur._milliseconds; this._days -= dur._days; this._months -= dur._months; this._bubble(); return this; }, get : function (units) { units = normalizeUnits(units); return this[units.toLowerCase() + 's'](); }, as : function (units) { var days, months; units = normalizeUnits(units); if (units === 'month' || units === 'year') { days = this._days + this._milliseconds / 864e5; months = this._months + daysToYears(days) * 12; return units === 'month' ? months : months / 12; } else { // handle milliseconds separately because of floating point math errors (issue #1867) days = this._days + Math.round(yearsToDays(this._months / 12)); switch (units) { case 'week': return days / 7 + this._milliseconds / 6048e5; case 'day': return days + this._milliseconds / 864e5; case 'hour': return days * 24 + this._milliseconds / 36e5; case 'minute': return days * 24 * 60 + this._milliseconds / 6e4; case 'second': return days * 24 * 60 * 60 + this._milliseconds / 1000; // Math.floor prevents floating point math errors here case 'millisecond': return Math.floor(days * 24 * 60 * 60 * 1000) + this._milliseconds; default: throw new Error('Unknown unit ' + units); } } }, lang : moment.fn.lang, locale : moment.fn.locale, toIsoString : deprecate( 'toIsoString() is deprecated. Please use toISOString() instead ' + '(notice the capitals)', function () { return this.toISOString(); } ), toISOString : function () { // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js var years = Math.abs(this.years()), months = Math.abs(this.months()), days = Math.abs(this.days()), hours = Math.abs(this.hours()), minutes = Math.abs(this.minutes()), seconds = Math.abs(this.seconds() + this.milliseconds() / 1000); if (!this.asSeconds()) { // this is the same as C#'s (Noda) and python (isodate)... // but not other JS (goog.date) return 'P0D'; } return (this.asSeconds() < 0 ? '-' : '') + 'P' + (years ? years + 'Y' : '') + (months ? months + 'M' : '') + (days ? days + 'D' : '') + ((hours || minutes || seconds) ? 'T' : '') + (hours ? hours + 'H' : '') + (minutes ? minutes + 'M' : '') + (seconds ? seconds + 'S' : ''); }, localeData : function () { return this._locale; } }); moment.duration.fn.toString = moment.duration.fn.toISOString; function makeDurationGetter(name) { moment.duration.fn[name] = function () { return this._data[name]; }; } for (i in unitMillisecondFactors) { if (hasOwnProp(unitMillisecondFactors, i)) { makeDurationGetter(i.toLowerCase()); } } moment.duration.fn.asMilliseconds = function () { return this.as('ms'); }; moment.duration.fn.asSeconds = function () { return this.as('s'); }; moment.duration.fn.asMinutes = function () { return this.as('m'); }; moment.duration.fn.asHours = function () { return this.as('h'); }; moment.duration.fn.asDays = function () { return this.as('d'); }; moment.duration.fn.asWeeks = function () { return this.as('weeks'); }; moment.duration.fn.asMonths = function () { return this.as('M'); }; moment.duration.fn.asYears = function () { return this.as('y'); }; /************************************ Default Locale ************************************/ // Set default locale, other locale will inherit from English. moment.locale('en', { ordinalParse: /\d{1,2}(th|st|nd|rd)/, ordinal : function (number) { var b = number % 10, output = (toInt(number % 100 / 10) === 1) ? 'th' : (b === 1) ? 'st' : (b === 2) ? 'nd' : (b === 3) ? 'rd' : 'th'; return number + output; } }); /* EMBED_LOCALES */ /************************************ Exposing Moment ************************************/ function makeGlobal(shouldDeprecate) { /*global ender:false */ if (typeof ender !== 'undefined') { return; } oldGlobalMoment = globalScope.moment; if (shouldDeprecate) { globalScope.moment = deprecate( 'Accessing Moment through the global scope is ' + 'deprecated, and will be removed in an upcoming ' + 'release.', moment); } else { globalScope.moment = moment; } } // CommonJS module is defined if (hasModule) { module.exports = moment; } else if (typeof define === 'function' && define.amd) { define('moment', function (require, exports, module) { if (module.config && module.config() && module.config().noGlobal === true) { // release the global variable globalScope.moment = oldGlobalMoment; } return moment; }); makeGlobal(true); } else { makeGlobal(); } }).call(this); // moment.js locale configuration // locale : chinese (zh-cn) // author : suupic : https://github.com/suupic // author : Zeno Zeng : https://github.com/zenozeng (function (factory) { if (typeof define === 'function' && define.amd) { define(['moment'], factory); // AMD } else if (typeof exports === 'object') { module.exports = factory(require('../moment')); // Node } else { factory((typeof global !== 'undefined' ? global : this).moment); // node or other global } }(function (moment) { return moment.defineLocale('zh-cn', { months : '一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月'.split('_'), monthsShort : '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'), weekdays : '星期日_星期一_星期二_星期三_星期四_星期五_星期六'.split('_'), weekdaysShort : '周日_周一_周二_周三_周四_周五_周六'.split('_'), weekdaysMin : '日_一_二_三_四_五_六'.split('_'), longDateFormat : { LT : 'Ah点mm', LTS : 'Ah点m分s秒', L : 'YYYY-MM-DD', LL : 'YYYY年MMMD日', LLL : 'YYYY年MMMD日LT', LLLL : 'YYYY年MMMD日ddddLT', l : 'YYYY-MM-DD', ll : 'YYYY年MMMD日', lll : 'YYYY年MMMD日LT', llll : 'YYYY年MMMD日ddddLT' }, meridiem : function (hour, minute, isLower) { var hm = hour * 100 + minute; if (hm < 600) { return '凌晨'; } else if (hm < 900) { return '早上'; } else if (hm < 1130) { return '上午'; } else if (hm < 1230) { return '中午'; } else if (hm < 1800) { return '下午'; } else { return '晚上'; } }, calendar : { sameDay : function () { return this.minutes() === 0 ? '[今天]Ah[点整]' : '[今天]LT'; }, nextDay : function () { return this.minutes() === 0 ? '[明天]Ah[点整]' : '[明天]LT'; }, lastDay : function () { return this.minutes() === 0 ? '[昨天]Ah[点整]' : '[昨天]LT'; }, nextWeek : function () { var startOfWeek, prefix; startOfWeek = moment().startOf('week'); prefix = this.unix() - startOfWeek.unix() >= 7 * 24 * 3600 ? '[下]' : '[本]'; return this.minutes() === 0 ? prefix + 'dddAh点整' : prefix + 'dddAh点mm'; }, lastWeek : function () { var startOfWeek, prefix; startOfWeek = moment().startOf('week'); prefix = this.unix() < startOfWeek.unix() ? '[上]' : '[本]'; return this.minutes() === 0 ? prefix + 'dddAh点整' : prefix + 'dddAh点mm'; }, sameElse : 'LL' }, ordinalParse: /\d{1,2}(日|月|周)/, ordinal : function (number, period) { switch (period) { case 'd': case 'D': case 'DDD': return number + '日'; case 'M': return number + '月'; case 'w': case 'W': return number + '周'; default: return number; } }, relativeTime : { future : '%s内', past : '%s前', s : '几秒', m : '1分钟', mm : '%d分钟', h : '1小时', hh : '%d小时', d : '1天', dd : '%d天', M : '1个月', MM : '%d个月', y : '1年', yy : '%d年' }, week : { // GB/T 7408-1994《数据元和交换格式·信息交换·日期和时间表示法》与ISO 8601:1988等效 dow : 1, // Monday is the first day of the week. doy : 4 // The week that contains Jan 4th is the first week of the year. } }); })); // Underscore.js 1.8.2 // http://underscorejs.org // (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors // Underscore may be freely distributed under the MIT license. (function() { // Baseline setup // -------------- // Establish the root object, `window` in the browser, or `exports` on the server. var root = this; // Save the previous value of the `_` variable. var previousUnderscore = root._; // Save bytes in the minified (but not gzipped) version: var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; // Create quick reference variables for speed access to core prototypes. var push = ArrayProto.push, slice = ArrayProto.slice, toString = ObjProto.toString, hasOwnProperty = ObjProto.hasOwnProperty; // All **ECMAScript 5** native function implementations that we hope to use // are declared here. var nativeIsArray = Array.isArray, nativeKeys = Object.keys, nativeBind = FuncProto.bind, nativeCreate = Object.create; // Naked function reference for surrogate-prototype-swapping. var Ctor = function(){}; // Create a safe reference to the Underscore object for use below. var _ = function(obj) { if (obj instanceof _) return obj; if (!(this instanceof _)) return new _(obj); this._wrapped = obj; }; // Export the Underscore object for **Node.js**, with // backwards-compatibility for the old `require()` API. If we're in // the browser, add `_` as a global object. if (typeof exports !== 'undefined') { if (typeof module !== 'undefined' && module.exports) { exports = module.exports = _; } exports._ = _; } else { root._ = _; } // Current version. _.VERSION = '1.8.2'; // Internal function that returns an efficient (for current engines) version // of the passed-in callback, to be repeatedly applied in other Underscore // functions. var optimizeCb = function(func, context, argCount) { if (context === void 0) return func; switch (argCount == null ? 3 : argCount) { case 1: return function(value) { return func.call(context, value); }; case 2: return function(value, other) { return func.call(context, value, other); }; case 3: return function(value, index, collection) { return func.call(context, value, index, collection); }; case 4: return function(accumulator, value, index, collection) { return func.call(context, accumulator, value, index, collection); }; } return function() { return func.apply(context, arguments); }; }; // A mostly-internal function to generate callbacks that can be applied // to each element in a collection, returning the desired result — either // identity, an arbitrary callback, a property matcher, or a property accessor. var cb = function(value, context, argCount) { if (value == null) return _.identity; if (_.isFunction(value)) return optimizeCb(value, context, argCount); if (_.isObject(value)) return _.matcher(value); return _.property(value); }; _.iteratee = function(value, context) { return cb(value, context, Infinity); }; // An internal function for creating assigner functions. var createAssigner = function(keysFunc, undefinedOnly) { return function(obj) { var length = arguments.length; if (length < 2 || obj == null) return obj; for (var index = 1; index < length; index++) { var source = arguments[index], keys = keysFunc(source), l = keys.length; for (var i = 0; i < l; i++) { var key = keys[i]; if (!undefinedOnly || obj[key] === void 0) obj[key] = source[key]; } } return obj; }; }; // An internal function for creating a new object that inherits from another. var baseCreate = function(prototype) { if (!_.isObject(prototype)) return {}; if (nativeCreate) return nativeCreate(prototype); Ctor.prototype = prototype; var result = new Ctor; Ctor.prototype = null; return result; }; // Helper for collection methods to determine whether a collection // should be iterated as an array or as an object // Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1; var isArrayLike = function(collection) { var length = collection && collection.length; return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX; }; // Collection Functions // -------------------- // The cornerstone, an `each` implementation, aka `forEach`. // Handles raw objects in addition to array-likes. Treats all // sparse array-likes as if they were dense. _.each = _.forEach = function(obj, iteratee, context) { iteratee = optimizeCb(iteratee, context); var i, length; if (isArrayLike(obj)) { for (i = 0, length = obj.length; i < length; i++) { iteratee(obj[i], i, obj); } } else { var keys = _.keys(obj); for (i = 0, length = keys.length; i < length; i++) { iteratee(obj[keys[i]], keys[i], obj); } } return obj; }; // Return the results of applying the iteratee to each element. _.map = _.collect = function(obj, iteratee, context) { iteratee = cb(iteratee, context); var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length, results = Array(length); for (var index = 0; index < length; index++) { var currentKey = keys ? keys[index] : index; results[index] = iteratee(obj[currentKey], currentKey, obj); } return results; }; // Create a reducing function iterating left or right. function createReduce(dir) { // Optimized iterator function as using arguments.length // in the main function will deoptimize the, see #1991. function iterator(obj, iteratee, memo, keys, index, length) { for (; index >= 0 && index < length; index += dir) { var currentKey = keys ? keys[index] : index; memo = iteratee(memo, obj[currentKey], currentKey, obj); } return memo; } return function(obj, iteratee, memo, context) { iteratee = optimizeCb(iteratee, context, 4); var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length, index = dir > 0 ? 0 : length - 1; // Determine the initial value if none is provided. if (arguments.length < 3) { memo = obj[keys ? keys[index] : index]; index += dir; } return iterator(obj, iteratee, memo, keys, index, length); }; } // **Reduce** builds up a single result from a list of values, aka `inject`, // or `foldl`. _.reduce = _.foldl = _.inject = createReduce(1); // The right-associative version of reduce, also known as `foldr`. _.reduceRight = _.foldr = createReduce(-1); // Return the first value which passes a truth test. Aliased as `detect`. _.find = _.detect = function(obj, predicate, context) { var key; if (isArrayLike(obj)) { key = _.findIndex(obj, predicate, context); } else { key = _.findKey(obj, predicate, context); } if (key !== void 0 && key !== -1) return obj[key]; }; // Return all the elements that pass a truth test. // Aliased as `select`. _.filter = _.select = function(obj, predicate, context) { var results = []; predicate = cb(predicate, context); _.each(obj, function(value, index, list) { if (predicate(value, index, list)) results.push(value); }); return results; }; // Return all the elements for which a truth test fails. _.reject = function(obj, predicate, context) { return _.filter(obj, _.negate(cb(predicate)), context); }; // Determine whether all of the elements match a truth test. // Aliased as `all`. _.every = _.all = function(obj, predicate, context) { predicate = cb(predicate, context); var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length; for (var index = 0; index < length; index++) { var currentKey = keys ? keys[index] : index; if (!predicate(obj[currentKey], currentKey, obj)) return false; } return true; }; // Determine if at least one element in the object matches a truth test. // Aliased as `any`. _.some = _.any = function(obj, predicate, context) { predicate = cb(predicate, context); var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length; for (var index = 0; index < length; index++) { var currentKey = keys ? keys[index] : index; if (predicate(obj[currentKey], currentKey, obj)) return true; } return false; }; // Determine if the array or object contains a given value (using `===`). // Aliased as `includes` and `include`. _.contains = _.includes = _.include = function(obj, target, fromIndex) { if (!isArrayLike(obj)) obj = _.values(obj); return _.indexOf(obj, target, typeof fromIndex == 'number' && fromIndex) >= 0; }; // Invoke a method (with arguments) on every item in a collection. _.invoke = function(obj, method) { var args = slice.call(arguments, 2); var isFunc = _.isFunction(method); return _.map(obj, function(value) { var func = isFunc ? method : value[method]; return func == null ? func : func.apply(value, args); }); }; // Convenience version of a common use case of `map`: fetching a property. _.pluck = function(obj, key) { return _.map(obj, _.property(key)); }; // Convenience version of a common use case of `filter`: selecting only objects // containing specific `key:value` pairs. _.where = function(obj, attrs) { return _.filter(obj, _.matcher(attrs)); }; // Convenience version of a common use case of `find`: getting the first object // containing specific `key:value` pairs. _.findWhere = function(obj, attrs) { return _.find(obj, _.matcher(attrs)); }; // Return the maximum element (or element-based computation). _.max = function(obj, iteratee, context) { var result = -Infinity, lastComputed = -Infinity, value, computed; if (iteratee == null && obj != null) { obj = isArrayLike(obj) ? obj : _.values(obj); for (var i = 0, length = obj.length; i < length; i++) { value = obj[i]; if (value > result) { result = value; } } } else { iteratee = cb(iteratee, context); _.each(obj, function(value, index, list) { computed = iteratee(value, index, list); if (computed > lastComputed || computed === -Infinity && result === -Infinity) { result = value; lastComputed = computed; } }); } return result; }; // Return the minimum element (or element-based computation). _.min = function(obj, iteratee, context) { var result = Infinity, lastComputed = Infinity, value, computed; if (iteratee == null && obj != null) { obj = isArrayLike(obj) ? obj : _.values(obj); for (var i = 0, length = obj.length; i < length; i++) { value = obj[i]; if (value < result) { result = value; } } } else { iteratee = cb(iteratee, context); _.each(obj, function(value, index, list) { computed = iteratee(value, index, list); if (computed < lastComputed || computed === Infinity && result === Infinity) { result = value; lastComputed = computed; } }); } return result; }; // Shuffle a collection, using the modern version of the // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle). _.shuffle = function(obj) { var set = isArrayLike(obj) ? obj : _.values(obj); var length = set.length; var shuffled = Array(length); for (var index = 0, rand; index < length; index++) { rand = _.random(0, index); if (rand !== index) shuffled[index] = shuffled[rand]; shuffled[rand] = set[index]; } return shuffled; }; // Sample **n** random values from a collection. // If **n** is not specified, returns a single random element. // The internal `guard` argument allows it to work with `map`. _.sample = function(obj, n, guard) { if (n == null || guard) { if (!isArrayLike(obj)) obj = _.values(obj); return obj[_.random(obj.length - 1)]; } return _.shuffle(obj).slice(0, Math.max(0, n)); }; // Sort the object's values by a criterion produced by an iteratee. _.sortBy = function(obj, iteratee, context) { iteratee = cb(iteratee, context); return _.pluck(_.map(obj, function(value, index, list) { return { value: value, index: index, criteria: iteratee(value, index, list) }; }).sort(function(left, right) { var a = left.criteria; var b = right.criteria; if (a !== b) { if (a > b || a === void 0) return 1; if (a < b || b === void 0) return -1; } return left.index - right.index; }), 'value'); }; // An internal function used for aggregate "group by" operations. var group = function(behavior) { return function(obj, iteratee, context) { var result = {}; iteratee = cb(iteratee, context); _.each(obj, function(value, index) { var key = iteratee(value, index, obj); behavior(result, value, key); }); return result; }; }; // Groups the object's values by a criterion. Pass either a string attribute // to group by, or a function that returns the criterion. _.groupBy = group(function(result, value, key) { if (_.has(result, key)) result[key].push(value); else result[key] = [value]; }); // Indexes the object's values by a criterion, similar to `groupBy`, but for // when you know that your index values will be unique. _.indexBy = group(function(result, value, key) { result[key] = value; }); // Counts instances of an object that group by a certain criterion. Pass // either a string attribute to count by, or a function that returns the // criterion. _.countBy = group(function(result, value, key) { if (_.has(result, key)) result[key]++; else result[key] = 1; }); // Safely create a real, live array from anything iterable. _.toArray = function(obj) { if (!obj) return []; if (_.isArray(obj)) return slice.call(obj); if (isArrayLike(obj)) return _.map(obj, _.identity); return _.values(obj); }; // Return the number of elements in an object. _.size = function(obj) { if (obj == null) return 0; return isArrayLike(obj) ? obj.length : _.keys(obj).length; }; // Split a collection into two arrays: one whose elements all satisfy the given // predicate, and one whose elements all do not satisfy the predicate. _.partition = function(obj, predicate, context) { predicate = cb(predicate, context); var pass = [], fail = []; _.each(obj, function(value, key, obj) { (predicate(value, key, obj) ? pass : fail).push(value); }); return [pass, fail]; }; // Array Functions // --------------- // Get the first element of an array. Passing **n** will return the first N // values in the array. Aliased as `head` and `take`. The **guard** check // allows it to work with `_.map`. _.first = _.head = _.take = function(array, n, guard) { if (array == null) return void 0; if (n == null || guard) return array[0]; return _.initial(array, array.length - n); }; // Returns everything but the last entry of the array. Especially useful on // the arguments object. Passing **n** will return all the values in // the array, excluding the last N. _.initial = function(array, n, guard) { return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n))); }; // Get the last element of an array. Passing **n** will return the last N // values in the array. _.last = function(array, n, guard) { if (array == null) return void 0; if (n == null || guard) return array[array.length - 1]; return _.rest(array, Math.max(0, array.length - n)); }; // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. // Especially useful on the arguments object. Passing an **n** will return // the rest N values in the array. _.rest = _.tail = _.drop = function(array, n, guard) { return slice.call(array, n == null || guard ? 1 : n); }; // Trim out all falsy values from an array. _.compact = function(array) { return _.filter(array, _.identity); }; // Internal implementation of a recursive `flatten` function. var flatten = function(input, shallow, strict, startIndex) { var output = [], idx = 0; for (var i = startIndex || 0, length = input && input.length; i < length; i++) { var value = input[i]; if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) { //flatten current level of array or arguments object if (!shallow) value = flatten(value, shallow, strict); var j = 0, len = value.length; output.length += len; while (j < len) { output[idx++] = value[j++]; } } else if (!strict) { output[idx++] = value; } } return output; }; // Flatten out an array, either recursively (by default), or just one level. _.flatten = function(array, shallow) { return flatten(array, shallow, false); }; // Return a version of the array that does not contain the specified value(s). _.without = function(array) { return _.difference(array, slice.call(arguments, 1)); }; // Produce a duplicate-free version of the array. If the array has already // been sorted, you have the option of using a faster algorithm. // Aliased as `unique`. _.uniq = _.unique = function(array, isSorted, iteratee, context) { if (array == null) return []; if (!_.isBoolean(isSorted)) { context = iteratee; iteratee = isSorted; isSorted = false; } if (iteratee != null) iteratee = cb(iteratee, context); var result = []; var seen = []; for (var i = 0, length = array.length; i < length; i++) { var value = array[i], computed = iteratee ? iteratee(value, i, array) : value; if (isSorted) { if (!i || seen !== computed) result.push(value); seen = computed; } else if (iteratee) { if (!_.contains(seen, computed)) { seen.push(computed); result.push(value); } } else if (!_.contains(result, value)) { result.push(value); } } return result; }; // Produce an array that contains the union: each distinct element from all of // the passed-in arrays. _.union = function() { return _.uniq(flatten(arguments, true, true)); }; // Produce an array that contains every item shared between all the // passed-in arrays. _.intersection = function(array) { if (array == null) return []; var result = []; var argsLength = arguments.length; for (var i = 0, length = array.length; i < length; i++) { var item = array[i]; if (_.contains(result, item)) continue; for (var j = 1; j < argsLength; j++) { if (!_.contains(arguments[j], item)) break; } if (j === argsLength) result.push(item); } return result; }; // Take the difference between one array and a number of other arrays. // Only the elements present in just the first array will remain. _.difference = function(array) { var rest = flatten(arguments, true, true, 1); return _.filter(array, function(value){ return !_.contains(rest, value); }); }; // Zip together multiple lists into a single array -- elements that share // an index go together. _.zip = function() { return _.unzip(arguments); }; // Complement of _.zip. Unzip accepts an array of arrays and groups // each array's elements on shared indices _.unzip = function(array) { var length = array && _.max(array, 'length').length || 0; var result = Array(length); for (var index = 0; index < length; index++) { result[index] = _.pluck(array, index); } return result; }; // Converts lists into objects. Pass either a single array of `[key, value]` // pairs, or two parallel arrays of the same length -- one of keys, and one of // the corresponding values. _.object = function(list, values) { var result = {}; for (var i = 0, length = list && list.length; i < length; i++) { if (values) { result[list[i]] = values[i]; } else { result[list[i][0]] = list[i][1]; } } return result; }; // Return the position of the first occurrence of an item in an array, // or -1 if the item is not included in the array. // If the array is large and already in sort order, pass `true` // for **isSorted** to use binary search. _.indexOf = function(array, item, isSorted) { var i = 0, length = array && array.length; if (typeof isSorted == 'number') { i = isSorted < 0 ? Math.max(0, length + isSorted) : isSorted; } else if (isSorted && length) { i = _.sortedIndex(array, item); return array[i] === item ? i : -1; } if (item !== item) { return _.findIndex(slice.call(array, i), _.isNaN); } for (; i < length; i++) if (array[i] === item) return i; return -1; }; _.lastIndexOf = function(array, item, from) { var idx = array ? array.length : 0; if (typeof from == 'number') { idx = from < 0 ? idx + from + 1 : Math.min(idx, from + 1); } if (item !== item) { return _.findLastIndex(slice.call(array, 0, idx), _.isNaN); } while (--idx >= 0) if (array[idx] === item) return idx; return -1; }; // Generator function to create the findIndex and findLastIndex functions function createIndexFinder(dir) { return function(array, predicate, context) { predicate = cb(predicate, context); var length = array != null && array.length; var index = dir > 0 ? 0 : length - 1; for (; index >= 0 && index < length; index += dir) { if (predicate(array[index], index, array)) return index; } return -1; }; } // Returns the first index on an array-like that passes a predicate test _.findIndex = createIndexFinder(1); _.findLastIndex = createIndexFinder(-1); // Use a comparator function to figure out the smallest index at which // an object should be inserted so as to maintain order. Uses binary search. _.sortedIndex = function(array, obj, iteratee, context) { iteratee = cb(iteratee, context, 1); var value = iteratee(obj); var low = 0, high = array.length; while (low < high) { var mid = Math.floor((low + high) / 2); if (iteratee(array[mid]) < value) low = mid + 1; else high = mid; } return low; }; // Generate an integer Array containing an arithmetic progression. A port of // the native Python `range()` function. See // [the Python documentation](http://docs.python.org/library/functions.html#range). _.range = function(start, stop, step) { if (arguments.length <= 1) { stop = start || 0; start = 0; } step = step || 1; var length = Math.max(Math.ceil((stop - start) / step), 0); var range = Array(length); for (var idx = 0; idx < length; idx++, start += step) { range[idx] = start; } return range; }; // Function (ahem) Functions // ------------------ // Determines whether to execute a function as a constructor // or a normal function with the provided arguments var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) { if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); var self = baseCreate(sourceFunc.prototype); var result = sourceFunc.apply(self, args); if (_.isObject(result)) return result; return self; }; // Create a function bound to a given object (assigning `this`, and arguments, // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if // available. _.bind = function(func, context) { if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function'); var args = slice.call(arguments, 2); var bound = function() { return executeBound(func, bound, context, this, args.concat(slice.call(arguments))); }; return bound; }; // Partially apply a function by creating a version that has had some of its // arguments pre-filled, without changing its dynamic `this` context. _ acts // as a placeholder, allowing any combination of arguments to be pre-filled. _.partial = function(func) { var boundArgs = slice.call(arguments, 1); var bound = function() { var position = 0, length = boundArgs.length; var args = Array(length); for (var i = 0; i < length; i++) { args[i] = boundArgs[i] === _ ? arguments[position++] : boundArgs[i]; } while (position < arguments.length) args.push(arguments[position++]); return executeBound(func, bound, this, this, args); }; return bound; }; // Bind a number of an object's methods to that object. Remaining arguments // are the method names to be bound. Useful for ensuring that all callbacks // defined on an object belong to it. _.bindAll = function(obj) { var i, length = arguments.length, key; if (length <= 1) throw new Error('bindAll must be passed function names'); for (i = 1; i < length; i++) { key = arguments[i]; obj[key] = _.bind(obj[key], obj); } return obj; }; // Memoize an expensive function by storing its results. _.memoize = function(func, hasher) { var memoize = function(key) { var cache = memoize.cache; var address = '' + (hasher ? hasher.apply(this, arguments) : key); if (!_.has(cache, address)) cache[address] = func.apply(this, arguments); return cache[address]; }; memoize.cache = {}; return memoize; }; // Delays a function for the given number of milliseconds, and then calls // it with the arguments supplied. _.delay = function(func, wait) { var args = slice.call(arguments, 2); return setTimeout(function(){ return func.apply(null, args); }, wait); }; // Defers a function, scheduling it to run after the current call stack has // cleared. _.defer = _.partial(_.delay, _, 1); // Returns a function, that, when invoked, will only be triggered at most once // during a given window of time. Normally, the throttled function will run // as much as it can, without ever going more than once per `wait` duration; // but if you'd like to disable the execution on the leading edge, pass // `{leading: false}`. To disable execution on the trailing edge, ditto. _.throttle = function(func, wait, options) { var context, args, result; var timeout = null; var previous = 0; if (!options) options = {}; var later = function() { previous = options.leading === false ? 0 : _.now(); timeout = null; result = func.apply(context, args); if (!timeout) context = args = null; }; return function() { var now = _.now(); if (!previous && options.leading === false) previous = now; var remaining = wait - (now - previous); context = this; args = arguments; if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; result = func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining); } return result; }; }; // Returns a function, that, as long as it continues to be invoked, will not // be triggered. The function will be called after it stops being called for // N milliseconds. If `immediate` is passed, trigger the function on the // leading edge, instead of the trailing. _.debounce = function(func, wait, immediate) { var timeout, args, context, timestamp, result; var later = function() { var last = _.now() - timestamp; if (last < wait && last >= 0) { timeout = setTimeout(later, wait - last); } else { timeout = null; if (!immediate) { result = func.apply(context, args); if (!timeout) context = args = null; } } }; return function() { context = this; args = arguments; timestamp = _.now(); var callNow = immediate && !timeout; if (!timeout) timeout = setTimeout(later, wait); if (callNow) { result = func.apply(context, args); context = args = null; } return result; }; }; // Returns the first function passed as an argument to the second, // allowing you to adjust arguments, run code before and after, and // conditionally execute the original function. _.wrap = function(func, wrapper) { return _.partial(wrapper, func); }; // Returns a negated version of the passed-in predicate. _.negate = function(predicate) { return function() { return !predicate.apply(this, arguments); }; }; // Returns a function that is the composition of a list of functions, each // consuming the return value of the function that follows. _.compose = function() { var args = arguments; var start = args.length - 1; return function() { var i = start; var result = args[start].apply(this, arguments); while (i--) result = args[i].call(this, result); return result; }; }; // Returns a function that will only be executed on and after the Nth call. _.after = function(times, func) { return function() { if (--times < 1) { return func.apply(this, arguments); } }; }; // Returns a function that will only be executed up to (but not including) the Nth call. _.before = function(times, func) { var memo; return function() { if (--times > 0) { memo = func.apply(this, arguments); } if (times <= 1) func = null; return memo; }; }; // Returns a function that will be executed at most one time, no matter how // often you call it. Useful for lazy initialization. _.once = _.partial(_.before, 2); // Object Functions // ---------------- // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed. var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString'); var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString', 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString']; function collectNonEnumProps(obj, keys) { var nonEnumIdx = nonEnumerableProps.length; var constructor = obj.constructor; var proto = (_.isFunction(constructor) && constructor.prototype) || ObjProto; // Constructor is a special case. var prop = 'constructor'; if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop); while (nonEnumIdx--) { prop = nonEnumerableProps[nonEnumIdx]; if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) { keys.push(prop); } } } // Retrieve the names of an object's own properties. // Delegates to **ECMAScript 5**'s native `Object.keys` _.keys = function(obj) { if (!_.isObject(obj)) return []; if (nativeKeys) return nativeKeys(obj); var keys = []; for (var key in obj) if (_.has(obj, key)) keys.push(key); // Ahem, IE < 9. if (hasEnumBug) collectNonEnumProps(obj, keys); return keys; }; // Retrieve all the property names of an object. _.allKeys = function(obj) { if (!_.isObject(obj)) return []; var keys = []; for (var key in obj) keys.push(key); // Ahem, IE < 9. if (hasEnumBug) collectNonEnumProps(obj, keys); return keys; }; // Retrieve the values of an object's properties. _.values = function(obj) { var keys = _.keys(obj); var length = keys.length; var values = Array(length); for (var i = 0; i < length; i++) { values[i] = obj[keys[i]]; } return values; }; // Returns the results of applying the iteratee to each element of the object // In contrast to _.map it returns an object _.mapObject = function(obj, iteratee, context) { iteratee = cb(iteratee, context); var keys = _.keys(obj), length = keys.length, results = {}, currentKey; for (var index = 0; index < length; index++) { currentKey = keys[index]; results[currentKey] = iteratee(obj[currentKey], currentKey, obj); } return results; }; // Convert an object into a list of `[key, value]` pairs. _.pairs = function(obj) { var keys = _.keys(obj); var length = keys.length; var pairs = Array(length); for (var i = 0; i < length; i++) { pairs[i] = [keys[i], obj[keys[i]]]; } return pairs; }; // Invert the keys and values of an object. The values must be serializable. _.invert = function(obj) { var result = {}; var keys = _.keys(obj); for (var i = 0, length = keys.length; i < length; i++) { result[obj[keys[i]]] = keys[i]; } return result; }; // Return a sorted list of the function names available on the object. // Aliased as `methods` _.functions = _.methods = function(obj) { var names = []; for (var key in obj) { if (_.isFunction(obj[key])) names.push(key); } return names.sort(); }; // Extend a given object with all the properties in passed-in object(s). _.extend = createAssigner(_.allKeys); // Assigns a given object with all the own properties in the passed-in object(s) // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) _.extendOwn = _.assign = createAssigner(_.keys); // Returns the first key on an object that passes a predicate test _.findKey = function(obj, predicate, context) { predicate = cb(predicate, context); var keys = _.keys(obj), key; for (var i = 0, length = keys.length; i < length; i++) { key = keys[i]; if (predicate(obj[key], key, obj)) return key; } }; // Return a copy of the object only containing the whitelisted properties. _.pick = function(object, oiteratee, context) { var result = {}, obj = object, iteratee, keys; if (obj == null) return result; if (_.isFunction(oiteratee)) { keys = _.allKeys(obj); iteratee = optimizeCb(oiteratee, context); } else { keys = flatten(arguments, false, false, 1); iteratee = function(value, key, obj) { return key in obj; }; obj = Object(obj); } for (var i = 0, length = keys.length; i < length; i++) { var key = keys[i]; var value = obj[key]; if (iteratee(value, key, obj)) result[key] = value; } return result; }; // Return a copy of the object without the blacklisted properties. _.omit = function(obj, iteratee, context) { if (_.isFunction(iteratee)) { iteratee = _.negate(iteratee); } else { var keys = _.map(flatten(arguments, false, false, 1), String); iteratee = function(value, key) { return !_.contains(keys, key); }; } return _.pick(obj, iteratee, context); }; // Fill in a given object with default properties. _.defaults = createAssigner(_.allKeys, true); // Create a (shallow-cloned) duplicate of an object. _.clone = function(obj) { if (!_.isObject(obj)) return obj; return _.isArray(obj) ? obj.slice() : _.extend({}, obj); }; // Invokes interceptor with the obj, and then returns obj. // The primary purpose of this method is to "tap into" a method chain, in // order to perform operations on intermediate results within the chain. _.tap = function(obj, interceptor) { interceptor(obj); return obj; }; // Returns whether an object has a given set of `key:value` pairs. _.isMatch = function(object, attrs) { var keys = _.keys(attrs), length = keys.length; if (object == null) return !length; var obj = Object(object); for (var i = 0; i < length; i++) { var key = keys[i]; if (attrs[key] !== obj[key] || !(key in obj)) return false; } return true; }; // Internal recursive comparison function for `isEqual`. var eq = function(a, b, aStack, bStack) { // Identical objects are equal. `0 === -0`, but they aren't identical. // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). if (a === b) return a !== 0 || 1 / a === 1 / b; // A strict comparison is necessary because `null == undefined`. if (a == null || b == null) return a === b; // Unwrap any wrapped objects. if (a instanceof _) a = a._wrapped; if (b instanceof _) b = b._wrapped; // Compare `[[Class]]` names. var className = toString.call(a); if (className !== toString.call(b)) return false; switch (className) { // Strings, numbers, regular expressions, dates, and booleans are compared by value. case '[object RegExp]': // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i') case '[object String]': // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is // equivalent to `new String("5")`. return '' + a === '' + b; case '[object Number]': // `NaN`s are equivalent, but non-reflexive. // Object(NaN) is equivalent to NaN if (+a !== +a) return +b !== +b; // An `egal` comparison is performed for other numeric values. return +a === 0 ? 1 / +a === 1 / b : +a === +b; case '[object Date]': case '[object Boolean]': // Coerce dates and booleans to numeric primitive values. Dates are compared by their // millisecond representations. Note that invalid dates with millisecond representations // of `NaN` are not equivalent. return +a === +b; } var areArrays = className === '[object Array]'; if (!areArrays) { if (typeof a != 'object' || typeof b != 'object') return false; // Objects with different constructors are not equivalent, but `Object`s or `Array`s // from different frames are. var aCtor = a.constructor, bCtor = b.constructor; if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor && _.isFunction(bCtor) && bCtor instanceof bCtor) && ('constructor' in a && 'constructor' in b)) { return false; } } // Assume equality for cyclic structures. The algorithm for detecting cyclic // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. // Initializing stack of traversed objects. // It's done here since we only need them for objects and arrays comparison. aStack = aStack || []; bStack = bStack || []; var length = aStack.length; while (length--) { // Linear search. Performance is inversely proportional to the number of // unique nested structures. if (aStack[length] === a) return bStack[length] === b; } // Add the first object to the stack of traversed objects. aStack.push(a); bStack.push(b); // Recursively compare objects and arrays. if (areArrays) { // Compare array lengths to determine if a deep comparison is necessary. length = a.length; if (length !== b.length) return false; // Deep compare the contents, ignoring non-numeric properties. while (length--) { if (!eq(a[length], b[length], aStack, bStack)) return false; } } else { // Deep compare objects. var keys = _.keys(a), key; length = keys.length; // Ensure that both objects contain the same number of properties before comparing deep equality. if (_.keys(b).length !== length) return false; while (length--) { // Deep compare each member key = keys[length]; if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false; } } // Remove the first object from the stack of traversed objects. aStack.pop(); bStack.pop(); return true; }; // Perform a deep comparison to check if two objects are equal. _.isEqual = function(a, b) { return eq(a, b); }; // Is a given array, string, or object empty? // An "empty" object has no enumerable own-properties. _.isEmpty = function(obj) { if (obj == null) return true; if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0; return _.keys(obj).length === 0; }; // Is a given value a DOM element? _.isElement = function(obj) { return !!(obj && obj.nodeType === 1); }; // Is a given value an array? // Delegates to ECMA5's native Array.isArray _.isArray = nativeIsArray || function(obj) { return toString.call(obj) === '[object Array]'; }; // Is a given variable an object? _.isObject = function(obj) { var type = typeof obj; return type === 'function' || type === 'object' && !!obj; }; // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError. _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error'], function(name) { _['is' + name] = function(obj) { return toString.call(obj) === '[object ' + name + ']'; }; }); // Define a fallback version of the method in browsers (ahem, IE < 9), where // there isn't any inspectable "Arguments" type. if (!_.isArguments(arguments)) { _.isArguments = function(obj) { return _.has(obj, 'callee'); }; } // Optimize `isFunction` if appropriate. Work around some typeof bugs in old v8, // IE 11 (#1621), and in Safari 8 (#1929). if (typeof /./ != 'function' && typeof Int8Array != 'object') { _.isFunction = function(obj) { return typeof obj == 'function' || false; }; } // Is a given object a finite number? _.isFinite = function(obj) { return isFinite(obj) && !isNaN(parseFloat(obj)); }; // Is the given value `NaN`? (NaN is the only number which does not equal itself). _.isNaN = function(obj) { return _.isNumber(obj) && obj !== +obj; }; // Is a given value a boolean? _.isBoolean = function(obj) { return obj === true || obj === false || toString.call(obj) === '[object Boolean]'; }; // Is a given value equal to null? _.isNull = function(obj) { return obj === null; }; // Is a given variable undefined? _.isUndefined = function(obj) { return obj === void 0; }; // Shortcut function for checking if an object has a given property directly // on itself (in other words, not on a prototype). _.has = function(obj, key) { return obj != null && hasOwnProperty.call(obj, key); }; // Utility Functions // ----------------- // Run Underscore.js in *noConflict* mode, returning the `_` variable to its // previous owner. Returns a reference to the Underscore object. _.noConflict = function() { root._ = previousUnderscore; return this; }; // Keep the identity function around for default iteratees. _.identity = function(value) { return value; }; // Predicate-generating functions. Often useful outside of Underscore. _.constant = function(value) { return function() { return value; }; }; _.noop = function(){}; _.property = function(key) { return function(obj) { return obj == null ? void 0 : obj[key]; }; }; // Generates a function for a given object that returns a given property. _.propertyOf = function(obj) { return obj == null ? function(){} : function(key) { return obj[key]; }; }; // Returns a predicate for checking whether an object has a given set of // `key:value` pairs. _.matcher = _.matches = function(attrs) { attrs = _.extendOwn({}, attrs); return function(obj) { return _.isMatch(obj, attrs); }; }; // Run a function **n** times. _.times = function(n, iteratee, context) { var accum = Array(Math.max(0, n)); iteratee = optimizeCb(iteratee, context, 1); for (var i = 0; i < n; i++) accum[i] = iteratee(i); return accum; }; // Return a random integer between min and max (inclusive). _.random = function(min, max) { if (max == null) { max = min; min = 0; } return min + Math.floor(Math.random() * (max - min + 1)); }; // A (possibly faster) way to get the current timestamp as an integer. _.now = Date.now || function() { return new Date().getTime(); }; // List of HTML entities for escaping. var escapeMap = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '`': '`' }; var unescapeMap = _.invert(escapeMap); // Functions for escaping and unescaping strings to/from HTML interpolation. var createEscaper = function(map) { var escaper = function(match) { return map[match]; }; // Regexes for identifying a key that needs to be escaped var source = '(?:' + _.keys(map).join('|') + ')'; var testRegexp = RegExp(source); var replaceRegexp = RegExp(source, 'g'); return function(string) { string = string == null ? '' : '' + string; return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string; }; }; _.escape = createEscaper(escapeMap); _.unescape = createEscaper(unescapeMap); // If the value of the named `property` is a function then invoke it with the // `object` as context; otherwise, return it. _.result = function(object, property, fallback) { var value = object == null ? void 0 : object[property]; if (value === void 0) { value = fallback; } return _.isFunction(value) ? value.call(object) : value; }; // Generate a unique integer id (unique within the entire client session). // Useful for temporary DOM ids. var idCounter = 0; _.uniqueId = function(prefix) { var id = ++idCounter + ''; return prefix ? prefix + id : id; }; // By default, Underscore uses ERB-style template delimiters, change the // following template settings to use alternative delimiters. _.templateSettings = { evaluate : /<%([\s\S]+?)%>/g, interpolate : /<%=([\s\S]+?)%>/g, escape : /<%-([\s\S]+?)%>/g }; // When customizing `templateSettings`, if you don't want to define an // interpolation, evaluation or escaping regex, we need one that is // guaranteed not to match. var noMatch = /(.)^/; // Certain characters need to be escaped so that they can be put into a // string literal. var escapes = { "'": "'", '\\': '\\', '\r': 'r', '\n': 'n', '\u2028': 'u2028', '\u2029': 'u2029' }; var escaper = /\\|'|\r|\n|\u2028|\u2029/g; var escapeChar = function(match) { return '\\' + escapes[match]; }; // JavaScript micro-templating, similar to John Resig's implementation. // Underscore templating handles arbitrary delimiters, preserves whitespace, // and correctly escapes quotes within interpolated code. // NB: `oldSettings` only exists for backwards compatibility. _.template = function(text, settings, oldSettings) { if (!settings && oldSettings) settings = oldSettings; settings = _.defaults({}, settings, _.templateSettings); // Combine delimiters into one regular expression via alternation. var matcher = RegExp([ (settings.escape || noMatch).source, (settings.interpolate || noMatch).source, (settings.evaluate || noMatch).source ].join('|') + '|$', 'g'); // Compile the template source, escaping string literals appropriately. var index = 0; var source = "__p+='"; text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { source += text.slice(index, offset).replace(escaper, escapeChar); index = offset + match.length; if (escape) { source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; } else if (interpolate) { source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; } else if (evaluate) { source += "';\n" + evaluate + "\n__p+='"; } // Adobe VMs need the match returned to produce the correct offest. return match; }); source += "';\n"; // If a variable is not specified, place data values in local scope. if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; source = "var __t,__p='',__j=Array.prototype.join," + "print=function(){__p+=__j.call(arguments,'');};\n" + source + 'return __p;\n'; try { var render = new Function(settings.variable || 'obj', '_', source); } catch (e) { e.source = source; throw e; } var template = function(data) { return render.call(this, data, _); }; // Provide the compiled source as a convenience for precompilation. var argument = settings.variable || 'obj'; template.source = 'function(' + argument + '){\n' + source + '}'; return template; }; // Add a "chain" function. Start chaining a wrapped Underscore object. _.chain = function(obj) { var instance = _(obj); instance._chain = true; return instance; }; // OOP // --------------- // If Underscore is called as a function, it returns a wrapped object that // can be used OO-style. This wrapper holds altered versions of all the // underscore functions. Wrapped objects may be chained. // Helper function to continue chaining intermediate results. var result = function(instance, obj) { return instance._chain ? _(obj).chain() : obj; }; // Add your own custom functions to the Underscore object. _.mixin = function(obj) { _.each(_.functions(obj), function(name) { var func = _[name] = obj[name]; _.prototype[name] = function() { var args = [this._wrapped]; push.apply(args, arguments); return result(this, func.apply(_, args)); }; }); }; // Add all of the Underscore functions to the wrapper object. _.mixin(_); // Add all mutator Array functions to the wrapper. _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { var method = ArrayProto[name]; _.prototype[name] = function() { var obj = this._wrapped; method.apply(obj, arguments); if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0]; return result(this, obj); }; }); // Add all accessor Array functions to the wrapper. _.each(['concat', 'join', 'slice'], function(name) { var method = ArrayProto[name]; _.prototype[name] = function() { return result(this, method.apply(this._wrapped, arguments)); }; }); // Extracts the result from a wrapped and chained object. _.prototype.value = function() { return this._wrapped; }; // Provide unwrapping proxy for some methods used in engine operations // such as arithmetic and JSON stringification. _.prototype.valueOf = _.prototype.toJSON = _.prototype.value; _.prototype.toString = function() { return '' + this._wrapped; }; // AMD registration happens at the end for compatibility with AMD loaders // that may not enforce next-turn semantics on modules. Even though general // practice for AMD registration is to be anonymous, underscore registers // as a named module because, like jQuery, it is a base library that is // popular enough to be bundled in a third party lib, but not be part of // an AMD load request. Those cases could generate an error when an // anonymous define() is called outside of a loader request. if (typeof define === 'function' && define.amd) { define('underscore', [], function() { return _; }); } }.call(this)); /* =================================================== * bootstrap-transition.js v2.3.2 * http://getbootstrap.com/2.3.2/javascript.html#transitions * =================================================== * Copyright 2013 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ========================================================== */ !function ($) { "use strict"; // jshint ;_; /* CSS TRANSITION SUPPORT (http://www.modernizr.com/) * ======================================================= */ $(function () { $.support.transition = (function () { var transitionEnd = (function () { var el = document.createElement('bootstrap') , transEndEventNames = { 'WebkitTransition' : 'webkitTransitionEnd' , 'MozTransition' : 'transitionend' , 'OTransition' : 'oTransitionEnd otransitionend' , 'transition' : 'transitionend' } , name for (name in transEndEventNames){ if (el.style[name] !== undefined) { return transEndEventNames[name] } } }()) return transitionEnd && { end: transitionEnd } })() }) }(window.jQuery); /* ========================================================== * bootstrap-affix.js v2.3.2 * http://getbootstrap.com/2.3.2/javascript.html#affix * ========================================================== * Copyright 2013 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ========================================================== */ !function ($) { "use strict"; // jshint ;_; /* AFFIX CLASS DEFINITION * ====================== */ var Affix = function (element, options) { this.options = $.extend({}, $.fn.affix.defaults, options) this.$window = $(window) .on('scroll.affix.data-api', $.proxy(this.checkPosition, this)) .on('click.affix.data-api', $.proxy(function () { setTimeout($.proxy(this.checkPosition, this), 1) }, this)) this.$element = $(element) this.checkPosition() } Affix.prototype.checkPosition = function () { if (!this.$element.is(':visible')) return var scrollHeight = $(document).height() , scrollTop = this.$window.scrollTop() , position = this.$element.offset() , offset = this.options.offset , offsetBottom = offset.bottom , offsetTop = offset.top , reset = 'affix affix-top affix-bottom' , affix if (typeof offset != 'object') offsetBottom = offsetTop = offset if (typeof offsetTop == 'function') offsetTop = offset.top() if (typeof offsetBottom == 'function') offsetBottom = offset.bottom() affix = this.unpin != null && (scrollTop + this.unpin <= position.top) ? false : offsetBottom != null && (position.top + this.$element.height() >= scrollHeight - offsetBottom) ? 'bottom' : offsetTop != null && scrollTop <= offsetTop ? 'top' : false if (this.affixed === affix) return this.affixed = affix this.unpin = affix == 'bottom' ? position.top - scrollTop : null this.$element.removeClass(reset).addClass('affix' + (affix ? '-' + affix : '')) } /* AFFIX PLUGIN DEFINITION * ======================= */ var old = $.fn.affix $.fn.affix = function (option) { return this.each(function () { var $this = $(this) , data = $this.data('affix') , options = typeof option == 'object' && option if (!data) $this.data('affix', (data = new Affix(this, options))) if (typeof option == 'string') data[option]() }) } $.fn.affix.Constructor = Affix $.fn.affix.defaults = { offset: 0 } /* AFFIX NO CONFLICT * ================= */ $.fn.affix.noConflict = function () { $.fn.affix = old return this } /* AFFIX DATA-API * ============== */ $(window).on('load', function () { $('[data-spy="affix"]').each(function () { var $spy = $(this) , data = $spy.data() data.offset = data.offset || {} data.offsetBottom && (data.offset.bottom = data.offsetBottom) data.offsetTop && (data.offset.top = data.offsetTop) $spy.affix(data) }) }) }(window.jQuery); /* ========================================================== * bootstrap-alert.js v2.3.2 * http://getbootstrap.com/2.3.2/javascript.html#alerts * ========================================================== * Copyright 2013 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ========================================================== */ !function ($) { "use strict"; // jshint ;_; /* ALERT CLASS DEFINITION * ====================== */ var dismiss = '[data-dismiss="alert"]' , Alert = function (el) { $(el).on('click', dismiss, this.close) } Alert.prototype.close = function (e) { var $this = $(this) , selector = $this.attr('data-target') , $parent if (!selector) { selector = $this.attr('href') selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 } $parent = $(selector) e && e.preventDefault() $parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent()) $parent.trigger(e = $.Event('close')) if (e.isDefaultPrevented()) return $parent.removeClass('in') function removeElement() { $parent .trigger('closed') .remove() } $.support.transition && $parent.hasClass('fade') ? $parent.on($.support.transition.end, removeElement) : removeElement() } /* ALERT PLUGIN DEFINITION * ======================= */ var old = $.fn.alert $.fn.alert = function (option) { return this.each(function () { var $this = $(this) , data = $this.data('alert') if (!data) $this.data('alert', (data = new Alert(this))) if (typeof option == 'string') data[option].call($this) }) } $.fn.alert.Constructor = Alert /* ALERT NO CONFLICT * ================= */ $.fn.alert.noConflict = function () { $.fn.alert = old return this } /* ALERT DATA-API * ============== */ $(document).on('click.alert.data-api', dismiss, Alert.prototype.close) }(window.jQuery); /* ============================================================ * bootstrap-button.js v2.3.2 * http://getbootstrap.com/2.3.2/javascript.html#buttons * ============================================================ * Copyright 2013 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ============================================================ */ !function ($) { "use strict"; // jshint ;_; /* BUTTON PUBLIC CLASS DEFINITION * ============================== */ var Button = function (element, options) { this.$element = $(element) this.options = $.extend({}, $.fn.button.defaults, options) } Button.prototype.setState = function (state) { var d = 'disabled' , $el = this.$element , data = $el.data() , val = $el.is('input') ? 'val' : 'html' state = state + 'Text' data.resetText || $el.data('resetText', $el[val]()) $el[val](data[state] || this.options[state]) // push to event loop to allow forms to submit setTimeout(function () { state == 'loadingText' ? $el.addClass(d).attr(d, d) : $el.removeClass(d).removeAttr(d) }, 0) } Button.prototype.toggle = function () { var $parent = this.$element.closest('[data-toggle="buttons-radio"]') $parent && $parent .find('.active') .removeClass('active') this.$element.toggleClass('active') } /* BUTTON PLUGIN DEFINITION * ======================== */ var old = $.fn.button $.fn.button = function (option) { return this.each(function () { var $this = $(this) , data = $this.data('button') , options = typeof option == 'object' && option if (!data) $this.data('button', (data = new Button(this, options))) if (option == 'toggle') data.toggle() else if (option) data.setState(option) }) } $.fn.button.defaults = { loadingText: 'loading...' } $.fn.button.Constructor = Button /* BUTTON NO CONFLICT * ================== */ $.fn.button.noConflict = function () { $.fn.button = old return this } /* BUTTON DATA-API * =============== */ $(document).on('click.button.data-api', '[data-toggle^=button]', function (e) { var $btn = $(e.target) if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') $btn.button('toggle') }) }(window.jQuery); /* ============================================================= * bootstrap-collapse.js v2.3.2 * http://getbootstrap.com/2.3.2/javascript.html#collapse * ============================================================= * Copyright 2013 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ============================================================ */ !function ($) { "use strict"; // jshint ;_; /* COLLAPSE PUBLIC CLASS DEFINITION * ================================ */ var Collapse = function (element, options) { this.$element = $(element) this.options = $.extend({}, $.fn.collapse.defaults, options) if (this.options.parent) { this.$parent = $(this.options.parent) } this.options.toggle && this.toggle() } Collapse.prototype = { constructor: Collapse , dimension: function () { var hasWidth = this.$element.hasClass('width') return hasWidth ? 'width' : 'height' } , show: function () { var dimension , scroll , actives , hasData if (this.transitioning || this.$element.hasClass('in')) return dimension = this.dimension() scroll = $.camelCase(['scroll', dimension].join('-')) actives = this.$parent && this.$parent.find('> .accordion-group > .in') if (actives && actives.length) { hasData = actives.data('collapse') if (hasData && hasData.transitioning) return actives.collapse('hide') hasData || actives.data('collapse', null) } this.$element[dimension](0) this.transition('addClass', $.Event('show'), 'shown') $.support.transition && this.$element[dimension](this.$element[0][scroll]) } , hide: function () { var dimension if (this.transitioning || !this.$element.hasClass('in')) return dimension = this.dimension() this.reset(this.$element[dimension]()) this.transition('removeClass', $.Event('hide'), 'hidden') this.$element[dimension](0) } , reset: function (size) { var dimension = this.dimension() this.$element .removeClass('collapse') [dimension](size || 'auto') [0].offsetWidth this.$element[size !== null ? 'addClass' : 'removeClass']('collapse') return this } , transition: function (method, startEvent, completeEvent) { var that = this , complete = function () { if (startEvent.type == 'show') that.reset() that.transitioning = 0 that.$element.trigger(completeEvent) } this.$element.trigger(startEvent) if (startEvent.isDefaultPrevented()) return this.transitioning = 1 this.$element[method]('in') $.support.transition && this.$element.hasClass('collapse') ? this.$element.one($.support.transition.end, complete) : complete() } , toggle: function () { this[this.$element.hasClass('in') ? 'hide' : 'show']() } } /* COLLAPSE PLUGIN DEFINITION * ========================== */ var old = $.fn.collapse $.fn.collapse = function (option) { return this.each(function () { var $this = $(this) , data = $this.data('collapse') , options = $.extend({}, $.fn.collapse.defaults, $this.data(), typeof option == 'object' && option) if (!data) $this.data('collapse', (data = new Collapse(this, options))) if (typeof option == 'string') data[option]() }) } $.fn.collapse.defaults = { toggle: true } $.fn.collapse.Constructor = Collapse /* COLLAPSE NO CONFLICT * ==================== */ $.fn.collapse.noConflict = function () { $.fn.collapse = old return this } /* COLLAPSE DATA-API * ================= */ $(document).on('click.collapse.data-api', '[data-toggle=collapse]', function (e) { var $this = $(this), href , target = $this.attr('data-target') || e.preventDefault() || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7 , option = $(target).data('collapse') ? 'toggle' : $this.data() $this[$(target).hasClass('in') ? 'addClass' : 'removeClass']('collapsed') $(target).collapse(option) }) }(window.jQuery); /* ============================================================ * bootstrap-dropdown.js v2.3.2 * http://getbootstrap.com/2.3.2/javascript.html#dropdowns * ============================================================ * Copyright 2013 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ============================================================ */ !function ($) { "use strict"; // jshint ;_; /* DROPDOWN CLASS DEFINITION * ========================= */ var toggle = '[data-toggle=dropdown]' , Dropdown = function (element) { var $el = $(element).on('click.dropdown.data-api', this.toggle) $('html').on('click.dropdown.data-api', function () { $el.parent().removeClass('open') }) } Dropdown.prototype = { constructor: Dropdown , toggle: function (e) { var $this = $(this) , $parent , isActive if ($this.is('.disabled, :disabled')) return $parent = getParent($this) isActive = $parent.hasClass('open') clearMenus() if (!isActive) { if ('ontouchstart' in document.documentElement) { // if mobile we we use a backdrop because click events don't delegate $(''; }; _.el.addClass('has-' + name + 's').append(html).find('.' + name).click(function() { var me = $(this); me.hasClass('dot') ? _.stop().to(me.index()) : me.hasClass('prev') ? _.prev() : _.next(); }); }; }; // Create a jQuery plugin $.fn.unslider = function(o) { var len = this.length; // Enable multiple-slider support return this.each(function(index) { // Cache a copy of $(this), so it var me = $(this), key = 'unslider' + (len > 1 ? '-' + ++index : ''), instance = (new Unslider).init(me, o); // Invoke an Unslider instance me.data(key, instance).data('key', key); }); }; Unslider.version = "1.0.0"; })(jQuery, false); /*! SWFObject v2.3.20130521 is released under the MIT License */ /* global ActiveXObject: false */ var swfobject = function () { var UNDEF = "undefined", OBJECT = "object", SHOCKWAVE_FLASH = "Shockwave Flash", SHOCKWAVE_FLASH_AX = "ShockwaveFlash.ShockwaveFlash", FLASH_MIME_TYPE = "application/x-shockwave-flash", EXPRESS_INSTALL_ID = "SWFObjectExprInst", ON_READY_STATE_CHANGE = "onreadystatechange", win = window, doc = document, nav = navigator, plugin = false, domLoadFnArr = [], regObjArr = [], objIdArr = [], listenersArr = [], storedFbContent, storedFbContentId, storedCallbackFn, storedCallbackObj, isDomLoaded = false, isExpressInstallActive = false, dynamicStylesheet, dynamicStylesheetMedia, autoHideShow = true, encodeURIEnabled = false, /* Centralized function for browser feature detection - User agent string detection is only used when no good alternative is possible - Is executed directly for optimal performance */ ua = function () { var w3cdom = typeof doc.getElementById !== UNDEF && typeof doc.getElementsByTagName !== UNDEF && typeof doc.createElement !== UNDEF, u = nav.userAgent.toLowerCase(), p = nav.platform.toLowerCase(), windows = p ? /win/.test(p) : /win/.test(u), mac = p ? /mac/.test(p) : /mac/.test(u), webkit = /webkit/.test(u) ? parseFloat(u.replace(/^.*webkit\/(\d+(\.\d+)?).*$/, "$1")) : false, // returns either the webkit version or false if not webkit ie = nav.appName === "Microsoft Internet Explorer", playerVersion = [0, 0, 0], d = null; if (typeof nav.plugins !== UNDEF && typeof nav.plugins[SHOCKWAVE_FLASH] === OBJECT) { d = nav.plugins[SHOCKWAVE_FLASH].description; // nav.mimeTypes["application/x-shockwave-flash"].enabledPlugin indicates whether plug-ins are enabled or disabled in Safari 3+ if (d && (typeof nav.mimeTypes !== UNDEF && nav.mimeTypes[FLASH_MIME_TYPE] && nav.mimeTypes[FLASH_MIME_TYPE].enabledPlugin)) { plugin = true; ie = false; // cascaded feature detection for Internet Explorer d = d.replace(/^.*\s+(\S+\s+\S+$)/, "$1"); playerVersion[0] = toInt(d.replace(/^(.*)\..*$/, "$1")); playerVersion[1] = toInt(d.replace(/^.*\.(.*)\s.*$/, "$1")); playerVersion[2] = /[a-zA-Z]/.test(d) ? toInt(d.replace(/^.*[a-zA-Z]+(.*)$/, "$1")) : 0; } } else if (typeof win.ActiveXObject !== UNDEF) { try { var a = new ActiveXObject(SHOCKWAVE_FLASH_AX); if (a) { // a will return null when ActiveX is disabled d = a.GetVariable("$version"); if (d) { ie = true; // cascaded feature detection for Internet Explorer d = d.split(" ")[1].split(","); playerVersion = [toInt(d[0]), toInt(d[1]), toInt(d[2])]; } } } catch (e) {} } return {w3: w3cdom, pv: playerVersion, wk: webkit, ie: ie, win: windows, mac: mac}; }(), /* Cross-browser onDomLoad - Will fire an event as soon as the DOM of a web page is loaded - Internet Explorer workaround based on Diego Perini's solution: http://javascript.nwbox.com/IEContentLoaded/ - Regular onload serves as fallback */ onDomLoad = function () { if (!ua.w3) { return; } if ((typeof doc.readyState !== UNDEF && (doc.readyState === "complete" || doc.readyState === "interactive")) || (typeof doc.readyState === UNDEF && (doc.getElementsByTagName("body")[0] || doc.body))) { // function is fired after onload, e.g. when script is inserted dynamically callDomLoadFunctions(); } if (!isDomLoaded) { if (typeof doc.addEventListener !== UNDEF) { doc.addEventListener("DOMContentLoaded", callDomLoadFunctions, false); } if (ua.ie) { doc.attachEvent(ON_READY_STATE_CHANGE, function detach() { if (doc.readyState === "complete") { doc.detachEvent(ON_READY_STATE_CHANGE, detach); callDomLoadFunctions(); } }); if (win == top) { // if not inside an iframe (function checkDomLoadedIE() { if (isDomLoaded) { return; } try { doc.documentElement.doScroll("left"); } catch (e) { setTimeout(checkDomLoadedIE, 0); return; } callDomLoadFunctions(); }()); } } if (ua.wk) { (function checkDomLoadedWK() { if (isDomLoaded) { return; } if (!/loaded|complete/.test(doc.readyState)) { setTimeout(checkDomLoadedWK, 0); return; } callDomLoadFunctions(); }()); } } }(); function callDomLoadFunctions() { if (isDomLoaded || !document.getElementsByTagName("body")[0]) { return; } try { // test if we can really add/remove elements to/from the DOM; we don't want to fire it too early var t, span = createElement("span"); span.style.display = "none"; //hide the span in case someone has styled spans via CSS t = doc.getElementsByTagName("body")[0].appendChild(span); t.parentNode.removeChild(t); t = null; //clear the variables span = null; } catch (e) { return; } isDomLoaded = true; var dl = domLoadFnArr.length; for (var i = 0; i < dl; i++) { domLoadFnArr[i](); } } function addDomLoadEvent(fn) { if (isDomLoaded) { fn(); } else { domLoadFnArr[domLoadFnArr.length] = fn; // Array.push() is only available in IE5.5+ } } /* Cross-browser onload - Based on James Edwards' solution: http://brothercake.com/site/resources/scripts/onload/ - Will fire an event as soon as a web page including all of its assets are loaded */ function addLoadEvent(fn) { if (typeof win.addEventListener !== UNDEF) { win.addEventListener("load", fn, false); } else if (typeof doc.addEventListener !== UNDEF) { doc.addEventListener("load", fn, false); } else if (typeof win.attachEvent !== UNDEF) { addListener(win, "onload", fn); } else if (typeof win.onload === "function") { var fnOld = win.onload; win.onload = function () { fnOld(); fn(); }; } else { win.onload = fn; } } /* Detect the Flash Player version for non-Internet Explorer browsers - Detecting the plug-in version via the object element is more precise than using the plugins collection item's description: a. Both release and build numbers can be detected b. Avoid wrong descriptions by corrupt installers provided by Adobe c. Avoid wrong descriptions by multiple Flash Player entries in the plugin Array, caused by incorrect browser imports - Disadvantage of this method is that it depends on the availability of the DOM, while the plugins collection is immediately available */ function testPlayerVersion() { var b = doc.getElementsByTagName("body")[0]; var o = createElement(OBJECT); o.setAttribute("style", "visibility: hidden;"); o.setAttribute("type", FLASH_MIME_TYPE); var t = b.appendChild(o); if (t) { var counter = 0; (function checkGetVariable() { if (typeof t.GetVariable !== UNDEF) { try { var d = t.GetVariable("$version"); if (d) { d = d.split(" ")[1].split(","); ua.pv = [toInt(d[0]), toInt(d[1]), toInt(d[2])]; } } catch (e) { //t.GetVariable("$version") is known to fail in Flash Player 8 on Firefox //If this error is encountered, assume FP8 or lower. Time to upgrade. ua.pv = [8, 0, 0]; } } else if (counter < 10) { counter++; setTimeout(checkGetVariable, 10); return; } b.removeChild(o); t = null; matchVersions(); }()); } else { matchVersions(); } } /* Perform Flash Player and SWF version matching; static publishing only */ function matchVersions() { var rl = regObjArr.length; if (rl > 0) { for (var i = 0; i < rl; i++) { // for each registered object element var id = regObjArr[i].id; var cb = regObjArr[i].callbackFn; var cbObj = {success: false, id: id}; if (ua.pv[0] > 0) { var obj = getElementById(id); if (obj) { if (hasPlayerVersion(regObjArr[i].swfVersion) && !(ua.wk && ua.wk < 312)) { // Flash Player version >= published SWF version: Houston, we have a match! setVisibility(id, true); if (cb) { cbObj.success = true; cbObj.ref = getObjectById(id); cbObj.id = id; cb(cbObj); } } else if (regObjArr[i].expressInstall && canExpressInstall()) { // show the Adobe Express Install dialog if set by the web page author and if supported var att = {}; att.data = regObjArr[i].expressInstall; att.width = obj.getAttribute("width") || "0"; att.height = obj.getAttribute("height") || "0"; if (obj.getAttribute("class")) { att.styleclass = obj.getAttribute("class"); } if (obj.getAttribute("align")) { att.align = obj.getAttribute("align"); } // parse HTML object param element's name-value pairs var par = {}; var p = obj.getElementsByTagName("param"); var pl = p.length; for (var j = 0; j < pl; j++) { if (p[j].getAttribute("name").toLowerCase() !== "movie") { par[p[j].getAttribute("name")] = p[j].getAttribute("value"); } } showExpressInstall(att, par, id, cb); } else { // Flash Player and SWF version mismatch or an older Webkit engine that ignores the HTML object element's nested param elements: display fallback content instead of SWF displayFbContent(obj); if (cb) { cb(cbObj); } } } } else { // if no Flash Player is installed or the fp version cannot be detected we let the HTML object element do its job (either show a SWF or fallback content) setVisibility(id, true); if (cb) { var o = getObjectById(id); // test whether there is an HTML object element or not if (o && typeof o.SetVariable !== UNDEF) { cbObj.success = true; cbObj.ref = o; cbObj.id = o.id; } cb(cbObj); } } } } } /* Main function - Will preferably execute onDomLoad, otherwise onload (as a fallback) */ domLoadFnArr[0] = function () { if (plugin) { testPlayerVersion(); } else { matchVersions(); } }; function getObjectById(objectIdStr) { var r = null, o = getElementById(objectIdStr); if (o && o.nodeName.toUpperCase() === "OBJECT") { //If targeted object is valid Flash file if (typeof o.SetVariable !== UNDEF) { r = o; } else { //If SetVariable is not working on targeted object but a nested object is //available, assume classic nested object markup. Return nested object. //If SetVariable is not working on targeted object and there is no nested object, //return the original object anyway. This is probably new simplified markup. r = o.getElementsByTagName(OBJECT)[0] || o; } } return r; } /* Requirements for Adobe Express Install - only one instance can be active at a time - fp 6.0.65 or higher - Win/Mac OS only - no Webkit engines older than version 312 */ function canExpressInstall() { return !isExpressInstallActive && hasPlayerVersion("6.0.65") && (ua.win || ua.mac) && !(ua.wk && ua.wk < 312); } /* Show the Adobe Express Install dialog - Reference: http://www.adobe.com/cfusion/knowledgebase/index.cfm?id=6a253b75 */ function showExpressInstall(att, par, replaceElemIdStr, callbackFn) { var obj = getElementById(replaceElemIdStr); //Ensure that replaceElemIdStr is really a string and not an element replaceElemIdStr = getId(replaceElemIdStr); isExpressInstallActive = true; storedCallbackFn = callbackFn || null; storedCallbackObj = {success: false, id: replaceElemIdStr}; if (obj) { if (obj.nodeName.toUpperCase() === "OBJECT") { // static publishing storedFbContent = abstractFbContent(obj); storedFbContentId = null; } else { // dynamic publishing storedFbContent = obj; storedFbContentId = replaceElemIdStr; } att.id = EXPRESS_INSTALL_ID; if (typeof att.width === UNDEF || (!/%$/.test(att.width) && toInt(att.width) < 310)) { att.width = "310"; } if (typeof att.height === UNDEF || (!/%$/.test(att.height) && toInt(att.height) < 137)) { att.height = "137"; } var pt = ua.ie ? "ActiveX" : "PlugIn", fv = "MMredirectURL=" + encodeURIComponent(win.location.toString().replace(/&/g, "%26")) + "&MMplayerType=" + pt + "&MMdoctitle=" + encodeURIComponent(doc.title.slice(0, 47) + " - Flash Player Installation"); if (typeof par.flashvars !== UNDEF) { par.flashvars += "&" + fv; } else { par.flashvars = fv; } // IE only: when a SWF is loading (AND: not available in cache) wait for the readyState of the object element to become 4 before removing it, // because you cannot properly cancel a loading SWF file without breaking browser load references, also obj.onreadystatechange doesn't work if (ua.ie && obj.readyState != 4) { var newObj = createElement("div"); replaceElemIdStr += "SWFObjectNew"; newObj.setAttribute("id", replaceElemIdStr); obj.parentNode.insertBefore(newObj, obj); // insert placeholder div that will be replaced by the object element that loads expressinstall.swf obj.style.display = "none"; removeSWF(obj); //removeSWF accepts elements now } createSWF(att, par, replaceElemIdStr); } } /* Functions to abstract and display fallback content */ function displayFbContent(obj) { if (ua.ie && obj.readyState != 4) { // IE only: when a SWF is loading (AND: not available in cache) wait for the readyState of the object element to become 4 before removing it, // because you cannot properly cancel a loading SWF file without breaking browser load references, also obj.onreadystatechange doesn't work obj.style.display = "none"; var el = createElement("div"); obj.parentNode.insertBefore(el, obj); // insert placeholder div that will be replaced by the fallback content el.parentNode.replaceChild(abstractFbContent(obj), el); removeSWF(obj); //removeSWF accepts elements now } else { obj.parentNode.replaceChild(abstractFbContent(obj), obj); } } function abstractFbContent(obj) { var ac = createElement("div"); if (ua.win && ua.ie) { ac.innerHTML = obj.innerHTML; } else { var nestedObj = obj.getElementsByTagName(OBJECT)[0]; if (nestedObj) { var c = nestedObj.childNodes; if (c) { var cl = c.length; for (var i = 0; i < cl; i++) { if (!(c[i].nodeType == 1 && c[i].nodeName === "PARAM") && !(c[i].nodeType == 8)) { ac.appendChild(c[i].cloneNode(true)); } } } } } return ac; } function createIeObject(url, paramStr) { var div = createElement("div"); div.innerHTML = "" + paramStr + ""; return div.firstChild; } /* Cross-browser dynamic SWF creation */ function createSWF(attObj, parObj, id) { var r, el = getElementById(id); id = getId(id); // ensure id is truly an ID and not an element if (ua.wk && ua.wk < 312) { return r; } if (el) { var o = (ua.ie) ? createElement("div") : createElement(OBJECT), attr, attrLower, param; if (typeof attObj.id === UNDEF) { // if no 'id' is defined for the object element, it will inherit the 'id' from the fallback content attObj.id = id; } //Add params for (param in parObj) { //filter out prototype additions from other potential libraries and IE specific param element if (parObj.hasOwnProperty(param) && param.toLowerCase() !== "movie") { createObjParam(o, param, parObj[param]); } } //Create IE object, complete with param nodes if (ua.ie) { o = createIeObject(attObj.data, o.innerHTML); } //Add attributes to object for (attr in attObj) { if (attObj.hasOwnProperty(attr)) { // filter out prototype additions from other potential libraries attrLower = attr.toLowerCase(); // 'class' is an ECMA4 reserved keyword if (attrLower === "styleclass") { o.setAttribute("class", attObj[attr]); } else if (attrLower !== "classid" && attrLower !== "data") { o.setAttribute(attr, attObj[attr]); } } } if (ua.ie) { objIdArr[objIdArr.length] = attObj.id; // stored to fix object 'leaks' on unload (dynamic publishing only) } else { o.setAttribute("type", FLASH_MIME_TYPE); o.setAttribute("data", attObj.data); } el.parentNode.replaceChild(o, el); r = o; } return r; } function createObjParam(el, pName, pValue) { var p = createElement("param"); p.setAttribute("name", pName); p.setAttribute("value", pValue); el.appendChild(p); } /* Cross-browser SWF removal - Especially needed to safely and completely remove a SWF in Internet Explorer */ function removeSWF(id) { var obj = getElementById(id); if (obj && obj.nodeName.toUpperCase() === "OBJECT") { if (ua.ie) { obj.style.display = "none"; (function removeSWFInIE() { if (obj.readyState == 4) { //This step prevents memory leaks in Internet Explorer for (var i in obj) { if (typeof obj[i] === "function") { obj[i] = null; } } obj.parentNode.removeChild(obj); } else { setTimeout(removeSWFInIE, 10); } }()); } else { obj.parentNode.removeChild(obj); } } } function isElement(id) { return (id && id.nodeType && id.nodeType === 1); } function getId(thing) { return (isElement(thing)) ? thing.id : thing; } /* Functions to optimize JavaScript compression */ function getElementById(id) { //Allow users to pass an element OR an element's ID if (isElement(id)) { return id; } var el = null; try { el = doc.getElementById(id); } catch (e) {} return el; } function createElement(el) { return doc.createElement(el); } //To aid compression; replaces 14 instances of pareseInt with radix function toInt(str) { return parseInt(str, 10); } /* Updated attachEvent function for Internet Explorer - Stores attachEvent information in an Array, so on unload the detachEvent functions can be called to avoid memory leaks */ function addListener(target, eventType, fn) { target.attachEvent(eventType, fn); listenersArr[listenersArr.length] = [target, eventType, fn]; } /* Flash Player and SWF content version matching */ function hasPlayerVersion(rv) { rv += ""; //Coerce number to string, if needed. var pv = ua.pv, v = rv.split("."); v[0] = toInt(v[0]); v[1] = toInt(v[1]) || 0; // supports short notation, e.g. "9" instead of "9.0.0" v[2] = toInt(v[2]) || 0; return (pv[0] > v[0] || (pv[0] == v[0] && pv[1] > v[1]) || (pv[0] == v[0] && pv[1] == v[1] && pv[2] >= v[2])) ? true : false; } /* Cross-browser dynamic CSS creation - Based on Bobby van der Sluis' solution: http://www.bobbyvandersluis.com/articles/dynamicCSS.php */ function createCSS(sel, decl, media, newStyle) { var h = doc.getElementsByTagName("head")[0]; if (!h) { return; } // to also support badly authored HTML pages that lack a head element var m = (typeof media === "string") ? media : "screen"; if (newStyle) { dynamicStylesheet = null; dynamicStylesheetMedia = null; } if (!dynamicStylesheet || dynamicStylesheetMedia != m) { // create dynamic stylesheet + get a global reference to it var s = createElement("style"); s.setAttribute("type", "text/css"); s.setAttribute("media", m); dynamicStylesheet = h.appendChild(s); if (ua.ie && typeof doc.styleSheets !== UNDEF && doc.styleSheets.length > 0) { dynamicStylesheet = doc.styleSheets[doc.styleSheets.length - 1]; } dynamicStylesheetMedia = m; } // add style rule if (dynamicStylesheet) { if (typeof dynamicStylesheet.addRule !== UNDEF) { dynamicStylesheet.addRule(sel, decl); } else if (typeof doc.createTextNode !== UNDEF) { dynamicStylesheet.appendChild(doc.createTextNode(sel + " {" + decl + "}")); } } } function setVisibility(id, isVisible) { if (!autoHideShow) { return; } var v = isVisible ? "visible" : "hidden", el = getElementById(id); if (isDomLoaded && el) { el.style.visibility = v; } else if (typeof id === "string") { createCSS("#" + id, "visibility:" + v); } } /* Filter to avoid XSS attacks */ function urlEncodeIfNecessary(s) { var regex = /[\\\"<>\.;]/; var hasBadChars = regex.exec(s) !== null; return hasBadChars && typeof encodeURIComponent !== UNDEF ? encodeURIComponent(s) : s; } /* Release memory to avoid memory leaks caused by closures, fix hanging audio/video threads and force open sockets/NetConnections to disconnect (Internet Explorer only) */ var cleanup = function () { if (ua.ie) { window.attachEvent("onunload", function () { // remove listeners to avoid memory leaks var ll = listenersArr.length; for (var i = 0; i < ll; i++) { listenersArr[i][0].detachEvent(listenersArr[i][1], listenersArr[i][2]); } // cleanup dynamically embedded objects to fix audio/video threads and force open sockets and NetConnections to disconnect var il = objIdArr.length; for (var j = 0; j < il; j++) { removeSWF(objIdArr[j]); } // cleanup library's main closures to avoid memory leaks for (var k in ua) { ua[k] = null; } ua = null; for (var l in swfobject) { swfobject[l] = null; } swfobject = null; }); } }(); return { /* Public API - Reference: http://code.google.com/p/swfobject/wiki/documentation */ registerObject: function (objectIdStr, swfVersionStr, xiSwfUrlStr, callbackFn) { if (ua.w3 && objectIdStr && swfVersionStr) { var regObj = {}; regObj.id = objectIdStr; regObj.swfVersion = swfVersionStr; regObj.expressInstall = xiSwfUrlStr; regObj.callbackFn = callbackFn; regObjArr[regObjArr.length] = regObj; setVisibility(objectIdStr, false); } else if (callbackFn) { callbackFn({success: false, id: objectIdStr}); } }, getObjectById: function (objectIdStr) { if (ua.w3) { return getObjectById(objectIdStr); } }, embedSWF: function (swfUrlStr, replaceElemIdStr, widthStr, heightStr, swfVersionStr, xiSwfUrlStr, flashvarsObj, parObj, attObj, callbackFn) { var id = getId(replaceElemIdStr), callbackObj = {success: false, id: id}; if (ua.w3 && !(ua.wk && ua.wk < 312) && swfUrlStr && replaceElemIdStr && widthStr && heightStr && swfVersionStr) { setVisibility(id, false); addDomLoadEvent(function () { widthStr += ""; // auto-convert to string heightStr += ""; var att = {}; if (attObj && typeof attObj === OBJECT) { for (var i in attObj) { // copy object to avoid the use of references, because web authors often reuse attObj for multiple SWFs att[i] = attObj[i]; } } att.data = swfUrlStr; att.width = widthStr; att.height = heightStr; var par = {}; if (parObj && typeof parObj === OBJECT) { for (var j in parObj) { // copy object to avoid the use of references, because web authors often reuse parObj for multiple SWFs par[j] = parObj[j]; } } if (flashvarsObj && typeof flashvarsObj === OBJECT) { for (var k in flashvarsObj) { // copy object to avoid the use of references, because web authors often reuse flashvarsObj for multiple SWFs if (flashvarsObj.hasOwnProperty(k)) { var key = (encodeURIEnabled) ? encodeURIComponent(k) : k, value = (encodeURIEnabled) ? encodeURIComponent(flashvarsObj[k]) : flashvarsObj[k]; if (typeof par.flashvars !== UNDEF) { par.flashvars += "&" + key + "=" + value; } else { par.flashvars = key + "=" + value; } } } } if (hasPlayerVersion(swfVersionStr)) { // create SWF var obj = createSWF(att, par, replaceElemIdStr); if (att.id == id) { setVisibility(id, true); } callbackObj.success = true; callbackObj.ref = obj; callbackObj.id = obj.id; } else if (xiSwfUrlStr && canExpressInstall()) { // show Adobe Express Install att.data = xiSwfUrlStr; showExpressInstall(att, par, replaceElemIdStr, callbackFn); return; } else { // show fallback content setVisibility(id, true); } if (callbackFn) { callbackFn(callbackObj); } }); } else if (callbackFn) { callbackFn(callbackObj); } }, switchOffAutoHideShow: function () { autoHideShow = false; }, enableUriEncoding: function (bool) { encodeURIEnabled = (typeof bool === UNDEF) ? true : bool; }, ua: ua, getFlashPlayerVersion: function () { return {major: ua.pv[0], minor: ua.pv[1], release: ua.pv[2]}; }, hasFlashPlayerVersion: hasPlayerVersion, createSWF: function (attObj, parObj, replaceElemIdStr) { if (ua.w3) { return createSWF(attObj, parObj, replaceElemIdStr); } else { return undefined; } }, showExpressInstall: function (att, par, replaceElemIdStr, callbackFn) { if (ua.w3 && canExpressInstall()) { showExpressInstall(att, par, replaceElemIdStr, callbackFn); } }, removeSWF: function (objElemIdStr) { if (ua.w3) { removeSWF(objElemIdStr); } }, createCSS: function (selStr, declStr, mediaStr, newStyleBoolean) { if (ua.w3) { createCSS(selStr, declStr, mediaStr, newStyleBoolean); } }, addDomLoadEvent: addDomLoadEvent, addLoadEvent: addLoadEvent, getQueryParamValue: function (param) { var q = doc.location.search || doc.location.hash; if (q) { if (/\?/.test(q)) { q = q.split("?")[1]; } // strip question mark if (!param) { return urlEncodeIfNecessary(q); } var pairs = q.split("&"); for (var i = 0; i < pairs.length; i++) { if (pairs[i].substring(0, pairs[i].indexOf("=")) == param) { return urlEncodeIfNecessary(pairs[i].substring((pairs[i].indexOf("=") + 1))); } } } return ""; }, // For internal usage only expressInstallCallback: function () { if (isExpressInstallActive) { var obj = getElementById(EXPRESS_INSTALL_ID); if (obj && storedFbContent) { obj.parentNode.replaceChild(storedFbContent, obj); if (storedFbContentId) { setVisibility(storedFbContentId, true); if (ua.ie) { storedFbContent.style.display = "block"; } } if (storedCallbackFn) { storedCallbackFn(storedCallbackObj); } } isExpressInstallActive = false; } }, version: "2.3" }; }(); /*! Flowplayer v5.4.6 (2014-08-14) | flowplayer.org/license */ !function($) { /* jQuery.browser for 1.9+ We all love feature detection but that's sometimes not enough. @author Tero Piirainen */ !function($) { if (!$.browser) { var b = $.browser = {}, ua = navigator.userAgent.toLowerCase(), match = /(chrome)[ \/]([\w.]+)/.exec(ua) || /(safari)[ \/]([\w.]+)/.exec(ua) || /(webkit)[ \/]([\w.]+)/.exec(ua) || /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || /(msie) ([\w.]+)/.exec(ua) || ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || []; if (match[1]) { b[match[1]] = true; b.version = match[2] || "0"; } } }(jQuery); // auto-install (any video tag with parent .flowplayer) $(function() { if (typeof $.fn.flowplayer == 'function') { $("video").parent(".flowplayer").flowplayer(); } }); var instances = [], extensions = [], UA = window.navigator.userAgent; /* flowplayer() */ window.flowplayer = function(fn) { return $.isFunction(fn) ? extensions.push(fn) : typeof fn == 'number' || fn === undefined ? instances[fn || 0] : $(fn).data("flowplayer"); }; $(window).on('beforeunload', function() { $.each(instances, function(i, api) { if (api.conf.splash) { api.unload(); } else { api.bind("error", function () { $(".flowplayer.is-error .fp-message").remove(); }); } }); }); var supportLocalStorage = false; try { if (typeof window.localStorage == "object") { window.localStorage.flowplayerTestStorage = "test"; supportLocalStorage = true; } } catch (ignored) {} $.extend(flowplayer, { version: '5.4.6', engine: {}, conf: {}, support: {}, defaults: { debug: false, // true = forced playback disabled: false, // first engine to try engine: 'html5', fullscreen: window == window.top, // keyboard shortcuts keyboard: true, // default aspect ratio ratio: 9 / 16, adaptiveRatio: false, // scale flash object to video's aspect ratio in normal mode? flashfit: false, rtmp: 0, splash: false, live: false, swf: "//releases.flowplayer.org/5.4.6/flowplayer.swf", speeds: [0.25, 0.5, 1, 1.5, 2], tooltip: true, // initial volume level volume: !supportLocalStorage ? 1 : localStorage.muted == "true" ? 0 : !isNaN(localStorage.volume) ? localStorage.volume || 1 : 1, // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#error-codes errors: [ // video exceptions '', 'Video loading aborted', 'Network error', 'Video not properly encoded', 'Video file not found', // player exceptions 'Unsupported video', 'Skin not found', 'SWF file not found', 'Subtitles not found', 'Invalid RTMP URL', 'Unsupported video format. Try installing Adobe Flash.' ], errorUrls: ['','','','','','','','','','', 'http://get.adobe.com/flashplayer/' ], playlist: [] } }); // keep track of players var playerCount = 1; // jQuery plugin $.fn.flowplayer = function(opts, callback) { if (typeof opts == 'string') opts = { swf: opts } if ($.isFunction(opts)) { callback = opts; opts = {} } return !opts && this.data("flowplayer") || this.each(function() { // private variables var root = $(this).addClass("is-loading"), conf = $.extend({}, flowplayer.defaults, flowplayer.conf, opts, root.data()), videoTag = $("video", root).addClass("fp-engine").removeAttr("controls"), urlResolver = videoTag.length ? new URLResolver(videoTag) : null, storage = {}, lastSeekPosition, engine; if (conf.playlist.length) { // Create initial video tag if called without var preload = videoTag.attr('preload'), placeHolder; if (videoTag.length) videoTag.replaceWith(placeHolder = $('

')); videoTag = $('

")[0]; for (var i = 0; i < vendors.length; i++) { if (el.style[vendors[i] + 'AnimationName'] !== 'undefined') return true; } })(); }(); /* The most minimal Flash embedding */ // movie required in opts function embed(swf, flashvars) { var id = "obj" + ("" + Math.random()).slice(2, 15), tag = '' : ' data="' + swf + '" type="application/x-shockwave-flash">'; var opts = { width: "100%", height: "100%", allowscriptaccess: "always", wmode: "transparent", quality: "high", flashvars: "", // https://github.com/flowplayer/flowplayer/issues/13#issuecomment-9369919 movie: swf + ($.browser.msie ? "?" + id : ""), name: id }; // flashvars $.each(flashvars, function(key, value) { opts.flashvars += key + "=" + value + "&"; }); // parameters $.each(opts, function(key, value) { tag += ''; }); tag += ""; return $(tag); } // Flash is buggy allover if (window.attachEvent) { window.attachEvent("onbeforeunload", function() { __flash_savedUnloadHandler = __flash_unloadHandler = function() {}; }); } flowplayer.engine.flash = function(player, root) { var conf = player.conf, video = player.video, callbackId, objectTag, api; var engine = { pick: function(sources) { if (flowplayer.support.flashVideo) { // always pick video/flash first var flash = $.grep(sources, function(source) { return source.type == 'flash'; })[0]; if (flash) return flash; for (var i = 0, source; i < sources.length; i++) { source = sources[i]; if (/mp4|flv/.test(source.type)) return source; } } }, load: function(video) { function escapeURL(url) { return url.replace(/&/g, '%26').replace(/&/g, '%26').replace(/=/g, '%3D'); } var html5Tag = $("video", root), url = escapeURL(video.src); is_absolute = /^https?:/.test(url); // html5 tag not needed (pause needed for firefox) try { if (html5Tag.length > 0 && flowplayer.support.video) html5Tag[0].pause(); } catch (e) { // Omit errors on calling pause(), see https://github.com/flowplayer/flowplayer/issues/490 } var removeTag = function() { html5Tag.remove(); }; var hasSupportedSource = function(sources) { return $.grep(sources, function(src) { return !!html5Tag[0].canPlayType('video/' + src.type); }).length > 0; }; if (flowplayer.support.video && html5Tag.prop('autoplay') && hasSupportedSource(video.sources)) html5Tag.one('timeupdate', removeTag); else removeTag(); // convert to absolute if (!is_absolute && !conf.rtmp) url = $("").attr("src", url)[0].src; if (api) { api.__play(url); } else { callbackId = "fp" + ("" + Math.random()).slice(3, 15); var opts = { hostname: conf.embedded ? conf.hostname : location.hostname, url: url, callback: "jQuery."+ callbackId }; if (root.data("origin")) { opts.origin = root.data("origin"); } if (is_absolute) delete conf.rtmp; // optional conf $.each(['key', 'autoplay', 'preload', 'rtmp', 'loop', 'debug', 'preload', 'splash', 'bufferTime'], function(i, key) { if (conf[key]) opts[key] = conf[key]; }); // issue #376 if (opts.rtmp) { opts.rtmp = escapeURL(opts.rtmp); } objectTag = embed(conf.swf, opts); objectTag.prependTo(root); api = objectTag[0]; // throw error if no loading occurs setTimeout(function() { try { if (!api.PercentLoaded()) { return root.trigger("error", [player, { code: 7, url: conf.swf }]); } } catch (e) {} }, 5000); // detect disabled flash setTimeout(function() { if (typeof api.PercentLoaded === 'undefined') { root.trigger('flashdisabled', [player]); } }, 1000); // listen $[callbackId] = function(type, arg) { if (conf.debug && type != "status") console.log("--", type, arg); var event = $.Event(type); switch (type) { // RTMP sends a lot of finish events in vain // case "finish": if (conf.rtmp) return; case "ready": arg = $.extend(video, arg); break; case "click": event.flash = true; break; case "keydown": event.which = arg; break; case "seek": video.time = arg; break; case "status": player.trigger("progress", arg.time); if (arg.buffer < video.bytes && !video.buffered) { video.buffer = arg.buffer / video.bytes * video.duration; player.trigger("buffer", video.buffer); } else if (!video.buffered) { video.buffered = true; player.trigger("buffered"); } break; } if (type != 'buffered') { // add some delay so that player is truly ready after an event setTimeout(function() { player.trigger(event, arg); }, 1) } }; } }, // not supported yet speed: $.noop, unload: function() { api && api.__unload && api.__unload(); delete $[callbackId]; $("object", root).remove(); api = 0; } }; $.each("pause,resume,seek,volume".split(","), function(i, name) { engine[name] = function(arg) { try { if (player.ready) { if (name == 'seek' && player.video.time && !player.paused) { player.trigger("beforeseek"); } if (arg === undefined) { api["__" + name](); } else { api["__" + name](arg); } } } catch (e) { if (typeof api["__" + name] === 'undefined') { //flash lost it's methods return root.trigger('flashdisabled', [player]); } throw e; } }; }); var win = $(window); // handle Flash object aspect ratio player.bind("ready fullscreen fullscreen-exit", function(e) { var origH = root.height(), origW = root.width(); if (player.conf.flashfit || /full/.test(e.type)) { var fs = player.isFullscreen, truefs = fs && FS_SUPPORT, ie7 = !flowplayer.support.inlineBlock, screenW = fs ? (truefs ? screen.width : win.width()) : origW, screenH = fs ? (truefs ? screen.height : win.height()) : origH, // default values for fullscreen-exit without flashfit hmargin = 0, vmargin = 0, objwidth = ie7 ? origW : '', objheight = ie7 ? origH : '', aspectratio, dataratio; if (player.conf.flashfit || e.type === "fullscreen") { aspectratio = player.video.width / player.video.height, dataratio = player.video.height / player.video.width, objheight = Math.max(dataratio * screenW), objwidth = Math.max(aspectratio * screenH); objheight = objheight > screenH ? objwidth * dataratio : objheight; objheight = Math.min(Math.round(objheight), screenH); objwidth = objwidth > screenW ? objheight * aspectratio : objwidth; objwidth = Math.min(Math.round(objwidth), screenW); vmargin = Math.max(Math.round((screenH + vmargin - objheight) / 2), 0); hmargin = Math.max(Math.round((screenW + hmargin - objwidth) / 2), 0); } $("object", root).css({ width: objwidth, height: objheight, marginTop: vmargin, marginLeft: hmargin }); } }); return engine; }; var VIDEO = $('

\
\
\ \ \

\

\ \
\
\
\
\
\ \
\
\
\
\
\
\ 00:00\ \ 00:00\
\

\
'.replace(/class="/g, 'class="fp-') ); function find(klass) { return $(".fp-" + klass, root); } // widgets var progress = find("progress"), buffer = find("buffer"), elapsed = find("elapsed"), remaining = find("remaining"), waiting = find("waiting"), ratio = find("ratio"), speed = find("speed"), durationEl = find("duration"), origRatio = ratio.css("paddingTop"), // sliders timeline = find("timeline").slider2(api.rtl), timelineApi = timeline.data("api"), volume = find("volume"), fullscreen = find("fullscreen"), volumeSlider = find("volumeslider").slider2(api.rtl), volumeApi = volumeSlider.data("api"), noToggle = root.is(".fixed-controls, .no-toggle"); timelineApi.disableAnimation(root.hasClass('is-touch')); // aspect ratio function setRatio(val) { if ((root.css('width') === '0px' || root.css('height') === '0px') || val !== flowplayer.defaults.ratio) { if (!parseInt(origRatio, 10)) ratio.css("paddingTop", val * 100 + "%"); } if (!support.inlineBlock) $("object", root).height(root.height()); } function hover(flag) { root.toggleClass("is-mouseover", flag).toggleClass("is-mouseout", !flag); } // loading... if (!support.animation) waiting.html("

loading …

"); setRatio(conf.ratio); // no fullscreen in IFRAME try { if (!conf.fullscreen) fullscreen.remove(); } catch (e) { fullscreen.remove(); } api.bind("ready", function() { var duration = api.video.duration; timelineApi.disable(api.disabled || !duration); conf.adaptiveRatio && setRatio(api.video.height / api.video.width); // initial time & volume durationEl.add(remaining).html(format(duration)); // do we need additional space for showing hour ((duration >= 3600) && root.addClass('is-long')) || root.removeClass('is-long'); volumeApi.slide(api.volumeLevel); }).bind("unload", function() { if (!origRatio) ratio.css("paddingTop", ""); // buffer }).bind("buffer", function() { var video = api.video, max = video.buffer / video.duration; if (!video.seekable && support.seekable) timelineApi.max(max); if (max < 1) buffer.css("width", (max * 100) + "%"); else buffer.css({ width: '100%' }); }).bind("speed", function(e, api, val) { speed.text(val + "x").addClass("fp-hilite"); setTimeout(function() { speed.removeClass("fp-hilite") }, 1000); }).bind("buffered", function() { buffer.css({ width: '100%' }); timelineApi.max(1); // progress }).bind("progress", function() { var time = api.video.time, duration = api.video.duration; if (!timelineApi.dragging) { timelineApi.slide(time / duration, api.seeking ? 0 : 250); } elapsed.html(format(time)); remaining.html("-" + format(duration - time)); }).bind("finish resume seek", function(e) { root.toggleClass("is-finished", e.type == "finish"); }).bind("stop", function() { elapsed.html(format(0)); timelineApi.slide(0, 100); }).bind("finish", function() { elapsed.html(format(api.video.duration)); timelineApi.slide(1, 100); root.removeClass("is-seeking"); // misc }).bind("beforeseek", function() { progress.stop(); }).bind("volume", function() { volumeApi.slide(api.volumeLevel); }).bind("disable", function() { var flag = api.disabled; timelineApi.disable(flag); volumeApi.disable(flag); root.toggleClass("is-disabled", api.disabled); }).bind("mute", function(e, api, flag) { root.toggleClass("is-muted", flag); }).bind("error", function(e, api, error) { root.removeClass("is-loading").addClass("is-error"); if (error) { error.message = conf.errors[error.code]; api.error = true; var el = $(".fp-message", root); $("h2", el).text((api.engine || 'html5') + ": " + error.message); $("p", el).text(error.url || api.video.url || api.video.src || conf.errorUrls[error.code]); root.unbind("mouseenter click").removeClass("is-mouseover"); } // hover }).bind("mouseenter mouseleave", function(e) { if (noToggle) return; var is_over = e.type == "mouseenter", lastMove; // is-mouseover/out hover(is_over); if (is_over) { root.bind("pause.x mousemove.x volume.x", function() { hover(true); lastMove = new Date; }); hovertimer = setInterval(function() { if (new Date - lastMove > 5000) { hover(false) lastMove = new Date; } }, 100); } else { root.unbind(".x"); clearInterval(hovertimer); } // allow dragging over the player edge }).bind("mouseleave", function() { if (timelineApi.dragging || volumeApi.dragging) { root.addClass("is-mouseover").removeClass("is-mouseout"); } // click }).bind("click.player", function(e) { if ($(e.target).is(".fp-ui, .fp-engine") || e.flash) { e.preventDefault(); return api.toggle(); } }).bind('contextmenu', function(ev) { ev.preventDefault(); var o = root.offset(), w = $(window), left = ev.clientX - o.left, t = ev.clientY - o.top + w.scrollTop(); var menu = root.find('.fp-context-menu').css({ left: left + 'px', top: t + 'px', display: 'block' }).on('click', function(ev) { ev.stopPropagation(); }); $('html').on('click.outsidemenu', function(ev) { menu.hide(); $('html').off('click.outsidemenu'); }); }).bind('flashdisabled', function() { root.addClass('is-flash-disabled').one('ready', function() { root.removeClass('is-flash-disabled').find('.fp-flash-disabled').remove(); }).append('
Adobe Flash is disabled for this page, click player area to enable.
'); }); // poster -> background image if (conf.poster) root.css("backgroundImage", "url(" + conf.poster + ")"); var bc = root.css("backgroundColor"), has_bg = root.css("backgroundImage") != "none" || bc && bc != "rgba(0, 0, 0, 0)" && bc != "transparent"; // is-poster class if (has_bg && !conf.splash && !conf.autoplay) { api.bind("ready stop", function() { root.addClass("is-poster").one("progress", function() { root.removeClass("is-poster"); }); }); } // default background color if not present if (!has_bg && api.forcedSplash) { root.css("backgroundColor", "#555"); } $(".fp-toggle, .fp-play", root).click(api.toggle); /* controlbar elements */ $.each(['mute', 'fullscreen', 'unload'], function(i, key) { find(key).click(function() { api[key](); }); }); timeline.bind("slide", function(e, val) { api.seeking = true; api.seek(val * api.video.duration); }); volumeSlider.bind("slide", function(e, val) { api.volume(val); }); // times find("time").click(function(e) { $(this).toggleClass("is-inverted"); }); hover(noToggle); }); var focused, focusedRoot, IS_HELP = "is-help"; // keyboard. single global listener $(document).bind("keydown.fp", function(e) { var el = focused, metaKeyPressed = e.ctrlKey || e.metaKey || e.altKey, key = e.which, conf = el && el.conf; if (!el || !conf.keyboard || el.disabled) return; // help dialog (shift key not truly required) if ($.inArray(key, [63, 187, 191]) != -1) { focusedRoot.toggleClass(IS_HELP); return false; } // close help / unload if (key == 27 && focusedRoot.hasClass(IS_HELP)) { focusedRoot.toggleClass(IS_HELP); return false; } if (!metaKeyPressed && el.ready) { e.preventDefault(); // slow motion / fast forward if (e.shiftKey) { if (key == 39) el.speed(true); else if (key == 37) el.speed(false); return; } // 1, 2, 3, 4 .. if (key < 58 && key > 47) return el.seekTo(key - 48); switch (key) { case 38: case 75: el.volume(el.volumeLevel + 0.15); break; // volume up case 40: case 74: el.volume(el.volumeLevel - 0.15); break; // volume down case 39: case 76: el.seeking = true; el.seek(true); break; // forward case 37: case 72: el.seeking = true; el.seek(false); break; // backward case 190: el.seekTo(); break; // to last seek position case 32: el.toggle(); break; // spacebar case 70: conf.fullscreen && el.fullscreen(); break; // toggle fullscreen case 77: el.mute(); break; // mute case 81: el.unload(); break; // unload/stop } } }); flowplayer(function(api, root) { // no keyboard configured if (!api.conf.keyboard) return; // hover root.bind("mouseenter mouseleave", function(e) { focused = !api.disabled && e.type == 'mouseenter' ? api : 0; if (focused) focusedRoot = root; }); // TODO: add to player-layout.html root.append('\
\ \
\

spaceplay / pause

\

qunload | stop

\

ffullscreen

\

shift + slower / faster (latest Chrome and Safari)

\
\
\

volume

\

mmute

\
\
\

seek

\

 . seek to previous\

126 seek to 10%, 20%, …60%

\
\
\ '); if (api.conf.tooltip) { $(".fp-ui", root).attr("title", "Hit ? for help").on("mouseout.tip", function() { $(this).removeAttr("title").off("mouseout.tip"); }); } $(".fp-close", root).click(function() { root.toggleClass(IS_HELP); }); }); var VENDOR = $.browser.mozilla ? "moz": "webkit", FS_ENTER = "fullscreen", FS_EXIT = "fullscreen-exit", FULL_PLAYER, FS_SUPPORT = flowplayer.support.fullscreen, FS_NATIVE_SUPPORT = typeof document.exitFullscreen == 'function', ua = navigator.userAgent.toLowerCase(), IS_SAFARI = /(safari)[ \/]([\w.]+)/.exec(ua) && !/(chrome)[ \/]([\w.]+)/.exec(ua); // esc button $(document).bind(FS_NATIVE_SUPPORT ? "fullscreenchange" : VENDOR + "fullscreenchange", function(e) { var el = $(document.webkitCurrentFullScreenElement || document.mozFullScreenElement || document.fullscreenElement || e.target); if (el.length && !FULL_PLAYER) { FULL_PLAYER = el.trigger(FS_ENTER, [el]); } else { FULL_PLAYER.trigger(FS_EXIT, [FULL_PLAYER]); FULL_PLAYER = null; } }); flowplayer(function(player, root) { if (!player.conf.fullscreen) return; var win = $(window), fsResume = {index: 0, pos: 0, play: false}, scrollTop; player.isFullscreen = false; player.fullscreen = function(flag) { if (player.disabled) return; if (flag === undefined) flag = !player.isFullscreen; if (flag) scrollTop = win.scrollTop(); if ((VENDOR == "webkit" || IS_SAFARI) && player.engine == "flash") { // play current index on fullscreen toggle fsResume.index = player.video.index; if (player.conf.rtmp) // avoid restart $.extend(fsResume, {pos: player.video.time, play: player.playing}); } if (FS_SUPPORT) { if (flag) { if (FS_NATIVE_SUPPORT) { root[0].requestFullscreen(); } else { root[0][VENDOR + 'RequestFullScreen'](Element.ALLOW_KEYBOARD_INPUT); if (IS_SAFARI && !document.webkitCurrentFullScreenElement && !document.mozFullScreenElement) { // Element.ALLOW_KEYBOARD_INPUT not allowed root[0][VENDOR + 'RequestFullScreen'](); } } } else { if (FS_NATIVE_SUPPORT) { document.exitFullscreen(); } else { document[VENDOR + 'CancelFullScreen'](); } } } else { player.trigger(flag ? FS_ENTER : FS_EXIT, [player]); } return player; }; var lastClick; root.bind("mousedown.fs", function() { if (+new Date - lastClick < 150 && player.ready) player.fullscreen(); lastClick = +new Date; }); player.bind(FS_ENTER, function(e) { root.addClass("is-fullscreen"); player.isFullscreen = true; }).bind(FS_EXIT, function(e) { var oldOpacity; if (!FS_SUPPORT && player.engine === "html5") { oldOpacity = root.css('opacity') || ''; root.css('opacity', 0); } root.removeClass("is-fullscreen"); if (!FS_SUPPORT && player.engine === "html5") setTimeout(function() { root.css('opacity', oldOpacity); }); player.isFullscreen = false; win.scrollTop(scrollTop); }).bind("ready", function () { if (fsResume.index > 0) { player.play(fsResume.index); // above loads "different" clip, resume position below fsResume.index = 0; } else if (fsResume.pos && !isNaN(fsResume.pos)) { var fsreset = function () { if (!fsResume.play) player.pause(); $.extend(fsResume, {pos: 0, play: false}); }; if (player.conf.live) { player.resume(); fsreset(); } else { player.resume().seek(fsResume.pos, fsreset); } } }); }); flowplayer(function(player, root) { var conf = $.extend({ active: 'is-active', advance: true, query: ".fp-playlist a" }, player.conf), klass = conf.active; // getters function els() { return $(conf.query, root); } function active() { return $(conf.query + "." + klass, root); } player.play = function(i) { if (i === undefined) return player.resume(); if (typeof i === 'number' && !player.conf.playlist[i]) return player; else if (typeof i != 'number') player.load.apply(null, arguments); player.unbind('resume.fromfirst'); // Don't start from beginning if clip explicitely chosen player.video.index = i; player.load(typeof player.conf.playlist[i] === 'string' ? player.conf.playlist[i].toString() : $.map(player.conf.playlist[i], function(item) { return $.extend({}, item); }) ); return player; }; player.next = function(e) { e && e.preventDefault(); var current = player.video.index; if (current != -1) { current = current === player.conf.playlist.length - 1 ? 0 : current + 1; player.play(current); } return player; }; player.prev = function(e) { e && e.preventDefault(); var current = player.video.index; if (current != -1) { current = current === 0 ? player.conf.playlist.length - 1 : current - 1; player.play(current); } return player; }; $('.fp-next', root).click(player.next); $('.fp-prev', root).click(player.prev); if (conf.advance) { root.unbind("finish.pl").bind("finish.pl", function(e, player) { // next clip is found or loop var next = player.video.index + 1; if (next < player.conf.playlist.length || conf.loop) { next = next === player.conf.playlist.length ? 0 : next; root.removeClass('is-finished'); setTimeout(function() { // Let other finish callbacks fire first player.play(next); }); // stop to last clip, play button starts from 1:st clip } else { root.addClass("is-playing"); // show play button // If we have multiple items in playlist, start from first if (player.conf.playlist.length > 1) player.one("resume.fromfirst", function() { player.play(0); return false; }); } }); } var playlistInitialized = false; if (player.conf.playlist.length) { // playlist configured by javascript, generate playlist playlistInitialized = true; var plEl = root.find('.fp-playlist'); if (!plEl.length) { plEl = $('
'); var cntrls = $('.fp-next,.fp-prev', root); if (!cntrls.length) $('video', root).after(plEl); else cntrls.eq(0).before(plEl); } plEl.empty(); $.each(player.conf.playlist, function(i, item) { var href; if (typeof item === 'string') { href = item; } else { for (var key in item[0]) { if (item[0].hasOwnProperty(key)) { href = item[0][key]; break; } } } plEl.append($('').attr({ href: href, 'data-index': i })); }); } if (els().length) { if (!playlistInitialized) { player.conf.playlist = []; els().each(function() { var src = $(this).attr('href'); $(this).attr('data-index', player.conf.playlist.length); player.conf.playlist.push(src); }); } /* click -> play */ root.on("click", conf.query, function(e) { e.preventDefault(); var el = $(e.target).closest(conf.query); var toPlay = Number(el.attr('data-index')); if (toPlay != -1) { player.play(toPlay); } }); // playlist wide cuepoint support var has_cuepoints = els().filter("[data-cuepoints]").length; // highlight player.bind("load", function(e, api, video) { var prev = active().removeClass(klass), prevIndex = prev.attr('data-index'), index = video.index = player.video.index || 0, el = $('a[data-index="' + index + '"]', root).addClass(klass), is_last = index == player.conf.playlist.length - 1; // index root.removeClass("video" + prevIndex).addClass("video" + index).toggleClass("last-video", is_last); // video properties video.index = api.video.index = index; video.is_last = api.video.is_last = is_last; // cuepoints if (has_cuepoints) player.cuepoints = el.data("cuepoints"); // without namespace callback called only once. unknown rason. }).bind("unload.pl", function() { active().toggleClass(klass); }); } if (player.conf.playlist.length) { // disable single clip looping player.conf.loop = false; } }); var CUE_RE = / ?cue\d+ ?/; flowplayer(function(player, root) { var lastTime = 0; player.cuepoints = player.conf.cuepoints || []; function setClass(index) { root[0].className = root[0].className.replace(CUE_RE, " "); if (index >= 0) root.addClass("cue" + index); } player.bind("progress", function(e, api, time) { // avoid throwing multiple times if (lastTime && time - lastTime < 0.015) return lastTime = time; lastTime = time; var cues = player.cuepoints || []; for (var i = 0, cue; i < cues.length; i++) { cue = cues[i]; if (!isNaN(cue)) cue = { time: cue }; if (cue.time < 0) cue.time = player.video.duration + cue.time; cue.index = i; // progress_interval / 2 = 0.125 if (Math.abs(cue.time - time) < 0.125 * player.currentSpeed) { setClass(i); root.trigger("cuepoint", [player, cue]); } } // no CSS class name }).bind("unload seek", setClass); if (player.conf.generate_cuepoints) { player.bind("load", function() { // clean up cuepoint elements of previous playlist items $(".fp-cuepoint", root).remove(); }).bind("ready", function() { var cues = player.cuepoints || [], duration = player.video.duration, timeline = $(".fp-timeline", root).css("overflow", "visible"); $.each(cues, function(i, cue) { var time = cue.time || cue; if (time < 0) time = duration + cue; var el = $("").addClass("fp-cuepoint fp-cuepoint" + i) .css("left", (time / duration * 100) + "%"); el.appendTo(timeline).mousedown(function() { player.seek(time); // preventDefault() doesn't work return false; }); }); }); } }); flowplayer(function(player, root, engine) { var track = $("track", root), conf = player.conf; if (flowplayer.support.subtitles) { player.subtitles = track.length && track[0].track; // use native when supported if (conf.nativesubtitles && conf.engine == 'html5') return; } // avoid duplicate loads track.remove(); var TIMECODE_RE = /^(([0-9]{2}:)?[0-9]{2}:[0-9]{2}[,.]{1}[0-9]{3}) --\> (([0-9]{2}:)?[0-9]{2}:[0-9]{2}[,.]{1}[0-9]{3})(.*)/; function seconds(timecode) { var els = timecode.split(':'); if (els.length == 2) els.unshift(0); return els[0] * 60 * 60 + els[1] * 60 + parseFloat(els[2].replace(',','.')); } player.subtitles = []; var url = track.attr("src"); if (!url) return; setTimeout(function() { $.get(url, function(txt) { for (var i = 0, lines = txt.split("\n"), len = lines.length, entry = {}, title, timecode, text, cue; i < len; i++) { timecode = TIMECODE_RE.exec(lines[i]); if (timecode) { // title title = lines[i - 1]; // text text = "

" + lines[++i] + "


"; while ($.trim(lines[++i]) && i < lines.length) text += "

" + lines[i] + "


"; // entry entry = { title: title, startTime: seconds(timecode[1]), endTime: seconds(timecode[2] || timecode[3]), text: text }; cue = { time: entry.startTime, subtitle: entry }; player.subtitles.push(entry); player.cuepoints.push(cue); player.cuepoints.push({ time: entry.endTime, subtitleEnd: title }); // initial cuepoint if (entry.startTime === 0) { player.trigger("cuepoint", cue); } } } }).fail(function() { player.trigger("error", {code: 8, url: url }); return false; }); }); var wrap = $("
").appendTo(root), currentPoint; player.bind("cuepoint", function(e, api, cue) { if (cue.subtitle) { currentPoint = cue.index; wrap.html(cue.subtitle.text).addClass("fp-active"); } else if (cue.subtitleEnd) { wrap.removeClass("fp-active"); currentPoint = cue.index; } }).bind("seek", function(e, api, time) { // Clear future subtitles if seeking backwards if (currentPoint && player.cuepoints[currentPoint] && player.cuepoints[currentPoint].time > time) { wrap.removeClass('fp-active'); currentPoint = null; } $.each(player.cuepoints || [], function(i, cue) { var entry = cue.subtitle; //Trigger cuepoint if start time before seek position and end time nonexistent or in the future if (entry && currentPoint != cue.index) { if (time >= cue.time && (!entry.endTime || time <= entry.endTime)) player.trigger("cuepoint", cue); } // Also handle cuepoints that act as the removal trigger else if (cue.subtitleEnd && time >= cue.time && cue.index == currentPoint + 1) player.trigger("cuepoint", cue); }); }); }); flowplayer(function(player, root) { var id = player.conf.analytics, time = 0, last = 0; if (id) { // load Analytics script if needed if (typeof _gat == 'undefined') $.getScript("//google-analytics.com/ga.js"); function track(e) { if (time && typeof _gat != 'undefined') { var tracker = _gat._getTracker(id), video = player.video; tracker._setAllowLinker(true); // http://code.google.com/apis/analytics/docs/tracking/eventTrackerGuide.html tracker._trackEvent( "Video / Seconds played", player.engine + "/" + video.type, root.attr("title") || video.src.split("/").slice(-1)[0].replace(TYPE_RE, ''), Math.round(time / 1000) ); time = 0; } } player.bind("load unload", track).bind("progress", function() { if (!player.seeking) { time += last ? (+new Date - last) : 0; last = +new Date; } }).bind("pause", function() { last = 0; }); $(window).unload(track); } });var isIeMobile = /IEMobile/.test(UA); if (flowplayer.support.touch || isIeMobile) { flowplayer(function(player, root) { var isAndroid = /Android/.test(UA) && !/Firefox/.test(UA) && !/Opera/.test(UA), isSilk = /Silk/.test(UA), androidVer = isAndroid ? parseFloat(/Android\ (\d\.\d)/.exec(UA)[1], 10) : 0; // custom load for android if (isAndroid) { player.conf.videoTypePreference = "mp4"; // Android has problems with webm aspect ratio if (!/Chrome/.test(UA) && androidVer < 4) { var originalLoad = player.load; player.load = function(video, callback) { var ret = originalLoad.apply(player, arguments); player.trigger('ready', [player, player.video]); return ret; }; } } // hide volume if (!flowplayer.support.volume) { root.addClass("no-volume no-mute"); } root.addClass("is-touch"); root.find('.fp-timeline').data('api').disableAnimation(); // fake mouseover effect with click var hasMoved = false; root.bind('touchmove', function() { hasMoved = true; }).bind("touchend click", function(e) { if (hasMoved) { //not intentional, most likely scrolling hasMoved = false; return; } if (player.playing && !root.hasClass("is-mouseover")) { root.addClass("is-mouseover").removeClass("is-mouseout"); return false; } if (player.paused && root.hasClass("is-mouseout") && !player.splash) { player.toggle(); } if (player.paused && isIeMobile) { // IE on WP7 need an additional api.play() call $('video.fp-engine', root)[0].play(); } }); // native fullscreen if (player.conf.native_fullscreen && typeof $('", { "class": "fp-embed", title: 'Copy to your site'}).appendTo(ui), target = $("
", { 'class': 'fp-embed-code'}) .append("