27414 lines
832 KiB
JavaScript
27414 lines
832 KiB
JavaScript
//! moment.js
|
|
|
|
;(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
typeof define === 'function' && define.amd ? define(factory) :
|
|
global.moment = factory()
|
|
}(this, (function () { 'use strict';
|
|
|
|
var hookCallback;
|
|
|
|
function hooks () {
|
|
return hookCallback.apply(null, arguments);
|
|
}
|
|
|
|
// This is done to register the method called with moment()
|
|
// without creating circular dependencies.
|
|
function setHookCallback (callback) {
|
|
hookCallback = callback;
|
|
}
|
|
|
|
function isArray(input) {
|
|
return input instanceof Array || Object.prototype.toString.call(input) === '[object Array]';
|
|
}
|
|
|
|
function isObject(input) {
|
|
// IE8 will treat undefined and null as object if it wasn't for
|
|
// input != null
|
|
return input != null && Object.prototype.toString.call(input) === '[object Object]';
|
|
}
|
|
|
|
function isObjectEmpty(obj) {
|
|
if (Object.getOwnPropertyNames) {
|
|
return (Object.getOwnPropertyNames(obj).length === 0);
|
|
} else {
|
|
var k;
|
|
for (k in obj) {
|
|
if (obj.hasOwnProperty(k)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
function isUndefined(input) {
|
|
return input === void 0;
|
|
}
|
|
|
|
function isNumber(input) {
|
|
return typeof input === 'number' || Object.prototype.toString.call(input) === '[object Number]';
|
|
}
|
|
|
|
function isDate(input) {
|
|
return input instanceof Date || Object.prototype.toString.call(input) === '[object Date]';
|
|
}
|
|
|
|
function map(arr, fn) {
|
|
var res = [], i;
|
|
for (i = 0; i < arr.length; ++i) {
|
|
res.push(fn(arr[i], i));
|
|
}
|
|
return res;
|
|
}
|
|
|
|
function hasOwnProp(a, b) {
|
|
return Object.prototype.hasOwnProperty.call(a, b);
|
|
}
|
|
|
|
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 createUTC (input, format, locale, strict) {
|
|
return createLocalOrUTC(input, format, locale, strict, true).utc();
|
|
}
|
|
|
|
function defaultParsingFlags() {
|
|
// We need to deep clone this object.
|
|
return {
|
|
empty : false,
|
|
unusedTokens : [],
|
|
unusedInput : [],
|
|
overflow : -2,
|
|
charsLeftOver : 0,
|
|
nullInput : false,
|
|
invalidMonth : null,
|
|
invalidFormat : false,
|
|
userInvalidated : false,
|
|
iso : false,
|
|
parsedDateParts : [],
|
|
meridiem : null,
|
|
rfc2822 : false,
|
|
weekdayMismatch : false
|
|
};
|
|
}
|
|
|
|
function getParsingFlags(m) {
|
|
if (m._pf == null) {
|
|
m._pf = defaultParsingFlags();
|
|
}
|
|
return m._pf;
|
|
}
|
|
|
|
var some;
|
|
if (Array.prototype.some) {
|
|
some = Array.prototype.some;
|
|
} else {
|
|
some = function (fun) {
|
|
var t = Object(this);
|
|
var len = t.length >>> 0;
|
|
|
|
for (var i = 0; i < len; i++) {
|
|
if (i in t && fun.call(this, t[i], i, t)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
};
|
|
}
|
|
|
|
function isValid(m) {
|
|
if (m._isValid == null) {
|
|
var flags = getParsingFlags(m);
|
|
var parsedParts = some.call(flags.parsedDateParts, function (i) {
|
|
return i != null;
|
|
});
|
|
var isNowValid = !isNaN(m._d.getTime()) &&
|
|
flags.overflow < 0 &&
|
|
!flags.empty &&
|
|
!flags.invalidMonth &&
|
|
!flags.invalidWeekday &&
|
|
!flags.weekdayMismatch &&
|
|
!flags.nullInput &&
|
|
!flags.invalidFormat &&
|
|
!flags.userInvalidated &&
|
|
(!flags.meridiem || (flags.meridiem && parsedParts));
|
|
|
|
if (m._strict) {
|
|
isNowValid = isNowValid &&
|
|
flags.charsLeftOver === 0 &&
|
|
flags.unusedTokens.length === 0 &&
|
|
flags.bigHour === undefined;
|
|
}
|
|
|
|
if (Object.isFrozen == null || !Object.isFrozen(m)) {
|
|
m._isValid = isNowValid;
|
|
}
|
|
else {
|
|
return isNowValid;
|
|
}
|
|
}
|
|
return m._isValid;
|
|
}
|
|
|
|
function createInvalid (flags) {
|
|
var m = createUTC(NaN);
|
|
if (flags != null) {
|
|
extend(getParsingFlags(m), flags);
|
|
}
|
|
else {
|
|
getParsingFlags(m).userInvalidated = true;
|
|
}
|
|
|
|
return m;
|
|
}
|
|
|
|
// Plugins that add properties should also add the key here (null value),
|
|
// so we can properly clone ourselves.
|
|
var momentProperties = hooks.momentProperties = [];
|
|
|
|
function copyConfig(to, from) {
|
|
var i, prop, val;
|
|
|
|
if (!isUndefined(from._isAMomentObject)) {
|
|
to._isAMomentObject = from._isAMomentObject;
|
|
}
|
|
if (!isUndefined(from._i)) {
|
|
to._i = from._i;
|
|
}
|
|
if (!isUndefined(from._f)) {
|
|
to._f = from._f;
|
|
}
|
|
if (!isUndefined(from._l)) {
|
|
to._l = from._l;
|
|
}
|
|
if (!isUndefined(from._strict)) {
|
|
to._strict = from._strict;
|
|
}
|
|
if (!isUndefined(from._tzm)) {
|
|
to._tzm = from._tzm;
|
|
}
|
|
if (!isUndefined(from._isUTC)) {
|
|
to._isUTC = from._isUTC;
|
|
}
|
|
if (!isUndefined(from._offset)) {
|
|
to._offset = from._offset;
|
|
}
|
|
if (!isUndefined(from._pf)) {
|
|
to._pf = getParsingFlags(from);
|
|
}
|
|
if (!isUndefined(from._locale)) {
|
|
to._locale = from._locale;
|
|
}
|
|
|
|
if (momentProperties.length > 0) {
|
|
for (i = 0; i < momentProperties.length; i++) {
|
|
prop = momentProperties[i];
|
|
val = from[prop];
|
|
if (!isUndefined(val)) {
|
|
to[prop] = val;
|
|
}
|
|
}
|
|
}
|
|
|
|
return to;
|
|
}
|
|
|
|
var updateInProgress = false;
|
|
|
|
// Moment prototype object
|
|
function Moment(config) {
|
|
copyConfig(this, config);
|
|
this._d = new Date(config._d != null ? config._d.getTime() : NaN);
|
|
if (!this.isValid()) {
|
|
this._d = new Date(NaN);
|
|
}
|
|
// Prevent infinite loop in case updateOffset creates new moment
|
|
// objects.
|
|
if (updateInProgress === false) {
|
|
updateInProgress = true;
|
|
hooks.updateOffset(this);
|
|
updateInProgress = false;
|
|
}
|
|
}
|
|
|
|
function isMoment (obj) {
|
|
return obj instanceof Moment || (obj != null && obj._isAMomentObject != null);
|
|
}
|
|
|
|
function absFloor (number) {
|
|
if (number < 0) {
|
|
// -0 -> 0
|
|
return Math.ceil(number) || 0;
|
|
} else {
|
|
return Math.floor(number);
|
|
}
|
|
}
|
|
|
|
function toInt(argumentForCoercion) {
|
|
var coercedNumber = +argumentForCoercion,
|
|
value = 0;
|
|
|
|
if (coercedNumber !== 0 && isFinite(coercedNumber)) {
|
|
value = absFloor(coercedNumber);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
// 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 warn(msg) {
|
|
if (hooks.suppressDeprecationWarnings === false &&
|
|
(typeof console !== 'undefined') && console.warn) {
|
|
console.warn('Deprecation warning: ' + msg);
|
|
}
|
|
}
|
|
|
|
function deprecate(msg, fn) {
|
|
var firstTime = true;
|
|
|
|
return extend(function () {
|
|
if (hooks.deprecationHandler != null) {
|
|
hooks.deprecationHandler(null, msg);
|
|
}
|
|
if (firstTime) {
|
|
var args = [];
|
|
var arg;
|
|
for (var i = 0; i < arguments.length; i++) {
|
|
arg = '';
|
|
if (typeof arguments[i] === 'object') {
|
|
arg += '\n[' + i + '] ';
|
|
for (var key in arguments[0]) {
|
|
arg += key + ': ' + arguments[0][key] + ', ';
|
|
}
|
|
arg = arg.slice(0, -2); // Remove trailing comma and space
|
|
} else {
|
|
arg = arguments[i];
|
|
}
|
|
args.push(arg);
|
|
}
|
|
warn(msg + '\nArguments: ' + Array.prototype.slice.call(args).join('') + '\n' + (new Error()).stack);
|
|
firstTime = false;
|
|
}
|
|
return fn.apply(this, arguments);
|
|
}, fn);
|
|
}
|
|
|
|
var deprecations = {};
|
|
|
|
function deprecateSimple(name, msg) {
|
|
if (hooks.deprecationHandler != null) {
|
|
hooks.deprecationHandler(name, msg);
|
|
}
|
|
if (!deprecations[name]) {
|
|
warn(msg);
|
|
deprecations[name] = true;
|
|
}
|
|
}
|
|
|
|
hooks.suppressDeprecationWarnings = false;
|
|
hooks.deprecationHandler = null;
|
|
|
|
function isFunction(input) {
|
|
return input instanceof Function || Object.prototype.toString.call(input) === '[object Function]';
|
|
}
|
|
|
|
function set (config) {
|
|
var prop, i;
|
|
for (i in config) {
|
|
prop = config[i];
|
|
if (isFunction(prop)) {
|
|
this[i] = prop;
|
|
} else {
|
|
this['_' + i] = prop;
|
|
}
|
|
}
|
|
this._config = config;
|
|
// Lenient ordinal parsing accepts just a number in addition to
|
|
// number + (possibly) stuff coming from _dayOfMonthOrdinalParse.
|
|
// TODO: Remove "ordinalParse" fallback in next major release.
|
|
this._dayOfMonthOrdinalParseLenient = new RegExp(
|
|
(this._dayOfMonthOrdinalParse.source || this._ordinalParse.source) +
|
|
'|' + (/\d{1,2}/).source);
|
|
}
|
|
|
|
function mergeConfigs(parentConfig, childConfig) {
|
|
var res = extend({}, parentConfig), prop;
|
|
for (prop in childConfig) {
|
|
if (hasOwnProp(childConfig, prop)) {
|
|
if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) {
|
|
res[prop] = {};
|
|
extend(res[prop], parentConfig[prop]);
|
|
extend(res[prop], childConfig[prop]);
|
|
} else if (childConfig[prop] != null) {
|
|
res[prop] = childConfig[prop];
|
|
} else {
|
|
delete res[prop];
|
|
}
|
|
}
|
|
}
|
|
for (prop in parentConfig) {
|
|
if (hasOwnProp(parentConfig, prop) &&
|
|
!hasOwnProp(childConfig, prop) &&
|
|
isObject(parentConfig[prop])) {
|
|
// make sure changes to properties don't modify parent config
|
|
res[prop] = extend({}, res[prop]);
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
function Locale(config) {
|
|
if (config != null) {
|
|
this.set(config);
|
|
}
|
|
}
|
|
|
|
var keys;
|
|
|
|
if (Object.keys) {
|
|
keys = Object.keys;
|
|
} else {
|
|
keys = function (obj) {
|
|
var i, res = [];
|
|
for (i in obj) {
|
|
if (hasOwnProp(obj, i)) {
|
|
res.push(i);
|
|
}
|
|
}
|
|
return res;
|
|
};
|
|
}
|
|
|
|
var defaultCalendar = {
|
|
sameDay : '[Today at] LT',
|
|
nextDay : '[Tomorrow at] LT',
|
|
nextWeek : 'dddd [at] LT',
|
|
lastDay : '[Yesterday at] LT',
|
|
lastWeek : '[Last] dddd [at] LT',
|
|
sameElse : 'L'
|
|
};
|
|
|
|
function calendar (key, mom, now) {
|
|
var output = this._calendar[key] || this._calendar['sameElse'];
|
|
return isFunction(output) ? output.call(mom, now) : output;
|
|
}
|
|
|
|
var defaultLongDateFormat = {
|
|
LTS : 'h:mm:ss A',
|
|
LT : 'h:mm A',
|
|
L : 'MM/DD/YYYY',
|
|
LL : 'MMMM D, YYYY',
|
|
LLL : 'MMMM D, YYYY h:mm A',
|
|
LLLL : 'dddd, MMMM D, YYYY h:mm A'
|
|
};
|
|
|
|
function longDateFormat (key) {
|
|
var format = this._longDateFormat[key],
|
|
formatUpper = this._longDateFormat[key.toUpperCase()];
|
|
|
|
if (format || !formatUpper) {
|
|
return format;
|
|
}
|
|
|
|
this._longDateFormat[key] = formatUpper.replace(/MMMM|MM|DD|dddd/g, function (val) {
|
|
return val.slice(1);
|
|
});
|
|
|
|
return this._longDateFormat[key];
|
|
}
|
|
|
|
var defaultInvalidDate = 'Invalid date';
|
|
|
|
function invalidDate () {
|
|
return this._invalidDate;
|
|
}
|
|
|
|
var defaultOrdinal = '%d';
|
|
var defaultDayOfMonthOrdinalParse = /\d{1,2}/;
|
|
|
|
function ordinal (number) {
|
|
return this._ordinal.replace('%d', number);
|
|
}
|
|
|
|
var defaultRelativeTime = {
|
|
future : 'in %s',
|
|
past : '%s ago',
|
|
s : 'a few seconds',
|
|
ss : '%d 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'
|
|
};
|
|
|
|
function relativeTime (number, withoutSuffix, string, isFuture) {
|
|
var output = this._relativeTime[string];
|
|
return (isFunction(output)) ?
|
|
output(number, withoutSuffix, string, isFuture) :
|
|
output.replace(/%d/i, number);
|
|
}
|
|
|
|
function pastFuture (diff, output) {
|
|
var format = this._relativeTime[diff > 0 ? 'future' : 'past'];
|
|
return isFunction(format) ? format(output) : format.replace(/%s/i, output);
|
|
}
|
|
|
|
var aliases = {};
|
|
|
|
function addUnitAlias (unit, shorthand) {
|
|
var lowerCase = unit.toLowerCase();
|
|
aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit;
|
|
}
|
|
|
|
function normalizeUnits(units) {
|
|
return typeof units === 'string' ? aliases[units] || aliases[units.toLowerCase()] : undefined;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
var priorities = {};
|
|
|
|
function addUnitPriority(unit, priority) {
|
|
priorities[unit] = priority;
|
|
}
|
|
|
|
function getPrioritizedUnits(unitsObj) {
|
|
var units = [];
|
|
for (var u in unitsObj) {
|
|
units.push({unit: u, priority: priorities[u]});
|
|
}
|
|
units.sort(function (a, b) {
|
|
return a.priority - b.priority;
|
|
});
|
|
return units;
|
|
}
|
|
|
|
function zeroFill(number, targetLength, forceSign) {
|
|
var absNumber = '' + Math.abs(number),
|
|
zerosToFill = targetLength - absNumber.length,
|
|
sign = number >= 0;
|
|
return (sign ? (forceSign ? '+' : '') : '-') +
|
|
Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + absNumber;
|
|
}
|
|
|
|
var formattingTokens = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g;
|
|
|
|
var localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g;
|
|
|
|
var formatFunctions = {};
|
|
|
|
var formatTokenFunctions = {};
|
|
|
|
// token: 'M'
|
|
// padded: ['MM', 2]
|
|
// ordinal: 'Mo'
|
|
// callback: function () { this.month() + 1 }
|
|
function addFormatToken (token, padded, ordinal, callback) {
|
|
var func = callback;
|
|
if (typeof callback === 'string') {
|
|
func = function () {
|
|
return this[callback]();
|
|
};
|
|
}
|
|
if (token) {
|
|
formatTokenFunctions[token] = func;
|
|
}
|
|
if (padded) {
|
|
formatTokenFunctions[padded[0]] = function () {
|
|
return zeroFill(func.apply(this, arguments), padded[1], padded[2]);
|
|
};
|
|
}
|
|
if (ordinal) {
|
|
formatTokenFunctions[ordinal] = function () {
|
|
return this.localeData().ordinal(func.apply(this, arguments), token);
|
|
};
|
|
}
|
|
}
|
|
|
|
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 = '', i;
|
|
for (i = 0; i < length; i++) {
|
|
output += isFunction(array[i]) ? 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());
|
|
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;
|
|
}
|
|
|
|
var match1 = /\d/; // 0 - 9
|
|
var match2 = /\d\d/; // 00 - 99
|
|
var match3 = /\d{3}/; // 000 - 999
|
|
var match4 = /\d{4}/; // 0000 - 9999
|
|
var match6 = /[+-]?\d{6}/; // -999999 - 999999
|
|
var match1to2 = /\d\d?/; // 0 - 99
|
|
var match3to4 = /\d\d\d\d?/; // 999 - 9999
|
|
var match5to6 = /\d\d\d\d\d\d?/; // 99999 - 999999
|
|
var match1to3 = /\d{1,3}/; // 0 - 999
|
|
var match1to4 = /\d{1,4}/; // 0 - 9999
|
|
var match1to6 = /[+-]?\d{1,6}/; // -999999 - 999999
|
|
|
|
var matchUnsigned = /\d+/; // 0 - inf
|
|
var matchSigned = /[+-]?\d+/; // -inf - inf
|
|
|
|
var matchOffset = /Z|[+-]\d\d:?\d\d/gi; // +00:00 -00:00 +0000 -0000 or Z
|
|
var matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi; // +00 -00 +00:00 -00:00 +0000 -0000 or Z
|
|
|
|
var matchTimestamp = /[+-]?\d+(\.\d{1,3})?/; // 123456789 123456789.123
|
|
|
|
// any word (or two) characters or numbers including two/three word month in arabic.
|
|
// includes scottish gaelic two word and hyphenated months
|
|
var matchWord = /[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i;
|
|
|
|
var regexes = {};
|
|
|
|
function addRegexToken (token, regex, strictRegex) {
|
|
regexes[token] = isFunction(regex) ? regex : function (isStrict, localeData) {
|
|
return (isStrict && strictRegex) ? strictRegex : regex;
|
|
};
|
|
}
|
|
|
|
function getParseRegexForToken (token, config) {
|
|
if (!hasOwnProp(regexes, token)) {
|
|
return new RegExp(unescapeFormat(token));
|
|
}
|
|
|
|
return regexes[token](config._strict, config._locale);
|
|
}
|
|
|
|
// Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
|
|
function unescapeFormat(s) {
|
|
return regexEscape(s.replace('\\', '').replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) {
|
|
return p1 || p2 || p3 || p4;
|
|
}));
|
|
}
|
|
|
|
function regexEscape(s) {
|
|
return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
}
|
|
|
|
var tokens = {};
|
|
|
|
function addParseToken (token, callback) {
|
|
var i, func = callback;
|
|
if (typeof token === 'string') {
|
|
token = [token];
|
|
}
|
|
if (isNumber(callback)) {
|
|
func = function (input, array) {
|
|
array[callback] = toInt(input);
|
|
};
|
|
}
|
|
for (i = 0; i < token.length; i++) {
|
|
tokens[token[i]] = func;
|
|
}
|
|
}
|
|
|
|
function addWeekParseToken (token, callback) {
|
|
addParseToken(token, function (input, array, config, token) {
|
|
config._w = config._w || {};
|
|
callback(input, config._w, config, token);
|
|
});
|
|
}
|
|
|
|
function addTimeToArrayFromToken(token, input, config) {
|
|
if (input != null && hasOwnProp(tokens, token)) {
|
|
tokens[token](input, config._a, config, token);
|
|
}
|
|
}
|
|
|
|
var YEAR = 0;
|
|
var MONTH = 1;
|
|
var DATE = 2;
|
|
var HOUR = 3;
|
|
var MINUTE = 4;
|
|
var SECOND = 5;
|
|
var MILLISECOND = 6;
|
|
var WEEK = 7;
|
|
var WEEKDAY = 8;
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('Y', 0, 0, function () {
|
|
var y = this.year();
|
|
return y <= 9999 ? '' + y : '+' + y;
|
|
});
|
|
|
|
addFormatToken(0, ['YY', 2], 0, function () {
|
|
return this.year() % 100;
|
|
});
|
|
|
|
addFormatToken(0, ['YYYY', 4], 0, 'year');
|
|
addFormatToken(0, ['YYYYY', 5], 0, 'year');
|
|
addFormatToken(0, ['YYYYYY', 6, true], 0, 'year');
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('year', 'y');
|
|
|
|
// PRIORITIES
|
|
|
|
addUnitPriority('year', 1);
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('Y', matchSigned);
|
|
addRegexToken('YY', match1to2, match2);
|
|
addRegexToken('YYYY', match1to4, match4);
|
|
addRegexToken('YYYYY', match1to6, match6);
|
|
addRegexToken('YYYYYY', match1to6, match6);
|
|
|
|
addParseToken(['YYYYY', 'YYYYYY'], YEAR);
|
|
addParseToken('YYYY', function (input, array) {
|
|
array[YEAR] = input.length === 2 ? hooks.parseTwoDigitYear(input) : toInt(input);
|
|
});
|
|
addParseToken('YY', function (input, array) {
|
|
array[YEAR] = hooks.parseTwoDigitYear(input);
|
|
});
|
|
addParseToken('Y', function (input, array) {
|
|
array[YEAR] = parseInt(input, 10);
|
|
});
|
|
|
|
// HELPERS
|
|
|
|
function daysInYear(year) {
|
|
return isLeapYear(year) ? 366 : 365;
|
|
}
|
|
|
|
function isLeapYear(year) {
|
|
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
|
|
}
|
|
|
|
// HOOKS
|
|
|
|
hooks.parseTwoDigitYear = function (input) {
|
|
return toInt(input) + (toInt(input) > 68 ? 1900 : 2000);
|
|
};
|
|
|
|
// MOMENTS
|
|
|
|
var getSetYear = makeGetSet('FullYear', true);
|
|
|
|
function getIsLeapYear () {
|
|
return isLeapYear(this.year());
|
|
}
|
|
|
|
function makeGetSet (unit, keepTime) {
|
|
return function (value) {
|
|
if (value != null) {
|
|
set$1(this, unit, value);
|
|
hooks.updateOffset(this, keepTime);
|
|
return this;
|
|
} else {
|
|
return get(this, unit);
|
|
}
|
|
};
|
|
}
|
|
|
|
function get (mom, unit) {
|
|
return mom.isValid() ?
|
|
mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]() : NaN;
|
|
}
|
|
|
|
function set$1 (mom, unit, value) {
|
|
if (mom.isValid() && !isNaN(value)) {
|
|
if (unit === 'FullYear' && isLeapYear(mom.year()) && mom.month() === 1 && mom.date() === 29) {
|
|
mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value, mom.month(), daysInMonth(value, mom.month()));
|
|
}
|
|
else {
|
|
mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value);
|
|
}
|
|
}
|
|
}
|
|
|
|
// MOMENTS
|
|
|
|
function stringGet (units) {
|
|
units = normalizeUnits(units);
|
|
if (isFunction(this[units])) {
|
|
return this[units]();
|
|
}
|
|
return this;
|
|
}
|
|
|
|
|
|
function stringSet (units, value) {
|
|
if (typeof units === 'object') {
|
|
units = normalizeObjectUnits(units);
|
|
var prioritized = getPrioritizedUnits(units);
|
|
for (var i = 0; i < prioritized.length; i++) {
|
|
this[prioritized[i].unit](units[prioritized[i].unit]);
|
|
}
|
|
} else {
|
|
units = normalizeUnits(units);
|
|
if (isFunction(this[units])) {
|
|
return this[units](value);
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
|
|
function mod(n, x) {
|
|
return ((n % x) + x) % x;
|
|
}
|
|
|
|
var indexOf;
|
|
|
|
if (Array.prototype.indexOf) {
|
|
indexOf = Array.prototype.indexOf;
|
|
} else {
|
|
indexOf = function (o) {
|
|
// I know
|
|
var i;
|
|
for (i = 0; i < this.length; ++i) {
|
|
if (this[i] === o) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
};
|
|
}
|
|
|
|
function daysInMonth(year, month) {
|
|
if (isNaN(year) || isNaN(month)) {
|
|
return NaN;
|
|
}
|
|
var modMonth = mod(month, 12);
|
|
year += (month - modMonth) / 12;
|
|
return modMonth === 1 ? (isLeapYear(year) ? 29 : 28) : (31 - modMonth % 7 % 2);
|
|
}
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('M', ['MM', 2], 'Mo', function () {
|
|
return this.month() + 1;
|
|
});
|
|
|
|
addFormatToken('MMM', 0, 0, function (format) {
|
|
return this.localeData().monthsShort(this, format);
|
|
});
|
|
|
|
addFormatToken('MMMM', 0, 0, function (format) {
|
|
return this.localeData().months(this, format);
|
|
});
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('month', 'M');
|
|
|
|
// PRIORITY
|
|
|
|
addUnitPriority('month', 8);
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('M', match1to2);
|
|
addRegexToken('MM', match1to2, match2);
|
|
addRegexToken('MMM', function (isStrict, locale) {
|
|
return locale.monthsShortRegex(isStrict);
|
|
});
|
|
addRegexToken('MMMM', function (isStrict, locale) {
|
|
return locale.monthsRegex(isStrict);
|
|
});
|
|
|
|
addParseToken(['M', 'MM'], function (input, array) {
|
|
array[MONTH] = toInt(input) - 1;
|
|
});
|
|
|
|
addParseToken(['MMM', 'MMMM'], function (input, array, config, token) {
|
|
var month = config._locale.monthsParse(input, token, config._strict);
|
|
// if we didn't find a month name, mark the date as invalid.
|
|
if (month != null) {
|
|
array[MONTH] = month;
|
|
} else {
|
|
getParsingFlags(config).invalidMonth = input;
|
|
}
|
|
});
|
|
|
|
// LOCALES
|
|
|
|
var MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/;
|
|
var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_');
|
|
function localeMonths (m, format) {
|
|
if (!m) {
|
|
return isArray(this._months) ? this._months :
|
|
this._months['standalone'];
|
|
}
|
|
return isArray(this._months) ? this._months[m.month()] :
|
|
this._months[(this._months.isFormat || MONTHS_IN_FORMAT).test(format) ? 'format' : 'standalone'][m.month()];
|
|
}
|
|
|
|
var defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_');
|
|
function localeMonthsShort (m, format) {
|
|
if (!m) {
|
|
return isArray(this._monthsShort) ? this._monthsShort :
|
|
this._monthsShort['standalone'];
|
|
}
|
|
return isArray(this._monthsShort) ? this._monthsShort[m.month()] :
|
|
this._monthsShort[MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'][m.month()];
|
|
}
|
|
|
|
function handleStrictParse(monthName, format, strict) {
|
|
var i, ii, mom, llc = monthName.toLocaleLowerCase();
|
|
if (!this._monthsParse) {
|
|
// this is not used
|
|
this._monthsParse = [];
|
|
this._longMonthsParse = [];
|
|
this._shortMonthsParse = [];
|
|
for (i = 0; i < 12; ++i) {
|
|
mom = createUTC([2000, i]);
|
|
this._shortMonthsParse[i] = this.monthsShort(mom, '').toLocaleLowerCase();
|
|
this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase();
|
|
}
|
|
}
|
|
|
|
if (strict) {
|
|
if (format === 'MMM') {
|
|
ii = indexOf.call(this._shortMonthsParse, llc);
|
|
return ii !== -1 ? ii : null;
|
|
} else {
|
|
ii = indexOf.call(this._longMonthsParse, llc);
|
|
return ii !== -1 ? ii : null;
|
|
}
|
|
} else {
|
|
if (format === 'MMM') {
|
|
ii = indexOf.call(this._shortMonthsParse, llc);
|
|
if (ii !== -1) {
|
|
return ii;
|
|
}
|
|
ii = indexOf.call(this._longMonthsParse, llc);
|
|
return ii !== -1 ? ii : null;
|
|
} else {
|
|
ii = indexOf.call(this._longMonthsParse, llc);
|
|
if (ii !== -1) {
|
|
return ii;
|
|
}
|
|
ii = indexOf.call(this._shortMonthsParse, llc);
|
|
return ii !== -1 ? ii : null;
|
|
}
|
|
}
|
|
}
|
|
|
|
function localeMonthsParse (monthName, format, strict) {
|
|
var i, mom, regex;
|
|
|
|
if (this._monthsParseExact) {
|
|
return handleStrictParse.call(this, monthName, format, strict);
|
|
}
|
|
|
|
if (!this._monthsParse) {
|
|
this._monthsParse = [];
|
|
this._longMonthsParse = [];
|
|
this._shortMonthsParse = [];
|
|
}
|
|
|
|
// TODO: add sorting
|
|
// Sorting makes sure if one month (or abbr) is a prefix of another
|
|
// see sorting in computeMonthsParse
|
|
for (i = 0; i < 12; i++) {
|
|
// make the regex if we don't have it already
|
|
mom = createUTC([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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// MOMENTS
|
|
|
|
function setMonth (mom, value) {
|
|
var dayOfMonth;
|
|
|
|
if (!mom.isValid()) {
|
|
// No op
|
|
return mom;
|
|
}
|
|
|
|
if (typeof value === 'string') {
|
|
if (/^\d+$/.test(value)) {
|
|
value = toInt(value);
|
|
} else {
|
|
value = mom.localeData().monthsParse(value);
|
|
// TODO: Another silent failure?
|
|
if (!isNumber(value)) {
|
|
return mom;
|
|
}
|
|
}
|
|
}
|
|
|
|
dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value));
|
|
mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth);
|
|
return mom;
|
|
}
|
|
|
|
function getSetMonth (value) {
|
|
if (value != null) {
|
|
setMonth(this, value);
|
|
hooks.updateOffset(this, true);
|
|
return this;
|
|
} else {
|
|
return get(this, 'Month');
|
|
}
|
|
}
|
|
|
|
function getDaysInMonth () {
|
|
return daysInMonth(this.year(), this.month());
|
|
}
|
|
|
|
var defaultMonthsShortRegex = matchWord;
|
|
function monthsShortRegex (isStrict) {
|
|
if (this._monthsParseExact) {
|
|
if (!hasOwnProp(this, '_monthsRegex')) {
|
|
computeMonthsParse.call(this);
|
|
}
|
|
if (isStrict) {
|
|
return this._monthsShortStrictRegex;
|
|
} else {
|
|
return this._monthsShortRegex;
|
|
}
|
|
} else {
|
|
if (!hasOwnProp(this, '_monthsShortRegex')) {
|
|
this._monthsShortRegex = defaultMonthsShortRegex;
|
|
}
|
|
return this._monthsShortStrictRegex && isStrict ?
|
|
this._monthsShortStrictRegex : this._monthsShortRegex;
|
|
}
|
|
}
|
|
|
|
var defaultMonthsRegex = matchWord;
|
|
function monthsRegex (isStrict) {
|
|
if (this._monthsParseExact) {
|
|
if (!hasOwnProp(this, '_monthsRegex')) {
|
|
computeMonthsParse.call(this);
|
|
}
|
|
if (isStrict) {
|
|
return this._monthsStrictRegex;
|
|
} else {
|
|
return this._monthsRegex;
|
|
}
|
|
} else {
|
|
if (!hasOwnProp(this, '_monthsRegex')) {
|
|
this._monthsRegex = defaultMonthsRegex;
|
|
}
|
|
return this._monthsStrictRegex && isStrict ?
|
|
this._monthsStrictRegex : this._monthsRegex;
|
|
}
|
|
}
|
|
|
|
function computeMonthsParse () {
|
|
function cmpLenRev(a, b) {
|
|
return b.length - a.length;
|
|
}
|
|
|
|
var shortPieces = [], longPieces = [], mixedPieces = [],
|
|
i, mom;
|
|
for (i = 0; i < 12; i++) {
|
|
// make the regex if we don't have it already
|
|
mom = createUTC([2000, i]);
|
|
shortPieces.push(this.monthsShort(mom, ''));
|
|
longPieces.push(this.months(mom, ''));
|
|
mixedPieces.push(this.months(mom, ''));
|
|
mixedPieces.push(this.monthsShort(mom, ''));
|
|
}
|
|
// Sorting makes sure if one month (or abbr) is a prefix of another it
|
|
// will match the longer piece.
|
|
shortPieces.sort(cmpLenRev);
|
|
longPieces.sort(cmpLenRev);
|
|
mixedPieces.sort(cmpLenRev);
|
|
for (i = 0; i < 12; i++) {
|
|
shortPieces[i] = regexEscape(shortPieces[i]);
|
|
longPieces[i] = regexEscape(longPieces[i]);
|
|
}
|
|
for (i = 0; i < 24; i++) {
|
|
mixedPieces[i] = regexEscape(mixedPieces[i]);
|
|
}
|
|
|
|
this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
|
|
this._monthsShortRegex = this._monthsRegex;
|
|
this._monthsStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i');
|
|
this._monthsShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i');
|
|
}
|
|
|
|
function createDate (y, m, d, h, M, s, ms) {
|
|
// can't just apply() to create a date:
|
|
// https://stackoverflow.com/q/181348
|
|
var date = new Date(y, m, d, h, M, s, ms);
|
|
|
|
// the date constructor remaps years 0-99 to 1900-1999
|
|
if (y < 100 && y >= 0 && isFinite(date.getFullYear())) {
|
|
date.setFullYear(y);
|
|
}
|
|
return date;
|
|
}
|
|
|
|
function createUTCDate (y) {
|
|
var date = new Date(Date.UTC.apply(null, arguments));
|
|
|
|
// the Date.UTC function remaps years 0-99 to 1900-1999
|
|
if (y < 100 && y >= 0 && isFinite(date.getUTCFullYear())) {
|
|
date.setUTCFullYear(y);
|
|
}
|
|
return date;
|
|
}
|
|
|
|
// start-of-first-week - start-of-year
|
|
function firstWeekOffset(year, dow, doy) {
|
|
var // first-week day -- which january is always in the first week (4 for iso, 1 for other)
|
|
fwd = 7 + dow - doy,
|
|
// first-week day local weekday -- which local weekday is fwd
|
|
fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7;
|
|
|
|
return -fwdlw + fwd - 1;
|
|
}
|
|
|
|
// https://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday
|
|
function dayOfYearFromWeeks(year, week, weekday, dow, doy) {
|
|
var localWeekday = (7 + weekday - dow) % 7,
|
|
weekOffset = firstWeekOffset(year, dow, doy),
|
|
dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset,
|
|
resYear, resDayOfYear;
|
|
|
|
if (dayOfYear <= 0) {
|
|
resYear = year - 1;
|
|
resDayOfYear = daysInYear(resYear) + dayOfYear;
|
|
} else if (dayOfYear > daysInYear(year)) {
|
|
resYear = year + 1;
|
|
resDayOfYear = dayOfYear - daysInYear(year);
|
|
} else {
|
|
resYear = year;
|
|
resDayOfYear = dayOfYear;
|
|
}
|
|
|
|
return {
|
|
year: resYear,
|
|
dayOfYear: resDayOfYear
|
|
};
|
|
}
|
|
|
|
function weekOfYear(mom, dow, doy) {
|
|
var weekOffset = firstWeekOffset(mom.year(), dow, doy),
|
|
week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1,
|
|
resWeek, resYear;
|
|
|
|
if (week < 1) {
|
|
resYear = mom.year() - 1;
|
|
resWeek = week + weeksInYear(resYear, dow, doy);
|
|
} else if (week > weeksInYear(mom.year(), dow, doy)) {
|
|
resWeek = week - weeksInYear(mom.year(), dow, doy);
|
|
resYear = mom.year() + 1;
|
|
} else {
|
|
resYear = mom.year();
|
|
resWeek = week;
|
|
}
|
|
|
|
return {
|
|
week: resWeek,
|
|
year: resYear
|
|
};
|
|
}
|
|
|
|
function weeksInYear(year, dow, doy) {
|
|
var weekOffset = firstWeekOffset(year, dow, doy),
|
|
weekOffsetNext = firstWeekOffset(year + 1, dow, doy);
|
|
return (daysInYear(year) - weekOffset + weekOffsetNext) / 7;
|
|
}
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('w', ['ww', 2], 'wo', 'week');
|
|
addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek');
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('week', 'w');
|
|
addUnitAlias('isoWeek', 'W');
|
|
|
|
// PRIORITIES
|
|
|
|
addUnitPriority('week', 5);
|
|
addUnitPriority('isoWeek', 5);
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('w', match1to2);
|
|
addRegexToken('ww', match1to2, match2);
|
|
addRegexToken('W', match1to2);
|
|
addRegexToken('WW', match1to2, match2);
|
|
|
|
addWeekParseToken(['w', 'ww', 'W', 'WW'], function (input, week, config, token) {
|
|
week[token.substr(0, 1)] = toInt(input);
|
|
});
|
|
|
|
// HELPERS
|
|
|
|
// LOCALES
|
|
|
|
function localeWeek (mom) {
|
|
return weekOfYear(mom, this._week.dow, this._week.doy).week;
|
|
}
|
|
|
|
var defaultLocaleWeek = {
|
|
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.
|
|
};
|
|
|
|
function localeFirstDayOfWeek () {
|
|
return this._week.dow;
|
|
}
|
|
|
|
function localeFirstDayOfYear () {
|
|
return this._week.doy;
|
|
}
|
|
|
|
// MOMENTS
|
|
|
|
function getSetWeek (input) {
|
|
var week = this.localeData().week(this);
|
|
return input == null ? week : this.add((input - week) * 7, 'd');
|
|
}
|
|
|
|
function getSetISOWeek (input) {
|
|
var week = weekOfYear(this, 1, 4).week;
|
|
return input == null ? week : this.add((input - week) * 7, 'd');
|
|
}
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('d', 0, 'do', 'day');
|
|
|
|
addFormatToken('dd', 0, 0, function (format) {
|
|
return this.localeData().weekdaysMin(this, format);
|
|
});
|
|
|
|
addFormatToken('ddd', 0, 0, function (format) {
|
|
return this.localeData().weekdaysShort(this, format);
|
|
});
|
|
|
|
addFormatToken('dddd', 0, 0, function (format) {
|
|
return this.localeData().weekdays(this, format);
|
|
});
|
|
|
|
addFormatToken('e', 0, 0, 'weekday');
|
|
addFormatToken('E', 0, 0, 'isoWeekday');
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('day', 'd');
|
|
addUnitAlias('weekday', 'e');
|
|
addUnitAlias('isoWeekday', 'E');
|
|
|
|
// PRIORITY
|
|
addUnitPriority('day', 11);
|
|
addUnitPriority('weekday', 11);
|
|
addUnitPriority('isoWeekday', 11);
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('d', match1to2);
|
|
addRegexToken('e', match1to2);
|
|
addRegexToken('E', match1to2);
|
|
addRegexToken('dd', function (isStrict, locale) {
|
|
return locale.weekdaysMinRegex(isStrict);
|
|
});
|
|
addRegexToken('ddd', function (isStrict, locale) {
|
|
return locale.weekdaysShortRegex(isStrict);
|
|
});
|
|
addRegexToken('dddd', function (isStrict, locale) {
|
|
return locale.weekdaysRegex(isStrict);
|
|
});
|
|
|
|
addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) {
|
|
var weekday = config._locale.weekdaysParse(input, token, config._strict);
|
|
// if we didn't get a weekday name, mark the date as invalid
|
|
if (weekday != null) {
|
|
week.d = weekday;
|
|
} else {
|
|
getParsingFlags(config).invalidWeekday = input;
|
|
}
|
|
});
|
|
|
|
addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) {
|
|
week[token] = toInt(input);
|
|
});
|
|
|
|
// HELPERS
|
|
|
|
function parseWeekday(input, locale) {
|
|
if (typeof input !== 'string') {
|
|
return input;
|
|
}
|
|
|
|
if (!isNaN(input)) {
|
|
return parseInt(input, 10);
|
|
}
|
|
|
|
input = locale.weekdaysParse(input);
|
|
if (typeof input === 'number') {
|
|
return input;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function parseIsoWeekday(input, locale) {
|
|
if (typeof input === 'string') {
|
|
return locale.weekdaysParse(input) % 7 || 7;
|
|
}
|
|
return isNaN(input) ? null : input;
|
|
}
|
|
|
|
// LOCALES
|
|
|
|
var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_');
|
|
function localeWeekdays (m, format) {
|
|
if (!m) {
|
|
return isArray(this._weekdays) ? this._weekdays :
|
|
this._weekdays['standalone'];
|
|
}
|
|
return isArray(this._weekdays) ? this._weekdays[m.day()] :
|
|
this._weekdays[this._weekdays.isFormat.test(format) ? 'format' : 'standalone'][m.day()];
|
|
}
|
|
|
|
var defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_');
|
|
function localeWeekdaysShort (m) {
|
|
return (m) ? this._weekdaysShort[m.day()] : this._weekdaysShort;
|
|
}
|
|
|
|
var defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_');
|
|
function localeWeekdaysMin (m) {
|
|
return (m) ? this._weekdaysMin[m.day()] : this._weekdaysMin;
|
|
}
|
|
|
|
function handleStrictParse$1(weekdayName, format, strict) {
|
|
var i, ii, mom, llc = weekdayName.toLocaleLowerCase();
|
|
if (!this._weekdaysParse) {
|
|
this._weekdaysParse = [];
|
|
this._shortWeekdaysParse = [];
|
|
this._minWeekdaysParse = [];
|
|
|
|
for (i = 0; i < 7; ++i) {
|
|
mom = createUTC([2000, 1]).day(i);
|
|
this._minWeekdaysParse[i] = this.weekdaysMin(mom, '').toLocaleLowerCase();
|
|
this._shortWeekdaysParse[i] = this.weekdaysShort(mom, '').toLocaleLowerCase();
|
|
this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase();
|
|
}
|
|
}
|
|
|
|
if (strict) {
|
|
if (format === 'dddd') {
|
|
ii = indexOf.call(this._weekdaysParse, llc);
|
|
return ii !== -1 ? ii : null;
|
|
} else if (format === 'ddd') {
|
|
ii = indexOf.call(this._shortWeekdaysParse, llc);
|
|
return ii !== -1 ? ii : null;
|
|
} else {
|
|
ii = indexOf.call(this._minWeekdaysParse, llc);
|
|
return ii !== -1 ? ii : null;
|
|
}
|
|
} else {
|
|
if (format === 'dddd') {
|
|
ii = indexOf.call(this._weekdaysParse, llc);
|
|
if (ii !== -1) {
|
|
return ii;
|
|
}
|
|
ii = indexOf.call(this._shortWeekdaysParse, llc);
|
|
if (ii !== -1) {
|
|
return ii;
|
|
}
|
|
ii = indexOf.call(this._minWeekdaysParse, llc);
|
|
return ii !== -1 ? ii : null;
|
|
} else if (format === 'ddd') {
|
|
ii = indexOf.call(this._shortWeekdaysParse, llc);
|
|
if (ii !== -1) {
|
|
return ii;
|
|
}
|
|
ii = indexOf.call(this._weekdaysParse, llc);
|
|
if (ii !== -1) {
|
|
return ii;
|
|
}
|
|
ii = indexOf.call(this._minWeekdaysParse, llc);
|
|
return ii !== -1 ? ii : null;
|
|
} else {
|
|
ii = indexOf.call(this._minWeekdaysParse, llc);
|
|
if (ii !== -1) {
|
|
return ii;
|
|
}
|
|
ii = indexOf.call(this._weekdaysParse, llc);
|
|
if (ii !== -1) {
|
|
return ii;
|
|
}
|
|
ii = indexOf.call(this._shortWeekdaysParse, llc);
|
|
return ii !== -1 ? ii : null;
|
|
}
|
|
}
|
|
}
|
|
|
|
function localeWeekdaysParse (weekdayName, format, strict) {
|
|
var i, mom, regex;
|
|
|
|
if (this._weekdaysParseExact) {
|
|
return handleStrictParse$1.call(this, weekdayName, format, strict);
|
|
}
|
|
|
|
if (!this._weekdaysParse) {
|
|
this._weekdaysParse = [];
|
|
this._minWeekdaysParse = [];
|
|
this._shortWeekdaysParse = [];
|
|
this._fullWeekdaysParse = [];
|
|
}
|
|
|
|
for (i = 0; i < 7; i++) {
|
|
// make the regex if we don't have it already
|
|
|
|
mom = createUTC([2000, 1]).day(i);
|
|
if (strict && !this._fullWeekdaysParse[i]) {
|
|
this._fullWeekdaysParse[i] = new RegExp('^' + this.weekdays(mom, '').replace('.', '\.?') + '$', 'i');
|
|
this._shortWeekdaysParse[i] = new RegExp('^' + this.weekdaysShort(mom, '').replace('.', '\.?') + '$', 'i');
|
|
this._minWeekdaysParse[i] = new RegExp('^' + this.weekdaysMin(mom, '').replace('.', '\.?') + '$', 'i');
|
|
}
|
|
if (!this._weekdaysParse[i]) {
|
|
regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, '');
|
|
this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');
|
|
}
|
|
// test the regex
|
|
if (strict && format === 'dddd' && this._fullWeekdaysParse[i].test(weekdayName)) {
|
|
return i;
|
|
} else if (strict && format === 'ddd' && this._shortWeekdaysParse[i].test(weekdayName)) {
|
|
return i;
|
|
} else if (strict && format === 'dd' && this._minWeekdaysParse[i].test(weekdayName)) {
|
|
return i;
|
|
} else if (!strict && this._weekdaysParse[i].test(weekdayName)) {
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
|
|
// MOMENTS
|
|
|
|
function getSetDayOfWeek (input) {
|
|
if (!this.isValid()) {
|
|
return input != null ? this : NaN;
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|
|
function getSetLocaleDayOfWeek (input) {
|
|
if (!this.isValid()) {
|
|
return input != null ? this : NaN;
|
|
}
|
|
var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7;
|
|
return input == null ? weekday : this.add(input - weekday, 'd');
|
|
}
|
|
|
|
function getSetISODayOfWeek (input) {
|
|
if (!this.isValid()) {
|
|
return input != null ? this : NaN;
|
|
}
|
|
|
|
// 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.
|
|
|
|
if (input != null) {
|
|
var weekday = parseIsoWeekday(input, this.localeData());
|
|
return this.day(this.day() % 7 ? weekday : weekday - 7);
|
|
} else {
|
|
return this.day() || 7;
|
|
}
|
|
}
|
|
|
|
var defaultWeekdaysRegex = matchWord;
|
|
function weekdaysRegex (isStrict) {
|
|
if (this._weekdaysParseExact) {
|
|
if (!hasOwnProp(this, '_weekdaysRegex')) {
|
|
computeWeekdaysParse.call(this);
|
|
}
|
|
if (isStrict) {
|
|
return this._weekdaysStrictRegex;
|
|
} else {
|
|
return this._weekdaysRegex;
|
|
}
|
|
} else {
|
|
if (!hasOwnProp(this, '_weekdaysRegex')) {
|
|
this._weekdaysRegex = defaultWeekdaysRegex;
|
|
}
|
|
return this._weekdaysStrictRegex && isStrict ?
|
|
this._weekdaysStrictRegex : this._weekdaysRegex;
|
|
}
|
|
}
|
|
|
|
var defaultWeekdaysShortRegex = matchWord;
|
|
function weekdaysShortRegex (isStrict) {
|
|
if (this._weekdaysParseExact) {
|
|
if (!hasOwnProp(this, '_weekdaysRegex')) {
|
|
computeWeekdaysParse.call(this);
|
|
}
|
|
if (isStrict) {
|
|
return this._weekdaysShortStrictRegex;
|
|
} else {
|
|
return this._weekdaysShortRegex;
|
|
}
|
|
} else {
|
|
if (!hasOwnProp(this, '_weekdaysShortRegex')) {
|
|
this._weekdaysShortRegex = defaultWeekdaysShortRegex;
|
|
}
|
|
return this._weekdaysShortStrictRegex && isStrict ?
|
|
this._weekdaysShortStrictRegex : this._weekdaysShortRegex;
|
|
}
|
|
}
|
|
|
|
var defaultWeekdaysMinRegex = matchWord;
|
|
function weekdaysMinRegex (isStrict) {
|
|
if (this._weekdaysParseExact) {
|
|
if (!hasOwnProp(this, '_weekdaysRegex')) {
|
|
computeWeekdaysParse.call(this);
|
|
}
|
|
if (isStrict) {
|
|
return this._weekdaysMinStrictRegex;
|
|
} else {
|
|
return this._weekdaysMinRegex;
|
|
}
|
|
} else {
|
|
if (!hasOwnProp(this, '_weekdaysMinRegex')) {
|
|
this._weekdaysMinRegex = defaultWeekdaysMinRegex;
|
|
}
|
|
return this._weekdaysMinStrictRegex && isStrict ?
|
|
this._weekdaysMinStrictRegex : this._weekdaysMinRegex;
|
|
}
|
|
}
|
|
|
|
|
|
function computeWeekdaysParse () {
|
|
function cmpLenRev(a, b) {
|
|
return b.length - a.length;
|
|
}
|
|
|
|
var minPieces = [], shortPieces = [], longPieces = [], mixedPieces = [],
|
|
i, mom, minp, shortp, longp;
|
|
for (i = 0; i < 7; i++) {
|
|
// make the regex if we don't have it already
|
|
mom = createUTC([2000, 1]).day(i);
|
|
minp = this.weekdaysMin(mom, '');
|
|
shortp = this.weekdaysShort(mom, '');
|
|
longp = this.weekdays(mom, '');
|
|
minPieces.push(minp);
|
|
shortPieces.push(shortp);
|
|
longPieces.push(longp);
|
|
mixedPieces.push(minp);
|
|
mixedPieces.push(shortp);
|
|
mixedPieces.push(longp);
|
|
}
|
|
// Sorting makes sure if one weekday (or abbr) is a prefix of another it
|
|
// will match the longer piece.
|
|
minPieces.sort(cmpLenRev);
|
|
shortPieces.sort(cmpLenRev);
|
|
longPieces.sort(cmpLenRev);
|
|
mixedPieces.sort(cmpLenRev);
|
|
for (i = 0; i < 7; i++) {
|
|
shortPieces[i] = regexEscape(shortPieces[i]);
|
|
longPieces[i] = regexEscape(longPieces[i]);
|
|
mixedPieces[i] = regexEscape(mixedPieces[i]);
|
|
}
|
|
|
|
this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
|
|
this._weekdaysShortRegex = this._weekdaysRegex;
|
|
this._weekdaysMinRegex = this._weekdaysRegex;
|
|
|
|
this._weekdaysStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i');
|
|
this._weekdaysShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i');
|
|
this._weekdaysMinStrictRegex = new RegExp('^(' + minPieces.join('|') + ')', 'i');
|
|
}
|
|
|
|
// FORMATTING
|
|
|
|
function hFormat() {
|
|
return this.hours() % 12 || 12;
|
|
}
|
|
|
|
function kFormat() {
|
|
return this.hours() || 24;
|
|
}
|
|
|
|
addFormatToken('H', ['HH', 2], 0, 'hour');
|
|
addFormatToken('h', ['hh', 2], 0, hFormat);
|
|
addFormatToken('k', ['kk', 2], 0, kFormat);
|
|
|
|
addFormatToken('hmm', 0, 0, function () {
|
|
return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2);
|
|
});
|
|
|
|
addFormatToken('hmmss', 0, 0, function () {
|
|
return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2) +
|
|
zeroFill(this.seconds(), 2);
|
|
});
|
|
|
|
addFormatToken('Hmm', 0, 0, function () {
|
|
return '' + this.hours() + zeroFill(this.minutes(), 2);
|
|
});
|
|
|
|
addFormatToken('Hmmss', 0, 0, function () {
|
|
return '' + this.hours() + zeroFill(this.minutes(), 2) +
|
|
zeroFill(this.seconds(), 2);
|
|
});
|
|
|
|
function meridiem (token, lowercase) {
|
|
addFormatToken(token, 0, 0, function () {
|
|
return this.localeData().meridiem(this.hours(), this.minutes(), lowercase);
|
|
});
|
|
}
|
|
|
|
meridiem('a', true);
|
|
meridiem('A', false);
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('hour', 'h');
|
|
|
|
// PRIORITY
|
|
addUnitPriority('hour', 13);
|
|
|
|
// PARSING
|
|
|
|
function matchMeridiem (isStrict, locale) {
|
|
return locale._meridiemParse;
|
|
}
|
|
|
|
addRegexToken('a', matchMeridiem);
|
|
addRegexToken('A', matchMeridiem);
|
|
addRegexToken('H', match1to2);
|
|
addRegexToken('h', match1to2);
|
|
addRegexToken('k', match1to2);
|
|
addRegexToken('HH', match1to2, match2);
|
|
addRegexToken('hh', match1to2, match2);
|
|
addRegexToken('kk', match1to2, match2);
|
|
|
|
addRegexToken('hmm', match3to4);
|
|
addRegexToken('hmmss', match5to6);
|
|
addRegexToken('Hmm', match3to4);
|
|
addRegexToken('Hmmss', match5to6);
|
|
|
|
addParseToken(['H', 'HH'], HOUR);
|
|
addParseToken(['k', 'kk'], function (input, array, config) {
|
|
var kInput = toInt(input);
|
|
array[HOUR] = kInput === 24 ? 0 : kInput;
|
|
});
|
|
addParseToken(['a', 'A'], function (input, array, config) {
|
|
config._isPm = config._locale.isPM(input);
|
|
config._meridiem = input;
|
|
});
|
|
addParseToken(['h', 'hh'], function (input, array, config) {
|
|
array[HOUR] = toInt(input);
|
|
getParsingFlags(config).bigHour = true;
|
|
});
|
|
addParseToken('hmm', function (input, array, config) {
|
|
var pos = input.length - 2;
|
|
array[HOUR] = toInt(input.substr(0, pos));
|
|
array[MINUTE] = toInt(input.substr(pos));
|
|
getParsingFlags(config).bigHour = true;
|
|
});
|
|
addParseToken('hmmss', function (input, array, config) {
|
|
var pos1 = input.length - 4;
|
|
var pos2 = input.length - 2;
|
|
array[HOUR] = toInt(input.substr(0, pos1));
|
|
array[MINUTE] = toInt(input.substr(pos1, 2));
|
|
array[SECOND] = toInt(input.substr(pos2));
|
|
getParsingFlags(config).bigHour = true;
|
|
});
|
|
addParseToken('Hmm', function (input, array, config) {
|
|
var pos = input.length - 2;
|
|
array[HOUR] = toInt(input.substr(0, pos));
|
|
array[MINUTE] = toInt(input.substr(pos));
|
|
});
|
|
addParseToken('Hmmss', function (input, array, config) {
|
|
var pos1 = input.length - 4;
|
|
var pos2 = input.length - 2;
|
|
array[HOUR] = toInt(input.substr(0, pos1));
|
|
array[MINUTE] = toInt(input.substr(pos1, 2));
|
|
array[SECOND] = toInt(input.substr(pos2));
|
|
});
|
|
|
|
// LOCALES
|
|
|
|
function localeIsPM (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');
|
|
}
|
|
|
|
var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i;
|
|
function localeMeridiem (hours, minutes, isLower) {
|
|
if (hours > 11) {
|
|
return isLower ? 'pm' : 'PM';
|
|
} else {
|
|
return isLower ? 'am' : 'AM';
|
|
}
|
|
}
|
|
|
|
|
|
// MOMENTS
|
|
|
|
// 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.
|
|
var getSetHour = makeGetSet('Hours', true);
|
|
|
|
var baseConfig = {
|
|
calendar: defaultCalendar,
|
|
longDateFormat: defaultLongDateFormat,
|
|
invalidDate: defaultInvalidDate,
|
|
ordinal: defaultOrdinal,
|
|
dayOfMonthOrdinalParse: defaultDayOfMonthOrdinalParse,
|
|
relativeTime: defaultRelativeTime,
|
|
|
|
months: defaultLocaleMonths,
|
|
monthsShort: defaultLocaleMonthsShort,
|
|
|
|
week: defaultLocaleWeek,
|
|
|
|
weekdays: defaultLocaleWeekdays,
|
|
weekdaysMin: defaultLocaleWeekdaysMin,
|
|
weekdaysShort: defaultLocaleWeekdaysShort,
|
|
|
|
meridiemParse: defaultLocaleMeridiemParse
|
|
};
|
|
|
|
// internal storage for locale config files
|
|
var locales = {};
|
|
var localeFamilies = {};
|
|
var globalLocale;
|
|
|
|
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 globalLocale;
|
|
}
|
|
|
|
function loadLocale(name) {
|
|
var oldLocale = null;
|
|
// TODO: Find a better way to register and load all the locales in Node
|
|
if (!locales[name] && (typeof module !== 'undefined') &&
|
|
module && module.exports) {
|
|
try {
|
|
oldLocale = globalLocale._abbr;
|
|
var aliasedRequire = require;
|
|
aliasedRequire('./locale/' + name);
|
|
getSetGlobalLocale(oldLocale);
|
|
} catch (e) {}
|
|
}
|
|
return locales[name];
|
|
}
|
|
|
|
// 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.
|
|
function getSetGlobalLocale (key, values) {
|
|
var data;
|
|
if (key) {
|
|
if (isUndefined(values)) {
|
|
data = getLocale(key);
|
|
}
|
|
else {
|
|
data = defineLocale(key, values);
|
|
}
|
|
|
|
if (data) {
|
|
// moment.duration._locale = moment._locale = data;
|
|
globalLocale = data;
|
|
}
|
|
else {
|
|
if ((typeof console !== 'undefined') && console.warn) {
|
|
//warn user if arguments are passed but the locale could not be set
|
|
console.warn('Locale ' + key + ' not found. Did you forget to load it?');
|
|
}
|
|
}
|
|
}
|
|
|
|
return globalLocale._abbr;
|
|
}
|
|
|
|
function defineLocale (name, config) {
|
|
if (config !== null) {
|
|
var locale, parentConfig = baseConfig;
|
|
config.abbr = name;
|
|
if (locales[name] != null) {
|
|
deprecateSimple('defineLocaleOverride',
|
|
'use moment.updateLocale(localeName, config) to change ' +
|
|
'an existing locale. moment.defineLocale(localeName, ' +
|
|
'config) should only be used for creating a new locale ' +
|
|
'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.');
|
|
parentConfig = locales[name]._config;
|
|
} else if (config.parentLocale != null) {
|
|
if (locales[config.parentLocale] != null) {
|
|
parentConfig = locales[config.parentLocale]._config;
|
|
} else {
|
|
locale = loadLocale(config.parentLocale);
|
|
if (locale != null) {
|
|
parentConfig = locale._config;
|
|
} else {
|
|
if (!localeFamilies[config.parentLocale]) {
|
|
localeFamilies[config.parentLocale] = [];
|
|
}
|
|
localeFamilies[config.parentLocale].push({
|
|
name: name,
|
|
config: config
|
|
});
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
locales[name] = new Locale(mergeConfigs(parentConfig, config));
|
|
|
|
if (localeFamilies[name]) {
|
|
localeFamilies[name].forEach(function (x) {
|
|
defineLocale(x.name, x.config);
|
|
});
|
|
}
|
|
|
|
// backwards compat for now: also set the locale
|
|
// make sure we set the locale AFTER all child locales have been
|
|
// created, so we won't end up with the child locale set.
|
|
getSetGlobalLocale(name);
|
|
|
|
|
|
return locales[name];
|
|
} else {
|
|
// useful for testing
|
|
delete locales[name];
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function updateLocale(name, config) {
|
|
if (config != null) {
|
|
var locale, tmpLocale, parentConfig = baseConfig;
|
|
// MERGE
|
|
tmpLocale = loadLocale(name);
|
|
if (tmpLocale != null) {
|
|
parentConfig = tmpLocale._config;
|
|
}
|
|
config = mergeConfigs(parentConfig, config);
|
|
locale = new Locale(config);
|
|
locale.parentLocale = locales[name];
|
|
locales[name] = locale;
|
|
|
|
// backwards compat for now: also set the locale
|
|
getSetGlobalLocale(name);
|
|
} else {
|
|
// pass null for config to unupdate, useful for tests
|
|
if (locales[name] != null) {
|
|
if (locales[name].parentLocale != null) {
|
|
locales[name] = locales[name].parentLocale;
|
|
} else if (locales[name] != null) {
|
|
delete locales[name];
|
|
}
|
|
}
|
|
}
|
|
return locales[name];
|
|
}
|
|
|
|
// returns locale data
|
|
function getLocale (key) {
|
|
var locale;
|
|
|
|
if (key && key._locale && key._locale._abbr) {
|
|
key = key._locale._abbr;
|
|
}
|
|
|
|
if (!key) {
|
|
return globalLocale;
|
|
}
|
|
|
|
if (!isArray(key)) {
|
|
//short-circuit everything else
|
|
locale = loadLocale(key);
|
|
if (locale) {
|
|
return locale;
|
|
}
|
|
key = [key];
|
|
}
|
|
|
|
return chooseLocale(key);
|
|
}
|
|
|
|
function listLocales() {
|
|
return keys(locales);
|
|
}
|
|
|
|
function checkOverflow (m) {
|
|
var overflow;
|
|
var a = m._a;
|
|
|
|
if (a && getParsingFlags(m).overflow === -2) {
|
|
overflow =
|
|
a[MONTH] < 0 || a[MONTH] > 11 ? MONTH :
|
|
a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH]) ? DATE :
|
|
a[HOUR] < 0 || a[HOUR] > 24 || (a[HOUR] === 24 && (a[MINUTE] !== 0 || a[SECOND] !== 0 || a[MILLISECOND] !== 0)) ? HOUR :
|
|
a[MINUTE] < 0 || a[MINUTE] > 59 ? MINUTE :
|
|
a[SECOND] < 0 || a[SECOND] > 59 ? SECOND :
|
|
a[MILLISECOND] < 0 || a[MILLISECOND] > 999 ? MILLISECOND :
|
|
-1;
|
|
|
|
if (getParsingFlags(m)._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) {
|
|
overflow = DATE;
|
|
}
|
|
if (getParsingFlags(m)._overflowWeeks && overflow === -1) {
|
|
overflow = WEEK;
|
|
}
|
|
if (getParsingFlags(m)._overflowWeekday && overflow === -1) {
|
|
overflow = WEEKDAY;
|
|
}
|
|
|
|
getParsingFlags(m).overflow = overflow;
|
|
}
|
|
|
|
return m;
|
|
}
|
|
|
|
// Pick the first defined of two or three arguments.
|
|
function defaults(a, b, c) {
|
|
if (a != null) {
|
|
return a;
|
|
}
|
|
if (b != null) {
|
|
return b;
|
|
}
|
|
return c;
|
|
}
|
|
|
|
function currentDateArray(config) {
|
|
// hooks is actually the exported moment object
|
|
var nowValue = new Date(hooks.now());
|
|
if (config._useUTC) {
|
|
return [nowValue.getUTCFullYear(), nowValue.getUTCMonth(), nowValue.getUTCDate()];
|
|
}
|
|
return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()];
|
|
}
|
|
|
|
// 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 configFromArray (config) {
|
|
var i, date, input = [], currentDate, expectedWeekday, 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 != null) {
|
|
yearToUse = defaults(config._a[YEAR], currentDate[YEAR]);
|
|
|
|
if (config._dayOfYear > daysInYear(yearToUse) || config._dayOfYear === 0) {
|
|
getParsingFlags(config)._overflowDayOfYear = true;
|
|
}
|
|
|
|
date = createUTCDate(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 ? createUTCDate : createDate).apply(null, input);
|
|
expectedWeekday = config._useUTC ? config._d.getUTCDay() : config._d.getDay();
|
|
|
|
// Apply timezone offset from input. The actual utcOffset can be changed
|
|
// with parseZone.
|
|
if (config._tzm != null) {
|
|
config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
|
|
}
|
|
|
|
if (config._nextDay) {
|
|
config._a[HOUR] = 24;
|
|
}
|
|
|
|
// check for mismatching day of week
|
|
if (config._w && typeof config._w.d !== 'undefined' && config._w.d !== expectedWeekday) {
|
|
getParsingFlags(config).weekdayMismatch = true;
|
|
}
|
|
}
|
|
|
|
function dayOfYearFromWeekInfo(config) {
|
|
var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow;
|
|
|
|
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 = defaults(w.GG, config._a[YEAR], weekOfYear(createLocal(), 1, 4).year);
|
|
week = defaults(w.W, 1);
|
|
weekday = defaults(w.E, 1);
|
|
if (weekday < 1 || weekday > 7) {
|
|
weekdayOverflow = true;
|
|
}
|
|
} else {
|
|
dow = config._locale._week.dow;
|
|
doy = config._locale._week.doy;
|
|
|
|
var curWeek = weekOfYear(createLocal(), dow, doy);
|
|
|
|
weekYear = defaults(w.gg, config._a[YEAR], curWeek.year);
|
|
|
|
// Default to current week.
|
|
week = defaults(w.w, curWeek.week);
|
|
|
|
if (w.d != null) {
|
|
// weekday -- low day numbers are considered next week
|
|
weekday = w.d;
|
|
if (weekday < 0 || weekday > 6) {
|
|
weekdayOverflow = true;
|
|
}
|
|
} else if (w.e != null) {
|
|
// local weekday -- counting starts from begining of week
|
|
weekday = w.e + dow;
|
|
if (w.e < 0 || w.e > 6) {
|
|
weekdayOverflow = true;
|
|
}
|
|
} else {
|
|
// default to begining of week
|
|
weekday = dow;
|
|
}
|
|
}
|
|
if (week < 1 || week > weeksInYear(weekYear, dow, doy)) {
|
|
getParsingFlags(config)._overflowWeeks = true;
|
|
} else if (weekdayOverflow != null) {
|
|
getParsingFlags(config)._overflowWeekday = true;
|
|
} else {
|
|
temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy);
|
|
config._a[YEAR] = temp.year;
|
|
config._dayOfYear = temp.dayOfYear;
|
|
}
|
|
}
|
|
|
|
// iso 8601 regex
|
|
// 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00)
|
|
var extendedIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/;
|
|
var basicIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/;
|
|
|
|
var tzRegex = /Z|[+-]\d\d(?::?\d\d)?/;
|
|
|
|
var isoDates = [
|
|
['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/],
|
|
['YYYY-MM-DD', /\d{4}-\d\d-\d\d/],
|
|
['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/],
|
|
['GGGG-[W]WW', /\d{4}-W\d\d/, false],
|
|
['YYYY-DDD', /\d{4}-\d{3}/],
|
|
['YYYY-MM', /\d{4}-\d\d/, false],
|
|
['YYYYYYMMDD', /[+-]\d{10}/],
|
|
['YYYYMMDD', /\d{8}/],
|
|
// YYYYMM is NOT allowed by the standard
|
|
['GGGG[W]WWE', /\d{4}W\d{3}/],
|
|
['GGGG[W]WW', /\d{4}W\d{2}/, false],
|
|
['YYYYDDD', /\d{7}/]
|
|
];
|
|
|
|
// iso time formats and regexes
|
|
var isoTimes = [
|
|
['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/],
|
|
['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/],
|
|
['HH:mm:ss', /\d\d:\d\d:\d\d/],
|
|
['HH:mm', /\d\d:\d\d/],
|
|
['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/],
|
|
['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/],
|
|
['HHmmss', /\d\d\d\d\d\d/],
|
|
['HHmm', /\d\d\d\d/],
|
|
['HH', /\d\d/]
|
|
];
|
|
|
|
var aspNetJsonRegex = /^\/?Date\((\-?\d+)/i;
|
|
|
|
// date from iso format
|
|
function configFromISO(config) {
|
|
var i, l,
|
|
string = config._i,
|
|
match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string),
|
|
allowTime, dateFormat, timeFormat, tzFormat;
|
|
|
|
if (match) {
|
|
getParsingFlags(config).iso = true;
|
|
|
|
for (i = 0, l = isoDates.length; i < l; i++) {
|
|
if (isoDates[i][1].exec(match[1])) {
|
|
dateFormat = isoDates[i][0];
|
|
allowTime = isoDates[i][2] !== false;
|
|
break;
|
|
}
|
|
}
|
|
if (dateFormat == null) {
|
|
config._isValid = false;
|
|
return;
|
|
}
|
|
if (match[3]) {
|
|
for (i = 0, l = isoTimes.length; i < l; i++) {
|
|
if (isoTimes[i][1].exec(match[3])) {
|
|
// match[2] should be 'T' or space
|
|
timeFormat = (match[2] || ' ') + isoTimes[i][0];
|
|
break;
|
|
}
|
|
}
|
|
if (timeFormat == null) {
|
|
config._isValid = false;
|
|
return;
|
|
}
|
|
}
|
|
if (!allowTime && timeFormat != null) {
|
|
config._isValid = false;
|
|
return;
|
|
}
|
|
if (match[4]) {
|
|
if (tzRegex.exec(match[4])) {
|
|
tzFormat = 'Z';
|
|
} else {
|
|
config._isValid = false;
|
|
return;
|
|
}
|
|
}
|
|
config._f = dateFormat + (timeFormat || '') + (tzFormat || '');
|
|
configFromStringAndFormat(config);
|
|
} else {
|
|
config._isValid = false;
|
|
}
|
|
}
|
|
|
|
// RFC 2822 regex: For details see https://tools.ietf.org/html/rfc2822#section-3.3
|
|
var rfc2822 = /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/;
|
|
|
|
function extractFromRFC2822Strings(yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr) {
|
|
var result = [
|
|
untruncateYear(yearStr),
|
|
defaultLocaleMonthsShort.indexOf(monthStr),
|
|
parseInt(dayStr, 10),
|
|
parseInt(hourStr, 10),
|
|
parseInt(minuteStr, 10)
|
|
];
|
|
|
|
if (secondStr) {
|
|
result.push(parseInt(secondStr, 10));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function untruncateYear(yearStr) {
|
|
var year = parseInt(yearStr, 10);
|
|
if (year <= 49) {
|
|
return 2000 + year;
|
|
} else if (year <= 999) {
|
|
return 1900 + year;
|
|
}
|
|
return year;
|
|
}
|
|
|
|
function preprocessRFC2822(s) {
|
|
// Remove comments and folding whitespace and replace multiple-spaces with a single space
|
|
return s.replace(/\([^)]*\)|[\n\t]/g, ' ').replace(/(\s\s+)/g, ' ').trim();
|
|
}
|
|
|
|
function checkWeekday(weekdayStr, parsedInput, config) {
|
|
if (weekdayStr) {
|
|
// TODO: Replace the vanilla JS Date object with an indepentent day-of-week check.
|
|
var weekdayProvided = defaultLocaleWeekdaysShort.indexOf(weekdayStr),
|
|
weekdayActual = new Date(parsedInput[0], parsedInput[1], parsedInput[2]).getDay();
|
|
if (weekdayProvided !== weekdayActual) {
|
|
getParsingFlags(config).weekdayMismatch = true;
|
|
config._isValid = false;
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
var obsOffsets = {
|
|
UT: 0,
|
|
GMT: 0,
|
|
EDT: -4 * 60,
|
|
EST: -5 * 60,
|
|
CDT: -5 * 60,
|
|
CST: -6 * 60,
|
|
MDT: -6 * 60,
|
|
MST: -7 * 60,
|
|
PDT: -7 * 60,
|
|
PST: -8 * 60
|
|
};
|
|
|
|
function calculateOffset(obsOffset, militaryOffset, numOffset) {
|
|
if (obsOffset) {
|
|
return obsOffsets[obsOffset];
|
|
} else if (militaryOffset) {
|
|
// the only allowed military tz is Z
|
|
return 0;
|
|
} else {
|
|
var hm = parseInt(numOffset, 10);
|
|
var m = hm % 100, h = (hm - m) / 100;
|
|
return h * 60 + m;
|
|
}
|
|
}
|
|
|
|
// date and time from ref 2822 format
|
|
function configFromRFC2822(config) {
|
|
var match = rfc2822.exec(preprocessRFC2822(config._i));
|
|
if (match) {
|
|
var parsedArray = extractFromRFC2822Strings(match[4], match[3], match[2], match[5], match[6], match[7]);
|
|
if (!checkWeekday(match[1], parsedArray, config)) {
|
|
return;
|
|
}
|
|
|
|
config._a = parsedArray;
|
|
config._tzm = calculateOffset(match[8], match[9], match[10]);
|
|
|
|
config._d = createUTCDate.apply(null, config._a);
|
|
config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
|
|
|
|
getParsingFlags(config).rfc2822 = true;
|
|
} else {
|
|
config._isValid = false;
|
|
}
|
|
}
|
|
|
|
// date from iso format or fallback
|
|
function configFromString(config) {
|
|
var matched = aspNetJsonRegex.exec(config._i);
|
|
|
|
if (matched !== null) {
|
|
config._d = new Date(+matched[1]);
|
|
return;
|
|
}
|
|
|
|
configFromISO(config);
|
|
if (config._isValid === false) {
|
|
delete config._isValid;
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
configFromRFC2822(config);
|
|
if (config._isValid === false) {
|
|
delete config._isValid;
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
// Final attempt, use Input Fallback
|
|
hooks.createFromInputFallback(config);
|
|
}
|
|
|
|
hooks.createFromInputFallback = deprecate(
|
|
'value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), ' +
|
|
'which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are ' +
|
|
'discouraged and will be removed in an upcoming major release. Please refer to ' +
|
|
'http://momentjs.com/guides/#/warnings/js-date/ for more info.',
|
|
function (config) {
|
|
config._d = new Date(config._i + (config._useUTC ? ' UTC' : ''));
|
|
}
|
|
);
|
|
|
|
// constant that refers to the ISO standard
|
|
hooks.ISO_8601 = function () {};
|
|
|
|
// constant that refers to the RFC 2822 form
|
|
hooks.RFC_2822 = function () {};
|
|
|
|
// date from string and format string
|
|
function configFromStringAndFormat(config) {
|
|
// TODO: Move this to another part of the creation flow to prevent circular deps
|
|
if (config._f === hooks.ISO_8601) {
|
|
configFromISO(config);
|
|
return;
|
|
}
|
|
if (config._f === hooks.RFC_2822) {
|
|
configFromRFC2822(config);
|
|
return;
|
|
}
|
|
config._a = [];
|
|
getParsingFlags(config).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];
|
|
// console.log('token', token, 'parsedInput', parsedInput,
|
|
// 'regex', getParseRegexForToken(token, config));
|
|
if (parsedInput) {
|
|
skipped = string.substr(0, string.indexOf(parsedInput));
|
|
if (skipped.length > 0) {
|
|
getParsingFlags(config).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) {
|
|
getParsingFlags(config).empty = false;
|
|
}
|
|
else {
|
|
getParsingFlags(config).unusedTokens.push(token);
|
|
}
|
|
addTimeToArrayFromToken(token, parsedInput, config);
|
|
}
|
|
else if (config._strict && !parsedInput) {
|
|
getParsingFlags(config).unusedTokens.push(token);
|
|
}
|
|
}
|
|
|
|
// add remaining unparsed input length to the string
|
|
getParsingFlags(config).charsLeftOver = stringLength - totalParsedInputLength;
|
|
if (string.length > 0) {
|
|
getParsingFlags(config).unusedInput.push(string);
|
|
}
|
|
|
|
// clear _12h flag if hour is <= 12
|
|
if (config._a[HOUR] <= 12 &&
|
|
getParsingFlags(config).bigHour === true &&
|
|
config._a[HOUR] > 0) {
|
|
getParsingFlags(config).bigHour = undefined;
|
|
}
|
|
|
|
getParsingFlags(config).parsedDateParts = config._a.slice(0);
|
|
getParsingFlags(config).meridiem = config._meridiem;
|
|
// handle meridiem
|
|
config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], config._meridiem);
|
|
|
|
configFromArray(config);
|
|
checkOverflow(config);
|
|
}
|
|
|
|
|
|
function meridiemFixWrap (locale, hour, meridiem) {
|
|
var isPm;
|
|
|
|
if (meridiem == null) {
|
|
// nothing to do
|
|
return hour;
|
|
}
|
|
if (locale.meridiemHour != null) {
|
|
return locale.meridiemHour(hour, meridiem);
|
|
} else if (locale.isPM != null) {
|
|
// Fallback
|
|
isPm = locale.isPM(meridiem);
|
|
if (isPm && hour < 12) {
|
|
hour += 12;
|
|
}
|
|
if (!isPm && hour === 12) {
|
|
hour = 0;
|
|
}
|
|
return hour;
|
|
} else {
|
|
// this is not supposed to happen
|
|
return hour;
|
|
}
|
|
}
|
|
|
|
// date from string and array of format strings
|
|
function configFromStringAndArray(config) {
|
|
var tempConfig,
|
|
bestMoment,
|
|
|
|
scoreToBeat,
|
|
i,
|
|
currentScore;
|
|
|
|
if (config._f.length === 0) {
|
|
getParsingFlags(config).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._f = config._f[i];
|
|
configFromStringAndFormat(tempConfig);
|
|
|
|
if (!isValid(tempConfig)) {
|
|
continue;
|
|
}
|
|
|
|
// if there is any input that was not parsed add a penalty for that format
|
|
currentScore += getParsingFlags(tempConfig).charsLeftOver;
|
|
|
|
//or tokens
|
|
currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10;
|
|
|
|
getParsingFlags(tempConfig).score = currentScore;
|
|
|
|
if (scoreToBeat == null || currentScore < scoreToBeat) {
|
|
scoreToBeat = currentScore;
|
|
bestMoment = tempConfig;
|
|
}
|
|
}
|
|
|
|
extend(config, bestMoment || tempConfig);
|
|
}
|
|
|
|
function configFromObject(config) {
|
|
if (config._d) {
|
|
return;
|
|
}
|
|
|
|
var i = normalizeObjectUnits(config._i);
|
|
config._a = map([i.year, i.month, i.day || i.date, i.hour, i.minute, i.second, i.millisecond], function (obj) {
|
|
return obj && parseInt(obj, 10);
|
|
});
|
|
|
|
configFromArray(config);
|
|
}
|
|
|
|
function createFromConfig (config) {
|
|
var res = new Moment(checkOverflow(prepareConfig(config)));
|
|
if (res._nextDay) {
|
|
// Adding is smart enough around DST
|
|
res.add(1, 'd');
|
|
res._nextDay = undefined;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
function prepareConfig (config) {
|
|
var input = config._i,
|
|
format = config._f;
|
|
|
|
config._locale = config._locale || getLocale(config._l);
|
|
|
|
if (input === null || (format === undefined && input === '')) {
|
|
return createInvalid({nullInput: true});
|
|
}
|
|
|
|
if (typeof input === 'string') {
|
|
config._i = input = config._locale.preparse(input);
|
|
}
|
|
|
|
if (isMoment(input)) {
|
|
return new Moment(checkOverflow(input));
|
|
} else if (isDate(input)) {
|
|
config._d = input;
|
|
} else if (isArray(format)) {
|
|
configFromStringAndArray(config);
|
|
} else if (format) {
|
|
configFromStringAndFormat(config);
|
|
} else {
|
|
configFromInput(config);
|
|
}
|
|
|
|
if (!isValid(config)) {
|
|
config._d = null;
|
|
}
|
|
|
|
return config;
|
|
}
|
|
|
|
function configFromInput(config) {
|
|
var input = config._i;
|
|
if (isUndefined(input)) {
|
|
config._d = new Date(hooks.now());
|
|
} else if (isDate(input)) {
|
|
config._d = new Date(input.valueOf());
|
|
} else if (typeof input === 'string') {
|
|
configFromString(config);
|
|
} else if (isArray(input)) {
|
|
config._a = map(input.slice(0), function (obj) {
|
|
return parseInt(obj, 10);
|
|
});
|
|
configFromArray(config);
|
|
} else if (isObject(input)) {
|
|
configFromObject(config);
|
|
} else if (isNumber(input)) {
|
|
// from milliseconds
|
|
config._d = new Date(input);
|
|
} else {
|
|
hooks.createFromInputFallback(config);
|
|
}
|
|
}
|
|
|
|
function createLocalOrUTC (input, format, locale, strict, isUTC) {
|
|
var c = {};
|
|
|
|
if (locale === true || locale === false) {
|
|
strict = locale;
|
|
locale = undefined;
|
|
}
|
|
|
|
if ((isObject(input) && isObjectEmpty(input)) ||
|
|
(isArray(input) && input.length === 0)) {
|
|
input = undefined;
|
|
}
|
|
// object construction must be done this way.
|
|
// https://github.com/moment/moment/issues/1423
|
|
c._isAMomentObject = true;
|
|
c._useUTC = c._isUTC = isUTC;
|
|
c._l = locale;
|
|
c._i = input;
|
|
c._f = format;
|
|
c._strict = strict;
|
|
|
|
return createFromConfig(c);
|
|
}
|
|
|
|
function createLocal (input, format, locale, strict) {
|
|
return createLocalOrUTC(input, format, locale, strict, false);
|
|
}
|
|
|
|
var prototypeMin = deprecate(
|
|
'moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/',
|
|
function () {
|
|
var other = createLocal.apply(null, arguments);
|
|
if (this.isValid() && other.isValid()) {
|
|
return other < this ? this : other;
|
|
} else {
|
|
return createInvalid();
|
|
}
|
|
}
|
|
);
|
|
|
|
var prototypeMax = deprecate(
|
|
'moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/',
|
|
function () {
|
|
var other = createLocal.apply(null, arguments);
|
|
if (this.isValid() && other.isValid()) {
|
|
return other > this ? this : other;
|
|
} else {
|
|
return createInvalid();
|
|
}
|
|
}
|
|
);
|
|
|
|
// 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 createLocal();
|
|
}
|
|
res = moments[0];
|
|
for (i = 1; i < moments.length; ++i) {
|
|
if (!moments[i].isValid() || moments[i][fn](res)) {
|
|
res = moments[i];
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
// TODO: Use [].sort instead?
|
|
function min () {
|
|
var args = [].slice.call(arguments, 0);
|
|
|
|
return pickBy('isBefore', args);
|
|
}
|
|
|
|
function max () {
|
|
var args = [].slice.call(arguments, 0);
|
|
|
|
return pickBy('isAfter', args);
|
|
}
|
|
|
|
var now = function () {
|
|
return Date.now ? Date.now() : +(new Date());
|
|
};
|
|
|
|
var ordering = ['year', 'quarter', 'month', 'week', 'day', 'hour', 'minute', 'second', 'millisecond'];
|
|
|
|
function isDurationValid(m) {
|
|
for (var key in m) {
|
|
if (!(indexOf.call(ordering, key) !== -1 && (m[key] == null || !isNaN(m[key])))) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
var unitHasDecimal = false;
|
|
for (var i = 0; i < ordering.length; ++i) {
|
|
if (m[ordering[i]]) {
|
|
if (unitHasDecimal) {
|
|
return false; // only allow non-integers for smallest unit
|
|
}
|
|
if (parseFloat(m[ordering[i]]) !== toInt(m[ordering[i]])) {
|
|
unitHasDecimal = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function isValid$1() {
|
|
return this._isValid;
|
|
}
|
|
|
|
function createInvalid$1() {
|
|
return createDuration(NaN);
|
|
}
|
|
|
|
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;
|
|
|
|
this._isValid = isDurationValid(normalizedInput);
|
|
|
|
// representation for dateAddRemove
|
|
this._milliseconds = +milliseconds +
|
|
seconds * 1e3 + // 1000
|
|
minutes * 6e4 + // 1000 * 60
|
|
hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978
|
|
// 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 to 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 = getLocale();
|
|
|
|
this._bubble();
|
|
}
|
|
|
|
function isDuration (obj) {
|
|
return obj instanceof Duration;
|
|
}
|
|
|
|
function absRound (number) {
|
|
if (number < 0) {
|
|
return Math.round(-1 * number) * -1;
|
|
} else {
|
|
return Math.round(number);
|
|
}
|
|
}
|
|
|
|
// FORMATTING
|
|
|
|
function offset (token, separator) {
|
|
addFormatToken(token, 0, 0, function () {
|
|
var offset = this.utcOffset();
|
|
var sign = '+';
|
|
if (offset < 0) {
|
|
offset = -offset;
|
|
sign = '-';
|
|
}
|
|
return sign + zeroFill(~~(offset / 60), 2) + separator + zeroFill(~~(offset) % 60, 2);
|
|
});
|
|
}
|
|
|
|
offset('Z', ':');
|
|
offset('ZZ', '');
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('Z', matchShortOffset);
|
|
addRegexToken('ZZ', matchShortOffset);
|
|
addParseToken(['Z', 'ZZ'], function (input, array, config) {
|
|
config._useUTC = true;
|
|
config._tzm = offsetFromString(matchShortOffset, input);
|
|
});
|
|
|
|
// HELPERS
|
|
|
|
// timezone chunker
|
|
// '+10:00' > ['10', '00']
|
|
// '-1530' > ['-15', '30']
|
|
var chunkOffset = /([\+\-]|\d\d)/gi;
|
|
|
|
function offsetFromString(matcher, string) {
|
|
var matches = (string || '').match(matcher);
|
|
|
|
if (matches === null) {
|
|
return null;
|
|
}
|
|
|
|
var chunk = matches[matches.length - 1] || [];
|
|
var parts = (chunk + '').match(chunkOffset) || ['-', 0, 0];
|
|
var minutes = +(parts[1] * 60) + toInt(parts[2]);
|
|
|
|
return minutes === 0 ?
|
|
0 :
|
|
parts[0] === '+' ? minutes : -minutes;
|
|
}
|
|
|
|
// Return a moment from input, that is local/utc/zone equivalent to model.
|
|
function cloneWithOffset(input, model) {
|
|
var res, diff;
|
|
if (model._isUTC) {
|
|
res = model.clone();
|
|
diff = (isMoment(input) || isDate(input) ? input.valueOf() : createLocal(input).valueOf()) - res.valueOf();
|
|
// Use low-level api, because this fn is low-level api.
|
|
res._d.setTime(res._d.valueOf() + diff);
|
|
hooks.updateOffset(res, false);
|
|
return res;
|
|
} else {
|
|
return createLocal(input).local();
|
|
}
|
|
}
|
|
|
|
function getDateOffset (m) {
|
|
// On Firefox.24 Date#getTimezoneOffset returns a floating point.
|
|
// https://github.com/moment/moment/pull/1871
|
|
return -Math.round(m._d.getTimezoneOffset() / 15) * 15;
|
|
}
|
|
|
|
// HOOKS
|
|
|
|
// This function will be called whenever a moment is mutated.
|
|
// It is intended to keep the offset in sync with the timezone.
|
|
hooks.updateOffset = function () {};
|
|
|
|
// MOMENTS
|
|
|
|
// keepLocalTime = true means only change the timezone, without
|
|
// affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]-->
|
|
// 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset
|
|
// +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.
|
|
function getSetOffset (input, keepLocalTime, keepMinutes) {
|
|
var offset = this._offset || 0,
|
|
localAdjust;
|
|
if (!this.isValid()) {
|
|
return input != null ? this : NaN;
|
|
}
|
|
if (input != null) {
|
|
if (typeof input === 'string') {
|
|
input = offsetFromString(matchShortOffset, input);
|
|
if (input === null) {
|
|
return this;
|
|
}
|
|
} else if (Math.abs(input) < 16 && !keepMinutes) {
|
|
input = input * 60;
|
|
}
|
|
if (!this._isUTC && keepLocalTime) {
|
|
localAdjust = getDateOffset(this);
|
|
}
|
|
this._offset = input;
|
|
this._isUTC = true;
|
|
if (localAdjust != null) {
|
|
this.add(localAdjust, 'm');
|
|
}
|
|
if (offset !== input) {
|
|
if (!keepLocalTime || this._changeInProgress) {
|
|
addSubtract(this, createDuration(input - offset, 'm'), 1, false);
|
|
} else if (!this._changeInProgress) {
|
|
this._changeInProgress = true;
|
|
hooks.updateOffset(this, true);
|
|
this._changeInProgress = null;
|
|
}
|
|
}
|
|
return this;
|
|
} else {
|
|
return this._isUTC ? offset : getDateOffset(this);
|
|
}
|
|
}
|
|
|
|
function getSetZone (input, keepLocalTime) {
|
|
if (input != null) {
|
|
if (typeof input !== 'string') {
|
|
input = -input;
|
|
}
|
|
|
|
this.utcOffset(input, keepLocalTime);
|
|
|
|
return this;
|
|
} else {
|
|
return -this.utcOffset();
|
|
}
|
|
}
|
|
|
|
function setOffsetToUTC (keepLocalTime) {
|
|
return this.utcOffset(0, keepLocalTime);
|
|
}
|
|
|
|
function setOffsetToLocal (keepLocalTime) {
|
|
if (this._isUTC) {
|
|
this.utcOffset(0, keepLocalTime);
|
|
this._isUTC = false;
|
|
|
|
if (keepLocalTime) {
|
|
this.subtract(getDateOffset(this), 'm');
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
|
|
function setOffsetToParsedOffset () {
|
|
if (this._tzm != null) {
|
|
this.utcOffset(this._tzm, false, true);
|
|
} else if (typeof this._i === 'string') {
|
|
var tZone = offsetFromString(matchOffset, this._i);
|
|
if (tZone != null) {
|
|
this.utcOffset(tZone);
|
|
}
|
|
else {
|
|
this.utcOffset(0, true);
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
|
|
function hasAlignedHourOffset (input) {
|
|
if (!this.isValid()) {
|
|
return false;
|
|
}
|
|
input = input ? createLocal(input).utcOffset() : 0;
|
|
|
|
return (this.utcOffset() - input) % 60 === 0;
|
|
}
|
|
|
|
function isDaylightSavingTime () {
|
|
return (
|
|
this.utcOffset() > this.clone().month(0).utcOffset() ||
|
|
this.utcOffset() > this.clone().month(5).utcOffset()
|
|
);
|
|
}
|
|
|
|
function isDaylightSavingTimeShifted () {
|
|
if (!isUndefined(this._isDSTShifted)) {
|
|
return this._isDSTShifted;
|
|
}
|
|
|
|
var c = {};
|
|
|
|
copyConfig(c, this);
|
|
c = prepareConfig(c);
|
|
|
|
if (c._a) {
|
|
var other = c._isUTC ? createUTC(c._a) : createLocal(c._a);
|
|
this._isDSTShifted = this.isValid() &&
|
|
compareArrays(c._a, other.toArray()) > 0;
|
|
} else {
|
|
this._isDSTShifted = false;
|
|
}
|
|
|
|
return this._isDSTShifted;
|
|
}
|
|
|
|
function isLocal () {
|
|
return this.isValid() ? !this._isUTC : false;
|
|
}
|
|
|
|
function isUtcOffset () {
|
|
return this.isValid() ? this._isUTC : false;
|
|
}
|
|
|
|
function isUtc () {
|
|
return this.isValid() ? this._isUTC && this._offset === 0 : false;
|
|
}
|
|
|
|
// ASP.NET json date format regex
|
|
var aspNetRegex = /^(\-|\+)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/;
|
|
|
|
// from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html
|
|
// somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere
|
|
// and further modified to allow for strings containing both week and day
|
|
var isoRegex = /^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;
|
|
|
|
function createDuration (input, key) {
|
|
var duration = input,
|
|
// matching against regexp is expensive, do it on demand
|
|
match = null,
|
|
sign,
|
|
ret,
|
|
diffRes;
|
|
|
|
if (isDuration(input)) {
|
|
duration = {
|
|
ms : input._milliseconds,
|
|
d : input._days,
|
|
M : input._months
|
|
};
|
|
} else if (isNumber(input)) {
|
|
duration = {};
|
|
if (key) {
|
|
duration[key] = input;
|
|
} else {
|
|
duration.milliseconds = input;
|
|
}
|
|
} else if (!!(match = aspNetRegex.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(absRound(match[MILLISECOND] * 1000)) * sign // the millisecond decimal point is included in the match
|
|
};
|
|
} else if (!!(match = isoRegex.exec(input))) {
|
|
sign = (match[1] === '-') ? -1 : (match[1] === '+') ? 1 : 1;
|
|
duration = {
|
|
y : parseIso(match[2], sign),
|
|
M : parseIso(match[3], sign),
|
|
w : parseIso(match[4], sign),
|
|
d : parseIso(match[5], sign),
|
|
h : parseIso(match[6], sign),
|
|
m : parseIso(match[7], sign),
|
|
s : parseIso(match[8], sign)
|
|
};
|
|
} else if (duration == null) {// checks for null or undefined
|
|
duration = {};
|
|
} else if (typeof duration === 'object' && ('from' in duration || 'to' in duration)) {
|
|
diffRes = momentsDifference(createLocal(duration.from), createLocal(duration.to));
|
|
|
|
duration = {};
|
|
duration.ms = diffRes.milliseconds;
|
|
duration.M = diffRes.months;
|
|
}
|
|
|
|
ret = new Duration(duration);
|
|
|
|
if (isDuration(input) && hasOwnProp(input, '_locale')) {
|
|
ret._locale = input._locale;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
createDuration.fn = Duration.prototype;
|
|
createDuration.invalid = createInvalid$1;
|
|
|
|
function parseIso (inp, sign) {
|
|
// 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;
|
|
}
|
|
|
|
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;
|
|
if (!(base.isValid() && other.isValid())) {
|
|
return {milliseconds: 0, months: 0};
|
|
}
|
|
|
|
other = cloneWithOffset(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). ' +
|
|
'See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.');
|
|
tmp = val; val = period; period = tmp;
|
|
}
|
|
|
|
val = typeof val === 'string' ? +val : val;
|
|
dur = createDuration(val, period);
|
|
addSubtract(this, dur, direction);
|
|
return this;
|
|
};
|
|
}
|
|
|
|
function addSubtract (mom, duration, isAdding, updateOffset) {
|
|
var milliseconds = duration._milliseconds,
|
|
days = absRound(duration._days),
|
|
months = absRound(duration._months);
|
|
|
|
if (!mom.isValid()) {
|
|
// No op
|
|
return;
|
|
}
|
|
|
|
updateOffset = updateOffset == null ? true : updateOffset;
|
|
|
|
if (months) {
|
|
setMonth(mom, get(mom, 'Month') + months * isAdding);
|
|
}
|
|
if (days) {
|
|
set$1(mom, 'Date', get(mom, 'Date') + days * isAdding);
|
|
}
|
|
if (milliseconds) {
|
|
mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding);
|
|
}
|
|
if (updateOffset) {
|
|
hooks.updateOffset(mom, days || months);
|
|
}
|
|
}
|
|
|
|
var add = createAdder(1, 'add');
|
|
var subtract = createAdder(-1, 'subtract');
|
|
|
|
function getCalendarFormat(myMoment, now) {
|
|
var diff = myMoment.diff(now, 'days', true);
|
|
return diff < -6 ? 'sameElse' :
|
|
diff < -1 ? 'lastWeek' :
|
|
diff < 0 ? 'lastDay' :
|
|
diff < 1 ? 'sameDay' :
|
|
diff < 2 ? 'nextDay' :
|
|
diff < 7 ? 'nextWeek' : 'sameElse';
|
|
}
|
|
|
|
function calendar$1 (time, formats) {
|
|
// We want to compare the start of today, vs this.
|
|
// Getting start-of-today depends on whether we're local/utc/offset or not.
|
|
var now = time || createLocal(),
|
|
sod = cloneWithOffset(now, this).startOf('day'),
|
|
format = hooks.calendarFormat(this, sod) || 'sameElse';
|
|
|
|
var output = formats && (isFunction(formats[format]) ? formats[format].call(this, now) : formats[format]);
|
|
|
|
return this.format(output || this.localeData().calendar(format, this, createLocal(now)));
|
|
}
|
|
|
|
function clone () {
|
|
return new Moment(this);
|
|
}
|
|
|
|
function isAfter (input, units) {
|
|
var localInput = isMoment(input) ? input : createLocal(input);
|
|
if (!(this.isValid() && localInput.isValid())) {
|
|
return false;
|
|
}
|
|
units = normalizeUnits(!isUndefined(units) ? units : 'millisecond');
|
|
if (units === 'millisecond') {
|
|
return this.valueOf() > localInput.valueOf();
|
|
} else {
|
|
return localInput.valueOf() < this.clone().startOf(units).valueOf();
|
|
}
|
|
}
|
|
|
|
function isBefore (input, units) {
|
|
var localInput = isMoment(input) ? input : createLocal(input);
|
|
if (!(this.isValid() && localInput.isValid())) {
|
|
return false;
|
|
}
|
|
units = normalizeUnits(!isUndefined(units) ? units : 'millisecond');
|
|
if (units === 'millisecond') {
|
|
return this.valueOf() < localInput.valueOf();
|
|
} else {
|
|
return this.clone().endOf(units).valueOf() < localInput.valueOf();
|
|
}
|
|
}
|
|
|
|
function isBetween (from, to, units, inclusivity) {
|
|
inclusivity = inclusivity || '()';
|
|
return (inclusivity[0] === '(' ? this.isAfter(from, units) : !this.isBefore(from, units)) &&
|
|
(inclusivity[1] === ')' ? this.isBefore(to, units) : !this.isAfter(to, units));
|
|
}
|
|
|
|
function isSame (input, units) {
|
|
var localInput = isMoment(input) ? input : createLocal(input),
|
|
inputMs;
|
|
if (!(this.isValid() && localInput.isValid())) {
|
|
return false;
|
|
}
|
|
units = normalizeUnits(units || 'millisecond');
|
|
if (units === 'millisecond') {
|
|
return this.valueOf() === localInput.valueOf();
|
|
} else {
|
|
inputMs = localInput.valueOf();
|
|
return this.clone().startOf(units).valueOf() <= inputMs && inputMs <= this.clone().endOf(units).valueOf();
|
|
}
|
|
}
|
|
|
|
function isSameOrAfter (input, units) {
|
|
return this.isSame(input, units) || this.isAfter(input,units);
|
|
}
|
|
|
|
function isSameOrBefore (input, units) {
|
|
return this.isSame(input, units) || this.isBefore(input,units);
|
|
}
|
|
|
|
function diff (input, units, asFloat) {
|
|
var that,
|
|
zoneDelta,
|
|
output;
|
|
|
|
if (!this.isValid()) {
|
|
return NaN;
|
|
}
|
|
|
|
that = cloneWithOffset(input, this);
|
|
|
|
if (!that.isValid()) {
|
|
return NaN;
|
|
}
|
|
|
|
zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4;
|
|
|
|
units = normalizeUnits(units);
|
|
|
|
switch (units) {
|
|
case 'year': output = monthDiff(this, that) / 12; break;
|
|
case 'month': output = monthDiff(this, that); break;
|
|
case 'quarter': output = monthDiff(this, that) / 3; break;
|
|
case 'second': output = (this - that) / 1e3; break; // 1000
|
|
case 'minute': output = (this - that) / 6e4; break; // 1000 * 60
|
|
case 'hour': output = (this - that) / 36e5; break; // 1000 * 60 * 60
|
|
case 'day': output = (this - that - zoneDelta) / 864e5; break; // 1000 * 60 * 60 * 24, negate dst
|
|
case 'week': output = (this - that - zoneDelta) / 6048e5; break; // 1000 * 60 * 60 * 24 * 7, negate dst
|
|
default: output = this - that;
|
|
}
|
|
|
|
return asFloat ? output : absFloor(output);
|
|
}
|
|
|
|
function monthDiff (a, b) {
|
|
// difference in months
|
|
var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()),
|
|
// b is in (anchor - 1 month, anchor + 1 month)
|
|
anchor = a.clone().add(wholeMonthDiff, 'months'),
|
|
anchor2, adjust;
|
|
|
|
if (b - anchor < 0) {
|
|
anchor2 = a.clone().add(wholeMonthDiff - 1, 'months');
|
|
// linear across the month
|
|
adjust = (b - anchor) / (anchor - anchor2);
|
|
} else {
|
|
anchor2 = a.clone().add(wholeMonthDiff + 1, 'months');
|
|
// linear across the month
|
|
adjust = (b - anchor) / (anchor2 - anchor);
|
|
}
|
|
|
|
//check for negative zero, return zero if negative zero
|
|
return -(wholeMonthDiff + adjust) || 0;
|
|
}
|
|
|
|
hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ';
|
|
hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]';
|
|
|
|
function toString () {
|
|
return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ');
|
|
}
|
|
|
|
function toISOString(keepOffset) {
|
|
if (!this.isValid()) {
|
|
return null;
|
|
}
|
|
var utc = keepOffset !== true;
|
|
var m = utc ? this.clone().utc() : this;
|
|
if (m.year() < 0 || m.year() > 9999) {
|
|
return formatMoment(m, utc ? 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYYYY-MM-DD[T]HH:mm:ss.SSSZ');
|
|
}
|
|
if (isFunction(Date.prototype.toISOString)) {
|
|
// native implementation is ~50x faster, use it when we can
|
|
if (utc) {
|
|
return this.toDate().toISOString();
|
|
} else {
|
|
return new Date(this.valueOf() + this.utcOffset() * 60 * 1000).toISOString().replace('Z', formatMoment(m, 'Z'));
|
|
}
|
|
}
|
|
return formatMoment(m, utc ? 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYY-MM-DD[T]HH:mm:ss.SSSZ');
|
|
}
|
|
|
|
/**
|
|
* Return a human readable representation of a moment that can
|
|
* also be evaluated to get a new moment which is the same
|
|
*
|
|
* @link https://nodejs.org/dist/latest/docs/api/util.html#util_custom_inspect_function_on_objects
|
|
*/
|
|
function inspect () {
|
|
if (!this.isValid()) {
|
|
return 'moment.invalid(/* ' + this._i + ' */)';
|
|
}
|
|
var func = 'moment';
|
|
var zone = '';
|
|
if (!this.isLocal()) {
|
|
func = this.utcOffset() === 0 ? 'moment.utc' : 'moment.parseZone';
|
|
zone = 'Z';
|
|
}
|
|
var prefix = '[' + func + '("]';
|
|
var year = (0 <= this.year() && this.year() <= 9999) ? 'YYYY' : 'YYYYYY';
|
|
var datetime = '-MM-DD[T]HH:mm:ss.SSS';
|
|
var suffix = zone + '[")]';
|
|
|
|
return this.format(prefix + year + datetime + suffix);
|
|
}
|
|
|
|
function format (inputString) {
|
|
if (!inputString) {
|
|
inputString = this.isUtc() ? hooks.defaultFormatUtc : hooks.defaultFormat;
|
|
}
|
|
var output = formatMoment(this, inputString);
|
|
return this.localeData().postformat(output);
|
|
}
|
|
|
|
function from (time, withoutSuffix) {
|
|
if (this.isValid() &&
|
|
((isMoment(time) && time.isValid()) ||
|
|
createLocal(time).isValid())) {
|
|
return createDuration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix);
|
|
} else {
|
|
return this.localeData().invalidDate();
|
|
}
|
|
}
|
|
|
|
function fromNow (withoutSuffix) {
|
|
return this.from(createLocal(), withoutSuffix);
|
|
}
|
|
|
|
function to (time, withoutSuffix) {
|
|
if (this.isValid() &&
|
|
((isMoment(time) && time.isValid()) ||
|
|
createLocal(time).isValid())) {
|
|
return createDuration({from: this, to: time}).locale(this.locale()).humanize(!withoutSuffix);
|
|
} else {
|
|
return this.localeData().invalidDate();
|
|
}
|
|
}
|
|
|
|
function toNow (withoutSuffix) {
|
|
return this.to(createLocal(), withoutSuffix);
|
|
}
|
|
|
|
// If passed a locale key, it will set the locale for this
|
|
// instance. Otherwise, it will return the locale configuration
|
|
// variables for this instance.
|
|
function locale (key) {
|
|
var newLocaleData;
|
|
|
|
if (key === undefined) {
|
|
return this._locale._abbr;
|
|
} else {
|
|
newLocaleData = getLocale(key);
|
|
if (newLocaleData != null) {
|
|
this._locale = newLocaleData;
|
|
}
|
|
return this;
|
|
}
|
|
}
|
|
|
|
var 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);
|
|
}
|
|
}
|
|
);
|
|
|
|
function localeData () {
|
|
return this._locale;
|
|
}
|
|
|
|
function startOf (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':
|
|
case 'date':
|
|
this.hours(0);
|
|
/* falls through */
|
|
case 'hour':
|
|
this.minutes(0);
|
|
/* falls through */
|
|
case 'minute':
|
|
this.seconds(0);
|
|
/* falls through */
|
|
case 'second':
|
|
this.milliseconds(0);
|
|
}
|
|
|
|
// weeks are a special case
|
|
if (units === 'week') {
|
|
this.weekday(0);
|
|
}
|
|
if (units === 'isoWeek') {
|
|
this.isoWeekday(1);
|
|
}
|
|
|
|
// quarters are also special
|
|
if (units === 'quarter') {
|
|
this.month(Math.floor(this.month() / 3) * 3);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
function endOf (units) {
|
|
units = normalizeUnits(units);
|
|
if (units === undefined || units === 'millisecond') {
|
|
return this;
|
|
}
|
|
|
|
// 'date' is an alias for 'day', so it should be considered as such.
|
|
if (units === 'date') {
|
|
units = 'day';
|
|
}
|
|
|
|
return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms');
|
|
}
|
|
|
|
function valueOf () {
|
|
return this._d.valueOf() - ((this._offset || 0) * 60000);
|
|
}
|
|
|
|
function unix () {
|
|
return Math.floor(this.valueOf() / 1000);
|
|
}
|
|
|
|
function toDate () {
|
|
return new Date(this.valueOf());
|
|
}
|
|
|
|
function toArray () {
|
|
var m = this;
|
|
return [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second(), m.millisecond()];
|
|
}
|
|
|
|
function toObject () {
|
|
var m = this;
|
|
return {
|
|
years: m.year(),
|
|
months: m.month(),
|
|
date: m.date(),
|
|
hours: m.hours(),
|
|
minutes: m.minutes(),
|
|
seconds: m.seconds(),
|
|
milliseconds: m.milliseconds()
|
|
};
|
|
}
|
|
|
|
function toJSON () {
|
|
// new Date(NaN).toJSON() === null
|
|
return this.isValid() ? this.toISOString() : null;
|
|
}
|
|
|
|
function isValid$2 () {
|
|
return isValid(this);
|
|
}
|
|
|
|
function parsingFlags () {
|
|
return extend({}, getParsingFlags(this));
|
|
}
|
|
|
|
function invalidAt () {
|
|
return getParsingFlags(this).overflow;
|
|
}
|
|
|
|
function creationData() {
|
|
return {
|
|
input: this._i,
|
|
format: this._f,
|
|
locale: this._locale,
|
|
isUTC: this._isUTC,
|
|
strict: this._strict
|
|
};
|
|
}
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken(0, ['gg', 2], 0, function () {
|
|
return this.weekYear() % 100;
|
|
});
|
|
|
|
addFormatToken(0, ['GG', 2], 0, function () {
|
|
return this.isoWeekYear() % 100;
|
|
});
|
|
|
|
function addWeekYearFormatToken (token, getter) {
|
|
addFormatToken(0, [token, token.length], 0, getter);
|
|
}
|
|
|
|
addWeekYearFormatToken('gggg', 'weekYear');
|
|
addWeekYearFormatToken('ggggg', 'weekYear');
|
|
addWeekYearFormatToken('GGGG', 'isoWeekYear');
|
|
addWeekYearFormatToken('GGGGG', 'isoWeekYear');
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('weekYear', 'gg');
|
|
addUnitAlias('isoWeekYear', 'GG');
|
|
|
|
// PRIORITY
|
|
|
|
addUnitPriority('weekYear', 1);
|
|
addUnitPriority('isoWeekYear', 1);
|
|
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('G', matchSigned);
|
|
addRegexToken('g', matchSigned);
|
|
addRegexToken('GG', match1to2, match2);
|
|
addRegexToken('gg', match1to2, match2);
|
|
addRegexToken('GGGG', match1to4, match4);
|
|
addRegexToken('gggg', match1to4, match4);
|
|
addRegexToken('GGGGG', match1to6, match6);
|
|
addRegexToken('ggggg', match1to6, match6);
|
|
|
|
addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (input, week, config, token) {
|
|
week[token.substr(0, 2)] = toInt(input);
|
|
});
|
|
|
|
addWeekParseToken(['gg', 'GG'], function (input, week, config, token) {
|
|
week[token] = hooks.parseTwoDigitYear(input);
|
|
});
|
|
|
|
// MOMENTS
|
|
|
|
function getSetWeekYear (input) {
|
|
return getSetWeekYearHelper.call(this,
|
|
input,
|
|
this.week(),
|
|
this.weekday(),
|
|
this.localeData()._week.dow,
|
|
this.localeData()._week.doy);
|
|
}
|
|
|
|
function getSetISOWeekYear (input) {
|
|
return getSetWeekYearHelper.call(this,
|
|
input, this.isoWeek(), this.isoWeekday(), 1, 4);
|
|
}
|
|
|
|
function getISOWeeksInYear () {
|
|
return weeksInYear(this.year(), 1, 4);
|
|
}
|
|
|
|
function getWeeksInYear () {
|
|
var weekInfo = this.localeData()._week;
|
|
return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy);
|
|
}
|
|
|
|
function getSetWeekYearHelper(input, week, weekday, dow, doy) {
|
|
var weeksTarget;
|
|
if (input == null) {
|
|
return weekOfYear(this, dow, doy).year;
|
|
} else {
|
|
weeksTarget = weeksInYear(input, dow, doy);
|
|
if (week > weeksTarget) {
|
|
week = weeksTarget;
|
|
}
|
|
return setWeekAll.call(this, input, week, weekday, dow, doy);
|
|
}
|
|
}
|
|
|
|
function setWeekAll(weekYear, week, weekday, dow, doy) {
|
|
var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy),
|
|
date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear);
|
|
|
|
this.year(date.getUTCFullYear());
|
|
this.month(date.getUTCMonth());
|
|
this.date(date.getUTCDate());
|
|
return this;
|
|
}
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('Q', 0, 'Qo', 'quarter');
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('quarter', 'Q');
|
|
|
|
// PRIORITY
|
|
|
|
addUnitPriority('quarter', 7);
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('Q', match1);
|
|
addParseToken('Q', function (input, array) {
|
|
array[MONTH] = (toInt(input) - 1) * 3;
|
|
});
|
|
|
|
// MOMENTS
|
|
|
|
function getSetQuarter (input) {
|
|
return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3);
|
|
}
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('D', ['DD', 2], 'Do', 'date');
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('date', 'D');
|
|
|
|
// PRIOROITY
|
|
addUnitPriority('date', 9);
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('D', match1to2);
|
|
addRegexToken('DD', match1to2, match2);
|
|
addRegexToken('Do', function (isStrict, locale) {
|
|
// TODO: Remove "ordinalParse" fallback in next major release.
|
|
return isStrict ?
|
|
(locale._dayOfMonthOrdinalParse || locale._ordinalParse) :
|
|
locale._dayOfMonthOrdinalParseLenient;
|
|
});
|
|
|
|
addParseToken(['D', 'DD'], DATE);
|
|
addParseToken('Do', function (input, array) {
|
|
array[DATE] = toInt(input.match(match1to2)[0]);
|
|
});
|
|
|
|
// MOMENTS
|
|
|
|
var getSetDayOfMonth = makeGetSet('Date', true);
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear');
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('dayOfYear', 'DDD');
|
|
|
|
// PRIORITY
|
|
addUnitPriority('dayOfYear', 4);
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('DDD', match1to3);
|
|
addRegexToken('DDDD', match3);
|
|
addParseToken(['DDD', 'DDDD'], function (input, array, config) {
|
|
config._dayOfYear = toInt(input);
|
|
});
|
|
|
|
// HELPERS
|
|
|
|
// MOMENTS
|
|
|
|
function getSetDayOfYear (input) {
|
|
var dayOfYear = Math.round((this.clone().startOf('day') - this.clone().startOf('year')) / 864e5) + 1;
|
|
return input == null ? dayOfYear : this.add((input - dayOfYear), 'd');
|
|
}
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('m', ['mm', 2], 0, 'minute');
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('minute', 'm');
|
|
|
|
// PRIORITY
|
|
|
|
addUnitPriority('minute', 14);
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('m', match1to2);
|
|
addRegexToken('mm', match1to2, match2);
|
|
addParseToken(['m', 'mm'], MINUTE);
|
|
|
|
// MOMENTS
|
|
|
|
var getSetMinute = makeGetSet('Minutes', false);
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('s', ['ss', 2], 0, 'second');
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('second', 's');
|
|
|
|
// PRIORITY
|
|
|
|
addUnitPriority('second', 15);
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('s', match1to2);
|
|
addRegexToken('ss', match1to2, match2);
|
|
addParseToken(['s', 'ss'], SECOND);
|
|
|
|
// MOMENTS
|
|
|
|
var getSetSecond = makeGetSet('Seconds', false);
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('S', 0, 0, function () {
|
|
return ~~(this.millisecond() / 100);
|
|
});
|
|
|
|
addFormatToken(0, ['SS', 2], 0, function () {
|
|
return ~~(this.millisecond() / 10);
|
|
});
|
|
|
|
addFormatToken(0, ['SSS', 3], 0, 'millisecond');
|
|
addFormatToken(0, ['SSSS', 4], 0, function () {
|
|
return this.millisecond() * 10;
|
|
});
|
|
addFormatToken(0, ['SSSSS', 5], 0, function () {
|
|
return this.millisecond() * 100;
|
|
});
|
|
addFormatToken(0, ['SSSSSS', 6], 0, function () {
|
|
return this.millisecond() * 1000;
|
|
});
|
|
addFormatToken(0, ['SSSSSSS', 7], 0, function () {
|
|
return this.millisecond() * 10000;
|
|
});
|
|
addFormatToken(0, ['SSSSSSSS', 8], 0, function () {
|
|
return this.millisecond() * 100000;
|
|
});
|
|
addFormatToken(0, ['SSSSSSSSS', 9], 0, function () {
|
|
return this.millisecond() * 1000000;
|
|
});
|
|
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('millisecond', 'ms');
|
|
|
|
// PRIORITY
|
|
|
|
addUnitPriority('millisecond', 16);
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('S', match1to3, match1);
|
|
addRegexToken('SS', match1to3, match2);
|
|
addRegexToken('SSS', match1to3, match3);
|
|
|
|
var token;
|
|
for (token = 'SSSS'; token.length <= 9; token += 'S') {
|
|
addRegexToken(token, matchUnsigned);
|
|
}
|
|
|
|
function parseMs(input, array) {
|
|
array[MILLISECOND] = toInt(('0.' + input) * 1000);
|
|
}
|
|
|
|
for (token = 'S'; token.length <= 9; token += 'S') {
|
|
addParseToken(token, parseMs);
|
|
}
|
|
// MOMENTS
|
|
|
|
var getSetMillisecond = makeGetSet('Milliseconds', false);
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('z', 0, 0, 'zoneAbbr');
|
|
addFormatToken('zz', 0, 0, 'zoneName');
|
|
|
|
// MOMENTS
|
|
|
|
function getZoneAbbr () {
|
|
return this._isUTC ? 'UTC' : '';
|
|
}
|
|
|
|
function getZoneName () {
|
|
return this._isUTC ? 'Coordinated Universal Time' : '';
|
|
}
|
|
|
|
var proto = Moment.prototype;
|
|
|
|
proto.add = add;
|
|
proto.calendar = calendar$1;
|
|
proto.clone = clone;
|
|
proto.diff = diff;
|
|
proto.endOf = endOf;
|
|
proto.format = format;
|
|
proto.from = from;
|
|
proto.fromNow = fromNow;
|
|
proto.to = to;
|
|
proto.toNow = toNow;
|
|
proto.get = stringGet;
|
|
proto.invalidAt = invalidAt;
|
|
proto.isAfter = isAfter;
|
|
proto.isBefore = isBefore;
|
|
proto.isBetween = isBetween;
|
|
proto.isSame = isSame;
|
|
proto.isSameOrAfter = isSameOrAfter;
|
|
proto.isSameOrBefore = isSameOrBefore;
|
|
proto.isValid = isValid$2;
|
|
proto.lang = lang;
|
|
proto.locale = locale;
|
|
proto.localeData = localeData;
|
|
proto.max = prototypeMax;
|
|
proto.min = prototypeMin;
|
|
proto.parsingFlags = parsingFlags;
|
|
proto.set = stringSet;
|
|
proto.startOf = startOf;
|
|
proto.subtract = subtract;
|
|
proto.toArray = toArray;
|
|
proto.toObject = toObject;
|
|
proto.toDate = toDate;
|
|
proto.toISOString = toISOString;
|
|
proto.inspect = inspect;
|
|
proto.toJSON = toJSON;
|
|
proto.toString = toString;
|
|
proto.unix = unix;
|
|
proto.valueOf = valueOf;
|
|
proto.creationData = creationData;
|
|
proto.year = getSetYear;
|
|
proto.isLeapYear = getIsLeapYear;
|
|
proto.weekYear = getSetWeekYear;
|
|
proto.isoWeekYear = getSetISOWeekYear;
|
|
proto.quarter = proto.quarters = getSetQuarter;
|
|
proto.month = getSetMonth;
|
|
proto.daysInMonth = getDaysInMonth;
|
|
proto.week = proto.weeks = getSetWeek;
|
|
proto.isoWeek = proto.isoWeeks = getSetISOWeek;
|
|
proto.weeksInYear = getWeeksInYear;
|
|
proto.isoWeeksInYear = getISOWeeksInYear;
|
|
proto.date = getSetDayOfMonth;
|
|
proto.day = proto.days = getSetDayOfWeek;
|
|
proto.weekday = getSetLocaleDayOfWeek;
|
|
proto.isoWeekday = getSetISODayOfWeek;
|
|
proto.dayOfYear = getSetDayOfYear;
|
|
proto.hour = proto.hours = getSetHour;
|
|
proto.minute = proto.minutes = getSetMinute;
|
|
proto.second = proto.seconds = getSetSecond;
|
|
proto.millisecond = proto.milliseconds = getSetMillisecond;
|
|
proto.utcOffset = getSetOffset;
|
|
proto.utc = setOffsetToUTC;
|
|
proto.local = setOffsetToLocal;
|
|
proto.parseZone = setOffsetToParsedOffset;
|
|
proto.hasAlignedHourOffset = hasAlignedHourOffset;
|
|
proto.isDST = isDaylightSavingTime;
|
|
proto.isLocal = isLocal;
|
|
proto.isUtcOffset = isUtcOffset;
|
|
proto.isUtc = isUtc;
|
|
proto.isUTC = isUtc;
|
|
proto.zoneAbbr = getZoneAbbr;
|
|
proto.zoneName = getZoneName;
|
|
proto.dates = deprecate('dates accessor is deprecated. Use date instead.', getSetDayOfMonth);
|
|
proto.months = deprecate('months accessor is deprecated. Use month instead', getSetMonth);
|
|
proto.years = deprecate('years accessor is deprecated. Use year instead', getSetYear);
|
|
proto.zone = deprecate('moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/', getSetZone);
|
|
proto.isDSTShifted = deprecate('isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information', isDaylightSavingTimeShifted);
|
|
|
|
function createUnix (input) {
|
|
return createLocal(input * 1000);
|
|
}
|
|
|
|
function createInZone () {
|
|
return createLocal.apply(null, arguments).parseZone();
|
|
}
|
|
|
|
function preParsePostFormat (string) {
|
|
return string;
|
|
}
|
|
|
|
var proto$1 = Locale.prototype;
|
|
|
|
proto$1.calendar = calendar;
|
|
proto$1.longDateFormat = longDateFormat;
|
|
proto$1.invalidDate = invalidDate;
|
|
proto$1.ordinal = ordinal;
|
|
proto$1.preparse = preParsePostFormat;
|
|
proto$1.postformat = preParsePostFormat;
|
|
proto$1.relativeTime = relativeTime;
|
|
proto$1.pastFuture = pastFuture;
|
|
proto$1.set = set;
|
|
|
|
proto$1.months = localeMonths;
|
|
proto$1.monthsShort = localeMonthsShort;
|
|
proto$1.monthsParse = localeMonthsParse;
|
|
proto$1.monthsRegex = monthsRegex;
|
|
proto$1.monthsShortRegex = monthsShortRegex;
|
|
proto$1.week = localeWeek;
|
|
proto$1.firstDayOfYear = localeFirstDayOfYear;
|
|
proto$1.firstDayOfWeek = localeFirstDayOfWeek;
|
|
|
|
proto$1.weekdays = localeWeekdays;
|
|
proto$1.weekdaysMin = localeWeekdaysMin;
|
|
proto$1.weekdaysShort = localeWeekdaysShort;
|
|
proto$1.weekdaysParse = localeWeekdaysParse;
|
|
|
|
proto$1.weekdaysRegex = weekdaysRegex;
|
|
proto$1.weekdaysShortRegex = weekdaysShortRegex;
|
|
proto$1.weekdaysMinRegex = weekdaysMinRegex;
|
|
|
|
proto$1.isPM = localeIsPM;
|
|
proto$1.meridiem = localeMeridiem;
|
|
|
|
function get$1 (format, index, field, setter) {
|
|
var locale = getLocale();
|
|
var utc = createUTC().set(setter, index);
|
|
return locale[field](utc, format);
|
|
}
|
|
|
|
function listMonthsImpl (format, index, field) {
|
|
if (isNumber(format)) {
|
|
index = format;
|
|
format = undefined;
|
|
}
|
|
|
|
format = format || '';
|
|
|
|
if (index != null) {
|
|
return get$1(format, index, field, 'month');
|
|
}
|
|
|
|
var i;
|
|
var out = [];
|
|
for (i = 0; i < 12; i++) {
|
|
out[i] = get$1(format, i, field, 'month');
|
|
}
|
|
return out;
|
|
}
|
|
|
|
// ()
|
|
// (5)
|
|
// (fmt, 5)
|
|
// (fmt)
|
|
// (true)
|
|
// (true, 5)
|
|
// (true, fmt, 5)
|
|
// (true, fmt)
|
|
function listWeekdaysImpl (localeSorted, format, index, field) {
|
|
if (typeof localeSorted === 'boolean') {
|
|
if (isNumber(format)) {
|
|
index = format;
|
|
format = undefined;
|
|
}
|
|
|
|
format = format || '';
|
|
} else {
|
|
format = localeSorted;
|
|
index = format;
|
|
localeSorted = false;
|
|
|
|
if (isNumber(format)) {
|
|
index = format;
|
|
format = undefined;
|
|
}
|
|
|
|
format = format || '';
|
|
}
|
|
|
|
var locale = getLocale(),
|
|
shift = localeSorted ? locale._week.dow : 0;
|
|
|
|
if (index != null) {
|
|
return get$1(format, (index + shift) % 7, field, 'day');
|
|
}
|
|
|
|
var i;
|
|
var out = [];
|
|
for (i = 0; i < 7; i++) {
|
|
out[i] = get$1(format, (i + shift) % 7, field, 'day');
|
|
}
|
|
return out;
|
|
}
|
|
|
|
function listMonths (format, index) {
|
|
return listMonthsImpl(format, index, 'months');
|
|
}
|
|
|
|
function listMonthsShort (format, index) {
|
|
return listMonthsImpl(format, index, 'monthsShort');
|
|
}
|
|
|
|
function listWeekdays (localeSorted, format, index) {
|
|
return listWeekdaysImpl(localeSorted, format, index, 'weekdays');
|
|
}
|
|
|
|
function listWeekdaysShort (localeSorted, format, index) {
|
|
return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort');
|
|
}
|
|
|
|
function listWeekdaysMin (localeSorted, format, index) {
|
|
return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin');
|
|
}
|
|
|
|
getSetGlobalLocale('en', {
|
|
dayOfMonthOrdinalParse: /\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;
|
|
}
|
|
});
|
|
|
|
// Side effect imports
|
|
|
|
hooks.lang = deprecate('moment.lang is deprecated. Use moment.locale instead.', getSetGlobalLocale);
|
|
hooks.langData = deprecate('moment.langData is deprecated. Use moment.localeData instead.', getLocale);
|
|
|
|
var mathAbs = Math.abs;
|
|
|
|
function abs () {
|
|
var data = this._data;
|
|
|
|
this._milliseconds = mathAbs(this._milliseconds);
|
|
this._days = mathAbs(this._days);
|
|
this._months = mathAbs(this._months);
|
|
|
|
data.milliseconds = mathAbs(data.milliseconds);
|
|
data.seconds = mathAbs(data.seconds);
|
|
data.minutes = mathAbs(data.minutes);
|
|
data.hours = mathAbs(data.hours);
|
|
data.months = mathAbs(data.months);
|
|
data.years = mathAbs(data.years);
|
|
|
|
return this;
|
|
}
|
|
|
|
function addSubtract$1 (duration, input, value, direction) {
|
|
var other = createDuration(input, value);
|
|
|
|
duration._milliseconds += direction * other._milliseconds;
|
|
duration._days += direction * other._days;
|
|
duration._months += direction * other._months;
|
|
|
|
return duration._bubble();
|
|
}
|
|
|
|
// supports only 2.0-style add(1, 's') or add(duration)
|
|
function add$1 (input, value) {
|
|
return addSubtract$1(this, input, value, 1);
|
|
}
|
|
|
|
// supports only 2.0-style subtract(1, 's') or subtract(duration)
|
|
function subtract$1 (input, value) {
|
|
return addSubtract$1(this, input, value, -1);
|
|
}
|
|
|
|
function absCeil (number) {
|
|
if (number < 0) {
|
|
return Math.floor(number);
|
|
} else {
|
|
return Math.ceil(number);
|
|
}
|
|
}
|
|
|
|
function bubble () {
|
|
var milliseconds = this._milliseconds;
|
|
var days = this._days;
|
|
var months = this._months;
|
|
var data = this._data;
|
|
var seconds, minutes, hours, years, monthsFromDays;
|
|
|
|
// if we have a mix of positive and negative values, bubble down first
|
|
// check: https://github.com/moment/moment/issues/2166
|
|
if (!((milliseconds >= 0 && days >= 0 && months >= 0) ||
|
|
(milliseconds <= 0 && days <= 0 && months <= 0))) {
|
|
milliseconds += absCeil(monthsToDays(months) + days) * 864e5;
|
|
days = 0;
|
|
months = 0;
|
|
}
|
|
|
|
// The following code bubbles up values, see the tests for
|
|
// examples of what that means.
|
|
data.milliseconds = milliseconds % 1000;
|
|
|
|
seconds = absFloor(milliseconds / 1000);
|
|
data.seconds = seconds % 60;
|
|
|
|
minutes = absFloor(seconds / 60);
|
|
data.minutes = minutes % 60;
|
|
|
|
hours = absFloor(minutes / 60);
|
|
data.hours = hours % 24;
|
|
|
|
days += absFloor(hours / 24);
|
|
|
|
// convert days to months
|
|
monthsFromDays = absFloor(daysToMonths(days));
|
|
months += monthsFromDays;
|
|
days -= absCeil(monthsToDays(monthsFromDays));
|
|
|
|
// 12 months -> 1 year
|
|
years = absFloor(months / 12);
|
|
months %= 12;
|
|
|
|
data.days = days;
|
|
data.months = months;
|
|
data.years = years;
|
|
|
|
return this;
|
|
}
|
|
|
|
function daysToMonths (days) {
|
|
// 400 years have 146097 days (taking into account leap year rules)
|
|
// 400 years have 12 months === 4800
|
|
return days * 4800 / 146097;
|
|
}
|
|
|
|
function monthsToDays (months) {
|
|
// the reverse of daysToMonths
|
|
return months * 146097 / 4800;
|
|
}
|
|
|
|
function as (units) {
|
|
if (!this.isValid()) {
|
|
return NaN;
|
|
}
|
|
var days;
|
|
var months;
|
|
var milliseconds = this._milliseconds;
|
|
|
|
units = normalizeUnits(units);
|
|
|
|
if (units === 'month' || units === 'year') {
|
|
days = this._days + milliseconds / 864e5;
|
|
months = this._months + daysToMonths(days);
|
|
return units === 'month' ? months : months / 12;
|
|
} else {
|
|
// handle milliseconds separately because of floating point math errors (issue #1867)
|
|
days = this._days + Math.round(monthsToDays(this._months));
|
|
switch (units) {
|
|
case 'week' : return days / 7 + milliseconds / 6048e5;
|
|
case 'day' : return days + milliseconds / 864e5;
|
|
case 'hour' : return days * 24 + milliseconds / 36e5;
|
|
case 'minute' : return days * 1440 + milliseconds / 6e4;
|
|
case 'second' : return days * 86400 + milliseconds / 1000;
|
|
// Math.floor prevents floating point math errors here
|
|
case 'millisecond': return Math.floor(days * 864e5) + milliseconds;
|
|
default: throw new Error('Unknown unit ' + units);
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: Use this.as('ms')?
|
|
function valueOf$1 () {
|
|
if (!this.isValid()) {
|
|
return NaN;
|
|
}
|
|
return (
|
|
this._milliseconds +
|
|
this._days * 864e5 +
|
|
(this._months % 12) * 2592e6 +
|
|
toInt(this._months / 12) * 31536e6
|
|
);
|
|
}
|
|
|
|
function makeAs (alias) {
|
|
return function () {
|
|
return this.as(alias);
|
|
};
|
|
}
|
|
|
|
var asMilliseconds = makeAs('ms');
|
|
var asSeconds = makeAs('s');
|
|
var asMinutes = makeAs('m');
|
|
var asHours = makeAs('h');
|
|
var asDays = makeAs('d');
|
|
var asWeeks = makeAs('w');
|
|
var asMonths = makeAs('M');
|
|
var asYears = makeAs('y');
|
|
|
|
function clone$1 () {
|
|
return createDuration(this);
|
|
}
|
|
|
|
function get$2 (units) {
|
|
units = normalizeUnits(units);
|
|
return this.isValid() ? this[units + 's']() : NaN;
|
|
}
|
|
|
|
function makeGetter(name) {
|
|
return function () {
|
|
return this.isValid() ? this._data[name] : NaN;
|
|
};
|
|
}
|
|
|
|
var milliseconds = makeGetter('milliseconds');
|
|
var seconds = makeGetter('seconds');
|
|
var minutes = makeGetter('minutes');
|
|
var hours = makeGetter('hours');
|
|
var days = makeGetter('days');
|
|
var months = makeGetter('months');
|
|
var years = makeGetter('years');
|
|
|
|
function weeks () {
|
|
return absFloor(this.days() / 7);
|
|
}
|
|
|
|
var round = Math.round;
|
|
var thresholds = {
|
|
ss: 44, // a few seconds to seconds
|
|
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
|
|
};
|
|
|
|
// 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$1 (posNegDuration, withoutSuffix, locale) {
|
|
var duration = createDuration(posNegDuration).abs();
|
|
var seconds = round(duration.as('s'));
|
|
var minutes = round(duration.as('m'));
|
|
var hours = round(duration.as('h'));
|
|
var days = round(duration.as('d'));
|
|
var months = round(duration.as('M'));
|
|
var years = round(duration.as('y'));
|
|
|
|
var a = seconds <= thresholds.ss && ['s', seconds] ||
|
|
seconds < thresholds.s && ['ss', seconds] ||
|
|
minutes <= 1 && ['m'] ||
|
|
minutes < thresholds.m && ['mm', minutes] ||
|
|
hours <= 1 && ['h'] ||
|
|
hours < thresholds.h && ['hh', hours] ||
|
|
days <= 1 && ['d'] ||
|
|
days < thresholds.d && ['dd', days] ||
|
|
months <= 1 && ['M'] ||
|
|
months < thresholds.M && ['MM', months] ||
|
|
years <= 1 && ['y'] || ['yy', years];
|
|
|
|
a[2] = withoutSuffix;
|
|
a[3] = +posNegDuration > 0;
|
|
a[4] = locale;
|
|
return substituteTimeAgo.apply(null, a);
|
|
}
|
|
|
|
// This function allows you to set the rounding function for relative time strings
|
|
function getSetRelativeTimeRounding (roundingFunction) {
|
|
if (roundingFunction === undefined) {
|
|
return round;
|
|
}
|
|
if (typeof(roundingFunction) === 'function') {
|
|
round = roundingFunction;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// This function allows you to set a threshold for relative time strings
|
|
function getSetRelativeTimeThreshold (threshold, limit) {
|
|
if (thresholds[threshold] === undefined) {
|
|
return false;
|
|
}
|
|
if (limit === undefined) {
|
|
return thresholds[threshold];
|
|
}
|
|
thresholds[threshold] = limit;
|
|
if (threshold === 's') {
|
|
thresholds.ss = limit - 1;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function humanize (withSuffix) {
|
|
if (!this.isValid()) {
|
|
return this.localeData().invalidDate();
|
|
}
|
|
|
|
var locale = this.localeData();
|
|
var output = relativeTime$1(this, !withSuffix, locale);
|
|
|
|
if (withSuffix) {
|
|
output = locale.pastFuture(+this, output);
|
|
}
|
|
|
|
return locale.postformat(output);
|
|
}
|
|
|
|
var abs$1 = Math.abs;
|
|
|
|
function sign(x) {
|
|
return ((x > 0) - (x < 0)) || +x;
|
|
}
|
|
|
|
function toISOString$1() {
|
|
// for ISO strings we do not use the normal bubbling rules:
|
|
// * milliseconds bubble up until they become hours
|
|
// * days do not bubble at all
|
|
// * months bubble up until they become years
|
|
// This is because there is no context-free conversion between hours and days
|
|
// (think of clock changes)
|
|
// and also not between days and months (28-31 days per month)
|
|
if (!this.isValid()) {
|
|
return this.localeData().invalidDate();
|
|
}
|
|
|
|
var seconds = abs$1(this._milliseconds) / 1000;
|
|
var days = abs$1(this._days);
|
|
var months = abs$1(this._months);
|
|
var minutes, hours, years;
|
|
|
|
// 3600 seconds -> 60 minutes -> 1 hour
|
|
minutes = absFloor(seconds / 60);
|
|
hours = absFloor(minutes / 60);
|
|
seconds %= 60;
|
|
minutes %= 60;
|
|
|
|
// 12 months -> 1 year
|
|
years = absFloor(months / 12);
|
|
months %= 12;
|
|
|
|
|
|
// inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js
|
|
var Y = years;
|
|
var M = months;
|
|
var D = days;
|
|
var h = hours;
|
|
var m = minutes;
|
|
var s = seconds ? seconds.toFixed(3).replace(/\.?0+$/, '') : '';
|
|
var total = this.asSeconds();
|
|
|
|
if (!total) {
|
|
// this is the same as C#'s (Noda) and python (isodate)...
|
|
// but not other JS (goog.date)
|
|
return 'P0D';
|
|
}
|
|
|
|
var totalSign = total < 0 ? '-' : '';
|
|
var ymSign = sign(this._months) !== sign(total) ? '-' : '';
|
|
var daysSign = sign(this._days) !== sign(total) ? '-' : '';
|
|
var hmsSign = sign(this._milliseconds) !== sign(total) ? '-' : '';
|
|
|
|
return totalSign + 'P' +
|
|
(Y ? ymSign + Y + 'Y' : '') +
|
|
(M ? ymSign + M + 'M' : '') +
|
|
(D ? daysSign + D + 'D' : '') +
|
|
((h || m || s) ? 'T' : '') +
|
|
(h ? hmsSign + h + 'H' : '') +
|
|
(m ? hmsSign + m + 'M' : '') +
|
|
(s ? hmsSign + s + 'S' : '');
|
|
}
|
|
|
|
var proto$2 = Duration.prototype;
|
|
|
|
proto$2.isValid = isValid$1;
|
|
proto$2.abs = abs;
|
|
proto$2.add = add$1;
|
|
proto$2.subtract = subtract$1;
|
|
proto$2.as = as;
|
|
proto$2.asMilliseconds = asMilliseconds;
|
|
proto$2.asSeconds = asSeconds;
|
|
proto$2.asMinutes = asMinutes;
|
|
proto$2.asHours = asHours;
|
|
proto$2.asDays = asDays;
|
|
proto$2.asWeeks = asWeeks;
|
|
proto$2.asMonths = asMonths;
|
|
proto$2.asYears = asYears;
|
|
proto$2.valueOf = valueOf$1;
|
|
proto$2._bubble = bubble;
|
|
proto$2.clone = clone$1;
|
|
proto$2.get = get$2;
|
|
proto$2.milliseconds = milliseconds;
|
|
proto$2.seconds = seconds;
|
|
proto$2.minutes = minutes;
|
|
proto$2.hours = hours;
|
|
proto$2.days = days;
|
|
proto$2.weeks = weeks;
|
|
proto$2.months = months;
|
|
proto$2.years = years;
|
|
proto$2.humanize = humanize;
|
|
proto$2.toISOString = toISOString$1;
|
|
proto$2.toString = toISOString$1;
|
|
proto$2.toJSON = toISOString$1;
|
|
proto$2.locale = locale;
|
|
proto$2.localeData = localeData;
|
|
|
|
proto$2.toIsoString = deprecate('toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', toISOString$1);
|
|
proto$2.lang = lang;
|
|
|
|
// Side effect imports
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('X', 0, 0, 'unix');
|
|
addFormatToken('x', 0, 0, 'valueOf');
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('x', matchSigned);
|
|
addRegexToken('X', matchTimestamp);
|
|
addParseToken('X', function (input, array, config) {
|
|
config._d = new Date(parseFloat(input, 10) * 1000);
|
|
});
|
|
addParseToken('x', function (input, array, config) {
|
|
config._d = new Date(toInt(input));
|
|
});
|
|
|
|
// Side effect imports
|
|
|
|
|
|
hooks.version = '2.21.0';
|
|
|
|
setHookCallback(createLocal);
|
|
|
|
hooks.fn = proto;
|
|
hooks.min = min;
|
|
hooks.max = max;
|
|
hooks.now = now;
|
|
hooks.utc = createUTC;
|
|
hooks.unix = createUnix;
|
|
hooks.months = listMonths;
|
|
hooks.isDate = isDate;
|
|
hooks.locale = getSetGlobalLocale;
|
|
hooks.invalid = createInvalid;
|
|
hooks.duration = createDuration;
|
|
hooks.isMoment = isMoment;
|
|
hooks.weekdays = listWeekdays;
|
|
hooks.parseZone = createInZone;
|
|
hooks.localeData = getLocale;
|
|
hooks.isDuration = isDuration;
|
|
hooks.monthsShort = listMonthsShort;
|
|
hooks.weekdaysMin = listWeekdaysMin;
|
|
hooks.defineLocale = defineLocale;
|
|
hooks.updateLocale = updateLocale;
|
|
hooks.locales = listLocales;
|
|
hooks.weekdaysShort = listWeekdaysShort;
|
|
hooks.normalizeUnits = normalizeUnits;
|
|
hooks.relativeTimeRounding = getSetRelativeTimeRounding;
|
|
hooks.relativeTimeThreshold = getSetRelativeTimeThreshold;
|
|
hooks.calendarFormat = getCalendarFormat;
|
|
hooks.prototype = proto;
|
|
|
|
// currently HTML5 input type only supports 24-hour formats
|
|
hooks.HTML5_FMT = {
|
|
DATETIME_LOCAL: 'YYYY-MM-DDTHH:mm', // <input type="datetime-local" />
|
|
DATETIME_LOCAL_SECONDS: 'YYYY-MM-DDTHH:mm:ss', // <input type="datetime-local" step="1" />
|
|
DATETIME_LOCAL_MS: 'YYYY-MM-DDTHH:mm:ss.SSS', // <input type="datetime-local" step="0.001" />
|
|
DATE: 'YYYY-MM-DD', // <input type="date" />
|
|
TIME: 'HH:mm', // <input type="time" />
|
|
TIME_SECONDS: 'HH:mm:ss', // <input type="time" step="1" />
|
|
TIME_MS: 'HH:mm:ss.SSS', // <input type="time" step="0.001" />
|
|
WEEK: 'YYYY-[W]WW', // <input type="week" />
|
|
MONTH: 'YYYY-MM' // <input type="month" />
|
|
};
|
|
|
|
return hooks;
|
|
|
|
})));
|
|
|
|
/*!
|
|
* Bootstrap v3.3.5 (http://getbootstrap.com)
|
|
* Copyright 2011-2015 Twitter, Inc.
|
|
* Licensed under the MIT license
|
|
*/
|
|
|
|
if (typeof jQuery === 'undefined') {
|
|
throw new Error('Bootstrap\'s JavaScript requires jQuery')
|
|
}
|
|
|
|
+function ($) {
|
|
'use strict';
|
|
var version = $.fn.jquery.split(' ')[0].split('.')
|
|
if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1)) {
|
|
throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher')
|
|
}
|
|
}(jQuery);
|
|
|
|
/* ========================================================================
|
|
* Bootstrap: transition.js v3.3.5
|
|
* http://getbootstrap.com/javascript/#transitions
|
|
* ========================================================================
|
|
* Copyright 2011-2015 Twitter, Inc.
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
* ======================================================================== */
|
|
|
|
|
|
+function ($) {
|
|
'use strict';
|
|
|
|
// CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
|
|
// ============================================================
|
|
|
|
function transitionEnd() {
|
|
var el = document.createElement('bootstrap')
|
|
|
|
var transEndEventNames = {
|
|
WebkitTransition : 'webkitTransitionEnd',
|
|
MozTransition : 'transitionend',
|
|
OTransition : 'oTransitionEnd otransitionend',
|
|
transition : 'transitionend'
|
|
}
|
|
|
|
for (var name in transEndEventNames) {
|
|
if (el.style[name] !== undefined) {
|
|
return { end: transEndEventNames[name] }
|
|
}
|
|
}
|
|
|
|
return false // explicit for ie8 ( ._.)
|
|
}
|
|
|
|
// http://blog.alexmaccaw.com/css-transitions
|
|
$.fn.emulateTransitionEnd = function (duration) {
|
|
var called = false
|
|
var $el = this
|
|
$(this).one('bsTransitionEnd', function () { called = true })
|
|
var callback = function () { if (!called) $($el).trigger($.support.transition.end) }
|
|
setTimeout(callback, duration)
|
|
return this
|
|
}
|
|
|
|
$(function () {
|
|
$.support.transition = transitionEnd()
|
|
|
|
if (!$.support.transition) return
|
|
|
|
$.event.special.bsTransitionEnd = {
|
|
bindType: $.support.transition.end,
|
|
delegateType: $.support.transition.end,
|
|
handle: function (e) {
|
|
if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments)
|
|
}
|
|
}
|
|
})
|
|
|
|
}(jQuery);
|
|
|
|
/* ========================================================================
|
|
* Bootstrap: alert.js v3.3.5
|
|
* http://getbootstrap.com/javascript/#alerts
|
|
* ========================================================================
|
|
* Copyright 2011-2015 Twitter, Inc.
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
* ======================================================================== */
|
|
|
|
|
|
+function ($) {
|
|
'use strict';
|
|
|
|
// ALERT CLASS DEFINITION
|
|
// ======================
|
|
|
|
var dismiss = '[data-dismiss="alert"]'
|
|
var Alert = function (el) {
|
|
$(el).on('click', dismiss, this.close)
|
|
}
|
|
|
|
Alert.VERSION = '3.3.5'
|
|
|
|
Alert.TRANSITION_DURATION = 150
|
|
|
|
Alert.prototype.close = function (e) {
|
|
var $this = $(this)
|
|
var selector = $this.attr('data-target')
|
|
|
|
if (!selector) {
|
|
selector = $this.attr('href')
|
|
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
|
|
}
|
|
|
|
var $parent = $(selector)
|
|
|
|
if (e) e.preventDefault()
|
|
|
|
if (!$parent.length) {
|
|
$parent = $this.closest('.alert')
|
|
}
|
|
|
|
$parent.trigger(e = $.Event('close.bs.alert'))
|
|
|
|
if (e.isDefaultPrevented()) return
|
|
|
|
$parent.removeClass('in')
|
|
|
|
function removeElement() {
|
|
// detach from parent, fire event then clean up data
|
|
$parent.detach().trigger('closed.bs.alert').remove()
|
|
}
|
|
|
|
$.support.transition && $parent.hasClass('fade') ?
|
|
$parent
|
|
.one('bsTransitionEnd', removeElement)
|
|
.emulateTransitionEnd(Alert.TRANSITION_DURATION) :
|
|
removeElement()
|
|
}
|
|
|
|
|
|
// ALERT PLUGIN DEFINITION
|
|
// =======================
|
|
|
|
function Plugin(option) {
|
|
return this.each(function () {
|
|
var $this = $(this)
|
|
var data = $this.data('bs.alert')
|
|
|
|
if (!data) $this.data('bs.alert', (data = new Alert(this)))
|
|
if (typeof option == 'string') data[option].call($this)
|
|
})
|
|
}
|
|
|
|
var old = $.fn.alert
|
|
|
|
$.fn.alert = Plugin
|
|
$.fn.alert.Constructor = Alert
|
|
|
|
|
|
// ALERT NO CONFLICT
|
|
// =================
|
|
|
|
$.fn.alert.noConflict = function () {
|
|
$.fn.alert = old
|
|
return this
|
|
}
|
|
|
|
|
|
// ALERT DATA-API
|
|
// ==============
|
|
|
|
$(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close)
|
|
|
|
}(jQuery);
|
|
|
|
/* ========================================================================
|
|
* Bootstrap: button.js v3.3.5
|
|
* http://getbootstrap.com/javascript/#buttons
|
|
* ========================================================================
|
|
* Copyright 2011-2015 Twitter, Inc.
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
* ======================================================================== */
|
|
|
|
|
|
+function ($) {
|
|
'use strict';
|
|
|
|
// BUTTON PUBLIC CLASS DEFINITION
|
|
// ==============================
|
|
|
|
var Button = function (element, options) {
|
|
this.$element = $(element)
|
|
this.options = $.extend({}, Button.DEFAULTS, options)
|
|
this.isLoading = false
|
|
}
|
|
|
|
Button.VERSION = '3.3.5'
|
|
|
|
Button.DEFAULTS = {
|
|
loadingText: 'loading...'
|
|
}
|
|
|
|
Button.prototype.setState = function (state) {
|
|
var d = 'disabled'
|
|
var $el = this.$element
|
|
var val = $el.is('input') ? 'val' : 'html'
|
|
var data = $el.data()
|
|
|
|
state += 'Text'
|
|
|
|
if (data.resetText == null) $el.data('resetText', $el[val]())
|
|
|
|
// push to event loop to allow forms to submit
|
|
setTimeout($.proxy(function () {
|
|
$el[val](data[state] == null ? this.options[state] : data[state])
|
|
|
|
if (state == 'loadingText') {
|
|
this.isLoading = true
|
|
$el.addClass(d).attr(d, d)
|
|
} else if (this.isLoading) {
|
|
this.isLoading = false
|
|
$el.removeClass(d).removeAttr(d)
|
|
}
|
|
}, this), 0)
|
|
}
|
|
|
|
Button.prototype.toggle = function () {
|
|
var changed = true
|
|
var $parent = this.$element.closest('[data-toggle="buttons"]')
|
|
|
|
if ($parent.length) {
|
|
var $input = this.$element.find('input')
|
|
if ($input.prop('type') == 'radio') {
|
|
if ($input.prop('checked')) changed = false
|
|
$parent.find('.active').removeClass('active')
|
|
this.$element.addClass('active')
|
|
} else if ($input.prop('type') == 'checkbox') {
|
|
if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false
|
|
this.$element.toggleClass('active')
|
|
}
|
|
$input.prop('checked', this.$element.hasClass('active'))
|
|
if (changed) $input.trigger('change')
|
|
} else {
|
|
this.$element.attr('aria-pressed', !this.$element.hasClass('active'))
|
|
this.$element.toggleClass('active')
|
|
}
|
|
}
|
|
|
|
|
|
// BUTTON PLUGIN DEFINITION
|
|
// ========================
|
|
|
|
function Plugin(option) {
|
|
return this.each(function () {
|
|
var $this = $(this)
|
|
var data = $this.data('bs.button')
|
|
var options = typeof option == 'object' && option
|
|
|
|
if (!data) $this.data('bs.button', (data = new Button(this, options)))
|
|
|
|
if (option == 'toggle') data.toggle()
|
|
else if (option) data.setState(option)
|
|
})
|
|
}
|
|
|
|
var old = $.fn.button
|
|
|
|
$.fn.button = Plugin
|
|
$.fn.button.Constructor = Button
|
|
|
|
|
|
// BUTTON NO CONFLICT
|
|
// ==================
|
|
|
|
$.fn.button.noConflict = function () {
|
|
$.fn.button = old
|
|
return this
|
|
}
|
|
|
|
|
|
// BUTTON DATA-API
|
|
// ===============
|
|
|
|
$(document)
|
|
.on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) {
|
|
var $btn = $(e.target)
|
|
if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
|
|
Plugin.call($btn, 'toggle')
|
|
if (!($(e.target).is('input[type="radio"]') || $(e.target).is('input[type="checkbox"]'))) e.preventDefault()
|
|
})
|
|
.on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) {
|
|
$(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type))
|
|
})
|
|
|
|
}(jQuery);
|
|
|
|
/* ========================================================================
|
|
* Bootstrap: carousel.js v3.3.5
|
|
* http://getbootstrap.com/javascript/#carousel
|
|
* ========================================================================
|
|
* Copyright 2011-2015 Twitter, Inc.
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
* ======================================================================== */
|
|
|
|
|
|
+function ($) {
|
|
'use strict';
|
|
|
|
// CAROUSEL CLASS DEFINITION
|
|
// =========================
|
|
|
|
var Carousel = function (element, options) {
|
|
this.$element = $(element)
|
|
this.$indicators = this.$element.find('.carousel-indicators')
|
|
this.options = options
|
|
this.paused = null
|
|
this.sliding = null
|
|
this.interval = null
|
|
this.$active = null
|
|
this.$items = null
|
|
|
|
this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this))
|
|
|
|
this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element
|
|
.on('mouseenter.bs.carousel', $.proxy(this.pause, this))
|
|
.on('mouseleave.bs.carousel', $.proxy(this.cycle, this))
|
|
}
|
|
|
|
Carousel.VERSION = '3.3.5'
|
|
|
|
Carousel.TRANSITION_DURATION = 600
|
|
|
|
Carousel.DEFAULTS = {
|
|
interval: 5000,
|
|
pause: 'hover',
|
|
wrap: true,
|
|
keyboard: true
|
|
}
|
|
|
|
Carousel.prototype.keydown = function (e) {
|
|
if (/input|textarea/i.test(e.target.tagName)) return
|
|
switch (e.which) {
|
|
case 37: this.prev(); break
|
|
case 39: this.next(); break
|
|
default: return
|
|
}
|
|
|
|
e.preventDefault()
|
|
}
|
|
|
|
Carousel.prototype.cycle = function (e) {
|
|
e || (this.paused = false)
|
|
|
|
this.interval && clearInterval(this.interval)
|
|
|
|
this.options.interval
|
|
&& !this.paused
|
|
&& (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
|
|
|
|
return this
|
|
}
|
|
|
|
Carousel.prototype.getItemIndex = function (item) {
|
|
this.$items = item.parent().children('.item')
|
|
return this.$items.index(item || this.$active)
|
|
}
|
|
|
|
Carousel.prototype.getItemForDirection = function (direction, active) {
|
|
var activeIndex = this.getItemIndex(active)
|
|
var willWrap = (direction == 'prev' && activeIndex === 0)
|
|
|| (direction == 'next' && activeIndex == (this.$items.length - 1))
|
|
if (willWrap && !this.options.wrap) return active
|
|
var delta = direction == 'prev' ? -1 : 1
|
|
var itemIndex = (activeIndex + delta) % this.$items.length
|
|
return this.$items.eq(itemIndex)
|
|
}
|
|
|
|
Carousel.prototype.to = function (pos) {
|
|
var that = this
|
|
var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active'))
|
|
|
|
if (pos > (this.$items.length - 1) || pos < 0) return
|
|
|
|
if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid"
|
|
if (activeIndex == pos) return this.pause().cycle()
|
|
|
|
return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos))
|
|
}
|
|
|
|
Carousel.prototype.pause = function (e) {
|
|
e || (this.paused = true)
|
|
|
|
if (this.$element.find('.next, .prev').length && $.support.transition) {
|
|
this.$element.trigger($.support.transition.end)
|
|
this.cycle(true)
|
|
}
|
|
|
|
this.interval = clearInterval(this.interval)
|
|
|
|
return this
|
|
}
|
|
|
|
Carousel.prototype.next = function () {
|
|
if (this.sliding) return
|
|
return this.slide('next')
|
|
}
|
|
|
|
Carousel.prototype.prev = function () {
|
|
if (this.sliding) return
|
|
return this.slide('prev')
|
|
}
|
|
|
|
Carousel.prototype.slide = function (type, next) {
|
|
var $active = this.$element.find('.item.active')
|
|
var $next = next || this.getItemForDirection(type, $active)
|
|
var isCycling = this.interval
|
|
var direction = type == 'next' ? 'left' : 'right'
|
|
var that = this
|
|
|
|
if ($next.hasClass('active')) return (this.sliding = false)
|
|
|
|
var relatedTarget = $next[0]
|
|
var slideEvent = $.Event('slide.bs.carousel', {
|
|
relatedTarget: relatedTarget,
|
|
direction: direction
|
|
})
|
|
this.$element.trigger(slideEvent)
|
|
if (slideEvent.isDefaultPrevented()) return
|
|
|
|
this.sliding = true
|
|
|
|
isCycling && this.pause()
|
|
|
|
if (this.$indicators.length) {
|
|
this.$indicators.find('.active').removeClass('active')
|
|
var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)])
|
|
$nextIndicator && $nextIndicator.addClass('active')
|
|
}
|
|
|
|
var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid"
|
|
if ($.support.transition && this.$element.hasClass('slide')) {
|
|
$next.addClass(type)
|
|
$next[0].offsetWidth // force reflow
|
|
$active.addClass(direction)
|
|
$next.addClass(direction)
|
|
$active
|
|
.one('bsTransitionEnd', function () {
|
|
$next.removeClass([type, direction].join(' ')).addClass('active')
|
|
$active.removeClass(['active', direction].join(' '))
|
|
that.sliding = false
|
|
setTimeout(function () {
|
|
that.$element.trigger(slidEvent)
|
|
}, 0)
|
|
})
|
|
.emulateTransitionEnd(Carousel.TRANSITION_DURATION)
|
|
} else {
|
|
$active.removeClass('active')
|
|
$next.addClass('active')
|
|
this.sliding = false
|
|
this.$element.trigger(slidEvent)
|
|
}
|
|
|
|
isCycling && this.cycle()
|
|
|
|
return this
|
|
}
|
|
|
|
|
|
// CAROUSEL PLUGIN DEFINITION
|
|
// ==========================
|
|
|
|
function Plugin(option) {
|
|
return this.each(function () {
|
|
var $this = $(this)
|
|
var data = $this.data('bs.carousel')
|
|
var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option)
|
|
var action = typeof option == 'string' ? option : options.slide
|
|
|
|
if (!data) $this.data('bs.carousel', (data = new Carousel(this, options)))
|
|
if (typeof option == 'number') data.to(option)
|
|
else if (action) data[action]()
|
|
else if (options.interval) data.pause().cycle()
|
|
})
|
|
}
|
|
|
|
var old = $.fn.carousel
|
|
|
|
$.fn.carousel = Plugin
|
|
$.fn.carousel.Constructor = Carousel
|
|
|
|
|
|
// CAROUSEL NO CONFLICT
|
|
// ====================
|
|
|
|
$.fn.carousel.noConflict = function () {
|
|
$.fn.carousel = old
|
|
return this
|
|
}
|
|
|
|
|
|
// CAROUSEL DATA-API
|
|
// =================
|
|
|
|
var clickHandler = function (e) {
|
|
var href
|
|
var $this = $(this)
|
|
var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7
|
|
if (!$target.hasClass('carousel')) return
|
|
var options = $.extend({}, $target.data(), $this.data())
|
|
var slideIndex = $this.attr('data-slide-to')
|
|
if (slideIndex) options.interval = false
|
|
|
|
Plugin.call($target, options)
|
|
|
|
if (slideIndex) {
|
|
$target.data('bs.carousel').to(slideIndex)
|
|
}
|
|
|
|
e.preventDefault()
|
|
}
|
|
|
|
$(document)
|
|
.on('click.bs.carousel.data-api', '[data-slide]', clickHandler)
|
|
.on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler)
|
|
|
|
$(window).on('load', function () {
|
|
$('[data-ride="carousel"]').each(function () {
|
|
var $carousel = $(this)
|
|
Plugin.call($carousel, $carousel.data())
|
|
})
|
|
})
|
|
|
|
}(jQuery);
|
|
|
|
/* ========================================================================
|
|
* Bootstrap: collapse.js v3.3.5
|
|
* http://getbootstrap.com/javascript/#collapse
|
|
* ========================================================================
|
|
* Copyright 2011-2015 Twitter, Inc.
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
* ======================================================================== */
|
|
|
|
|
|
+function ($) {
|
|
'use strict';
|
|
|
|
// COLLAPSE PUBLIC CLASS DEFINITION
|
|
// ================================
|
|
|
|
var Collapse = function (element, options) {
|
|
this.$element = $(element)
|
|
this.options = $.extend({}, Collapse.DEFAULTS, options)
|
|
this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' +
|
|
'[data-toggle="collapse"][data-target="#' + element.id + '"]')
|
|
this.transitioning = null
|
|
|
|
if (this.options.parent) {
|
|
this.$parent = this.getParent()
|
|
} else {
|
|
this.addAriaAndCollapsedClass(this.$element, this.$trigger)
|
|
}
|
|
|
|
if (this.options.toggle) this.toggle()
|
|
}
|
|
|
|
Collapse.VERSION = '3.3.5'
|
|
|
|
Collapse.TRANSITION_DURATION = 350
|
|
|
|
Collapse.DEFAULTS = {
|
|
toggle: true
|
|
}
|
|
|
|
Collapse.prototype.dimension = function () {
|
|
var hasWidth = this.$element.hasClass('width')
|
|
return hasWidth ? 'width' : 'height'
|
|
}
|
|
|
|
Collapse.prototype.show = function () {
|
|
if (this.transitioning || this.$element.hasClass('in')) return
|
|
|
|
var activesData
|
|
var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing')
|
|
|
|
if (actives && actives.length) {
|
|
activesData = actives.data('bs.collapse')
|
|
if (activesData && activesData.transitioning) return
|
|
}
|
|
|
|
var startEvent = $.Event('show.bs.collapse')
|
|
this.$element.trigger(startEvent)
|
|
if (startEvent.isDefaultPrevented()) return
|
|
|
|
if (actives && actives.length) {
|
|
Plugin.call(actives, 'hide')
|
|
activesData || actives.data('bs.collapse', null)
|
|
}
|
|
|
|
var dimension = this.dimension()
|
|
|
|
this.$element
|
|
.removeClass('collapse')
|
|
.addClass('collapsing')[dimension](0)
|
|
.attr('aria-expanded', true)
|
|
|
|
this.$trigger
|
|
.removeClass('collapsed')
|
|
.attr('aria-expanded', true)
|
|
|
|
this.transitioning = 1
|
|
|
|
var complete = function () {
|
|
this.$element
|
|
.removeClass('collapsing')
|
|
.addClass('collapse in')[dimension]('')
|
|
this.transitioning = 0
|
|
this.$element
|
|
.trigger('shown.bs.collapse')
|
|
}
|
|
|
|
if (!$.support.transition) return complete.call(this)
|
|
|
|
var scrollSize = $.camelCase(['scroll', dimension].join('-'))
|
|
|
|
this.$element
|
|
.one('bsTransitionEnd', $.proxy(complete, this))
|
|
.emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize])
|
|
}
|
|
|
|
Collapse.prototype.hide = function () {
|
|
if (this.transitioning || !this.$element.hasClass('in')) return
|
|
|
|
var startEvent = $.Event('hide.bs.collapse')
|
|
this.$element.trigger(startEvent)
|
|
if (startEvent.isDefaultPrevented()) return
|
|
|
|
var dimension = this.dimension()
|
|
|
|
this.$element[dimension](this.$element[dimension]())[0].offsetHeight
|
|
|
|
this.$element
|
|
.addClass('collapsing')
|
|
.removeClass('collapse in')
|
|
.attr('aria-expanded', false)
|
|
|
|
this.$trigger
|
|
.addClass('collapsed')
|
|
.attr('aria-expanded', false)
|
|
|
|
this.transitioning = 1
|
|
|
|
var complete = function () {
|
|
this.transitioning = 0
|
|
this.$element
|
|
.removeClass('collapsing')
|
|
.addClass('collapse')
|
|
.trigger('hidden.bs.collapse')
|
|
}
|
|
|
|
if (!$.support.transition) return complete.call(this)
|
|
|
|
this.$element
|
|
[dimension](0)
|
|
.one('bsTransitionEnd', $.proxy(complete, this))
|
|
.emulateTransitionEnd(Collapse.TRANSITION_DURATION)
|
|
}
|
|
|
|
Collapse.prototype.toggle = function () {
|
|
this[this.$element.hasClass('in') ? 'hide' : 'show']()
|
|
}
|
|
|
|
Collapse.prototype.getParent = function () {
|
|
return $(this.options.parent)
|
|
.find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]')
|
|
.each($.proxy(function (i, element) {
|
|
var $element = $(element)
|
|
this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element)
|
|
}, this))
|
|
.end()
|
|
}
|
|
|
|
Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) {
|
|
var isOpen = $element.hasClass('in')
|
|
|
|
$element.attr('aria-expanded', isOpen)
|
|
$trigger
|
|
.toggleClass('collapsed', !isOpen)
|
|
.attr('aria-expanded', isOpen)
|
|
}
|
|
|
|
function getTargetFromTrigger($trigger) {
|
|
var href
|
|
var target = $trigger.attr('data-target')
|
|
|| (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7
|
|
|
|
return $(target)
|
|
}
|
|
|
|
|
|
// COLLAPSE PLUGIN DEFINITION
|
|
// ==========================
|
|
|
|
function Plugin(option) {
|
|
return this.each(function () {
|
|
var $this = $(this)
|
|
var data = $this.data('bs.collapse')
|
|
var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)
|
|
|
|
if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false
|
|
if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))
|
|
if (typeof option == 'string') data[option]()
|
|
})
|
|
}
|
|
|
|
var old = $.fn.collapse
|
|
|
|
$.fn.collapse = Plugin
|
|
$.fn.collapse.Constructor = Collapse
|
|
|
|
|
|
// COLLAPSE NO CONFLICT
|
|
// ====================
|
|
|
|
$.fn.collapse.noConflict = function () {
|
|
$.fn.collapse = old
|
|
return this
|
|
}
|
|
|
|
|
|
// COLLAPSE DATA-API
|
|
// =================
|
|
|
|
$(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) {
|
|
var $this = $(this)
|
|
|
|
if (!$this.attr('data-target')) e.preventDefault()
|
|
|
|
var $target = getTargetFromTrigger($this)
|
|
var data = $target.data('bs.collapse')
|
|
var option = data ? 'toggle' : $this.data()
|
|
|
|
Plugin.call($target, option)
|
|
})
|
|
|
|
}(jQuery);
|
|
|
|
/* ========================================================================
|
|
* Bootstrap: dropdown.js v3.3.5
|
|
* http://getbootstrap.com/javascript/#dropdowns
|
|
* ========================================================================
|
|
* Copyright 2011-2015 Twitter, Inc.
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
* ======================================================================== */
|
|
|
|
|
|
+function ($) {
|
|
'use strict';
|
|
|
|
// DROPDOWN CLASS DEFINITION
|
|
// =========================
|
|
|
|
var backdrop = '.dropdown-backdrop'
|
|
var toggle = '[data-toggle="dropdown"]'
|
|
var Dropdown = function (element) {
|
|
$(element).on('click.bs.dropdown', this.toggle)
|
|
}
|
|
|
|
Dropdown.VERSION = '3.3.5'
|
|
|
|
function getParent($this) {
|
|
var selector = $this.attr('data-target')
|
|
|
|
if (!selector) {
|
|
selector = $this.attr('href')
|
|
selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
|
|
}
|
|
|
|
var $parent = selector && $(selector)
|
|
|
|
return $parent && $parent.length ? $parent : $this.parent()
|
|
}
|
|
|
|
function clearMenus(e) {
|
|
if (e && e.which === 3) return
|
|
$(backdrop).remove()
|
|
$(toggle).each(function () {
|
|
var $this = $(this)
|
|
var $parent = getParent($this)
|
|
var relatedTarget = { relatedTarget: this }
|
|
|
|
if (!$parent.hasClass('open')) return
|
|
|
|
if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return
|
|
|
|
$parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))
|
|
|
|
if (e.isDefaultPrevented()) return
|
|
|
|
$this.attr('aria-expanded', 'false')
|
|
$parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget)
|
|
})
|
|
}
|
|
|
|
Dropdown.prototype.toggle = function (e) {
|
|
var $this = $(this)
|
|
|
|
if ($this.is('.disabled, :disabled')) return
|
|
|
|
var $parent = getParent($this)
|
|
var isActive = $parent.hasClass('open')
|
|
|
|
clearMenus()
|
|
|
|
if (!isActive) {
|
|
if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
|
|
// if mobile we use a backdrop because click events don't delegate
|
|
$(document.createElement('div'))
|
|
.addClass('dropdown-backdrop')
|
|
.insertAfter($(this))
|
|
.on('click', clearMenus)
|
|
}
|
|
|
|
var relatedTarget = { relatedTarget: this }
|
|
$parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget))
|
|
|
|
if (e.isDefaultPrevented()) return
|
|
|
|
$this
|
|
.trigger('focus')
|
|
.attr('aria-expanded', 'true')
|
|
|
|
$parent
|
|
.toggleClass('open')
|
|
.trigger('shown.bs.dropdown', relatedTarget)
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
Dropdown.prototype.keydown = function (e) {
|
|
if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return
|
|
|
|
var $this = $(this)
|
|
|
|
e.preventDefault()
|
|
e.stopPropagation()
|
|
|
|
if ($this.is('.disabled, :disabled')) return
|
|
|
|
var $parent = getParent($this)
|
|
var isActive = $parent.hasClass('open')
|
|
|
|
if (!isActive && e.which != 27 || isActive && e.which == 27) {
|
|
if (e.which == 27) $parent.find(toggle).trigger('focus')
|
|
return $this.trigger('click')
|
|
}
|
|
|
|
var desc = ' li:not(.disabled):visible a'
|
|
var $items = $parent.find('.dropdown-menu' + desc)
|
|
|
|
if (!$items.length) return
|
|
|
|
var index = $items.index(e.target)
|
|
|
|
if (e.which == 38 && index > 0) index-- // up
|
|
if (e.which == 40 && index < $items.length - 1) index++ // down
|
|
if (!~index) index = 0
|
|
|
|
$items.eq(index).trigger('focus')
|
|
}
|
|
|
|
|
|
// DROPDOWN PLUGIN DEFINITION
|
|
// ==========================
|
|
|
|
function Plugin(option) {
|
|
return this.each(function () {
|
|
var $this = $(this)
|
|
var data = $this.data('bs.dropdown')
|
|
|
|
if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)))
|
|
if (typeof option == 'string') data[option].call($this)
|
|
})
|
|
}
|
|
|
|
var old = $.fn.dropdown
|
|
|
|
$.fn.dropdown = Plugin
|
|
$.fn.dropdown.Constructor = Dropdown
|
|
|
|
|
|
// DROPDOWN NO CONFLICT
|
|
// ====================
|
|
|
|
$.fn.dropdown.noConflict = function () {
|
|
$.fn.dropdown = old
|
|
return this
|
|
}
|
|
|
|
|
|
// APPLY TO STANDARD DROPDOWN ELEMENTS
|
|
// ===================================
|
|
|
|
$(document)
|
|
.on('click.bs.dropdown.data-api', clearMenus)
|
|
.on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
|
|
.on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
|
|
.on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)
|
|
.on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown)
|
|
|
|
}(jQuery);
|
|
|
|
/* ========================================================================
|
|
* Bootstrap: modal.js v3.3.5
|
|
* http://getbootstrap.com/javascript/#modals
|
|
* ========================================================================
|
|
* Copyright 2011-2015 Twitter, Inc.
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
* ======================================================================== */
|
|
|
|
|
|
+function ($) {
|
|
'use strict';
|
|
|
|
// MODAL CLASS DEFINITION
|
|
// ======================
|
|
|
|
var Modal = function (element, options) {
|
|
this.options = options
|
|
this.$body = $(document.body)
|
|
this.$element = $(element)
|
|
this.$dialog = this.$element.find('.modal-dialog')
|
|
this.$backdrop = null
|
|
this.isShown = null
|
|
this.originalBodyPad = null
|
|
this.scrollbarWidth = 0
|
|
this.ignoreBackdropClick = false
|
|
|
|
if (this.options.remote) {
|
|
this.$element
|
|
.find('.modal-content')
|
|
.load(this.options.remote, $.proxy(function () {
|
|
this.$element.trigger('loaded.bs.modal')
|
|
}, this))
|
|
}
|
|
}
|
|
|
|
Modal.VERSION = '3.3.5'
|
|
|
|
Modal.TRANSITION_DURATION = 300
|
|
Modal.BACKDROP_TRANSITION_DURATION = 150
|
|
|
|
Modal.DEFAULTS = {
|
|
backdrop: true,
|
|
keyboard: true,
|
|
show: true
|
|
}
|
|
|
|
Modal.prototype.toggle = function (_relatedTarget) {
|
|
return this.isShown ? this.hide() : this.show(_relatedTarget)
|
|
}
|
|
|
|
Modal.prototype.show = function (_relatedTarget) {
|
|
var that = this
|
|
var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })
|
|
|
|
this.$element.trigger(e)
|
|
|
|
if (this.isShown || e.isDefaultPrevented()) return
|
|
|
|
this.isShown = true
|
|
|
|
this.checkScrollbar()
|
|
this.setScrollbar()
|
|
this.$body.addClass('modal-open')
|
|
|
|
this.escape()
|
|
this.resize()
|
|
|
|
this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))
|
|
|
|
this.$dialog.on('mousedown.dismiss.bs.modal', function () {
|
|
that.$element.one('mouseup.dismiss.bs.modal', function (e) {
|
|
if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true
|
|
})
|
|
})
|
|
|
|
this.backdrop(function () {
|
|
var transition = $.support.transition && that.$element.hasClass('fade')
|
|
|
|
if (!that.$element.parent().length) {
|
|
that.$element.appendTo(that.$body) // don't move modals dom position
|
|
}
|
|
|
|
that.$element
|
|
.show()
|
|
.scrollTop(0)
|
|
|
|
that.adjustDialog()
|
|
|
|
if (transition) {
|
|
that.$element[0].offsetWidth // force reflow
|
|
}
|
|
|
|
that.$element.addClass('in')
|
|
|
|
that.enforceFocus()
|
|
|
|
var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })
|
|
|
|
transition ?
|
|
that.$dialog // wait for modal to slide in
|
|
.one('bsTransitionEnd', function () {
|
|
that.$element.trigger('focus').trigger(e)
|
|
})
|
|
.emulateTransitionEnd(Modal.TRANSITION_DURATION) :
|
|
that.$element.trigger('focus').trigger(e)
|
|
})
|
|
}
|
|
|
|
Modal.prototype.hide = function (e) {
|
|
if (e) e.preventDefault()
|
|
|
|
e = $.Event('hide.bs.modal')
|
|
|
|
this.$element.trigger(e)
|
|
|
|
if (!this.isShown || e.isDefaultPrevented()) return
|
|
|
|
this.isShown = false
|
|
|
|
this.escape()
|
|
this.resize()
|
|
|
|
$(document).off('focusin.bs.modal')
|
|
|
|
this.$element
|
|
.removeClass('in')
|
|
.off('click.dismiss.bs.modal')
|
|
.off('mouseup.dismiss.bs.modal')
|
|
|
|
this.$dialog.off('mousedown.dismiss.bs.modal')
|
|
|
|
$.support.transition && this.$element.hasClass('fade') ?
|
|
this.$element
|
|
.one('bsTransitionEnd', $.proxy(this.hideModal, this))
|
|
.emulateTransitionEnd(Modal.TRANSITION_DURATION) :
|
|
this.hideModal()
|
|
}
|
|
|
|
Modal.prototype.enforceFocus = function () {
|
|
$(document)
|
|
.off('focusin.bs.modal') // guard against infinite focus loop
|
|
.on('focusin.bs.modal', $.proxy(function (e) {
|
|
if (this.$element[0] !== e.target && !this.$element.has(e.target).length) {
|
|
this.$element.trigger('focus')
|
|
}
|
|
}, this))
|
|
}
|
|
|
|
Modal.prototype.escape = function () {
|
|
if (this.isShown && this.options.keyboard) {
|
|
this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) {
|
|
e.which == 27 && this.hide()
|
|
}, this))
|
|
} else if (!this.isShown) {
|
|
this.$element.off('keydown.dismiss.bs.modal')
|
|
}
|
|
}
|
|
|
|
Modal.prototype.resize = function () {
|
|
if (this.isShown) {
|
|
$(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this))
|
|
} else {
|
|
$(window).off('resize.bs.modal')
|
|
}
|
|
}
|
|
|
|
Modal.prototype.hideModal = function () {
|
|
var that = this
|
|
this.$element.hide()
|
|
this.backdrop(function () {
|
|
that.$body.removeClass('modal-open')
|
|
that.resetAdjustments()
|
|
that.resetScrollbar()
|
|
that.$element.trigger('hidden.bs.modal')
|
|
})
|
|
}
|
|
|
|
Modal.prototype.removeBackdrop = function () {
|
|
this.$backdrop && this.$backdrop.remove()
|
|
this.$backdrop = null
|
|
}
|
|
|
|
Modal.prototype.backdrop = function (callback) {
|
|
var that = this
|
|
var animate = this.$element.hasClass('fade') ? 'fade' : ''
|
|
|
|
if (this.isShown && this.options.backdrop) {
|
|
var doAnimate = $.support.transition && animate
|
|
|
|
this.$backdrop = $(document.createElement('div'))
|
|
.addClass('modal-backdrop ' + animate)
|
|
.appendTo(this.$body)
|
|
|
|
this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) {
|
|
if (this.ignoreBackdropClick) {
|
|
this.ignoreBackdropClick = false
|
|
return
|
|
}
|
|
if (e.target !== e.currentTarget) return
|
|
this.options.backdrop == 'static'
|
|
? this.$element[0].focus()
|
|
: this.hide()
|
|
}, this))
|
|
|
|
if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
|
|
|
|
this.$backdrop.addClass('in')
|
|
|
|
if (!callback) return
|
|
|
|
doAnimate ?
|
|
this.$backdrop
|
|
.one('bsTransitionEnd', callback)
|
|
.emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
|
|
callback()
|
|
|
|
} else if (!this.isShown && this.$backdrop) {
|
|
this.$backdrop.removeClass('in')
|
|
|
|
var callbackRemove = function () {
|
|
that.removeBackdrop()
|
|
callback && callback()
|
|
}
|
|
$.support.transition && this.$element.hasClass('fade') ?
|
|
this.$backdrop
|
|
.one('bsTransitionEnd', callbackRemove)
|
|
.emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
|
|
callbackRemove()
|
|
|
|
} else if (callback) {
|
|
callback()
|
|
}
|
|
}
|
|
|
|
// these following methods are used to handle overflowing modals
|
|
|
|
Modal.prototype.handleUpdate = function () {
|
|
this.adjustDialog()
|
|
}
|
|
|
|
Modal.prototype.adjustDialog = function () {
|
|
var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight
|
|
|
|
this.$element.css({
|
|
paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',
|
|
paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''
|
|
})
|
|
}
|
|
|
|
Modal.prototype.resetAdjustments = function () {
|
|
this.$element.css({
|
|
paddingLeft: '',
|
|
paddingRight: ''
|
|
})
|
|
}
|
|
|
|
Modal.prototype.checkScrollbar = function () {
|
|
var fullWindowWidth = window.innerWidth
|
|
if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8
|
|
var documentElementRect = document.documentElement.getBoundingClientRect()
|
|
fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left)
|
|
}
|
|
this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth
|
|
this.scrollbarWidth = this.measureScrollbar()
|
|
}
|
|
|
|
Modal.prototype.setScrollbar = function () {
|
|
var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)
|
|
this.originalBodyPad = document.body.style.paddingRight || ''
|
|
if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth)
|
|
}
|
|
|
|
Modal.prototype.resetScrollbar = function () {
|
|
this.$body.css('padding-right', this.originalBodyPad)
|
|
}
|
|
|
|
Modal.prototype.measureScrollbar = function () { // thx walsh
|
|
var scrollDiv = document.createElement('div')
|
|
scrollDiv.className = 'modal-scrollbar-measure'
|
|
this.$body.append(scrollDiv)
|
|
var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth
|
|
this.$body[0].removeChild(scrollDiv)
|
|
return scrollbarWidth
|
|
}
|
|
|
|
|
|
// MODAL PLUGIN DEFINITION
|
|
// =======================
|
|
|
|
function Plugin(option, _relatedTarget) {
|
|
return this.each(function () {
|
|
var $this = $(this)
|
|
var data = $this.data('bs.modal')
|
|
var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)
|
|
|
|
if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
|
|
if (typeof option == 'string') data[option](_relatedTarget)
|
|
else if (options.show) data.show(_relatedTarget)
|
|
})
|
|
}
|
|
|
|
var old = $.fn.modal
|
|
|
|
$.fn.modal = Plugin
|
|
$.fn.modal.Constructor = Modal
|
|
|
|
|
|
// MODAL NO CONFLICT
|
|
// =================
|
|
|
|
$.fn.modal.noConflict = function () {
|
|
$.fn.modal = old
|
|
return this
|
|
}
|
|
|
|
|
|
// MODAL DATA-API
|
|
// ==============
|
|
|
|
$(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
|
|
var $this = $(this)
|
|
var href = $this.attr('href')
|
|
var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7
|
|
var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
|
|
|
|
if ($this.is('a')) e.preventDefault()
|
|
|
|
$target.one('show.bs.modal', function (showEvent) {
|
|
if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown
|
|
$target.one('hidden.bs.modal', function () {
|
|
$this.is(':visible') && $this.trigger('focus')
|
|
})
|
|
})
|
|
Plugin.call($target, option, this)
|
|
})
|
|
|
|
}(jQuery);
|
|
|
|
/* ========================================================================
|
|
* Bootstrap: tooltip.js v3.3.5
|
|
* http://getbootstrap.com/javascript/#tooltip
|
|
* Inspired by the original jQuery.tipsy by Jason Frame
|
|
* ========================================================================
|
|
* Copyright 2011-2015 Twitter, Inc.
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
* ======================================================================== */
|
|
|
|
|
|
+function ($) {
|
|
'use strict';
|
|
|
|
// TOOLTIP PUBLIC CLASS DEFINITION
|
|
// ===============================
|
|
|
|
var Tooltip = function (element, options) {
|
|
this.type = null
|
|
this.options = null
|
|
this.enabled = null
|
|
this.timeout = null
|
|
this.hoverState = null
|
|
this.$element = null
|
|
this.inState = null
|
|
|
|
this.init('tooltip', element, options)
|
|
}
|
|
|
|
Tooltip.VERSION = '3.3.5'
|
|
|
|
Tooltip.TRANSITION_DURATION = 150
|
|
|
|
Tooltip.DEFAULTS = {
|
|
animation: true,
|
|
placement: 'top',
|
|
selector: false,
|
|
template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',
|
|
trigger: 'hover focus',
|
|
title: '',
|
|
delay: 0,
|
|
html: false,
|
|
container: false,
|
|
viewport: {
|
|
selector: 'body',
|
|
padding: 0
|
|
}
|
|
}
|
|
|
|
Tooltip.prototype.init = function (type, element, options) {
|
|
this.enabled = true
|
|
this.type = type
|
|
this.$element = $(element)
|
|
this.options = this.getOptions(options)
|
|
this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport))
|
|
this.inState = { click: false, hover: false, focus: false }
|
|
|
|
if (this.$element[0] instanceof document.constructor && !this.options.selector) {
|
|
throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!')
|
|
}
|
|
|
|
var triggers = this.options.trigger.split(' ')
|
|
|
|
for (var i = triggers.length; i--;) {
|
|
var trigger = triggers[i]
|
|
|
|
if (trigger == 'click') {
|
|
this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
|
|
} else if (trigger != 'manual') {
|
|
var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin'
|
|
var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'
|
|
|
|
this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
|
|
this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
|
|
}
|
|
}
|
|
|
|
this.options.selector ?
|
|
(this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
|
|
this.fixTitle()
|
|
}
|
|
|
|
Tooltip.prototype.getDefaults = function () {
|
|
return Tooltip.DEFAULTS
|
|
}
|
|
|
|
Tooltip.prototype.getOptions = function (options) {
|
|
options = $.extend({}, this.getDefaults(), this.$element.data(), options)
|
|
|
|
if (options.delay && typeof options.delay == 'number') {
|
|
options.delay = {
|
|
show: options.delay,
|
|
hide: options.delay
|
|
}
|
|
}
|
|
|
|
return options
|
|
}
|
|
|
|
Tooltip.prototype.getDelegateOptions = function () {
|
|
var options = {}
|
|
var defaults = this.getDefaults()
|
|
|
|
this._options && $.each(this._options, function (key, value) {
|
|
if (defaults[key] != value) options[key] = value
|
|
})
|
|
|
|
return options
|
|
}
|
|
|
|
Tooltip.prototype.enter = function (obj) {
|
|
var self = obj instanceof this.constructor ?
|
|
obj : $(obj.currentTarget).data('bs.' + this.type)
|
|
|
|
if (!self) {
|
|
self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
|
|
$(obj.currentTarget).data('bs.' + this.type, self)
|
|
}
|
|
|
|
if (obj instanceof $.Event) {
|
|
self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true
|
|
}
|
|
|
|
if (self.tip().hasClass('in') || self.hoverState == 'in') {
|
|
self.hoverState = 'in'
|
|
return
|
|
}
|
|
|
|
clearTimeout(self.timeout)
|
|
|
|
self.hoverState = 'in'
|
|
|
|
if (!self.options.delay || !self.options.delay.show) return self.show()
|
|
|
|
self.timeout = setTimeout(function () {
|
|
if (self.hoverState == 'in') self.show()
|
|
}, self.options.delay.show)
|
|
}
|
|
|
|
Tooltip.prototype.isInStateTrue = function () {
|
|
for (var key in this.inState) {
|
|
if (this.inState[key]) return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
Tooltip.prototype.leave = function (obj) {
|
|
var self = obj instanceof this.constructor ?
|
|
obj : $(obj.currentTarget).data('bs.' + this.type)
|
|
|
|
if (!self) {
|
|
self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
|
|
$(obj.currentTarget).data('bs.' + this.type, self)
|
|
}
|
|
|
|
if (obj instanceof $.Event) {
|
|
self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false
|
|
}
|
|
|
|
if (self.isInStateTrue()) return
|
|
|
|
clearTimeout(self.timeout)
|
|
|
|
self.hoverState = 'out'
|
|
|
|
if (!self.options.delay || !self.options.delay.hide) return self.hide()
|
|
|
|
self.timeout = setTimeout(function () {
|
|
if (self.hoverState == 'out') self.hide()
|
|
}, self.options.delay.hide)
|
|
}
|
|
|
|
Tooltip.prototype.show = function () {
|
|
var e = $.Event('show.bs.' + this.type)
|
|
|
|
if (this.hasContent() && this.enabled) {
|
|
this.$element.trigger(e)
|
|
|
|
var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0])
|
|
if (e.isDefaultPrevented() || !inDom) return
|
|
var that = this
|
|
|
|
var $tip = this.tip()
|
|
|
|
var tipId = this.getUID(this.type)
|
|
|
|
this.setContent()
|
|
$tip.attr('id', tipId)
|
|
this.$element.attr('aria-describedby', tipId)
|
|
|
|
if (this.options.animation) $tip.addClass('fade')
|
|
|
|
var placement = typeof this.options.placement == 'function' ?
|
|
this.options.placement.call(this, $tip[0], this.$element[0]) :
|
|
this.options.placement
|
|
|
|
var autoToken = /\s?auto?\s?/i
|
|
var autoPlace = autoToken.test(placement)
|
|
if (autoPlace) placement = placement.replace(autoToken, '') || 'top'
|
|
|
|
$tip
|
|
.detach()
|
|
.css({ top: 0, left: 0, display: 'block' })
|
|
.addClass(placement)
|
|
.data('bs.' + this.type, this)
|
|
|
|
this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
|
|
this.$element.trigger('inserted.bs.' + this.type)
|
|
|
|
var pos = this.getPosition()
|
|
var actualWidth = $tip[0].offsetWidth
|
|
var actualHeight = $tip[0].offsetHeight
|
|
|
|
if (autoPlace) {
|
|
var orgPlacement = placement
|
|
var viewportDim = this.getPosition(this.$viewport)
|
|
|
|
placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' :
|
|
placement == 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' :
|
|
placement == 'right' && pos.right + actualWidth > viewportDim.width ? 'left' :
|
|
placement == 'left' && pos.left - actualWidth < viewportDim.left ? 'right' :
|
|
placement
|
|
|
|
$tip
|
|
.removeClass(orgPlacement)
|
|
.addClass(placement)
|
|
}
|
|
|
|
var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)
|
|
|
|
this.applyPlacement(calculatedOffset, placement)
|
|
|
|
var complete = function () {
|
|
var prevHoverState = that.hoverState
|
|
that.$element.trigger('shown.bs.' + that.type)
|
|
that.hoverState = null
|
|
|
|
if (prevHoverState == 'out') that.leave(that)
|
|
}
|
|
|
|
$.support.transition && this.$tip.hasClass('fade') ?
|
|
$tip
|
|
.one('bsTransitionEnd', complete)
|
|
.emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
|
|
complete()
|
|
}
|
|
}
|
|
|
|
Tooltip.prototype.applyPlacement = function (offset, placement) {
|
|
var $tip = this.tip()
|
|
var width = $tip[0].offsetWidth
|
|
var height = $tip[0].offsetHeight
|
|
|
|
// manually read margins because getBoundingClientRect includes difference
|
|
var marginTop = parseInt($tip.css('margin-top'), 10)
|
|
var marginLeft = parseInt($tip.css('margin-left'), 10)
|
|
|
|
// we must check for NaN for ie 8/9
|
|
if (isNaN(marginTop)) marginTop = 0
|
|
if (isNaN(marginLeft)) marginLeft = 0
|
|
|
|
offset.top += marginTop
|
|
offset.left += marginLeft
|
|
|
|
// $.fn.offset doesn't round pixel values
|
|
// so we use setOffset directly with our own function B-0
|
|
$.offset.setOffset($tip[0], $.extend({
|
|
using: function (props) {
|
|
$tip.css({
|
|
top: Math.round(props.top),
|
|
left: Math.round(props.left)
|
|
})
|
|
}
|
|
}, offset), 0)
|
|
|
|
$tip.addClass('in')
|
|
|
|
// check to see if placing tip in new offset caused the tip to resize itself
|
|
var actualWidth = $tip[0].offsetWidth
|
|
var actualHeight = $tip[0].offsetHeight
|
|
|
|
if (placement == 'top' && actualHeight != height) {
|
|
offset.top = offset.top + height - actualHeight
|
|
}
|
|
|
|
var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)
|
|
|
|
if (delta.left) offset.left += delta.left
|
|
else offset.top += delta.top
|
|
|
|
var isVertical = /top|bottom/.test(placement)
|
|
var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight
|
|
var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight'
|
|
|
|
$tip.offset(offset)
|
|
this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical)
|
|
}
|
|
|
|
Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) {
|
|
this.arrow()
|
|
.css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')
|
|
.css(isVertical ? 'top' : 'left', '')
|
|
}
|
|
|
|
Tooltip.prototype.setContent = function () {
|
|
var $tip = this.tip()
|
|
var title = this.getTitle()
|
|
|
|
$tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
|
|
$tip.removeClass('fade in top bottom left right')
|
|
}
|
|
|
|
Tooltip.prototype.hide = function (callback) {
|
|
var that = this
|
|
var $tip = $(this.$tip)
|
|
var e = $.Event('hide.bs.' + this.type)
|
|
|
|
function complete() {
|
|
if (that.hoverState != 'in') $tip.detach()
|
|
that.$element
|
|
.removeAttr('aria-describedby')
|
|
.trigger('hidden.bs.' + that.type)
|
|
callback && callback()
|
|
}
|
|
|
|
this.$element.trigger(e)
|
|
|
|
if (e.isDefaultPrevented()) return
|
|
|
|
$tip.removeClass('in')
|
|
|
|
$.support.transition && $tip.hasClass('fade') ?
|
|
$tip
|
|
.one('bsTransitionEnd', complete)
|
|
.emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
|
|
complete()
|
|
|
|
this.hoverState = null
|
|
|
|
return this
|
|
}
|
|
|
|
Tooltip.prototype.fixTitle = function () {
|
|
var $e = this.$element
|
|
if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') {
|
|
$e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
|
|
}
|
|
}
|
|
|
|
Tooltip.prototype.hasContent = function () {
|
|
return this.getTitle()
|
|
}
|
|
|
|
Tooltip.prototype.getPosition = function ($element) {
|
|
$element = $element || this.$element
|
|
|
|
var el = $element[0]
|
|
var isBody = el.tagName == 'BODY'
|
|
|
|
var elRect = el.getBoundingClientRect()
|
|
if (elRect.width == null) {
|
|
// width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093
|
|
elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })
|
|
}
|
|
var elOffset = isBody ? { top: 0, left: 0 } : $element.offset()
|
|
var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }
|
|
var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null
|
|
|
|
return $.extend({}, elRect, scroll, outerDims, elOffset)
|
|
}
|
|
|
|
Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
|
|
return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } :
|
|
placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :
|
|
placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
|
|
/* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }
|
|
|
|
}
|
|
|
|
Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {
|
|
var delta = { top: 0, left: 0 }
|
|
if (!this.$viewport) return delta
|
|
|
|
var viewportPadding = this.options.viewport && this.options.viewport.padding || 0
|
|
var viewportDimensions = this.getPosition(this.$viewport)
|
|
|
|
if (/right|left/.test(placement)) {
|
|
var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll
|
|
var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight
|
|
if (topEdgeOffset < viewportDimensions.top) { // top overflow
|
|
delta.top = viewportDimensions.top - topEdgeOffset
|
|
} else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow
|
|
delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset
|
|
}
|
|
} else {
|
|
var leftEdgeOffset = pos.left - viewportPadding
|
|
var rightEdgeOffset = pos.left + viewportPadding + actualWidth
|
|
if (leftEdgeOffset < viewportDimensions.left) { // left overflow
|
|
delta.left = viewportDimensions.left - leftEdgeOffset
|
|
} else if (rightEdgeOffset > viewportDimensions.right) { // right overflow
|
|
delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset
|
|
}
|
|
}
|
|
|
|
return delta
|
|
}
|
|
|
|
Tooltip.prototype.getTitle = function () {
|
|
var title
|
|
var $e = this.$element
|
|
var o = this.options
|
|
|
|
title = $e.attr('data-original-title')
|
|
|| (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
|
|
|
|
return title
|
|
}
|
|
|
|
Tooltip.prototype.getUID = function (prefix) {
|
|
do prefix += ~~(Math.random() * 1000000)
|
|
while (document.getElementById(prefix))
|
|
return prefix
|
|
}
|
|
|
|
Tooltip.prototype.tip = function () {
|
|
if (!this.$tip) {
|
|
this.$tip = $(this.options.template)
|
|
if (this.$tip.length != 1) {
|
|
throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!')
|
|
}
|
|
}
|
|
return this.$tip
|
|
}
|
|
|
|
Tooltip.prototype.arrow = function () {
|
|
return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'))
|
|
}
|
|
|
|
Tooltip.prototype.enable = function () {
|
|
this.enabled = true
|
|
}
|
|
|
|
Tooltip.prototype.disable = function () {
|
|
this.enabled = false
|
|
}
|
|
|
|
Tooltip.prototype.toggleEnabled = function () {
|
|
this.enabled = !this.enabled
|
|
}
|
|
|
|
Tooltip.prototype.toggle = function (e) {
|
|
var self = this
|
|
if (e) {
|
|
self = $(e.currentTarget).data('bs.' + this.type)
|
|
if (!self) {
|
|
self = new this.constructor(e.currentTarget, this.getDelegateOptions())
|
|
$(e.currentTarget).data('bs.' + this.type, self)
|
|
}
|
|
}
|
|
|
|
if (e) {
|
|
self.inState.click = !self.inState.click
|
|
if (self.isInStateTrue()) self.enter(self)
|
|
else self.leave(self)
|
|
} else {
|
|
self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
|
|
}
|
|
}
|
|
|
|
Tooltip.prototype.destroy = function () {
|
|
var that = this
|
|
clearTimeout(this.timeout)
|
|
this.hide(function () {
|
|
that.$element.off('.' + that.type).removeData('bs.' + that.type)
|
|
if (that.$tip) {
|
|
that.$tip.detach()
|
|
}
|
|
that.$tip = null
|
|
that.$arrow = null
|
|
that.$viewport = null
|
|
})
|
|
}
|
|
|
|
|
|
// TOOLTIP PLUGIN DEFINITION
|
|
// =========================
|
|
|
|
function Plugin(option) {
|
|
return this.each(function () {
|
|
var $this = $(this)
|
|
var data = $this.data('bs.tooltip')
|
|
var options = typeof option == 'object' && option
|
|
|
|
if (!data && /destroy|hide/.test(option)) return
|
|
if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
|
|
if (typeof option == 'string') data[option]()
|
|
})
|
|
}
|
|
|
|
var old = $.fn.tooltip
|
|
|
|
$.fn.tooltip = Plugin
|
|
$.fn.tooltip.Constructor = Tooltip
|
|
|
|
|
|
// TOOLTIP NO CONFLICT
|
|
// ===================
|
|
|
|
$.fn.tooltip.noConflict = function () {
|
|
$.fn.tooltip = old
|
|
return this
|
|
}
|
|
|
|
}(jQuery);
|
|
|
|
/* ========================================================================
|
|
* Bootstrap: popover.js v3.3.5
|
|
* http://getbootstrap.com/javascript/#popovers
|
|
* ========================================================================
|
|
* Copyright 2011-2015 Twitter, Inc.
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
* ======================================================================== */
|
|
|
|
|
|
+function ($) {
|
|
'use strict';
|
|
|
|
// POPOVER PUBLIC CLASS DEFINITION
|
|
// ===============================
|
|
|
|
var Popover = function (element, options) {
|
|
this.init('popover', element, options)
|
|
}
|
|
|
|
if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')
|
|
|
|
Popover.VERSION = '3.3.5'
|
|
|
|
Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {
|
|
placement: 'right',
|
|
trigger: 'click',
|
|
content: '',
|
|
template: '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
|
|
})
|
|
|
|
|
|
// NOTE: POPOVER EXTENDS tooltip.js
|
|
// ================================
|
|
|
|
Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)
|
|
|
|
Popover.prototype.constructor = Popover
|
|
|
|
Popover.prototype.getDefaults = function () {
|
|
return Popover.DEFAULTS
|
|
}
|
|
|
|
Popover.prototype.setContent = function () {
|
|
var $tip = this.tip()
|
|
var title = this.getTitle()
|
|
var content = this.getContent()
|
|
|
|
$tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
|
|
$tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events
|
|
this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text'
|
|
](content)
|
|
|
|
$tip.removeClass('fade top bottom left right in')
|
|
|
|
// IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
|
|
// this manually by checking the contents.
|
|
if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()
|
|
}
|
|
|
|
Popover.prototype.hasContent = function () {
|
|
return this.getTitle() || this.getContent()
|
|
}
|
|
|
|
Popover.prototype.getContent = function () {
|
|
var $e = this.$element
|
|
var o = this.options
|
|
|
|
return $e.attr('data-content')
|
|
|| (typeof o.content == 'function' ?
|
|
o.content.call($e[0]) :
|
|
o.content)
|
|
}
|
|
|
|
Popover.prototype.arrow = function () {
|
|
return (this.$arrow = this.$arrow || this.tip().find('.arrow'))
|
|
}
|
|
|
|
|
|
// POPOVER PLUGIN DEFINITION
|
|
// =========================
|
|
|
|
function Plugin(option) {
|
|
return this.each(function () {
|
|
var $this = $(this)
|
|
var data = $this.data('bs.popover')
|
|
var options = typeof option == 'object' && option
|
|
|
|
if (!data && /destroy|hide/.test(option)) return
|
|
if (!data) $this.data('bs.popover', (data = new Popover(this, options)))
|
|
if (typeof option == 'string') data[option]()
|
|
})
|
|
}
|
|
|
|
var old = $.fn.popover
|
|
|
|
$.fn.popover = Plugin
|
|
$.fn.popover.Constructor = Popover
|
|
|
|
|
|
// POPOVER NO CONFLICT
|
|
// ===================
|
|
|
|
$.fn.popover.noConflict = function () {
|
|
$.fn.popover = old
|
|
return this
|
|
}
|
|
|
|
}(jQuery);
|
|
|
|
/* ========================================================================
|
|
* Bootstrap: scrollspy.js v3.3.5
|
|
* http://getbootstrap.com/javascript/#scrollspy
|
|
* ========================================================================
|
|
* Copyright 2011-2015 Twitter, Inc.
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
* ======================================================================== */
|
|
|
|
|
|
+function ($) {
|
|
'use strict';
|
|
|
|
// SCROLLSPY CLASS DEFINITION
|
|
// ==========================
|
|
|
|
function ScrollSpy(element, options) {
|
|
this.$body = $(document.body)
|
|
this.$scrollElement = $(element).is(document.body) ? $(window) : $(element)
|
|
this.options = $.extend({}, ScrollSpy.DEFAULTS, options)
|
|
this.selector = (this.options.target || '') + ' .nav li > a'
|
|
this.offsets = []
|
|
this.targets = []
|
|
this.activeTarget = null
|
|
this.scrollHeight = 0
|
|
|
|
this.$scrollElement.on('scroll.bs.scrollspy', $.proxy(this.process, this))
|
|
this.refresh()
|
|
this.process()
|
|
}
|
|
|
|
ScrollSpy.VERSION = '3.3.5'
|
|
|
|
ScrollSpy.DEFAULTS = {
|
|
offset: 10
|
|
}
|
|
|
|
ScrollSpy.prototype.getScrollHeight = function () {
|
|
return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight)
|
|
}
|
|
|
|
ScrollSpy.prototype.refresh = function () {
|
|
var that = this
|
|
var offsetMethod = 'offset'
|
|
var offsetBase = 0
|
|
|
|
this.offsets = []
|
|
this.targets = []
|
|
this.scrollHeight = this.getScrollHeight()
|
|
|
|
if (!$.isWindow(this.$scrollElement[0])) {
|
|
offsetMethod = 'position'
|
|
offsetBase = this.$scrollElement.scrollTop()
|
|
}
|
|
|
|
this.$body
|
|
.find(this.selector)
|
|
.map(function () {
|
|
var $el = $(this)
|
|
var href = $el.data('target') || $el.attr('href')
|
|
var $href = /^#./.test(href) && $(href)
|
|
|
|
return ($href
|
|
&& $href.length
|
|
&& $href.is(':visible')
|
|
&& [[$href[offsetMethod]().top + offsetBase, href]]) || null
|
|
})
|
|
.sort(function (a, b) { return a[0] - b[0] })
|
|
.each(function () {
|
|
that.offsets.push(this[0])
|
|
that.targets.push(this[1])
|
|
})
|
|
}
|
|
|
|
ScrollSpy.prototype.process = function () {
|
|
var scrollTop = this.$scrollElement.scrollTop() + this.options.offset
|
|
var scrollHeight = this.getScrollHeight()
|
|
var maxScroll = this.options.offset + scrollHeight - this.$scrollElement.height()
|
|
var offsets = this.offsets
|
|
var targets = this.targets
|
|
var activeTarget = this.activeTarget
|
|
var i
|
|
|
|
if (this.scrollHeight != scrollHeight) {
|
|
this.refresh()
|
|
}
|
|
|
|
if (scrollTop >= maxScroll) {
|
|
return activeTarget != (i = targets[targets.length - 1]) && this.activate(i)
|
|
}
|
|
|
|
if (activeTarget && scrollTop < offsets[0]) {
|
|
this.activeTarget = null
|
|
return this.clear()
|
|
}
|
|
|
|
for (i = offsets.length; i--;) {
|
|
activeTarget != targets[i]
|
|
&& scrollTop >= offsets[i]
|
|
&& (offsets[i + 1] === undefined || scrollTop < offsets[i + 1])
|
|
&& this.activate(targets[i])
|
|
}
|
|
}
|
|
|
|
ScrollSpy.prototype.activate = function (target) {
|
|
this.activeTarget = target
|
|
|
|
this.clear()
|
|
|
|
var selector = this.selector +
|
|
'[data-target="' + target + '"],' +
|
|
this.selector + '[href="' + target + '"]'
|
|
|
|
var active = $(selector)
|
|
.parents('li')
|
|
.addClass('active')
|
|
|
|
if (active.parent('.dropdown-menu').length) {
|
|
active = active
|
|
.closest('li.dropdown')
|
|
.addClass('active')
|
|
}
|
|
|
|
active.trigger('activate.bs.scrollspy')
|
|
}
|
|
|
|
ScrollSpy.prototype.clear = function () {
|
|
$(this.selector)
|
|
.parentsUntil(this.options.target, '.active')
|
|
.removeClass('active')
|
|
}
|
|
|
|
|
|
// SCROLLSPY PLUGIN DEFINITION
|
|
// ===========================
|
|
|
|
function Plugin(option) {
|
|
return this.each(function () {
|
|
var $this = $(this)
|
|
var data = $this.data('bs.scrollspy')
|
|
var options = typeof option == 'object' && option
|
|
|
|
if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))
|
|
if (typeof option == 'string') data[option]()
|
|
})
|
|
}
|
|
|
|
var old = $.fn.scrollspy
|
|
|
|
$.fn.scrollspy = Plugin
|
|
$.fn.scrollspy.Constructor = ScrollSpy
|
|
|
|
|
|
// SCROLLSPY NO CONFLICT
|
|
// =====================
|
|
|
|
$.fn.scrollspy.noConflict = function () {
|
|
$.fn.scrollspy = old
|
|
return this
|
|
}
|
|
|
|
|
|
// SCROLLSPY DATA-API
|
|
// ==================
|
|
|
|
$(window).on('load.bs.scrollspy.data-api', function () {
|
|
$('[data-spy="scroll"]').each(function () {
|
|
var $spy = $(this)
|
|
Plugin.call($spy, $spy.data())
|
|
})
|
|
})
|
|
|
|
}(jQuery);
|
|
|
|
/* ========================================================================
|
|
* Bootstrap: tab.js v3.3.5
|
|
* http://getbootstrap.com/javascript/#tabs
|
|
* ========================================================================
|
|
* Copyright 2011-2015 Twitter, Inc.
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
* ======================================================================== */
|
|
|
|
|
|
+function ($) {
|
|
'use strict';
|
|
|
|
// TAB CLASS DEFINITION
|
|
// ====================
|
|
|
|
var Tab = function (element) {
|
|
// jscs:disable requireDollarBeforejQueryAssignment
|
|
this.element = $(element)
|
|
// jscs:enable requireDollarBeforejQueryAssignment
|
|
}
|
|
|
|
Tab.VERSION = '3.3.5'
|
|
|
|
Tab.TRANSITION_DURATION = 150
|
|
|
|
Tab.prototype.show = function () {
|
|
var $this = this.element
|
|
var $ul = $this.closest('ul:not(.dropdown-menu)')
|
|
var selector = $this.data('target')
|
|
|
|
if (!selector) {
|
|
selector = $this.attr('href')
|
|
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
|
|
}
|
|
|
|
if ($this.parent('li').hasClass('active')) return
|
|
|
|
var $previous = $ul.find('.active:last a')
|
|
var hideEvent = $.Event('hide.bs.tab', {
|
|
relatedTarget: $this[0]
|
|
})
|
|
var showEvent = $.Event('show.bs.tab', {
|
|
relatedTarget: $previous[0]
|
|
})
|
|
|
|
$previous.trigger(hideEvent)
|
|
$this.trigger(showEvent)
|
|
|
|
if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return
|
|
|
|
var $target = $(selector)
|
|
|
|
this.activate($this.closest('li'), $ul)
|
|
this.activate($target, $target.parent(), function () {
|
|
$previous.trigger({
|
|
type: 'hidden.bs.tab',
|
|
relatedTarget: $this[0]
|
|
})
|
|
$this.trigger({
|
|
type: 'shown.bs.tab',
|
|
relatedTarget: $previous[0]
|
|
})
|
|
})
|
|
}
|
|
|
|
Tab.prototype.activate = function (element, container, callback) {
|
|
var $active = container.find('> .active')
|
|
var transition = callback
|
|
&& $.support.transition
|
|
&& ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length)
|
|
|
|
function next() {
|
|
$active
|
|
.removeClass('active')
|
|
.find('> .dropdown-menu > .active')
|
|
.removeClass('active')
|
|
.end()
|
|
.find('[data-toggle="tab"]')
|
|
.attr('aria-expanded', false)
|
|
|
|
element
|
|
.addClass('active')
|
|
.find('[data-toggle="tab"]')
|
|
.attr('aria-expanded', true)
|
|
|
|
if (transition) {
|
|
element[0].offsetWidth // reflow for transition
|
|
element.addClass('in')
|
|
} else {
|
|
element.removeClass('fade')
|
|
}
|
|
|
|
if (element.parent('.dropdown-menu').length) {
|
|
element
|
|
.closest('li.dropdown')
|
|
.addClass('active')
|
|
.end()
|
|
.find('[data-toggle="tab"]')
|
|
.attr('aria-expanded', true)
|
|
}
|
|
|
|
callback && callback()
|
|
}
|
|
|
|
$active.length && transition ?
|
|
$active
|
|
.one('bsTransitionEnd', next)
|
|
.emulateTransitionEnd(Tab.TRANSITION_DURATION) :
|
|
next()
|
|
|
|
$active.removeClass('in')
|
|
}
|
|
|
|
|
|
// TAB PLUGIN DEFINITION
|
|
// =====================
|
|
|
|
function Plugin(option) {
|
|
return this.each(function () {
|
|
var $this = $(this)
|
|
var data = $this.data('bs.tab')
|
|
|
|
if (!data) $this.data('bs.tab', (data = new Tab(this)))
|
|
if (typeof option == 'string') data[option]()
|
|
})
|
|
}
|
|
|
|
var old = $.fn.tab
|
|
|
|
$.fn.tab = Plugin
|
|
$.fn.tab.Constructor = Tab
|
|
|
|
|
|
// TAB NO CONFLICT
|
|
// ===============
|
|
|
|
$.fn.tab.noConflict = function () {
|
|
$.fn.tab = old
|
|
return this
|
|
}
|
|
|
|
|
|
// TAB DATA-API
|
|
// ============
|
|
|
|
var clickHandler = function (e) {
|
|
e.preventDefault()
|
|
Plugin.call($(this), 'show')
|
|
}
|
|
|
|
$(document)
|
|
.on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler)
|
|
.on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler)
|
|
|
|
}(jQuery);
|
|
|
|
/* ========================================================================
|
|
* Bootstrap: affix.js v3.3.5
|
|
* http://getbootstrap.com/javascript/#affix
|
|
* ========================================================================
|
|
* Copyright 2011-2015 Twitter, Inc.
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
* ======================================================================== */
|
|
|
|
|
|
+function ($) {
|
|
'use strict';
|
|
|
|
// AFFIX CLASS DEFINITION
|
|
// ======================
|
|
|
|
var Affix = function (element, options) {
|
|
this.options = $.extend({}, Affix.DEFAULTS, options)
|
|
|
|
this.$target = $(this.options.target)
|
|
.on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))
|
|
.on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this))
|
|
|
|
this.$element = $(element)
|
|
this.affixed = null
|
|
this.unpin = null
|
|
this.pinnedOffset = null
|
|
|
|
this.checkPosition()
|
|
}
|
|
|
|
Affix.VERSION = '3.3.5'
|
|
|
|
Affix.RESET = 'affix affix-top affix-bottom'
|
|
|
|
Affix.DEFAULTS = {
|
|
offset: 0,
|
|
target: window
|
|
}
|
|
|
|
Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) {
|
|
var scrollTop = this.$target.scrollTop()
|
|
var position = this.$element.offset()
|
|
var targetHeight = this.$target.height()
|
|
|
|
if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false
|
|
|
|
if (this.affixed == 'bottom') {
|
|
if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom'
|
|
return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom'
|
|
}
|
|
|
|
var initializing = this.affixed == null
|
|
var colliderTop = initializing ? scrollTop : position.top
|
|
var colliderHeight = initializing ? targetHeight : height
|
|
|
|
if (offsetTop != null && scrollTop <= offsetTop) return 'top'
|
|
if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom'
|
|
|
|
return false
|
|
}
|
|
|
|
Affix.prototype.getPinnedOffset = function () {
|
|
if (this.pinnedOffset) return this.pinnedOffset
|
|
this.$element.removeClass(Affix.RESET).addClass('affix')
|
|
var scrollTop = this.$target.scrollTop()
|
|
var position = this.$element.offset()
|
|
return (this.pinnedOffset = position.top - scrollTop)
|
|
}
|
|
|
|
Affix.prototype.checkPositionWithEventLoop = function () {
|
|
setTimeout($.proxy(this.checkPosition, this), 1)
|
|
}
|
|
|
|
Affix.prototype.checkPosition = function () {
|
|
if (!this.$element.is(':visible')) return
|
|
|
|
var height = this.$element.height()
|
|
var offset = this.options.offset
|
|
var offsetTop = offset.top
|
|
var offsetBottom = offset.bottom
|
|
var scrollHeight = Math.max($(document).height(), $(document.body).height())
|
|
|
|
if (typeof offset != 'object') offsetBottom = offsetTop = offset
|
|
if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element)
|
|
if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element)
|
|
|
|
var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom)
|
|
|
|
if (this.affixed != affix) {
|
|
if (this.unpin != null) this.$element.css('top', '')
|
|
|
|
var affixType = 'affix' + (affix ? '-' + affix : '')
|
|
var e = $.Event(affixType + '.bs.affix')
|
|
|
|
this.$element.trigger(e)
|
|
|
|
if (e.isDefaultPrevented()) return
|
|
|
|
this.affixed = affix
|
|
this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null
|
|
|
|
this.$element
|
|
.removeClass(Affix.RESET)
|
|
.addClass(affixType)
|
|
.trigger(affixType.replace('affix', 'affixed') + '.bs.affix')
|
|
}
|
|
|
|
if (affix == 'bottom') {
|
|
this.$element.offset({
|
|
top: scrollHeight - height - offsetBottom
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
// AFFIX PLUGIN DEFINITION
|
|
// =======================
|
|
|
|
function Plugin(option) {
|
|
return this.each(function () {
|
|
var $this = $(this)
|
|
var data = $this.data('bs.affix')
|
|
var options = typeof option == 'object' && option
|
|
|
|
if (!data) $this.data('bs.affix', (data = new Affix(this, options)))
|
|
if (typeof option == 'string') data[option]()
|
|
})
|
|
}
|
|
|
|
var old = $.fn.affix
|
|
|
|
$.fn.affix = Plugin
|
|
$.fn.affix.Constructor = Affix
|
|
|
|
|
|
// 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)
|
|
var data = $spy.data()
|
|
|
|
data.offset = data.offset || {}
|
|
|
|
if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom
|
|
if (data.offsetTop != null) data.offset.top = data.offsetTop
|
|
|
|
Plugin.call($spy, data)
|
|
})
|
|
})
|
|
|
|
}(jQuery);
|
|
|
|
/* ========================================================================
|
|
* Bootstrap: collapse.js v3.3.5
|
|
* http://getbootstrap.com/javascript/#collapse
|
|
* ========================================================================
|
|
* Copyright 2011-2015 Twitter, Inc.
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
* ======================================================================== */
|
|
|
|
|
|
+function ($) {
|
|
'use strict';
|
|
|
|
// COLLAPSE PUBLIC CLASS DEFINITION
|
|
// ================================
|
|
|
|
var Collapse = function (element, options) {
|
|
this.$element = $(element)
|
|
this.options = $.extend({}, Collapse.DEFAULTS, options)
|
|
this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' +
|
|
'[data-toggle="collapse"][data-target="#' + element.id + '"]')
|
|
this.transitioning = null
|
|
|
|
if (this.options.parent) {
|
|
this.$parent = this.getParent()
|
|
} else {
|
|
this.addAriaAndCollapsedClass(this.$element, this.$trigger)
|
|
}
|
|
|
|
if (this.options.toggle) this.toggle()
|
|
}
|
|
|
|
Collapse.VERSION = '3.3.5'
|
|
|
|
Collapse.TRANSITION_DURATION = 350
|
|
|
|
Collapse.DEFAULTS = {
|
|
toggle: true
|
|
}
|
|
|
|
Collapse.prototype.dimension = function () {
|
|
var hasWidth = this.$element.hasClass('width')
|
|
return hasWidth ? 'width' : 'height'
|
|
}
|
|
|
|
Collapse.prototype.show = function () {
|
|
if (this.transitioning || this.$element.hasClass('in')) return
|
|
|
|
var activesData
|
|
var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing')
|
|
|
|
if (actives && actives.length) {
|
|
activesData = actives.data('bs.collapse')
|
|
if (activesData && activesData.transitioning) return
|
|
}
|
|
|
|
var startEvent = $.Event('show.bs.collapse')
|
|
this.$element.trigger(startEvent)
|
|
if (startEvent.isDefaultPrevented()) return
|
|
|
|
if (actives && actives.length) {
|
|
Plugin.call(actives, 'hide')
|
|
activesData || actives.data('bs.collapse', null)
|
|
}
|
|
|
|
var dimension = this.dimension()
|
|
|
|
this.$element
|
|
.removeClass('collapse')
|
|
.addClass('collapsing')[dimension](0)
|
|
.attr('aria-expanded', true)
|
|
|
|
this.$trigger
|
|
.removeClass('collapsed')
|
|
.attr('aria-expanded', true)
|
|
|
|
this.transitioning = 1
|
|
|
|
var complete = function () {
|
|
this.$element
|
|
.removeClass('collapsing')
|
|
.addClass('collapse in')[dimension]('')
|
|
this.transitioning = 0
|
|
this.$element
|
|
.trigger('shown.bs.collapse')
|
|
}
|
|
|
|
if (!$.support.transition) return complete.call(this)
|
|
|
|
var scrollSize = $.camelCase(['scroll', dimension].join('-'))
|
|
|
|
this.$element
|
|
.one('bsTransitionEnd', $.proxy(complete, this))
|
|
.emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize])
|
|
}
|
|
|
|
Collapse.prototype.hide = function () {
|
|
if (this.transitioning || !this.$element.hasClass('in')) return
|
|
|
|
var startEvent = $.Event('hide.bs.collapse')
|
|
this.$element.trigger(startEvent)
|
|
if (startEvent.isDefaultPrevented()) return
|
|
|
|
var dimension = this.dimension()
|
|
|
|
this.$element[dimension](this.$element[dimension]())[0].offsetHeight
|
|
|
|
this.$element
|
|
.addClass('collapsing')
|
|
.removeClass('collapse in')
|
|
.attr('aria-expanded', false)
|
|
|
|
this.$trigger
|
|
.addClass('collapsed')
|
|
.attr('aria-expanded', false)
|
|
|
|
this.transitioning = 1
|
|
|
|
var complete = function () {
|
|
this.transitioning = 0
|
|
this.$element
|
|
.removeClass('collapsing')
|
|
.addClass('collapse')
|
|
.trigger('hidden.bs.collapse')
|
|
}
|
|
|
|
if (!$.support.transition) return complete.call(this)
|
|
|
|
this.$element
|
|
[dimension](0)
|
|
.one('bsTransitionEnd', $.proxy(complete, this))
|
|
.emulateTransitionEnd(Collapse.TRANSITION_DURATION)
|
|
}
|
|
|
|
Collapse.prototype.toggle = function () {
|
|
this[this.$element.hasClass('in') ? 'hide' : 'show']()
|
|
}
|
|
|
|
Collapse.prototype.getParent = function () {
|
|
return $(this.options.parent)
|
|
.find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]')
|
|
.each($.proxy(function (i, element) {
|
|
var $element = $(element)
|
|
this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element)
|
|
}, this))
|
|
.end()
|
|
}
|
|
|
|
Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) {
|
|
var isOpen = $element.hasClass('in')
|
|
|
|
$element.attr('aria-expanded', isOpen)
|
|
$trigger
|
|
.toggleClass('collapsed', !isOpen)
|
|
.attr('aria-expanded', isOpen)
|
|
}
|
|
|
|
function getTargetFromTrigger($trigger) {
|
|
var href
|
|
var target = $trigger.attr('data-target')
|
|
|| (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7
|
|
|
|
return $(target)
|
|
}
|
|
|
|
|
|
// COLLAPSE PLUGIN DEFINITION
|
|
// ==========================
|
|
|
|
function Plugin(option) {
|
|
return this.each(function () {
|
|
var $this = $(this)
|
|
var data = $this.data('bs.collapse')
|
|
var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)
|
|
|
|
if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false
|
|
if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))
|
|
if (typeof option == 'string') data[option]()
|
|
})
|
|
}
|
|
|
|
var old = $.fn.collapse
|
|
|
|
$.fn.collapse = Plugin
|
|
$.fn.collapse.Constructor = Collapse
|
|
|
|
|
|
// COLLAPSE NO CONFLICT
|
|
// ====================
|
|
|
|
$.fn.collapse.noConflict = function () {
|
|
$.fn.collapse = old
|
|
return this
|
|
}
|
|
|
|
|
|
// COLLAPSE DATA-API
|
|
// =================
|
|
|
|
$(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) {
|
|
var $this = $(this)
|
|
|
|
if (!$this.attr('data-target')) e.preventDefault()
|
|
|
|
var $target = getTargetFromTrigger($this)
|
|
var data = $target.data('bs.collapse')
|
|
var option = data ? 'toggle' : $this.data()
|
|
|
|
Plugin.call($target, option)
|
|
})
|
|
|
|
}(jQuery);
|
|
|
|
/* ========================================================================
|
|
* Bootstrap: transition.js v3.3.5
|
|
* http://getbootstrap.com/javascript/#transitions
|
|
* ========================================================================
|
|
* Copyright 2011-2015 Twitter, Inc.
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
* ======================================================================== */
|
|
|
|
|
|
+function ($) {
|
|
'use strict';
|
|
|
|
// CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
|
|
// ============================================================
|
|
|
|
function transitionEnd() {
|
|
var el = document.createElement('bootstrap')
|
|
|
|
var transEndEventNames = {
|
|
WebkitTransition : 'webkitTransitionEnd',
|
|
MozTransition : 'transitionend',
|
|
OTransition : 'oTransitionEnd otransitionend',
|
|
transition : 'transitionend'
|
|
}
|
|
|
|
for (var name in transEndEventNames) {
|
|
if (el.style[name] !== undefined) {
|
|
return { end: transEndEventNames[name] }
|
|
}
|
|
}
|
|
|
|
return false // explicit for ie8 ( ._.)
|
|
}
|
|
|
|
// http://blog.alexmaccaw.com/css-transitions
|
|
$.fn.emulateTransitionEnd = function (duration) {
|
|
var called = false
|
|
var $el = this
|
|
$(this).one('bsTransitionEnd', function () { called = true })
|
|
var callback = function () { if (!called) $($el).trigger($.support.transition.end) }
|
|
setTimeout(callback, duration)
|
|
return this
|
|
}
|
|
|
|
$(function () {
|
|
$.support.transition = transitionEnd()
|
|
|
|
if (!$.support.transition) return
|
|
|
|
$.event.special.bsTransitionEnd = {
|
|
bindType: $.support.transition.end,
|
|
delegateType: $.support.transition.end,
|
|
handle: function (e) {
|
|
if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments)
|
|
}
|
|
}
|
|
})
|
|
|
|
}(jQuery);
|
|
|
|
/**!
|
|
* @fileOverview Kickass library to create and place poppers near their reference elements.
|
|
* @version 1.14.1
|
|
* @license
|
|
* Copyright (c) 2016 Federico Zivolo and contributors
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*/
|
|
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
typeof define === 'function' && define.amd ? define(factory) :
|
|
(global.Popper = factory());
|
|
}(this, (function () { 'use strict';
|
|
|
|
var isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
var longerTimeoutBrowsers = ['Edge', 'Trident', 'Firefox'];
|
|
var timeoutDuration = 0;
|
|
for (var i = 0; i < longerTimeoutBrowsers.length; i += 1) {
|
|
if (isBrowser && navigator.userAgent.indexOf(longerTimeoutBrowsers[i]) >= 0) {
|
|
timeoutDuration = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
function microtaskDebounce(fn) {
|
|
var called = false;
|
|
return function () {
|
|
if (called) {
|
|
return;
|
|
}
|
|
called = true;
|
|
window.Promise.resolve().then(function () {
|
|
called = false;
|
|
fn();
|
|
});
|
|
};
|
|
}
|
|
|
|
function taskDebounce(fn) {
|
|
var scheduled = false;
|
|
return function () {
|
|
if (!scheduled) {
|
|
scheduled = true;
|
|
setTimeout(function () {
|
|
scheduled = false;
|
|
fn();
|
|
}, timeoutDuration);
|
|
}
|
|
};
|
|
}
|
|
|
|
var supportsMicroTasks = isBrowser && window.Promise;
|
|
|
|
/**
|
|
* Create a debounced version of a method, that's asynchronously deferred
|
|
* but called in the minimum time possible.
|
|
*
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Function} fn
|
|
* @returns {Function}
|
|
*/
|
|
var debounce = supportsMicroTasks ? microtaskDebounce : taskDebounce;
|
|
|
|
/**
|
|
* Check if the given variable is a function
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Any} functionToCheck - variable to check
|
|
* @returns {Boolean} answer to: is a function?
|
|
*/
|
|
function isFunction(functionToCheck) {
|
|
var getType = {};
|
|
return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
|
|
}
|
|
|
|
/**
|
|
* Get CSS computed property of the given element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Eement} element
|
|
* @argument {String} property
|
|
*/
|
|
function getStyleComputedProperty(element, property) {
|
|
if (element.nodeType !== 1) {
|
|
return [];
|
|
}
|
|
// NOTE: 1 DOM access here
|
|
var css = getComputedStyle(element, null);
|
|
return property ? css[property] : css;
|
|
}
|
|
|
|
/**
|
|
* Returns the parentNode or the host of the element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @returns {Element} parent
|
|
*/
|
|
function getParentNode(element) {
|
|
if (element.nodeName === 'HTML') {
|
|
return element;
|
|
}
|
|
return element.parentNode || element.host;
|
|
}
|
|
|
|
/**
|
|
* Returns the scrolling parent of the given element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @returns {Element} scroll parent
|
|
*/
|
|
function getScrollParent(element) {
|
|
// Return body, `getScroll` will take care to get the correct `scrollTop` from it
|
|
if (!element) {
|
|
return document.body;
|
|
}
|
|
|
|
switch (element.nodeName) {
|
|
case 'HTML':
|
|
case 'BODY':
|
|
return element.ownerDocument.body;
|
|
case '#document':
|
|
return element.body;
|
|
}
|
|
|
|
// Firefox want us to check `-x` and `-y` variations as well
|
|
|
|
var _getStyleComputedProp = getStyleComputedProperty(element),
|
|
overflow = _getStyleComputedProp.overflow,
|
|
overflowX = _getStyleComputedProp.overflowX,
|
|
overflowY = _getStyleComputedProp.overflowY;
|
|
|
|
if (/(auto|scroll|overlay)/.test(overflow + overflowY + overflowX)) {
|
|
return element;
|
|
}
|
|
|
|
return getScrollParent(getParentNode(element));
|
|
}
|
|
|
|
/**
|
|
* Tells if you are running Internet Explorer
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {number} version to check
|
|
* @returns {Boolean} isIE
|
|
*/
|
|
var cache = {};
|
|
|
|
var isIE = function () {
|
|
var version = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'all';
|
|
|
|
version = version.toString();
|
|
if (cache.hasOwnProperty(version)) {
|
|
return cache[version];
|
|
}
|
|
switch (version) {
|
|
case '11':
|
|
cache[version] = navigator.userAgent.indexOf('Trident') !== -1;
|
|
break;
|
|
case '10':
|
|
cache[version] = navigator.appVersion.indexOf('MSIE 10') !== -1;
|
|
break;
|
|
case 'all':
|
|
cache[version] = navigator.userAgent.indexOf('Trident') !== -1 || navigator.userAgent.indexOf('MSIE') !== -1;
|
|
break;
|
|
}
|
|
|
|
//Set IE
|
|
cache.all = cache.all || Object.keys(cache).some(function (key) {
|
|
return cache[key];
|
|
});
|
|
return cache[version];
|
|
};
|
|
|
|
/**
|
|
* Returns the offset parent of the given element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @returns {Element} offset parent
|
|
*/
|
|
function getOffsetParent(element) {
|
|
if (!element) {
|
|
return document.documentElement;
|
|
}
|
|
|
|
var noOffsetParent = isIE(10) ? document.body : null;
|
|
|
|
// NOTE: 1 DOM access here
|
|
var offsetParent = element.offsetParent;
|
|
// Skip hidden elements which don't have an offsetParent
|
|
while (offsetParent === noOffsetParent && element.nextElementSibling) {
|
|
offsetParent = (element = element.nextElementSibling).offsetParent;
|
|
}
|
|
|
|
var nodeName = offsetParent && offsetParent.nodeName;
|
|
|
|
if (!nodeName || nodeName === 'BODY' || nodeName === 'HTML') {
|
|
return element ? element.ownerDocument.documentElement : document.documentElement;
|
|
}
|
|
|
|
// .offsetParent will return the closest TD or TABLE in case
|
|
// no offsetParent is present, I hate this job...
|
|
if (['TD', 'TABLE'].indexOf(offsetParent.nodeName) !== -1 && getStyleComputedProperty(offsetParent, 'position') === 'static') {
|
|
return getOffsetParent(offsetParent);
|
|
}
|
|
|
|
return offsetParent;
|
|
}
|
|
|
|
function isOffsetContainer(element) {
|
|
var nodeName = element.nodeName;
|
|
|
|
if (nodeName === 'BODY') {
|
|
return false;
|
|
}
|
|
return nodeName === 'HTML' || getOffsetParent(element.firstElementChild) === element;
|
|
}
|
|
|
|
/**
|
|
* Finds the root node (document, shadowDOM root) of the given element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} node
|
|
* @returns {Element} root node
|
|
*/
|
|
function getRoot(node) {
|
|
if (node.parentNode !== null) {
|
|
return getRoot(node.parentNode);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* Finds the offset parent common to the two provided nodes
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element1
|
|
* @argument {Element} element2
|
|
* @returns {Element} common offset parent
|
|
*/
|
|
function findCommonOffsetParent(element1, element2) {
|
|
// This check is needed to avoid errors in case one of the elements isn't defined for any reason
|
|
if (!element1 || !element1.nodeType || !element2 || !element2.nodeType) {
|
|
return document.documentElement;
|
|
}
|
|
|
|
// Here we make sure to give as "start" the element that comes first in the DOM
|
|
var order = element1.compareDocumentPosition(element2) & Node.DOCUMENT_POSITION_FOLLOWING;
|
|
var start = order ? element1 : element2;
|
|
var end = order ? element2 : element1;
|
|
|
|
// Get common ancestor container
|
|
var range = document.createRange();
|
|
range.setStart(start, 0);
|
|
range.setEnd(end, 0);
|
|
var commonAncestorContainer = range.commonAncestorContainer;
|
|
|
|
// Both nodes are inside #document
|
|
|
|
if (element1 !== commonAncestorContainer && element2 !== commonAncestorContainer || start.contains(end)) {
|
|
if (isOffsetContainer(commonAncestorContainer)) {
|
|
return commonAncestorContainer;
|
|
}
|
|
|
|
return getOffsetParent(commonAncestorContainer);
|
|
}
|
|
|
|
// one of the nodes is inside shadowDOM, find which one
|
|
var element1root = getRoot(element1);
|
|
if (element1root.host) {
|
|
return findCommonOffsetParent(element1root.host, element2);
|
|
} else {
|
|
return findCommonOffsetParent(element1, getRoot(element2).host);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the scroll value of the given element in the given side (top and left)
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @argument {String} side `top` or `left`
|
|
* @returns {number} amount of scrolled pixels
|
|
*/
|
|
function getScroll(element) {
|
|
var side = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'top';
|
|
|
|
var upperSide = side === 'top' ? 'scrollTop' : 'scrollLeft';
|
|
var nodeName = element.nodeName;
|
|
|
|
if (nodeName === 'BODY' || nodeName === 'HTML') {
|
|
var html = element.ownerDocument.documentElement;
|
|
var scrollingElement = element.ownerDocument.scrollingElement || html;
|
|
return scrollingElement[upperSide];
|
|
}
|
|
|
|
return element[upperSide];
|
|
}
|
|
|
|
/*
|
|
* Sum or subtract the element scroll values (left and top) from a given rect object
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {Object} rect - Rect object you want to change
|
|
* @param {HTMLElement} element - The element from the function reads the scroll values
|
|
* @param {Boolean} subtract - set to true if you want to subtract the scroll values
|
|
* @return {Object} rect - The modifier rect object
|
|
*/
|
|
function includeScroll(rect, element) {
|
|
var subtract = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
|
|
var scrollTop = getScroll(element, 'top');
|
|
var scrollLeft = getScroll(element, 'left');
|
|
var modifier = subtract ? -1 : 1;
|
|
rect.top += scrollTop * modifier;
|
|
rect.bottom += scrollTop * modifier;
|
|
rect.left += scrollLeft * modifier;
|
|
rect.right += scrollLeft * modifier;
|
|
return rect;
|
|
}
|
|
|
|
/*
|
|
* Helper to detect borders of a given element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {CSSStyleDeclaration} styles
|
|
* Result of `getStyleComputedProperty` on the given element
|
|
* @param {String} axis - `x` or `y`
|
|
* @return {number} borders - The borders size of the given axis
|
|
*/
|
|
|
|
function getBordersSize(styles, axis) {
|
|
var sideA = axis === 'x' ? 'Left' : 'Top';
|
|
var sideB = sideA === 'Left' ? 'Right' : 'Bottom';
|
|
|
|
return parseFloat(styles['border' + sideA + 'Width'], 10) + parseFloat(styles['border' + sideB + 'Width'], 10);
|
|
}
|
|
|
|
function getSize(axis, body, html, computedStyle) {
|
|
return Math.max(body['offset' + axis], body['scroll' + axis], html['client' + axis], html['offset' + axis], html['scroll' + axis], isIE(10) ? html['offset' + axis] + computedStyle['margin' + (axis === 'Height' ? 'Top' : 'Left')] + computedStyle['margin' + (axis === 'Height' ? 'Bottom' : 'Right')] : 0);
|
|
}
|
|
|
|
function getWindowSizes() {
|
|
var body = document.body;
|
|
var html = document.documentElement;
|
|
var computedStyle = isIE(10) && getComputedStyle(html);
|
|
|
|
return {
|
|
height: getSize('Height', body, html, computedStyle),
|
|
width: getSize('Width', body, html, computedStyle)
|
|
};
|
|
}
|
|
|
|
var classCallCheck = function (instance, Constructor) {
|
|
if (!(instance instanceof Constructor)) {
|
|
throw new TypeError("Cannot call a class as a function");
|
|
}
|
|
};
|
|
|
|
var createClass = function () {
|
|
function defineProperties(target, props) {
|
|
for (var i = 0; i < props.length; i++) {
|
|
var descriptor = props[i];
|
|
descriptor.enumerable = descriptor.enumerable || false;
|
|
descriptor.configurable = true;
|
|
if ("value" in descriptor) descriptor.writable = true;
|
|
Object.defineProperty(target, descriptor.key, descriptor);
|
|
}
|
|
}
|
|
|
|
return function (Constructor, protoProps, staticProps) {
|
|
if (protoProps) defineProperties(Constructor.prototype, protoProps);
|
|
if (staticProps) defineProperties(Constructor, staticProps);
|
|
return Constructor;
|
|
};
|
|
}();
|
|
|
|
|
|
|
|
|
|
|
|
var defineProperty = function (obj, key, value) {
|
|
if (key in obj) {
|
|
Object.defineProperty(obj, key, {
|
|
value: value,
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true
|
|
});
|
|
} else {
|
|
obj[key] = value;
|
|
}
|
|
|
|
return obj;
|
|
};
|
|
|
|
var _extends = Object.assign || function (target) {
|
|
for (var i = 1; i < arguments.length; i++) {
|
|
var source = arguments[i];
|
|
|
|
for (var key in source) {
|
|
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
target[key] = source[key];
|
|
}
|
|
}
|
|
}
|
|
|
|
return target;
|
|
};
|
|
|
|
/**
|
|
* Given element offsets, generate an output similar to getBoundingClientRect
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Object} offsets
|
|
* @returns {Object} ClientRect like output
|
|
*/
|
|
function getClientRect(offsets) {
|
|
return _extends({}, offsets, {
|
|
right: offsets.left + offsets.width,
|
|
bottom: offsets.top + offsets.height
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get bounding client rect of given element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {HTMLElement} element
|
|
* @return {Object} client rect
|
|
*/
|
|
function getBoundingClientRect(element) {
|
|
var rect = {};
|
|
|
|
// IE10 10 FIX: Please, don't ask, the element isn't
|
|
// considered in DOM in some circumstances...
|
|
// This isn't reproducible in IE10 compatibility mode of IE11
|
|
try {
|
|
if (isIE(10)) {
|
|
rect = element.getBoundingClientRect();
|
|
var scrollTop = getScroll(element, 'top');
|
|
var scrollLeft = getScroll(element, 'left');
|
|
rect.top += scrollTop;
|
|
rect.left += scrollLeft;
|
|
rect.bottom += scrollTop;
|
|
rect.right += scrollLeft;
|
|
} else {
|
|
rect = element.getBoundingClientRect();
|
|
}
|
|
} catch (e) {}
|
|
|
|
var result = {
|
|
left: rect.left,
|
|
top: rect.top,
|
|
width: rect.right - rect.left,
|
|
height: rect.bottom - rect.top
|
|
};
|
|
|
|
// subtract scrollbar size from sizes
|
|
var sizes = element.nodeName === 'HTML' ? getWindowSizes() : {};
|
|
var width = sizes.width || element.clientWidth || result.right - result.left;
|
|
var height = sizes.height || element.clientHeight || result.bottom - result.top;
|
|
|
|
var horizScrollbar = element.offsetWidth - width;
|
|
var vertScrollbar = element.offsetHeight - height;
|
|
|
|
// if an hypothetical scrollbar is detected, we must be sure it's not a `border`
|
|
// we make this check conditional for performance reasons
|
|
if (horizScrollbar || vertScrollbar) {
|
|
var styles = getStyleComputedProperty(element);
|
|
horizScrollbar -= getBordersSize(styles, 'x');
|
|
vertScrollbar -= getBordersSize(styles, 'y');
|
|
|
|
result.width -= horizScrollbar;
|
|
result.height -= vertScrollbar;
|
|
}
|
|
|
|
return getClientRect(result);
|
|
}
|
|
|
|
function getOffsetRectRelativeToArbitraryNode(children, parent) {
|
|
var fixedPosition = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
|
|
var isIE10 = isIE(10);
|
|
var isHTML = parent.nodeName === 'HTML';
|
|
var childrenRect = getBoundingClientRect(children);
|
|
var parentRect = getBoundingClientRect(parent);
|
|
var scrollParent = getScrollParent(children);
|
|
|
|
var styles = getStyleComputedProperty(parent);
|
|
var borderTopWidth = parseFloat(styles.borderTopWidth, 10);
|
|
var borderLeftWidth = parseFloat(styles.borderLeftWidth, 10);
|
|
|
|
// In cases where the parent is fixed, we must ignore negative scroll in offset calc
|
|
if (fixedPosition && parent.nodeName === 'HTML') {
|
|
parentRect.top = Math.max(parentRect.top, 0);
|
|
parentRect.left = Math.max(parentRect.left, 0);
|
|
}
|
|
var offsets = getClientRect({
|
|
top: childrenRect.top - parentRect.top - borderTopWidth,
|
|
left: childrenRect.left - parentRect.left - borderLeftWidth,
|
|
width: childrenRect.width,
|
|
height: childrenRect.height
|
|
});
|
|
offsets.marginTop = 0;
|
|
offsets.marginLeft = 0;
|
|
|
|
// Subtract margins of documentElement in case it's being used as parent
|
|
// we do this only on HTML because it's the only element that behaves
|
|
// differently when margins are applied to it. The margins are included in
|
|
// the box of the documentElement, in the other cases not.
|
|
if (!isIE10 && isHTML) {
|
|
var marginTop = parseFloat(styles.marginTop, 10);
|
|
var marginLeft = parseFloat(styles.marginLeft, 10);
|
|
|
|
offsets.top -= borderTopWidth - marginTop;
|
|
offsets.bottom -= borderTopWidth - marginTop;
|
|
offsets.left -= borderLeftWidth - marginLeft;
|
|
offsets.right -= borderLeftWidth - marginLeft;
|
|
|
|
// Attach marginTop and marginLeft because in some circumstances we may need them
|
|
offsets.marginTop = marginTop;
|
|
offsets.marginLeft = marginLeft;
|
|
}
|
|
|
|
if (isIE10 && !fixedPosition ? parent.contains(scrollParent) : parent === scrollParent && scrollParent.nodeName !== 'BODY') {
|
|
offsets = includeScroll(offsets, parent);
|
|
}
|
|
|
|
return offsets;
|
|
}
|
|
|
|
function getViewportOffsetRectRelativeToArtbitraryNode(element) {
|
|
var excludeScroll = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
|
|
|
var html = element.ownerDocument.documentElement;
|
|
var relativeOffset = getOffsetRectRelativeToArbitraryNode(element, html);
|
|
var width = Math.max(html.clientWidth, window.innerWidth || 0);
|
|
var height = Math.max(html.clientHeight, window.innerHeight || 0);
|
|
|
|
var scrollTop = !excludeScroll ? getScroll(html) : 0;
|
|
var scrollLeft = !excludeScroll ? getScroll(html, 'left') : 0;
|
|
|
|
var offset = {
|
|
top: scrollTop - relativeOffset.top + relativeOffset.marginTop,
|
|
left: scrollLeft - relativeOffset.left + relativeOffset.marginLeft,
|
|
width: width,
|
|
height: height
|
|
};
|
|
|
|
return getClientRect(offset);
|
|
}
|
|
|
|
/**
|
|
* Check if the given element is fixed or is inside a fixed parent
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @argument {Element} customContainer
|
|
* @returns {Boolean} answer to "isFixed?"
|
|
*/
|
|
function isFixed(element) {
|
|
var nodeName = element.nodeName;
|
|
if (nodeName === 'BODY' || nodeName === 'HTML') {
|
|
return false;
|
|
}
|
|
if (getStyleComputedProperty(element, 'position') === 'fixed') {
|
|
return true;
|
|
}
|
|
return isFixed(getParentNode(element));
|
|
}
|
|
|
|
/**
|
|
* Finds the first parent of an element that has a transformed property defined
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @returns {Element} first transformed parent or documentElement
|
|
*/
|
|
|
|
function getFixedPositionOffsetParent(element) {
|
|
// This check is needed to avoid errors in case one of the elements isn't defined for any reason
|
|
if (!element || !element.parentElement || isIE()) {
|
|
return document.documentElement;
|
|
}
|
|
var el = element.parentElement;
|
|
while (el && getStyleComputedProperty(el, 'transform') === 'none') {
|
|
el = el.parentElement;
|
|
}
|
|
return el || document.documentElement;
|
|
}
|
|
|
|
/**
|
|
* Computed the boundaries limits and return them
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {HTMLElement} popper
|
|
* @param {HTMLElement} reference
|
|
* @param {number} padding
|
|
* @param {HTMLElement} boundariesElement - Element used to define the boundaries
|
|
* @param {Boolean} fixedPosition - Is in fixed position mode
|
|
* @returns {Object} Coordinates of the boundaries
|
|
*/
|
|
function getBoundaries(popper, reference, padding, boundariesElement) {
|
|
var fixedPosition = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
|
|
|
|
// NOTE: 1 DOM access here
|
|
|
|
var boundaries = { top: 0, left: 0 };
|
|
var offsetParent = fixedPosition ? getFixedPositionOffsetParent(popper) : findCommonOffsetParent(popper, reference);
|
|
|
|
// Handle viewport case
|
|
if (boundariesElement === 'viewport') {
|
|
boundaries = getViewportOffsetRectRelativeToArtbitraryNode(offsetParent, fixedPosition);
|
|
} else {
|
|
// Handle other cases based on DOM element used as boundaries
|
|
var boundariesNode = void 0;
|
|
if (boundariesElement === 'scrollParent') {
|
|
boundariesNode = getScrollParent(getParentNode(reference));
|
|
if (boundariesNode.nodeName === 'BODY') {
|
|
boundariesNode = popper.ownerDocument.documentElement;
|
|
}
|
|
} else if (boundariesElement === 'window') {
|
|
boundariesNode = popper.ownerDocument.documentElement;
|
|
} else {
|
|
boundariesNode = boundariesElement;
|
|
}
|
|
|
|
var offsets = getOffsetRectRelativeToArbitraryNode(boundariesNode, offsetParent, fixedPosition);
|
|
|
|
// In case of HTML, we need a different computation
|
|
if (boundariesNode.nodeName === 'HTML' && !isFixed(offsetParent)) {
|
|
var _getWindowSizes = getWindowSizes(),
|
|
height = _getWindowSizes.height,
|
|
width = _getWindowSizes.width;
|
|
|
|
boundaries.top += offsets.top - offsets.marginTop;
|
|
boundaries.bottom = height + offsets.top;
|
|
boundaries.left += offsets.left - offsets.marginLeft;
|
|
boundaries.right = width + offsets.left;
|
|
} else {
|
|
// for all the other DOM elements, this one is good
|
|
boundaries = offsets;
|
|
}
|
|
}
|
|
|
|
// Add paddings
|
|
boundaries.left += padding;
|
|
boundaries.top += padding;
|
|
boundaries.right -= padding;
|
|
boundaries.bottom -= padding;
|
|
|
|
return boundaries;
|
|
}
|
|
|
|
function getArea(_ref) {
|
|
var width = _ref.width,
|
|
height = _ref.height;
|
|
|
|
return width * height;
|
|
}
|
|
|
|
/**
|
|
* Utility used to transform the `auto` placement to the placement with more
|
|
* available space.
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Object} data - The data object generated by update method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {Object} The data object, properly modified
|
|
*/
|
|
function computeAutoPlacement(placement, refRect, popper, reference, boundariesElement) {
|
|
var padding = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 0;
|
|
|
|
if (placement.indexOf('auto') === -1) {
|
|
return placement;
|
|
}
|
|
|
|
var boundaries = getBoundaries(popper, reference, padding, boundariesElement);
|
|
|
|
var rects = {
|
|
top: {
|
|
width: boundaries.width,
|
|
height: refRect.top - boundaries.top
|
|
},
|
|
right: {
|
|
width: boundaries.right - refRect.right,
|
|
height: boundaries.height
|
|
},
|
|
bottom: {
|
|
width: boundaries.width,
|
|
height: boundaries.bottom - refRect.bottom
|
|
},
|
|
left: {
|
|
width: refRect.left - boundaries.left,
|
|
height: boundaries.height
|
|
}
|
|
};
|
|
|
|
var sortedAreas = Object.keys(rects).map(function (key) {
|
|
return _extends({
|
|
key: key
|
|
}, rects[key], {
|
|
area: getArea(rects[key])
|
|
});
|
|
}).sort(function (a, b) {
|
|
return b.area - a.area;
|
|
});
|
|
|
|
var filteredAreas = sortedAreas.filter(function (_ref2) {
|
|
var width = _ref2.width,
|
|
height = _ref2.height;
|
|
return width >= popper.clientWidth && height >= popper.clientHeight;
|
|
});
|
|
|
|
var computedPlacement = filteredAreas.length > 0 ? filteredAreas[0].key : sortedAreas[0].key;
|
|
|
|
var variation = placement.split('-')[1];
|
|
|
|
return computedPlacement + (variation ? '-' + variation : '');
|
|
}
|
|
|
|
/**
|
|
* Get offsets to the reference element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {Object} state
|
|
* @param {Element} popper - the popper element
|
|
* @param {Element} reference - the reference element (the popper will be relative to this)
|
|
* @param {Element} fixedPosition - is in fixed position mode
|
|
* @returns {Object} An object containing the offsets which will be applied to the popper
|
|
*/
|
|
function getReferenceOffsets(state, popper, reference) {
|
|
var fixedPosition = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
|
|
|
|
var commonOffsetParent = fixedPosition ? getFixedPositionOffsetParent(popper) : findCommonOffsetParent(popper, reference);
|
|
return getOffsetRectRelativeToArbitraryNode(reference, commonOffsetParent, fixedPosition);
|
|
}
|
|
|
|
/**
|
|
* Get the outer sizes of the given element (offset size + margins)
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @returns {Object} object containing width and height properties
|
|
*/
|
|
function getOuterSizes(element) {
|
|
var styles = getComputedStyle(element);
|
|
var x = parseFloat(styles.marginTop) + parseFloat(styles.marginBottom);
|
|
var y = parseFloat(styles.marginLeft) + parseFloat(styles.marginRight);
|
|
var result = {
|
|
width: element.offsetWidth + y,
|
|
height: element.offsetHeight + x
|
|
};
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Get the opposite placement of the given one
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {String} placement
|
|
* @returns {String} flipped placement
|
|
*/
|
|
function getOppositePlacement(placement) {
|
|
var hash = { left: 'right', right: 'left', bottom: 'top', top: 'bottom' };
|
|
return placement.replace(/left|right|bottom|top/g, function (matched) {
|
|
return hash[matched];
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get offsets to the popper
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {Object} position - CSS position the Popper will get applied
|
|
* @param {HTMLElement} popper - the popper element
|
|
* @param {Object} referenceOffsets - the reference offsets (the popper will be relative to this)
|
|
* @param {String} placement - one of the valid placement options
|
|
* @returns {Object} popperOffsets - An object containing the offsets which will be applied to the popper
|
|
*/
|
|
function getPopperOffsets(popper, referenceOffsets, placement) {
|
|
placement = placement.split('-')[0];
|
|
|
|
// Get popper node sizes
|
|
var popperRect = getOuterSizes(popper);
|
|
|
|
// Add position, width and height to our offsets object
|
|
var popperOffsets = {
|
|
width: popperRect.width,
|
|
height: popperRect.height
|
|
};
|
|
|
|
// depending by the popper placement we have to compute its offsets slightly differently
|
|
var isHoriz = ['right', 'left'].indexOf(placement) !== -1;
|
|
var mainSide = isHoriz ? 'top' : 'left';
|
|
var secondarySide = isHoriz ? 'left' : 'top';
|
|
var measurement = isHoriz ? 'height' : 'width';
|
|
var secondaryMeasurement = !isHoriz ? 'height' : 'width';
|
|
|
|
popperOffsets[mainSide] = referenceOffsets[mainSide] + referenceOffsets[measurement] / 2 - popperRect[measurement] / 2;
|
|
if (placement === secondarySide) {
|
|
popperOffsets[secondarySide] = referenceOffsets[secondarySide] - popperRect[secondaryMeasurement];
|
|
} else {
|
|
popperOffsets[secondarySide] = referenceOffsets[getOppositePlacement(secondarySide)];
|
|
}
|
|
|
|
return popperOffsets;
|
|
}
|
|
|
|
/**
|
|
* Mimics the `find` method of Array
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Array} arr
|
|
* @argument prop
|
|
* @argument value
|
|
* @returns index or -1
|
|
*/
|
|
function find(arr, check) {
|
|
// use native find if supported
|
|
if (Array.prototype.find) {
|
|
return arr.find(check);
|
|
}
|
|
|
|
// use `filter` to obtain the same behavior of `find`
|
|
return arr.filter(check)[0];
|
|
}
|
|
|
|
/**
|
|
* Return the index of the matching object
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Array} arr
|
|
* @argument prop
|
|
* @argument value
|
|
* @returns index or -1
|
|
*/
|
|
function findIndex(arr, prop, value) {
|
|
// use native findIndex if supported
|
|
if (Array.prototype.findIndex) {
|
|
return arr.findIndex(function (cur) {
|
|
return cur[prop] === value;
|
|
});
|
|
}
|
|
|
|
// use `find` + `indexOf` if `findIndex` isn't supported
|
|
var match = find(arr, function (obj) {
|
|
return obj[prop] === value;
|
|
});
|
|
return arr.indexOf(match);
|
|
}
|
|
|
|
/**
|
|
* Loop trough the list of modifiers and run them in order,
|
|
* each of them will then edit the data object.
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {dataObject} data
|
|
* @param {Array} modifiers
|
|
* @param {String} ends - Optional modifier name used as stopper
|
|
* @returns {dataObject}
|
|
*/
|
|
function runModifiers(modifiers, data, ends) {
|
|
var modifiersToRun = ends === undefined ? modifiers : modifiers.slice(0, findIndex(modifiers, 'name', ends));
|
|
|
|
modifiersToRun.forEach(function (modifier) {
|
|
if (modifier['function']) {
|
|
// eslint-disable-line dot-notation
|
|
console.warn('`modifier.function` is deprecated, use `modifier.fn`!');
|
|
}
|
|
var fn = modifier['function'] || modifier.fn; // eslint-disable-line dot-notation
|
|
if (modifier.enabled && isFunction(fn)) {
|
|
// Add properties to offsets to make them a complete clientRect object
|
|
// we do this before each modifier to make sure the previous one doesn't
|
|
// mess with these values
|
|
data.offsets.popper = getClientRect(data.offsets.popper);
|
|
data.offsets.reference = getClientRect(data.offsets.reference);
|
|
|
|
data = fn(data, modifier);
|
|
}
|
|
});
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Updates the position of the popper, computing the new offsets and applying
|
|
* the new style.<br />
|
|
* Prefer `scheduleUpdate` over `update` because of performance reasons.
|
|
* @method
|
|
* @memberof Popper
|
|
*/
|
|
function update() {
|
|
// if popper is destroyed, don't perform any further update
|
|
if (this.state.isDestroyed) {
|
|
return;
|
|
}
|
|
|
|
var data = {
|
|
instance: this,
|
|
styles: {},
|
|
arrowStyles: {},
|
|
attributes: {},
|
|
flipped: false,
|
|
offsets: {}
|
|
};
|
|
|
|
// compute reference element offsets
|
|
data.offsets.reference = getReferenceOffsets(this.state, this.popper, this.reference, this.options.positionFixed);
|
|
|
|
// compute auto placement, store placement inside the data object,
|
|
// modifiers will be able to edit `placement` if needed
|
|
// and refer to originalPlacement to know the original value
|
|
data.placement = computeAutoPlacement(this.options.placement, data.offsets.reference, this.popper, this.reference, this.options.modifiers.flip.boundariesElement, this.options.modifiers.flip.padding);
|
|
|
|
// store the computed placement inside `originalPlacement`
|
|
data.originalPlacement = data.placement;
|
|
|
|
data.positionFixed = this.options.positionFixed;
|
|
|
|
// compute the popper offsets
|
|
data.offsets.popper = getPopperOffsets(this.popper, data.offsets.reference, data.placement);
|
|
data.offsets.popper.position = this.options.positionFixed ? 'fixed' : 'absolute';
|
|
|
|
// run the modifiers
|
|
data = runModifiers(this.modifiers, data);
|
|
|
|
// the first `update` will call `onCreate` callback
|
|
// the other ones will call `onUpdate` callback
|
|
if (!this.state.isCreated) {
|
|
this.state.isCreated = true;
|
|
this.options.onCreate(data);
|
|
} else {
|
|
this.options.onUpdate(data);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper used to know if the given modifier is enabled.
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @returns {Boolean}
|
|
*/
|
|
function isModifierEnabled(modifiers, modifierName) {
|
|
return modifiers.some(function (_ref) {
|
|
var name = _ref.name,
|
|
enabled = _ref.enabled;
|
|
return enabled && name === modifierName;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get the prefixed supported property name
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {String} property (camelCase)
|
|
* @returns {String} prefixed property (camelCase or PascalCase, depending on the vendor prefix)
|
|
*/
|
|
function getSupportedPropertyName(property) {
|
|
var prefixes = [false, 'ms', 'Webkit', 'Moz', 'O'];
|
|
var upperProp = property.charAt(0).toUpperCase() + property.slice(1);
|
|
|
|
for (var i = 0; i < prefixes.length; i++) {
|
|
var prefix = prefixes[i];
|
|
var toCheck = prefix ? '' + prefix + upperProp : property;
|
|
if (typeof document.body.style[toCheck] !== 'undefined') {
|
|
return toCheck;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Destroy the popper
|
|
* @method
|
|
* @memberof Popper
|
|
*/
|
|
function destroy() {
|
|
this.state.isDestroyed = true;
|
|
|
|
// touch DOM only if `applyStyle` modifier is enabled
|
|
if (isModifierEnabled(this.modifiers, 'applyStyle')) {
|
|
this.popper.removeAttribute('x-placement');
|
|
this.popper.style.position = '';
|
|
this.popper.style.top = '';
|
|
this.popper.style.left = '';
|
|
this.popper.style.right = '';
|
|
this.popper.style.bottom = '';
|
|
this.popper.style.willChange = '';
|
|
this.popper.style[getSupportedPropertyName('transform')] = '';
|
|
}
|
|
|
|
this.disableEventListeners();
|
|
|
|
// remove the popper if user explicity asked for the deletion on destroy
|
|
// do not use `remove` because IE11 doesn't support it
|
|
if (this.options.removeOnDestroy) {
|
|
this.popper.parentNode.removeChild(this.popper);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Get the window associated with the element
|
|
* @argument {Element} element
|
|
* @returns {Window}
|
|
*/
|
|
function getWindow(element) {
|
|
var ownerDocument = element.ownerDocument;
|
|
return ownerDocument ? ownerDocument.defaultView : window;
|
|
}
|
|
|
|
function attachToScrollParents(scrollParent, event, callback, scrollParents) {
|
|
var isBody = scrollParent.nodeName === 'BODY';
|
|
var target = isBody ? scrollParent.ownerDocument.defaultView : scrollParent;
|
|
target.addEventListener(event, callback, { passive: true });
|
|
|
|
if (!isBody) {
|
|
attachToScrollParents(getScrollParent(target.parentNode), event, callback, scrollParents);
|
|
}
|
|
scrollParents.push(target);
|
|
}
|
|
|
|
/**
|
|
* Setup needed event listeners used to update the popper position
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @private
|
|
*/
|
|
function setupEventListeners(reference, options, state, updateBound) {
|
|
// Resize event listener on window
|
|
state.updateBound = updateBound;
|
|
getWindow(reference).addEventListener('resize', state.updateBound, { passive: true });
|
|
|
|
// Scroll event listener on scroll parents
|
|
var scrollElement = getScrollParent(reference);
|
|
attachToScrollParents(scrollElement, 'scroll', state.updateBound, state.scrollParents);
|
|
state.scrollElement = scrollElement;
|
|
state.eventsEnabled = true;
|
|
|
|
return state;
|
|
}
|
|
|
|
/**
|
|
* It will add resize/scroll events and start recalculating
|
|
* position of the popper element when they are triggered.
|
|
* @method
|
|
* @memberof Popper
|
|
*/
|
|
function enableEventListeners() {
|
|
if (!this.state.eventsEnabled) {
|
|
this.state = setupEventListeners(this.reference, this.options, this.state, this.scheduleUpdate);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove event listeners used to update the popper position
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @private
|
|
*/
|
|
function removeEventListeners(reference, state) {
|
|
// Remove resize event listener on window
|
|
getWindow(reference).removeEventListener('resize', state.updateBound);
|
|
|
|
// Remove scroll event listener on scroll parents
|
|
state.scrollParents.forEach(function (target) {
|
|
target.removeEventListener('scroll', state.updateBound);
|
|
});
|
|
|
|
// Reset state
|
|
state.updateBound = null;
|
|
state.scrollParents = [];
|
|
state.scrollElement = null;
|
|
state.eventsEnabled = false;
|
|
return state;
|
|
}
|
|
|
|
/**
|
|
* It will remove resize/scroll events and won't recalculate popper position
|
|
* when they are triggered. It also won't trigger onUpdate callback anymore,
|
|
* unless you call `update` method manually.
|
|
* @method
|
|
* @memberof Popper
|
|
*/
|
|
function disableEventListeners() {
|
|
if (this.state.eventsEnabled) {
|
|
cancelAnimationFrame(this.scheduleUpdate);
|
|
this.state = removeEventListeners(this.reference, this.state);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Tells if a given input is a number
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {*} input to check
|
|
* @return {Boolean}
|
|
*/
|
|
function isNumeric(n) {
|
|
return n !== '' && !isNaN(parseFloat(n)) && isFinite(n);
|
|
}
|
|
|
|
/**
|
|
* Set the style to the given popper
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element - Element to apply the style to
|
|
* @argument {Object} styles
|
|
* Object with a list of properties and values which will be applied to the element
|
|
*/
|
|
function setStyles(element, styles) {
|
|
Object.keys(styles).forEach(function (prop) {
|
|
var unit = '';
|
|
// add unit if the value is numeric and is one of the following
|
|
if (['width', 'height', 'top', 'right', 'bottom', 'left'].indexOf(prop) !== -1 && isNumeric(styles[prop])) {
|
|
unit = 'px';
|
|
}
|
|
element.style[prop] = styles[prop] + unit;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Set the attributes to the given popper
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element - Element to apply the attributes to
|
|
* @argument {Object} styles
|
|
* Object with a list of properties and values which will be applied to the element
|
|
*/
|
|
function setAttributes(element, attributes) {
|
|
Object.keys(attributes).forEach(function (prop) {
|
|
var value = attributes[prop];
|
|
if (value !== false) {
|
|
element.setAttribute(prop, attributes[prop]);
|
|
} else {
|
|
element.removeAttribute(prop);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @function
|
|
* @memberof Modifiers
|
|
* @argument {Object} data - The data object generated by `update` method
|
|
* @argument {Object} data.styles - List of style properties - values to apply to popper element
|
|
* @argument {Object} data.attributes - List of attribute properties - values to apply to popper element
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {Object} The same data object
|
|
*/
|
|
function applyStyle(data) {
|
|
// any property present in `data.styles` will be applied to the popper,
|
|
// in this way we can make the 3rd party modifiers add custom styles to it
|
|
// Be aware, modifiers could override the properties defined in the previous
|
|
// lines of this modifier!
|
|
setStyles(data.instance.popper, data.styles);
|
|
|
|
// any property present in `data.attributes` will be applied to the popper,
|
|
// they will be set as HTML attributes of the element
|
|
setAttributes(data.instance.popper, data.attributes);
|
|
|
|
// if arrowElement is defined and arrowStyles has some properties
|
|
if (data.arrowElement && Object.keys(data.arrowStyles).length) {
|
|
setStyles(data.arrowElement, data.arrowStyles);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Set the x-placement attribute before everything else because it could be used
|
|
* to add margins to the popper margins needs to be calculated to get the
|
|
* correct popper offsets.
|
|
* @method
|
|
* @memberof Popper.modifiers
|
|
* @param {HTMLElement} reference - The reference element used to position the popper
|
|
* @param {HTMLElement} popper - The HTML element used as popper
|
|
* @param {Object} options - Popper.js options
|
|
*/
|
|
function applyStyleOnLoad(reference, popper, options, modifierOptions, state) {
|
|
// compute reference element offsets
|
|
var referenceOffsets = getReferenceOffsets(state, popper, reference, options.positionFixed);
|
|
|
|
// compute auto placement, store placement inside the data object,
|
|
// modifiers will be able to edit `placement` if needed
|
|
// and refer to originalPlacement to know the original value
|
|
var placement = computeAutoPlacement(options.placement, referenceOffsets, popper, reference, options.modifiers.flip.boundariesElement, options.modifiers.flip.padding);
|
|
|
|
popper.setAttribute('x-placement', placement);
|
|
|
|
// Apply `position` to popper before anything else because
|
|
// without the position applied we can't guarantee correct computations
|
|
setStyles(popper, { position: options.positionFixed ? 'fixed' : 'absolute' });
|
|
|
|
return options;
|
|
}
|
|
|
|
/**
|
|
* @function
|
|
* @memberof Modifiers
|
|
* @argument {Object} data - The data object generated by `update` method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {Object} The data object, properly modified
|
|
*/
|
|
function computeStyle(data, options) {
|
|
var x = options.x,
|
|
y = options.y;
|
|
var popper = data.offsets.popper;
|
|
|
|
// Remove this legacy support in Popper.js v2
|
|
|
|
var legacyGpuAccelerationOption = find(data.instance.modifiers, function (modifier) {
|
|
return modifier.name === 'applyStyle';
|
|
}).gpuAcceleration;
|
|
if (legacyGpuAccelerationOption !== undefined) {
|
|
console.warn('WARNING: `gpuAcceleration` option moved to `computeStyle` modifier and will not be supported in future versions of Popper.js!');
|
|
}
|
|
var gpuAcceleration = legacyGpuAccelerationOption !== undefined ? legacyGpuAccelerationOption : options.gpuAcceleration;
|
|
|
|
var offsetParent = getOffsetParent(data.instance.popper);
|
|
var offsetParentRect = getBoundingClientRect(offsetParent);
|
|
|
|
// Styles
|
|
var styles = {
|
|
position: popper.position
|
|
};
|
|
|
|
// floor sides to avoid blurry text
|
|
var offsets = {
|
|
left: Math.floor(popper.left),
|
|
top: Math.floor(popper.top),
|
|
bottom: Math.floor(popper.bottom),
|
|
right: Math.floor(popper.right)
|
|
};
|
|
|
|
var sideA = x === 'bottom' ? 'top' : 'bottom';
|
|
var sideB = y === 'right' ? 'left' : 'right';
|
|
|
|
// if gpuAcceleration is set to `true` and transform is supported,
|
|
// we use `translate3d` to apply the position to the popper we
|
|
// automatically use the supported prefixed version if needed
|
|
var prefixedProperty = getSupportedPropertyName('transform');
|
|
|
|
// now, let's make a step back and look at this code closely (wtf?)
|
|
// If the content of the popper grows once it's been positioned, it
|
|
// may happen that the popper gets misplaced because of the new content
|
|
// overflowing its reference element
|
|
// To avoid this problem, we provide two options (x and y), which allow
|
|
// the consumer to define the offset origin.
|
|
// If we position a popper on top of a reference element, we can set
|
|
// `x` to `top` to make the popper grow towards its top instead of
|
|
// its bottom.
|
|
var left = void 0,
|
|
top = void 0;
|
|
if (sideA === 'bottom') {
|
|
top = -offsetParentRect.height + offsets.bottom;
|
|
} else {
|
|
top = offsets.top;
|
|
}
|
|
if (sideB === 'right') {
|
|
left = -offsetParentRect.width + offsets.right;
|
|
} else {
|
|
left = offsets.left;
|
|
}
|
|
if (gpuAcceleration && prefixedProperty) {
|
|
styles[prefixedProperty] = 'translate3d(' + left + 'px, ' + top + 'px, 0)';
|
|
styles[sideA] = 0;
|
|
styles[sideB] = 0;
|
|
styles.willChange = 'transform';
|
|
} else {
|
|
// othwerise, we use the standard `top`, `left`, `bottom` and `right` properties
|
|
var invertTop = sideA === 'bottom' ? -1 : 1;
|
|
var invertLeft = sideB === 'right' ? -1 : 1;
|
|
styles[sideA] = top * invertTop;
|
|
styles[sideB] = left * invertLeft;
|
|
styles.willChange = sideA + ', ' + sideB;
|
|
}
|
|
|
|
// Attributes
|
|
var attributes = {
|
|
'x-placement': data.placement
|
|
};
|
|
|
|
// Update `data` attributes, styles and arrowStyles
|
|
data.attributes = _extends({}, attributes, data.attributes);
|
|
data.styles = _extends({}, styles, data.styles);
|
|
data.arrowStyles = _extends({}, data.offsets.arrow, data.arrowStyles);
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Helper used to know if the given modifier depends from another one.<br />
|
|
* It checks if the needed modifier is listed and enabled.
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {Array} modifiers - list of modifiers
|
|
* @param {String} requestingName - name of requesting modifier
|
|
* @param {String} requestedName - name of requested modifier
|
|
* @returns {Boolean}
|
|
*/
|
|
function isModifierRequired(modifiers, requestingName, requestedName) {
|
|
var requesting = find(modifiers, function (_ref) {
|
|
var name = _ref.name;
|
|
return name === requestingName;
|
|
});
|
|
|
|
var isRequired = !!requesting && modifiers.some(function (modifier) {
|
|
return modifier.name === requestedName && modifier.enabled && modifier.order < requesting.order;
|
|
});
|
|
|
|
if (!isRequired) {
|
|
var _requesting = '`' + requestingName + '`';
|
|
var requested = '`' + requestedName + '`';
|
|
console.warn(requested + ' modifier is required by ' + _requesting + ' modifier in order to work, be sure to include it before ' + _requesting + '!');
|
|
}
|
|
return isRequired;
|
|
}
|
|
|
|
/**
|
|
* @function
|
|
* @memberof Modifiers
|
|
* @argument {Object} data - The data object generated by update method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {Object} The data object, properly modified
|
|
*/
|
|
function arrow(data, options) {
|
|
var _data$offsets$arrow;
|
|
|
|
// arrow depends on keepTogether in order to work
|
|
if (!isModifierRequired(data.instance.modifiers, 'arrow', 'keepTogether')) {
|
|
return data;
|
|
}
|
|
|
|
var arrowElement = options.element;
|
|
|
|
// if arrowElement is a string, suppose it's a CSS selector
|
|
if (typeof arrowElement === 'string') {
|
|
arrowElement = data.instance.popper.querySelector(arrowElement);
|
|
|
|
// if arrowElement is not found, don't run the modifier
|
|
if (!arrowElement) {
|
|
return data;
|
|
}
|
|
} else {
|
|
// if the arrowElement isn't a query selector we must check that the
|
|
// provided DOM node is child of its popper node
|
|
if (!data.instance.popper.contains(arrowElement)) {
|
|
console.warn('WARNING: `arrow.element` must be child of its popper element!');
|
|
return data;
|
|
}
|
|
}
|
|
|
|
var placement = data.placement.split('-')[0];
|
|
var _data$offsets = data.offsets,
|
|
popper = _data$offsets.popper,
|
|
reference = _data$offsets.reference;
|
|
|
|
var isVertical = ['left', 'right'].indexOf(placement) !== -1;
|
|
|
|
var len = isVertical ? 'height' : 'width';
|
|
var sideCapitalized = isVertical ? 'Top' : 'Left';
|
|
var side = sideCapitalized.toLowerCase();
|
|
var altSide = isVertical ? 'left' : 'top';
|
|
var opSide = isVertical ? 'bottom' : 'right';
|
|
var arrowElementSize = getOuterSizes(arrowElement)[len];
|
|
|
|
//
|
|
// extends keepTogether behavior making sure the popper and its
|
|
// reference have enough pixels in conjuction
|
|
//
|
|
|
|
// top/left side
|
|
if (reference[opSide] - arrowElementSize < popper[side]) {
|
|
data.offsets.popper[side] -= popper[side] - (reference[opSide] - arrowElementSize);
|
|
}
|
|
// bottom/right side
|
|
if (reference[side] + arrowElementSize > popper[opSide]) {
|
|
data.offsets.popper[side] += reference[side] + arrowElementSize - popper[opSide];
|
|
}
|
|
data.offsets.popper = getClientRect(data.offsets.popper);
|
|
|
|
// compute center of the popper
|
|
var center = reference[side] + reference[len] / 2 - arrowElementSize / 2;
|
|
|
|
// Compute the sideValue using the updated popper offsets
|
|
// take popper margin in account because we don't have this info available
|
|
var css = getStyleComputedProperty(data.instance.popper);
|
|
var popperMarginSide = parseFloat(css['margin' + sideCapitalized], 10);
|
|
var popperBorderSide = parseFloat(css['border' + sideCapitalized + 'Width'], 10);
|
|
var sideValue = center - data.offsets.popper[side] - popperMarginSide - popperBorderSide;
|
|
|
|
// prevent arrowElement from being placed not contiguously to its popper
|
|
sideValue = Math.max(Math.min(popper[len] - arrowElementSize, sideValue), 0);
|
|
|
|
data.arrowElement = arrowElement;
|
|
data.offsets.arrow = (_data$offsets$arrow = {}, defineProperty(_data$offsets$arrow, side, Math.round(sideValue)), defineProperty(_data$offsets$arrow, altSide, ''), _data$offsets$arrow);
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Get the opposite placement variation of the given one
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {String} placement variation
|
|
* @returns {String} flipped placement variation
|
|
*/
|
|
function getOppositeVariation(variation) {
|
|
if (variation === 'end') {
|
|
return 'start';
|
|
} else if (variation === 'start') {
|
|
return 'end';
|
|
}
|
|
return variation;
|
|
}
|
|
|
|
/**
|
|
* List of accepted placements to use as values of the `placement` option.<br />
|
|
* Valid placements are:
|
|
* - `auto`
|
|
* - `top`
|
|
* - `right`
|
|
* - `bottom`
|
|
* - `left`
|
|
*
|
|
* Each placement can have a variation from this list:
|
|
* - `-start`
|
|
* - `-end`
|
|
*
|
|
* Variations are interpreted easily if you think of them as the left to right
|
|
* written languages. Horizontally (`top` and `bottom`), `start` is left and `end`
|
|
* is right.<br />
|
|
* Vertically (`left` and `right`), `start` is top and `end` is bottom.
|
|
*
|
|
* Some valid examples are:
|
|
* - `top-end` (on top of reference, right aligned)
|
|
* - `right-start` (on right of reference, top aligned)
|
|
* - `bottom` (on bottom, centered)
|
|
* - `auto-right` (on the side with more space available, alignment depends by placement)
|
|
*
|
|
* @static
|
|
* @type {Array}
|
|
* @enum {String}
|
|
* @readonly
|
|
* @method placements
|
|
* @memberof Popper
|
|
*/
|
|
var placements = ['auto-start', 'auto', 'auto-end', 'top-start', 'top', 'top-end', 'right-start', 'right', 'right-end', 'bottom-end', 'bottom', 'bottom-start', 'left-end', 'left', 'left-start'];
|
|
|
|
// Get rid of `auto` `auto-start` and `auto-end`
|
|
var validPlacements = placements.slice(3);
|
|
|
|
/**
|
|
* Given an initial placement, returns all the subsequent placements
|
|
* clockwise (or counter-clockwise).
|
|
*
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {String} placement - A valid placement (it accepts variations)
|
|
* @argument {Boolean} counter - Set to true to walk the placements counterclockwise
|
|
* @returns {Array} placements including their variations
|
|
*/
|
|
function clockwise(placement) {
|
|
var counter = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
|
|
|
var index = validPlacements.indexOf(placement);
|
|
var arr = validPlacements.slice(index + 1).concat(validPlacements.slice(0, index));
|
|
return counter ? arr.reverse() : arr;
|
|
}
|
|
|
|
var BEHAVIORS = {
|
|
FLIP: 'flip',
|
|
CLOCKWISE: 'clockwise',
|
|
COUNTERCLOCKWISE: 'counterclockwise'
|
|
};
|
|
|
|
/**
|
|
* @function
|
|
* @memberof Modifiers
|
|
* @argument {Object} data - The data object generated by update method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {Object} The data object, properly modified
|
|
*/
|
|
function flip(data, options) {
|
|
// if `inner` modifier is enabled, we can't use the `flip` modifier
|
|
if (isModifierEnabled(data.instance.modifiers, 'inner')) {
|
|
return data;
|
|
}
|
|
|
|
if (data.flipped && data.placement === data.originalPlacement) {
|
|
// seems like flip is trying to loop, probably there's not enough space on any of the flippable sides
|
|
return data;
|
|
}
|
|
|
|
var boundaries = getBoundaries(data.instance.popper, data.instance.reference, options.padding, options.boundariesElement, data.positionFixed);
|
|
|
|
var placement = data.placement.split('-')[0];
|
|
var placementOpposite = getOppositePlacement(placement);
|
|
var variation = data.placement.split('-')[1] || '';
|
|
|
|
var flipOrder = [];
|
|
|
|
switch (options.behavior) {
|
|
case BEHAVIORS.FLIP:
|
|
flipOrder = [placement, placementOpposite];
|
|
break;
|
|
case BEHAVIORS.CLOCKWISE:
|
|
flipOrder = clockwise(placement);
|
|
break;
|
|
case BEHAVIORS.COUNTERCLOCKWISE:
|
|
flipOrder = clockwise(placement, true);
|
|
break;
|
|
default:
|
|
flipOrder = options.behavior;
|
|
}
|
|
|
|
flipOrder.forEach(function (step, index) {
|
|
if (placement !== step || flipOrder.length === index + 1) {
|
|
return data;
|
|
}
|
|
|
|
placement = data.placement.split('-')[0];
|
|
placementOpposite = getOppositePlacement(placement);
|
|
|
|
var popperOffsets = data.offsets.popper;
|
|
var refOffsets = data.offsets.reference;
|
|
|
|
// using floor because the reference offsets may contain decimals we are not going to consider here
|
|
var floor = Math.floor;
|
|
var overlapsRef = placement === 'left' && floor(popperOffsets.right) > floor(refOffsets.left) || placement === 'right' && floor(popperOffsets.left) < floor(refOffsets.right) || placement === 'top' && floor(popperOffsets.bottom) > floor(refOffsets.top) || placement === 'bottom' && floor(popperOffsets.top) < floor(refOffsets.bottom);
|
|
|
|
var overflowsLeft = floor(popperOffsets.left) < floor(boundaries.left);
|
|
var overflowsRight = floor(popperOffsets.right) > floor(boundaries.right);
|
|
var overflowsTop = floor(popperOffsets.top) < floor(boundaries.top);
|
|
var overflowsBottom = floor(popperOffsets.bottom) > floor(boundaries.bottom);
|
|
|
|
var overflowsBoundaries = placement === 'left' && overflowsLeft || placement === 'right' && overflowsRight || placement === 'top' && overflowsTop || placement === 'bottom' && overflowsBottom;
|
|
|
|
// flip the variation if required
|
|
var isVertical = ['top', 'bottom'].indexOf(placement) !== -1;
|
|
var flippedVariation = !!options.flipVariations && (isVertical && variation === 'start' && overflowsLeft || isVertical && variation === 'end' && overflowsRight || !isVertical && variation === 'start' && overflowsTop || !isVertical && variation === 'end' && overflowsBottom);
|
|
|
|
if (overlapsRef || overflowsBoundaries || flippedVariation) {
|
|
// this boolean to detect any flip loop
|
|
data.flipped = true;
|
|
|
|
if (overlapsRef || overflowsBoundaries) {
|
|
placement = flipOrder[index + 1];
|
|
}
|
|
|
|
if (flippedVariation) {
|
|
variation = getOppositeVariation(variation);
|
|
}
|
|
|
|
data.placement = placement + (variation ? '-' + variation : '');
|
|
|
|
// this object contains `position`, we want to preserve it along with
|
|
// any additional property we may add in the future
|
|
data.offsets.popper = _extends({}, data.offsets.popper, getPopperOffsets(data.instance.popper, data.offsets.reference, data.placement));
|
|
|
|
data = runModifiers(data.instance.modifiers, data, 'flip');
|
|
}
|
|
});
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* @function
|
|
* @memberof Modifiers
|
|
* @argument {Object} data - The data object generated by update method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {Object} The data object, properly modified
|
|
*/
|
|
function keepTogether(data) {
|
|
var _data$offsets = data.offsets,
|
|
popper = _data$offsets.popper,
|
|
reference = _data$offsets.reference;
|
|
|
|
var placement = data.placement.split('-')[0];
|
|
var floor = Math.floor;
|
|
var isVertical = ['top', 'bottom'].indexOf(placement) !== -1;
|
|
var side = isVertical ? 'right' : 'bottom';
|
|
var opSide = isVertical ? 'left' : 'top';
|
|
var measurement = isVertical ? 'width' : 'height';
|
|
|
|
if (popper[side] < floor(reference[opSide])) {
|
|
data.offsets.popper[opSide] = floor(reference[opSide]) - popper[measurement];
|
|
}
|
|
if (popper[opSide] > floor(reference[side])) {
|
|
data.offsets.popper[opSide] = floor(reference[side]);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Converts a string containing value + unit into a px value number
|
|
* @function
|
|
* @memberof {modifiers~offset}
|
|
* @private
|
|
* @argument {String} str - Value + unit string
|
|
* @argument {String} measurement - `height` or `width`
|
|
* @argument {Object} popperOffsets
|
|
* @argument {Object} referenceOffsets
|
|
* @returns {Number|String}
|
|
* Value in pixels, or original string if no values were extracted
|
|
*/
|
|
function toValue(str, measurement, popperOffsets, referenceOffsets) {
|
|
// separate value from unit
|
|
var split = str.match(/((?:\-|\+)?\d*\.?\d*)(.*)/);
|
|
var value = +split[1];
|
|
var unit = split[2];
|
|
|
|
// If it's not a number it's an operator, I guess
|
|
if (!value) {
|
|
return str;
|
|
}
|
|
|
|
if (unit.indexOf('%') === 0) {
|
|
var element = void 0;
|
|
switch (unit) {
|
|
case '%p':
|
|
element = popperOffsets;
|
|
break;
|
|
case '%':
|
|
case '%r':
|
|
default:
|
|
element = referenceOffsets;
|
|
}
|
|
|
|
var rect = getClientRect(element);
|
|
return rect[measurement] / 100 * value;
|
|
} else if (unit === 'vh' || unit === 'vw') {
|
|
// if is a vh or vw, we calculate the size based on the viewport
|
|
var size = void 0;
|
|
if (unit === 'vh') {
|
|
size = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
|
|
} else {
|
|
size = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
|
|
}
|
|
return size / 100 * value;
|
|
} else {
|
|
// if is an explicit pixel unit, we get rid of the unit and keep the value
|
|
// if is an implicit unit, it's px, and we return just the value
|
|
return value;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse an `offset` string to extrapolate `x` and `y` numeric offsets.
|
|
* @function
|
|
* @memberof {modifiers~offset}
|
|
* @private
|
|
* @argument {String} offset
|
|
* @argument {Object} popperOffsets
|
|
* @argument {Object} referenceOffsets
|
|
* @argument {String} basePlacement
|
|
* @returns {Array} a two cells array with x and y offsets in numbers
|
|
*/
|
|
function parseOffset(offset, popperOffsets, referenceOffsets, basePlacement) {
|
|
var offsets = [0, 0];
|
|
|
|
// Use height if placement is left or right and index is 0 otherwise use width
|
|
// in this way the first offset will use an axis and the second one
|
|
// will use the other one
|
|
var useHeight = ['right', 'left'].indexOf(basePlacement) !== -1;
|
|
|
|
// Split the offset string to obtain a list of values and operands
|
|
// The regex addresses values with the plus or minus sign in front (+10, -20, etc)
|
|
var fragments = offset.split(/(\+|\-)/).map(function (frag) {
|
|
return frag.trim();
|
|
});
|
|
|
|
// Detect if the offset string contains a pair of values or a single one
|
|
// they could be separated by comma or space
|
|
var divider = fragments.indexOf(find(fragments, function (frag) {
|
|
return frag.search(/,|\s/) !== -1;
|
|
}));
|
|
|
|
if (fragments[divider] && fragments[divider].indexOf(',') === -1) {
|
|
console.warn('Offsets separated by white space(s) are deprecated, use a comma (,) instead.');
|
|
}
|
|
|
|
// If divider is found, we divide the list of values and operands to divide
|
|
// them by ofset X and Y.
|
|
var splitRegex = /\s*,\s*|\s+/;
|
|
var ops = divider !== -1 ? [fragments.slice(0, divider).concat([fragments[divider].split(splitRegex)[0]]), [fragments[divider].split(splitRegex)[1]].concat(fragments.slice(divider + 1))] : [fragments];
|
|
|
|
// Convert the values with units to absolute pixels to allow our computations
|
|
ops = ops.map(function (op, index) {
|
|
// Most of the units rely on the orientation of the popper
|
|
var measurement = (index === 1 ? !useHeight : useHeight) ? 'height' : 'width';
|
|
var mergeWithPrevious = false;
|
|
return op
|
|
// This aggregates any `+` or `-` sign that aren't considered operators
|
|
// e.g.: 10 + +5 => [10, +, +5]
|
|
.reduce(function (a, b) {
|
|
if (a[a.length - 1] === '' && ['+', '-'].indexOf(b) !== -1) {
|
|
a[a.length - 1] = b;
|
|
mergeWithPrevious = true;
|
|
return a;
|
|
} else if (mergeWithPrevious) {
|
|
a[a.length - 1] += b;
|
|
mergeWithPrevious = false;
|
|
return a;
|
|
} else {
|
|
return a.concat(b);
|
|
}
|
|
}, [])
|
|
// Here we convert the string values into number values (in px)
|
|
.map(function (str) {
|
|
return toValue(str, measurement, popperOffsets, referenceOffsets);
|
|
});
|
|
});
|
|
|
|
// Loop trough the offsets arrays and execute the operations
|
|
ops.forEach(function (op, index) {
|
|
op.forEach(function (frag, index2) {
|
|
if (isNumeric(frag)) {
|
|
offsets[index] += frag * (op[index2 - 1] === '-' ? -1 : 1);
|
|
}
|
|
});
|
|
});
|
|
return offsets;
|
|
}
|
|
|
|
/**
|
|
* @function
|
|
* @memberof Modifiers
|
|
* @argument {Object} data - The data object generated by update method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @argument {Number|String} options.offset=0
|
|
* The offset value as described in the modifier description
|
|
* @returns {Object} The data object, properly modified
|
|
*/
|
|
function offset(data, _ref) {
|
|
var offset = _ref.offset;
|
|
var placement = data.placement,
|
|
_data$offsets = data.offsets,
|
|
popper = _data$offsets.popper,
|
|
reference = _data$offsets.reference;
|
|
|
|
var basePlacement = placement.split('-')[0];
|
|
|
|
var offsets = void 0;
|
|
if (isNumeric(+offset)) {
|
|
offsets = [+offset, 0];
|
|
} else {
|
|
offsets = parseOffset(offset, popper, reference, basePlacement);
|
|
}
|
|
|
|
if (basePlacement === 'left') {
|
|
popper.top += offsets[0];
|
|
popper.left -= offsets[1];
|
|
} else if (basePlacement === 'right') {
|
|
popper.top += offsets[0];
|
|
popper.left += offsets[1];
|
|
} else if (basePlacement === 'top') {
|
|
popper.left += offsets[0];
|
|
popper.top -= offsets[1];
|
|
} else if (basePlacement === 'bottom') {
|
|
popper.left += offsets[0];
|
|
popper.top += offsets[1];
|
|
}
|
|
|
|
data.popper = popper;
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* @function
|
|
* @memberof Modifiers
|
|
* @argument {Object} data - The data object generated by `update` method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {Object} The data object, properly modified
|
|
*/
|
|
function preventOverflow(data, options) {
|
|
var boundariesElement = options.boundariesElement || getOffsetParent(data.instance.popper);
|
|
|
|
// If offsetParent is the reference element, we really want to
|
|
// go one step up and use the next offsetParent as reference to
|
|
// avoid to make this modifier completely useless and look like broken
|
|
if (data.instance.reference === boundariesElement) {
|
|
boundariesElement = getOffsetParent(boundariesElement);
|
|
}
|
|
|
|
var boundaries = getBoundaries(data.instance.popper, data.instance.reference, options.padding, boundariesElement, data.positionFixed);
|
|
options.boundaries = boundaries;
|
|
|
|
var order = options.priority;
|
|
var popper = data.offsets.popper;
|
|
|
|
var check = {
|
|
primary: function primary(placement) {
|
|
var value = popper[placement];
|
|
if (popper[placement] < boundaries[placement] && !options.escapeWithReference) {
|
|
value = Math.max(popper[placement], boundaries[placement]);
|
|
}
|
|
return defineProperty({}, placement, value);
|
|
},
|
|
secondary: function secondary(placement) {
|
|
var mainSide = placement === 'right' ? 'left' : 'top';
|
|
var value = popper[mainSide];
|
|
if (popper[placement] > boundaries[placement] && !options.escapeWithReference) {
|
|
value = Math.min(popper[mainSide], boundaries[placement] - (placement === 'right' ? popper.width : popper.height));
|
|
}
|
|
return defineProperty({}, mainSide, value);
|
|
}
|
|
};
|
|
|
|
order.forEach(function (placement) {
|
|
var side = ['left', 'top'].indexOf(placement) !== -1 ? 'primary' : 'secondary';
|
|
popper = _extends({}, popper, check[side](placement));
|
|
});
|
|
|
|
data.offsets.popper = popper;
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* @function
|
|
* @memberof Modifiers
|
|
* @argument {Object} data - The data object generated by `update` method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {Object} The data object, properly modified
|
|
*/
|
|
function shift(data) {
|
|
var placement = data.placement;
|
|
var basePlacement = placement.split('-')[0];
|
|
var shiftvariation = placement.split('-')[1];
|
|
|
|
// if shift shiftvariation is specified, run the modifier
|
|
if (shiftvariation) {
|
|
var _data$offsets = data.offsets,
|
|
reference = _data$offsets.reference,
|
|
popper = _data$offsets.popper;
|
|
|
|
var isVertical = ['bottom', 'top'].indexOf(basePlacement) !== -1;
|
|
var side = isVertical ? 'left' : 'top';
|
|
var measurement = isVertical ? 'width' : 'height';
|
|
|
|
var shiftOffsets = {
|
|
start: defineProperty({}, side, reference[side]),
|
|
end: defineProperty({}, side, reference[side] + reference[measurement] - popper[measurement])
|
|
};
|
|
|
|
data.offsets.popper = _extends({}, popper, shiftOffsets[shiftvariation]);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* @function
|
|
* @memberof Modifiers
|
|
* @argument {Object} data - The data object generated by update method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {Object} The data object, properly modified
|
|
*/
|
|
function hide(data) {
|
|
if (!isModifierRequired(data.instance.modifiers, 'hide', 'preventOverflow')) {
|
|
return data;
|
|
}
|
|
|
|
var refRect = data.offsets.reference;
|
|
var bound = find(data.instance.modifiers, function (modifier) {
|
|
return modifier.name === 'preventOverflow';
|
|
}).boundaries;
|
|
|
|
if (refRect.bottom < bound.top || refRect.left > bound.right || refRect.top > bound.bottom || refRect.right < bound.left) {
|
|
// Avoid unnecessary DOM access if visibility hasn't changed
|
|
if (data.hide === true) {
|
|
return data;
|
|
}
|
|
|
|
data.hide = true;
|
|
data.attributes['x-out-of-boundaries'] = '';
|
|
} else {
|
|
// Avoid unnecessary DOM access if visibility hasn't changed
|
|
if (data.hide === false) {
|
|
return data;
|
|
}
|
|
|
|
data.hide = false;
|
|
data.attributes['x-out-of-boundaries'] = false;
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* @function
|
|
* @memberof Modifiers
|
|
* @argument {Object} data - The data object generated by `update` method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {Object} The data object, properly modified
|
|
*/
|
|
function inner(data) {
|
|
var placement = data.placement;
|
|
var basePlacement = placement.split('-')[0];
|
|
var _data$offsets = data.offsets,
|
|
popper = _data$offsets.popper,
|
|
reference = _data$offsets.reference;
|
|
|
|
var isHoriz = ['left', 'right'].indexOf(basePlacement) !== -1;
|
|
|
|
var subtractLength = ['top', 'left'].indexOf(basePlacement) === -1;
|
|
|
|
popper[isHoriz ? 'left' : 'top'] = reference[basePlacement] - (subtractLength ? popper[isHoriz ? 'width' : 'height'] : 0);
|
|
|
|
data.placement = getOppositePlacement(placement);
|
|
data.offsets.popper = getClientRect(popper);
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Modifier function, each modifier can have a function of this type assigned
|
|
* to its `fn` property.<br />
|
|
* These functions will be called on each update, this means that you must
|
|
* make sure they are performant enough to avoid performance bottlenecks.
|
|
*
|
|
* @function ModifierFn
|
|
* @argument {dataObject} data - The data object generated by `update` method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {dataObject} The data object, properly modified
|
|
*/
|
|
|
|
/**
|
|
* Modifiers are plugins used to alter the behavior of your poppers.<br />
|
|
* Popper.js uses a set of 9 modifiers to provide all the basic functionalities
|
|
* needed by the library.
|
|
*
|
|
* Usually you don't want to override the `order`, `fn` and `onLoad` props.
|
|
* All the other properties are configurations that could be tweaked.
|
|
* @namespace modifiers
|
|
*/
|
|
var modifiers = {
|
|
/**
|
|
* Modifier used to shift the popper on the start or end of its reference
|
|
* element.<br />
|
|
* It will read the variation of the `placement` property.<br />
|
|
* It can be one either `-end` or `-start`.
|
|
* @memberof modifiers
|
|
* @inner
|
|
*/
|
|
shift: {
|
|
/** @prop {number} order=100 - Index used to define the order of execution */
|
|
order: 100,
|
|
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
|
|
enabled: true,
|
|
/** @prop {ModifierFn} */
|
|
fn: shift
|
|
},
|
|
|
|
/**
|
|
* The `offset` modifier can shift your popper on both its axis.
|
|
*
|
|
* It accepts the following units:
|
|
* - `px` or unitless, interpreted as pixels
|
|
* - `%` or `%r`, percentage relative to the length of the reference element
|
|
* - `%p`, percentage relative to the length of the popper element
|
|
* - `vw`, CSS viewport width unit
|
|
* - `vh`, CSS viewport height unit
|
|
*
|
|
* For length is intended the main axis relative to the placement of the popper.<br />
|
|
* This means that if the placement is `top` or `bottom`, the length will be the
|
|
* `width`. In case of `left` or `right`, it will be the height.
|
|
*
|
|
* You can provide a single value (as `Number` or `String`), or a pair of values
|
|
* as `String` divided by a comma or one (or more) white spaces.<br />
|
|
* The latter is a deprecated method because it leads to confusion and will be
|
|
* removed in v2.<br />
|
|
* Additionally, it accepts additions and subtractions between different units.
|
|
* Note that multiplications and divisions aren't supported.
|
|
*
|
|
* Valid examples are:
|
|
* ```
|
|
* 10
|
|
* '10%'
|
|
* '10, 10'
|
|
* '10%, 10'
|
|
* '10 + 10%'
|
|
* '10 - 5vh + 3%'
|
|
* '-10px + 5vh, 5px - 6%'
|
|
* ```
|
|
* > **NB**: If you desire to apply offsets to your poppers in a way that may make them overlap
|
|
* > with their reference element, unfortunately, you will have to disable the `flip` modifier.
|
|
* > More on this [reading this issue](https://github.com/FezVrasta/popper.js/issues/373)
|
|
*
|
|
* @memberof modifiers
|
|
* @inner
|
|
*/
|
|
offset: {
|
|
/** @prop {number} order=200 - Index used to define the order of execution */
|
|
order: 200,
|
|
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
|
|
enabled: true,
|
|
/** @prop {ModifierFn} */
|
|
fn: offset,
|
|
/** @prop {Number|String} offset=0
|
|
* The offset value as described in the modifier description
|
|
*/
|
|
offset: 0
|
|
},
|
|
|
|
/**
|
|
* Modifier used to prevent the popper from being positioned outside the boundary.
|
|
*
|
|
* An scenario exists where the reference itself is not within the boundaries.<br />
|
|
* We can say it has "escaped the boundaries" — or just "escaped".<br />
|
|
* In this case we need to decide whether the popper should either:
|
|
*
|
|
* - detach from the reference and remain "trapped" in the boundaries, or
|
|
* - if it should ignore the boundary and "escape with its reference"
|
|
*
|
|
* When `escapeWithReference` is set to`true` and reference is completely
|
|
* outside its boundaries, the popper will overflow (or completely leave)
|
|
* the boundaries in order to remain attached to the edge of the reference.
|
|
*
|
|
* @memberof modifiers
|
|
* @inner
|
|
*/
|
|
preventOverflow: {
|
|
/** @prop {number} order=300 - Index used to define the order of execution */
|
|
order: 300,
|
|
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
|
|
enabled: true,
|
|
/** @prop {ModifierFn} */
|
|
fn: preventOverflow,
|
|
/**
|
|
* @prop {Array} [priority=['left','right','top','bottom']]
|
|
* Popper will try to prevent overflow following these priorities by default,
|
|
* then, it could overflow on the left and on top of the `boundariesElement`
|
|
*/
|
|
priority: ['left', 'right', 'top', 'bottom'],
|
|
/**
|
|
* @prop {number} padding=5
|
|
* Amount of pixel used to define a minimum distance between the boundaries
|
|
* and the popper this makes sure the popper has always a little padding
|
|
* between the edges of its container
|
|
*/
|
|
padding: 5,
|
|
/**
|
|
* @prop {String|HTMLElement} boundariesElement='scrollParent'
|
|
* Boundaries used by the modifier, can be `scrollParent`, `window`,
|
|
* `viewport` or any DOM element.
|
|
*/
|
|
boundariesElement: 'scrollParent'
|
|
},
|
|
|
|
/**
|
|
* Modifier used to make sure the reference and its popper stay near eachothers
|
|
* without leaving any gap between the two. Expecially useful when the arrow is
|
|
* enabled and you want to assure it to point to its reference element.
|
|
* It cares only about the first axis, you can still have poppers with margin
|
|
* between the popper and its reference element.
|
|
* @memberof modifiers
|
|
* @inner
|
|
*/
|
|
keepTogether: {
|
|
/** @prop {number} order=400 - Index used to define the order of execution */
|
|
order: 400,
|
|
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
|
|
enabled: true,
|
|
/** @prop {ModifierFn} */
|
|
fn: keepTogether
|
|
},
|
|
|
|
/**
|
|
* This modifier is used to move the `arrowElement` of the popper to make
|
|
* sure it is positioned between the reference element and its popper element.
|
|
* It will read the outer size of the `arrowElement` node to detect how many
|
|
* pixels of conjuction are needed.
|
|
*
|
|
* It has no effect if no `arrowElement` is provided.
|
|
* @memberof modifiers
|
|
* @inner
|
|
*/
|
|
arrow: {
|
|
/** @prop {number} order=500 - Index used to define the order of execution */
|
|
order: 500,
|
|
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
|
|
enabled: true,
|
|
/** @prop {ModifierFn} */
|
|
fn: arrow,
|
|
/** @prop {String|HTMLElement} element='[x-arrow]' - Selector or node used as arrow */
|
|
element: '[x-arrow]'
|
|
},
|
|
|
|
/**
|
|
* Modifier used to flip the popper's placement when it starts to overlap its
|
|
* reference element.
|
|
*
|
|
* Requires the `preventOverflow` modifier before it in order to work.
|
|
*
|
|
* **NOTE:** this modifier will interrupt the current update cycle and will
|
|
* restart it if it detects the need to flip the placement.
|
|
* @memberof modifiers
|
|
* @inner
|
|
*/
|
|
flip: {
|
|
/** @prop {number} order=600 - Index used to define the order of execution */
|
|
order: 600,
|
|
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
|
|
enabled: true,
|
|
/** @prop {ModifierFn} */
|
|
fn: flip,
|
|
/**
|
|
* @prop {String|Array} behavior='flip'
|
|
* The behavior used to change the popper's placement. It can be one of
|
|
* `flip`, `clockwise`, `counterclockwise` or an array with a list of valid
|
|
* placements (with optional variations).
|
|
*/
|
|
behavior: 'flip',
|
|
/**
|
|
* @prop {number} padding=5
|
|
* The popper will flip if it hits the edges of the `boundariesElement`
|
|
*/
|
|
padding: 5,
|
|
/**
|
|
* @prop {String|HTMLElement} boundariesElement='viewport'
|
|
* The element which will define the boundaries of the popper position,
|
|
* the popper will never be placed outside of the defined boundaries
|
|
* (except if keepTogether is enabled)
|
|
*/
|
|
boundariesElement: 'viewport'
|
|
},
|
|
|
|
/**
|
|
* Modifier used to make the popper flow toward the inner of the reference element.
|
|
* By default, when this modifier is disabled, the popper will be placed outside
|
|
* the reference element.
|
|
* @memberof modifiers
|
|
* @inner
|
|
*/
|
|
inner: {
|
|
/** @prop {number} order=700 - Index used to define the order of execution */
|
|
order: 700,
|
|
/** @prop {Boolean} enabled=false - Whether the modifier is enabled or not */
|
|
enabled: false,
|
|
/** @prop {ModifierFn} */
|
|
fn: inner
|
|
},
|
|
|
|
/**
|
|
* Modifier used to hide the popper when its reference element is outside of the
|
|
* popper boundaries. It will set a `x-out-of-boundaries` attribute which can
|
|
* be used to hide with a CSS selector the popper when its reference is
|
|
* out of boundaries.
|
|
*
|
|
* Requires the `preventOverflow` modifier before it in order to work.
|
|
* @memberof modifiers
|
|
* @inner
|
|
*/
|
|
hide: {
|
|
/** @prop {number} order=800 - Index used to define the order of execution */
|
|
order: 800,
|
|
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
|
|
enabled: true,
|
|
/** @prop {ModifierFn} */
|
|
fn: hide
|
|
},
|
|
|
|
/**
|
|
* Computes the style that will be applied to the popper element to gets
|
|
* properly positioned.
|
|
*
|
|
* Note that this modifier will not touch the DOM, it just prepares the styles
|
|
* so that `applyStyle` modifier can apply it. This separation is useful
|
|
* in case you need to replace `applyStyle` with a custom implementation.
|
|
*
|
|
* This modifier has `850` as `order` value to maintain backward compatibility
|
|
* with previous versions of Popper.js. Expect the modifiers ordering method
|
|
* to change in future major versions of the library.
|
|
*
|
|
* @memberof modifiers
|
|
* @inner
|
|
*/
|
|
computeStyle: {
|
|
/** @prop {number} order=850 - Index used to define the order of execution */
|
|
order: 850,
|
|
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
|
|
enabled: true,
|
|
/** @prop {ModifierFn} */
|
|
fn: computeStyle,
|
|
/**
|
|
* @prop {Boolean} gpuAcceleration=true
|
|
* If true, it uses the CSS 3d transformation to position the popper.
|
|
* Otherwise, it will use the `top` and `left` properties.
|
|
*/
|
|
gpuAcceleration: true,
|
|
/**
|
|
* @prop {string} [x='bottom']
|
|
* Where to anchor the X axis (`bottom` or `top`). AKA X offset origin.
|
|
* Change this if your popper should grow in a direction different from `bottom`
|
|
*/
|
|
x: 'bottom',
|
|
/**
|
|
* @prop {string} [x='left']
|
|
* Where to anchor the Y axis (`left` or `right`). AKA Y offset origin.
|
|
* Change this if your popper should grow in a direction different from `right`
|
|
*/
|
|
y: 'right'
|
|
},
|
|
|
|
/**
|
|
* Applies the computed styles to the popper element.
|
|
*
|
|
* All the DOM manipulations are limited to this modifier. This is useful in case
|
|
* you want to integrate Popper.js inside a framework or view library and you
|
|
* want to delegate all the DOM manipulations to it.
|
|
*
|
|
* Note that if you disable this modifier, you must make sure the popper element
|
|
* has its position set to `absolute` before Popper.js can do its work!
|
|
*
|
|
* Just disable this modifier and define you own to achieve the desired effect.
|
|
*
|
|
* @memberof modifiers
|
|
* @inner
|
|
*/
|
|
applyStyle: {
|
|
/** @prop {number} order=900 - Index used to define the order of execution */
|
|
order: 900,
|
|
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
|
|
enabled: true,
|
|
/** @prop {ModifierFn} */
|
|
fn: applyStyle,
|
|
/** @prop {Function} */
|
|
onLoad: applyStyleOnLoad,
|
|
/**
|
|
* @deprecated since version 1.10.0, the property moved to `computeStyle` modifier
|
|
* @prop {Boolean} gpuAcceleration=true
|
|
* If true, it uses the CSS 3d transformation to position the popper.
|
|
* Otherwise, it will use the `top` and `left` properties.
|
|
*/
|
|
gpuAcceleration: undefined
|
|
}
|
|
};
|
|
|
|
/**
|
|
* The `dataObject` is an object containing all the informations used by Popper.js
|
|
* this object get passed to modifiers and to the `onCreate` and `onUpdate` callbacks.
|
|
* @name dataObject
|
|
* @property {Object} data.instance The Popper.js instance
|
|
* @property {String} data.placement Placement applied to popper
|
|
* @property {String} data.originalPlacement Placement originally defined on init
|
|
* @property {Boolean} data.flipped True if popper has been flipped by flip modifier
|
|
* @property {Boolean} data.hide True if the reference element is out of boundaries, useful to know when to hide the popper.
|
|
* @property {HTMLElement} data.arrowElement Node used as arrow by arrow modifier
|
|
* @property {Object} data.styles Any CSS property defined here will be applied to the popper, it expects the JavaScript nomenclature (eg. `marginBottom`)
|
|
* @property {Object} data.arrowStyles Any CSS property defined here will be applied to the popper arrow, it expects the JavaScript nomenclature (eg. `marginBottom`)
|
|
* @property {Object} data.boundaries Offsets of the popper boundaries
|
|
* @property {Object} data.offsets The measurements of popper, reference and arrow elements.
|
|
* @property {Object} data.offsets.popper `top`, `left`, `width`, `height` values
|
|
* @property {Object} data.offsets.reference `top`, `left`, `width`, `height` values
|
|
* @property {Object} data.offsets.arrow] `top` and `left` offsets, only one of them will be different from 0
|
|
*/
|
|
|
|
/**
|
|
* Default options provided to Popper.js constructor.<br />
|
|
* These can be overriden using the `options` argument of Popper.js.<br />
|
|
* To override an option, simply pass as 3rd argument an object with the same
|
|
* structure of this object, example:
|
|
* ```
|
|
* new Popper(ref, pop, {
|
|
* modifiers: {
|
|
* preventOverflow: { enabled: false }
|
|
* }
|
|
* })
|
|
* ```
|
|
* @type {Object}
|
|
* @static
|
|
* @memberof Popper
|
|
*/
|
|
var Defaults = {
|
|
/**
|
|
* Popper's placement
|
|
* @prop {Popper.placements} placement='bottom'
|
|
*/
|
|
placement: 'bottom',
|
|
|
|
/**
|
|
* Set this to true if you want popper to position it self in 'fixed' mode
|
|
* @prop {Boolean} positionFixed=false
|
|
*/
|
|
positionFixed: false,
|
|
|
|
/**
|
|
* Whether events (resize, scroll) are initially enabled
|
|
* @prop {Boolean} eventsEnabled=true
|
|
*/
|
|
eventsEnabled: true,
|
|
|
|
/**
|
|
* Set to true if you want to automatically remove the popper when
|
|
* you call the `destroy` method.
|
|
* @prop {Boolean} removeOnDestroy=false
|
|
*/
|
|
removeOnDestroy: false,
|
|
|
|
/**
|
|
* Callback called when the popper is created.<br />
|
|
* By default, is set to no-op.<br />
|
|
* Access Popper.js instance with `data.instance`.
|
|
* @prop {onCreate}
|
|
*/
|
|
onCreate: function onCreate() {},
|
|
|
|
/**
|
|
* Callback called when the popper is updated, this callback is not called
|
|
* on the initialization/creation of the popper, but only on subsequent
|
|
* updates.<br />
|
|
* By default, is set to no-op.<br />
|
|
* Access Popper.js instance with `data.instance`.
|
|
* @prop {onUpdate}
|
|
*/
|
|
onUpdate: function onUpdate() {},
|
|
|
|
/**
|
|
* List of modifiers used to modify the offsets before they are applied to the popper.
|
|
* They provide most of the functionalities of Popper.js
|
|
* @prop {modifiers}
|
|
*/
|
|
modifiers: modifiers
|
|
};
|
|
|
|
/**
|
|
* @callback onCreate
|
|
* @param {dataObject} data
|
|
*/
|
|
|
|
/**
|
|
* @callback onUpdate
|
|
* @param {dataObject} data
|
|
*/
|
|
|
|
// Utils
|
|
// Methods
|
|
var Popper = function () {
|
|
/**
|
|
* Create a new Popper.js instance
|
|
* @class Popper
|
|
* @param {HTMLElement|referenceObject} reference - The reference element used to position the popper
|
|
* @param {HTMLElement} popper - The HTML element used as popper.
|
|
* @param {Object} options - Your custom options to override the ones defined in [Defaults](#defaults)
|
|
* @return {Object} instance - The generated Popper.js instance
|
|
*/
|
|
function Popper(reference, popper) {
|
|
var _this = this;
|
|
|
|
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
classCallCheck(this, Popper);
|
|
|
|
this.scheduleUpdate = function () {
|
|
return requestAnimationFrame(_this.update);
|
|
};
|
|
|
|
// make update() debounced, so that it only runs at most once-per-tick
|
|
this.update = debounce(this.update.bind(this));
|
|
|
|
// with {} we create a new object with the options inside it
|
|
this.options = _extends({}, Popper.Defaults, options);
|
|
|
|
// init state
|
|
this.state = {
|
|
isDestroyed: false,
|
|
isCreated: false,
|
|
scrollParents: []
|
|
};
|
|
|
|
// get reference and popper elements (allow jQuery wrappers)
|
|
this.reference = reference && reference.jquery ? reference[0] : reference;
|
|
this.popper = popper && popper.jquery ? popper[0] : popper;
|
|
|
|
// Deep merge modifiers options
|
|
this.options.modifiers = {};
|
|
Object.keys(_extends({}, Popper.Defaults.modifiers, options.modifiers)).forEach(function (name) {
|
|
_this.options.modifiers[name] = _extends({}, Popper.Defaults.modifiers[name] || {}, options.modifiers ? options.modifiers[name] : {});
|
|
});
|
|
|
|
// Refactoring modifiers' list (Object => Array)
|
|
this.modifiers = Object.keys(this.options.modifiers).map(function (name) {
|
|
return _extends({
|
|
name: name
|
|
}, _this.options.modifiers[name]);
|
|
})
|
|
// sort the modifiers by order
|
|
.sort(function (a, b) {
|
|
return a.order - b.order;
|
|
});
|
|
|
|
// modifiers have the ability to execute arbitrary code when Popper.js get inited
|
|
// such code is executed in the same order of its modifier
|
|
// they could add new properties to their options configuration
|
|
// BE AWARE: don't add options to `options.modifiers.name` but to `modifierOptions`!
|
|
this.modifiers.forEach(function (modifierOptions) {
|
|
if (modifierOptions.enabled && isFunction(modifierOptions.onLoad)) {
|
|
modifierOptions.onLoad(_this.reference, _this.popper, _this.options, modifierOptions, _this.state);
|
|
}
|
|
});
|
|
|
|
// fire the first update to position the popper in the right place
|
|
this.update();
|
|
|
|
var eventsEnabled = this.options.eventsEnabled;
|
|
if (eventsEnabled) {
|
|
// setup event listeners, they will take care of update the position in specific situations
|
|
this.enableEventListeners();
|
|
}
|
|
|
|
this.state.eventsEnabled = eventsEnabled;
|
|
}
|
|
|
|
// We can't use class properties because they don't get listed in the
|
|
// class prototype and break stuff like Sinon stubs
|
|
|
|
|
|
createClass(Popper, [{
|
|
key: 'update',
|
|
value: function update$$1() {
|
|
return update.call(this);
|
|
}
|
|
}, {
|
|
key: 'destroy',
|
|
value: function destroy$$1() {
|
|
return destroy.call(this);
|
|
}
|
|
}, {
|
|
key: 'enableEventListeners',
|
|
value: function enableEventListeners$$1() {
|
|
return enableEventListeners.call(this);
|
|
}
|
|
}, {
|
|
key: 'disableEventListeners',
|
|
value: function disableEventListeners$$1() {
|
|
return disableEventListeners.call(this);
|
|
}
|
|
|
|
/**
|
|
* Schedule an update, it will run on the next UI update available
|
|
* @method scheduleUpdate
|
|
* @memberof Popper
|
|
*/
|
|
|
|
|
|
/**
|
|
* Collection of utilities useful when writing custom modifiers.
|
|
* Starting from version 1.7, this method is available only if you
|
|
* include `popper-utils.js` before `popper.js`.
|
|
*
|
|
* **DEPRECATION**: This way to access PopperUtils is deprecated
|
|
* and will be removed in v2! Use the PopperUtils module directly instead.
|
|
* Due to the high instability of the methods contained in Utils, we can't
|
|
* guarantee them to follow semver. Use them at your own risk!
|
|
* @static
|
|
* @private
|
|
* @type {Object}
|
|
* @deprecated since version 1.8
|
|
* @member Utils
|
|
* @memberof Popper
|
|
*/
|
|
|
|
}]);
|
|
return Popper;
|
|
}();
|
|
|
|
/**
|
|
* The `referenceObject` is an object that provides an interface compatible with Popper.js
|
|
* and lets you use it as replacement of a real DOM node.<br />
|
|
* You can use this method to position a popper relatively to a set of coordinates
|
|
* in case you don't have a DOM node to use as reference.
|
|
*
|
|
* ```
|
|
* new Popper(referenceObject, popperNode);
|
|
* ```
|
|
*
|
|
* NB: This feature isn't supported in Internet Explorer 10
|
|
* @name referenceObject
|
|
* @property {Function} data.getBoundingClientRect
|
|
* A function that returns a set of coordinates compatible with the native `getBoundingClientRect` method.
|
|
* @property {number} data.clientWidth
|
|
* An ES6 getter that will return the width of the virtual reference element.
|
|
* @property {number} data.clientHeight
|
|
* An ES6 getter that will return the height of the virtual reference element.
|
|
*/
|
|
|
|
|
|
Popper.Utils = (typeof window !== 'undefined' ? window : global).PopperUtils;
|
|
Popper.placements = placements;
|
|
Popper.Defaults = Defaults;
|
|
|
|
return Popper;
|
|
|
|
})));
|
|
//# sourceMappingURL=popper.js.map
|
|
|
|
/**!
|
|
* @fileOverview Kickass library to create and place poppers near their reference elements.
|
|
* @version 1.14.1
|
|
* @license
|
|
* Copyright (c) 2016 Federico Zivolo and contributors
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*/
|
|
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
|
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
|
(factory((global.PopperUtils = {})));
|
|
}(this, (function (exports) { 'use strict';
|
|
|
|
/**
|
|
* Get CSS computed property of the given element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Eement} element
|
|
* @argument {String} property
|
|
*/
|
|
function getStyleComputedProperty(element, property) {
|
|
if (element.nodeType !== 1) {
|
|
return [];
|
|
}
|
|
// NOTE: 1 DOM access here
|
|
var css = getComputedStyle(element, null);
|
|
return property ? css[property] : css;
|
|
}
|
|
|
|
/**
|
|
* Returns the parentNode or the host of the element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @returns {Element} parent
|
|
*/
|
|
function getParentNode(element) {
|
|
if (element.nodeName === 'HTML') {
|
|
return element;
|
|
}
|
|
return element.parentNode || element.host;
|
|
}
|
|
|
|
/**
|
|
* Returns the scrolling parent of the given element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @returns {Element} scroll parent
|
|
*/
|
|
function getScrollParent(element) {
|
|
// Return body, `getScroll` will take care to get the correct `scrollTop` from it
|
|
if (!element) {
|
|
return document.body;
|
|
}
|
|
|
|
switch (element.nodeName) {
|
|
case 'HTML':
|
|
case 'BODY':
|
|
return element.ownerDocument.body;
|
|
case '#document':
|
|
return element.body;
|
|
}
|
|
|
|
// Firefox want us to check `-x` and `-y` variations as well
|
|
|
|
var _getStyleComputedProp = getStyleComputedProperty(element),
|
|
overflow = _getStyleComputedProp.overflow,
|
|
overflowX = _getStyleComputedProp.overflowX,
|
|
overflowY = _getStyleComputedProp.overflowY;
|
|
|
|
if (/(auto|scroll|overlay)/.test(overflow + overflowY + overflowX)) {
|
|
return element;
|
|
}
|
|
|
|
return getScrollParent(getParentNode(element));
|
|
}
|
|
|
|
/**
|
|
* Tells if you are running Internet Explorer
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {number} version to check
|
|
* @returns {Boolean} isIE
|
|
*/
|
|
var cache = {};
|
|
|
|
var isIE = function () {
|
|
var version = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'all';
|
|
|
|
version = version.toString();
|
|
if (cache.hasOwnProperty(version)) {
|
|
return cache[version];
|
|
}
|
|
switch (version) {
|
|
case '11':
|
|
cache[version] = navigator.userAgent.indexOf('Trident') !== -1;
|
|
break;
|
|
case '10':
|
|
cache[version] = navigator.appVersion.indexOf('MSIE 10') !== -1;
|
|
break;
|
|
case 'all':
|
|
cache[version] = navigator.userAgent.indexOf('Trident') !== -1 || navigator.userAgent.indexOf('MSIE') !== -1;
|
|
break;
|
|
}
|
|
|
|
//Set IE
|
|
cache.all = cache.all || Object.keys(cache).some(function (key) {
|
|
return cache[key];
|
|
});
|
|
return cache[version];
|
|
};
|
|
|
|
/**
|
|
* Returns the offset parent of the given element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @returns {Element} offset parent
|
|
*/
|
|
function getOffsetParent(element) {
|
|
if (!element) {
|
|
return document.documentElement;
|
|
}
|
|
|
|
var noOffsetParent = isIE(10) ? document.body : null;
|
|
|
|
// NOTE: 1 DOM access here
|
|
var offsetParent = element.offsetParent;
|
|
// Skip hidden elements which don't have an offsetParent
|
|
while (offsetParent === noOffsetParent && element.nextElementSibling) {
|
|
offsetParent = (element = element.nextElementSibling).offsetParent;
|
|
}
|
|
|
|
var nodeName = offsetParent && offsetParent.nodeName;
|
|
|
|
if (!nodeName || nodeName === 'BODY' || nodeName === 'HTML') {
|
|
return element ? element.ownerDocument.documentElement : document.documentElement;
|
|
}
|
|
|
|
// .offsetParent will return the closest TD or TABLE in case
|
|
// no offsetParent is present, I hate this job...
|
|
if (['TD', 'TABLE'].indexOf(offsetParent.nodeName) !== -1 && getStyleComputedProperty(offsetParent, 'position') === 'static') {
|
|
return getOffsetParent(offsetParent);
|
|
}
|
|
|
|
return offsetParent;
|
|
}
|
|
|
|
function isOffsetContainer(element) {
|
|
var nodeName = element.nodeName;
|
|
|
|
if (nodeName === 'BODY') {
|
|
return false;
|
|
}
|
|
return nodeName === 'HTML' || getOffsetParent(element.firstElementChild) === element;
|
|
}
|
|
|
|
/**
|
|
* Finds the root node (document, shadowDOM root) of the given element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} node
|
|
* @returns {Element} root node
|
|
*/
|
|
function getRoot(node) {
|
|
if (node.parentNode !== null) {
|
|
return getRoot(node.parentNode);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* Finds the offset parent common to the two provided nodes
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element1
|
|
* @argument {Element} element2
|
|
* @returns {Element} common offset parent
|
|
*/
|
|
function findCommonOffsetParent(element1, element2) {
|
|
// This check is needed to avoid errors in case one of the elements isn't defined for any reason
|
|
if (!element1 || !element1.nodeType || !element2 || !element2.nodeType) {
|
|
return document.documentElement;
|
|
}
|
|
|
|
// Here we make sure to give as "start" the element that comes first in the DOM
|
|
var order = element1.compareDocumentPosition(element2) & Node.DOCUMENT_POSITION_FOLLOWING;
|
|
var start = order ? element1 : element2;
|
|
var end = order ? element2 : element1;
|
|
|
|
// Get common ancestor container
|
|
var range = document.createRange();
|
|
range.setStart(start, 0);
|
|
range.setEnd(end, 0);
|
|
var commonAncestorContainer = range.commonAncestorContainer;
|
|
|
|
// Both nodes are inside #document
|
|
|
|
if (element1 !== commonAncestorContainer && element2 !== commonAncestorContainer || start.contains(end)) {
|
|
if (isOffsetContainer(commonAncestorContainer)) {
|
|
return commonAncestorContainer;
|
|
}
|
|
|
|
return getOffsetParent(commonAncestorContainer);
|
|
}
|
|
|
|
// one of the nodes is inside shadowDOM, find which one
|
|
var element1root = getRoot(element1);
|
|
if (element1root.host) {
|
|
return findCommonOffsetParent(element1root.host, element2);
|
|
} else {
|
|
return findCommonOffsetParent(element1, getRoot(element2).host);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the scroll value of the given element in the given side (top and left)
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @argument {String} side `top` or `left`
|
|
* @returns {number} amount of scrolled pixels
|
|
*/
|
|
function getScroll(element) {
|
|
var side = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'top';
|
|
|
|
var upperSide = side === 'top' ? 'scrollTop' : 'scrollLeft';
|
|
var nodeName = element.nodeName;
|
|
|
|
if (nodeName === 'BODY' || nodeName === 'HTML') {
|
|
var html = element.ownerDocument.documentElement;
|
|
var scrollingElement = element.ownerDocument.scrollingElement || html;
|
|
return scrollingElement[upperSide];
|
|
}
|
|
|
|
return element[upperSide];
|
|
}
|
|
|
|
/*
|
|
* Sum or subtract the element scroll values (left and top) from a given rect object
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {Object} rect - Rect object you want to change
|
|
* @param {HTMLElement} element - The element from the function reads the scroll values
|
|
* @param {Boolean} subtract - set to true if you want to subtract the scroll values
|
|
* @return {Object} rect - The modifier rect object
|
|
*/
|
|
function includeScroll(rect, element) {
|
|
var subtract = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
|
|
var scrollTop = getScroll(element, 'top');
|
|
var scrollLeft = getScroll(element, 'left');
|
|
var modifier = subtract ? -1 : 1;
|
|
rect.top += scrollTop * modifier;
|
|
rect.bottom += scrollTop * modifier;
|
|
rect.left += scrollLeft * modifier;
|
|
rect.right += scrollLeft * modifier;
|
|
return rect;
|
|
}
|
|
|
|
/*
|
|
* Helper to detect borders of a given element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {CSSStyleDeclaration} styles
|
|
* Result of `getStyleComputedProperty` on the given element
|
|
* @param {String} axis - `x` or `y`
|
|
* @return {number} borders - The borders size of the given axis
|
|
*/
|
|
|
|
function getBordersSize(styles, axis) {
|
|
var sideA = axis === 'x' ? 'Left' : 'Top';
|
|
var sideB = sideA === 'Left' ? 'Right' : 'Bottom';
|
|
|
|
return parseFloat(styles['border' + sideA + 'Width'], 10) + parseFloat(styles['border' + sideB + 'Width'], 10);
|
|
}
|
|
|
|
function getSize(axis, body, html, computedStyle) {
|
|
return Math.max(body['offset' + axis], body['scroll' + axis], html['client' + axis], html['offset' + axis], html['scroll' + axis], isIE(10) ? html['offset' + axis] + computedStyle['margin' + (axis === 'Height' ? 'Top' : 'Left')] + computedStyle['margin' + (axis === 'Height' ? 'Bottom' : 'Right')] : 0);
|
|
}
|
|
|
|
function getWindowSizes() {
|
|
var body = document.body;
|
|
var html = document.documentElement;
|
|
var computedStyle = isIE(10) && getComputedStyle(html);
|
|
|
|
return {
|
|
height: getSize('Height', body, html, computedStyle),
|
|
width: getSize('Width', body, html, computedStyle)
|
|
};
|
|
}
|
|
|
|
var _extends = Object.assign || function (target) {
|
|
for (var i = 1; i < arguments.length; i++) {
|
|
var source = arguments[i];
|
|
|
|
for (var key in source) {
|
|
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
target[key] = source[key];
|
|
}
|
|
}
|
|
}
|
|
|
|
return target;
|
|
};
|
|
|
|
/**
|
|
* Given element offsets, generate an output similar to getBoundingClientRect
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Object} offsets
|
|
* @returns {Object} ClientRect like output
|
|
*/
|
|
function getClientRect(offsets) {
|
|
return _extends({}, offsets, {
|
|
right: offsets.left + offsets.width,
|
|
bottom: offsets.top + offsets.height
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get bounding client rect of given element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {HTMLElement} element
|
|
* @return {Object} client rect
|
|
*/
|
|
function getBoundingClientRect(element) {
|
|
var rect = {};
|
|
|
|
// IE10 10 FIX: Please, don't ask, the element isn't
|
|
// considered in DOM in some circumstances...
|
|
// This isn't reproducible in IE10 compatibility mode of IE11
|
|
try {
|
|
if (isIE(10)) {
|
|
rect = element.getBoundingClientRect();
|
|
var scrollTop = getScroll(element, 'top');
|
|
var scrollLeft = getScroll(element, 'left');
|
|
rect.top += scrollTop;
|
|
rect.left += scrollLeft;
|
|
rect.bottom += scrollTop;
|
|
rect.right += scrollLeft;
|
|
} else {
|
|
rect = element.getBoundingClientRect();
|
|
}
|
|
} catch (e) {}
|
|
|
|
var result = {
|
|
left: rect.left,
|
|
top: rect.top,
|
|
width: rect.right - rect.left,
|
|
height: rect.bottom - rect.top
|
|
};
|
|
|
|
// subtract scrollbar size from sizes
|
|
var sizes = element.nodeName === 'HTML' ? getWindowSizes() : {};
|
|
var width = sizes.width || element.clientWidth || result.right - result.left;
|
|
var height = sizes.height || element.clientHeight || result.bottom - result.top;
|
|
|
|
var horizScrollbar = element.offsetWidth - width;
|
|
var vertScrollbar = element.offsetHeight - height;
|
|
|
|
// if an hypothetical scrollbar is detected, we must be sure it's not a `border`
|
|
// we make this check conditional for performance reasons
|
|
if (horizScrollbar || vertScrollbar) {
|
|
var styles = getStyleComputedProperty(element);
|
|
horizScrollbar -= getBordersSize(styles, 'x');
|
|
vertScrollbar -= getBordersSize(styles, 'y');
|
|
|
|
result.width -= horizScrollbar;
|
|
result.height -= vertScrollbar;
|
|
}
|
|
|
|
return getClientRect(result);
|
|
}
|
|
|
|
function getOffsetRectRelativeToArbitraryNode(children, parent) {
|
|
var fixedPosition = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
|
|
var isIE10 = isIE(10);
|
|
var isHTML = parent.nodeName === 'HTML';
|
|
var childrenRect = getBoundingClientRect(children);
|
|
var parentRect = getBoundingClientRect(parent);
|
|
var scrollParent = getScrollParent(children);
|
|
|
|
var styles = getStyleComputedProperty(parent);
|
|
var borderTopWidth = parseFloat(styles.borderTopWidth, 10);
|
|
var borderLeftWidth = parseFloat(styles.borderLeftWidth, 10);
|
|
|
|
// In cases where the parent is fixed, we must ignore negative scroll in offset calc
|
|
if (fixedPosition && parent.nodeName === 'HTML') {
|
|
parentRect.top = Math.max(parentRect.top, 0);
|
|
parentRect.left = Math.max(parentRect.left, 0);
|
|
}
|
|
var offsets = getClientRect({
|
|
top: childrenRect.top - parentRect.top - borderTopWidth,
|
|
left: childrenRect.left - parentRect.left - borderLeftWidth,
|
|
width: childrenRect.width,
|
|
height: childrenRect.height
|
|
});
|
|
offsets.marginTop = 0;
|
|
offsets.marginLeft = 0;
|
|
|
|
// Subtract margins of documentElement in case it's being used as parent
|
|
// we do this only on HTML because it's the only element that behaves
|
|
// differently when margins are applied to it. The margins are included in
|
|
// the box of the documentElement, in the other cases not.
|
|
if (!isIE10 && isHTML) {
|
|
var marginTop = parseFloat(styles.marginTop, 10);
|
|
var marginLeft = parseFloat(styles.marginLeft, 10);
|
|
|
|
offsets.top -= borderTopWidth - marginTop;
|
|
offsets.bottom -= borderTopWidth - marginTop;
|
|
offsets.left -= borderLeftWidth - marginLeft;
|
|
offsets.right -= borderLeftWidth - marginLeft;
|
|
|
|
// Attach marginTop and marginLeft because in some circumstances we may need them
|
|
offsets.marginTop = marginTop;
|
|
offsets.marginLeft = marginLeft;
|
|
}
|
|
|
|
if (isIE10 && !fixedPosition ? parent.contains(scrollParent) : parent === scrollParent && scrollParent.nodeName !== 'BODY') {
|
|
offsets = includeScroll(offsets, parent);
|
|
}
|
|
|
|
return offsets;
|
|
}
|
|
|
|
function getViewportOffsetRectRelativeToArtbitraryNode(element) {
|
|
var excludeScroll = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
|
|
|
var html = element.ownerDocument.documentElement;
|
|
var relativeOffset = getOffsetRectRelativeToArbitraryNode(element, html);
|
|
var width = Math.max(html.clientWidth, window.innerWidth || 0);
|
|
var height = Math.max(html.clientHeight, window.innerHeight || 0);
|
|
|
|
var scrollTop = !excludeScroll ? getScroll(html) : 0;
|
|
var scrollLeft = !excludeScroll ? getScroll(html, 'left') : 0;
|
|
|
|
var offset = {
|
|
top: scrollTop - relativeOffset.top + relativeOffset.marginTop,
|
|
left: scrollLeft - relativeOffset.left + relativeOffset.marginLeft,
|
|
width: width,
|
|
height: height
|
|
};
|
|
|
|
return getClientRect(offset);
|
|
}
|
|
|
|
/**
|
|
* Check if the given element is fixed or is inside a fixed parent
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @argument {Element} customContainer
|
|
* @returns {Boolean} answer to "isFixed?"
|
|
*/
|
|
function isFixed(element) {
|
|
var nodeName = element.nodeName;
|
|
if (nodeName === 'BODY' || nodeName === 'HTML') {
|
|
return false;
|
|
}
|
|
if (getStyleComputedProperty(element, 'position') === 'fixed') {
|
|
return true;
|
|
}
|
|
return isFixed(getParentNode(element));
|
|
}
|
|
|
|
/**
|
|
* Finds the first parent of an element that has a transformed property defined
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @returns {Element} first transformed parent or documentElement
|
|
*/
|
|
|
|
function getFixedPositionOffsetParent(element) {
|
|
// This check is needed to avoid errors in case one of the elements isn't defined for any reason
|
|
if (!element || !element.parentElement || isIE()) {
|
|
return document.documentElement;
|
|
}
|
|
var el = element.parentElement;
|
|
while (el && getStyleComputedProperty(el, 'transform') === 'none') {
|
|
el = el.parentElement;
|
|
}
|
|
return el || document.documentElement;
|
|
}
|
|
|
|
/**
|
|
* Computed the boundaries limits and return them
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {HTMLElement} popper
|
|
* @param {HTMLElement} reference
|
|
* @param {number} padding
|
|
* @param {HTMLElement} boundariesElement - Element used to define the boundaries
|
|
* @param {Boolean} fixedPosition - Is in fixed position mode
|
|
* @returns {Object} Coordinates of the boundaries
|
|
*/
|
|
function getBoundaries(popper, reference, padding, boundariesElement) {
|
|
var fixedPosition = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
|
|
|
|
// NOTE: 1 DOM access here
|
|
|
|
var boundaries = { top: 0, left: 0 };
|
|
var offsetParent = fixedPosition ? getFixedPositionOffsetParent(popper) : findCommonOffsetParent(popper, reference);
|
|
|
|
// Handle viewport case
|
|
if (boundariesElement === 'viewport') {
|
|
boundaries = getViewportOffsetRectRelativeToArtbitraryNode(offsetParent, fixedPosition);
|
|
} else {
|
|
// Handle other cases based on DOM element used as boundaries
|
|
var boundariesNode = void 0;
|
|
if (boundariesElement === 'scrollParent') {
|
|
boundariesNode = getScrollParent(getParentNode(reference));
|
|
if (boundariesNode.nodeName === 'BODY') {
|
|
boundariesNode = popper.ownerDocument.documentElement;
|
|
}
|
|
} else if (boundariesElement === 'window') {
|
|
boundariesNode = popper.ownerDocument.documentElement;
|
|
} else {
|
|
boundariesNode = boundariesElement;
|
|
}
|
|
|
|
var offsets = getOffsetRectRelativeToArbitraryNode(boundariesNode, offsetParent, fixedPosition);
|
|
|
|
// In case of HTML, we need a different computation
|
|
if (boundariesNode.nodeName === 'HTML' && !isFixed(offsetParent)) {
|
|
var _getWindowSizes = getWindowSizes(),
|
|
height = _getWindowSizes.height,
|
|
width = _getWindowSizes.width;
|
|
|
|
boundaries.top += offsets.top - offsets.marginTop;
|
|
boundaries.bottom = height + offsets.top;
|
|
boundaries.left += offsets.left - offsets.marginLeft;
|
|
boundaries.right = width + offsets.left;
|
|
} else {
|
|
// for all the other DOM elements, this one is good
|
|
boundaries = offsets;
|
|
}
|
|
}
|
|
|
|
// Add paddings
|
|
boundaries.left += padding;
|
|
boundaries.top += padding;
|
|
boundaries.right -= padding;
|
|
boundaries.bottom -= padding;
|
|
|
|
return boundaries;
|
|
}
|
|
|
|
function getArea(_ref) {
|
|
var width = _ref.width,
|
|
height = _ref.height;
|
|
|
|
return width * height;
|
|
}
|
|
|
|
/**
|
|
* Utility used to transform the `auto` placement to the placement with more
|
|
* available space.
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Object} data - The data object generated by update method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {Object} The data object, properly modified
|
|
*/
|
|
function computeAutoPlacement(placement, refRect, popper, reference, boundariesElement) {
|
|
var padding = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 0;
|
|
|
|
if (placement.indexOf('auto') === -1) {
|
|
return placement;
|
|
}
|
|
|
|
var boundaries = getBoundaries(popper, reference, padding, boundariesElement);
|
|
|
|
var rects = {
|
|
top: {
|
|
width: boundaries.width,
|
|
height: refRect.top - boundaries.top
|
|
},
|
|
right: {
|
|
width: boundaries.right - refRect.right,
|
|
height: boundaries.height
|
|
},
|
|
bottom: {
|
|
width: boundaries.width,
|
|
height: boundaries.bottom - refRect.bottom
|
|
},
|
|
left: {
|
|
width: refRect.left - boundaries.left,
|
|
height: boundaries.height
|
|
}
|
|
};
|
|
|
|
var sortedAreas = Object.keys(rects).map(function (key) {
|
|
return _extends({
|
|
key: key
|
|
}, rects[key], {
|
|
area: getArea(rects[key])
|
|
});
|
|
}).sort(function (a, b) {
|
|
return b.area - a.area;
|
|
});
|
|
|
|
var filteredAreas = sortedAreas.filter(function (_ref2) {
|
|
var width = _ref2.width,
|
|
height = _ref2.height;
|
|
return width >= popper.clientWidth && height >= popper.clientHeight;
|
|
});
|
|
|
|
var computedPlacement = filteredAreas.length > 0 ? filteredAreas[0].key : sortedAreas[0].key;
|
|
|
|
var variation = placement.split('-')[1];
|
|
|
|
return computedPlacement + (variation ? '-' + variation : '');
|
|
}
|
|
|
|
var isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
var longerTimeoutBrowsers = ['Edge', 'Trident', 'Firefox'];
|
|
var timeoutDuration = 0;
|
|
for (var i = 0; i < longerTimeoutBrowsers.length; i += 1) {
|
|
if (isBrowser && navigator.userAgent.indexOf(longerTimeoutBrowsers[i]) >= 0) {
|
|
timeoutDuration = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
function microtaskDebounce(fn) {
|
|
var called = false;
|
|
return function () {
|
|
if (called) {
|
|
return;
|
|
}
|
|
called = true;
|
|
window.Promise.resolve().then(function () {
|
|
called = false;
|
|
fn();
|
|
});
|
|
};
|
|
}
|
|
|
|
function taskDebounce(fn) {
|
|
var scheduled = false;
|
|
return function () {
|
|
if (!scheduled) {
|
|
scheduled = true;
|
|
setTimeout(function () {
|
|
scheduled = false;
|
|
fn();
|
|
}, timeoutDuration);
|
|
}
|
|
};
|
|
}
|
|
|
|
var supportsMicroTasks = isBrowser && window.Promise;
|
|
|
|
/**
|
|
* Create a debounced version of a method, that's asynchronously deferred
|
|
* but called in the minimum time possible.
|
|
*
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Function} fn
|
|
* @returns {Function}
|
|
*/
|
|
var debounce = supportsMicroTasks ? microtaskDebounce : taskDebounce;
|
|
|
|
/**
|
|
* Mimics the `find` method of Array
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Array} arr
|
|
* @argument prop
|
|
* @argument value
|
|
* @returns index or -1
|
|
*/
|
|
function find(arr, check) {
|
|
// use native find if supported
|
|
if (Array.prototype.find) {
|
|
return arr.find(check);
|
|
}
|
|
|
|
// use `filter` to obtain the same behavior of `find`
|
|
return arr.filter(check)[0];
|
|
}
|
|
|
|
/**
|
|
* Return the index of the matching object
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Array} arr
|
|
* @argument prop
|
|
* @argument value
|
|
* @returns index or -1
|
|
*/
|
|
function findIndex(arr, prop, value) {
|
|
// use native findIndex if supported
|
|
if (Array.prototype.findIndex) {
|
|
return arr.findIndex(function (cur) {
|
|
return cur[prop] === value;
|
|
});
|
|
}
|
|
|
|
// use `find` + `indexOf` if `findIndex` isn't supported
|
|
var match = find(arr, function (obj) {
|
|
return obj[prop] === value;
|
|
});
|
|
return arr.indexOf(match);
|
|
}
|
|
|
|
/**
|
|
* Get the position of the given element, relative to its offset parent
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {Element} element
|
|
* @return {Object} position - Coordinates of the element and its `scrollTop`
|
|
*/
|
|
function getOffsetRect(element) {
|
|
var elementRect = void 0;
|
|
if (element.nodeName === 'HTML') {
|
|
var _getWindowSizes = getWindowSizes(),
|
|
width = _getWindowSizes.width,
|
|
height = _getWindowSizes.height;
|
|
|
|
elementRect = {
|
|
width: width,
|
|
height: height,
|
|
left: 0,
|
|
top: 0
|
|
};
|
|
} else {
|
|
elementRect = {
|
|
width: element.offsetWidth,
|
|
height: element.offsetHeight,
|
|
left: element.offsetLeft,
|
|
top: element.offsetTop
|
|
};
|
|
}
|
|
|
|
// position
|
|
return getClientRect(elementRect);
|
|
}
|
|
|
|
/**
|
|
* Get the outer sizes of the given element (offset size + margins)
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @returns {Object} object containing width and height properties
|
|
*/
|
|
function getOuterSizes(element) {
|
|
var styles = getComputedStyle(element);
|
|
var x = parseFloat(styles.marginTop) + parseFloat(styles.marginBottom);
|
|
var y = parseFloat(styles.marginLeft) + parseFloat(styles.marginRight);
|
|
var result = {
|
|
width: element.offsetWidth + y,
|
|
height: element.offsetHeight + x
|
|
};
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Get the opposite placement of the given one
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {String} placement
|
|
* @returns {String} flipped placement
|
|
*/
|
|
function getOppositePlacement(placement) {
|
|
var hash = { left: 'right', right: 'left', bottom: 'top', top: 'bottom' };
|
|
return placement.replace(/left|right|bottom|top/g, function (matched) {
|
|
return hash[matched];
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get offsets to the popper
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {Object} position - CSS position the Popper will get applied
|
|
* @param {HTMLElement} popper - the popper element
|
|
* @param {Object} referenceOffsets - the reference offsets (the popper will be relative to this)
|
|
* @param {String} placement - one of the valid placement options
|
|
* @returns {Object} popperOffsets - An object containing the offsets which will be applied to the popper
|
|
*/
|
|
function getPopperOffsets(popper, referenceOffsets, placement) {
|
|
placement = placement.split('-')[0];
|
|
|
|
// Get popper node sizes
|
|
var popperRect = getOuterSizes(popper);
|
|
|
|
// Add position, width and height to our offsets object
|
|
var popperOffsets = {
|
|
width: popperRect.width,
|
|
height: popperRect.height
|
|
};
|
|
|
|
// depending by the popper placement we have to compute its offsets slightly differently
|
|
var isHoriz = ['right', 'left'].indexOf(placement) !== -1;
|
|
var mainSide = isHoriz ? 'top' : 'left';
|
|
var secondarySide = isHoriz ? 'left' : 'top';
|
|
var measurement = isHoriz ? 'height' : 'width';
|
|
var secondaryMeasurement = !isHoriz ? 'height' : 'width';
|
|
|
|
popperOffsets[mainSide] = referenceOffsets[mainSide] + referenceOffsets[measurement] / 2 - popperRect[measurement] / 2;
|
|
if (placement === secondarySide) {
|
|
popperOffsets[secondarySide] = referenceOffsets[secondarySide] - popperRect[secondaryMeasurement];
|
|
} else {
|
|
popperOffsets[secondarySide] = referenceOffsets[getOppositePlacement(secondarySide)];
|
|
}
|
|
|
|
return popperOffsets;
|
|
}
|
|
|
|
/**
|
|
* Get offsets to the reference element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {Object} state
|
|
* @param {Element} popper - the popper element
|
|
* @param {Element} reference - the reference element (the popper will be relative to this)
|
|
* @param {Element} fixedPosition - is in fixed position mode
|
|
* @returns {Object} An object containing the offsets which will be applied to the popper
|
|
*/
|
|
function getReferenceOffsets(state, popper, reference) {
|
|
var fixedPosition = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
|
|
|
|
var commonOffsetParent = fixedPosition ? getFixedPositionOffsetParent(popper) : findCommonOffsetParent(popper, reference);
|
|
return getOffsetRectRelativeToArbitraryNode(reference, commonOffsetParent, fixedPosition);
|
|
}
|
|
|
|
/**
|
|
* Get the prefixed supported property name
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {String} property (camelCase)
|
|
* @returns {String} prefixed property (camelCase or PascalCase, depending on the vendor prefix)
|
|
*/
|
|
function getSupportedPropertyName(property) {
|
|
var prefixes = [false, 'ms', 'Webkit', 'Moz', 'O'];
|
|
var upperProp = property.charAt(0).toUpperCase() + property.slice(1);
|
|
|
|
for (var i = 0; i < prefixes.length; i++) {
|
|
var prefix = prefixes[i];
|
|
var toCheck = prefix ? '' + prefix + upperProp : property;
|
|
if (typeof document.body.style[toCheck] !== 'undefined') {
|
|
return toCheck;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Check if the given variable is a function
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Any} functionToCheck - variable to check
|
|
* @returns {Boolean} answer to: is a function?
|
|
*/
|
|
function isFunction(functionToCheck) {
|
|
var getType = {};
|
|
return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
|
|
}
|
|
|
|
/**
|
|
* Helper used to know if the given modifier is enabled.
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @returns {Boolean}
|
|
*/
|
|
function isModifierEnabled(modifiers, modifierName) {
|
|
return modifiers.some(function (_ref) {
|
|
var name = _ref.name,
|
|
enabled = _ref.enabled;
|
|
return enabled && name === modifierName;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Helper used to know if the given modifier depends from another one.<br />
|
|
* It checks if the needed modifier is listed and enabled.
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {Array} modifiers - list of modifiers
|
|
* @param {String} requestingName - name of requesting modifier
|
|
* @param {String} requestedName - name of requested modifier
|
|
* @returns {Boolean}
|
|
*/
|
|
function isModifierRequired(modifiers, requestingName, requestedName) {
|
|
var requesting = find(modifiers, function (_ref) {
|
|
var name = _ref.name;
|
|
return name === requestingName;
|
|
});
|
|
|
|
var isRequired = !!requesting && modifiers.some(function (modifier) {
|
|
return modifier.name === requestedName && modifier.enabled && modifier.order < requesting.order;
|
|
});
|
|
|
|
if (!isRequired) {
|
|
var _requesting = '`' + requestingName + '`';
|
|
var requested = '`' + requestedName + '`';
|
|
console.warn(requested + ' modifier is required by ' + _requesting + ' modifier in order to work, be sure to include it before ' + _requesting + '!');
|
|
}
|
|
return isRequired;
|
|
}
|
|
|
|
/**
|
|
* Tells if a given input is a number
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {*} input to check
|
|
* @return {Boolean}
|
|
*/
|
|
function isNumeric(n) {
|
|
return n !== '' && !isNaN(parseFloat(n)) && isFinite(n);
|
|
}
|
|
|
|
/**
|
|
* Get the window associated with the element
|
|
* @argument {Element} element
|
|
* @returns {Window}
|
|
*/
|
|
function getWindow(element) {
|
|
var ownerDocument = element.ownerDocument;
|
|
return ownerDocument ? ownerDocument.defaultView : window;
|
|
}
|
|
|
|
/**
|
|
* Remove event listeners used to update the popper position
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @private
|
|
*/
|
|
function removeEventListeners(reference, state) {
|
|
// Remove resize event listener on window
|
|
getWindow(reference).removeEventListener('resize', state.updateBound);
|
|
|
|
// Remove scroll event listener on scroll parents
|
|
state.scrollParents.forEach(function (target) {
|
|
target.removeEventListener('scroll', state.updateBound);
|
|
});
|
|
|
|
// Reset state
|
|
state.updateBound = null;
|
|
state.scrollParents = [];
|
|
state.scrollElement = null;
|
|
state.eventsEnabled = false;
|
|
return state;
|
|
}
|
|
|
|
/**
|
|
* Loop trough the list of modifiers and run them in order,
|
|
* each of them will then edit the data object.
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {dataObject} data
|
|
* @param {Array} modifiers
|
|
* @param {String} ends - Optional modifier name used as stopper
|
|
* @returns {dataObject}
|
|
*/
|
|
function runModifiers(modifiers, data, ends) {
|
|
var modifiersToRun = ends === undefined ? modifiers : modifiers.slice(0, findIndex(modifiers, 'name', ends));
|
|
|
|
modifiersToRun.forEach(function (modifier) {
|
|
if (modifier['function']) {
|
|
// eslint-disable-line dot-notation
|
|
console.warn('`modifier.function` is deprecated, use `modifier.fn`!');
|
|
}
|
|
var fn = modifier['function'] || modifier.fn; // eslint-disable-line dot-notation
|
|
if (modifier.enabled && isFunction(fn)) {
|
|
// Add properties to offsets to make them a complete clientRect object
|
|
// we do this before each modifier to make sure the previous one doesn't
|
|
// mess with these values
|
|
data.offsets.popper = getClientRect(data.offsets.popper);
|
|
data.offsets.reference = getClientRect(data.offsets.reference);
|
|
|
|
data = fn(data, modifier);
|
|
}
|
|
});
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Set the attributes to the given popper
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element - Element to apply the attributes to
|
|
* @argument {Object} styles
|
|
* Object with a list of properties and values which will be applied to the element
|
|
*/
|
|
function setAttributes(element, attributes) {
|
|
Object.keys(attributes).forEach(function (prop) {
|
|
var value = attributes[prop];
|
|
if (value !== false) {
|
|
element.setAttribute(prop, attributes[prop]);
|
|
} else {
|
|
element.removeAttribute(prop);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Set the style to the given popper
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element - Element to apply the style to
|
|
* @argument {Object} styles
|
|
* Object with a list of properties and values which will be applied to the element
|
|
*/
|
|
function setStyles(element, styles) {
|
|
Object.keys(styles).forEach(function (prop) {
|
|
var unit = '';
|
|
// add unit if the value is numeric and is one of the following
|
|
if (['width', 'height', 'top', 'right', 'bottom', 'left'].indexOf(prop) !== -1 && isNumeric(styles[prop])) {
|
|
unit = 'px';
|
|
}
|
|
element.style[prop] = styles[prop] + unit;
|
|
});
|
|
}
|
|
|
|
function attachToScrollParents(scrollParent, event, callback, scrollParents) {
|
|
var isBody = scrollParent.nodeName === 'BODY';
|
|
var target = isBody ? scrollParent.ownerDocument.defaultView : scrollParent;
|
|
target.addEventListener(event, callback, { passive: true });
|
|
|
|
if (!isBody) {
|
|
attachToScrollParents(getScrollParent(target.parentNode), event, callback, scrollParents);
|
|
}
|
|
scrollParents.push(target);
|
|
}
|
|
|
|
/**
|
|
* Setup needed event listeners used to update the popper position
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @private
|
|
*/
|
|
function setupEventListeners(reference, options, state, updateBound) {
|
|
// Resize event listener on window
|
|
state.updateBound = updateBound;
|
|
getWindow(reference).addEventListener('resize', state.updateBound, { passive: true });
|
|
|
|
// Scroll event listener on scroll parents
|
|
var scrollElement = getScrollParent(reference);
|
|
attachToScrollParents(scrollElement, 'scroll', state.updateBound, state.scrollParents);
|
|
state.scrollElement = scrollElement;
|
|
state.eventsEnabled = true;
|
|
|
|
return state;
|
|
}
|
|
|
|
// This is here just for backward compatibility with versions lower than v1.10.3
|
|
// you should import the utilities using named exports, if you want them all use:
|
|
// ```
|
|
// import * as PopperUtils from 'popper-utils';
|
|
// ```
|
|
// The default export will be removed in the next major version.
|
|
var index = {
|
|
computeAutoPlacement: computeAutoPlacement,
|
|
debounce: debounce,
|
|
findIndex: findIndex,
|
|
getBordersSize: getBordersSize,
|
|
getBoundaries: getBoundaries,
|
|
getBoundingClientRect: getBoundingClientRect,
|
|
getClientRect: getClientRect,
|
|
getOffsetParent: getOffsetParent,
|
|
getOffsetRect: getOffsetRect,
|
|
getOffsetRectRelativeToArbitraryNode: getOffsetRectRelativeToArbitraryNode,
|
|
getOuterSizes: getOuterSizes,
|
|
getParentNode: getParentNode,
|
|
getPopperOffsets: getPopperOffsets,
|
|
getReferenceOffsets: getReferenceOffsets,
|
|
getScroll: getScroll,
|
|
getScrollParent: getScrollParent,
|
|
getStyleComputedProperty: getStyleComputedProperty,
|
|
getSupportedPropertyName: getSupportedPropertyName,
|
|
getWindowSizes: getWindowSizes,
|
|
isFixed: isFixed,
|
|
isFunction: isFunction,
|
|
isModifierEnabled: isModifierEnabled,
|
|
isModifierRequired: isModifierRequired,
|
|
isNumeric: isNumeric,
|
|
removeEventListeners: removeEventListeners,
|
|
runModifiers: runModifiers,
|
|
setAttributes: setAttributes,
|
|
setStyles: setStyles,
|
|
setupEventListeners: setupEventListeners
|
|
};
|
|
|
|
exports.computeAutoPlacement = computeAutoPlacement;
|
|
exports.debounce = debounce;
|
|
exports.findIndex = findIndex;
|
|
exports.getBordersSize = getBordersSize;
|
|
exports.getBoundaries = getBoundaries;
|
|
exports.getBoundingClientRect = getBoundingClientRect;
|
|
exports.getClientRect = getClientRect;
|
|
exports.getOffsetParent = getOffsetParent;
|
|
exports.getOffsetRect = getOffsetRect;
|
|
exports.getOffsetRectRelativeToArbitraryNode = getOffsetRectRelativeToArbitraryNode;
|
|
exports.getOuterSizes = getOuterSizes;
|
|
exports.getParentNode = getParentNode;
|
|
exports.getPopperOffsets = getPopperOffsets;
|
|
exports.getReferenceOffsets = getReferenceOffsets;
|
|
exports.getScroll = getScroll;
|
|
exports.getScrollParent = getScrollParent;
|
|
exports.getStyleComputedProperty = getStyleComputedProperty;
|
|
exports.getSupportedPropertyName = getSupportedPropertyName;
|
|
exports.getWindowSizes = getWindowSizes;
|
|
exports.isFixed = isFixed;
|
|
exports.isFunction = isFunction;
|
|
exports.isModifierEnabled = isModifierEnabled;
|
|
exports.isModifierRequired = isModifierRequired;
|
|
exports.isNumeric = isNumeric;
|
|
exports.removeEventListeners = removeEventListeners;
|
|
exports.runModifiers = runModifiers;
|
|
exports.setAttributes = setAttributes;
|
|
exports.setStyles = setStyles;
|
|
exports.setupEventListeners = setupEventListeners;
|
|
exports['default'] = index;
|
|
|
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
|
})));
|
|
//# sourceMappingURL=popper-utils.js.map
|
|
|
|
/*!
|
|
* Select2 4.0.5
|
|
* https://select2.github.io
|
|
*
|
|
* Released under the MIT license
|
|
* https://github.com/select2/select2/blob/master/LICENSE.md
|
|
*/
|
|
(function (factory) {
|
|
if (typeof define === 'function' && define.amd) {
|
|
// AMD. Register as an anonymous module.
|
|
define(['jquery'], factory);
|
|
} else if (typeof module === 'object' && module.exports) {
|
|
// Node/CommonJS
|
|
module.exports = function (root, jQuery) {
|
|
if (jQuery === undefined) {
|
|
// require('jQuery') returns a factory that requires window to
|
|
// build a jQuery instance, we normalize how we use modules
|
|
// that require this pattern but the window provided is a noop
|
|
// if it's defined (how jquery works)
|
|
if (typeof window !== 'undefined') {
|
|
jQuery = require('jquery');
|
|
}
|
|
else {
|
|
jQuery = require('jquery')(root);
|
|
}
|
|
}
|
|
factory(jQuery);
|
|
return jQuery;
|
|
};
|
|
} else {
|
|
// Browser globals
|
|
factory(jQuery);
|
|
}
|
|
} (function (jQuery) {
|
|
// This is needed so we can catch the AMD loader configuration and use it
|
|
// The inner file should be wrapped (by `banner.start.js`) in a function that
|
|
// returns the AMD loader references.
|
|
var S2 =(function () {
|
|
// Restore the Select2 AMD loader so it can be used
|
|
// Needed mostly in the language files, where the loader is not inserted
|
|
if (jQuery && jQuery.fn && jQuery.fn.select2 && jQuery.fn.select2.amd) {
|
|
var S2 = jQuery.fn.select2.amd;
|
|
}
|
|
var S2;(function () { if (!S2 || !S2.requirejs) {
|
|
if (!S2) { S2 = {}; } else { require = S2; }
|
|
/**
|
|
* @license almond 0.3.3 Copyright jQuery Foundation and other contributors.
|
|
* Released under MIT license, http://github.com/requirejs/almond/LICENSE
|
|
*/
|
|
//Going sloppy to avoid 'use strict' string cost, but strict practices should
|
|
//be followed.
|
|
/*global setTimeout: false */
|
|
|
|
var requirejs, require, define;
|
|
(function (undef) {
|
|
var main, req, makeMap, handlers,
|
|
defined = {},
|
|
waiting = {},
|
|
config = {},
|
|
defining = {},
|
|
hasOwn = Object.prototype.hasOwnProperty,
|
|
aps = [].slice,
|
|
jsSuffixRegExp = /\.js$/;
|
|
|
|
function hasProp(obj, prop) {
|
|
return hasOwn.call(obj, prop);
|
|
}
|
|
|
|
/**
|
|
* Given a relative module name, like ./something, normalize it to
|
|
* a real name that can be mapped to a path.
|
|
* @param {String} name the relative name
|
|
* @param {String} baseName a real name that the name arg is relative
|
|
* to.
|
|
* @returns {String} normalized name
|
|
*/
|
|
function normalize(name, baseName) {
|
|
var nameParts, nameSegment, mapValue, foundMap, lastIndex,
|
|
foundI, foundStarMap, starI, i, j, part, normalizedBaseParts,
|
|
baseParts = baseName && baseName.split("/"),
|
|
map = config.map,
|
|
starMap = (map && map['*']) || {};
|
|
|
|
//Adjust any relative paths.
|
|
if (name) {
|
|
name = name.split('/');
|
|
lastIndex = name.length - 1;
|
|
|
|
// If wanting node ID compatibility, strip .js from end
|
|
// of IDs. Have to do this here, and not in nameToUrl
|
|
// because node allows either .js or non .js to map
|
|
// to same file.
|
|
if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
|
|
name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
|
|
}
|
|
|
|
// Starts with a '.' so need the baseName
|
|
if (name[0].charAt(0) === '.' && baseParts) {
|
|
//Convert baseName to array, and lop off the last part,
|
|
//so that . matches that 'directory' and not name of the baseName's
|
|
//module. For instance, baseName of 'one/two/three', maps to
|
|
//'one/two/three.js', but we want the directory, 'one/two' for
|
|
//this normalization.
|
|
normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
|
|
name = normalizedBaseParts.concat(name);
|
|
}
|
|
|
|
//start trimDots
|
|
for (i = 0; i < name.length; i++) {
|
|
part = name[i];
|
|
if (part === '.') {
|
|
name.splice(i, 1);
|
|
i -= 1;
|
|
} else if (part === '..') {
|
|
// If at the start, or previous value is still ..,
|
|
// keep them so that when converted to a path it may
|
|
// still work when converted to a path, even though
|
|
// as an ID it is less than ideal. In larger point
|
|
// releases, may be better to just kick out an error.
|
|
if (i === 0 || (i === 1 && name[2] === '..') || name[i - 1] === '..') {
|
|
continue;
|
|
} else if (i > 0) {
|
|
name.splice(i - 1, 2);
|
|
i -= 2;
|
|
}
|
|
}
|
|
}
|
|
//end trimDots
|
|
|
|
name = name.join('/');
|
|
}
|
|
|
|
//Apply map config if available.
|
|
if ((baseParts || starMap) && map) {
|
|
nameParts = name.split('/');
|
|
|
|
for (i = nameParts.length; i > 0; i -= 1) {
|
|
nameSegment = nameParts.slice(0, i).join("/");
|
|
|
|
if (baseParts) {
|
|
//Find the longest baseName segment match in the config.
|
|
//So, do joins on the biggest to smallest lengths of baseParts.
|
|
for (j = baseParts.length; j > 0; j -= 1) {
|
|
mapValue = map[baseParts.slice(0, j).join('/')];
|
|
|
|
//baseName segment has config, find if it has one for
|
|
//this name.
|
|
if (mapValue) {
|
|
mapValue = mapValue[nameSegment];
|
|
if (mapValue) {
|
|
//Match, update name to the new value.
|
|
foundMap = mapValue;
|
|
foundI = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (foundMap) {
|
|
break;
|
|
}
|
|
|
|
//Check for a star map match, but just hold on to it,
|
|
//if there is a shorter segment match later in a matching
|
|
//config, then favor over this star map.
|
|
if (!foundStarMap && starMap && starMap[nameSegment]) {
|
|
foundStarMap = starMap[nameSegment];
|
|
starI = i;
|
|
}
|
|
}
|
|
|
|
if (!foundMap && foundStarMap) {
|
|
foundMap = foundStarMap;
|
|
foundI = starI;
|
|
}
|
|
|
|
if (foundMap) {
|
|
nameParts.splice(0, foundI, foundMap);
|
|
name = nameParts.join('/');
|
|
}
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
function makeRequire(relName, forceSync) {
|
|
return function () {
|
|
//A version of a require function that passes a moduleName
|
|
//value for items that may need to
|
|
//look up paths relative to the moduleName
|
|
var args = aps.call(arguments, 0);
|
|
|
|
//If first arg is not require('string'), and there is only
|
|
//one arg, it is the array form without a callback. Insert
|
|
//a null so that the following concat is correct.
|
|
if (typeof args[0] !== 'string' && args.length === 1) {
|
|
args.push(null);
|
|
}
|
|
return req.apply(undef, args.concat([relName, forceSync]));
|
|
};
|
|
}
|
|
|
|
function makeNormalize(relName) {
|
|
return function (name) {
|
|
return normalize(name, relName);
|
|
};
|
|
}
|
|
|
|
function makeLoad(depName) {
|
|
return function (value) {
|
|
defined[depName] = value;
|
|
};
|
|
}
|
|
|
|
function callDep(name) {
|
|
if (hasProp(waiting, name)) {
|
|
var args = waiting[name];
|
|
delete waiting[name];
|
|
defining[name] = true;
|
|
main.apply(undef, args);
|
|
}
|
|
|
|
if (!hasProp(defined, name) && !hasProp(defining, name)) {
|
|
throw new Error('No ' + name);
|
|
}
|
|
return defined[name];
|
|
}
|
|
|
|
//Turns a plugin!resource to [plugin, resource]
|
|
//with the plugin being undefined if the name
|
|
//did not have a plugin prefix.
|
|
function splitPrefix(name) {
|
|
var prefix,
|
|
index = name ? name.indexOf('!') : -1;
|
|
if (index > -1) {
|
|
prefix = name.substring(0, index);
|
|
name = name.substring(index + 1, name.length);
|
|
}
|
|
return [prefix, name];
|
|
}
|
|
|
|
//Creates a parts array for a relName where first part is plugin ID,
|
|
//second part is resource ID. Assumes relName has already been normalized.
|
|
function makeRelParts(relName) {
|
|
return relName ? splitPrefix(relName) : [];
|
|
}
|
|
|
|
/**
|
|
* Makes a name map, normalizing the name, and using a plugin
|
|
* for normalization if necessary. Grabs a ref to plugin
|
|
* too, as an optimization.
|
|
*/
|
|
makeMap = function (name, relParts) {
|
|
var plugin,
|
|
parts = splitPrefix(name),
|
|
prefix = parts[0],
|
|
relResourceName = relParts[1];
|
|
|
|
name = parts[1];
|
|
|
|
if (prefix) {
|
|
prefix = normalize(prefix, relResourceName);
|
|
plugin = callDep(prefix);
|
|
}
|
|
|
|
//Normalize according
|
|
if (prefix) {
|
|
if (plugin && plugin.normalize) {
|
|
name = plugin.normalize(name, makeNormalize(relResourceName));
|
|
} else {
|
|
name = normalize(name, relResourceName);
|
|
}
|
|
} else {
|
|
name = normalize(name, relResourceName);
|
|
parts = splitPrefix(name);
|
|
prefix = parts[0];
|
|
name = parts[1];
|
|
if (prefix) {
|
|
plugin = callDep(prefix);
|
|
}
|
|
}
|
|
|
|
//Using ridiculous property names for space reasons
|
|
return {
|
|
f: prefix ? prefix + '!' + name : name, //fullName
|
|
n: name,
|
|
pr: prefix,
|
|
p: plugin
|
|
};
|
|
};
|
|
|
|
function makeConfig(name) {
|
|
return function () {
|
|
return (config && config.config && config.config[name]) || {};
|
|
};
|
|
}
|
|
|
|
handlers = {
|
|
require: function (name) {
|
|
return makeRequire(name);
|
|
},
|
|
exports: function (name) {
|
|
var e = defined[name];
|
|
if (typeof e !== 'undefined') {
|
|
return e;
|
|
} else {
|
|
return (defined[name] = {});
|
|
}
|
|
},
|
|
module: function (name) {
|
|
return {
|
|
id: name,
|
|
uri: '',
|
|
exports: defined[name],
|
|
config: makeConfig(name)
|
|
};
|
|
}
|
|
};
|
|
|
|
main = function (name, deps, callback, relName) {
|
|
var cjsModule, depName, ret, map, i, relParts,
|
|
args = [],
|
|
callbackType = typeof callback,
|
|
usingExports;
|
|
|
|
//Use name if no relName
|
|
relName = relName || name;
|
|
relParts = makeRelParts(relName);
|
|
|
|
//Call the callback to define the module, if necessary.
|
|
if (callbackType === 'undefined' || callbackType === 'function') {
|
|
//Pull out the defined dependencies and pass the ordered
|
|
//values to the callback.
|
|
//Default to [require, exports, module] if no deps
|
|
deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
|
|
for (i = 0; i < deps.length; i += 1) {
|
|
map = makeMap(deps[i], relParts);
|
|
depName = map.f;
|
|
|
|
//Fast path CommonJS standard dependencies.
|
|
if (depName === "require") {
|
|
args[i] = handlers.require(name);
|
|
} else if (depName === "exports") {
|
|
//CommonJS module spec 1.1
|
|
args[i] = handlers.exports(name);
|
|
usingExports = true;
|
|
} else if (depName === "module") {
|
|
//CommonJS module spec 1.1
|
|
cjsModule = args[i] = handlers.module(name);
|
|
} else if (hasProp(defined, depName) ||
|
|
hasProp(waiting, depName) ||
|
|
hasProp(defining, depName)) {
|
|
args[i] = callDep(depName);
|
|
} else if (map.p) {
|
|
map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {});
|
|
args[i] = defined[depName];
|
|
} else {
|
|
throw new Error(name + ' missing ' + depName);
|
|
}
|
|
}
|
|
|
|
ret = callback ? callback.apply(defined[name], args) : undefined;
|
|
|
|
if (name) {
|
|
//If setting exports via "module" is in play,
|
|
//favor that over return value and exports. After that,
|
|
//favor a non-undefined return value over exports use.
|
|
if (cjsModule && cjsModule.exports !== undef &&
|
|
cjsModule.exports !== defined[name]) {
|
|
defined[name] = cjsModule.exports;
|
|
} else if (ret !== undef || !usingExports) {
|
|
//Use the return value from the function.
|
|
defined[name] = ret;
|
|
}
|
|
}
|
|
} else if (name) {
|
|
//May just be an object definition for the module. Only
|
|
//worry about defining if have a module name.
|
|
defined[name] = callback;
|
|
}
|
|
};
|
|
|
|
requirejs = require = req = function (deps, callback, relName, forceSync, alt) {
|
|
if (typeof deps === "string") {
|
|
if (handlers[deps]) {
|
|
//callback in this case is really relName
|
|
return handlers[deps](callback);
|
|
}
|
|
//Just return the module wanted. In this scenario, the
|
|
//deps arg is the module name, and second arg (if passed)
|
|
//is just the relName.
|
|
//Normalize module name, if it contains . or ..
|
|
return callDep(makeMap(deps, makeRelParts(callback)).f);
|
|
} else if (!deps.splice) {
|
|
//deps is a config object, not an array.
|
|
config = deps;
|
|
if (config.deps) {
|
|
req(config.deps, config.callback);
|
|
}
|
|
if (!callback) {
|
|
return;
|
|
}
|
|
|
|
if (callback.splice) {
|
|
//callback is an array, which means it is a dependency list.
|
|
//Adjust args if there are dependencies
|
|
deps = callback;
|
|
callback = relName;
|
|
relName = null;
|
|
} else {
|
|
deps = undef;
|
|
}
|
|
}
|
|
|
|
//Support require(['a'])
|
|
callback = callback || function () {};
|
|
|
|
//If relName is a function, it is an errback handler,
|
|
//so remove it.
|
|
if (typeof relName === 'function') {
|
|
relName = forceSync;
|
|
forceSync = alt;
|
|
}
|
|
|
|
//Simulate async callback;
|
|
if (forceSync) {
|
|
main(undef, deps, callback, relName);
|
|
} else {
|
|
//Using a non-zero value because of concern for what old browsers
|
|
//do, and latest browsers "upgrade" to 4 if lower value is used:
|
|
//http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout:
|
|
//If want a value immediately, use require('id') instead -- something
|
|
//that works in almond on the global level, but not guaranteed and
|
|
//unlikely to work in other AMD implementations.
|
|
setTimeout(function () {
|
|
main(undef, deps, callback, relName);
|
|
}, 4);
|
|
}
|
|
|
|
return req;
|
|
};
|
|
|
|
/**
|
|
* Just drops the config on the floor, but returns req in case
|
|
* the config return value is used.
|
|
*/
|
|
req.config = function (cfg) {
|
|
return req(cfg);
|
|
};
|
|
|
|
/**
|
|
* Expose module registry for debugging and tooling
|
|
*/
|
|
requirejs._defined = defined;
|
|
|
|
define = function (name, deps, callback) {
|
|
if (typeof name !== 'string') {
|
|
throw new Error('See almond README: incorrect module build, no module name');
|
|
}
|
|
|
|
//This module may not have dependencies
|
|
if (!deps.splice) {
|
|
//deps is not an array, so probably means
|
|
//an object literal or factory function for
|
|
//the value. Adjust args.
|
|
callback = deps;
|
|
deps = [];
|
|
}
|
|
|
|
if (!hasProp(defined, name) && !hasProp(waiting, name)) {
|
|
waiting[name] = [name, deps, callback];
|
|
}
|
|
};
|
|
|
|
define.amd = {
|
|
jQuery: true
|
|
};
|
|
}());
|
|
|
|
S2.requirejs = requirejs;S2.require = require;S2.define = define;
|
|
}
|
|
}());
|
|
S2.define("almond", function(){});
|
|
|
|
/* global jQuery:false, $:false */
|
|
S2.define('jquery',[],function () {
|
|
var _$ = jQuery || $;
|
|
|
|
if (_$ == null && console && console.error) {
|
|
console.error(
|
|
'Select2: An instance of jQuery or a jQuery-compatible library was not ' +
|
|
'found. Make sure that you are including jQuery before Select2 on your ' +
|
|
'web page.'
|
|
);
|
|
}
|
|
|
|
return _$;
|
|
});
|
|
|
|
S2.define('select2/utils',[
|
|
'jquery'
|
|
], function ($) {
|
|
var Utils = {};
|
|
|
|
Utils.Extend = function (ChildClass, SuperClass) {
|
|
var __hasProp = {}.hasOwnProperty;
|
|
|
|
function BaseConstructor () {
|
|
this.constructor = ChildClass;
|
|
}
|
|
|
|
for (var key in SuperClass) {
|
|
if (__hasProp.call(SuperClass, key)) {
|
|
ChildClass[key] = SuperClass[key];
|
|
}
|
|
}
|
|
|
|
BaseConstructor.prototype = SuperClass.prototype;
|
|
ChildClass.prototype = new BaseConstructor();
|
|
ChildClass.__super__ = SuperClass.prototype;
|
|
|
|
return ChildClass;
|
|
};
|
|
|
|
function getMethods (theClass) {
|
|
var proto = theClass.prototype;
|
|
|
|
var methods = [];
|
|
|
|
for (var methodName in proto) {
|
|
var m = proto[methodName];
|
|
|
|
if (typeof m !== 'function') {
|
|
continue;
|
|
}
|
|
|
|
if (methodName === 'constructor') {
|
|
continue;
|
|
}
|
|
|
|
methods.push(methodName);
|
|
}
|
|
|
|
return methods;
|
|
}
|
|
|
|
Utils.Decorate = function (SuperClass, DecoratorClass) {
|
|
var decoratedMethods = getMethods(DecoratorClass);
|
|
var superMethods = getMethods(SuperClass);
|
|
|
|
function DecoratedClass () {
|
|
var unshift = Array.prototype.unshift;
|
|
|
|
var argCount = DecoratorClass.prototype.constructor.length;
|
|
|
|
var calledConstructor = SuperClass.prototype.constructor;
|
|
|
|
if (argCount > 0) {
|
|
unshift.call(arguments, SuperClass.prototype.constructor);
|
|
|
|
calledConstructor = DecoratorClass.prototype.constructor;
|
|
}
|
|
|
|
calledConstructor.apply(this, arguments);
|
|
}
|
|
|
|
DecoratorClass.displayName = SuperClass.displayName;
|
|
|
|
function ctr () {
|
|
this.constructor = DecoratedClass;
|
|
}
|
|
|
|
DecoratedClass.prototype = new ctr();
|
|
|
|
for (var m = 0; m < superMethods.length; m++) {
|
|
var superMethod = superMethods[m];
|
|
|
|
DecoratedClass.prototype[superMethod] =
|
|
SuperClass.prototype[superMethod];
|
|
}
|
|
|
|
var calledMethod = function (methodName) {
|
|
// Stub out the original method if it's not decorating an actual method
|
|
var originalMethod = function () {};
|
|
|
|
if (methodName in DecoratedClass.prototype) {
|
|
originalMethod = DecoratedClass.prototype[methodName];
|
|
}
|
|
|
|
var decoratedMethod = DecoratorClass.prototype[methodName];
|
|
|
|
return function () {
|
|
var unshift = Array.prototype.unshift;
|
|
|
|
unshift.call(arguments, originalMethod);
|
|
|
|
return decoratedMethod.apply(this, arguments);
|
|
};
|
|
};
|
|
|
|
for (var d = 0; d < decoratedMethods.length; d++) {
|
|
var decoratedMethod = decoratedMethods[d];
|
|
|
|
DecoratedClass.prototype[decoratedMethod] = calledMethod(decoratedMethod);
|
|
}
|
|
|
|
return DecoratedClass;
|
|
};
|
|
|
|
var Observable = function () {
|
|
this.listeners = {};
|
|
};
|
|
|
|
Observable.prototype.on = function (event, callback) {
|
|
this.listeners = this.listeners || {};
|
|
|
|
if (event in this.listeners) {
|
|
this.listeners[event].push(callback);
|
|
} else {
|
|
this.listeners[event] = [callback];
|
|
}
|
|
};
|
|
|
|
Observable.prototype.trigger = function (event) {
|
|
var slice = Array.prototype.slice;
|
|
var params = slice.call(arguments, 1);
|
|
|
|
this.listeners = this.listeners || {};
|
|
|
|
// Params should always come in as an array
|
|
if (params == null) {
|
|
params = [];
|
|
}
|
|
|
|
// If there are no arguments to the event, use a temporary object
|
|
if (params.length === 0) {
|
|
params.push({});
|
|
}
|
|
|
|
// Set the `_type` of the first object to the event
|
|
params[0]._type = event;
|
|
|
|
if (event in this.listeners) {
|
|
this.invoke(this.listeners[event], slice.call(arguments, 1));
|
|
}
|
|
|
|
if ('*' in this.listeners) {
|
|
this.invoke(this.listeners['*'], arguments);
|
|
}
|
|
};
|
|
|
|
Observable.prototype.invoke = function (listeners, params) {
|
|
for (var i = 0, len = listeners.length; i < len; i++) {
|
|
listeners[i].apply(this, params);
|
|
}
|
|
};
|
|
|
|
Utils.Observable = Observable;
|
|
|
|
Utils.generateChars = function (length) {
|
|
var chars = '';
|
|
|
|
for (var i = 0; i < length; i++) {
|
|
var randomChar = Math.floor(Math.random() * 36);
|
|
chars += randomChar.toString(36);
|
|
}
|
|
|
|
return chars;
|
|
};
|
|
|
|
Utils.bind = function (func, context) {
|
|
return function () {
|
|
func.apply(context, arguments);
|
|
};
|
|
};
|
|
|
|
Utils._convertData = function (data) {
|
|
for (var originalKey in data) {
|
|
var keys = originalKey.split('-');
|
|
|
|
var dataLevel = data;
|
|
|
|
if (keys.length === 1) {
|
|
continue;
|
|
}
|
|
|
|
for (var k = 0; k < keys.length; k++) {
|
|
var key = keys[k];
|
|
|
|
// Lowercase the first letter
|
|
// By default, dash-separated becomes camelCase
|
|
key = key.substring(0, 1).toLowerCase() + key.substring(1);
|
|
|
|
if (!(key in dataLevel)) {
|
|
dataLevel[key] = {};
|
|
}
|
|
|
|
if (k == keys.length - 1) {
|
|
dataLevel[key] = data[originalKey];
|
|
}
|
|
|
|
dataLevel = dataLevel[key];
|
|
}
|
|
|
|
delete data[originalKey];
|
|
}
|
|
|
|
return data;
|
|
};
|
|
|
|
Utils.hasScroll = function (index, el) {
|
|
// Adapted from the function created by @ShadowScripter
|
|
// and adapted by @BillBarry on the Stack Exchange Code Review website.
|
|
// The original code can be found at
|
|
// http://codereview.stackexchange.com/q/13338
|
|
// and was designed to be used with the Sizzle selector engine.
|
|
|
|
var $el = $(el);
|
|
var overflowX = el.style.overflowX;
|
|
var overflowY = el.style.overflowY;
|
|
|
|
//Check both x and y declarations
|
|
if (overflowX === overflowY &&
|
|
(overflowY === 'hidden' || overflowY === 'visible')) {
|
|
return false;
|
|
}
|
|
|
|
if (overflowX === 'scroll' || overflowY === 'scroll') {
|
|
return true;
|
|
}
|
|
|
|
return ($el.innerHeight() < el.scrollHeight ||
|
|
$el.innerWidth() < el.scrollWidth);
|
|
};
|
|
|
|
Utils.escapeMarkup = function (markup) {
|
|
var replaceMap = {
|
|
'\\': '\',
|
|
'&': '&',
|
|
'<': '<',
|
|
'>': '>',
|
|
'"': '"',
|
|
'\'': ''',
|
|
'/': '/'
|
|
};
|
|
|
|
// Do not try to escape the markup if it's not a string
|
|
if (typeof markup !== 'string') {
|
|
return markup;
|
|
}
|
|
|
|
return String(markup).replace(/[&<>"'\/\\]/g, function (match) {
|
|
return replaceMap[match];
|
|
});
|
|
};
|
|
|
|
// Append an array of jQuery nodes to a given element.
|
|
Utils.appendMany = function ($element, $nodes) {
|
|
// jQuery 1.7.x does not support $.fn.append() with an array
|
|
// Fall back to a jQuery object collection using $.fn.add()
|
|
if ($.fn.jquery.substr(0, 3) === '1.7') {
|
|
var $jqNodes = $();
|
|
|
|
$.map($nodes, function (node) {
|
|
$jqNodes = $jqNodes.add(node);
|
|
});
|
|
|
|
$nodes = $jqNodes;
|
|
}
|
|
|
|
$element.append($nodes);
|
|
};
|
|
|
|
return Utils;
|
|
});
|
|
|
|
S2.define('select2/results',[
|
|
'jquery',
|
|
'./utils'
|
|
], function ($, Utils) {
|
|
function Results ($element, options, dataAdapter) {
|
|
this.$element = $element;
|
|
this.data = dataAdapter;
|
|
this.options = options;
|
|
|
|
Results.__super__.constructor.call(this);
|
|
}
|
|
|
|
Utils.Extend(Results, Utils.Observable);
|
|
|
|
Results.prototype.render = function () {
|
|
var $results = $(
|
|
'<ul class="select2-results__options" role="tree"></ul>'
|
|
);
|
|
|
|
if (this.options.get('multiple')) {
|
|
$results.attr('aria-multiselectable', 'true');
|
|
}
|
|
|
|
this.$results = $results;
|
|
|
|
return $results;
|
|
};
|
|
|
|
Results.prototype.clear = function () {
|
|
this.$results.empty();
|
|
};
|
|
|
|
Results.prototype.displayMessage = function (params) {
|
|
var escapeMarkup = this.options.get('escapeMarkup');
|
|
|
|
this.clear();
|
|
this.hideLoading();
|
|
|
|
var $message = $(
|
|
'<li role="treeitem" aria-live="assertive"' +
|
|
' class="select2-results__option"></li>'
|
|
);
|
|
|
|
var message = this.options.get('translations').get(params.message);
|
|
|
|
$message.append(
|
|
escapeMarkup(
|
|
message(params.args)
|
|
)
|
|
);
|
|
|
|
$message[0].className += ' select2-results__message';
|
|
|
|
this.$results.append($message);
|
|
};
|
|
|
|
Results.prototype.hideMessages = function () {
|
|
this.$results.find('.select2-results__message').remove();
|
|
};
|
|
|
|
Results.prototype.append = function (data) {
|
|
this.hideLoading();
|
|
|
|
var $options = [];
|
|
|
|
if (data.results == null || data.results.length === 0) {
|
|
if (this.$results.children().length === 0) {
|
|
this.trigger('results:message', {
|
|
message: 'noResults'
|
|
});
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
data.results = this.sort(data.results);
|
|
|
|
for (var d = 0; d < data.results.length; d++) {
|
|
var item = data.results[d];
|
|
|
|
var $option = this.option(item);
|
|
|
|
$options.push($option);
|
|
}
|
|
|
|
this.$results.append($options);
|
|
};
|
|
|
|
Results.prototype.position = function ($results, $dropdown) {
|
|
var $resultsContainer = $dropdown.find('.select2-results');
|
|
$resultsContainer.append($results);
|
|
};
|
|
|
|
Results.prototype.sort = function (data) {
|
|
var sorter = this.options.get('sorter');
|
|
|
|
return sorter(data);
|
|
};
|
|
|
|
Results.prototype.highlightFirstItem = function () {
|
|
var $options = this.$results
|
|
.find('.select2-results__option[aria-selected]');
|
|
|
|
var $selected = $options.filter('[aria-selected=true]');
|
|
|
|
// Check if there are any selected options
|
|
if ($selected.length > 0) {
|
|
// If there are selected options, highlight the first
|
|
$selected.first().trigger('mouseenter');
|
|
} else {
|
|
// If there are no selected options, highlight the first option
|
|
// in the dropdown
|
|
$options.first().trigger('mouseenter');
|
|
}
|
|
|
|
this.ensureHighlightVisible();
|
|
};
|
|
|
|
Results.prototype.setClasses = function () {
|
|
var self = this;
|
|
|
|
this.data.current(function (selected) {
|
|
var selectedIds = $.map(selected, function (s) {
|
|
return s.id.toString();
|
|
});
|
|
|
|
var $options = self.$results
|
|
.find('.select2-results__option[aria-selected]');
|
|
|
|
$options.each(function () {
|
|
var $option = $(this);
|
|
|
|
var item = $.data(this, 'data');
|
|
|
|
// id needs to be converted to a string when comparing
|
|
var id = '' + item.id;
|
|
|
|
if ((item.element != null && item.element.selected) ||
|
|
(item.element == null && $.inArray(id, selectedIds) > -1)) {
|
|
$option.attr('aria-selected', 'true');
|
|
} else {
|
|
$option.attr('aria-selected', 'false');
|
|
}
|
|
});
|
|
|
|
});
|
|
};
|
|
|
|
Results.prototype.showLoading = function (params) {
|
|
this.hideLoading();
|
|
|
|
var loadingMore = this.options.get('translations').get('searching');
|
|
|
|
var loading = {
|
|
disabled: true,
|
|
loading: true,
|
|
text: loadingMore(params)
|
|
};
|
|
var $loading = this.option(loading);
|
|
$loading.className += ' loading-results';
|
|
|
|
this.$results.prepend($loading);
|
|
};
|
|
|
|
Results.prototype.hideLoading = function () {
|
|
this.$results.find('.loading-results').remove();
|
|
};
|
|
|
|
Results.prototype.option = function (data) {
|
|
var option = document.createElement('li');
|
|
option.className = 'select2-results__option';
|
|
|
|
var attrs = {
|
|
'role': 'treeitem',
|
|
'aria-selected': 'false'
|
|
};
|
|
|
|
if (data.disabled) {
|
|
delete attrs['aria-selected'];
|
|
attrs['aria-disabled'] = 'true';
|
|
}
|
|
|
|
if (data.id == null) {
|
|
delete attrs['aria-selected'];
|
|
}
|
|
|
|
if (data._resultId != null) {
|
|
option.id = data._resultId;
|
|
}
|
|
|
|
if (data.title) {
|
|
option.title = data.title;
|
|
}
|
|
|
|
if (data.children) {
|
|
attrs.role = 'group';
|
|
attrs['aria-label'] = data.text;
|
|
delete attrs['aria-selected'];
|
|
}
|
|
|
|
for (var attr in attrs) {
|
|
var val = attrs[attr];
|
|
|
|
option.setAttribute(attr, val);
|
|
}
|
|
|
|
if (data.children) {
|
|
var $option = $(option);
|
|
|
|
var label = document.createElement('strong');
|
|
label.className = 'select2-results__group';
|
|
|
|
var $label = $(label);
|
|
this.template(data, label);
|
|
|
|
var $children = [];
|
|
|
|
for (var c = 0; c < data.children.length; c++) {
|
|
var child = data.children[c];
|
|
|
|
var $child = this.option(child);
|
|
|
|
$children.push($child);
|
|
}
|
|
|
|
var $childrenContainer = $('<ul></ul>', {
|
|
'class': 'select2-results__options select2-results__options--nested'
|
|
});
|
|
|
|
$childrenContainer.append($children);
|
|
|
|
$option.append(label);
|
|
$option.append($childrenContainer);
|
|
} else {
|
|
this.template(data, option);
|
|
}
|
|
|
|
$.data(option, 'data', data);
|
|
|
|
return option;
|
|
};
|
|
|
|
Results.prototype.bind = function (container, $container) {
|
|
var self = this;
|
|
|
|
var id = container.id + '-results';
|
|
|
|
this.$results.attr('id', id);
|
|
|
|
container.on('results:all', function (params) {
|
|
self.clear();
|
|
self.append(params.data);
|
|
|
|
if (container.isOpen()) {
|
|
self.setClasses();
|
|
self.highlightFirstItem();
|
|
}
|
|
});
|
|
|
|
container.on('results:append', function (params) {
|
|
self.append(params.data);
|
|
|
|
if (container.isOpen()) {
|
|
self.setClasses();
|
|
}
|
|
});
|
|
|
|
container.on('query', function (params) {
|
|
self.hideMessages();
|
|
self.showLoading(params);
|
|
});
|
|
|
|
container.on('select', function () {
|
|
if (!container.isOpen()) {
|
|
return;
|
|
}
|
|
|
|
self.setClasses();
|
|
self.highlightFirstItem();
|
|
});
|
|
|
|
container.on('unselect', function () {
|
|
if (!container.isOpen()) {
|
|
return;
|
|
}
|
|
|
|
self.setClasses();
|
|
self.highlightFirstItem();
|
|
});
|
|
|
|
container.on('open', function () {
|
|
// When the dropdown is open, aria-expended="true"
|
|
self.$results.attr('aria-expanded', 'true');
|
|
self.$results.attr('aria-hidden', 'false');
|
|
|
|
self.setClasses();
|
|
self.ensureHighlightVisible();
|
|
});
|
|
|
|
container.on('close', function () {
|
|
// When the dropdown is closed, aria-expended="false"
|
|
self.$results.attr('aria-expanded', 'false');
|
|
self.$results.attr('aria-hidden', 'true');
|
|
self.$results.removeAttr('aria-activedescendant');
|
|
});
|
|
|
|
container.on('results:toggle', function () {
|
|
var $highlighted = self.getHighlightedResults();
|
|
|
|
if ($highlighted.length === 0) {
|
|
return;
|
|
}
|
|
|
|
$highlighted.trigger('mouseup');
|
|
});
|
|
|
|
container.on('results:select', function () {
|
|
var $highlighted = self.getHighlightedResults();
|
|
|
|
if ($highlighted.length === 0) {
|
|
return;
|
|
}
|
|
|
|
var data = $highlighted.data('data');
|
|
|
|
if ($highlighted.attr('aria-selected') == 'true') {
|
|
self.trigger('close', {});
|
|
} else {
|
|
self.trigger('select', {
|
|
data: data
|
|
});
|
|
}
|
|
});
|
|
|
|
container.on('results:previous', function () {
|
|
var $highlighted = self.getHighlightedResults();
|
|
|
|
var $options = self.$results.find('[aria-selected]');
|
|
|
|
var currentIndex = $options.index($highlighted);
|
|
|
|
// If we are already at te top, don't move further
|
|
if (currentIndex === 0) {
|
|
return;
|
|
}
|
|
|
|
var nextIndex = currentIndex - 1;
|
|
|
|
// If none are highlighted, highlight the first
|
|
if ($highlighted.length === 0) {
|
|
nextIndex = 0;
|
|
}
|
|
|
|
var $next = $options.eq(nextIndex);
|
|
|
|
$next.trigger('mouseenter');
|
|
|
|
var currentOffset = self.$results.offset().top;
|
|
var nextTop = $next.offset().top;
|
|
var nextOffset = self.$results.scrollTop() + (nextTop - currentOffset);
|
|
|
|
if (nextIndex === 0) {
|
|
self.$results.scrollTop(0);
|
|
} else if (nextTop - currentOffset < 0) {
|
|
self.$results.scrollTop(nextOffset);
|
|
}
|
|
});
|
|
|
|
container.on('results:next', function () {
|
|
var $highlighted = self.getHighlightedResults();
|
|
|
|
var $options = self.$results.find('[aria-selected]');
|
|
|
|
var currentIndex = $options.index($highlighted);
|
|
|
|
var nextIndex = currentIndex + 1;
|
|
|
|
// If we are at the last option, stay there
|
|
if (nextIndex >= $options.length) {
|
|
return;
|
|
}
|
|
|
|
var $next = $options.eq(nextIndex);
|
|
|
|
$next.trigger('mouseenter');
|
|
|
|
var currentOffset = self.$results.offset().top +
|
|
self.$results.outerHeight(false);
|
|
var nextBottom = $next.offset().top + $next.outerHeight(false);
|
|
var nextOffset = self.$results.scrollTop() + nextBottom - currentOffset;
|
|
|
|
if (nextIndex === 0) {
|
|
self.$results.scrollTop(0);
|
|
} else if (nextBottom > currentOffset) {
|
|
self.$results.scrollTop(nextOffset);
|
|
}
|
|
});
|
|
|
|
container.on('results:focus', function (params) {
|
|
params.element.addClass('select2-results__option--highlighted');
|
|
});
|
|
|
|
container.on('results:message', function (params) {
|
|
self.displayMessage(params);
|
|
});
|
|
|
|
if ($.fn.mousewheel) {
|
|
this.$results.on('mousewheel', function (e) {
|
|
var top = self.$results.scrollTop();
|
|
|
|
var bottom = self.$results.get(0).scrollHeight - top + e.deltaY;
|
|
|
|
var isAtTop = e.deltaY > 0 && top - e.deltaY <= 0;
|
|
var isAtBottom = e.deltaY < 0 && bottom <= self.$results.height();
|
|
|
|
if (isAtTop) {
|
|
self.$results.scrollTop(0);
|
|
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
} else if (isAtBottom) {
|
|
self.$results.scrollTop(
|
|
self.$results.get(0).scrollHeight - self.$results.height()
|
|
);
|
|
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
}
|
|
});
|
|
}
|
|
|
|
this.$results.on('mouseup', '.select2-results__option[aria-selected]',
|
|
function (evt) {
|
|
var $this = $(this);
|
|
|
|
var data = $this.data('data');
|
|
|
|
if ($this.attr('aria-selected') === 'true') {
|
|
if (self.options.get('multiple')) {
|
|
self.trigger('unselect', {
|
|
originalEvent: evt,
|
|
data: data
|
|
});
|
|
} else {
|
|
self.trigger('close', {});
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
self.trigger('select', {
|
|
originalEvent: evt,
|
|
data: data
|
|
});
|
|
});
|
|
|
|
this.$results.on('mouseenter', '.select2-results__option[aria-selected]',
|
|
function (evt) {
|
|
var data = $(this).data('data');
|
|
|
|
self.getHighlightedResults()
|
|
.removeClass('select2-results__option--highlighted');
|
|
|
|
self.trigger('results:focus', {
|
|
data: data,
|
|
element: $(this)
|
|
});
|
|
});
|
|
};
|
|
|
|
Results.prototype.getHighlightedResults = function () {
|
|
var $highlighted = this.$results
|
|
.find('.select2-results__option--highlighted');
|
|
|
|
return $highlighted;
|
|
};
|
|
|
|
Results.prototype.destroy = function () {
|
|
this.$results.remove();
|
|
};
|
|
|
|
Results.prototype.ensureHighlightVisible = function () {
|
|
var $highlighted = this.getHighlightedResults();
|
|
|
|
if ($highlighted.length === 0) {
|
|
return;
|
|
}
|
|
|
|
var $options = this.$results.find('[aria-selected]');
|
|
|
|
var currentIndex = $options.index($highlighted);
|
|
|
|
var currentOffset = this.$results.offset().top;
|
|
var nextTop = $highlighted.offset().top;
|
|
var nextOffset = this.$results.scrollTop() + (nextTop - currentOffset);
|
|
|
|
var offsetDelta = nextTop - currentOffset;
|
|
nextOffset -= $highlighted.outerHeight(false) * 2;
|
|
|
|
if (currentIndex <= 2) {
|
|
this.$results.scrollTop(0);
|
|
} else if (offsetDelta > this.$results.outerHeight() || offsetDelta < 0) {
|
|
this.$results.scrollTop(nextOffset);
|
|
}
|
|
};
|
|
|
|
Results.prototype.template = function (result, container) {
|
|
var template = this.options.get('templateResult');
|
|
var escapeMarkup = this.options.get('escapeMarkup');
|
|
|
|
var content = template(result, container);
|
|
|
|
if (content == null) {
|
|
container.style.display = 'none';
|
|
} else if (typeof content === 'string') {
|
|
container.innerHTML = escapeMarkup(content);
|
|
} else {
|
|
$(container).append(content);
|
|
}
|
|
};
|
|
|
|
return Results;
|
|
});
|
|
|
|
S2.define('select2/keys',[
|
|
|
|
], function () {
|
|
var KEYS = {
|
|
BACKSPACE: 8,
|
|
TAB: 9,
|
|
ENTER: 13,
|
|
SHIFT: 16,
|
|
CTRL: 17,
|
|
ALT: 18,
|
|
ESC: 27,
|
|
SPACE: 32,
|
|
PAGE_UP: 33,
|
|
PAGE_DOWN: 34,
|
|
END: 35,
|
|
HOME: 36,
|
|
LEFT: 37,
|
|
UP: 38,
|
|
RIGHT: 39,
|
|
DOWN: 40,
|
|
DELETE: 46
|
|
};
|
|
|
|
return KEYS;
|
|
});
|
|
|
|
S2.define('select2/selection/base',[
|
|
'jquery',
|
|
'../utils',
|
|
'../keys'
|
|
], function ($, Utils, KEYS) {
|
|
function BaseSelection ($element, options) {
|
|
this.$element = $element;
|
|
this.options = options;
|
|
|
|
BaseSelection.__super__.constructor.call(this);
|
|
}
|
|
|
|
Utils.Extend(BaseSelection, Utils.Observable);
|
|
|
|
BaseSelection.prototype.render = function () {
|
|
var $selection = $(
|
|
'<span class="select2-selection" role="combobox" ' +
|
|
' aria-haspopup="true" aria-expanded="false">' +
|
|
'</span>'
|
|
);
|
|
|
|
this._tabindex = 0;
|
|
|
|
if (this.$element.data('old-tabindex') != null) {
|
|
this._tabindex = this.$element.data('old-tabindex');
|
|
} else if (this.$element.attr('tabindex') != null) {
|
|
this._tabindex = this.$element.attr('tabindex');
|
|
}
|
|
|
|
$selection.attr('title', this.$element.attr('title'));
|
|
$selection.attr('tabindex', this._tabindex);
|
|
|
|
this.$selection = $selection;
|
|
|
|
return $selection;
|
|
};
|
|
|
|
BaseSelection.prototype.bind = function (container, $container) {
|
|
var self = this;
|
|
|
|
var id = container.id + '-container';
|
|
var resultsId = container.id + '-results';
|
|
|
|
this.container = container;
|
|
|
|
this.$selection.on('focus', function (evt) {
|
|
self.trigger('focus', evt);
|
|
});
|
|
|
|
this.$selection.on('blur', function (evt) {
|
|
self._handleBlur(evt);
|
|
});
|
|
|
|
this.$selection.on('keydown', function (evt) {
|
|
self.trigger('keypress', evt);
|
|
|
|
if (evt.which === KEYS.SPACE) {
|
|
evt.preventDefault();
|
|
}
|
|
});
|
|
|
|
container.on('results:focus', function (params) {
|
|
self.$selection.attr('aria-activedescendant', params.data._resultId);
|
|
});
|
|
|
|
container.on('selection:update', function (params) {
|
|
self.update(params.data);
|
|
});
|
|
|
|
container.on('open', function () {
|
|
// When the dropdown is open, aria-expanded="true"
|
|
self.$selection.attr('aria-expanded', 'true');
|
|
self.$selection.attr('aria-owns', resultsId);
|
|
|
|
self._attachCloseHandler(container);
|
|
});
|
|
|
|
container.on('close', function () {
|
|
// When the dropdown is closed, aria-expanded="false"
|
|
self.$selection.attr('aria-expanded', 'false');
|
|
self.$selection.removeAttr('aria-activedescendant');
|
|
self.$selection.removeAttr('aria-owns');
|
|
|
|
self.$selection.focus();
|
|
|
|
self._detachCloseHandler(container);
|
|
});
|
|
|
|
container.on('enable', function () {
|
|
self.$selection.attr('tabindex', self._tabindex);
|
|
});
|
|
|
|
container.on('disable', function () {
|
|
self.$selection.attr('tabindex', '-1');
|
|
});
|
|
};
|
|
|
|
BaseSelection.prototype._handleBlur = function (evt) {
|
|
var self = this;
|
|
|
|
// This needs to be delayed as the active element is the body when the tab
|
|
// key is pressed, possibly along with others.
|
|
window.setTimeout(function () {
|
|
// Don't trigger `blur` if the focus is still in the selection
|
|
if (
|
|
(document.activeElement == self.$selection[0]) ||
|
|
($.contains(self.$selection[0], document.activeElement))
|
|
) {
|
|
return;
|
|
}
|
|
|
|
self.trigger('blur', evt);
|
|
}, 1);
|
|
};
|
|
|
|
BaseSelection.prototype._attachCloseHandler = function (container) {
|
|
var self = this;
|
|
|
|
$(document.body).on('mousedown.select2.' + container.id, function (e) {
|
|
var $target = $(e.target);
|
|
|
|
var $select = $target.closest('.select2');
|
|
|
|
var $all = $('.select2.select2-container--open');
|
|
|
|
$all.each(function () {
|
|
var $this = $(this);
|
|
|
|
if (this == $select[0]) {
|
|
return;
|
|
}
|
|
|
|
var $element = $this.data('element');
|
|
|
|
$element.select2('close');
|
|
});
|
|
});
|
|
};
|
|
|
|
BaseSelection.prototype._detachCloseHandler = function (container) {
|
|
$(document.body).off('mousedown.select2.' + container.id);
|
|
};
|
|
|
|
BaseSelection.prototype.position = function ($selection, $container) {
|
|
var $selectionContainer = $container.find('.selection');
|
|
$selectionContainer.append($selection);
|
|
};
|
|
|
|
BaseSelection.prototype.destroy = function () {
|
|
this._detachCloseHandler(this.container);
|
|
};
|
|
|
|
BaseSelection.prototype.update = function (data) {
|
|
throw new Error('The `update` method must be defined in child classes.');
|
|
};
|
|
|
|
return BaseSelection;
|
|
});
|
|
|
|
S2.define('select2/selection/single',[
|
|
'jquery',
|
|
'./base',
|
|
'../utils',
|
|
'../keys'
|
|
], function ($, BaseSelection, Utils, KEYS) {
|
|
function SingleSelection () {
|
|
SingleSelection.__super__.constructor.apply(this, arguments);
|
|
}
|
|
|
|
Utils.Extend(SingleSelection, BaseSelection);
|
|
|
|
SingleSelection.prototype.render = function () {
|
|
var $selection = SingleSelection.__super__.render.call(this);
|
|
|
|
$selection.addClass('select2-selection--single');
|
|
|
|
$selection.html(
|
|
'<span class="select2-selection__rendered"></span>' +
|
|
'<span class="select2-selection__arrow" role="presentation">' +
|
|
'<b role="presentation"></b>' +
|
|
'</span>'
|
|
);
|
|
|
|
return $selection;
|
|
};
|
|
|
|
SingleSelection.prototype.bind = function (container, $container) {
|
|
var self = this;
|
|
|
|
SingleSelection.__super__.bind.apply(this, arguments);
|
|
|
|
var id = container.id + '-container';
|
|
|
|
this.$selection.find('.select2-selection__rendered').attr('id', id);
|
|
this.$selection.attr('aria-labelledby', id);
|
|
|
|
this.$selection.on('mousedown', function (evt) {
|
|
// Only respond to left clicks
|
|
if (evt.which !== 1) {
|
|
return;
|
|
}
|
|
|
|
self.trigger('toggle', {
|
|
originalEvent: evt
|
|
});
|
|
});
|
|
|
|
this.$selection.on('focus', function (evt) {
|
|
// User focuses on the container
|
|
});
|
|
|
|
this.$selection.on('blur', function (evt) {
|
|
// User exits the container
|
|
});
|
|
|
|
container.on('focus', function (evt) {
|
|
if (!container.isOpen()) {
|
|
self.$selection.focus();
|
|
}
|
|
});
|
|
|
|
container.on('selection:update', function (params) {
|
|
self.update(params.data);
|
|
});
|
|
};
|
|
|
|
SingleSelection.prototype.clear = function () {
|
|
this.$selection.find('.select2-selection__rendered').empty();
|
|
};
|
|
|
|
SingleSelection.prototype.display = function (data, container) {
|
|
var template = this.options.get('templateSelection');
|
|
var escapeMarkup = this.options.get('escapeMarkup');
|
|
|
|
return escapeMarkup(template(data, container));
|
|
};
|
|
|
|
SingleSelection.prototype.selectionContainer = function () {
|
|
return $('<span></span>');
|
|
};
|
|
|
|
SingleSelection.prototype.update = function (data) {
|
|
if (data.length === 0) {
|
|
this.clear();
|
|
return;
|
|
}
|
|
|
|
var selection = data[0];
|
|
|
|
var $rendered = this.$selection.find('.select2-selection__rendered');
|
|
var formatted = this.display(selection, $rendered);
|
|
|
|
$rendered.empty().append(formatted);
|
|
$rendered.prop('title', selection.title || selection.text);
|
|
};
|
|
|
|
return SingleSelection;
|
|
});
|
|
|
|
S2.define('select2/selection/multiple',[
|
|
'jquery',
|
|
'./base',
|
|
'../utils'
|
|
], function ($, BaseSelection, Utils) {
|
|
function MultipleSelection ($element, options) {
|
|
MultipleSelection.__super__.constructor.apply(this, arguments);
|
|
}
|
|
|
|
Utils.Extend(MultipleSelection, BaseSelection);
|
|
|
|
MultipleSelection.prototype.render = function () {
|
|
var $selection = MultipleSelection.__super__.render.call(this);
|
|
|
|
$selection.addClass('select2-selection--multiple');
|
|
|
|
$selection.html(
|
|
'<ul class="select2-selection__rendered"></ul>'
|
|
);
|
|
|
|
return $selection;
|
|
};
|
|
|
|
MultipleSelection.prototype.bind = function (container, $container) {
|
|
var self = this;
|
|
|
|
MultipleSelection.__super__.bind.apply(this, arguments);
|
|
|
|
this.$selection.on('click', function (evt) {
|
|
self.trigger('toggle', {
|
|
originalEvent: evt
|
|
});
|
|
});
|
|
|
|
this.$selection.on(
|
|
'click',
|
|
'.select2-selection__choice__remove',
|
|
function (evt) {
|
|
// Ignore the event if it is disabled
|
|
if (self.options.get('disabled')) {
|
|
return;
|
|
}
|
|
|
|
var $remove = $(this);
|
|
var $selection = $remove.parent();
|
|
|
|
var data = $selection.data('data');
|
|
|
|
self.trigger('unselect', {
|
|
originalEvent: evt,
|
|
data: data
|
|
});
|
|
}
|
|
);
|
|
};
|
|
|
|
MultipleSelection.prototype.clear = function () {
|
|
this.$selection.find('.select2-selection__rendered').empty();
|
|
};
|
|
|
|
MultipleSelection.prototype.display = function (data, container) {
|
|
var template = this.options.get('templateSelection');
|
|
var escapeMarkup = this.options.get('escapeMarkup');
|
|
|
|
return escapeMarkup(template(data, container));
|
|
};
|
|
|
|
MultipleSelection.prototype.selectionContainer = function () {
|
|
var $container = $(
|
|
'<li class="select2-selection__choice">' +
|
|
'<span class="select2-selection__choice__remove" role="presentation">' +
|
|
'×' +
|
|
'</span>' +
|
|
'</li>'
|
|
);
|
|
|
|
return $container;
|
|
};
|
|
|
|
MultipleSelection.prototype.update = function (data) {
|
|
this.clear();
|
|
|
|
if (data.length === 0) {
|
|
return;
|
|
}
|
|
|
|
var $selections = [];
|
|
|
|
for (var d = 0; d < data.length; d++) {
|
|
var selection = data[d];
|
|
|
|
var $selection = this.selectionContainer();
|
|
var formatted = this.display(selection, $selection);
|
|
|
|
$selection.append(formatted);
|
|
$selection.prop('title', selection.title || selection.text);
|
|
|
|
$selection.data('data', selection);
|
|
|
|
$selections.push($selection);
|
|
}
|
|
|
|
var $rendered = this.$selection.find('.select2-selection__rendered');
|
|
|
|
Utils.appendMany($rendered, $selections);
|
|
};
|
|
|
|
return MultipleSelection;
|
|
});
|
|
|
|
S2.define('select2/selection/placeholder',[
|
|
'../utils'
|
|
], function (Utils) {
|
|
function Placeholder (decorated, $element, options) {
|
|
this.placeholder = this.normalizePlaceholder(options.get('placeholder'));
|
|
|
|
decorated.call(this, $element, options);
|
|
}
|
|
|
|
Placeholder.prototype.normalizePlaceholder = function (_, placeholder) {
|
|
if (typeof placeholder === 'string') {
|
|
placeholder = {
|
|
id: '',
|
|
text: placeholder
|
|
};
|
|
}
|
|
|
|
return placeholder;
|
|
};
|
|
|
|
Placeholder.prototype.createPlaceholder = function (decorated, placeholder) {
|
|
var $placeholder = this.selectionContainer();
|
|
|
|
$placeholder.html(this.display(placeholder));
|
|
$placeholder.addClass('select2-selection__placeholder')
|
|
.removeClass('select2-selection__choice');
|
|
|
|
return $placeholder;
|
|
};
|
|
|
|
Placeholder.prototype.update = function (decorated, data) {
|
|
var singlePlaceholder = (
|
|
data.length == 1 && data[0].id != this.placeholder.id
|
|
);
|
|
var multipleSelections = data.length > 1;
|
|
|
|
if (multipleSelections || singlePlaceholder) {
|
|
return decorated.call(this, data);
|
|
}
|
|
|
|
this.clear();
|
|
|
|
var $placeholder = this.createPlaceholder(this.placeholder);
|
|
|
|
this.$selection.find('.select2-selection__rendered').append($placeholder);
|
|
};
|
|
|
|
return Placeholder;
|
|
});
|
|
|
|
S2.define('select2/selection/allowClear',[
|
|
'jquery',
|
|
'../keys'
|
|
], function ($, KEYS) {
|
|
function AllowClear () { }
|
|
|
|
AllowClear.prototype.bind = function (decorated, container, $container) {
|
|
var self = this;
|
|
|
|
decorated.call(this, container, $container);
|
|
|
|
if (this.placeholder == null) {
|
|
if (this.options.get('debug') && window.console && console.error) {
|
|
console.error(
|
|
'Select2: The `allowClear` option should be used in combination ' +
|
|
'with the `placeholder` option.'
|
|
);
|
|
}
|
|
}
|
|
|
|
this.$selection.on('mousedown', '.select2-selection__clear',
|
|
function (evt) {
|
|
self._handleClear(evt);
|
|
});
|
|
|
|
container.on('keypress', function (evt) {
|
|
self._handleKeyboardClear(evt, container);
|
|
});
|
|
};
|
|
|
|
AllowClear.prototype._handleClear = function (_, evt) {
|
|
// Ignore the event if it is disabled
|
|
if (this.options.get('disabled')) {
|
|
return;
|
|
}
|
|
|
|
var $clear = this.$selection.find('.select2-selection__clear');
|
|
|
|
// Ignore the event if nothing has been selected
|
|
if ($clear.length === 0) {
|
|
return;
|
|
}
|
|
|
|
evt.stopPropagation();
|
|
|
|
var data = $clear.data('data');
|
|
|
|
for (var d = 0; d < data.length; d++) {
|
|
var unselectData = {
|
|
data: data[d]
|
|
};
|
|
|
|
// Trigger the `unselect` event, so people can prevent it from being
|
|
// cleared.
|
|
this.trigger('unselect', unselectData);
|
|
|
|
// If the event was prevented, don't clear it out.
|
|
if (unselectData.prevented) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
this.$element.val(this.placeholder.id).trigger('change');
|
|
|
|
this.trigger('toggle', {});
|
|
};
|
|
|
|
AllowClear.prototype._handleKeyboardClear = function (_, evt, container) {
|
|
if (container.isOpen()) {
|
|
return;
|
|
}
|
|
|
|
if (evt.which == KEYS.DELETE || evt.which == KEYS.BACKSPACE) {
|
|
this._handleClear(evt);
|
|
}
|
|
};
|
|
|
|
AllowClear.prototype.update = function (decorated, data) {
|
|
decorated.call(this, data);
|
|
|
|
if (this.$selection.find('.select2-selection__placeholder').length > 0 ||
|
|
data.length === 0) {
|
|
return;
|
|
}
|
|
|
|
var $remove = $(
|
|
'<span class="select2-selection__clear">' +
|
|
'×' +
|
|
'</span>'
|
|
);
|
|
$remove.data('data', data);
|
|
|
|
this.$selection.find('.select2-selection__rendered').prepend($remove);
|
|
};
|
|
|
|
return AllowClear;
|
|
});
|
|
|
|
S2.define('select2/selection/search',[
|
|
'jquery',
|
|
'../utils',
|
|
'../keys'
|
|
], function ($, Utils, KEYS) {
|
|
function Search (decorated, $element, options) {
|
|
decorated.call(this, $element, options);
|
|
}
|
|
|
|
Search.prototype.render = function (decorated) {
|
|
var $search = $(
|
|
'<li class="select2-search select2-search--inline">' +
|
|
'<input class="select2-search__field" type="search" tabindex="-1"' +
|
|
' autocomplete="off" autocorrect="off" autocapitalize="none"' +
|
|
' spellcheck="false" role="textbox" aria-autocomplete="list" />' +
|
|
'</li>'
|
|
);
|
|
|
|
this.$searchContainer = $search;
|
|
this.$search = $search.find('input');
|
|
|
|
var $rendered = decorated.call(this);
|
|
|
|
this._transferTabIndex();
|
|
|
|
return $rendered;
|
|
};
|
|
|
|
Search.prototype.bind = function (decorated, container, $container) {
|
|
var self = this;
|
|
|
|
decorated.call(this, container, $container);
|
|
|
|
container.on('open', function () {
|
|
self.$search.trigger('focus');
|
|
});
|
|
|
|
container.on('close', function () {
|
|
self.$search.val('');
|
|
self.$search.removeAttr('aria-activedescendant');
|
|
self.$search.trigger('focus');
|
|
});
|
|
|
|
container.on('enable', function () {
|
|
self.$search.prop('disabled', false);
|
|
|
|
self._transferTabIndex();
|
|
});
|
|
|
|
container.on('disable', function () {
|
|
self.$search.prop('disabled', true);
|
|
});
|
|
|
|
container.on('focus', function (evt) {
|
|
self.$search.trigger('focus');
|
|
});
|
|
|
|
container.on('results:focus', function (params) {
|
|
self.$search.attr('aria-activedescendant', params.id);
|
|
});
|
|
|
|
this.$selection.on('focusin', '.select2-search--inline', function (evt) {
|
|
self.trigger('focus', evt);
|
|
});
|
|
|
|
this.$selection.on('focusout', '.select2-search--inline', function (evt) {
|
|
self._handleBlur(evt);
|
|
});
|
|
|
|
this.$selection.on('keydown', '.select2-search--inline', function (evt) {
|
|
evt.stopPropagation();
|
|
|
|
self.trigger('keypress', evt);
|
|
|
|
self._keyUpPrevented = evt.isDefaultPrevented();
|
|
|
|
var key = evt.which;
|
|
|
|
if (key === KEYS.BACKSPACE && self.$search.val() === '') {
|
|
var $previousChoice = self.$searchContainer
|
|
.prev('.select2-selection__choice');
|
|
|
|
if ($previousChoice.length > 0) {
|
|
var item = $previousChoice.data('data');
|
|
|
|
self.searchRemoveChoice(item);
|
|
|
|
evt.preventDefault();
|
|
}
|
|
}
|
|
});
|
|
|
|
// Try to detect the IE version should the `documentMode` property that
|
|
// is stored on the document. This is only implemented in IE and is
|
|
// slightly cleaner than doing a user agent check.
|
|
// This property is not available in Edge, but Edge also doesn't have
|
|
// this bug.
|
|
var msie = document.documentMode;
|
|
var disableInputEvents = msie && msie <= 11;
|
|
|
|
// Workaround for browsers which do not support the `input` event
|
|
// This will prevent double-triggering of events for browsers which support
|
|
// both the `keyup` and `input` events.
|
|
this.$selection.on(
|
|
'input.searchcheck',
|
|
'.select2-search--inline',
|
|
function (evt) {
|
|
// IE will trigger the `input` event when a placeholder is used on a
|
|
// search box. To get around this issue, we are forced to ignore all
|
|
// `input` events in IE and keep using `keyup`.
|
|
if (disableInputEvents) {
|
|
self.$selection.off('input.search input.searchcheck');
|
|
return;
|
|
}
|
|
|
|
// Unbind the duplicated `keyup` event
|
|
self.$selection.off('keyup.search');
|
|
}
|
|
);
|
|
|
|
this.$selection.on(
|
|
'keyup.search input.search',
|
|
'.select2-search--inline',
|
|
function (evt) {
|
|
// IE will trigger the `input` event when a placeholder is used on a
|
|
// search box. To get around this issue, we are forced to ignore all
|
|
// `input` events in IE and keep using `keyup`.
|
|
if (disableInputEvents && evt.type === 'input') {
|
|
self.$selection.off('input.search input.searchcheck');
|
|
return;
|
|
}
|
|
|
|
var key = evt.which;
|
|
|
|
// We can freely ignore events from modifier keys
|
|
if (key == KEYS.SHIFT || key == KEYS.CTRL || key == KEYS.ALT) {
|
|
return;
|
|
}
|
|
|
|
// Tabbing will be handled during the `keydown` phase
|
|
if (key == KEYS.TAB) {
|
|
return;
|
|
}
|
|
|
|
self.handleSearch(evt);
|
|
}
|
|
);
|
|
};
|
|
|
|
/**
|
|
* This method will transfer the tabindex attribute from the rendered
|
|
* selection to the search box. This allows for the search box to be used as
|
|
* the primary focus instead of the selection container.
|
|
*
|
|
* @private
|
|
*/
|
|
Search.prototype._transferTabIndex = function (decorated) {
|
|
this.$search.attr('tabindex', this.$selection.attr('tabindex'));
|
|
this.$selection.attr('tabindex', '-1');
|
|
};
|
|
|
|
Search.prototype.createPlaceholder = function (decorated, placeholder) {
|
|
this.$search.attr('placeholder', placeholder.text);
|
|
};
|
|
|
|
Search.prototype.update = function (decorated, data) {
|
|
var searchHadFocus = this.$search[0] == document.activeElement;
|
|
|
|
this.$search.attr('placeholder', '');
|
|
|
|
decorated.call(this, data);
|
|
|
|
this.$selection.find('.select2-selection__rendered')
|
|
.append(this.$searchContainer);
|
|
|
|
this.resizeSearch();
|
|
if (searchHadFocus) {
|
|
this.$search.focus();
|
|
}
|
|
};
|
|
|
|
Search.prototype.handleSearch = function () {
|
|
this.resizeSearch();
|
|
|
|
if (!this._keyUpPrevented) {
|
|
var input = this.$search.val();
|
|
|
|
this.trigger('query', {
|
|
term: input
|
|
});
|
|
}
|
|
|
|
this._keyUpPrevented = false;
|
|
};
|
|
|
|
Search.prototype.searchRemoveChoice = function (decorated, item) {
|
|
this.trigger('unselect', {
|
|
data: item
|
|
});
|
|
|
|
this.$search.val(item.text);
|
|
this.handleSearch();
|
|
};
|
|
|
|
Search.prototype.resizeSearch = function () {
|
|
this.$search.css('width', '25px');
|
|
|
|
var width = '';
|
|
|
|
if (this.$search.attr('placeholder') !== '') {
|
|
width = this.$selection.find('.select2-selection__rendered').innerWidth();
|
|
} else {
|
|
var minimumWidth = this.$search.val().length + 1;
|
|
|
|
width = (minimumWidth * 0.75) + 'em';
|
|
}
|
|
|
|
this.$search.css('width', width);
|
|
};
|
|
|
|
return Search;
|
|
});
|
|
|
|
S2.define('select2/selection/eventRelay',[
|
|
'jquery'
|
|
], function ($) {
|
|
function EventRelay () { }
|
|
|
|
EventRelay.prototype.bind = function (decorated, container, $container) {
|
|
var self = this;
|
|
var relayEvents = [
|
|
'open', 'opening',
|
|
'close', 'closing',
|
|
'select', 'selecting',
|
|
'unselect', 'unselecting'
|
|
];
|
|
|
|
var preventableEvents = ['opening', 'closing', 'selecting', 'unselecting'];
|
|
|
|
decorated.call(this, container, $container);
|
|
|
|
container.on('*', function (name, params) {
|
|
// Ignore events that should not be relayed
|
|
if ($.inArray(name, relayEvents) === -1) {
|
|
return;
|
|
}
|
|
|
|
// The parameters should always be an object
|
|
params = params || {};
|
|
|
|
// Generate the jQuery event for the Select2 event
|
|
var evt = $.Event('select2:' + name, {
|
|
params: params
|
|
});
|
|
|
|
self.$element.trigger(evt);
|
|
|
|
// Only handle preventable events if it was one
|
|
if ($.inArray(name, preventableEvents) === -1) {
|
|
return;
|
|
}
|
|
|
|
params.prevented = evt.isDefaultPrevented();
|
|
});
|
|
};
|
|
|
|
return EventRelay;
|
|
});
|
|
|
|
S2.define('select2/translation',[
|
|
'jquery',
|
|
'require'
|
|
], function ($, require) {
|
|
function Translation (dict) {
|
|
this.dict = dict || {};
|
|
}
|
|
|
|
Translation.prototype.all = function () {
|
|
return this.dict;
|
|
};
|
|
|
|
Translation.prototype.get = function (key) {
|
|
return this.dict[key];
|
|
};
|
|
|
|
Translation.prototype.extend = function (translation) {
|
|
this.dict = $.extend({}, translation.all(), this.dict);
|
|
};
|
|
|
|
// Static functions
|
|
|
|
Translation._cache = {};
|
|
|
|
Translation.loadPath = function (path) {
|
|
if (!(path in Translation._cache)) {
|
|
var translations = require(path);
|
|
|
|
Translation._cache[path] = translations;
|
|
}
|
|
|
|
return new Translation(Translation._cache[path]);
|
|
};
|
|
|
|
return Translation;
|
|
});
|
|
|
|
S2.define('select2/diacritics',[
|
|
|
|
], function () {
|
|
var diacritics = {
|
|
'\u24B6': 'A',
|
|
'\uFF21': 'A',
|
|
'\u00C0': 'A',
|
|
'\u00C1': 'A',
|
|
'\u00C2': 'A',
|
|
'\u1EA6': 'A',
|
|
'\u1EA4': 'A',
|
|
'\u1EAA': 'A',
|
|
'\u1EA8': 'A',
|
|
'\u00C3': 'A',
|
|
'\u0100': 'A',
|
|
'\u0102': 'A',
|
|
'\u1EB0': 'A',
|
|
'\u1EAE': 'A',
|
|
'\u1EB4': 'A',
|
|
'\u1EB2': 'A',
|
|
'\u0226': 'A',
|
|
'\u01E0': 'A',
|
|
'\u00C4': 'A',
|
|
'\u01DE': 'A',
|
|
'\u1EA2': 'A',
|
|
'\u00C5': 'A',
|
|
'\u01FA': 'A',
|
|
'\u01CD': 'A',
|
|
'\u0200': 'A',
|
|
'\u0202': 'A',
|
|
'\u1EA0': 'A',
|
|
'\u1EAC': 'A',
|
|
'\u1EB6': 'A',
|
|
'\u1E00': 'A',
|
|
'\u0104': 'A',
|
|
'\u023A': 'A',
|
|
'\u2C6F': 'A',
|
|
'\uA732': 'AA',
|
|
'\u00C6': 'AE',
|
|
'\u01FC': 'AE',
|
|
'\u01E2': 'AE',
|
|
'\uA734': 'AO',
|
|
'\uA736': 'AU',
|
|
'\uA738': 'AV',
|
|
'\uA73A': 'AV',
|
|
'\uA73C': 'AY',
|
|
'\u24B7': 'B',
|
|
'\uFF22': 'B',
|
|
'\u1E02': 'B',
|
|
'\u1E04': 'B',
|
|
'\u1E06': 'B',
|
|
'\u0243': 'B',
|
|
'\u0182': 'B',
|
|
'\u0181': 'B',
|
|
'\u24B8': 'C',
|
|
'\uFF23': 'C',
|
|
'\u0106': 'C',
|
|
'\u0108': 'C',
|
|
'\u010A': 'C',
|
|
'\u010C': 'C',
|
|
'\u00C7': 'C',
|
|
'\u1E08': 'C',
|
|
'\u0187': 'C',
|
|
'\u023B': 'C',
|
|
'\uA73E': 'C',
|
|
'\u24B9': 'D',
|
|
'\uFF24': 'D',
|
|
'\u1E0A': 'D',
|
|
'\u010E': 'D',
|
|
'\u1E0C': 'D',
|
|
'\u1E10': 'D',
|
|
'\u1E12': 'D',
|
|
'\u1E0E': 'D',
|
|
'\u0110': 'D',
|
|
'\u018B': 'D',
|
|
'\u018A': 'D',
|
|
'\u0189': 'D',
|
|
'\uA779': 'D',
|
|
'\u01F1': 'DZ',
|
|
'\u01C4': 'DZ',
|
|
'\u01F2': 'Dz',
|
|
'\u01C5': 'Dz',
|
|
'\u24BA': 'E',
|
|
'\uFF25': 'E',
|
|
'\u00C8': 'E',
|
|
'\u00C9': 'E',
|
|
'\u00CA': 'E',
|
|
'\u1EC0': 'E',
|
|
'\u1EBE': 'E',
|
|
'\u1EC4': 'E',
|
|
'\u1EC2': 'E',
|
|
'\u1EBC': 'E',
|
|
'\u0112': 'E',
|
|
'\u1E14': 'E',
|
|
'\u1E16': 'E',
|
|
'\u0114': 'E',
|
|
'\u0116': 'E',
|
|
'\u00CB': 'E',
|
|
'\u1EBA': 'E',
|
|
'\u011A': 'E',
|
|
'\u0204': 'E',
|
|
'\u0206': 'E',
|
|
'\u1EB8': 'E',
|
|
'\u1EC6': 'E',
|
|
'\u0228': 'E',
|
|
'\u1E1C': 'E',
|
|
'\u0118': 'E',
|
|
'\u1E18': 'E',
|
|
'\u1E1A': 'E',
|
|
'\u0190': 'E',
|
|
'\u018E': 'E',
|
|
'\u24BB': 'F',
|
|
'\uFF26': 'F',
|
|
'\u1E1E': 'F',
|
|
'\u0191': 'F',
|
|
'\uA77B': 'F',
|
|
'\u24BC': 'G',
|
|
'\uFF27': 'G',
|
|
'\u01F4': 'G',
|
|
'\u011C': 'G',
|
|
'\u1E20': 'G',
|
|
'\u011E': 'G',
|
|
'\u0120': 'G',
|
|
'\u01E6': 'G',
|
|
'\u0122': 'G',
|
|
'\u01E4': 'G',
|
|
'\u0193': 'G',
|
|
'\uA7A0': 'G',
|
|
'\uA77D': 'G',
|
|
'\uA77E': 'G',
|
|
'\u24BD': 'H',
|
|
'\uFF28': 'H',
|
|
'\u0124': 'H',
|
|
'\u1E22': 'H',
|
|
'\u1E26': 'H',
|
|
'\u021E': 'H',
|
|
'\u1E24': 'H',
|
|
'\u1E28': 'H',
|
|
'\u1E2A': 'H',
|
|
'\u0126': 'H',
|
|
'\u2C67': 'H',
|
|
'\u2C75': 'H',
|
|
'\uA78D': 'H',
|
|
'\u24BE': 'I',
|
|
'\uFF29': 'I',
|
|
'\u00CC': 'I',
|
|
'\u00CD': 'I',
|
|
'\u00CE': 'I',
|
|
'\u0128': 'I',
|
|
'\u012A': 'I',
|
|
'\u012C': 'I',
|
|
'\u0130': 'I',
|
|
'\u00CF': 'I',
|
|
'\u1E2E': 'I',
|
|
'\u1EC8': 'I',
|
|
'\u01CF': 'I',
|
|
'\u0208': 'I',
|
|
'\u020A': 'I',
|
|
'\u1ECA': 'I',
|
|
'\u012E': 'I',
|
|
'\u1E2C': 'I',
|
|
'\u0197': 'I',
|
|
'\u24BF': 'J',
|
|
'\uFF2A': 'J',
|
|
'\u0134': 'J',
|
|
'\u0248': 'J',
|
|
'\u24C0': 'K',
|
|
'\uFF2B': 'K',
|
|
'\u1E30': 'K',
|
|
'\u01E8': 'K',
|
|
'\u1E32': 'K',
|
|
'\u0136': 'K',
|
|
'\u1E34': 'K',
|
|
'\u0198': 'K',
|
|
'\u2C69': 'K',
|
|
'\uA740': 'K',
|
|
'\uA742': 'K',
|
|
'\uA744': 'K',
|
|
'\uA7A2': 'K',
|
|
'\u24C1': 'L',
|
|
'\uFF2C': 'L',
|
|
'\u013F': 'L',
|
|
'\u0139': 'L',
|
|
'\u013D': 'L',
|
|
'\u1E36': 'L',
|
|
'\u1E38': 'L',
|
|
'\u013B': 'L',
|
|
'\u1E3C': 'L',
|
|
'\u1E3A': 'L',
|
|
'\u0141': 'L',
|
|
'\u023D': 'L',
|
|
'\u2C62': 'L',
|
|
'\u2C60': 'L',
|
|
'\uA748': 'L',
|
|
'\uA746': 'L',
|
|
'\uA780': 'L',
|
|
'\u01C7': 'LJ',
|
|
'\u01C8': 'Lj',
|
|
'\u24C2': 'M',
|
|
'\uFF2D': 'M',
|
|
'\u1E3E': 'M',
|
|
'\u1E40': 'M',
|
|
'\u1E42': 'M',
|
|
'\u2C6E': 'M',
|
|
'\u019C': 'M',
|
|
'\u24C3': 'N',
|
|
'\uFF2E': 'N',
|
|
'\u01F8': 'N',
|
|
'\u0143': 'N',
|
|
'\u00D1': 'N',
|
|
'\u1E44': 'N',
|
|
'\u0147': 'N',
|
|
'\u1E46': 'N',
|
|
'\u0145': 'N',
|
|
'\u1E4A': 'N',
|
|
'\u1E48': 'N',
|
|
'\u0220': 'N',
|
|
'\u019D': 'N',
|
|
'\uA790': 'N',
|
|
'\uA7A4': 'N',
|
|
'\u01CA': 'NJ',
|
|
'\u01CB': 'Nj',
|
|
'\u24C4': 'O',
|
|
'\uFF2F': 'O',
|
|
'\u00D2': 'O',
|
|
'\u00D3': 'O',
|
|
'\u00D4': 'O',
|
|
'\u1ED2': 'O',
|
|
'\u1ED0': 'O',
|
|
'\u1ED6': 'O',
|
|
'\u1ED4': 'O',
|
|
'\u00D5': 'O',
|
|
'\u1E4C': 'O',
|
|
'\u022C': 'O',
|
|
'\u1E4E': 'O',
|
|
'\u014C': 'O',
|
|
'\u1E50': 'O',
|
|
'\u1E52': 'O',
|
|
'\u014E': 'O',
|
|
'\u022E': 'O',
|
|
'\u0230': 'O',
|
|
'\u00D6': 'O',
|
|
'\u022A': 'O',
|
|
'\u1ECE': 'O',
|
|
'\u0150': 'O',
|
|
'\u01D1': 'O',
|
|
'\u020C': 'O',
|
|
'\u020E': 'O',
|
|
'\u01A0': 'O',
|
|
'\u1EDC': 'O',
|
|
'\u1EDA': 'O',
|
|
'\u1EE0': 'O',
|
|
'\u1EDE': 'O',
|
|
'\u1EE2': 'O',
|
|
'\u1ECC': 'O',
|
|
'\u1ED8': 'O',
|
|
'\u01EA': 'O',
|
|
'\u01EC': 'O',
|
|
'\u00D8': 'O',
|
|
'\u01FE': 'O',
|
|
'\u0186': 'O',
|
|
'\u019F': 'O',
|
|
'\uA74A': 'O',
|
|
'\uA74C': 'O',
|
|
'\u01A2': 'OI',
|
|
'\uA74E': 'OO',
|
|
'\u0222': 'OU',
|
|
'\u24C5': 'P',
|
|
'\uFF30': 'P',
|
|
'\u1E54': 'P',
|
|
'\u1E56': 'P',
|
|
'\u01A4': 'P',
|
|
'\u2C63': 'P',
|
|
'\uA750': 'P',
|
|
'\uA752': 'P',
|
|
'\uA754': 'P',
|
|
'\u24C6': 'Q',
|
|
'\uFF31': 'Q',
|
|
'\uA756': 'Q',
|
|
'\uA758': 'Q',
|
|
'\u024A': 'Q',
|
|
'\u24C7': 'R',
|
|
'\uFF32': 'R',
|
|
'\u0154': 'R',
|
|
'\u1E58': 'R',
|
|
'\u0158': 'R',
|
|
'\u0210': 'R',
|
|
'\u0212': 'R',
|
|
'\u1E5A': 'R',
|
|
'\u1E5C': 'R',
|
|
'\u0156': 'R',
|
|
'\u1E5E': 'R',
|
|
'\u024C': 'R',
|
|
'\u2C64': 'R',
|
|
'\uA75A': 'R',
|
|
'\uA7A6': 'R',
|
|
'\uA782': 'R',
|
|
'\u24C8': 'S',
|
|
'\uFF33': 'S',
|
|
'\u1E9E': 'S',
|
|
'\u015A': 'S',
|
|
'\u1E64': 'S',
|
|
'\u015C': 'S',
|
|
'\u1E60': 'S',
|
|
'\u0160': 'S',
|
|
'\u1E66': 'S',
|
|
'\u1E62': 'S',
|
|
'\u1E68': 'S',
|
|
'\u0218': 'S',
|
|
'\u015E': 'S',
|
|
'\u2C7E': 'S',
|
|
'\uA7A8': 'S',
|
|
'\uA784': 'S',
|
|
'\u24C9': 'T',
|
|
'\uFF34': 'T',
|
|
'\u1E6A': 'T',
|
|
'\u0164': 'T',
|
|
'\u1E6C': 'T',
|
|
'\u021A': 'T',
|
|
'\u0162': 'T',
|
|
'\u1E70': 'T',
|
|
'\u1E6E': 'T',
|
|
'\u0166': 'T',
|
|
'\u01AC': 'T',
|
|
'\u01AE': 'T',
|
|
'\u023E': 'T',
|
|
'\uA786': 'T',
|
|
'\uA728': 'TZ',
|
|
'\u24CA': 'U',
|
|
'\uFF35': 'U',
|
|
'\u00D9': 'U',
|
|
'\u00DA': 'U',
|
|
'\u00DB': 'U',
|
|
'\u0168': 'U',
|
|
'\u1E78': 'U',
|
|
'\u016A': 'U',
|
|
'\u1E7A': 'U',
|
|
'\u016C': 'U',
|
|
'\u00DC': 'U',
|
|
'\u01DB': 'U',
|
|
'\u01D7': 'U',
|
|
'\u01D5': 'U',
|
|
'\u01D9': 'U',
|
|
'\u1EE6': 'U',
|
|
'\u016E': 'U',
|
|
'\u0170': 'U',
|
|
'\u01D3': 'U',
|
|
'\u0214': 'U',
|
|
'\u0216': 'U',
|
|
'\u01AF': 'U',
|
|
'\u1EEA': 'U',
|
|
'\u1EE8': 'U',
|
|
'\u1EEE': 'U',
|
|
'\u1EEC': 'U',
|
|
'\u1EF0': 'U',
|
|
'\u1EE4': 'U',
|
|
'\u1E72': 'U',
|
|
'\u0172': 'U',
|
|
'\u1E76': 'U',
|
|
'\u1E74': 'U',
|
|
'\u0244': 'U',
|
|
'\u24CB': 'V',
|
|
'\uFF36': 'V',
|
|
'\u1E7C': 'V',
|
|
'\u1E7E': 'V',
|
|
'\u01B2': 'V',
|
|
'\uA75E': 'V',
|
|
'\u0245': 'V',
|
|
'\uA760': 'VY',
|
|
'\u24CC': 'W',
|
|
'\uFF37': 'W',
|
|
'\u1E80': 'W',
|
|
'\u1E82': 'W',
|
|
'\u0174': 'W',
|
|
'\u1E86': 'W',
|
|
'\u1E84': 'W',
|
|
'\u1E88': 'W',
|
|
'\u2C72': 'W',
|
|
'\u24CD': 'X',
|
|
'\uFF38': 'X',
|
|
'\u1E8A': 'X',
|
|
'\u1E8C': 'X',
|
|
'\u24CE': 'Y',
|
|
'\uFF39': 'Y',
|
|
'\u1EF2': 'Y',
|
|
'\u00DD': 'Y',
|
|
'\u0176': 'Y',
|
|
'\u1EF8': 'Y',
|
|
'\u0232': 'Y',
|
|
'\u1E8E': 'Y',
|
|
'\u0178': 'Y',
|
|
'\u1EF6': 'Y',
|
|
'\u1EF4': 'Y',
|
|
'\u01B3': 'Y',
|
|
'\u024E': 'Y',
|
|
'\u1EFE': 'Y',
|
|
'\u24CF': 'Z',
|
|
'\uFF3A': 'Z',
|
|
'\u0179': 'Z',
|
|
'\u1E90': 'Z',
|
|
'\u017B': 'Z',
|
|
'\u017D': 'Z',
|
|
'\u1E92': 'Z',
|
|
'\u1E94': 'Z',
|
|
'\u01B5': 'Z',
|
|
'\u0224': 'Z',
|
|
'\u2C7F': 'Z',
|
|
'\u2C6B': 'Z',
|
|
'\uA762': 'Z',
|
|
'\u24D0': 'a',
|
|
'\uFF41': 'a',
|
|
'\u1E9A': 'a',
|
|
'\u00E0': 'a',
|
|
'\u00E1': 'a',
|
|
'\u00E2': 'a',
|
|
'\u1EA7': 'a',
|
|
'\u1EA5': 'a',
|
|
'\u1EAB': 'a',
|
|
'\u1EA9': 'a',
|
|
'\u00E3': 'a',
|
|
'\u0101': 'a',
|
|
'\u0103': 'a',
|
|
'\u1EB1': 'a',
|
|
'\u1EAF': 'a',
|
|
'\u1EB5': 'a',
|
|
'\u1EB3': 'a',
|
|
'\u0227': 'a',
|
|
'\u01E1': 'a',
|
|
'\u00E4': 'a',
|
|
'\u01DF': 'a',
|
|
'\u1EA3': 'a',
|
|
'\u00E5': 'a',
|
|
'\u01FB': 'a',
|
|
'\u01CE': 'a',
|
|
'\u0201': 'a',
|
|
'\u0203': 'a',
|
|
'\u1EA1': 'a',
|
|
'\u1EAD': 'a',
|
|
'\u1EB7': 'a',
|
|
'\u1E01': 'a',
|
|
'\u0105': 'a',
|
|
'\u2C65': 'a',
|
|
'\u0250': 'a',
|
|
'\uA733': 'aa',
|
|
'\u00E6': 'ae',
|
|
'\u01FD': 'ae',
|
|
'\u01E3': 'ae',
|
|
'\uA735': 'ao',
|
|
'\uA737': 'au',
|
|
'\uA739': 'av',
|
|
'\uA73B': 'av',
|
|
'\uA73D': 'ay',
|
|
'\u24D1': 'b',
|
|
'\uFF42': 'b',
|
|
'\u1E03': 'b',
|
|
'\u1E05': 'b',
|
|
'\u1E07': 'b',
|
|
'\u0180': 'b',
|
|
'\u0183': 'b',
|
|
'\u0253': 'b',
|
|
'\u24D2': 'c',
|
|
'\uFF43': 'c',
|
|
'\u0107': 'c',
|
|
'\u0109': 'c',
|
|
'\u010B': 'c',
|
|
'\u010D': 'c',
|
|
'\u00E7': 'c',
|
|
'\u1E09': 'c',
|
|
'\u0188': 'c',
|
|
'\u023C': 'c',
|
|
'\uA73F': 'c',
|
|
'\u2184': 'c',
|
|
'\u24D3': 'd',
|
|
'\uFF44': 'd',
|
|
'\u1E0B': 'd',
|
|
'\u010F': 'd',
|
|
'\u1E0D': 'd',
|
|
'\u1E11': 'd',
|
|
'\u1E13': 'd',
|
|
'\u1E0F': 'd',
|
|
'\u0111': 'd',
|
|
'\u018C': 'd',
|
|
'\u0256': 'd',
|
|
'\u0257': 'd',
|
|
'\uA77A': 'd',
|
|
'\u01F3': 'dz',
|
|
'\u01C6': 'dz',
|
|
'\u24D4': 'e',
|
|
'\uFF45': 'e',
|
|
'\u00E8': 'e',
|
|
'\u00E9': 'e',
|
|
'\u00EA': 'e',
|
|
'\u1EC1': 'e',
|
|
'\u1EBF': 'e',
|
|
'\u1EC5': 'e',
|
|
'\u1EC3': 'e',
|
|
'\u1EBD': 'e',
|
|
'\u0113': 'e',
|
|
'\u1E15': 'e',
|
|
'\u1E17': 'e',
|
|
'\u0115': 'e',
|
|
'\u0117': 'e',
|
|
'\u00EB': 'e',
|
|
'\u1EBB': 'e',
|
|
'\u011B': 'e',
|
|
'\u0205': 'e',
|
|
'\u0207': 'e',
|
|
'\u1EB9': 'e',
|
|
'\u1EC7': 'e',
|
|
'\u0229': 'e',
|
|
'\u1E1D': 'e',
|
|
'\u0119': 'e',
|
|
'\u1E19': 'e',
|
|
'\u1E1B': 'e',
|
|
'\u0247': 'e',
|
|
'\u025B': 'e',
|
|
'\u01DD': 'e',
|
|
'\u24D5': 'f',
|
|
'\uFF46': 'f',
|
|
'\u1E1F': 'f',
|
|
'\u0192': 'f',
|
|
'\uA77C': 'f',
|
|
'\u24D6': 'g',
|
|
'\uFF47': 'g',
|
|
'\u01F5': 'g',
|
|
'\u011D': 'g',
|
|
'\u1E21': 'g',
|
|
'\u011F': 'g',
|
|
'\u0121': 'g',
|
|
'\u01E7': 'g',
|
|
'\u0123': 'g',
|
|
'\u01E5': 'g',
|
|
'\u0260': 'g',
|
|
'\uA7A1': 'g',
|
|
'\u1D79': 'g',
|
|
'\uA77F': 'g',
|
|
'\u24D7': 'h',
|
|
'\uFF48': 'h',
|
|
'\u0125': 'h',
|
|
'\u1E23': 'h',
|
|
'\u1E27': 'h',
|
|
'\u021F': 'h',
|
|
'\u1E25': 'h',
|
|
'\u1E29': 'h',
|
|
'\u1E2B': 'h',
|
|
'\u1E96': 'h',
|
|
'\u0127': 'h',
|
|
'\u2C68': 'h',
|
|
'\u2C76': 'h',
|
|
'\u0265': 'h',
|
|
'\u0195': 'hv',
|
|
'\u24D8': 'i',
|
|
'\uFF49': 'i',
|
|
'\u00EC': 'i',
|
|
'\u00ED': 'i',
|
|
'\u00EE': 'i',
|
|
'\u0129': 'i',
|
|
'\u012B': 'i',
|
|
'\u012D': 'i',
|
|
'\u00EF': 'i',
|
|
'\u1E2F': 'i',
|
|
'\u1EC9': 'i',
|
|
'\u01D0': 'i',
|
|
'\u0209': 'i',
|
|
'\u020B': 'i',
|
|
'\u1ECB': 'i',
|
|
'\u012F': 'i',
|
|
'\u1E2D': 'i',
|
|
'\u0268': 'i',
|
|
'\u0131': 'i',
|
|
'\u24D9': 'j',
|
|
'\uFF4A': 'j',
|
|
'\u0135': 'j',
|
|
'\u01F0': 'j',
|
|
'\u0249': 'j',
|
|
'\u24DA': 'k',
|
|
'\uFF4B': 'k',
|
|
'\u1E31': 'k',
|
|
'\u01E9': 'k',
|
|
'\u1E33': 'k',
|
|
'\u0137': 'k',
|
|
'\u1E35': 'k',
|
|
'\u0199': 'k',
|
|
'\u2C6A': 'k',
|
|
'\uA741': 'k',
|
|
'\uA743': 'k',
|
|
'\uA745': 'k',
|
|
'\uA7A3': 'k',
|
|
'\u24DB': 'l',
|
|
'\uFF4C': 'l',
|
|
'\u0140': 'l',
|
|
'\u013A': 'l',
|
|
'\u013E': 'l',
|
|
'\u1E37': 'l',
|
|
'\u1E39': 'l',
|
|
'\u013C': 'l',
|
|
'\u1E3D': 'l',
|
|
'\u1E3B': 'l',
|
|
'\u017F': 'l',
|
|
'\u0142': 'l',
|
|
'\u019A': 'l',
|
|
'\u026B': 'l',
|
|
'\u2C61': 'l',
|
|
'\uA749': 'l',
|
|
'\uA781': 'l',
|
|
'\uA747': 'l',
|
|
'\u01C9': 'lj',
|
|
'\u24DC': 'm',
|
|
'\uFF4D': 'm',
|
|
'\u1E3F': 'm',
|
|
'\u1E41': 'm',
|
|
'\u1E43': 'm',
|
|
'\u0271': 'm',
|
|
'\u026F': 'm',
|
|
'\u24DD': 'n',
|
|
'\uFF4E': 'n',
|
|
'\u01F9': 'n',
|
|
'\u0144': 'n',
|
|
'\u00F1': 'n',
|
|
'\u1E45': 'n',
|
|
'\u0148': 'n',
|
|
'\u1E47': 'n',
|
|
'\u0146': 'n',
|
|
'\u1E4B': 'n',
|
|
'\u1E49': 'n',
|
|
'\u019E': 'n',
|
|
'\u0272': 'n',
|
|
'\u0149': 'n',
|
|
'\uA791': 'n',
|
|
'\uA7A5': 'n',
|
|
'\u01CC': 'nj',
|
|
'\u24DE': 'o',
|
|
'\uFF4F': 'o',
|
|
'\u00F2': 'o',
|
|
'\u00F3': 'o',
|
|
'\u00F4': 'o',
|
|
'\u1ED3': 'o',
|
|
'\u1ED1': 'o',
|
|
'\u1ED7': 'o',
|
|
'\u1ED5': 'o',
|
|
'\u00F5': 'o',
|
|
'\u1E4D': 'o',
|
|
'\u022D': 'o',
|
|
'\u1E4F': 'o',
|
|
'\u014D': 'o',
|
|
'\u1E51': 'o',
|
|
'\u1E53': 'o',
|
|
'\u014F': 'o',
|
|
'\u022F': 'o',
|
|
'\u0231': 'o',
|
|
'\u00F6': 'o',
|
|
'\u022B': 'o',
|
|
'\u1ECF': 'o',
|
|
'\u0151': 'o',
|
|
'\u01D2': 'o',
|
|
'\u020D': 'o',
|
|
'\u020F': 'o',
|
|
'\u01A1': 'o',
|
|
'\u1EDD': 'o',
|
|
'\u1EDB': 'o',
|
|
'\u1EE1': 'o',
|
|
'\u1EDF': 'o',
|
|
'\u1EE3': 'o',
|
|
'\u1ECD': 'o',
|
|
'\u1ED9': 'o',
|
|
'\u01EB': 'o',
|
|
'\u01ED': 'o',
|
|
'\u00F8': 'o',
|
|
'\u01FF': 'o',
|
|
'\u0254': 'o',
|
|
'\uA74B': 'o',
|
|
'\uA74D': 'o',
|
|
'\u0275': 'o',
|
|
'\u01A3': 'oi',
|
|
'\u0223': 'ou',
|
|
'\uA74F': 'oo',
|
|
'\u24DF': 'p',
|
|
'\uFF50': 'p',
|
|
'\u1E55': 'p',
|
|
'\u1E57': 'p',
|
|
'\u01A5': 'p',
|
|
'\u1D7D': 'p',
|
|
'\uA751': 'p',
|
|
'\uA753': 'p',
|
|
'\uA755': 'p',
|
|
'\u24E0': 'q',
|
|
'\uFF51': 'q',
|
|
'\u024B': 'q',
|
|
'\uA757': 'q',
|
|
'\uA759': 'q',
|
|
'\u24E1': 'r',
|
|
'\uFF52': 'r',
|
|
'\u0155': 'r',
|
|
'\u1E59': 'r',
|
|
'\u0159': 'r',
|
|
'\u0211': 'r',
|
|
'\u0213': 'r',
|
|
'\u1E5B': 'r',
|
|
'\u1E5D': 'r',
|
|
'\u0157': 'r',
|
|
'\u1E5F': 'r',
|
|
'\u024D': 'r',
|
|
'\u027D': 'r',
|
|
'\uA75B': 'r',
|
|
'\uA7A7': 'r',
|
|
'\uA783': 'r',
|
|
'\u24E2': 's',
|
|
'\uFF53': 's',
|
|
'\u00DF': 's',
|
|
'\u015B': 's',
|
|
'\u1E65': 's',
|
|
'\u015D': 's',
|
|
'\u1E61': 's',
|
|
'\u0161': 's',
|
|
'\u1E67': 's',
|
|
'\u1E63': 's',
|
|
'\u1E69': 's',
|
|
'\u0219': 's',
|
|
'\u015F': 's',
|
|
'\u023F': 's',
|
|
'\uA7A9': 's',
|
|
'\uA785': 's',
|
|
'\u1E9B': 's',
|
|
'\u24E3': 't',
|
|
'\uFF54': 't',
|
|
'\u1E6B': 't',
|
|
'\u1E97': 't',
|
|
'\u0165': 't',
|
|
'\u1E6D': 't',
|
|
'\u021B': 't',
|
|
'\u0163': 't',
|
|
'\u1E71': 't',
|
|
'\u1E6F': 't',
|
|
'\u0167': 't',
|
|
'\u01AD': 't',
|
|
'\u0288': 't',
|
|
'\u2C66': 't',
|
|
'\uA787': 't',
|
|
'\uA729': 'tz',
|
|
'\u24E4': 'u',
|
|
'\uFF55': 'u',
|
|
'\u00F9': 'u',
|
|
'\u00FA': 'u',
|
|
'\u00FB': 'u',
|
|
'\u0169': 'u',
|
|
'\u1E79': 'u',
|
|
'\u016B': 'u',
|
|
'\u1E7B': 'u',
|
|
'\u016D': 'u',
|
|
'\u00FC': 'u',
|
|
'\u01DC': 'u',
|
|
'\u01D8': 'u',
|
|
'\u01D6': 'u',
|
|
'\u01DA': 'u',
|
|
'\u1EE7': 'u',
|
|
'\u016F': 'u',
|
|
'\u0171': 'u',
|
|
'\u01D4': 'u',
|
|
'\u0215': 'u',
|
|
'\u0217': 'u',
|
|
'\u01B0': 'u',
|
|
'\u1EEB': 'u',
|
|
'\u1EE9': 'u',
|
|
'\u1EEF': 'u',
|
|
'\u1EED': 'u',
|
|
'\u1EF1': 'u',
|
|
'\u1EE5': 'u',
|
|
'\u1E73': 'u',
|
|
'\u0173': 'u',
|
|
'\u1E77': 'u',
|
|
'\u1E75': 'u',
|
|
'\u0289': 'u',
|
|
'\u24E5': 'v',
|
|
'\uFF56': 'v',
|
|
'\u1E7D': 'v',
|
|
'\u1E7F': 'v',
|
|
'\u028B': 'v',
|
|
'\uA75F': 'v',
|
|
'\u028C': 'v',
|
|
'\uA761': 'vy',
|
|
'\u24E6': 'w',
|
|
'\uFF57': 'w',
|
|
'\u1E81': 'w',
|
|
'\u1E83': 'w',
|
|
'\u0175': 'w',
|
|
'\u1E87': 'w',
|
|
'\u1E85': 'w',
|
|
'\u1E98': 'w',
|
|
'\u1E89': 'w',
|
|
'\u2C73': 'w',
|
|
'\u24E7': 'x',
|
|
'\uFF58': 'x',
|
|
'\u1E8B': 'x',
|
|
'\u1E8D': 'x',
|
|
'\u24E8': 'y',
|
|
'\uFF59': 'y',
|
|
'\u1EF3': 'y',
|
|
'\u00FD': 'y',
|
|
'\u0177': 'y',
|
|
'\u1EF9': 'y',
|
|
'\u0233': 'y',
|
|
'\u1E8F': 'y',
|
|
'\u00FF': 'y',
|
|
'\u1EF7': 'y',
|
|
'\u1E99': 'y',
|
|
'\u1EF5': 'y',
|
|
'\u01B4': 'y',
|
|
'\u024F': 'y',
|
|
'\u1EFF': 'y',
|
|
'\u24E9': 'z',
|
|
'\uFF5A': 'z',
|
|
'\u017A': 'z',
|
|
'\u1E91': 'z',
|
|
'\u017C': 'z',
|
|
'\u017E': 'z',
|
|
'\u1E93': 'z',
|
|
'\u1E95': 'z',
|
|
'\u01B6': 'z',
|
|
'\u0225': 'z',
|
|
'\u0240': 'z',
|
|
'\u2C6C': 'z',
|
|
'\uA763': 'z',
|
|
'\u0386': '\u0391',
|
|
'\u0388': '\u0395',
|
|
'\u0389': '\u0397',
|
|
'\u038A': '\u0399',
|
|
'\u03AA': '\u0399',
|
|
'\u038C': '\u039F',
|
|
'\u038E': '\u03A5',
|
|
'\u03AB': '\u03A5',
|
|
'\u038F': '\u03A9',
|
|
'\u03AC': '\u03B1',
|
|
'\u03AD': '\u03B5',
|
|
'\u03AE': '\u03B7',
|
|
'\u03AF': '\u03B9',
|
|
'\u03CA': '\u03B9',
|
|
'\u0390': '\u03B9',
|
|
'\u03CC': '\u03BF',
|
|
'\u03CD': '\u03C5',
|
|
'\u03CB': '\u03C5',
|
|
'\u03B0': '\u03C5',
|
|
'\u03C9': '\u03C9',
|
|
'\u03C2': '\u03C3'
|
|
};
|
|
|
|
return diacritics;
|
|
});
|
|
|
|
S2.define('select2/data/base',[
|
|
'../utils'
|
|
], function (Utils) {
|
|
function BaseAdapter ($element, options) {
|
|
BaseAdapter.__super__.constructor.call(this);
|
|
}
|
|
|
|
Utils.Extend(BaseAdapter, Utils.Observable);
|
|
|
|
BaseAdapter.prototype.current = function (callback) {
|
|
throw new Error('The `current` method must be defined in child classes.');
|
|
};
|
|
|
|
BaseAdapter.prototype.query = function (params, callback) {
|
|
throw new Error('The `query` method must be defined in child classes.');
|
|
};
|
|
|
|
BaseAdapter.prototype.bind = function (container, $container) {
|
|
// Can be implemented in subclasses
|
|
};
|
|
|
|
BaseAdapter.prototype.destroy = function () {
|
|
// Can be implemented in subclasses
|
|
};
|
|
|
|
BaseAdapter.prototype.generateResultId = function (container, data) {
|
|
var id = container.id + '-result-';
|
|
|
|
id += Utils.generateChars(4);
|
|
|
|
if (data.id != null) {
|
|
id += '-' + data.id.toString();
|
|
} else {
|
|
id += '-' + Utils.generateChars(4);
|
|
}
|
|
return id;
|
|
};
|
|
|
|
return BaseAdapter;
|
|
});
|
|
|
|
S2.define('select2/data/select',[
|
|
'./base',
|
|
'../utils',
|
|
'jquery'
|
|
], function (BaseAdapter, Utils, $) {
|
|
function SelectAdapter ($element, options) {
|
|
this.$element = $element;
|
|
this.options = options;
|
|
|
|
SelectAdapter.__super__.constructor.call(this);
|
|
}
|
|
|
|
Utils.Extend(SelectAdapter, BaseAdapter);
|
|
|
|
SelectAdapter.prototype.current = function (callback) {
|
|
var data = [];
|
|
var self = this;
|
|
|
|
this.$element.find(':selected').each(function () {
|
|
var $option = $(this);
|
|
|
|
var option = self.item($option);
|
|
|
|
data.push(option);
|
|
});
|
|
|
|
callback(data);
|
|
};
|
|
|
|
SelectAdapter.prototype.select = function (data) {
|
|
var self = this;
|
|
|
|
data.selected = true;
|
|
|
|
// If data.element is a DOM node, use it instead
|
|
if ($(data.element).is('option')) {
|
|
data.element.selected = true;
|
|
|
|
this.$element.trigger('change');
|
|
|
|
return;
|
|
}
|
|
|
|
if (this.$element.prop('multiple')) {
|
|
this.current(function (currentData) {
|
|
var val = [];
|
|
|
|
data = [data];
|
|
data.push.apply(data, currentData);
|
|
|
|
for (var d = 0; d < data.length; d++) {
|
|
var id = data[d].id;
|
|
|
|
if ($.inArray(id, val) === -1) {
|
|
val.push(id);
|
|
}
|
|
}
|
|
|
|
self.$element.val(val);
|
|
self.$element.trigger('change');
|
|
});
|
|
} else {
|
|
var val = data.id;
|
|
|
|
this.$element.val(val);
|
|
this.$element.trigger('change');
|
|
}
|
|
};
|
|
|
|
SelectAdapter.prototype.unselect = function (data) {
|
|
var self = this;
|
|
|
|
if (!this.$element.prop('multiple')) {
|
|
return;
|
|
}
|
|
|
|
data.selected = false;
|
|
|
|
if ($(data.element).is('option')) {
|
|
data.element.selected = false;
|
|
|
|
this.$element.trigger('change');
|
|
|
|
return;
|
|
}
|
|
|
|
this.current(function (currentData) {
|
|
var val = [];
|
|
|
|
for (var d = 0; d < currentData.length; d++) {
|
|
var id = currentData[d].id;
|
|
|
|
if (id !== data.id && $.inArray(id, val) === -1) {
|
|
val.push(id);
|
|
}
|
|
}
|
|
|
|
self.$element.val(val);
|
|
|
|
self.$element.trigger('change');
|
|
});
|
|
};
|
|
|
|
SelectAdapter.prototype.bind = function (container, $container) {
|
|
var self = this;
|
|
|
|
this.container = container;
|
|
|
|
container.on('select', function (params) {
|
|
self.select(params.data);
|
|
});
|
|
|
|
container.on('unselect', function (params) {
|
|
self.unselect(params.data);
|
|
});
|
|
};
|
|
|
|
SelectAdapter.prototype.destroy = function () {
|
|
// Remove anything added to child elements
|
|
this.$element.find('*').each(function () {
|
|
// Remove any custom data set by Select2
|
|
$.removeData(this, 'data');
|
|
});
|
|
};
|
|
|
|
SelectAdapter.prototype.query = function (params, callback) {
|
|
var data = [];
|
|
var self = this;
|
|
|
|
var $options = this.$element.children();
|
|
|
|
$options.each(function () {
|
|
var $option = $(this);
|
|
|
|
if (!$option.is('option') && !$option.is('optgroup')) {
|
|
return;
|
|
}
|
|
|
|
var option = self.item($option);
|
|
|
|
var matches = self.matches(params, option);
|
|
|
|
if (matches !== null) {
|
|
data.push(matches);
|
|
}
|
|
});
|
|
|
|
callback({
|
|
results: data
|
|
});
|
|
};
|
|
|
|
SelectAdapter.prototype.addOptions = function ($options) {
|
|
Utils.appendMany(this.$element, $options);
|
|
};
|
|
|
|
SelectAdapter.prototype.option = function (data) {
|
|
var option;
|
|
|
|
if (data.children) {
|
|
option = document.createElement('optgroup');
|
|
option.label = data.text;
|
|
} else {
|
|
option = document.createElement('option');
|
|
|
|
if (option.textContent !== undefined) {
|
|
option.textContent = data.text;
|
|
} else {
|
|
option.innerText = data.text;
|
|
}
|
|
}
|
|
|
|
if (data.id !== undefined) {
|
|
option.value = data.id;
|
|
}
|
|
|
|
if (data.disabled) {
|
|
option.disabled = true;
|
|
}
|
|
|
|
if (data.selected) {
|
|
option.selected = true;
|
|
}
|
|
|
|
if (data.title) {
|
|
option.title = data.title;
|
|
}
|
|
|
|
var $option = $(option);
|
|
|
|
var normalizedData = this._normalizeItem(data);
|
|
normalizedData.element = option;
|
|
|
|
// Override the option's data with the combined data
|
|
$.data(option, 'data', normalizedData);
|
|
|
|
return $option;
|
|
};
|
|
|
|
SelectAdapter.prototype.item = function ($option) {
|
|
var data = {};
|
|
|
|
data = $.data($option[0], 'data');
|
|
|
|
if (data != null) {
|
|
return data;
|
|
}
|
|
|
|
if ($option.is('option')) {
|
|
data = {
|
|
id: $option.val(),
|
|
text: $option.text(),
|
|
disabled: $option.prop('disabled'),
|
|
selected: $option.prop('selected'),
|
|
title: $option.prop('title')
|
|
};
|
|
} else if ($option.is('optgroup')) {
|
|
data = {
|
|
text: $option.prop('label'),
|
|
children: [],
|
|
title: $option.prop('title')
|
|
};
|
|
|
|
var $children = $option.children('option');
|
|
var children = [];
|
|
|
|
for (var c = 0; c < $children.length; c++) {
|
|
var $child = $($children[c]);
|
|
|
|
var child = this.item($child);
|
|
|
|
children.push(child);
|
|
}
|
|
|
|
data.children = children;
|
|
}
|
|
|
|
data = this._normalizeItem(data);
|
|
data.element = $option[0];
|
|
|
|
$.data($option[0], 'data', data);
|
|
|
|
return data;
|
|
};
|
|
|
|
SelectAdapter.prototype._normalizeItem = function (item) {
|
|
if (!$.isPlainObject(item)) {
|
|
item = {
|
|
id: item,
|
|
text: item
|
|
};
|
|
}
|
|
|
|
item = $.extend({}, {
|
|
text: ''
|
|
}, item);
|
|
|
|
var defaults = {
|
|
selected: false,
|
|
disabled: false
|
|
};
|
|
|
|
if (item.id != null) {
|
|
item.id = item.id.toString();
|
|
}
|
|
|
|
if (item.text != null) {
|
|
item.text = item.text.toString();
|
|
}
|
|
|
|
if (item._resultId == null && item.id && this.container != null) {
|
|
item._resultId = this.generateResultId(this.container, item);
|
|
}
|
|
|
|
return $.extend({}, defaults, item);
|
|
};
|
|
|
|
SelectAdapter.prototype.matches = function (params, data) {
|
|
var matcher = this.options.get('matcher');
|
|
|
|
return matcher(params, data);
|
|
};
|
|
|
|
return SelectAdapter;
|
|
});
|
|
|
|
S2.define('select2/data/array',[
|
|
'./select',
|
|
'../utils',
|
|
'jquery'
|
|
], function (SelectAdapter, Utils, $) {
|
|
function ArrayAdapter ($element, options) {
|
|
var data = options.get('data') || [];
|
|
|
|
ArrayAdapter.__super__.constructor.call(this, $element, options);
|
|
|
|
this.addOptions(this.convertToOptions(data));
|
|
}
|
|
|
|
Utils.Extend(ArrayAdapter, SelectAdapter);
|
|
|
|
ArrayAdapter.prototype.select = function (data) {
|
|
var $option = this.$element.find('option').filter(function (i, elm) {
|
|
return elm.value == data.id.toString();
|
|
});
|
|
|
|
if ($option.length === 0) {
|
|
$option = this.option(data);
|
|
|
|
this.addOptions($option);
|
|
}
|
|
|
|
ArrayAdapter.__super__.select.call(this, data);
|
|
};
|
|
|
|
ArrayAdapter.prototype.convertToOptions = function (data) {
|
|
var self = this;
|
|
|
|
var $existing = this.$element.find('option');
|
|
var existingIds = $existing.map(function () {
|
|
return self.item($(this)).id;
|
|
}).get();
|
|
|
|
var $options = [];
|
|
|
|
// Filter out all items except for the one passed in the argument
|
|
function onlyItem (item) {
|
|
return function () {
|
|
return $(this).val() == item.id;
|
|
};
|
|
}
|
|
|
|
for (var d = 0; d < data.length; d++) {
|
|
var item = this._normalizeItem(data[d]);
|
|
|
|
// Skip items which were pre-loaded, only merge the data
|
|
if ($.inArray(item.id, existingIds) >= 0) {
|
|
var $existingOption = $existing.filter(onlyItem(item));
|
|
|
|
var existingData = this.item($existingOption);
|
|
var newData = $.extend(true, {}, item, existingData);
|
|
|
|
var $newOption = this.option(newData);
|
|
|
|
$existingOption.replaceWith($newOption);
|
|
|
|
continue;
|
|
}
|
|
|
|
var $option = this.option(item);
|
|
|
|
if (item.children) {
|
|
var $children = this.convertToOptions(item.children);
|
|
|
|
Utils.appendMany($option, $children);
|
|
}
|
|
|
|
$options.push($option);
|
|
}
|
|
|
|
return $options;
|
|
};
|
|
|
|
return ArrayAdapter;
|
|
});
|
|
|
|
S2.define('select2/data/ajax',[
|
|
'./array',
|
|
'../utils',
|
|
'jquery'
|
|
], function (ArrayAdapter, Utils, $) {
|
|
function AjaxAdapter ($element, options) {
|
|
this.ajaxOptions = this._applyDefaults(options.get('ajax'));
|
|
|
|
if (this.ajaxOptions.processResults != null) {
|
|
this.processResults = this.ajaxOptions.processResults;
|
|
}
|
|
|
|
AjaxAdapter.__super__.constructor.call(this, $element, options);
|
|
}
|
|
|
|
Utils.Extend(AjaxAdapter, ArrayAdapter);
|
|
|
|
AjaxAdapter.prototype._applyDefaults = function (options) {
|
|
var defaults = {
|
|
data: function (params) {
|
|
return $.extend({}, params, {
|
|
q: params.term
|
|
});
|
|
},
|
|
transport: function (params, success, failure) {
|
|
var $request = $.ajax(params);
|
|
|
|
$request.then(success);
|
|
$request.fail(failure);
|
|
|
|
return $request;
|
|
}
|
|
};
|
|
|
|
return $.extend({}, defaults, options, true);
|
|
};
|
|
|
|
AjaxAdapter.prototype.processResults = function (results) {
|
|
return results;
|
|
};
|
|
|
|
AjaxAdapter.prototype.query = function (params, callback) {
|
|
var matches = [];
|
|
var self = this;
|
|
|
|
if (this._request != null) {
|
|
// JSONP requests cannot always be aborted
|
|
if ($.isFunction(this._request.abort)) {
|
|
this._request.abort();
|
|
}
|
|
|
|
this._request = null;
|
|
}
|
|
|
|
var options = $.extend({
|
|
type: 'GET'
|
|
}, this.ajaxOptions);
|
|
|
|
if (typeof options.url === 'function') {
|
|
options.url = options.url.call(this.$element, params);
|
|
}
|
|
|
|
if (typeof options.data === 'function') {
|
|
options.data = options.data.call(this.$element, params);
|
|
}
|
|
|
|
function request () {
|
|
var $request = options.transport(options, function (data) {
|
|
var results = self.processResults(data, params);
|
|
|
|
if (self.options.get('debug') && window.console && console.error) {
|
|
// Check to make sure that the response included a `results` key.
|
|
if (!results || !results.results || !$.isArray(results.results)) {
|
|
console.error(
|
|
'Select2: The AJAX results did not return an array in the ' +
|
|
'`results` key of the response.'
|
|
);
|
|
}
|
|
}
|
|
|
|
callback(results);
|
|
}, function () {
|
|
// Attempt to detect if a request was aborted
|
|
// Only works if the transport exposes a status property
|
|
if ($request.status && $request.status === '0') {
|
|
return;
|
|
}
|
|
|
|
self.trigger('results:message', {
|
|
message: 'errorLoading'
|
|
});
|
|
});
|
|
|
|
self._request = $request;
|
|
}
|
|
|
|
if (this.ajaxOptions.delay && params.term != null) {
|
|
if (this._queryTimeout) {
|
|
window.clearTimeout(this._queryTimeout);
|
|
}
|
|
|
|
this._queryTimeout = window.setTimeout(request, this.ajaxOptions.delay);
|
|
} else {
|
|
request();
|
|
}
|
|
};
|
|
|
|
return AjaxAdapter;
|
|
});
|
|
|
|
S2.define('select2/data/tags',[
|
|
'jquery'
|
|
], function ($) {
|
|
function Tags (decorated, $element, options) {
|
|
var tags = options.get('tags');
|
|
|
|
var createTag = options.get('createTag');
|
|
|
|
if (createTag !== undefined) {
|
|
this.createTag = createTag;
|
|
}
|
|
|
|
var insertTag = options.get('insertTag');
|
|
|
|
if (insertTag !== undefined) {
|
|
this.insertTag = insertTag;
|
|
}
|
|
|
|
decorated.call(this, $element, options);
|
|
|
|
if ($.isArray(tags)) {
|
|
for (var t = 0; t < tags.length; t++) {
|
|
var tag = tags[t];
|
|
var item = this._normalizeItem(tag);
|
|
|
|
var $option = this.option(item);
|
|
|
|
this.$element.append($option);
|
|
}
|
|
}
|
|
}
|
|
|
|
Tags.prototype.query = function (decorated, params, callback) {
|
|
var self = this;
|
|
|
|
this._removeOldTags();
|
|
|
|
if (params.term == null || params.page != null) {
|
|
decorated.call(this, params, callback);
|
|
return;
|
|
}
|
|
|
|
function wrapper (obj, child) {
|
|
var data = obj.results;
|
|
|
|
for (var i = 0; i < data.length; i++) {
|
|
var option = data[i];
|
|
|
|
var checkChildren = (
|
|
option.children != null &&
|
|
!wrapper({
|
|
results: option.children
|
|
}, true)
|
|
);
|
|
|
|
var optionText = (option.text || '').toUpperCase();
|
|
var paramsTerm = (params.term || '').toUpperCase();
|
|
|
|
var checkText = optionText === paramsTerm;
|
|
|
|
if (checkText || checkChildren) {
|
|
if (child) {
|
|
return false;
|
|
}
|
|
|
|
obj.data = data;
|
|
callback(obj);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (child) {
|
|
return true;
|
|
}
|
|
|
|
var tag = self.createTag(params);
|
|
|
|
if (tag != null) {
|
|
var $option = self.option(tag);
|
|
$option.attr('data-select2-tag', true);
|
|
|
|
self.addOptions([$option]);
|
|
|
|
self.insertTag(data, tag);
|
|
}
|
|
|
|
obj.results = data;
|
|
|
|
callback(obj);
|
|
}
|
|
|
|
decorated.call(this, params, wrapper);
|
|
};
|
|
|
|
Tags.prototype.createTag = function (decorated, params) {
|
|
var term = $.trim(params.term);
|
|
|
|
if (term === '') {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
id: term,
|
|
text: term
|
|
};
|
|
};
|
|
|
|
Tags.prototype.insertTag = function (_, data, tag) {
|
|
data.unshift(tag);
|
|
};
|
|
|
|
Tags.prototype._removeOldTags = function (_) {
|
|
var tag = this._lastTag;
|
|
|
|
var $options = this.$element.find('option[data-select2-tag]');
|
|
|
|
$options.each(function () {
|
|
if (this.selected) {
|
|
return;
|
|
}
|
|
|
|
$(this).remove();
|
|
});
|
|
};
|
|
|
|
return Tags;
|
|
});
|
|
|
|
S2.define('select2/data/tokenizer',[
|
|
'jquery'
|
|
], function ($) {
|
|
function Tokenizer (decorated, $element, options) {
|
|
var tokenizer = options.get('tokenizer');
|
|
|
|
if (tokenizer !== undefined) {
|
|
this.tokenizer = tokenizer;
|
|
}
|
|
|
|
decorated.call(this, $element, options);
|
|
}
|
|
|
|
Tokenizer.prototype.bind = function (decorated, container, $container) {
|
|
decorated.call(this, container, $container);
|
|
|
|
this.$search = container.dropdown.$search || container.selection.$search ||
|
|
$container.find('.select2-search__field');
|
|
};
|
|
|
|
Tokenizer.prototype.query = function (decorated, params, callback) {
|
|
var self = this;
|
|
|
|
function createAndSelect (data) {
|
|
// Normalize the data object so we can use it for checks
|
|
var item = self._normalizeItem(data);
|
|
|
|
// Check if the data object already exists as a tag
|
|
// Select it if it doesn't
|
|
var $existingOptions = self.$element.find('option').filter(function () {
|
|
return $(this).val() === item.id;
|
|
});
|
|
|
|
// If an existing option wasn't found for it, create the option
|
|
if (!$existingOptions.length) {
|
|
var $option = self.option(item);
|
|
$option.attr('data-select2-tag', true);
|
|
|
|
self._removeOldTags();
|
|
self.addOptions([$option]);
|
|
}
|
|
|
|
// Select the item, now that we know there is an option for it
|
|
select(item);
|
|
}
|
|
|
|
function select (data) {
|
|
self.trigger('select', {
|
|
data: data
|
|
});
|
|
}
|
|
|
|
params.term = params.term || '';
|
|
|
|
var tokenData = this.tokenizer(params, this.options, createAndSelect);
|
|
|
|
if (tokenData.term !== params.term) {
|
|
// Replace the search term if we have the search box
|
|
if (this.$search.length) {
|
|
this.$search.val(tokenData.term);
|
|
this.$search.focus();
|
|
}
|
|
|
|
params.term = tokenData.term;
|
|
}
|
|
|
|
decorated.call(this, params, callback);
|
|
};
|
|
|
|
Tokenizer.prototype.tokenizer = function (_, params, options, callback) {
|
|
var separators = options.get('tokenSeparators') || [];
|
|
var term = params.term;
|
|
var i = 0;
|
|
|
|
var createTag = this.createTag || function (params) {
|
|
return {
|
|
id: params.term,
|
|
text: params.term
|
|
};
|
|
};
|
|
|
|
while (i < term.length) {
|
|
var termChar = term[i];
|
|
|
|
if ($.inArray(termChar, separators) === -1) {
|
|
i++;
|
|
|
|
continue;
|
|
}
|
|
|
|
var part = term.substr(0, i);
|
|
var partParams = $.extend({}, params, {
|
|
term: part
|
|
});
|
|
|
|
var data = createTag(partParams);
|
|
|
|
if (data == null) {
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
callback(data);
|
|
|
|
// Reset the term to not include the tokenized portion
|
|
term = term.substr(i + 1) || '';
|
|
i = 0;
|
|
}
|
|
|
|
return {
|
|
term: term
|
|
};
|
|
};
|
|
|
|
return Tokenizer;
|
|
});
|
|
|
|
S2.define('select2/data/minimumInputLength',[
|
|
|
|
], function () {
|
|
function MinimumInputLength (decorated, $e, options) {
|
|
this.minimumInputLength = options.get('minimumInputLength');
|
|
|
|
decorated.call(this, $e, options);
|
|
}
|
|
|
|
MinimumInputLength.prototype.query = function (decorated, params, callback) {
|
|
params.term = params.term || '';
|
|
|
|
if (params.term.length < this.minimumInputLength) {
|
|
this.trigger('results:message', {
|
|
message: 'inputTooShort',
|
|
args: {
|
|
minimum: this.minimumInputLength,
|
|
input: params.term,
|
|
params: params
|
|
}
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
decorated.call(this, params, callback);
|
|
};
|
|
|
|
return MinimumInputLength;
|
|
});
|
|
|
|
S2.define('select2/data/maximumInputLength',[
|
|
|
|
], function () {
|
|
function MaximumInputLength (decorated, $e, options) {
|
|
this.maximumInputLength = options.get('maximumInputLength');
|
|
|
|
decorated.call(this, $e, options);
|
|
}
|
|
|
|
MaximumInputLength.prototype.query = function (decorated, params, callback) {
|
|
params.term = params.term || '';
|
|
|
|
if (this.maximumInputLength > 0 &&
|
|
params.term.length > this.maximumInputLength) {
|
|
this.trigger('results:message', {
|
|
message: 'inputTooLong',
|
|
args: {
|
|
maximum: this.maximumInputLength,
|
|
input: params.term,
|
|
params: params
|
|
}
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
decorated.call(this, params, callback);
|
|
};
|
|
|
|
return MaximumInputLength;
|
|
});
|
|
|
|
S2.define('select2/data/maximumSelectionLength',[
|
|
|
|
], function (){
|
|
function MaximumSelectionLength (decorated, $e, options) {
|
|
this.maximumSelectionLength = options.get('maximumSelectionLength');
|
|
|
|
decorated.call(this, $e, options);
|
|
}
|
|
|
|
MaximumSelectionLength.prototype.query =
|
|
function (decorated, params, callback) {
|
|
var self = this;
|
|
|
|
this.current(function (currentData) {
|
|
var count = currentData != null ? currentData.length : 0;
|
|
if (self.maximumSelectionLength > 0 &&
|
|
count >= self.maximumSelectionLength) {
|
|
self.trigger('results:message', {
|
|
message: 'maximumSelected',
|
|
args: {
|
|
maximum: self.maximumSelectionLength
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
decorated.call(self, params, callback);
|
|
});
|
|
};
|
|
|
|
return MaximumSelectionLength;
|
|
});
|
|
|
|
S2.define('select2/dropdown',[
|
|
'jquery',
|
|
'./utils'
|
|
], function ($, Utils) {
|
|
function Dropdown ($element, options) {
|
|
this.$element = $element;
|
|
this.options = options;
|
|
|
|
Dropdown.__super__.constructor.call(this);
|
|
}
|
|
|
|
Utils.Extend(Dropdown, Utils.Observable);
|
|
|
|
Dropdown.prototype.render = function () {
|
|
var $dropdown = $(
|
|
'<span class="select2-dropdown">' +
|
|
'<span class="select2-results"></span>' +
|
|
'</span>'
|
|
);
|
|
|
|
$dropdown.attr('dir', this.options.get('dir'));
|
|
|
|
this.$dropdown = $dropdown;
|
|
|
|
return $dropdown;
|
|
};
|
|
|
|
Dropdown.prototype.bind = function () {
|
|
// Should be implemented in subclasses
|
|
};
|
|
|
|
Dropdown.prototype.position = function ($dropdown, $container) {
|
|
// Should be implmented in subclasses
|
|
};
|
|
|
|
Dropdown.prototype.destroy = function () {
|
|
// Remove the dropdown from the DOM
|
|
this.$dropdown.remove();
|
|
};
|
|
|
|
return Dropdown;
|
|
});
|
|
|
|
S2.define('select2/dropdown/search',[
|
|
'jquery',
|
|
'../utils'
|
|
], function ($, Utils) {
|
|
function Search () { }
|
|
|
|
Search.prototype.render = function (decorated) {
|
|
var $rendered = decorated.call(this);
|
|
|
|
var $search = $(
|
|
'<span class="select2-search select2-search--dropdown">' +
|
|
'<input class="select2-search__field" type="search" tabindex="-1"' +
|
|
' autocomplete="off" autocorrect="off" autocapitalize="none"' +
|
|
' spellcheck="false" role="textbox" />' +
|
|
'</span>'
|
|
);
|
|
|
|
this.$searchContainer = $search;
|
|
this.$search = $search.find('input');
|
|
|
|
$rendered.prepend($search);
|
|
|
|
return $rendered;
|
|
};
|
|
|
|
Search.prototype.bind = function (decorated, container, $container) {
|
|
var self = this;
|
|
|
|
decorated.call(this, container, $container);
|
|
|
|
this.$search.on('keydown', function (evt) {
|
|
self.trigger('keypress', evt);
|
|
|
|
self._keyUpPrevented = evt.isDefaultPrevented();
|
|
});
|
|
|
|
// Workaround for browsers which do not support the `input` event
|
|
// This will prevent double-triggering of events for browsers which support
|
|
// both the `keyup` and `input` events.
|
|
this.$search.on('input', function (evt) {
|
|
// Unbind the duplicated `keyup` event
|
|
$(this).off('keyup');
|
|
});
|
|
|
|
this.$search.on('keyup input', function (evt) {
|
|
self.handleSearch(evt);
|
|
});
|
|
|
|
container.on('open', function () {
|
|
self.$search.attr('tabindex', 0);
|
|
|
|
self.$search.focus();
|
|
|
|
window.setTimeout(function () {
|
|
self.$search.focus();
|
|
}, 0);
|
|
});
|
|
|
|
container.on('close', function () {
|
|
self.$search.attr('tabindex', -1);
|
|
|
|
self.$search.val('');
|
|
});
|
|
|
|
container.on('focus', function () {
|
|
if (!container.isOpen()) {
|
|
self.$search.focus();
|
|
}
|
|
});
|
|
|
|
container.on('results:all', function (params) {
|
|
if (params.query.term == null || params.query.term === '') {
|
|
var showSearch = self.showSearch(params);
|
|
|
|
if (showSearch) {
|
|
self.$searchContainer.removeClass('select2-search--hide');
|
|
} else {
|
|
self.$searchContainer.addClass('select2-search--hide');
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
Search.prototype.handleSearch = function (evt) {
|
|
if (!this._keyUpPrevented) {
|
|
var input = this.$search.val();
|
|
|
|
this.trigger('query', {
|
|
term: input
|
|
});
|
|
}
|
|
|
|
this._keyUpPrevented = false;
|
|
};
|
|
|
|
Search.prototype.showSearch = function (_, params) {
|
|
return true;
|
|
};
|
|
|
|
return Search;
|
|
});
|
|
|
|
S2.define('select2/dropdown/hidePlaceholder',[
|
|
|
|
], function () {
|
|
function HidePlaceholder (decorated, $element, options, dataAdapter) {
|
|
this.placeholder = this.normalizePlaceholder(options.get('placeholder'));
|
|
|
|
decorated.call(this, $element, options, dataAdapter);
|
|
}
|
|
|
|
HidePlaceholder.prototype.append = function (decorated, data) {
|
|
data.results = this.removePlaceholder(data.results);
|
|
|
|
decorated.call(this, data);
|
|
};
|
|
|
|
HidePlaceholder.prototype.normalizePlaceholder = function (_, placeholder) {
|
|
if (typeof placeholder === 'string') {
|
|
placeholder = {
|
|
id: '',
|
|
text: placeholder
|
|
};
|
|
}
|
|
|
|
return placeholder;
|
|
};
|
|
|
|
HidePlaceholder.prototype.removePlaceholder = function (_, data) {
|
|
var modifiedData = data.slice(0);
|
|
|
|
for (var d = data.length - 1; d >= 0; d--) {
|
|
var item = data[d];
|
|
|
|
if (this.placeholder.id === item.id) {
|
|
modifiedData.splice(d, 1);
|
|
}
|
|
}
|
|
|
|
return modifiedData;
|
|
};
|
|
|
|
return HidePlaceholder;
|
|
});
|
|
|
|
S2.define('select2/dropdown/infiniteScroll',[
|
|
'jquery'
|
|
], function ($) {
|
|
function InfiniteScroll (decorated, $element, options, dataAdapter) {
|
|
this.lastParams = {};
|
|
|
|
decorated.call(this, $element, options, dataAdapter);
|
|
|
|
this.$loadingMore = this.createLoadingMore();
|
|
this.loading = false;
|
|
}
|
|
|
|
InfiniteScroll.prototype.append = function (decorated, data) {
|
|
this.$loadingMore.remove();
|
|
this.loading = false;
|
|
|
|
decorated.call(this, data);
|
|
|
|
if (this.showLoadingMore(data)) {
|
|
this.$results.append(this.$loadingMore);
|
|
}
|
|
};
|
|
|
|
InfiniteScroll.prototype.bind = function (decorated, container, $container) {
|
|
var self = this;
|
|
|
|
decorated.call(this, container, $container);
|
|
|
|
container.on('query', function (params) {
|
|
self.lastParams = params;
|
|
self.loading = true;
|
|
});
|
|
|
|
container.on('query:append', function (params) {
|
|
self.lastParams = params;
|
|
self.loading = true;
|
|
});
|
|
|
|
this.$results.on('scroll', function () {
|
|
var isLoadMoreVisible = $.contains(
|
|
document.documentElement,
|
|
self.$loadingMore[0]
|
|
);
|
|
|
|
if (self.loading || !isLoadMoreVisible) {
|
|
return;
|
|
}
|
|
|
|
var currentOffset = self.$results.offset().top +
|
|
self.$results.outerHeight(false);
|
|
var loadingMoreOffset = self.$loadingMore.offset().top +
|
|
self.$loadingMore.outerHeight(false);
|
|
|
|
if (currentOffset + 50 >= loadingMoreOffset) {
|
|
self.loadMore();
|
|
}
|
|
});
|
|
};
|
|
|
|
InfiniteScroll.prototype.loadMore = function () {
|
|
this.loading = true;
|
|
|
|
var params = $.extend({}, {page: 1}, this.lastParams);
|
|
|
|
params.page++;
|
|
|
|
this.trigger('query:append', params);
|
|
};
|
|
|
|
InfiniteScroll.prototype.showLoadingMore = function (_, data) {
|
|
return data.pagination && data.pagination.more;
|
|
};
|
|
|
|
InfiniteScroll.prototype.createLoadingMore = function () {
|
|
var $option = $(
|
|
'<li ' +
|
|
'class="select2-results__option select2-results__option--load-more"' +
|
|
'role="treeitem" aria-disabled="true"></li>'
|
|
);
|
|
|
|
var message = this.options.get('translations').get('loadingMore');
|
|
|
|
$option.html(message(this.lastParams));
|
|
|
|
return $option;
|
|
};
|
|
|
|
return InfiniteScroll;
|
|
});
|
|
|
|
S2.define('select2/dropdown/attachBody',[
|
|
'jquery',
|
|
'../utils'
|
|
], function ($, Utils) {
|
|
function AttachBody (decorated, $element, options) {
|
|
this.$dropdownParent = options.get('dropdownParent') || $(document.body);
|
|
|
|
decorated.call(this, $element, options);
|
|
}
|
|
|
|
AttachBody.prototype.bind = function (decorated, container, $container) {
|
|
var self = this;
|
|
|
|
var setupResultsEvents = false;
|
|
|
|
decorated.call(this, container, $container);
|
|
|
|
container.on('open', function () {
|
|
self._showDropdown();
|
|
self._attachPositioningHandler(container);
|
|
|
|
if (!setupResultsEvents) {
|
|
setupResultsEvents = true;
|
|
|
|
container.on('results:all', function () {
|
|
self._positionDropdown();
|
|
self._resizeDropdown();
|
|
});
|
|
|
|
container.on('results:append', function () {
|
|
self._positionDropdown();
|
|
self._resizeDropdown();
|
|
});
|
|
}
|
|
});
|
|
|
|
container.on('close', function () {
|
|
self._hideDropdown();
|
|
self._detachPositioningHandler(container);
|
|
});
|
|
|
|
this.$dropdownContainer.on('mousedown', function (evt) {
|
|
evt.stopPropagation();
|
|
});
|
|
};
|
|
|
|
AttachBody.prototype.destroy = function (decorated) {
|
|
decorated.call(this);
|
|
|
|
this.$dropdownContainer.remove();
|
|
};
|
|
|
|
AttachBody.prototype.position = function (decorated, $dropdown, $container) {
|
|
// Clone all of the container classes
|
|
$dropdown.attr('class', $container.attr('class'));
|
|
|
|
$dropdown.removeClass('select2');
|
|
$dropdown.addClass('select2-container--open');
|
|
|
|
$dropdown.css({
|
|
position: 'absolute',
|
|
top: -999999
|
|
});
|
|
|
|
this.$container = $container;
|
|
};
|
|
|
|
AttachBody.prototype.render = function (decorated) {
|
|
var $container = $('<span></span>');
|
|
|
|
var $dropdown = decorated.call(this);
|
|
$container.append($dropdown);
|
|
|
|
this.$dropdownContainer = $container;
|
|
|
|
return $container;
|
|
};
|
|
|
|
AttachBody.prototype._hideDropdown = function (decorated) {
|
|
this.$dropdownContainer.detach();
|
|
};
|
|
|
|
AttachBody.prototype._attachPositioningHandler =
|
|
function (decorated, container) {
|
|
var self = this;
|
|
|
|
var scrollEvent = 'scroll.select2.' + container.id;
|
|
var resizeEvent = 'resize.select2.' + container.id;
|
|
var orientationEvent = 'orientationchange.select2.' + container.id;
|
|
|
|
var $watchers = this.$container.parents().filter(Utils.hasScroll);
|
|
$watchers.each(function () {
|
|
$(this).data('select2-scroll-position', {
|
|
x: $(this).scrollLeft(),
|
|
y: $(this).scrollTop()
|
|
});
|
|
});
|
|
|
|
$watchers.on(scrollEvent, function (ev) {
|
|
var position = $(this).data('select2-scroll-position');
|
|
$(this).scrollTop(position.y);
|
|
});
|
|
|
|
$(window).on(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent,
|
|
function (e) {
|
|
self._positionDropdown();
|
|
self._resizeDropdown();
|
|
});
|
|
};
|
|
|
|
AttachBody.prototype._detachPositioningHandler =
|
|
function (decorated, container) {
|
|
var scrollEvent = 'scroll.select2.' + container.id;
|
|
var resizeEvent = 'resize.select2.' + container.id;
|
|
var orientationEvent = 'orientationchange.select2.' + container.id;
|
|
|
|
var $watchers = this.$container.parents().filter(Utils.hasScroll);
|
|
$watchers.off(scrollEvent);
|
|
|
|
$(window).off(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent);
|
|
};
|
|
|
|
AttachBody.prototype._positionDropdown = function () {
|
|
var $window = $(window);
|
|
|
|
var isCurrentlyAbove = this.$dropdown.hasClass('select2-dropdown--above');
|
|
var isCurrentlyBelow = this.$dropdown.hasClass('select2-dropdown--below');
|
|
|
|
var newDirection = null;
|
|
|
|
var offset = this.$container.offset();
|
|
|
|
offset.bottom = offset.top + this.$container.outerHeight(false);
|
|
|
|
var container = {
|
|
height: this.$container.outerHeight(false)
|
|
};
|
|
|
|
container.top = offset.top;
|
|
container.bottom = offset.top + container.height;
|
|
|
|
var dropdown = {
|
|
height: this.$dropdown.outerHeight(false)
|
|
};
|
|
|
|
var viewport = {
|
|
top: $window.scrollTop(),
|
|
bottom: $window.scrollTop() + $window.height()
|
|
};
|
|
|
|
var enoughRoomAbove = viewport.top < (offset.top - dropdown.height);
|
|
var enoughRoomBelow = viewport.bottom > (offset.bottom + dropdown.height);
|
|
|
|
var css = {
|
|
left: offset.left,
|
|
top: container.bottom
|
|
};
|
|
|
|
// Determine what the parent element is to use for calciulating the offset
|
|
var $offsetParent = this.$dropdownParent;
|
|
|
|
// For statically positoned elements, we need to get the element
|
|
// that is determining the offset
|
|
if ($offsetParent.css('position') === 'static') {
|
|
$offsetParent = $offsetParent.offsetParent();
|
|
}
|
|
|
|
var parentOffset = $offsetParent.offset();
|
|
|
|
css.top -= parentOffset.top;
|
|
css.left -= parentOffset.left;
|
|
|
|
if (!isCurrentlyAbove && !isCurrentlyBelow) {
|
|
newDirection = 'below';
|
|
}
|
|
|
|
if (!enoughRoomBelow && enoughRoomAbove && !isCurrentlyAbove) {
|
|
newDirection = 'above';
|
|
} else if (!enoughRoomAbove && enoughRoomBelow && isCurrentlyAbove) {
|
|
newDirection = 'below';
|
|
}
|
|
|
|
if (newDirection == 'above' ||
|
|
(isCurrentlyAbove && newDirection !== 'below')) {
|
|
css.top = container.top - parentOffset.top - dropdown.height;
|
|
}
|
|
|
|
if (newDirection != null) {
|
|
this.$dropdown
|
|
.removeClass('select2-dropdown--below select2-dropdown--above')
|
|
.addClass('select2-dropdown--' + newDirection);
|
|
this.$container
|
|
.removeClass('select2-container--below select2-container--above')
|
|
.addClass('select2-container--' + newDirection);
|
|
}
|
|
|
|
this.$dropdownContainer.css(css);
|
|
};
|
|
|
|
AttachBody.prototype._resizeDropdown = function () {
|
|
var css = {
|
|
width: this.$container.outerWidth(false) + 'px'
|
|
};
|
|
|
|
if (this.options.get('dropdownAutoWidth')) {
|
|
css.minWidth = css.width;
|
|
css.position = 'relative';
|
|
css.width = 'auto';
|
|
}
|
|
|
|
this.$dropdown.css(css);
|
|
};
|
|
|
|
AttachBody.prototype._showDropdown = function (decorated) {
|
|
this.$dropdownContainer.appendTo(this.$dropdownParent);
|
|
|
|
this._positionDropdown();
|
|
this._resizeDropdown();
|
|
};
|
|
|
|
return AttachBody;
|
|
});
|
|
|
|
S2.define('select2/dropdown/minimumResultsForSearch',[
|
|
|
|
], function () {
|
|
function countResults (data) {
|
|
var count = 0;
|
|
|
|
for (var d = 0; d < data.length; d++) {
|
|
var item = data[d];
|
|
|
|
if (item.children) {
|
|
count += countResults(item.children);
|
|
} else {
|
|
count++;
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
function MinimumResultsForSearch (decorated, $element, options, dataAdapter) {
|
|
this.minimumResultsForSearch = options.get('minimumResultsForSearch');
|
|
|
|
if (this.minimumResultsForSearch < 0) {
|
|
this.minimumResultsForSearch = Infinity;
|
|
}
|
|
|
|
decorated.call(this, $element, options, dataAdapter);
|
|
}
|
|
|
|
MinimumResultsForSearch.prototype.showSearch = function (decorated, params) {
|
|
if (countResults(params.data.results) < this.minimumResultsForSearch) {
|
|
return false;
|
|
}
|
|
|
|
return decorated.call(this, params);
|
|
};
|
|
|
|
return MinimumResultsForSearch;
|
|
});
|
|
|
|
S2.define('select2/dropdown/selectOnClose',[
|
|
|
|
], function () {
|
|
function SelectOnClose () { }
|
|
|
|
SelectOnClose.prototype.bind = function (decorated, container, $container) {
|
|
var self = this;
|
|
|
|
decorated.call(this, container, $container);
|
|
|
|
container.on('close', function (params) {
|
|
self._handleSelectOnClose(params);
|
|
});
|
|
};
|
|
|
|
SelectOnClose.prototype._handleSelectOnClose = function (_, params) {
|
|
if (params && params.originalSelect2Event != null) {
|
|
var event = params.originalSelect2Event;
|
|
|
|
// Don't select an item if the close event was triggered from a select or
|
|
// unselect event
|
|
if (event._type === 'select' || event._type === 'unselect') {
|
|
return;
|
|
}
|
|
}
|
|
|
|
var $highlightedResults = this.getHighlightedResults();
|
|
|
|
// Only select highlighted results
|
|
if ($highlightedResults.length < 1) {
|
|
return;
|
|
}
|
|
|
|
var data = $highlightedResults.data('data');
|
|
|
|
// Don't re-select already selected resulte
|
|
if (
|
|
(data.element != null && data.element.selected) ||
|
|
(data.element == null && data.selected)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
this.trigger('select', {
|
|
data: data
|
|
});
|
|
};
|
|
|
|
return SelectOnClose;
|
|
});
|
|
|
|
S2.define('select2/dropdown/closeOnSelect',[
|
|
|
|
], function () {
|
|
function CloseOnSelect () { }
|
|
|
|
CloseOnSelect.prototype.bind = function (decorated, container, $container) {
|
|
var self = this;
|
|
|
|
decorated.call(this, container, $container);
|
|
|
|
container.on('select', function (evt) {
|
|
self._selectTriggered(evt);
|
|
});
|
|
|
|
container.on('unselect', function (evt) {
|
|
self._selectTriggered(evt);
|
|
});
|
|
};
|
|
|
|
CloseOnSelect.prototype._selectTriggered = function (_, evt) {
|
|
var originalEvent = evt.originalEvent;
|
|
|
|
// Don't close if the control key is being held
|
|
if (originalEvent && originalEvent.ctrlKey) {
|
|
return;
|
|
}
|
|
|
|
this.trigger('close', {
|
|
originalEvent: originalEvent,
|
|
originalSelect2Event: evt
|
|
});
|
|
};
|
|
|
|
return CloseOnSelect;
|
|
});
|
|
|
|
S2.define('select2/i18n/en',[],function () {
|
|
// English
|
|
return {
|
|
errorLoading: function () {
|
|
return 'The results could not be loaded.';
|
|
},
|
|
inputTooLong: function (args) {
|
|
var overChars = args.input.length - args.maximum;
|
|
|
|
var message = 'Please delete ' + overChars + ' character';
|
|
|
|
if (overChars != 1) {
|
|
message += 's';
|
|
}
|
|
|
|
return message;
|
|
},
|
|
inputTooShort: function (args) {
|
|
var remainingChars = args.minimum - args.input.length;
|
|
|
|
var message = 'Please enter ' + remainingChars + ' or more characters';
|
|
|
|
return message;
|
|
},
|
|
loadingMore: function () {
|
|
return 'Loading more results…';
|
|
},
|
|
maximumSelected: function (args) {
|
|
var message = 'You can only select ' + args.maximum + ' item';
|
|
|
|
if (args.maximum != 1) {
|
|
message += 's';
|
|
}
|
|
|
|
return message;
|
|
},
|
|
noResults: function () {
|
|
return 'No results found';
|
|
},
|
|
searching: function () {
|
|
return 'Searching…';
|
|
}
|
|
};
|
|
});
|
|
|
|
S2.define('select2/defaults',[
|
|
'jquery',
|
|
'require',
|
|
|
|
'./results',
|
|
|
|
'./selection/single',
|
|
'./selection/multiple',
|
|
'./selection/placeholder',
|
|
'./selection/allowClear',
|
|
'./selection/search',
|
|
'./selection/eventRelay',
|
|
|
|
'./utils',
|
|
'./translation',
|
|
'./diacritics',
|
|
|
|
'./data/select',
|
|
'./data/array',
|
|
'./data/ajax',
|
|
'./data/tags',
|
|
'./data/tokenizer',
|
|
'./data/minimumInputLength',
|
|
'./data/maximumInputLength',
|
|
'./data/maximumSelectionLength',
|
|
|
|
'./dropdown',
|
|
'./dropdown/search',
|
|
'./dropdown/hidePlaceholder',
|
|
'./dropdown/infiniteScroll',
|
|
'./dropdown/attachBody',
|
|
'./dropdown/minimumResultsForSearch',
|
|
'./dropdown/selectOnClose',
|
|
'./dropdown/closeOnSelect',
|
|
|
|
'./i18n/en'
|
|
], function ($, require,
|
|
|
|
ResultsList,
|
|
|
|
SingleSelection, MultipleSelection, Placeholder, AllowClear,
|
|
SelectionSearch, EventRelay,
|
|
|
|
Utils, Translation, DIACRITICS,
|
|
|
|
SelectData, ArrayData, AjaxData, Tags, Tokenizer,
|
|
MinimumInputLength, MaximumInputLength, MaximumSelectionLength,
|
|
|
|
Dropdown, DropdownSearch, HidePlaceholder, InfiniteScroll,
|
|
AttachBody, MinimumResultsForSearch, SelectOnClose, CloseOnSelect,
|
|
|
|
EnglishTranslation) {
|
|
function Defaults () {
|
|
this.reset();
|
|
}
|
|
|
|
Defaults.prototype.apply = function (options) {
|
|
options = $.extend(true, {}, this.defaults, options);
|
|
|
|
if (options.dataAdapter == null) {
|
|
if (options.ajax != null) {
|
|
options.dataAdapter = AjaxData;
|
|
} else if (options.data != null) {
|
|
options.dataAdapter = ArrayData;
|
|
} else {
|
|
options.dataAdapter = SelectData;
|
|
}
|
|
|
|
if (options.minimumInputLength > 0) {
|
|
options.dataAdapter = Utils.Decorate(
|
|
options.dataAdapter,
|
|
MinimumInputLength
|
|
);
|
|
}
|
|
|
|
if (options.maximumInputLength > 0) {
|
|
options.dataAdapter = Utils.Decorate(
|
|
options.dataAdapter,
|
|
MaximumInputLength
|
|
);
|
|
}
|
|
|
|
if (options.maximumSelectionLength > 0) {
|
|
options.dataAdapter = Utils.Decorate(
|
|
options.dataAdapter,
|
|
MaximumSelectionLength
|
|
);
|
|
}
|
|
|
|
if (options.tags) {
|
|
options.dataAdapter = Utils.Decorate(options.dataAdapter, Tags);
|
|
}
|
|
|
|
if (options.tokenSeparators != null || options.tokenizer != null) {
|
|
options.dataAdapter = Utils.Decorate(
|
|
options.dataAdapter,
|
|
Tokenizer
|
|
);
|
|
}
|
|
|
|
if (options.query != null) {
|
|
var Query = require(options.amdBase + 'compat/query');
|
|
|
|
options.dataAdapter = Utils.Decorate(
|
|
options.dataAdapter,
|
|
Query
|
|
);
|
|
}
|
|
|
|
if (options.initSelection != null) {
|
|
var InitSelection = require(options.amdBase + 'compat/initSelection');
|
|
|
|
options.dataAdapter = Utils.Decorate(
|
|
options.dataAdapter,
|
|
InitSelection
|
|
);
|
|
}
|
|
}
|
|
|
|
if (options.resultsAdapter == null) {
|
|
options.resultsAdapter = ResultsList;
|
|
|
|
if (options.ajax != null) {
|
|
options.resultsAdapter = Utils.Decorate(
|
|
options.resultsAdapter,
|
|
InfiniteScroll
|
|
);
|
|
}
|
|
|
|
if (options.placeholder != null) {
|
|
options.resultsAdapter = Utils.Decorate(
|
|
options.resultsAdapter,
|
|
HidePlaceholder
|
|
);
|
|
}
|
|
|
|
if (options.selectOnClose) {
|
|
options.resultsAdapter = Utils.Decorate(
|
|
options.resultsAdapter,
|
|
SelectOnClose
|
|
);
|
|
}
|
|
}
|
|
|
|
if (options.dropdownAdapter == null) {
|
|
if (options.multiple) {
|
|
options.dropdownAdapter = Dropdown;
|
|
} else {
|
|
var SearchableDropdown = Utils.Decorate(Dropdown, DropdownSearch);
|
|
|
|
options.dropdownAdapter = SearchableDropdown;
|
|
}
|
|
|
|
if (options.minimumResultsForSearch !== 0) {
|
|
options.dropdownAdapter = Utils.Decorate(
|
|
options.dropdownAdapter,
|
|
MinimumResultsForSearch
|
|
);
|
|
}
|
|
|
|
if (options.closeOnSelect) {
|
|
options.dropdownAdapter = Utils.Decorate(
|
|
options.dropdownAdapter,
|
|
CloseOnSelect
|
|
);
|
|
}
|
|
|
|
if (
|
|
options.dropdownCssClass != null ||
|
|
options.dropdownCss != null ||
|
|
options.adaptDropdownCssClass != null
|
|
) {
|
|
var DropdownCSS = require(options.amdBase + 'compat/dropdownCss');
|
|
|
|
options.dropdownAdapter = Utils.Decorate(
|
|
options.dropdownAdapter,
|
|
DropdownCSS
|
|
);
|
|
}
|
|
|
|
options.dropdownAdapter = Utils.Decorate(
|
|
options.dropdownAdapter,
|
|
AttachBody
|
|
);
|
|
}
|
|
|
|
if (options.selectionAdapter == null) {
|
|
if (options.multiple) {
|
|
options.selectionAdapter = MultipleSelection;
|
|
} else {
|
|
options.selectionAdapter = SingleSelection;
|
|
}
|
|
|
|
// Add the placeholder mixin if a placeholder was specified
|
|
if (options.placeholder != null) {
|
|
options.selectionAdapter = Utils.Decorate(
|
|
options.selectionAdapter,
|
|
Placeholder
|
|
);
|
|
}
|
|
|
|
if (options.allowClear) {
|
|
options.selectionAdapter = Utils.Decorate(
|
|
options.selectionAdapter,
|
|
AllowClear
|
|
);
|
|
}
|
|
|
|
if (options.multiple) {
|
|
options.selectionAdapter = Utils.Decorate(
|
|
options.selectionAdapter,
|
|
SelectionSearch
|
|
);
|
|
}
|
|
|
|
if (
|
|
options.containerCssClass != null ||
|
|
options.containerCss != null ||
|
|
options.adaptContainerCssClass != null
|
|
) {
|
|
var ContainerCSS = require(options.amdBase + 'compat/containerCss');
|
|
|
|
options.selectionAdapter = Utils.Decorate(
|
|
options.selectionAdapter,
|
|
ContainerCSS
|
|
);
|
|
}
|
|
|
|
options.selectionAdapter = Utils.Decorate(
|
|
options.selectionAdapter,
|
|
EventRelay
|
|
);
|
|
}
|
|
|
|
if (typeof options.language === 'string') {
|
|
// Check if the language is specified with a region
|
|
if (options.language.indexOf('-') > 0) {
|
|
// Extract the region information if it is included
|
|
var languageParts = options.language.split('-');
|
|
var baseLanguage = languageParts[0];
|
|
|
|
options.language = [options.language, baseLanguage];
|
|
} else {
|
|
options.language = [options.language];
|
|
}
|
|
}
|
|
|
|
if ($.isArray(options.language)) {
|
|
var languages = new Translation();
|
|
options.language.push('en');
|
|
|
|
var languageNames = options.language;
|
|
|
|
for (var l = 0; l < languageNames.length; l++) {
|
|
var name = languageNames[l];
|
|
var language = {};
|
|
|
|
try {
|
|
// Try to load it with the original name
|
|
language = Translation.loadPath(name);
|
|
} catch (e) {
|
|
try {
|
|
// If we couldn't load it, check if it wasn't the full path
|
|
name = this.defaults.amdLanguageBase + name;
|
|
language = Translation.loadPath(name);
|
|
} catch (ex) {
|
|
// The translation could not be loaded at all. Sometimes this is
|
|
// because of a configuration problem, other times this can be
|
|
// because of how Select2 helps load all possible translation files.
|
|
if (options.debug && window.console && console.warn) {
|
|
console.warn(
|
|
'Select2: The language file for "' + name + '" could not be ' +
|
|
'automatically loaded. A fallback will be used instead.'
|
|
);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
languages.extend(language);
|
|
}
|
|
|
|
options.translations = languages;
|
|
} else {
|
|
var baseTranslation = Translation.loadPath(
|
|
this.defaults.amdLanguageBase + 'en'
|
|
);
|
|
var customTranslation = new Translation(options.language);
|
|
|
|
customTranslation.extend(baseTranslation);
|
|
|
|
options.translations = customTranslation;
|
|
}
|
|
|
|
return options;
|
|
};
|
|
|
|
Defaults.prototype.reset = function () {
|
|
function stripDiacritics (text) {
|
|
// Used 'uni range + named function' from http://jsperf.com/diacritics/18
|
|
function match(a) {
|
|
return DIACRITICS[a] || a;
|
|
}
|
|
|
|
return text.replace(/[^\u0000-\u007E]/g, match);
|
|
}
|
|
|
|
function matcher (params, data) {
|
|
// Always return the object if there is nothing to compare
|
|
if ($.trim(params.term) === '') {
|
|
return data;
|
|
}
|
|
|
|
// Do a recursive check for options with children
|
|
if (data.children && data.children.length > 0) {
|
|
// Clone the data object if there are children
|
|
// This is required as we modify the object to remove any non-matches
|
|
var match = $.extend(true, {}, data);
|
|
|
|
// Check each child of the option
|
|
for (var c = data.children.length - 1; c >= 0; c--) {
|
|
var child = data.children[c];
|
|
|
|
var matches = matcher(params, child);
|
|
|
|
// If there wasn't a match, remove the object in the array
|
|
if (matches == null) {
|
|
match.children.splice(c, 1);
|
|
}
|
|
}
|
|
|
|
// If any children matched, return the new object
|
|
if (match.children.length > 0) {
|
|
return match;
|
|
}
|
|
|
|
// If there were no matching children, check just the plain object
|
|
return matcher(params, match);
|
|
}
|
|
|
|
var original = stripDiacritics(data.text).toUpperCase();
|
|
var term = stripDiacritics(params.term).toUpperCase();
|
|
|
|
// Check if the text contains the term
|
|
if (original.indexOf(term) > -1) {
|
|
return data;
|
|
}
|
|
|
|
// If it doesn't contain the term, don't return anything
|
|
return null;
|
|
}
|
|
|
|
this.defaults = {
|
|
amdBase: './',
|
|
amdLanguageBase: './i18n/',
|
|
closeOnSelect: true,
|
|
debug: false,
|
|
dropdownAutoWidth: false,
|
|
escapeMarkup: Utils.escapeMarkup,
|
|
language: EnglishTranslation,
|
|
matcher: matcher,
|
|
minimumInputLength: 0,
|
|
maximumInputLength: 0,
|
|
maximumSelectionLength: 0,
|
|
minimumResultsForSearch: 0,
|
|
selectOnClose: false,
|
|
sorter: function (data) {
|
|
return data;
|
|
},
|
|
templateResult: function (result) {
|
|
return result.text;
|
|
},
|
|
templateSelection: function (selection) {
|
|
return selection.text;
|
|
},
|
|
theme: 'default',
|
|
width: 'resolve'
|
|
};
|
|
};
|
|
|
|
Defaults.prototype.set = function (key, value) {
|
|
var camelKey = $.camelCase(key);
|
|
|
|
var data = {};
|
|
data[camelKey] = value;
|
|
|
|
var convertedData = Utils._convertData(data);
|
|
|
|
$.extend(this.defaults, convertedData);
|
|
};
|
|
|
|
var defaults = new Defaults();
|
|
|
|
return defaults;
|
|
});
|
|
|
|
S2.define('select2/options',[
|
|
'require',
|
|
'jquery',
|
|
'./defaults',
|
|
'./utils'
|
|
], function (require, $, Defaults, Utils) {
|
|
function Options (options, $element) {
|
|
this.options = options;
|
|
|
|
if ($element != null) {
|
|
this.fromElement($element);
|
|
}
|
|
|
|
this.options = Defaults.apply(this.options);
|
|
|
|
if ($element && $element.is('input')) {
|
|
var InputCompat = require(this.get('amdBase') + 'compat/inputData');
|
|
|
|
this.options.dataAdapter = Utils.Decorate(
|
|
this.options.dataAdapter,
|
|
InputCompat
|
|
);
|
|
}
|
|
}
|
|
|
|
Options.prototype.fromElement = function ($e) {
|
|
var excludedData = ['select2'];
|
|
|
|
if (this.options.multiple == null) {
|
|
this.options.multiple = $e.prop('multiple');
|
|
}
|
|
|
|
if (this.options.disabled == null) {
|
|
this.options.disabled = $e.prop('disabled');
|
|
}
|
|
|
|
if (this.options.language == null) {
|
|
if ($e.prop('lang')) {
|
|
this.options.language = $e.prop('lang').toLowerCase();
|
|
} else if ($e.closest('[lang]').prop('lang')) {
|
|
this.options.language = $e.closest('[lang]').prop('lang');
|
|
}
|
|
}
|
|
|
|
if (this.options.dir == null) {
|
|
if ($e.prop('dir')) {
|
|
this.options.dir = $e.prop('dir');
|
|
} else if ($e.closest('[dir]').prop('dir')) {
|
|
this.options.dir = $e.closest('[dir]').prop('dir');
|
|
} else {
|
|
this.options.dir = 'ltr';
|
|
}
|
|
}
|
|
|
|
$e.prop('disabled', this.options.disabled);
|
|
$e.prop('multiple', this.options.multiple);
|
|
|
|
if ($e.data('select2Tags')) {
|
|
if (this.options.debug && window.console && console.warn) {
|
|
console.warn(
|
|
'Select2: The `data-select2-tags` attribute has been changed to ' +
|
|
'use the `data-data` and `data-tags="true"` attributes and will be ' +
|
|
'removed in future versions of Select2.'
|
|
);
|
|
}
|
|
|
|
$e.data('data', $e.data('select2Tags'));
|
|
$e.data('tags', true);
|
|
}
|
|
|
|
if ($e.data('ajaxUrl')) {
|
|
if (this.options.debug && window.console && console.warn) {
|
|
console.warn(
|
|
'Select2: The `data-ajax-url` attribute has been changed to ' +
|
|
'`data-ajax--url` and support for the old attribute will be removed' +
|
|
' in future versions of Select2.'
|
|
);
|
|
}
|
|
|
|
$e.attr('ajax--url', $e.data('ajaxUrl'));
|
|
$e.data('ajax--url', $e.data('ajaxUrl'));
|
|
}
|
|
|
|
var dataset = {};
|
|
|
|
// Prefer the element's `dataset` attribute if it exists
|
|
// jQuery 1.x does not correctly handle data attributes with multiple dashes
|
|
if ($.fn.jquery && $.fn.jquery.substr(0, 2) == '1.' && $e[0].dataset) {
|
|
dataset = $.extend(true, {}, $e[0].dataset, $e.data());
|
|
} else {
|
|
dataset = $e.data();
|
|
}
|
|
|
|
var data = $.extend(true, {}, dataset);
|
|
|
|
data = Utils._convertData(data);
|
|
|
|
for (var key in data) {
|
|
if ($.inArray(key, excludedData) > -1) {
|
|
continue;
|
|
}
|
|
|
|
if ($.isPlainObject(this.options[key])) {
|
|
$.extend(this.options[key], data[key]);
|
|
} else {
|
|
this.options[key] = data[key];
|
|
}
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
Options.prototype.get = function (key) {
|
|
return this.options[key];
|
|
};
|
|
|
|
Options.prototype.set = function (key, val) {
|
|
this.options[key] = val;
|
|
};
|
|
|
|
return Options;
|
|
});
|
|
|
|
S2.define('select2/core',[
|
|
'jquery',
|
|
'./options',
|
|
'./utils',
|
|
'./keys'
|
|
], function ($, Options, Utils, KEYS) {
|
|
var Select2 = function ($element, options) {
|
|
if ($element.data('select2') != null) {
|
|
$element.data('select2').destroy();
|
|
}
|
|
|
|
this.$element = $element;
|
|
|
|
this.id = this._generateId($element);
|
|
|
|
options = options || {};
|
|
|
|
this.options = new Options(options, $element);
|
|
|
|
Select2.__super__.constructor.call(this);
|
|
|
|
// Set up the tabindex
|
|
|
|
var tabindex = $element.attr('tabindex') || 0;
|
|
$element.data('old-tabindex', tabindex);
|
|
$element.attr('tabindex', '-1');
|
|
|
|
// Set up containers and adapters
|
|
|
|
var DataAdapter = this.options.get('dataAdapter');
|
|
this.dataAdapter = new DataAdapter($element, this.options);
|
|
|
|
var $container = this.render();
|
|
|
|
this._placeContainer($container);
|
|
|
|
var SelectionAdapter = this.options.get('selectionAdapter');
|
|
this.selection = new SelectionAdapter($element, this.options);
|
|
this.$selection = this.selection.render();
|
|
|
|
this.selection.position(this.$selection, $container);
|
|
|
|
var DropdownAdapter = this.options.get('dropdownAdapter');
|
|
this.dropdown = new DropdownAdapter($element, this.options);
|
|
this.$dropdown = this.dropdown.render();
|
|
|
|
this.dropdown.position(this.$dropdown, $container);
|
|
|
|
var ResultsAdapter = this.options.get('resultsAdapter');
|
|
this.results = new ResultsAdapter($element, this.options, this.dataAdapter);
|
|
this.$results = this.results.render();
|
|
|
|
this.results.position(this.$results, this.$dropdown);
|
|
|
|
// Bind events
|
|
|
|
var self = this;
|
|
|
|
// Bind the container to all of the adapters
|
|
this._bindAdapters();
|
|
|
|
// Register any DOM event handlers
|
|
this._registerDomEvents();
|
|
|
|
// Register any internal event handlers
|
|
this._registerDataEvents();
|
|
this._registerSelectionEvents();
|
|
this._registerDropdownEvents();
|
|
this._registerResultsEvents();
|
|
this._registerEvents();
|
|
|
|
// Set the initial state
|
|
this.dataAdapter.current(function (initialData) {
|
|
self.trigger('selection:update', {
|
|
data: initialData
|
|
});
|
|
});
|
|
|
|
// Hide the original select
|
|
$element.addClass('select2-hidden-accessible');
|
|
$element.attr('aria-hidden', 'true');
|
|
|
|
// Synchronize any monitored attributes
|
|
this._syncAttributes();
|
|
|
|
$element.data('select2', this);
|
|
};
|
|
|
|
Utils.Extend(Select2, Utils.Observable);
|
|
|
|
Select2.prototype._generateId = function ($element) {
|
|
var id = '';
|
|
|
|
if ($element.attr('id') != null) {
|
|
id = $element.attr('id');
|
|
} else if ($element.attr('name') != null) {
|
|
id = $element.attr('name') + '-' + Utils.generateChars(2);
|
|
} else {
|
|
id = Utils.generateChars(4);
|
|
}
|
|
|
|
id = id.replace(/(:|\.|\[|\]|,)/g, '');
|
|
id = 'select2-' + id;
|
|
|
|
return id;
|
|
};
|
|
|
|
Select2.prototype._placeContainer = function ($container) {
|
|
$container.insertAfter(this.$element);
|
|
|
|
var width = this._resolveWidth(this.$element, this.options.get('width'));
|
|
|
|
if (width != null) {
|
|
$container.css('width', width);
|
|
}
|
|
};
|
|
|
|
Select2.prototype._resolveWidth = function ($element, method) {
|
|
var WIDTH = /^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i;
|
|
|
|
if (method == 'resolve') {
|
|
var styleWidth = this._resolveWidth($element, 'style');
|
|
|
|
if (styleWidth != null) {
|
|
return styleWidth;
|
|
}
|
|
|
|
return this._resolveWidth($element, 'element');
|
|
}
|
|
|
|
if (method == 'element') {
|
|
var elementWidth = $element.outerWidth(false);
|
|
|
|
if (elementWidth <= 0) {
|
|
return 'auto';
|
|
}
|
|
|
|
return elementWidth + 'px';
|
|
}
|
|
|
|
if (method == 'style') {
|
|
var style = $element.attr('style');
|
|
|
|
if (typeof(style) !== 'string') {
|
|
return null;
|
|
}
|
|
|
|
var attrs = style.split(';');
|
|
|
|
for (var i = 0, l = attrs.length; i < l; i = i + 1) {
|
|
var attr = attrs[i].replace(/\s/g, '');
|
|
var matches = attr.match(WIDTH);
|
|
|
|
if (matches !== null && matches.length >= 1) {
|
|
return matches[1];
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
return method;
|
|
};
|
|
|
|
Select2.prototype._bindAdapters = function () {
|
|
this.dataAdapter.bind(this, this.$container);
|
|
this.selection.bind(this, this.$container);
|
|
|
|
this.dropdown.bind(this, this.$container);
|
|
this.results.bind(this, this.$container);
|
|
};
|
|
|
|
Select2.prototype._registerDomEvents = function () {
|
|
var self = this;
|
|
|
|
this.$element.on('change.select2', function () {
|
|
self.dataAdapter.current(function (data) {
|
|
self.trigger('selection:update', {
|
|
data: data
|
|
});
|
|
});
|
|
});
|
|
|
|
this.$element.on('focus.select2', function (evt) {
|
|
self.trigger('focus', evt);
|
|
});
|
|
|
|
this._syncA = Utils.bind(this._syncAttributes, this);
|
|
this._syncS = Utils.bind(this._syncSubtree, this);
|
|
|
|
if (this.$element[0].attachEvent) {
|
|
this.$element[0].attachEvent('onpropertychange', this._syncA);
|
|
}
|
|
|
|
var observer = window.MutationObserver ||
|
|
window.WebKitMutationObserver ||
|
|
window.MozMutationObserver
|
|
;
|
|
|
|
if (observer != null) {
|
|
this._observer = new observer(function (mutations) {
|
|
$.each(mutations, self._syncA);
|
|
$.each(mutations, self._syncS);
|
|
});
|
|
this._observer.observe(this.$element[0], {
|
|
attributes: true,
|
|
childList: true,
|
|
subtree: false
|
|
});
|
|
} else if (this.$element[0].addEventListener) {
|
|
this.$element[0].addEventListener(
|
|
'DOMAttrModified',
|
|
self._syncA,
|
|
false
|
|
);
|
|
this.$element[0].addEventListener(
|
|
'DOMNodeInserted',
|
|
self._syncS,
|
|
false
|
|
);
|
|
this.$element[0].addEventListener(
|
|
'DOMNodeRemoved',
|
|
self._syncS,
|
|
false
|
|
);
|
|
}
|
|
};
|
|
|
|
Select2.prototype._registerDataEvents = function () {
|
|
var self = this;
|
|
|
|
this.dataAdapter.on('*', function (name, params) {
|
|
self.trigger(name, params);
|
|
});
|
|
};
|
|
|
|
Select2.prototype._registerSelectionEvents = function () {
|
|
var self = this;
|
|
var nonRelayEvents = ['toggle', 'focus'];
|
|
|
|
this.selection.on('toggle', function () {
|
|
self.toggleDropdown();
|
|
});
|
|
|
|
this.selection.on('focus', function (params) {
|
|
self.focus(params);
|
|
});
|
|
|
|
this.selection.on('*', function (name, params) {
|
|
if ($.inArray(name, nonRelayEvents) !== -1) {
|
|
return;
|
|
}
|
|
|
|
self.trigger(name, params);
|
|
});
|
|
};
|
|
|
|
Select2.prototype._registerDropdownEvents = function () {
|
|
var self = this;
|
|
|
|
this.dropdown.on('*', function (name, params) {
|
|
self.trigger(name, params);
|
|
});
|
|
};
|
|
|
|
Select2.prototype._registerResultsEvents = function () {
|
|
var self = this;
|
|
|
|
this.results.on('*', function (name, params) {
|
|
self.trigger(name, params);
|
|
});
|
|
};
|
|
|
|
Select2.prototype._registerEvents = function () {
|
|
var self = this;
|
|
|
|
this.on('open', function () {
|
|
self.$container.addClass('select2-container--open');
|
|
});
|
|
|
|
this.on('close', function () {
|
|
self.$container.removeClass('select2-container--open');
|
|
});
|
|
|
|
this.on('enable', function () {
|
|
self.$container.removeClass('select2-container--disabled');
|
|
});
|
|
|
|
this.on('disable', function () {
|
|
self.$container.addClass('select2-container--disabled');
|
|
});
|
|
|
|
this.on('blur', function () {
|
|
self.$container.removeClass('select2-container--focus');
|
|
});
|
|
|
|
this.on('query', function (params) {
|
|
if (!self.isOpen()) {
|
|
self.trigger('open', {});
|
|
}
|
|
|
|
this.dataAdapter.query(params, function (data) {
|
|
self.trigger('results:all', {
|
|
data: data,
|
|
query: params
|
|
});
|
|
});
|
|
});
|
|
|
|
this.on('query:append', function (params) {
|
|
this.dataAdapter.query(params, function (data) {
|
|
self.trigger('results:append', {
|
|
data: data,
|
|
query: params
|
|
});
|
|
});
|
|
});
|
|
|
|
this.on('keypress', function (evt) {
|
|
var key = evt.which;
|
|
|
|
if (self.isOpen()) {
|
|
if (key === KEYS.ESC || key === KEYS.TAB ||
|
|
(key === KEYS.UP && evt.altKey)) {
|
|
self.close();
|
|
|
|
evt.preventDefault();
|
|
} else if (key === KEYS.ENTER) {
|
|
self.trigger('results:select', {});
|
|
|
|
evt.preventDefault();
|
|
} else if ((key === KEYS.SPACE && evt.ctrlKey)) {
|
|
self.trigger('results:toggle', {});
|
|
|
|
evt.preventDefault();
|
|
} else if (key === KEYS.UP) {
|
|
self.trigger('results:previous', {});
|
|
|
|
evt.preventDefault();
|
|
} else if (key === KEYS.DOWN) {
|
|
self.trigger('results:next', {});
|
|
|
|
evt.preventDefault();
|
|
}
|
|
} else {
|
|
if (key === KEYS.ENTER || key === KEYS.SPACE ||
|
|
(key === KEYS.DOWN && evt.altKey)) {
|
|
self.open();
|
|
|
|
evt.preventDefault();
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
Select2.prototype._syncAttributes = function () {
|
|
this.options.set('disabled', this.$element.prop('disabled'));
|
|
|
|
if (this.options.get('disabled')) {
|
|
if (this.isOpen()) {
|
|
this.close();
|
|
}
|
|
|
|
this.trigger('disable', {});
|
|
} else {
|
|
this.trigger('enable', {});
|
|
}
|
|
};
|
|
|
|
Select2.prototype._syncSubtree = function (evt, mutations) {
|
|
var changed = false;
|
|
var self = this;
|
|
|
|
// Ignore any mutation events raised for elements that aren't options or
|
|
// optgroups. This handles the case when the select element is destroyed
|
|
if (
|
|
evt && evt.target && (
|
|
evt.target.nodeName !== 'OPTION' && evt.target.nodeName !== 'OPTGROUP'
|
|
)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
if (!mutations) {
|
|
// If mutation events aren't supported, then we can only assume that the
|
|
// change affected the selections
|
|
changed = true;
|
|
} else if (mutations.addedNodes && mutations.addedNodes.length > 0) {
|
|
for (var n = 0; n < mutations.addedNodes.length; n++) {
|
|
var node = mutations.addedNodes[n];
|
|
|
|
if (node.selected) {
|
|
changed = true;
|
|
}
|
|
}
|
|
} else if (mutations.removedNodes && mutations.removedNodes.length > 0) {
|
|
changed = true;
|
|
}
|
|
|
|
// Only re-pull the data if we think there is a change
|
|
if (changed) {
|
|
this.dataAdapter.current(function (currentData) {
|
|
self.trigger('selection:update', {
|
|
data: currentData
|
|
});
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Override the trigger method to automatically trigger pre-events when
|
|
* there are events that can be prevented.
|
|
*/
|
|
Select2.prototype.trigger = function (name, args) {
|
|
var actualTrigger = Select2.__super__.trigger;
|
|
var preTriggerMap = {
|
|
'open': 'opening',
|
|
'close': 'closing',
|
|
'select': 'selecting',
|
|
'unselect': 'unselecting'
|
|
};
|
|
|
|
if (args === undefined) {
|
|
args = {};
|
|
}
|
|
|
|
if (name in preTriggerMap) {
|
|
var preTriggerName = preTriggerMap[name];
|
|
var preTriggerArgs = {
|
|
prevented: false,
|
|
name: name,
|
|
args: args
|
|
};
|
|
|
|
actualTrigger.call(this, preTriggerName, preTriggerArgs);
|
|
|
|
if (preTriggerArgs.prevented) {
|
|
args.prevented = true;
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
actualTrigger.call(this, name, args);
|
|
};
|
|
|
|
Select2.prototype.toggleDropdown = function () {
|
|
if (this.options.get('disabled')) {
|
|
return;
|
|
}
|
|
|
|
if (this.isOpen()) {
|
|
this.close();
|
|
} else {
|
|
this.open();
|
|
}
|
|
};
|
|
|
|
Select2.prototype.open = function () {
|
|
if (this.isOpen()) {
|
|
return;
|
|
}
|
|
|
|
this.trigger('query', {});
|
|
};
|
|
|
|
Select2.prototype.close = function () {
|
|
if (!this.isOpen()) {
|
|
return;
|
|
}
|
|
|
|
this.trigger('close', {});
|
|
};
|
|
|
|
Select2.prototype.isOpen = function () {
|
|
return this.$container.hasClass('select2-container--open');
|
|
};
|
|
|
|
Select2.prototype.hasFocus = function () {
|
|
return this.$container.hasClass('select2-container--focus');
|
|
};
|
|
|
|
Select2.prototype.focus = function (data) {
|
|
// No need to re-trigger focus events if we are already focused
|
|
if (this.hasFocus()) {
|
|
return;
|
|
}
|
|
|
|
this.$container.addClass('select2-container--focus');
|
|
this.trigger('focus', {});
|
|
};
|
|
|
|
Select2.prototype.enable = function (args) {
|
|
if (this.options.get('debug') && window.console && console.warn) {
|
|
console.warn(
|
|
'Select2: The `select2("enable")` method has been deprecated and will' +
|
|
' be removed in later Select2 versions. Use $element.prop("disabled")' +
|
|
' instead.'
|
|
);
|
|
}
|
|
|
|
if (args == null || args.length === 0) {
|
|
args = [true];
|
|
}
|
|
|
|
var disabled = !args[0];
|
|
|
|
this.$element.prop('disabled', disabled);
|
|
};
|
|
|
|
Select2.prototype.data = function () {
|
|
if (this.options.get('debug') &&
|
|
arguments.length > 0 && window.console && console.warn) {
|
|
console.warn(
|
|
'Select2: Data can no longer be set using `select2("data")`. You ' +
|
|
'should consider setting the value instead using `$element.val()`.'
|
|
);
|
|
}
|
|
|
|
var data = [];
|
|
|
|
this.dataAdapter.current(function (currentData) {
|
|
data = currentData;
|
|
});
|
|
|
|
return data;
|
|
};
|
|
|
|
Select2.prototype.val = function (args) {
|
|
if (this.options.get('debug') && window.console && console.warn) {
|
|
console.warn(
|
|
'Select2: The `select2("val")` method has been deprecated and will be' +
|
|
' removed in later Select2 versions. Use $element.val() instead.'
|
|
);
|
|
}
|
|
|
|
if (args == null || args.length === 0) {
|
|
return this.$element.val();
|
|
}
|
|
|
|
var newVal = args[0];
|
|
|
|
if ($.isArray(newVal)) {
|
|
newVal = $.map(newVal, function (obj) {
|
|
return obj.toString();
|
|
});
|
|
}
|
|
|
|
this.$element.val(newVal).trigger('change');
|
|
};
|
|
|
|
Select2.prototype.destroy = function () {
|
|
this.$container.remove();
|
|
|
|
if (this.$element[0].detachEvent) {
|
|
this.$element[0].detachEvent('onpropertychange', this._syncA);
|
|
}
|
|
|
|
if (this._observer != null) {
|
|
this._observer.disconnect();
|
|
this._observer = null;
|
|
} else if (this.$element[0].removeEventListener) {
|
|
this.$element[0]
|
|
.removeEventListener('DOMAttrModified', this._syncA, false);
|
|
this.$element[0]
|
|
.removeEventListener('DOMNodeInserted', this._syncS, false);
|
|
this.$element[0]
|
|
.removeEventListener('DOMNodeRemoved', this._syncS, false);
|
|
}
|
|
|
|
this._syncA = null;
|
|
this._syncS = null;
|
|
|
|
this.$element.off('.select2');
|
|
this.$element.attr('tabindex', this.$element.data('old-tabindex'));
|
|
|
|
this.$element.removeClass('select2-hidden-accessible');
|
|
this.$element.attr('aria-hidden', 'false');
|
|
this.$element.removeData('select2');
|
|
|
|
this.dataAdapter.destroy();
|
|
this.selection.destroy();
|
|
this.dropdown.destroy();
|
|
this.results.destroy();
|
|
|
|
this.dataAdapter = null;
|
|
this.selection = null;
|
|
this.dropdown = null;
|
|
this.results = null;
|
|
};
|
|
|
|
Select2.prototype.render = function () {
|
|
var $container = $(
|
|
'<span class="select2 select2-container">' +
|
|
'<span class="selection"></span>' +
|
|
'<span class="dropdown-wrapper" aria-hidden="true"></span>' +
|
|
'</span>'
|
|
);
|
|
|
|
$container.attr('dir', this.options.get('dir'));
|
|
|
|
this.$container = $container;
|
|
|
|
this.$container.addClass('select2-container--' + this.options.get('theme'));
|
|
|
|
$container.data('element', this.$element);
|
|
|
|
return $container;
|
|
};
|
|
|
|
return Select2;
|
|
});
|
|
|
|
S2.define('jquery-mousewheel',[
|
|
'jquery'
|
|
], function ($) {
|
|
// Used to shim jQuery.mousewheel for non-full builds.
|
|
return $;
|
|
});
|
|
|
|
S2.define('jquery.select2',[
|
|
'jquery',
|
|
'jquery-mousewheel',
|
|
|
|
'./select2/core',
|
|
'./select2/defaults'
|
|
], function ($, _, Select2, Defaults) {
|
|
if ($.fn.select2 == null) {
|
|
// All methods that should return the element
|
|
var thisMethods = ['open', 'close', 'destroy'];
|
|
|
|
$.fn.select2 = function (options) {
|
|
options = options || {};
|
|
|
|
if (typeof options === 'object') {
|
|
this.each(function () {
|
|
var instanceOptions = $.extend(true, {}, options);
|
|
|
|
var instance = new Select2($(this), instanceOptions);
|
|
});
|
|
|
|
return this;
|
|
} else if (typeof options === 'string') {
|
|
var ret;
|
|
var args = Array.prototype.slice.call(arguments, 1);
|
|
|
|
this.each(function () {
|
|
var instance = $(this).data('select2');
|
|
|
|
if (instance == null && window.console && console.error) {
|
|
console.error(
|
|
'The select2(\'' + options + '\') method was called on an ' +
|
|
'element that is not using Select2.'
|
|
);
|
|
}
|
|
|
|
ret = instance[options].apply(instance, args);
|
|
});
|
|
|
|
// Check if we should be returning `this`
|
|
if ($.inArray(options, thisMethods) > -1) {
|
|
return this;
|
|
}
|
|
|
|
return ret;
|
|
} else {
|
|
throw new Error('Invalid arguments for Select2: ' + options);
|
|
}
|
|
};
|
|
}
|
|
|
|
if ($.fn.select2.defaults == null) {
|
|
$.fn.select2.defaults = Defaults;
|
|
}
|
|
|
|
return Select2;
|
|
});
|
|
|
|
// Return the AMD loader configuration so it can be used outside of this file
|
|
return {
|
|
define: S2.define,
|
|
require: S2.require
|
|
};
|
|
}());
|
|
|
|
// Autoload the jQuery bindings
|
|
// We know that all of the modules exist above this, so we're safe
|
|
var select2 = S2.require('jquery.select2');
|
|
|
|
// Hold the AMD module references on the jQuery function that was just loaded
|
|
// This allows Select2 to use the internal loader outside of this file, such
|
|
// as in the language files.
|
|
jQuery.fn.select2.amd = S2;
|
|
|
|
// Return the Select2 instance for anyone who is importing it.
|
|
return select2;
|
|
}));
|
|
|
|
/*!
|
|
* iCheck v1.0.2, http://git.io/arlzeA
|
|
* ===================================
|
|
* Powerful jQuery and Zepto plugin for checkboxes and radio buttons customization
|
|
*
|
|
* (c) 2013 Damir Sultanov, http://fronteed.com
|
|
* MIT Licensed
|
|
*/
|
|
|
|
(function($) {
|
|
|
|
// Cached vars
|
|
var _iCheck = 'iCheck',
|
|
_iCheckHelper = _iCheck + '-helper',
|
|
_checkbox = 'checkbox',
|
|
_radio = 'radio',
|
|
_checked = 'checked',
|
|
_unchecked = 'un' + _checked,
|
|
_disabled = 'disabled',
|
|
_determinate = 'determinate',
|
|
_indeterminate = 'in' + _determinate,
|
|
_update = 'update',
|
|
_type = 'type',
|
|
_click = 'click',
|
|
_touch = 'touchbegin.i touchend.i',
|
|
_add = 'addClass',
|
|
_remove = 'removeClass',
|
|
_callback = 'trigger',
|
|
_label = 'label',
|
|
_cursor = 'cursor',
|
|
_mobile = /ipad|iphone|ipod|android|blackberry|windows phone|opera mini|silk/i.test(navigator.userAgent);
|
|
|
|
// Plugin init
|
|
$.fn[_iCheck] = function(options, fire) {
|
|
|
|
// Walker
|
|
var handle = 'input[type="' + _checkbox + '"], input[type="' + _radio + '"]',
|
|
stack = $(),
|
|
walker = function(object) {
|
|
object.each(function() {
|
|
var self = $(this);
|
|
|
|
if (self.is(handle)) {
|
|
stack = stack.add(self);
|
|
} else {
|
|
stack = stack.add(self.find(handle));
|
|
}
|
|
});
|
|
};
|
|
|
|
// Check if we should operate with some method
|
|
if (/^(check|uncheck|toggle|indeterminate|determinate|disable|enable|update|destroy)$/i.test(options)) {
|
|
|
|
// Normalize method's name
|
|
options = options.toLowerCase();
|
|
|
|
// Find checkboxes and radio buttons
|
|
walker(this);
|
|
|
|
return stack.each(function() {
|
|
var self = $(this);
|
|
|
|
if (options == 'destroy') {
|
|
tidy(self, 'ifDestroyed');
|
|
} else {
|
|
operate(self, true, options);
|
|
}
|
|
|
|
// Fire method's callback
|
|
if ($.isFunction(fire)) {
|
|
fire();
|
|
}
|
|
});
|
|
|
|
// Customization
|
|
} else if (typeof options == 'object' || !options) {
|
|
|
|
// Check if any options were passed
|
|
var settings = $.extend({
|
|
checkedClass: _checked,
|
|
disabledClass: _disabled,
|
|
indeterminateClass: _indeterminate,
|
|
labelHover: true
|
|
}, options),
|
|
|
|
selector = settings.handle,
|
|
hoverClass = settings.hoverClass || 'hover',
|
|
focusClass = settings.focusClass || 'focus',
|
|
activeClass = settings.activeClass || 'active',
|
|
labelHover = !!settings.labelHover,
|
|
labelHoverClass = settings.labelHoverClass || 'hover',
|
|
|
|
// Setup clickable area
|
|
area = ('' + settings.increaseArea).replace('%', '') | 0;
|
|
|
|
// Selector limit
|
|
if (selector == _checkbox || selector == _radio) {
|
|
handle = 'input[type="' + selector + '"]';
|
|
}
|
|
|
|
// Clickable area limit
|
|
if (area < -50) {
|
|
area = -50;
|
|
}
|
|
|
|
// Walk around the selector
|
|
walker(this);
|
|
|
|
return stack.each(function() {
|
|
var self = $(this);
|
|
|
|
// If already customized
|
|
tidy(self);
|
|
|
|
var node = this,
|
|
id = node.id,
|
|
|
|
// Layer styles
|
|
offset = -area + '%',
|
|
size = 100 + (area * 2) + '%',
|
|
layer = {
|
|
position: 'absolute',
|
|
top: offset,
|
|
left: offset,
|
|
display: 'block',
|
|
width: size,
|
|
height: size,
|
|
margin: 0,
|
|
padding: 0,
|
|
background: '#fff',
|
|
border: 0,
|
|
opacity: 0
|
|
},
|
|
|
|
// Choose how to hide input
|
|
hide = _mobile ? {
|
|
position: 'absolute',
|
|
visibility: 'hidden'
|
|
} : area ? layer : {
|
|
position: 'absolute',
|
|
opacity: 0
|
|
},
|
|
|
|
// Get proper class
|
|
className = node[_type] == _checkbox ? settings.checkboxClass || 'i' + _checkbox : settings.radioClass || 'i' + _radio,
|
|
|
|
// Find assigned labels
|
|
label = $(_label + '[for="' + id + '"]').add(self.closest(_label)),
|
|
|
|
// Check ARIA option
|
|
aria = !!settings.aria,
|
|
|
|
// Set ARIA placeholder
|
|
ariaID = _iCheck + '-' + Math.random().toString(36).substr(2,6),
|
|
|
|
// Parent & helper
|
|
parent = '<div class="' + className + '" ' + (aria ? 'role="' + node[_type] + '" ' : ''),
|
|
helper;
|
|
|
|
// Set ARIA "labelledby"
|
|
if (aria) {
|
|
label.each(function() {
|
|
parent += 'aria-labelledby="';
|
|
|
|
if (this.id) {
|
|
parent += this.id;
|
|
} else {
|
|
this.id = ariaID;
|
|
parent += ariaID;
|
|
}
|
|
|
|
parent += '"';
|
|
});
|
|
}
|
|
|
|
// Wrap input
|
|
parent = self.wrap(parent + '/>')[_callback]('ifCreated').parent().append(settings.insert);
|
|
|
|
// Layer addition
|
|
helper = $('<ins class="' + _iCheckHelper + '"/>').css(layer).appendTo(parent);
|
|
|
|
// Finalize customization
|
|
self.data(_iCheck, {o: settings, s: self.attr('style')}).css(hide);
|
|
!!settings.inheritClass && parent[_add](node.className || '');
|
|
!!settings.inheritID && id && parent.attr('id', _iCheck + '-' + id);
|
|
parent.css('position') == 'static' && parent.css('position', 'relative');
|
|
operate(self, true, _update);
|
|
|
|
// Label events
|
|
if (label.length) {
|
|
label.on(_click + '.i mouseover.i mouseout.i ' + _touch, function(event) {
|
|
var type = event[_type],
|
|
item = $(this);
|
|
|
|
// Do nothing if input is disabled
|
|
if (!node[_disabled]) {
|
|
|
|
// Click
|
|
if (type == _click) {
|
|
if ($(event.target).is('a')) {
|
|
return;
|
|
}
|
|
operate(self, false, true);
|
|
|
|
// Hover state
|
|
} else if (labelHover) {
|
|
|
|
// mouseout|touchend
|
|
if (/ut|nd/.test(type)) {
|
|
parent[_remove](hoverClass);
|
|
item[_remove](labelHoverClass);
|
|
} else {
|
|
parent[_add](hoverClass);
|
|
item[_add](labelHoverClass);
|
|
}
|
|
}
|
|
|
|
if (_mobile) {
|
|
event.stopPropagation();
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Input events
|
|
self.on(_click + '.i focus.i blur.i keyup.i keydown.i keypress.i', function(event) {
|
|
var type = event[_type],
|
|
key = event.keyCode;
|
|
|
|
// Click
|
|
if (type == _click) {
|
|
return false;
|
|
|
|
// Keydown
|
|
} else if (type == 'keydown' && key == 32) {
|
|
if (!(node[_type] == _radio && node[_checked])) {
|
|
if (node[_checked]) {
|
|
off(self, _checked);
|
|
} else {
|
|
on(self, _checked);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
|
|
// Keyup
|
|
} else if (type == 'keyup' && node[_type] == _radio) {
|
|
!node[_checked] && on(self, _checked);
|
|
|
|
// Focus/blur
|
|
} else if (/us|ur/.test(type)) {
|
|
parent[type == 'blur' ? _remove : _add](focusClass);
|
|
}
|
|
});
|
|
|
|
// Helper events
|
|
helper.on(_click + ' mousedown mouseup mouseover mouseout ' + _touch, function(event) {
|
|
var type = event[_type],
|
|
|
|
// mousedown|mouseup
|
|
toggle = /wn|up/.test(type) ? activeClass : hoverClass;
|
|
|
|
// Do nothing if input is disabled
|
|
if (!node[_disabled]) {
|
|
|
|
// Click
|
|
if (type == _click) {
|
|
operate(self, false, true);
|
|
|
|
// Active and hover states
|
|
} else {
|
|
|
|
// State is on
|
|
if (/wn|er|in/.test(type)) {
|
|
|
|
// mousedown|mouseover|touchbegin
|
|
parent[_add](toggle);
|
|
|
|
// State is off
|
|
} else {
|
|
parent[_remove](toggle + ' ' + activeClass);
|
|
}
|
|
|
|
// Label hover
|
|
if (label.length && labelHover && toggle == hoverClass) {
|
|
|
|
// mouseout|touchend
|
|
label[/ut|nd/.test(type) ? _remove : _add](labelHoverClass);
|
|
}
|
|
}
|
|
|
|
if (_mobile) {
|
|
event.stopPropagation();
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
});
|
|
});
|
|
} else {
|
|
return this;
|
|
}
|
|
};
|
|
|
|
// Do something with inputs
|
|
function operate(input, direct, method) {
|
|
var node = input[0],
|
|
state = /er/.test(method) ? _indeterminate : /bl/.test(method) ? _disabled : _checked,
|
|
active = method == _update ? {
|
|
checked: node[_checked],
|
|
disabled: node[_disabled],
|
|
indeterminate: input.attr(_indeterminate) == 'true' || input.attr(_determinate) == 'false'
|
|
} : node[state];
|
|
|
|
// Check, disable or indeterminate
|
|
if (/^(ch|di|in)/.test(method) && !active) {
|
|
on(input, state);
|
|
|
|
// Uncheck, enable or determinate
|
|
} else if (/^(un|en|de)/.test(method) && active) {
|
|
off(input, state);
|
|
|
|
// Update
|
|
} else if (method == _update) {
|
|
|
|
// Handle states
|
|
for (var each in active) {
|
|
if (active[each]) {
|
|
on(input, each, true);
|
|
} else {
|
|
off(input, each, true);
|
|
}
|
|
}
|
|
|
|
} else if (!direct || method == 'toggle') {
|
|
|
|
// Helper or label was clicked
|
|
if (!direct) {
|
|
input[_callback]('ifClicked');
|
|
}
|
|
|
|
// Toggle checked state
|
|
if (active) {
|
|
if (node[_type] !== _radio) {
|
|
off(input, state);
|
|
}
|
|
} else {
|
|
on(input, state);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add checked, disabled or indeterminate state
|
|
function on(input, state, keep) {
|
|
var node = input[0],
|
|
parent = input.parent(),
|
|
checked = state == _checked,
|
|
indeterminate = state == _indeterminate,
|
|
disabled = state == _disabled,
|
|
callback = indeterminate ? _determinate : checked ? _unchecked : 'enabled',
|
|
regular = option(input, callback + capitalize(node[_type])),
|
|
specific = option(input, state + capitalize(node[_type]));
|
|
|
|
// Prevent unnecessary actions
|
|
if (node[state] !== true) {
|
|
|
|
// Toggle assigned radio buttons
|
|
if (!keep && state == _checked && node[_type] == _radio && node.name) {
|
|
var form = input.closest('form'),
|
|
inputs = 'input[name="' + node.name + '"]';
|
|
|
|
inputs = form.length ? form.find(inputs) : $(inputs);
|
|
|
|
inputs.each(function() {
|
|
if (this !== node && $(this).data(_iCheck)) {
|
|
off($(this), state);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Indeterminate state
|
|
if (indeterminate) {
|
|
|
|
// Add indeterminate state
|
|
node[state] = true;
|
|
|
|
// Remove checked state
|
|
if (node[_checked]) {
|
|
off(input, _checked, 'force');
|
|
}
|
|
|
|
// Checked or disabled state
|
|
} else {
|
|
|
|
// Add checked or disabled state
|
|
if (!keep) {
|
|
node[state] = true;
|
|
}
|
|
|
|
// Remove indeterminate state
|
|
if (checked && node[_indeterminate]) {
|
|
off(input, _indeterminate, false);
|
|
}
|
|
}
|
|
|
|
// Trigger callbacks
|
|
callbacks(input, checked, state, keep);
|
|
}
|
|
|
|
// Add proper cursor
|
|
if (node[_disabled] && !!option(input, _cursor, true)) {
|
|
parent.find('.' + _iCheckHelper).css(_cursor, 'default');
|
|
}
|
|
|
|
// Add state class
|
|
parent[_add](specific || option(input, state) || '');
|
|
|
|
// Set ARIA attribute
|
|
if (!!parent.attr('role') && !indeterminate) {
|
|
parent.attr('aria-' + (disabled ? _disabled : _checked), 'true');
|
|
}
|
|
|
|
// Remove regular state class
|
|
parent[_remove](regular || option(input, callback) || '');
|
|
}
|
|
|
|
// Remove checked, disabled or indeterminate state
|
|
function off(input, state, keep) {
|
|
var node = input[0],
|
|
parent = input.parent(),
|
|
checked = state == _checked,
|
|
indeterminate = state == _indeterminate,
|
|
disabled = state == _disabled,
|
|
callback = indeterminate ? _determinate : checked ? _unchecked : 'enabled',
|
|
regular = option(input, callback + capitalize(node[_type])),
|
|
specific = option(input, state + capitalize(node[_type]));
|
|
|
|
// Prevent unnecessary actions
|
|
if (node[state] !== false) {
|
|
|
|
// Toggle state
|
|
if (indeterminate || !keep || keep == 'force') {
|
|
node[state] = false;
|
|
}
|
|
|
|
// Trigger callbacks
|
|
callbacks(input, checked, callback, keep);
|
|
}
|
|
|
|
// Add proper cursor
|
|
if (!node[_disabled] && !!option(input, _cursor, true)) {
|
|
parent.find('.' + _iCheckHelper).css(_cursor, 'pointer');
|
|
}
|
|
|
|
// Remove state class
|
|
parent[_remove](specific || option(input, state) || '');
|
|
|
|
// Set ARIA attribute
|
|
if (!!parent.attr('role') && !indeterminate) {
|
|
parent.attr('aria-' + (disabled ? _disabled : _checked), 'false');
|
|
}
|
|
|
|
// Add regular state class
|
|
parent[_add](regular || option(input, callback) || '');
|
|
}
|
|
|
|
// Remove all traces
|
|
function tidy(input, callback) {
|
|
if (input.data(_iCheck)) {
|
|
|
|
// Remove everything except input
|
|
input.parent().html(input.attr('style', input.data(_iCheck).s || ''));
|
|
|
|
// Callback
|
|
if (callback) {
|
|
input[_callback](callback);
|
|
}
|
|
|
|
// Unbind events
|
|
input.off('.i').unwrap();
|
|
$(_label + '[for="' + input[0].id + '"]').add(input.closest(_label)).off('.i');
|
|
}
|
|
}
|
|
|
|
// Get some option
|
|
function option(input, state, regular) {
|
|
if (input.data(_iCheck)) {
|
|
return input.data(_iCheck).o[state + (regular ? '' : 'Class')];
|
|
}
|
|
}
|
|
|
|
// Capitalize some string
|
|
function capitalize(string) {
|
|
return string.charAt(0).toUpperCase() + string.slice(1);
|
|
}
|
|
|
|
// Executable handlers
|
|
function callbacks(input, checked, callback, keep) {
|
|
if (!keep) {
|
|
if (checked) {
|
|
input[_callback]('ifToggled');
|
|
}
|
|
|
|
input[_callback]('ifChanged')[_callback]('if' + capitalize(callback));
|
|
}
|
|
}
|
|
})(window.jQuery || window.Zepto);
|
|
|
|
/*! X-editable - v1.5.1
|
|
* In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery
|
|
* http://github.com/vitalets/x-editable
|
|
* Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */
|
|
/**
|
|
Form with single input element, two buttons and two states: normal/loading.
|
|
Applied as jQuery method to DIV tag (not to form tag!). This is because form can be in loading state when spinner shown.
|
|
Editableform is linked with one of input types, e.g. 'text', 'select' etc.
|
|
|
|
@class editableform
|
|
@uses text
|
|
@uses textarea
|
|
**/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var EditableForm = function (div, options) {
|
|
this.options = $.extend({}, $.fn.editableform.defaults, options);
|
|
this.$div = $(div); //div, containing form. Not form tag. Not editable-element.
|
|
if(!this.options.scope) {
|
|
this.options.scope = this;
|
|
}
|
|
//nothing shown after init
|
|
};
|
|
|
|
EditableForm.prototype = {
|
|
constructor: EditableForm,
|
|
initInput: function() { //called once
|
|
//take input from options (as it is created in editable-element)
|
|
this.input = this.options.input;
|
|
|
|
//set initial value
|
|
//todo: may be add check: typeof str === 'string' ?
|
|
this.value = this.input.str2value(this.options.value);
|
|
|
|
//prerender: get input.$input
|
|
this.input.prerender();
|
|
},
|
|
initTemplate: function() {
|
|
this.$form = $($.fn.editableform.template);
|
|
},
|
|
initButtons: function() {
|
|
var $btn = this.$form.find('.editable-buttons');
|
|
$btn.append($.fn.editableform.buttons);
|
|
if(this.options.showbuttons === 'bottom') {
|
|
$btn.addClass('editable-buttons-bottom');
|
|
}
|
|
},
|
|
/**
|
|
Renders editableform
|
|
|
|
@method render
|
|
**/
|
|
render: function() {
|
|
//init loader
|
|
this.$loading = $($.fn.editableform.loading);
|
|
this.$div.empty().append(this.$loading);
|
|
|
|
//init form template and buttons
|
|
this.initTemplate();
|
|
if(this.options.showbuttons) {
|
|
this.initButtons();
|
|
} else {
|
|
this.$form.find('.editable-buttons').remove();
|
|
}
|
|
|
|
//show loading state
|
|
this.showLoading();
|
|
|
|
//flag showing is form now saving value to server.
|
|
//It is needed to wait when closing form.
|
|
this.isSaving = false;
|
|
|
|
/**
|
|
Fired when rendering starts
|
|
@event rendering
|
|
@param {Object} event event object
|
|
**/
|
|
this.$div.triggerHandler('rendering');
|
|
|
|
//init input
|
|
this.initInput();
|
|
|
|
//append input to form
|
|
this.$form.find('div.editable-input').append(this.input.$tpl);
|
|
|
|
//append form to container
|
|
this.$div.append(this.$form);
|
|
|
|
//render input
|
|
$.when(this.input.render())
|
|
.then($.proxy(function () {
|
|
//setup input to submit automatically when no buttons shown
|
|
if(!this.options.showbuttons) {
|
|
this.input.autosubmit();
|
|
}
|
|
|
|
//attach 'cancel' handler
|
|
this.$form.find('.editable-cancel').click($.proxy(this.cancel, this));
|
|
|
|
if(this.input.error) {
|
|
this.error(this.input.error);
|
|
this.$form.find('.editable-submit').attr('disabled', true);
|
|
this.input.$input.attr('disabled', true);
|
|
//prevent form from submitting
|
|
this.$form.submit(function(e){ e.preventDefault(); });
|
|
} else {
|
|
this.error(false);
|
|
this.input.$input.removeAttr('disabled');
|
|
this.$form.find('.editable-submit').removeAttr('disabled');
|
|
var value = (this.value === null || this.value === undefined || this.value === '') ? this.options.defaultValue : this.value;
|
|
this.input.value2input(value);
|
|
//attach submit handler
|
|
this.$form.submit($.proxy(this.submit, this));
|
|
}
|
|
|
|
/**
|
|
Fired when form is rendered
|
|
@event rendered
|
|
@param {Object} event event object
|
|
**/
|
|
this.$div.triggerHandler('rendered');
|
|
|
|
this.showForm();
|
|
|
|
//call postrender method to perform actions required visibility of form
|
|
if(this.input.postrender) {
|
|
this.input.postrender();
|
|
}
|
|
}, this));
|
|
},
|
|
cancel: function() {
|
|
/**
|
|
Fired when form was cancelled by user
|
|
@event cancel
|
|
@param {Object} event event object
|
|
**/
|
|
this.$div.triggerHandler('cancel');
|
|
},
|
|
showLoading: function() {
|
|
var w, h;
|
|
if(this.$form) {
|
|
//set loading size equal to form
|
|
w = this.$form.outerWidth();
|
|
h = this.$form.outerHeight();
|
|
if(w) {
|
|
this.$loading.width(w);
|
|
}
|
|
if(h) {
|
|
this.$loading.height(h);
|
|
}
|
|
this.$form.hide();
|
|
} else {
|
|
//stretch loading to fill container width
|
|
w = this.$loading.parent().width();
|
|
if(w) {
|
|
this.$loading.width(w);
|
|
}
|
|
}
|
|
this.$loading.show();
|
|
},
|
|
|
|
showForm: function(activate) {
|
|
this.$loading.hide();
|
|
this.$form.show();
|
|
if(activate !== false) {
|
|
this.input.activate();
|
|
}
|
|
/**
|
|
Fired when form is shown
|
|
@event show
|
|
@param {Object} event event object
|
|
**/
|
|
this.$div.triggerHandler('show');
|
|
},
|
|
|
|
error: function(msg) {
|
|
var $group = this.$form.find('.control-group'),
|
|
$block = this.$form.find('.editable-error-block'),
|
|
lines;
|
|
|
|
if(msg === false) {
|
|
$group.removeClass($.fn.editableform.errorGroupClass);
|
|
$block.removeClass($.fn.editableform.errorBlockClass).empty().hide();
|
|
} else {
|
|
//convert newline to <br> for more pretty error display
|
|
if(msg) {
|
|
lines = (''+msg).split('\n');
|
|
for (var i = 0; i < lines.length; i++) {
|
|
lines[i] = $('<div>').text(lines[i]).html();
|
|
}
|
|
msg = lines.join('<br>');
|
|
}
|
|
$group.addClass($.fn.editableform.errorGroupClass);
|
|
$block.addClass($.fn.editableform.errorBlockClass).html(msg).show();
|
|
}
|
|
},
|
|
|
|
submit: function(e) {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
|
|
//get new value from input
|
|
var newValue = this.input.input2value();
|
|
|
|
//validation: if validate returns string or truthy value - means error
|
|
//if returns object like {newValue: '...'} => submitted value is reassigned to it
|
|
var error = this.validate(newValue);
|
|
if ($.type(error) === 'object' && error.newValue !== undefined) {
|
|
newValue = error.newValue;
|
|
this.input.value2input(newValue);
|
|
if(typeof error.msg === 'string') {
|
|
this.error(error.msg);
|
|
this.showForm();
|
|
return;
|
|
}
|
|
} else if (error) {
|
|
this.error(error);
|
|
this.showForm();
|
|
return;
|
|
}
|
|
|
|
//if value not changed --> trigger 'nochange' event and return
|
|
/*jslint eqeq: true*/
|
|
if (!this.options.savenochange && this.input.value2str(newValue) == this.input.value2str(this.value)) {
|
|
/*jslint eqeq: false*/
|
|
/**
|
|
Fired when value not changed but form is submitted. Requires savenochange = false.
|
|
@event nochange
|
|
@param {Object} event event object
|
|
**/
|
|
this.$div.triggerHandler('nochange');
|
|
return;
|
|
}
|
|
|
|
//convert value for submitting to server
|
|
var submitValue = this.input.value2submit(newValue);
|
|
|
|
this.isSaving = true;
|
|
|
|
//sending data to server
|
|
$.when(this.save(submitValue))
|
|
.done($.proxy(function(response) {
|
|
this.isSaving = false;
|
|
|
|
//run success callback
|
|
var res = typeof this.options.success === 'function' ? this.options.success.call(this.options.scope, response, newValue) : null;
|
|
|
|
//if success callback returns false --> keep form open and do not activate input
|
|
if(res === false) {
|
|
this.error(false);
|
|
this.showForm(false);
|
|
return;
|
|
}
|
|
|
|
//if success callback returns string --> keep form open, show error and activate input
|
|
if(typeof res === 'string') {
|
|
this.error(res);
|
|
this.showForm();
|
|
return;
|
|
}
|
|
|
|
//if success callback returns object like {newValue: <something>} --> use that value instead of submitted
|
|
//it is usefull if you want to chnage value in url-function
|
|
if(res && typeof res === 'object' && res.hasOwnProperty('newValue')) {
|
|
newValue = res.newValue;
|
|
}
|
|
|
|
//clear error message
|
|
this.error(false);
|
|
this.value = newValue;
|
|
/**
|
|
Fired when form is submitted
|
|
@event save
|
|
@param {Object} event event object
|
|
@param {Object} params additional params
|
|
@param {mixed} params.newValue raw new value
|
|
@param {mixed} params.submitValue submitted value as string
|
|
@param {Object} params.response ajax response
|
|
|
|
@example
|
|
$('#form-div').on('save'), function(e, params){
|
|
if(params.newValue === 'username') {...}
|
|
});
|
|
**/
|
|
this.$div.triggerHandler('save', {newValue: newValue, submitValue: submitValue, response: response});
|
|
}, this))
|
|
.fail($.proxy(function(xhr) {
|
|
this.isSaving = false;
|
|
|
|
var msg;
|
|
if(typeof this.options.error === 'function') {
|
|
msg = this.options.error.call(this.options.scope, xhr, newValue);
|
|
} else {
|
|
msg = typeof xhr === 'string' ? xhr : xhr.responseText || xhr.statusText || 'Unknown error!';
|
|
}
|
|
|
|
this.error(msg);
|
|
this.showForm();
|
|
}, this));
|
|
},
|
|
|
|
save: function(submitValue) {
|
|
//try parse composite pk defined as json string in data-pk
|
|
this.options.pk = $.fn.editableutils.tryParseJson(this.options.pk, true);
|
|
|
|
var pk = (typeof this.options.pk === 'function') ? this.options.pk.call(this.options.scope) : this.options.pk,
|
|
/*
|
|
send on server in following cases:
|
|
1. url is function
|
|
2. url is string AND (pk defined OR send option = always)
|
|
*/
|
|
send = !!(typeof this.options.url === 'function' || (this.options.url && ((this.options.send === 'always') || (this.options.send === 'auto' && pk !== null && pk !== undefined)))),
|
|
params;
|
|
|
|
if (send) { //send to server
|
|
this.showLoading();
|
|
|
|
//standard params
|
|
params = {
|
|
name: this.options.name || '',
|
|
value: submitValue,
|
|
pk: pk
|
|
};
|
|
|
|
//additional params
|
|
if(typeof this.options.params === 'function') {
|
|
params = this.options.params.call(this.options.scope, params);
|
|
} else {
|
|
//try parse json in single quotes (from data-params attribute)
|
|
this.options.params = $.fn.editableutils.tryParseJson(this.options.params, true);
|
|
$.extend(params, this.options.params);
|
|
}
|
|
|
|
if(typeof this.options.url === 'function') { //user's function
|
|
return this.options.url.call(this.options.scope, params);
|
|
} else {
|
|
//send ajax to server and return deferred object
|
|
return $.ajax($.extend({
|
|
url : this.options.url,
|
|
data : params,
|
|
type : 'POST'
|
|
}, this.options.ajaxOptions));
|
|
}
|
|
}
|
|
},
|
|
|
|
validate: function (value) {
|
|
if (value === undefined) {
|
|
value = this.value;
|
|
}
|
|
if (typeof this.options.validate === 'function') {
|
|
return this.options.validate.call(this.options.scope, value);
|
|
}
|
|
},
|
|
|
|
option: function(key, value) {
|
|
if(key in this.options) {
|
|
this.options[key] = value;
|
|
}
|
|
|
|
if(key === 'value') {
|
|
this.setValue(value);
|
|
}
|
|
|
|
//do not pass option to input as it is passed in editable-element
|
|
},
|
|
|
|
setValue: function(value, convertStr) {
|
|
if(convertStr) {
|
|
this.value = this.input.str2value(value);
|
|
} else {
|
|
this.value = value;
|
|
}
|
|
|
|
//if form is visible, update input
|
|
if(this.$form && this.$form.is(':visible')) {
|
|
this.input.value2input(this.value);
|
|
}
|
|
}
|
|
};
|
|
|
|
/*
|
|
Initialize editableform. Applied to jQuery object.
|
|
|
|
@method $().editableform(options)
|
|
@params {Object} options
|
|
@example
|
|
var $form = $('<div>').editableform({
|
|
type: 'text',
|
|
name: 'username',
|
|
url: '/post',
|
|
value: 'vitaliy'
|
|
});
|
|
|
|
//to display form you should call 'render' method
|
|
$form.editableform('render');
|
|
*/
|
|
$.fn.editableform = function (option) {
|
|
var args = arguments;
|
|
return this.each(function () {
|
|
var $this = $(this),
|
|
data = $this.data('editableform'),
|
|
options = typeof option === 'object' && option;
|
|
if (!data) {
|
|
$this.data('editableform', (data = new EditableForm(this, options)));
|
|
}
|
|
|
|
if (typeof option === 'string') { //call method
|
|
data[option].apply(data, Array.prototype.slice.call(args, 1));
|
|
}
|
|
});
|
|
};
|
|
|
|
//keep link to constructor to allow inheritance
|
|
$.fn.editableform.Constructor = EditableForm;
|
|
|
|
//defaults
|
|
$.fn.editableform.defaults = {
|
|
/* see also defaults for input */
|
|
|
|
/**
|
|
Type of input. Can be <code>text|textarea|select|date|checklist</code>
|
|
|
|
@property type
|
|
@type string
|
|
@default 'text'
|
|
**/
|
|
type: 'text',
|
|
/**
|
|
Url for submit, e.g. <code>'/post'</code>
|
|
If function - it will be called instead of ajax. Function should return deferred object to run fail/done callbacks.
|
|
|
|
@property url
|
|
@type string|function
|
|
@default null
|
|
@example
|
|
url: function(params) {
|
|
var d = new $.Deferred;
|
|
if(params.value === 'abc') {
|
|
return d.reject('error message'); //returning error via deferred object
|
|
} else {
|
|
//async saving data in js model
|
|
someModel.asyncSaveMethod({
|
|
...,
|
|
success: function(){
|
|
d.resolve();
|
|
}
|
|
});
|
|
return d.promise();
|
|
}
|
|
}
|
|
**/
|
|
url:null,
|
|
/**
|
|
Additional params for submit. If defined as <code>object</code> - it is **appended** to original ajax data (pk, name and value).
|
|
If defined as <code>function</code> - returned object **overwrites** original ajax data.
|
|
@example
|
|
params: function(params) {
|
|
//originally params contain pk, name and value
|
|
params.a = 1;
|
|
return params;
|
|
}
|
|
|
|
@property params
|
|
@type object|function
|
|
@default null
|
|
**/
|
|
params:null,
|
|
/**
|
|
Name of field. Will be submitted on server. Can be taken from <code>id</code> attribute
|
|
|
|
@property name
|
|
@type string
|
|
@default null
|
|
**/
|
|
name: null,
|
|
/**
|
|
Primary key of editable object (e.g. record id in database). For composite keys use object, e.g. <code>{id: 1, lang: 'en'}</code>.
|
|
Can be calculated dynamically via function.
|
|
|
|
@property pk
|
|
@type string|object|function
|
|
@default null
|
|
**/
|
|
pk: null,
|
|
/**
|
|
Initial value. If not defined - will be taken from element's content.
|
|
For __select__ type should be defined (as it is ID of shown text).
|
|
|
|
@property value
|
|
@type string|object
|
|
@default null
|
|
**/
|
|
value: null,
|
|
/**
|
|
Value that will be displayed in input if original field value is empty (`null|undefined|''`).
|
|
|
|
@property defaultValue
|
|
@type string|object
|
|
@default null
|
|
@since 1.4.6
|
|
**/
|
|
defaultValue: null,
|
|
/**
|
|
Strategy for sending data on server. Can be `auto|always|never`.
|
|
When 'auto' data will be sent on server **only if pk and url defined**, otherwise new value will be stored locally.
|
|
|
|
@property send
|
|
@type string
|
|
@default 'auto'
|
|
**/
|
|
send: 'auto',
|
|
/**
|
|
Function for client-side validation. If returns string - means validation not passed and string showed as error.
|
|
Since 1.5.1 you can modify submitted value by returning object from `validate`:
|
|
`{newValue: '...'}` or `{newValue: '...', msg: '...'}`
|
|
|
|
@property validate
|
|
@type function
|
|
@default null
|
|
@example
|
|
validate: function(value) {
|
|
if($.trim(value) == '') {
|
|
return 'This field is required';
|
|
}
|
|
}
|
|
**/
|
|
validate: null,
|
|
/**
|
|
Success callback. Called when value successfully sent on server and **response status = 200**.
|
|
Usefull to work with json response. For example, if your backend response can be <code>{success: true}</code>
|
|
or <code>{success: false, msg: "server error"}</code> you can check it inside this callback.
|
|
If it returns **string** - means error occured and string is shown as error message.
|
|
If it returns **object like** <code>{newValue: <something>}</code> - it overwrites value, submitted by user.
|
|
Otherwise newValue simply rendered into element.
|
|
|
|
@property success
|
|
@type function
|
|
@default null
|
|
@example
|
|
success: function(response, newValue) {
|
|
if(!response.success) return response.msg;
|
|
}
|
|
**/
|
|
success: null,
|
|
/**
|
|
Error callback. Called when request failed (response status != 200).
|
|
Usefull when you want to parse error response and display a custom message.
|
|
Must return **string** - the message to be displayed in the error block.
|
|
|
|
@property error
|
|
@type function
|
|
@default null
|
|
@since 1.4.4
|
|
@example
|
|
error: function(response, newValue) {
|
|
if(response.status === 500) {
|
|
return 'Service unavailable. Please try later.';
|
|
} else {
|
|
return response.responseText;
|
|
}
|
|
}
|
|
**/
|
|
error: null,
|
|
/**
|
|
Additional options for submit ajax request.
|
|
List of values: http://api.jquery.com/jQuery.ajax
|
|
|
|
@property ajaxOptions
|
|
@type object
|
|
@default null
|
|
@since 1.1.1
|
|
@example
|
|
ajaxOptions: {
|
|
type: 'put',
|
|
dataType: 'json'
|
|
}
|
|
**/
|
|
ajaxOptions: null,
|
|
/**
|
|
Where to show buttons: left(true)|bottom|false
|
|
Form without buttons is auto-submitted.
|
|
|
|
@property showbuttons
|
|
@type boolean|string
|
|
@default true
|
|
@since 1.1.1
|
|
**/
|
|
showbuttons: true,
|
|
/**
|
|
Scope for callback methods (success, validate).
|
|
If <code>null</code> means editableform instance itself.
|
|
|
|
@property scope
|
|
@type DOMElement|object
|
|
@default null
|
|
@since 1.2.0
|
|
@private
|
|
**/
|
|
scope: null,
|
|
/**
|
|
Whether to save or cancel value when it was not changed but form was submitted
|
|
|
|
@property savenochange
|
|
@type boolean
|
|
@default false
|
|
@since 1.2.0
|
|
**/
|
|
savenochange: false
|
|
};
|
|
|
|
/*
|
|
Note: following params could redefined in engine: bootstrap or jqueryui:
|
|
Classes 'control-group' and 'editable-error-block' must always present!
|
|
*/
|
|
$.fn.editableform.template = '<form class="form-inline editableform">'+
|
|
'<div class="control-group">' +
|
|
'<div><div class="editable-input"></div><div class="editable-buttons"></div></div>'+
|
|
'<div class="editable-error-block"></div>' +
|
|
'</div>' +
|
|
'</form>';
|
|
|
|
//loading div
|
|
$.fn.editableform.loading = '<div class="editableform-loading"></div>';
|
|
|
|
//buttons
|
|
$.fn.editableform.buttons = '<button type="submit" class="editable-submit">ok</button>'+
|
|
'<button type="button" class="editable-cancel">cancel</button>';
|
|
|
|
//error class attached to control-group
|
|
$.fn.editableform.errorGroupClass = null;
|
|
|
|
//error class attached to editable-error-block
|
|
$.fn.editableform.errorBlockClass = 'editable-error';
|
|
|
|
//engine
|
|
$.fn.editableform.engine = 'jquery';
|
|
}(window.jQuery));
|
|
|
|
/**
|
|
* EditableForm utilites
|
|
*/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
//utils
|
|
$.fn.editableutils = {
|
|
/**
|
|
* classic JS inheritance function
|
|
*/
|
|
inherit: function (Child, Parent) {
|
|
var F = function() { };
|
|
F.prototype = Parent.prototype;
|
|
Child.prototype = new F();
|
|
Child.prototype.constructor = Child;
|
|
Child.superclass = Parent.prototype;
|
|
},
|
|
|
|
/**
|
|
* set caret position in input
|
|
* see http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area
|
|
*/
|
|
setCursorPosition: function(elem, pos) {
|
|
if (elem.setSelectionRange) {
|
|
elem.setSelectionRange(pos, pos);
|
|
} else if (elem.createTextRange) {
|
|
var range = elem.createTextRange();
|
|
range.collapse(true);
|
|
range.moveEnd('character', pos);
|
|
range.moveStart('character', pos);
|
|
range.select();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* function to parse JSON in *single* quotes. (jquery automatically parse only double quotes)
|
|
* That allows such code as: <a data-source="{'a': 'b', 'c': 'd'}">
|
|
* safe = true --> means no exception will be thrown
|
|
* for details see http://stackoverflow.com/questions/7410348/how-to-set-json-format-to-html5-data-attributes-in-the-jquery
|
|
*/
|
|
tryParseJson: function(s, safe) {
|
|
if (typeof s === 'string' && s.length && s.match(/^[\{\[].*[\}\]]$/)) {
|
|
if (safe) {
|
|
try {
|
|
/*jslint evil: true*/
|
|
s = (new Function('return ' + s))();
|
|
/*jslint evil: false*/
|
|
} catch (e) {} finally {
|
|
return s;
|
|
}
|
|
} else {
|
|
/*jslint evil: true*/
|
|
s = (new Function('return ' + s))();
|
|
/*jslint evil: false*/
|
|
}
|
|
}
|
|
return s;
|
|
},
|
|
|
|
/**
|
|
* slice object by specified keys
|
|
*/
|
|
sliceObj: function(obj, keys, caseSensitive /* default: false */) {
|
|
var key, keyLower, newObj = {};
|
|
|
|
if (!$.isArray(keys) || !keys.length) {
|
|
return newObj;
|
|
}
|
|
|
|
for (var i = 0; i < keys.length; i++) {
|
|
key = keys[i];
|
|
if (obj.hasOwnProperty(key)) {
|
|
newObj[key] = obj[key];
|
|
}
|
|
|
|
if(caseSensitive === true) {
|
|
continue;
|
|
}
|
|
|
|
//when getting data-* attributes via $.data() it's converted to lowercase.
|
|
//details: http://stackoverflow.com/questions/7602565/using-data-attributes-with-jquery
|
|
//workaround is code below.
|
|
keyLower = key.toLowerCase();
|
|
if (obj.hasOwnProperty(keyLower)) {
|
|
newObj[key] = obj[keyLower];
|
|
}
|
|
}
|
|
|
|
return newObj;
|
|
},
|
|
|
|
/*
|
|
exclude complex objects from $.data() before pass to config
|
|
*/
|
|
getConfigData: function($element) {
|
|
var data = {};
|
|
$.each($element.data(), function(k, v) {
|
|
if(typeof v !== 'object' || (v && typeof v === 'object' && (v.constructor === Object || v.constructor === Array))) {
|
|
data[k] = v;
|
|
}
|
|
});
|
|
return data;
|
|
},
|
|
|
|
/*
|
|
returns keys of object
|
|
*/
|
|
objectKeys: function(o) {
|
|
if (Object.keys) {
|
|
return Object.keys(o);
|
|
} else {
|
|
if (o !== Object(o)) {
|
|
throw new TypeError('Object.keys called on a non-object');
|
|
}
|
|
var k=[], p;
|
|
for (p in o) {
|
|
if (Object.prototype.hasOwnProperty.call(o,p)) {
|
|
k.push(p);
|
|
}
|
|
}
|
|
return k;
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
|
method to escape html.
|
|
**/
|
|
escape: function(str) {
|
|
return $('<div>').text(str).html();
|
|
},
|
|
|
|
/*
|
|
returns array items from sourceData having value property equal or inArray of 'value'
|
|
*/
|
|
itemsByValue: function(value, sourceData, valueProp) {
|
|
if(!sourceData || value === null) {
|
|
return [];
|
|
}
|
|
|
|
if (typeof(valueProp) !== "function") {
|
|
var idKey = valueProp || 'value';
|
|
valueProp = function (e) { return e[idKey]; };
|
|
}
|
|
|
|
var isValArray = $.isArray(value),
|
|
result = [],
|
|
that = this;
|
|
|
|
$.each(sourceData, function(i, o) {
|
|
if(o.children) {
|
|
result = result.concat(that.itemsByValue(value, o.children, valueProp));
|
|
} else {
|
|
/*jslint eqeq: true*/
|
|
if(isValArray) {
|
|
if($.grep(value, function(v){ return v == (o && typeof o === 'object' ? valueProp(o) : o); }).length) {
|
|
result.push(o);
|
|
}
|
|
} else {
|
|
var itemValue = (o && (typeof o === 'object')) ? valueProp(o) : o;
|
|
if(value == itemValue) {
|
|
result.push(o);
|
|
}
|
|
}
|
|
/*jslint eqeq: false*/
|
|
}
|
|
});
|
|
|
|
return result;
|
|
},
|
|
|
|
/*
|
|
Returns input by options: type, mode.
|
|
*/
|
|
createInput: function(options) {
|
|
var TypeConstructor, typeOptions, input,
|
|
type = options.type;
|
|
|
|
//`date` is some kind of virtual type that is transformed to one of exact types
|
|
//depending on mode and core lib
|
|
if(type === 'date') {
|
|
//inline
|
|
if(options.mode === 'inline') {
|
|
if($.fn.editabletypes.datefield) {
|
|
type = 'datefield';
|
|
} else if($.fn.editabletypes.dateuifield) {
|
|
type = 'dateuifield';
|
|
}
|
|
//popup
|
|
} else {
|
|
if($.fn.editabletypes.date) {
|
|
type = 'date';
|
|
} else if($.fn.editabletypes.dateui) {
|
|
type = 'dateui';
|
|
}
|
|
}
|
|
|
|
//if type still `date` and not exist in types, replace with `combodate` that is base input
|
|
if(type === 'date' && !$.fn.editabletypes.date) {
|
|
type = 'combodate';
|
|
}
|
|
}
|
|
|
|
//`datetime` should be datetimefield in 'inline' mode
|
|
if(type === 'datetime' && options.mode === 'inline') {
|
|
type = 'datetimefield';
|
|
}
|
|
|
|
//change wysihtml5 to textarea for jquery UI and plain versions
|
|
if(type === 'wysihtml5' && !$.fn.editabletypes[type]) {
|
|
type = 'textarea';
|
|
}
|
|
|
|
//create input of specified type. Input will be used for converting value, not in form
|
|
if(typeof $.fn.editabletypes[type] === 'function') {
|
|
TypeConstructor = $.fn.editabletypes[type];
|
|
typeOptions = this.sliceObj(options, this.objectKeys(TypeConstructor.defaults));
|
|
input = new TypeConstructor(typeOptions);
|
|
return input;
|
|
} else {
|
|
$.error('Unknown type: '+ type);
|
|
return false;
|
|
}
|
|
},
|
|
|
|
//see http://stackoverflow.com/questions/7264899/detect-css-transitions-using-javascript-and-without-modernizr
|
|
supportsTransitions: function () {
|
|
var b = document.body || document.documentElement,
|
|
s = b.style,
|
|
p = 'transition',
|
|
v = ['Moz', 'Webkit', 'Khtml', 'O', 'ms'];
|
|
|
|
if(typeof s[p] === 'string') {
|
|
return true;
|
|
}
|
|
|
|
// Tests for vendor specific prop
|
|
p = p.charAt(0).toUpperCase() + p.substr(1);
|
|
for(var i=0; i<v.length; i++) {
|
|
if(typeof s[v[i] + p] === 'string') {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
};
|
|
}(window.jQuery));
|
|
|
|
/**
|
|
Attaches stand-alone container with editable-form to HTML element. Element is used only for positioning, value is not stored anywhere.<br>
|
|
This method applied internally in <code>$().editable()</code>. You should subscribe on it's events (save / cancel) to get profit of it.<br>
|
|
Final realization can be different: bootstrap-popover, jqueryui-tooltip, poshytip, inline-div. It depends on which js file you include.<br>
|
|
Applied as jQuery method.
|
|
|
|
@class editableContainer
|
|
@uses editableform
|
|
**/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var Popup = function (element, options) {
|
|
this.init(element, options);
|
|
};
|
|
|
|
var Inline = function (element, options) {
|
|
this.init(element, options);
|
|
};
|
|
|
|
//methods
|
|
Popup.prototype = {
|
|
containerName: null, //method to call container on element
|
|
containerDataName: null, //object name in element's .data()
|
|
innerCss: null, //tbd in child class
|
|
containerClass: 'editable-container editable-popup', //css class applied to container element
|
|
defaults: {}, //container itself defaults
|
|
|
|
init: function(element, options) {
|
|
this.$element = $(element);
|
|
//since 1.4.1 container do not use data-* directly as they already merged into options.
|
|
this.options = $.extend({}, $.fn.editableContainer.defaults, options);
|
|
this.splitOptions();
|
|
|
|
//set scope of form callbacks to element
|
|
this.formOptions.scope = this.$element[0];
|
|
|
|
this.initContainer();
|
|
|
|
//flag to hide container, when saving value will finish
|
|
this.delayedHide = false;
|
|
|
|
//bind 'destroyed' listener to destroy container when element is removed from dom
|
|
this.$element.on('destroyed', $.proxy(function(){
|
|
this.destroy();
|
|
}, this));
|
|
|
|
//attach document handler to close containers on click / escape
|
|
if(!$(document).data('editable-handlers-attached')) {
|
|
//close all on escape
|
|
$(document).on('keyup.editable', function (e) {
|
|
if (e.which === 27) {
|
|
$('.editable-open').editableContainer('hide');
|
|
//todo: return focus on element
|
|
}
|
|
});
|
|
|
|
//close containers when click outside
|
|
//(mousedown could be better than click, it closes everything also on drag drop)
|
|
$(document).on('click.editable', function(e) {
|
|
var $target = $(e.target), i,
|
|
exclude_classes = ['.editable-container',
|
|
'.ui-datepicker-header',
|
|
'.datepicker', //in inline mode datepicker is rendered into body
|
|
'.modal-backdrop',
|
|
'.bootstrap-wysihtml5-insert-image-modal',
|
|
'.bootstrap-wysihtml5-insert-link-modal'
|
|
];
|
|
|
|
//check if element is detached. It occurs when clicking in bootstrap datepicker
|
|
if (!$.contains(document.documentElement, e.target)) {
|
|
return;
|
|
}
|
|
|
|
//for some reason FF 20 generates extra event (click) in select2 widget with e.target = document
|
|
//we need to filter it via construction below. See https://github.com/vitalets/x-editable/issues/199
|
|
//Possibly related to http://stackoverflow.com/questions/10119793/why-does-firefox-react-differently-from-webkit-and-ie-to-click-event-on-selec
|
|
if($target.is(document)) {
|
|
return;
|
|
}
|
|
|
|
//if click inside one of exclude classes --> no nothing
|
|
for(i=0; i<exclude_classes.length; i++) {
|
|
if($target.is(exclude_classes[i]) || $target.parents(exclude_classes[i]).length) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
//close all open containers (except one - target)
|
|
Popup.prototype.closeOthers(e.target);
|
|
});
|
|
|
|
$(document).data('editable-handlers-attached', true);
|
|
}
|
|
},
|
|
|
|
//split options on containerOptions and formOptions
|
|
splitOptions: function() {
|
|
this.containerOptions = {};
|
|
this.formOptions = {};
|
|
|
|
if(!$.fn[this.containerName]) {
|
|
throw new Error(this.containerName + ' not found. Have you included corresponding js file?');
|
|
}
|
|
|
|
//keys defined in container defaults go to container, others go to form
|
|
for(var k in this.options) {
|
|
if(k in this.defaults) {
|
|
this.containerOptions[k] = this.options[k];
|
|
} else {
|
|
this.formOptions[k] = this.options[k];
|
|
}
|
|
}
|
|
},
|
|
|
|
/*
|
|
Returns jquery object of container
|
|
@method tip()
|
|
*/
|
|
tip: function() {
|
|
return this.container() ? this.container().$tip : null;
|
|
},
|
|
|
|
/* returns container object */
|
|
container: function() {
|
|
var container;
|
|
//first, try get it by `containerDataName`
|
|
if(this.containerDataName) {
|
|
if(container = this.$element.data(this.containerDataName)) {
|
|
return container;
|
|
}
|
|
}
|
|
//second, try `containerName`
|
|
container = this.$element.data(this.containerName);
|
|
return container;
|
|
},
|
|
|
|
/* call native method of underlying container, e.g. this.$element.popover('method') */
|
|
call: function() {
|
|
this.$element[this.containerName].apply(this.$element, arguments);
|
|
},
|
|
|
|
initContainer: function(){
|
|
this.call(this.containerOptions);
|
|
},
|
|
|
|
renderForm: function() {
|
|
this.$form
|
|
.editableform(this.formOptions)
|
|
.on({
|
|
save: $.proxy(this.save, this), //click on submit button (value changed)
|
|
nochange: $.proxy(function(){ this.hide('nochange'); }, this), //click on submit button (value NOT changed)
|
|
cancel: $.proxy(function(){ this.hide('cancel'); }, this), //click on calcel button
|
|
show: $.proxy(function() {
|
|
if(this.delayedHide) {
|
|
this.hide(this.delayedHide.reason);
|
|
this.delayedHide = false;
|
|
} else {
|
|
this.setPosition();
|
|
}
|
|
}, this), //re-position container every time form is shown (occurs each time after loading state)
|
|
rendering: $.proxy(this.setPosition, this), //this allows to place container correctly when loading shown
|
|
resize: $.proxy(this.setPosition, this), //this allows to re-position container when form size is changed
|
|
rendered: $.proxy(function(){
|
|
/**
|
|
Fired when container is shown and form is rendered (for select will wait for loading dropdown options).
|
|
**Note:** Bootstrap popover has own `shown` event that now cannot be separated from x-editable's one.
|
|
The workaround is to check `arguments.length` that is always `2` for x-editable.
|
|
|
|
@event shown
|
|
@param {Object} event event object
|
|
@example
|
|
$('#username').on('shown', function(e, editable) {
|
|
editable.input.$input.val('overwriting value of input..');
|
|
});
|
|
**/
|
|
/*
|
|
TODO: added second param mainly to distinguish from bootstrap's shown event. It's a hotfix that will be solved in future versions via namespaced events.
|
|
*/
|
|
this.$element.triggerHandler('shown', $(this.options.scope).data('editable'));
|
|
}, this)
|
|
})
|
|
.editableform('render');
|
|
},
|
|
|
|
/**
|
|
Shows container with form
|
|
@method show()
|
|
@param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
|
|
**/
|
|
/* Note: poshytip owerwrites this method totally! */
|
|
show: function (closeAll) {
|
|
this.$element.addClass('editable-open');
|
|
if(closeAll !== false) {
|
|
//close all open containers (except this)
|
|
this.closeOthers(this.$element[0]);
|
|
}
|
|
|
|
//show container itself
|
|
this.innerShow();
|
|
this.tip().addClass(this.containerClass);
|
|
|
|
/*
|
|
Currently, form is re-rendered on every show.
|
|
The main reason is that we dont know, what will container do with content when closed:
|
|
remove(), detach() or just hide() - it depends on container.
|
|
|
|
Detaching form itself before hide and re-insert before show is good solution,
|
|
but visually it looks ugly --> container changes size before hide.
|
|
*/
|
|
|
|
//if form already exist - delete previous data
|
|
if(this.$form) {
|
|
//todo: destroy prev data!
|
|
//this.$form.destroy();
|
|
}
|
|
|
|
this.$form = $('<div>');
|
|
|
|
//insert form into container body
|
|
if(this.tip().is(this.innerCss)) {
|
|
//for inline container
|
|
this.tip().append(this.$form);
|
|
} else {
|
|
this.tip().find(this.innerCss).append(this.$form);
|
|
}
|
|
|
|
//render form
|
|
this.renderForm();
|
|
},
|
|
|
|
/**
|
|
Hides container with form
|
|
@method hide()
|
|
@param {string} reason Reason caused hiding. Can be <code>save|cancel|onblur|nochange|undefined (=manual)</code>
|
|
**/
|
|
hide: function(reason) {
|
|
if(!this.tip() || !this.tip().is(':visible') || !this.$element.hasClass('editable-open')) {
|
|
return;
|
|
}
|
|
|
|
//if form is saving value, schedule hide
|
|
if(this.$form.data('editableform').isSaving) {
|
|
this.delayedHide = {reason: reason};
|
|
return;
|
|
} else {
|
|
this.delayedHide = false;
|
|
}
|
|
|
|
this.$element.removeClass('editable-open');
|
|
this.innerHide();
|
|
|
|
/**
|
|
Fired when container was hidden. It occurs on both save or cancel.
|
|
**Note:** Bootstrap popover has own `hidden` event that now cannot be separated from x-editable's one.
|
|
The workaround is to check `arguments.length` that is always `2` for x-editable.
|
|
|
|
@event hidden
|
|
@param {object} event event object
|
|
@param {string} reason Reason caused hiding. Can be <code>save|cancel|onblur|nochange|manual</code>
|
|
@example
|
|
$('#username').on('hidden', function(e, reason) {
|
|
if(reason === 'save' || reason === 'cancel') {
|
|
//auto-open next editable
|
|
$(this).closest('tr').next().find('.editable').editable('show');
|
|
}
|
|
});
|
|
**/
|
|
this.$element.triggerHandler('hidden', reason || 'manual');
|
|
},
|
|
|
|
/* internal show method. To be overwritten in child classes */
|
|
innerShow: function () {
|
|
|
|
},
|
|
|
|
/* internal hide method. To be overwritten in child classes */
|
|
innerHide: function () {
|
|
|
|
},
|
|
|
|
/**
|
|
Toggles container visibility (show / hide)
|
|
@method toggle()
|
|
@param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
|
|
**/
|
|
toggle: function(closeAll) {
|
|
if(this.container() && this.tip() && this.tip().is(':visible')) {
|
|
this.hide();
|
|
} else {
|
|
this.show(closeAll);
|
|
}
|
|
},
|
|
|
|
/*
|
|
Updates the position of container when content changed.
|
|
@method setPosition()
|
|
*/
|
|
setPosition: function() {
|
|
//tbd in child class
|
|
},
|
|
|
|
save: function(e, params) {
|
|
/**
|
|
Fired when new value was submitted. You can use <code>$(this).data('editableContainer')</code> inside handler to access to editableContainer instance
|
|
|
|
@event save
|
|
@param {Object} event event object
|
|
@param {Object} params additional params
|
|
@param {mixed} params.newValue submitted value
|
|
@param {Object} params.response ajax response
|
|
@example
|
|
$('#username').on('save', function(e, params) {
|
|
//assuming server response: '{success: true}'
|
|
var pk = $(this).data('editableContainer').options.pk;
|
|
if(params.response && params.response.success) {
|
|
alert('value: ' + params.newValue + ' with pk: ' + pk + ' saved!');
|
|
} else {
|
|
alert('error!');
|
|
}
|
|
});
|
|
**/
|
|
this.$element.triggerHandler('save', params);
|
|
|
|
//hide must be after trigger, as saving value may require methods of plugin, applied to input
|
|
this.hide('save');
|
|
},
|
|
|
|
/**
|
|
Sets new option
|
|
|
|
@method option(key, value)
|
|
@param {string} key
|
|
@param {mixed} value
|
|
**/
|
|
option: function(key, value) {
|
|
this.options[key] = value;
|
|
if(key in this.containerOptions) {
|
|
this.containerOptions[key] = value;
|
|
this.setContainerOption(key, value);
|
|
} else {
|
|
this.formOptions[key] = value;
|
|
if(this.$form) {
|
|
this.$form.editableform('option', key, value);
|
|
}
|
|
}
|
|
},
|
|
|
|
setContainerOption: function(key, value) {
|
|
this.call('option', key, value);
|
|
},
|
|
|
|
/**
|
|
Destroys the container instance
|
|
@method destroy()
|
|
**/
|
|
destroy: function() {
|
|
this.hide();
|
|
this.innerDestroy();
|
|
this.$element.off('destroyed');
|
|
this.$element.removeData('editableContainer');
|
|
},
|
|
|
|
/* to be overwritten in child classes */
|
|
innerDestroy: function() {
|
|
|
|
},
|
|
|
|
/*
|
|
Closes other containers except one related to passed element.
|
|
Other containers can be cancelled or submitted (depends on onblur option)
|
|
*/
|
|
closeOthers: function(element) {
|
|
$('.editable-open').each(function(i, el){
|
|
//do nothing with passed element and it's children
|
|
if(el === element || $(el).find(element).length) {
|
|
return;
|
|
}
|
|
|
|
//otherwise cancel or submit all open containers
|
|
var $el = $(el),
|
|
ec = $el.data('editableContainer');
|
|
|
|
if(!ec) {
|
|
return;
|
|
}
|
|
|
|
if(ec.options.onblur === 'cancel') {
|
|
$el.data('editableContainer').hide('onblur');
|
|
} else if(ec.options.onblur === 'submit') {
|
|
$el.data('editableContainer').tip().find('form').submit();
|
|
}
|
|
});
|
|
|
|
},
|
|
|
|
/**
|
|
Activates input of visible container (e.g. set focus)
|
|
@method activate()
|
|
**/
|
|
activate: function() {
|
|
if(this.tip && this.tip().is(':visible') && this.$form) {
|
|
this.$form.data('editableform').input.activate();
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
/**
|
|
jQuery method to initialize editableContainer.
|
|
|
|
@method $().editableContainer(options)
|
|
@params {Object} options
|
|
@example
|
|
$('#edit').editableContainer({
|
|
type: 'text',
|
|
url: '/post',
|
|
pk: 1,
|
|
value: 'hello'
|
|
});
|
|
**/
|
|
$.fn.editableContainer = function (option) {
|
|
var args = arguments;
|
|
return this.each(function () {
|
|
var $this = $(this),
|
|
dataKey = 'editableContainer',
|
|
data = $this.data(dataKey),
|
|
options = typeof option === 'object' && option,
|
|
Constructor = (options.mode === 'inline') ? Inline : Popup;
|
|
|
|
if (!data) {
|
|
$this.data(dataKey, (data = new Constructor(this, options)));
|
|
}
|
|
|
|
if (typeof option === 'string') { //call method
|
|
data[option].apply(data, Array.prototype.slice.call(args, 1));
|
|
}
|
|
});
|
|
};
|
|
|
|
//store constructors
|
|
$.fn.editableContainer.Popup = Popup;
|
|
$.fn.editableContainer.Inline = Inline;
|
|
|
|
//defaults
|
|
$.fn.editableContainer.defaults = {
|
|
/**
|
|
Initial value of form input
|
|
|
|
@property value
|
|
@type mixed
|
|
@default null
|
|
@private
|
|
**/
|
|
value: null,
|
|
/**
|
|
Placement of container relative to element. Can be <code>top|right|bottom|left</code>. Not used for inline container.
|
|
|
|
@property placement
|
|
@type string
|
|
@default 'top'
|
|
**/
|
|
placement: 'top',
|
|
/**
|
|
Whether to hide container on save/cancel.
|
|
|
|
@property autohide
|
|
@type boolean
|
|
@default true
|
|
@private
|
|
**/
|
|
autohide: true,
|
|
/**
|
|
Action when user clicks outside the container. Can be <code>cancel|submit|ignore</code>.
|
|
Setting <code>ignore</code> allows to have several containers open.
|
|
|
|
@property onblur
|
|
@type string
|
|
@default 'cancel'
|
|
@since 1.1.1
|
|
**/
|
|
onblur: 'cancel',
|
|
|
|
/**
|
|
Animation speed (inline mode only)
|
|
@property anim
|
|
@type string
|
|
@default false
|
|
**/
|
|
anim: false,
|
|
|
|
/**
|
|
Mode of editable, can be `popup` or `inline`
|
|
|
|
@property mode
|
|
@type string
|
|
@default 'popup'
|
|
@since 1.4.0
|
|
**/
|
|
mode: 'popup'
|
|
};
|
|
|
|
/*
|
|
* workaround to have 'destroyed' event to destroy popover when element is destroyed
|
|
* see http://stackoverflow.com/questions/2200494/jquery-trigger-event-when-an-element-is-removed-from-the-dom
|
|
*/
|
|
jQuery.event.special.destroyed = {
|
|
remove: function(o) {
|
|
if (o.handler) {
|
|
o.handler();
|
|
}
|
|
}
|
|
};
|
|
|
|
}(window.jQuery));
|
|
|
|
/**
|
|
* Editable Inline
|
|
* ---------------------
|
|
*/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
//copy prototype from EditableContainer
|
|
//extend methods
|
|
$.extend($.fn.editableContainer.Inline.prototype, $.fn.editableContainer.Popup.prototype, {
|
|
containerName: 'editableform',
|
|
innerCss: '.editable-inline',
|
|
containerClass: 'editable-container editable-inline', //css class applied to container element
|
|
|
|
initContainer: function(){
|
|
//container is <span> element
|
|
this.$tip = $('<span></span>');
|
|
|
|
//convert anim to miliseconds (int)
|
|
if(!this.options.anim) {
|
|
this.options.anim = 0;
|
|
}
|
|
},
|
|
|
|
splitOptions: function() {
|
|
//all options are passed to form
|
|
this.containerOptions = {};
|
|
this.formOptions = this.options;
|
|
},
|
|
|
|
tip: function() {
|
|
return this.$tip;
|
|
},
|
|
|
|
innerShow: function () {
|
|
this.$element.hide();
|
|
this.tip().insertAfter(this.$element).show();
|
|
},
|
|
|
|
innerHide: function () {
|
|
this.$tip.hide(this.options.anim, $.proxy(function() {
|
|
this.$element.show();
|
|
this.innerDestroy();
|
|
}, this));
|
|
},
|
|
|
|
innerDestroy: function() {
|
|
if(this.tip()) {
|
|
this.tip().empty().remove();
|
|
}
|
|
}
|
|
});
|
|
|
|
}(window.jQuery));
|
|
/**
|
|
Makes editable any HTML element on the page. Applied as jQuery method.
|
|
|
|
@class editable
|
|
@uses editableContainer
|
|
**/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var Editable = function (element, options) {
|
|
this.$element = $(element);
|
|
//data-* has more priority over js options: because dynamically created elements may change data-*
|
|
this.options = $.extend({}, $.fn.editable.defaults, options, $.fn.editableutils.getConfigData(this.$element));
|
|
if(this.options.selector) {
|
|
this.initLive();
|
|
} else {
|
|
this.init();
|
|
}
|
|
|
|
//check for transition support
|
|
if(this.options.highlight && !$.fn.editableutils.supportsTransitions()) {
|
|
this.options.highlight = false;
|
|
}
|
|
};
|
|
|
|
Editable.prototype = {
|
|
constructor: Editable,
|
|
init: function () {
|
|
var isValueByText = false,
|
|
doAutotext, finalize;
|
|
|
|
//name
|
|
this.options.name = this.options.name || this.$element.attr('id');
|
|
|
|
//create input of specified type. Input needed already here to convert value for initial display (e.g. show text by id for select)
|
|
//also we set scope option to have access to element inside input specific callbacks (e. g. source as function)
|
|
this.options.scope = this.$element[0];
|
|
this.input = $.fn.editableutils.createInput(this.options);
|
|
if(!this.input) {
|
|
return;
|
|
}
|
|
|
|
//set value from settings or by element's text
|
|
if (this.options.value === undefined || this.options.value === null) {
|
|
this.value = this.input.html2value($.trim(this.$element.html()));
|
|
isValueByText = true;
|
|
} else {
|
|
/*
|
|
value can be string when received from 'data-value' attribute
|
|
for complext objects value can be set as json string in data-value attribute,
|
|
e.g. data-value="{city: 'Moscow', street: 'Lenina'}"
|
|
*/
|
|
this.options.value = $.fn.editableutils.tryParseJson(this.options.value, true);
|
|
if(typeof this.options.value === 'string') {
|
|
this.value = this.input.str2value(this.options.value);
|
|
} else {
|
|
this.value = this.options.value;
|
|
}
|
|
}
|
|
|
|
//add 'editable' class to every editable element
|
|
this.$element.addClass('editable');
|
|
|
|
//specifically for "textarea" add class .editable-pre-wrapped to keep linebreaks
|
|
if(this.input.type === 'textarea') {
|
|
this.$element.addClass('editable-pre-wrapped');
|
|
}
|
|
|
|
//attach handler activating editable. In disabled mode it just prevent default action (useful for links)
|
|
if(this.options.toggle !== 'manual') {
|
|
this.$element.addClass('editable-click');
|
|
this.$element.on(this.options.toggle + '.editable', $.proxy(function(e){
|
|
//prevent following link if editable enabled
|
|
if(!this.options.disabled) {
|
|
e.preventDefault();
|
|
}
|
|
|
|
//stop propagation not required because in document click handler it checks event target
|
|
//e.stopPropagation();
|
|
|
|
if(this.options.toggle === 'mouseenter') {
|
|
//for hover only show container
|
|
this.show();
|
|
} else {
|
|
//when toggle='click' we should not close all other containers as they will be closed automatically in document click listener
|
|
var closeAll = (this.options.toggle !== 'click');
|
|
this.toggle(closeAll);
|
|
}
|
|
}, this));
|
|
} else {
|
|
this.$element.attr('tabindex', -1); //do not stop focus on element when toggled manually
|
|
}
|
|
|
|
//if display is function it's far more convinient to have autotext = always to render correctly on init
|
|
//see https://github.com/vitalets/x-editable-yii/issues/34
|
|
if(typeof this.options.display === 'function') {
|
|
this.options.autotext = 'always';
|
|
}
|
|
|
|
//check conditions for autotext:
|
|
switch(this.options.autotext) {
|
|
case 'always':
|
|
doAutotext = true;
|
|
break;
|
|
case 'auto':
|
|
//if element text is empty and value is defined and value not generated by text --> run autotext
|
|
doAutotext = !$.trim(this.$element.text()).length && this.value !== null && this.value !== undefined && !isValueByText;
|
|
break;
|
|
default:
|
|
doAutotext = false;
|
|
}
|
|
|
|
//depending on autotext run render() or just finilize init
|
|
$.when(doAutotext ? this.render() : true).then($.proxy(function() {
|
|
if(this.options.disabled) {
|
|
this.disable();
|
|
} else {
|
|
this.enable();
|
|
}
|
|
/**
|
|
Fired when element was initialized by `$().editable()` method.
|
|
Please note that you should setup `init` handler **before** applying `editable`.
|
|
|
|
@event init
|
|
@param {Object} event event object
|
|
@param {Object} editable editable instance (as here it cannot accessed via data('editable'))
|
|
@since 1.2.0
|
|
@example
|
|
$('#username').on('init', function(e, editable) {
|
|
alert('initialized ' + editable.options.name);
|
|
});
|
|
$('#username').editable();
|
|
**/
|
|
this.$element.triggerHandler('init', this);
|
|
}, this));
|
|
},
|
|
|
|
/*
|
|
Initializes parent element for live editables
|
|
*/
|
|
initLive: function() {
|
|
//store selector
|
|
var selector = this.options.selector;
|
|
//modify options for child elements
|
|
this.options.selector = false;
|
|
this.options.autotext = 'never';
|
|
//listen toggle events
|
|
this.$element.on(this.options.toggle + '.editable', selector, $.proxy(function(e){
|
|
var $target = $(e.target);
|
|
if(!$target.data('editable')) {
|
|
//if delegated element initially empty, we need to clear it's text (that was manually set to `empty` by user)
|
|
//see https://github.com/vitalets/x-editable/issues/137
|
|
if($target.hasClass(this.options.emptyclass)) {
|
|
$target.empty();
|
|
}
|
|
$target.editable(this.options).trigger(e);
|
|
}
|
|
}, this));
|
|
},
|
|
|
|
/*
|
|
Renders value into element's text.
|
|
Can call custom display method from options.
|
|
Can return deferred object.
|
|
@method render()
|
|
@param {mixed} response server response (if exist) to pass into display function
|
|
*/
|
|
render: function(response) {
|
|
//do not display anything
|
|
if(this.options.display === false) {
|
|
return;
|
|
}
|
|
|
|
//if input has `value2htmlFinal` method, we pass callback in third param to be called when source is loaded
|
|
if(this.input.value2htmlFinal) {
|
|
return this.input.value2html(this.value, this.$element[0], this.options.display, response);
|
|
//if display method defined --> use it
|
|
} else if(typeof this.options.display === 'function') {
|
|
return this.options.display.call(this.$element[0], this.value, response);
|
|
//else use input's original value2html() method
|
|
} else {
|
|
return this.input.value2html(this.value, this.$element[0]);
|
|
}
|
|
},
|
|
|
|
/**
|
|
Enables editable
|
|
@method enable()
|
|
**/
|
|
enable: function() {
|
|
this.options.disabled = false;
|
|
this.$element.removeClass('editable-disabled');
|
|
this.handleEmpty(this.isEmpty);
|
|
if(this.options.toggle !== 'manual') {
|
|
if(this.$element.attr('tabindex') === '-1') {
|
|
this.$element.removeAttr('tabindex');
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
Disables editable
|
|
@method disable()
|
|
**/
|
|
disable: function() {
|
|
this.options.disabled = true;
|
|
this.hide();
|
|
this.$element.addClass('editable-disabled');
|
|
this.handleEmpty(this.isEmpty);
|
|
//do not stop focus on this element
|
|
this.$element.attr('tabindex', -1);
|
|
},
|
|
|
|
/**
|
|
Toggles enabled / disabled state of editable element
|
|
@method toggleDisabled()
|
|
**/
|
|
toggleDisabled: function() {
|
|
if(this.options.disabled) {
|
|
this.enable();
|
|
} else {
|
|
this.disable();
|
|
}
|
|
},
|
|
|
|
/**
|
|
Sets new option
|
|
|
|
@method option(key, value)
|
|
@param {string|object} key option name or object with several options
|
|
@param {mixed} value option new value
|
|
@example
|
|
$('.editable').editable('option', 'pk', 2);
|
|
**/
|
|
option: function(key, value) {
|
|
//set option(s) by object
|
|
if(key && typeof key === 'object') {
|
|
$.each(key, $.proxy(function(k, v){
|
|
this.option($.trim(k), v);
|
|
}, this));
|
|
return;
|
|
}
|
|
|
|
//set option by string
|
|
this.options[key] = value;
|
|
|
|
//disabled
|
|
if(key === 'disabled') {
|
|
return value ? this.disable() : this.enable();
|
|
}
|
|
|
|
//value
|
|
if(key === 'value') {
|
|
this.setValue(value);
|
|
}
|
|
|
|
//transfer new option to container!
|
|
if(this.container) {
|
|
this.container.option(key, value);
|
|
}
|
|
|
|
//pass option to input directly (as it points to the same in form)
|
|
if(this.input.option) {
|
|
this.input.option(key, value);
|
|
}
|
|
|
|
},
|
|
|
|
/*
|
|
* set emptytext if element is empty
|
|
*/
|
|
handleEmpty: function (isEmpty) {
|
|
//do not handle empty if we do not display anything
|
|
if(this.options.display === false) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
isEmpty may be set directly as param of method.
|
|
It is required when we enable/disable field and can't rely on content
|
|
as node content is text: "Empty" that is not empty %)
|
|
*/
|
|
if(isEmpty !== undefined) {
|
|
this.isEmpty = isEmpty;
|
|
} else {
|
|
//detect empty
|
|
//for some inputs we need more smart check
|
|
//e.g. wysihtml5 may have <br>, <p></p>, <img>
|
|
if(typeof(this.input.isEmpty) === 'function') {
|
|
this.isEmpty = this.input.isEmpty(this.$element);
|
|
} else {
|
|
this.isEmpty = $.trim(this.$element.html()) === '';
|
|
}
|
|
}
|
|
|
|
//emptytext shown only for enabled
|
|
if(!this.options.disabled) {
|
|
if (this.isEmpty) {
|
|
this.$element.html(this.options.emptytext);
|
|
if(this.options.emptyclass) {
|
|
this.$element.addClass(this.options.emptyclass);
|
|
}
|
|
} else if(this.options.emptyclass) {
|
|
this.$element.removeClass(this.options.emptyclass);
|
|
}
|
|
} else {
|
|
//below required if element disable property was changed
|
|
if(this.isEmpty) {
|
|
this.$element.empty();
|
|
if(this.options.emptyclass) {
|
|
this.$element.removeClass(this.options.emptyclass);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
Shows container with form
|
|
@method show()
|
|
@param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
|
|
**/
|
|
show: function (closeAll) {
|
|
if(this.options.disabled) {
|
|
return;
|
|
}
|
|
|
|
//init editableContainer: popover, tooltip, inline, etc..
|
|
if(!this.container) {
|
|
var containerOptions = $.extend({}, this.options, {
|
|
value: this.value,
|
|
input: this.input //pass input to form (as it is already created)
|
|
});
|
|
this.$element.editableContainer(containerOptions);
|
|
//listen `save` event
|
|
this.$element.on("save.internal", $.proxy(this.save, this));
|
|
this.container = this.$element.data('editableContainer');
|
|
} else if(this.container.tip().is(':visible')) {
|
|
return;
|
|
}
|
|
|
|
//show container
|
|
this.container.show(closeAll);
|
|
},
|
|
|
|
/**
|
|
Hides container with form
|
|
@method hide()
|
|
**/
|
|
hide: function () {
|
|
if(this.container) {
|
|
this.container.hide();
|
|
}
|
|
},
|
|
|
|
/**
|
|
Toggles container visibility (show / hide)
|
|
@method toggle()
|
|
@param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
|
|
**/
|
|
toggle: function(closeAll) {
|
|
if(this.container && this.container.tip().is(':visible')) {
|
|
this.hide();
|
|
} else {
|
|
this.show(closeAll);
|
|
}
|
|
},
|
|
|
|
/*
|
|
* called when form was submitted
|
|
*/
|
|
save: function(e, params) {
|
|
//mark element with unsaved class if needed
|
|
if(this.options.unsavedclass) {
|
|
/*
|
|
Add unsaved css to element if:
|
|
- url is not user's function
|
|
- value was not sent to server
|
|
- params.response === undefined, that means data was not sent
|
|
- value changed
|
|
*/
|
|
var sent = false;
|
|
sent = sent || typeof this.options.url === 'function';
|
|
sent = sent || this.options.display === false;
|
|
sent = sent || params.response !== undefined;
|
|
sent = sent || (this.options.savenochange && this.input.value2str(this.value) !== this.input.value2str(params.newValue));
|
|
|
|
if(sent) {
|
|
this.$element.removeClass(this.options.unsavedclass);
|
|
} else {
|
|
this.$element.addClass(this.options.unsavedclass);
|
|
}
|
|
}
|
|
|
|
//highlight when saving
|
|
if(this.options.highlight) {
|
|
var $e = this.$element,
|
|
bgColor = $e.css('background-color');
|
|
|
|
$e.css('background-color', this.options.highlight);
|
|
setTimeout(function(){
|
|
if(bgColor === 'transparent') {
|
|
bgColor = '';
|
|
}
|
|
$e.css('background-color', bgColor);
|
|
$e.addClass('editable-bg-transition');
|
|
setTimeout(function(){
|
|
$e.removeClass('editable-bg-transition');
|
|
}, 1700);
|
|
}, 10);
|
|
}
|
|
|
|
//set new value
|
|
this.setValue(params.newValue, false, params.response);
|
|
|
|
/**
|
|
Fired when new value was submitted. You can use <code>$(this).data('editable')</code> to access to editable instance
|
|
|
|
@event save
|
|
@param {Object} event event object
|
|
@param {Object} params additional params
|
|
@param {mixed} params.newValue submitted value
|
|
@param {Object} params.response ajax response
|
|
@example
|
|
$('#username').on('save', function(e, params) {
|
|
alert('Saved value: ' + params.newValue);
|
|
});
|
|
**/
|
|
//event itself is triggered by editableContainer. Description here is only for documentation
|
|
},
|
|
|
|
validate: function () {
|
|
if (typeof this.options.validate === 'function') {
|
|
return this.options.validate.call(this, this.value);
|
|
}
|
|
},
|
|
|
|
/**
|
|
Sets new value of editable
|
|
@method setValue(value, convertStr)
|
|
@param {mixed} value new value
|
|
@param {boolean} convertStr whether to convert value from string to internal format
|
|
**/
|
|
setValue: function(value, convertStr, response) {
|
|
if(convertStr) {
|
|
this.value = this.input.str2value(value);
|
|
} else {
|
|
this.value = value;
|
|
}
|
|
if(this.container) {
|
|
this.container.option('value', this.value);
|
|
}
|
|
$.when(this.render(response))
|
|
.then($.proxy(function() {
|
|
this.handleEmpty();
|
|
}, this));
|
|
},
|
|
|
|
/**
|
|
Activates input of visible container (e.g. set focus)
|
|
@method activate()
|
|
**/
|
|
activate: function() {
|
|
if(this.container) {
|
|
this.container.activate();
|
|
}
|
|
},
|
|
|
|
/**
|
|
Removes editable feature from element
|
|
@method destroy()
|
|
**/
|
|
destroy: function() {
|
|
this.disable();
|
|
|
|
if(this.container) {
|
|
this.container.destroy();
|
|
}
|
|
|
|
this.input.destroy();
|
|
|
|
if(this.options.toggle !== 'manual') {
|
|
this.$element.removeClass('editable-click');
|
|
this.$element.off(this.options.toggle + '.editable');
|
|
}
|
|
|
|
this.$element.off("save.internal");
|
|
|
|
this.$element.removeClass('editable editable-open editable-disabled');
|
|
this.$element.removeData('editable');
|
|
}
|
|
};
|
|
|
|
/* EDITABLE PLUGIN DEFINITION
|
|
* ======================= */
|
|
|
|
/**
|
|
jQuery method to initialize editable element.
|
|
|
|
@method $().editable(options)
|
|
@params {Object} options
|
|
@example
|
|
$('#username').editable({
|
|
type: 'text',
|
|
url: '/post',
|
|
pk: 1
|
|
});
|
|
**/
|
|
$.fn.editable = function (option) {
|
|
//special API methods returning non-jquery object
|
|
var result = {}, args = arguments, datakey = 'editable';
|
|
switch (option) {
|
|
/**
|
|
Runs client-side validation for all matched editables
|
|
|
|
@method validate()
|
|
@returns {Object} validation errors map
|
|
@example
|
|
$('#username, #fullname').editable('validate');
|
|
// possible result:
|
|
{
|
|
username: "username is required",
|
|
fullname: "fullname should be minimum 3 letters length"
|
|
}
|
|
**/
|
|
case 'validate':
|
|
this.each(function () {
|
|
var $this = $(this), data = $this.data(datakey), error;
|
|
if (data && (error = data.validate())) {
|
|
result[data.options.name] = error;
|
|
}
|
|
});
|
|
return result;
|
|
|
|
/**
|
|
Returns current values of editable elements.
|
|
Note that it returns an **object** with name-value pairs, not a value itself. It allows to get data from several elements.
|
|
If value of some editable is `null` or `undefined` it is excluded from result object.
|
|
When param `isSingle` is set to **true** - it is supposed you have single element and will return value of editable instead of object.
|
|
|
|
@method getValue()
|
|
@param {bool} isSingle whether to return just value of single element
|
|
@returns {Object} object of element names and values
|
|
@example
|
|
$('#username, #fullname').editable('getValue');
|
|
//result:
|
|
{
|
|
username: "superuser",
|
|
fullname: "John"
|
|
}
|
|
//isSingle = true
|
|
$('#username').editable('getValue', true);
|
|
//result "superuser"
|
|
**/
|
|
case 'getValue':
|
|
if(arguments.length === 2 && arguments[1] === true) { //isSingle = true
|
|
result = this.eq(0).data(datakey).value;
|
|
} else {
|
|
this.each(function () {
|
|
var $this = $(this), data = $this.data(datakey);
|
|
if (data && data.value !== undefined && data.value !== null) {
|
|
result[data.options.name] = data.input.value2submit(data.value);
|
|
}
|
|
});
|
|
}
|
|
return result;
|
|
|
|
/**
|
|
This method collects values from several editable elements and submit them all to server.
|
|
Internally it runs client-side validation for all fields and submits only in case of success.
|
|
See <a href="#newrecord">creating new records</a> for details.
|
|
Since 1.5.1 `submit` can be applied to single element to send data programmatically. In that case
|
|
`url`, `success` and `error` is taken from initial options and you can just call `$('#username').editable('submit')`.
|
|
|
|
@method submit(options)
|
|
@param {object} options
|
|
@param {object} options.url url to submit data
|
|
@param {object} options.data additional data to submit
|
|
@param {object} options.ajaxOptions additional ajax options
|
|
@param {function} options.error(obj) error handler
|
|
@param {function} options.success(obj,config) success handler
|
|
@returns {Object} jQuery object
|
|
**/
|
|
case 'submit': //collects value, validate and submit to server for creating new record
|
|
var config = arguments[1] || {},
|
|
$elems = this,
|
|
errors = this.editable('validate');
|
|
|
|
// validation ok
|
|
if($.isEmptyObject(errors)) {
|
|
var ajaxOptions = {};
|
|
|
|
// for single element use url, success etc from options
|
|
if($elems.length === 1) {
|
|
var editable = $elems.data('editable');
|
|
//standard params
|
|
var params = {
|
|
name: editable.options.name || '',
|
|
value: editable.input.value2submit(editable.value),
|
|
pk: (typeof editable.options.pk === 'function') ?
|
|
editable.options.pk.call(editable.options.scope) :
|
|
editable.options.pk
|
|
};
|
|
|
|
//additional params
|
|
if(typeof editable.options.params === 'function') {
|
|
params = editable.options.params.call(editable.options.scope, params);
|
|
} else {
|
|
//try parse json in single quotes (from data-params attribute)
|
|
editable.options.params = $.fn.editableutils.tryParseJson(editable.options.params, true);
|
|
$.extend(params, editable.options.params);
|
|
}
|
|
|
|
ajaxOptions = {
|
|
url: editable.options.url,
|
|
data: params,
|
|
type: 'POST'
|
|
};
|
|
|
|
// use success / error from options
|
|
config.success = config.success || editable.options.success;
|
|
config.error = config.error || editable.options.error;
|
|
|
|
// multiple elements
|
|
} else {
|
|
var values = this.editable('getValue');
|
|
|
|
ajaxOptions = {
|
|
url: config.url,
|
|
data: values,
|
|
type: 'POST'
|
|
};
|
|
}
|
|
|
|
// ajax success callabck (response 200 OK)
|
|
ajaxOptions.success = typeof config.success === 'function' ? function(response) {
|
|
config.success.call($elems, response, config);
|
|
} : $.noop;
|
|
|
|
// ajax error callabck
|
|
ajaxOptions.error = typeof config.error === 'function' ? function() {
|
|
config.error.apply($elems, arguments);
|
|
} : $.noop;
|
|
|
|
// extend ajaxOptions
|
|
if(config.ajaxOptions) {
|
|
$.extend(ajaxOptions, config.ajaxOptions);
|
|
}
|
|
|
|
// extra data
|
|
if(config.data) {
|
|
$.extend(ajaxOptions.data, config.data);
|
|
}
|
|
|
|
// perform ajax request
|
|
$.ajax(ajaxOptions);
|
|
} else { //client-side validation error
|
|
if(typeof config.error === 'function') {
|
|
config.error.call($elems, errors);
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
|
|
//return jquery object
|
|
return this.each(function () {
|
|
var $this = $(this),
|
|
data = $this.data(datakey),
|
|
options = typeof option === 'object' && option;
|
|
|
|
//for delegated targets do not store `editable` object for element
|
|
//it's allows several different selectors.
|
|
//see: https://github.com/vitalets/x-editable/issues/312
|
|
if(options && options.selector) {
|
|
data = new Editable(this, options);
|
|
return;
|
|
}
|
|
|
|
if (!data) {
|
|
$this.data(datakey, (data = new Editable(this, options)));
|
|
}
|
|
|
|
if (typeof option === 'string') { //call method
|
|
data[option].apply(data, Array.prototype.slice.call(args, 1));
|
|
}
|
|
});
|
|
};
|
|
|
|
|
|
$.fn.editable.defaults = {
|
|
/**
|
|
Type of input. Can be <code>text|textarea|select|date|checklist</code> and more
|
|
|
|
@property type
|
|
@type string
|
|
@default 'text'
|
|
**/
|
|
type: 'text',
|
|
/**
|
|
Sets disabled state of editable
|
|
|
|
@property disabled
|
|
@type boolean
|
|
@default false
|
|
**/
|
|
disabled: false,
|
|
/**
|
|
How to toggle editable. Can be <code>click|dblclick|mouseenter|manual</code>.
|
|
When set to <code>manual</code> you should manually call <code>show/hide</code> methods of editable.
|
|
**Note**: if you call <code>show</code> or <code>toggle</code> inside **click** handler of some DOM element,
|
|
you need to apply <code>e.stopPropagation()</code> because containers are being closed on any click on document.
|
|
|
|
@example
|
|
$('#edit-button').click(function(e) {
|
|
e.stopPropagation();
|
|
$('#username').editable('toggle');
|
|
});
|
|
|
|
@property toggle
|
|
@type string
|
|
@default 'click'
|
|
**/
|
|
toggle: 'click',
|
|
/**
|
|
Text shown when element is empty.
|
|
|
|
@property emptytext
|
|
@type string
|
|
@default 'Empty'
|
|
**/
|
|
emptytext: 'Empty',
|
|
/**
|
|
Allows to automatically set element's text based on it's value. Can be <code>auto|always|never</code>. Useful for select and date.
|
|
For example, if dropdown list is <code>{1: 'a', 2: 'b'}</code> and element's value set to <code>1</code>, it's html will be automatically set to <code>'a'</code>.
|
|
<code>auto</code> - text will be automatically set only if element is empty.
|
|
<code>always|never</code> - always(never) try to set element's text.
|
|
|
|
@property autotext
|
|
@type string
|
|
@default 'auto'
|
|
**/
|
|
autotext: 'auto',
|
|
/**
|
|
Initial value of input. If not set, taken from element's text.
|
|
Note, that if element's text is empty - text is automatically generated from value and can be customized (see `autotext` option).
|
|
For example, to display currency sign:
|
|
@example
|
|
<a id="price" data-type="text" data-value="100"></a>
|
|
<script>
|
|
$('#price').editable({
|
|
...
|
|
display: function(value) {
|
|
$(this).text(value + '$');
|
|
}
|
|
})
|
|
</script>
|
|
|
|
@property value
|
|
@type mixed
|
|
@default element's text
|
|
**/
|
|
value: null,
|
|
/**
|
|
Callback to perform custom displaying of value in element's text.
|
|
If `null`, default input's display used.
|
|
If `false`, no displaying methods will be called, element's text will never change.
|
|
Runs under element's scope.
|
|
_**Parameters:**_
|
|
|
|
* `value` current value to be displayed
|
|
* `response` server response (if display called after ajax submit), since 1.4.0
|
|
|
|
For _inputs with source_ (select, checklist) parameters are different:
|
|
|
|
* `value` current value to be displayed
|
|
* `sourceData` array of items for current input (e.g. dropdown items)
|
|
* `response` server response (if display called after ajax submit), since 1.4.0
|
|
|
|
To get currently selected items use `$.fn.editableutils.itemsByValue(value, sourceData)`.
|
|
|
|
@property display
|
|
@type function|boolean
|
|
@default null
|
|
@since 1.2.0
|
|
@example
|
|
display: function(value, sourceData) {
|
|
//display checklist as comma-separated values
|
|
var html = [],
|
|
checked = $.fn.editableutils.itemsByValue(value, sourceData);
|
|
|
|
if(checked.length) {
|
|
$.each(checked, function(i, v) { html.push($.fn.editableutils.escape(v.text)); });
|
|
$(this).html(html.join(', '));
|
|
} else {
|
|
$(this).empty();
|
|
}
|
|
}
|
|
**/
|
|
display: null,
|
|
/**
|
|
Css class applied when editable text is empty.
|
|
|
|
@property emptyclass
|
|
@type string
|
|
@since 1.4.1
|
|
@default editable-empty
|
|
**/
|
|
emptyclass: 'editable-empty',
|
|
/**
|
|
Css class applied when value was stored but not sent to server (`pk` is empty or `send = 'never'`).
|
|
You may set it to `null` if you work with editables locally and submit them together.
|
|
|
|
@property unsavedclass
|
|
@type string
|
|
@since 1.4.1
|
|
@default editable-unsaved
|
|
**/
|
|
unsavedclass: 'editable-unsaved',
|
|
/**
|
|
If selector is provided, editable will be delegated to the specified targets.
|
|
Usefull for dynamically generated DOM elements.
|
|
**Please note**, that delegated targets can't be initialized with `emptytext` and `autotext` options,
|
|
as they actually become editable only after first click.
|
|
You should manually set class `editable-click` to these elements.
|
|
Also, if element originally empty you should add class `editable-empty`, set `data-value=""` and write emptytext into element:
|
|
|
|
@property selector
|
|
@type string
|
|
@since 1.4.1
|
|
@default null
|
|
@example
|
|
<div id="user">
|
|
<!-- empty -->
|
|
<a href="#" data-name="username" data-type="text" class="editable-click editable-empty" data-value="" title="Username">Empty</a>
|
|
<!-- non-empty -->
|
|
<a href="#" data-name="group" data-type="select" data-source="/groups" data-value="1" class="editable-click" title="Group">Operator</a>
|
|
</div>
|
|
|
|
<script>
|
|
$('#user').editable({
|
|
selector: 'a',
|
|
url: '/post',
|
|
pk: 1
|
|
});
|
|
</script>
|
|
**/
|
|
selector: null,
|
|
/**
|
|
Color used to highlight element after update. Implemented via CSS3 transition, works in modern browsers.
|
|
|
|
@property highlight
|
|
@type string|boolean
|
|
@since 1.4.5
|
|
@default #FFFF80
|
|
**/
|
|
highlight: '#FFFF80'
|
|
};
|
|
|
|
}(window.jQuery));
|
|
|
|
/**
|
|
AbstractInput - base class for all editable inputs.
|
|
It defines interface to be implemented by any input type.
|
|
To create your own input you can inherit from this class.
|
|
|
|
@class abstractinput
|
|
**/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
//types
|
|
$.fn.editabletypes = {};
|
|
|
|
var AbstractInput = function () { };
|
|
|
|
AbstractInput.prototype = {
|
|
/**
|
|
Initializes input
|
|
|
|
@method init()
|
|
**/
|
|
init: function(type, options, defaults) {
|
|
this.type = type;
|
|
this.options = $.extend({}, defaults, options);
|
|
},
|
|
|
|
/*
|
|
this method called before render to init $tpl that is inserted in DOM
|
|
*/
|
|
prerender: function() {
|
|
this.$tpl = $(this.options.tpl); //whole tpl as jquery object
|
|
this.$input = this.$tpl; //control itself, can be changed in render method
|
|
this.$clear = null; //clear button
|
|
this.error = null; //error message, if input cannot be rendered
|
|
},
|
|
|
|
/**
|
|
Renders input from tpl. Can return jQuery deferred object.
|
|
Can be overwritten in child objects
|
|
|
|
@method render()
|
|
**/
|
|
render: function() {
|
|
|
|
},
|
|
|
|
/**
|
|
Sets element's html by value.
|
|
|
|
@method value2html(value, element)
|
|
@param {mixed} value
|
|
@param {DOMElement} element
|
|
**/
|
|
value2html: function(value, element) {
|
|
$(element)[this.options.escape ? 'text' : 'html']($.trim(value));
|
|
},
|
|
|
|
/**
|
|
Converts element's html to value
|
|
|
|
@method html2value(html)
|
|
@param {string} html
|
|
@returns {mixed}
|
|
**/
|
|
html2value: function(html) {
|
|
return $('<div>').html(html).text();
|
|
},
|
|
|
|
/**
|
|
Converts value to string (for internal compare). For submitting to server used value2submit().
|
|
|
|
@method value2str(value)
|
|
@param {mixed} value
|
|
@returns {string}
|
|
**/
|
|
value2str: function(value) {
|
|
return value;
|
|
},
|
|
|
|
/**
|
|
Converts string received from server into value. Usually from `data-value` attribute.
|
|
|
|
@method str2value(str)
|
|
@param {string} str
|
|
@returns {mixed}
|
|
**/
|
|
str2value: function(str) {
|
|
return str;
|
|
},
|
|
|
|
/**
|
|
Converts value for submitting to server. Result can be string or object.
|
|
|
|
@method value2submit(value)
|
|
@param {mixed} value
|
|
@returns {mixed}
|
|
**/
|
|
value2submit: function(value) {
|
|
return value;
|
|
},
|
|
|
|
/**
|
|
Sets value of input.
|
|
|
|
@method value2input(value)
|
|
@param {mixed} value
|
|
**/
|
|
value2input: function(value) {
|
|
this.$input.val(value);
|
|
},
|
|
|
|
/**
|
|
Returns value of input. Value can be object (e.g. datepicker)
|
|
|
|
@method input2value()
|
|
**/
|
|
input2value: function() {
|
|
return this.$input.val();
|
|
},
|
|
|
|
/**
|
|
Activates input. For text it sets focus.
|
|
|
|
@method activate()
|
|
**/
|
|
activate: function() {
|
|
if(this.$input.is(':visible')) {
|
|
this.$input.focus();
|
|
}
|
|
},
|
|
|
|
/**
|
|
Creates input.
|
|
|
|
@method clear()
|
|
**/
|
|
clear: function() {
|
|
this.$input.val(null);
|
|
},
|
|
|
|
/**
|
|
method to escape html.
|
|
**/
|
|
escape: function(str) {
|
|
return $('<div>').text(str).html();
|
|
},
|
|
|
|
/**
|
|
attach handler to automatically submit form when value changed (useful when buttons not shown)
|
|
**/
|
|
autosubmit: function() {
|
|
|
|
},
|
|
|
|
/**
|
|
Additional actions when destroying element
|
|
**/
|
|
destroy: function() {
|
|
},
|
|
|
|
// -------- helper functions --------
|
|
setClass: function() {
|
|
if(this.options.inputclass) {
|
|
this.$input.addClass(this.options.inputclass);
|
|
}
|
|
},
|
|
|
|
setAttr: function(attr) {
|
|
if (this.options[attr] !== undefined && this.options[attr] !== null) {
|
|
this.$input.attr(attr, this.options[attr]);
|
|
}
|
|
},
|
|
|
|
option: function(key, value) {
|
|
this.options[key] = value;
|
|
}
|
|
|
|
};
|
|
|
|
AbstractInput.defaults = {
|
|
/**
|
|
HTML template of input. Normally you should not change it.
|
|
|
|
@property tpl
|
|
@type string
|
|
@default ''
|
|
**/
|
|
tpl: '',
|
|
/**
|
|
CSS class automatically applied to input
|
|
|
|
@property inputclass
|
|
@type string
|
|
@default null
|
|
**/
|
|
inputclass: null,
|
|
|
|
/**
|
|
If `true` - html will be escaped in content of element via $.text() method.
|
|
If `false` - html will not be escaped, $.html() used.
|
|
When you use own `display` function, this option obviosly has no effect.
|
|
|
|
@property escape
|
|
@type boolean
|
|
@since 1.5.0
|
|
@default true
|
|
**/
|
|
escape: true,
|
|
|
|
//scope for external methods (e.g. source defined as function)
|
|
//for internal use only
|
|
scope: null,
|
|
|
|
//need to re-declare showbuttons here to get it's value from common config (passed only options existing in defaults)
|
|
showbuttons: true
|
|
};
|
|
|
|
$.extend($.fn.editabletypes, {abstractinput: AbstractInput});
|
|
|
|
}(window.jQuery));
|
|
|
|
/**
|
|
List - abstract class for inputs that have source option loaded from js array or via ajax
|
|
|
|
@class list
|
|
@extends abstractinput
|
|
**/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var List = function (options) {
|
|
|
|
};
|
|
|
|
$.fn.editableutils.inherit(List, $.fn.editabletypes.abstractinput);
|
|
|
|
$.extend(List.prototype, {
|
|
render: function () {
|
|
var deferred = $.Deferred();
|
|
|
|
this.error = null;
|
|
this.onSourceReady(function () {
|
|
this.renderList();
|
|
deferred.resolve();
|
|
}, function () {
|
|
this.error = this.options.sourceError;
|
|
deferred.resolve();
|
|
});
|
|
|
|
return deferred.promise();
|
|
},
|
|
|
|
html2value: function (html) {
|
|
return null; //can't set value by text
|
|
},
|
|
|
|
value2html: function (value, element, display, response) {
|
|
var deferred = $.Deferred(),
|
|
success = function () {
|
|
if(typeof display === 'function') {
|
|
//custom display method
|
|
display.call(element, value, this.sourceData, response);
|
|
} else {
|
|
this.value2htmlFinal(value, element);
|
|
}
|
|
deferred.resolve();
|
|
};
|
|
|
|
//for null value just call success without loading source
|
|
if(value === null) {
|
|
success.call(this);
|
|
} else {
|
|
this.onSourceReady(success, function () { deferred.resolve(); });
|
|
}
|
|
|
|
return deferred.promise();
|
|
},
|
|
|
|
// ------------- additional functions ------------
|
|
|
|
onSourceReady: function (success, error) {
|
|
//run source if it function
|
|
var source;
|
|
if ($.isFunction(this.options.source)) {
|
|
source = this.options.source.call(this.options.scope);
|
|
this.sourceData = null;
|
|
//note: if function returns the same source as URL - sourceData will be taken from cahce and no extra request performed
|
|
} else {
|
|
source = this.options.source;
|
|
}
|
|
|
|
//if allready loaded just call success
|
|
if(this.options.sourceCache && $.isArray(this.sourceData)) {
|
|
success.call(this);
|
|
return;
|
|
}
|
|
|
|
//try parse json in single quotes (for double quotes jquery does automatically)
|
|
try {
|
|
source = $.fn.editableutils.tryParseJson(source, false);
|
|
} catch (e) {
|
|
error.call(this);
|
|
return;
|
|
}
|
|
|
|
//loading from url
|
|
if (typeof source === 'string') {
|
|
//try to get sourceData from cache
|
|
if(this.options.sourceCache) {
|
|
var cacheID = source,
|
|
cache;
|
|
|
|
if (!$(document).data(cacheID)) {
|
|
$(document).data(cacheID, {});
|
|
}
|
|
cache = $(document).data(cacheID);
|
|
|
|
//check for cached data
|
|
if (cache.loading === false && cache.sourceData) { //take source from cache
|
|
this.sourceData = cache.sourceData;
|
|
this.doPrepend();
|
|
success.call(this);
|
|
return;
|
|
} else if (cache.loading === true) { //cache is loading, put callback in stack to be called later
|
|
cache.callbacks.push($.proxy(function () {
|
|
this.sourceData = cache.sourceData;
|
|
this.doPrepend();
|
|
success.call(this);
|
|
}, this));
|
|
|
|
//also collecting error callbacks
|
|
cache.err_callbacks.push($.proxy(error, this));
|
|
return;
|
|
} else { //no cache yet, activate it
|
|
cache.loading = true;
|
|
cache.callbacks = [];
|
|
cache.err_callbacks = [];
|
|
}
|
|
}
|
|
|
|
//ajaxOptions for source. Can be overwritten bt options.sourceOptions
|
|
var ajaxOptions = $.extend({
|
|
url: source,
|
|
type: 'get',
|
|
cache: false,
|
|
dataType: 'json',
|
|
success: $.proxy(function (data) {
|
|
if(cache) {
|
|
cache.loading = false;
|
|
}
|
|
this.sourceData = this.makeArray(data);
|
|
if($.isArray(this.sourceData)) {
|
|
if(cache) {
|
|
//store result in cache
|
|
cache.sourceData = this.sourceData;
|
|
//run success callbacks for other fields waiting for this source
|
|
$.each(cache.callbacks, function () { this.call(); });
|
|
}
|
|
this.doPrepend();
|
|
success.call(this);
|
|
} else {
|
|
error.call(this);
|
|
if(cache) {
|
|
//run error callbacks for other fields waiting for this source
|
|
$.each(cache.err_callbacks, function () { this.call(); });
|
|
}
|
|
}
|
|
}, this),
|
|
error: $.proxy(function () {
|
|
error.call(this);
|
|
if(cache) {
|
|
cache.loading = false;
|
|
//run error callbacks for other fields
|
|
$.each(cache.err_callbacks, function () { this.call(); });
|
|
}
|
|
}, this)
|
|
}, this.options.sourceOptions);
|
|
|
|
//loading sourceData from server
|
|
$.ajax(ajaxOptions);
|
|
|
|
} else { //options as json/array
|
|
this.sourceData = this.makeArray(source);
|
|
|
|
if($.isArray(this.sourceData)) {
|
|
this.doPrepend();
|
|
success.call(this);
|
|
} else {
|
|
error.call(this);
|
|
}
|
|
}
|
|
},
|
|
|
|
doPrepend: function () {
|
|
if(this.options.prepend === null || this.options.prepend === undefined) {
|
|
return;
|
|
}
|
|
|
|
if(!$.isArray(this.prependData)) {
|
|
//run prepend if it is function (once)
|
|
if ($.isFunction(this.options.prepend)) {
|
|
this.options.prepend = this.options.prepend.call(this.options.scope);
|
|
}
|
|
|
|
//try parse json in single quotes
|
|
this.options.prepend = $.fn.editableutils.tryParseJson(this.options.prepend, true);
|
|
|
|
//convert prepend from string to object
|
|
if (typeof this.options.prepend === 'string') {
|
|
this.options.prepend = {'': this.options.prepend};
|
|
}
|
|
|
|
this.prependData = this.makeArray(this.options.prepend);
|
|
}
|
|
|
|
if($.isArray(this.prependData) && $.isArray(this.sourceData)) {
|
|
this.sourceData = this.prependData.concat(this.sourceData);
|
|
}
|
|
},
|
|
|
|
/*
|
|
renders input list
|
|
*/
|
|
renderList: function() {
|
|
// this method should be overwritten in child class
|
|
},
|
|
|
|
/*
|
|
set element's html by value
|
|
*/
|
|
value2htmlFinal: function(value, element) {
|
|
// this method should be overwritten in child class
|
|
},
|
|
|
|
/**
|
|
* convert data to array suitable for sourceData, e.g. [{value: 1, text: 'abc'}, {...}]
|
|
*/
|
|
makeArray: function(data) {
|
|
var count, obj, result = [], item, iterateItem;
|
|
if(!data || typeof data === 'string') {
|
|
return null;
|
|
}
|
|
|
|
if($.isArray(data)) { //array
|
|
/*
|
|
function to iterate inside item of array if item is object.
|
|
Caclulates count of keys in item and store in obj.
|
|
*/
|
|
iterateItem = function (k, v) {
|
|
obj = {value: k, text: v};
|
|
if(count++ >= 2) {
|
|
return false;// exit from `each` if item has more than one key.
|
|
}
|
|
};
|
|
|
|
for(var i = 0; i < data.length; i++) {
|
|
item = data[i];
|
|
if(typeof item === 'object') {
|
|
count = 0; //count of keys inside item
|
|
$.each(item, iterateItem);
|
|
//case: [{val1: 'text1'}, {val2: 'text2} ...]
|
|
if(count === 1) {
|
|
result.push(obj);
|
|
//case: [{value: 1, text: 'text1'}, {value: 2, text: 'text2'}, ...]
|
|
} else if(count > 1) {
|
|
//removed check of existance: item.hasOwnProperty('value') && item.hasOwnProperty('text')
|
|
if(item.children) {
|
|
item.children = this.makeArray(item.children);
|
|
}
|
|
result.push(item);
|
|
}
|
|
} else {
|
|
//case: ['text1', 'text2' ...]
|
|
result.push({value: item, text: item});
|
|
}
|
|
}
|
|
} else { //case: {val1: 'text1', val2: 'text2, ...}
|
|
$.each(data, function (k, v) {
|
|
result.push({value: k, text: v});
|
|
});
|
|
}
|
|
return result;
|
|
},
|
|
|
|
option: function(key, value) {
|
|
this.options[key] = value;
|
|
if(key === 'source') {
|
|
this.sourceData = null;
|
|
}
|
|
if(key === 'prepend') {
|
|
this.prependData = null;
|
|
}
|
|
}
|
|
|
|
});
|
|
|
|
List.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
|
|
/**
|
|
Source data for list.
|
|
If **array** - it should be in format: `[{value: 1, text: "text1"}, {value: 2, text: "text2"}, ...]`
|
|
For compability, object format is also supported: `{"1": "text1", "2": "text2" ...}` but it does not guarantee elements order.
|
|
|
|
If **string** - considered ajax url to load items. In that case results will be cached for fields with the same source and name. See also `sourceCache` option.
|
|
|
|
If **function**, it should return data in format above (since 1.4.0).
|
|
|
|
Since 1.4.1 key `children` supported to render OPTGROUP (for **select** input only).
|
|
`[{text: "group1", children: [{value: 1, text: "text1"}, {value: 2, text: "text2"}]}, ...]`
|
|
|
|
|
|
@property source
|
|
@type string | array | object | function
|
|
@default null
|
|
**/
|
|
source: null,
|
|
/**
|
|
Data automatically prepended to the beginning of dropdown list.
|
|
|
|
@property prepend
|
|
@type string | array | object | function
|
|
@default false
|
|
**/
|
|
prepend: false,
|
|
/**
|
|
Error message when list cannot be loaded (e.g. ajax error)
|
|
|
|
@property sourceError
|
|
@type string
|
|
@default Error when loading list
|
|
**/
|
|
sourceError: 'Error when loading list',
|
|
/**
|
|
if <code>true</code> and source is **string url** - results will be cached for fields with the same source.
|
|
Usefull for editable column in grid to prevent extra requests.
|
|
|
|
@property sourceCache
|
|
@type boolean
|
|
@default true
|
|
@since 1.2.0
|
|
**/
|
|
sourceCache: true,
|
|
/**
|
|
Additional ajax options to be used in $.ajax() when loading list from server.
|
|
Useful to send extra parameters (`data` key) or change request method (`type` key).
|
|
|
|
@property sourceOptions
|
|
@type object|function
|
|
@default null
|
|
@since 1.5.0
|
|
**/
|
|
sourceOptions: null
|
|
});
|
|
|
|
$.fn.editabletypes.list = List;
|
|
|
|
}(window.jQuery));
|
|
|
|
/**
|
|
Text input
|
|
|
|
@class text
|
|
@extends abstractinput
|
|
@final
|
|
@example
|
|
<a href="#" id="username" data-type="text" data-pk="1">awesome</a>
|
|
<script>
|
|
$(function(){
|
|
$('#username').editable({
|
|
url: '/post',
|
|
title: 'Enter username'
|
|
});
|
|
});
|
|
</script>
|
|
**/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var Text = function (options) {
|
|
this.init('text', options, Text.defaults);
|
|
};
|
|
|
|
$.fn.editableutils.inherit(Text, $.fn.editabletypes.abstractinput);
|
|
|
|
$.extend(Text.prototype, {
|
|
render: function() {
|
|
this.renderClear();
|
|
this.setClass();
|
|
this.setAttr('placeholder');
|
|
},
|
|
|
|
activate: function() {
|
|
if(this.$input.is(':visible')) {
|
|
this.$input.focus();
|
|
$.fn.editableutils.setCursorPosition(this.$input.get(0), this.$input.val().length);
|
|
if(this.toggleClear) {
|
|
this.toggleClear();
|
|
}
|
|
}
|
|
},
|
|
|
|
//render clear button
|
|
renderClear: function() {
|
|
if (this.options.clear) {
|
|
this.$clear = $('<span class="editable-clear-x"></span>');
|
|
this.$input.after(this.$clear)
|
|
.css('padding-right', 24)
|
|
.keyup($.proxy(function(e) {
|
|
//arrows, enter, tab, etc
|
|
if(~$.inArray(e.keyCode, [40,38,9,13,27])) {
|
|
return;
|
|
}
|
|
|
|
clearTimeout(this.t);
|
|
var that = this;
|
|
this.t = setTimeout(function() {
|
|
that.toggleClear(e);
|
|
}, 100);
|
|
|
|
}, this))
|
|
.parent().css('position', 'relative');
|
|
|
|
this.$clear.click($.proxy(this.clear, this));
|
|
}
|
|
},
|
|
|
|
postrender: function() {
|
|
/*
|
|
//now `clear` is positioned via css
|
|
if(this.$clear) {
|
|
//can position clear button only here, when form is shown and height can be calculated
|
|
// var h = this.$input.outerHeight(true) || 20,
|
|
var h = this.$clear.parent().height(),
|
|
delta = (h - this.$clear.height()) / 2;
|
|
|
|
//this.$clear.css({bottom: delta, right: delta});
|
|
}
|
|
*/
|
|
},
|
|
|
|
//show / hide clear button
|
|
toggleClear: function(e) {
|
|
if(!this.$clear) {
|
|
return;
|
|
}
|
|
|
|
var len = this.$input.val().length,
|
|
visible = this.$clear.is(':visible');
|
|
|
|
if(len && !visible) {
|
|
this.$clear.show();
|
|
}
|
|
|
|
if(!len && visible) {
|
|
this.$clear.hide();
|
|
}
|
|
},
|
|
|
|
clear: function() {
|
|
this.$clear.hide();
|
|
this.$input.val('').focus();
|
|
}
|
|
});
|
|
|
|
Text.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
|
|
/**
|
|
@property tpl
|
|
@default <input type="text">
|
|
**/
|
|
tpl: '<input type="text">',
|
|
/**
|
|
Placeholder attribute of input. Shown when input is empty.
|
|
|
|
@property placeholder
|
|
@type string
|
|
@default null
|
|
**/
|
|
placeholder: null,
|
|
|
|
/**
|
|
Whether to show `clear` button
|
|
|
|
@property clear
|
|
@type boolean
|
|
@default true
|
|
**/
|
|
clear: true
|
|
});
|
|
|
|
$.fn.editabletypes.text = Text;
|
|
|
|
}(window.jQuery));
|
|
|
|
/**
|
|
Textarea input
|
|
|
|
@class textarea
|
|
@extends abstractinput
|
|
@final
|
|
@example
|
|
<a href="#" id="comments" data-type="textarea" data-pk="1">awesome comment!</a>
|
|
<script>
|
|
$(function(){
|
|
$('#comments').editable({
|
|
url: '/post',
|
|
title: 'Enter comments',
|
|
rows: 10
|
|
});
|
|
});
|
|
</script>
|
|
**/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var Textarea = function (options) {
|
|
this.init('textarea', options, Textarea.defaults);
|
|
};
|
|
|
|
$.fn.editableutils.inherit(Textarea, $.fn.editabletypes.abstractinput);
|
|
|
|
$.extend(Textarea.prototype, {
|
|
render: function () {
|
|
this.setClass();
|
|
this.setAttr('placeholder');
|
|
this.setAttr('rows');
|
|
|
|
//ctrl + enter
|
|
this.$input.keydown(function (e) {
|
|
if (e.ctrlKey && e.which === 13) {
|
|
$(this).closest('form').submit();
|
|
}
|
|
});
|
|
},
|
|
|
|
//using `white-space: pre-wrap` solves \n <--> BR conversion very elegant!
|
|
/*
|
|
value2html: function(value, element) {
|
|
var html = '', lines;
|
|
if(value) {
|
|
lines = value.split("\n");
|
|
for (var i = 0; i < lines.length; i++) {
|
|
lines[i] = $('<div>').text(lines[i]).html();
|
|
}
|
|
html = lines.join('<br>');
|
|
}
|
|
$(element).html(html);
|
|
},
|
|
|
|
html2value: function(html) {
|
|
if(!html) {
|
|
return '';
|
|
}
|
|
|
|
var regex = new RegExp(String.fromCharCode(10), 'g');
|
|
var lines = html.split(/<br\s*\/?>/i);
|
|
for (var i = 0; i < lines.length; i++) {
|
|
var text = $('<div>').html(lines[i]).text();
|
|
|
|
// Remove newline characters (\n) to avoid them being converted by value2html() method
|
|
// thus adding extra <br> tags
|
|
text = text.replace(regex, '');
|
|
|
|
lines[i] = text;
|
|
}
|
|
return lines.join("\n");
|
|
},
|
|
*/
|
|
activate: function() {
|
|
$.fn.editabletypes.text.prototype.activate.call(this);
|
|
}
|
|
});
|
|
|
|
Textarea.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
|
|
/**
|
|
@property tpl
|
|
@default <textarea></textarea>
|
|
**/
|
|
tpl:'<textarea></textarea>',
|
|
/**
|
|
@property inputclass
|
|
@default input-large
|
|
**/
|
|
inputclass: 'input-large',
|
|
/**
|
|
Placeholder attribute of input. Shown when input is empty.
|
|
|
|
@property placeholder
|
|
@type string
|
|
@default null
|
|
**/
|
|
placeholder: null,
|
|
/**
|
|
Number of rows in textarea
|
|
|
|
@property rows
|
|
@type integer
|
|
@default 7
|
|
**/
|
|
rows: 7
|
|
});
|
|
|
|
$.fn.editabletypes.textarea = Textarea;
|
|
|
|
}(window.jQuery));
|
|
|
|
/**
|
|
Select (dropdown)
|
|
|
|
@class select
|
|
@extends list
|
|
@final
|
|
@example
|
|
<a href="#" id="status" data-type="select" data-pk="1" data-url="/post" data-title="Select status"></a>
|
|
<script>
|
|
$(function(){
|
|
$('#status').editable({
|
|
value: 2,
|
|
source: [
|
|
{value: 1, text: 'Active'},
|
|
{value: 2, text: 'Blocked'},
|
|
{value: 3, text: 'Deleted'}
|
|
]
|
|
});
|
|
});
|
|
</script>
|
|
**/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var Select = function (options) {
|
|
this.init('select', options, Select.defaults);
|
|
};
|
|
|
|
$.fn.editableutils.inherit(Select, $.fn.editabletypes.list);
|
|
|
|
$.extend(Select.prototype, {
|
|
renderList: function() {
|
|
this.$input.empty();
|
|
|
|
var fillItems = function($el, data) {
|
|
var attr;
|
|
if($.isArray(data)) {
|
|
for(var i=0; i<data.length; i++) {
|
|
attr = {};
|
|
if(data[i].children) {
|
|
attr.label = data[i].text;
|
|
$el.append(fillItems($('<optgroup>', attr), data[i].children));
|
|
} else {
|
|
attr.value = data[i].value;
|
|
if(data[i].disabled) {
|
|
attr.disabled = true;
|
|
}
|
|
$el.append($('<option>', attr).text(data[i].text));
|
|
}
|
|
}
|
|
}
|
|
return $el;
|
|
};
|
|
|
|
fillItems(this.$input, this.sourceData);
|
|
|
|
this.setClass();
|
|
|
|
//enter submit
|
|
this.$input.on('keydown.editable', function (e) {
|
|
if (e.which === 13) {
|
|
$(this).closest('form').submit();
|
|
}
|
|
});
|
|
},
|
|
|
|
value2htmlFinal: function(value, element) {
|
|
var text = '',
|
|
items = $.fn.editableutils.itemsByValue(value, this.sourceData);
|
|
|
|
if(items.length) {
|
|
text = items[0].text;
|
|
}
|
|
|
|
//$(element).text(text);
|
|
$.fn.editabletypes.abstractinput.prototype.value2html.call(this, text, element);
|
|
},
|
|
|
|
autosubmit: function() {
|
|
this.$input.off('keydown.editable').on('change.editable', function(){
|
|
$(this).closest('form').submit();
|
|
});
|
|
}
|
|
});
|
|
|
|
Select.defaults = $.extend({}, $.fn.editabletypes.list.defaults, {
|
|
/**
|
|
@property tpl
|
|
@default <select></select>
|
|
**/
|
|
tpl:'<select></select>'
|
|
});
|
|
|
|
$.fn.editabletypes.select = Select;
|
|
|
|
}(window.jQuery));
|
|
|
|
/**
|
|
List of checkboxes.
|
|
Internally value stored as javascript array of values.
|
|
|
|
@class checklist
|
|
@extends list
|
|
@final
|
|
@example
|
|
<a href="#" id="options" data-type="checklist" data-pk="1" data-url="/post" data-title="Select options"></a>
|
|
<script>
|
|
$(function(){
|
|
$('#options').editable({
|
|
value: [2, 3],
|
|
source: [
|
|
{value: 1, text: 'option1'},
|
|
{value: 2, text: 'option2'},
|
|
{value: 3, text: 'option3'}
|
|
]
|
|
});
|
|
});
|
|
</script>
|
|
**/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var Checklist = function (options) {
|
|
this.init('checklist', options, Checklist.defaults);
|
|
};
|
|
|
|
$.fn.editableutils.inherit(Checklist, $.fn.editabletypes.list);
|
|
|
|
$.extend(Checklist.prototype, {
|
|
renderList: function() {
|
|
var $label, $div;
|
|
|
|
this.$tpl.empty();
|
|
|
|
if(!$.isArray(this.sourceData)) {
|
|
return;
|
|
}
|
|
|
|
for(var i=0; i<this.sourceData.length; i++) {
|
|
$label = $('<label>').append($('<input>', {
|
|
type: 'checkbox',
|
|
value: this.sourceData[i].value
|
|
}))
|
|
.append($('<span>').text(' '+this.sourceData[i].text));
|
|
|
|
$('<div>').append($label).appendTo(this.$tpl);
|
|
}
|
|
|
|
this.$input = this.$tpl.find('input[type="checkbox"]');
|
|
this.setClass();
|
|
},
|
|
|
|
value2str: function(value) {
|
|
return $.isArray(value) ? value.sort().join($.trim(this.options.separator)) : '';
|
|
},
|
|
|
|
//parse separated string
|
|
str2value: function(str) {
|
|
var reg, value = null;
|
|
if(typeof str === 'string' && str.length) {
|
|
reg = new RegExp('\\s*'+$.trim(this.options.separator)+'\\s*');
|
|
value = str.split(reg);
|
|
} else if($.isArray(str)) {
|
|
value = str;
|
|
} else {
|
|
value = [str];
|
|
}
|
|
return value;
|
|
},
|
|
|
|
//set checked on required checkboxes
|
|
value2input: function(value) {
|
|
this.$input.prop('checked', false);
|
|
if($.isArray(value) && value.length) {
|
|
this.$input.each(function(i, el) {
|
|
var $el = $(el);
|
|
// cannot use $.inArray as it performs strict comparison
|
|
$.each(value, function(j, val){
|
|
/*jslint eqeq: true*/
|
|
if($el.val() == val) {
|
|
/*jslint eqeq: false*/
|
|
$el.prop('checked', true);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
},
|
|
|
|
input2value: function() {
|
|
var checked = [];
|
|
this.$input.filter(':checked').each(function(i, el) {
|
|
checked.push($(el).val());
|
|
});
|
|
return checked;
|
|
},
|
|
|
|
//collect text of checked boxes
|
|
value2htmlFinal: function(value, element) {
|
|
var html = [],
|
|
checked = $.fn.editableutils.itemsByValue(value, this.sourceData),
|
|
escape = this.options.escape;
|
|
|
|
if(checked.length) {
|
|
$.each(checked, function(i, v) {
|
|
var text = escape ? $.fn.editableutils.escape(v.text) : v.text;
|
|
html.push(text);
|
|
});
|
|
$(element).html(html.join('<br>'));
|
|
} else {
|
|
$(element).empty();
|
|
}
|
|
},
|
|
|
|
activate: function() {
|
|
this.$input.first().focus();
|
|
},
|
|
|
|
autosubmit: function() {
|
|
this.$input.on('keydown', function(e){
|
|
if (e.which === 13) {
|
|
$(this).closest('form').submit();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
Checklist.defaults = $.extend({}, $.fn.editabletypes.list.defaults, {
|
|
/**
|
|
@property tpl
|
|
@default <div></div>
|
|
**/
|
|
tpl:'<div class="editable-checklist"></div>',
|
|
|
|
/**
|
|
@property inputclass
|
|
@type string
|
|
@default null
|
|
**/
|
|
inputclass: null,
|
|
|
|
/**
|
|
Separator of values when reading from `data-value` attribute
|
|
|
|
@property separator
|
|
@type string
|
|
@default ','
|
|
**/
|
|
separator: ','
|
|
});
|
|
|
|
$.fn.editabletypes.checklist = Checklist;
|
|
|
|
}(window.jQuery));
|
|
|
|
/**
|
|
HTML5 input types.
|
|
Following types are supported:
|
|
|
|
* password
|
|
* email
|
|
* url
|
|
* tel
|
|
* number
|
|
* range
|
|
* time
|
|
|
|
Learn more about html5 inputs:
|
|
http://www.w3.org/wiki/HTML5_form_additions
|
|
To check browser compatibility please see:
|
|
https://developer.mozilla.org/en-US/docs/HTML/Element/Input
|
|
|
|
@class html5types
|
|
@extends text
|
|
@final
|
|
@since 1.3.0
|
|
@example
|
|
<a href="#" id="email" data-type="email" data-pk="1">admin@example.com</a>
|
|
<script>
|
|
$(function(){
|
|
$('#email').editable({
|
|
url: '/post',
|
|
title: 'Enter email'
|
|
});
|
|
});
|
|
</script>
|
|
**/
|
|
|
|
/**
|
|
@property tpl
|
|
@default depends on type
|
|
**/
|
|
|
|
/*
|
|
Password
|
|
*/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var Password = function (options) {
|
|
this.init('password', options, Password.defaults);
|
|
};
|
|
$.fn.editableutils.inherit(Password, $.fn.editabletypes.text);
|
|
$.extend(Password.prototype, {
|
|
//do not display password, show '[hidden]' instead
|
|
value2html: function(value, element) {
|
|
if(value) {
|
|
$(element).text('[hidden]');
|
|
} else {
|
|
$(element).empty();
|
|
}
|
|
},
|
|
//as password not displayed, should not set value by html
|
|
html2value: function(html) {
|
|
return null;
|
|
}
|
|
});
|
|
Password.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
|
|
tpl: '<input type="password">'
|
|
});
|
|
$.fn.editabletypes.password = Password;
|
|
}(window.jQuery));
|
|
|
|
|
|
/*
|
|
Email
|
|
*/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var Email = function (options) {
|
|
this.init('email', options, Email.defaults);
|
|
};
|
|
$.fn.editableutils.inherit(Email, $.fn.editabletypes.text);
|
|
Email.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
|
|
tpl: '<input type="email">'
|
|
});
|
|
$.fn.editabletypes.email = Email;
|
|
}(window.jQuery));
|
|
|
|
|
|
/*
|
|
Url
|
|
*/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var Url = function (options) {
|
|
this.init('url', options, Url.defaults);
|
|
};
|
|
$.fn.editableutils.inherit(Url, $.fn.editabletypes.text);
|
|
Url.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
|
|
tpl: '<input type="url">'
|
|
});
|
|
$.fn.editabletypes.url = Url;
|
|
}(window.jQuery));
|
|
|
|
|
|
/*
|
|
Tel
|
|
*/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var Tel = function (options) {
|
|
this.init('tel', options, Tel.defaults);
|
|
};
|
|
$.fn.editableutils.inherit(Tel, $.fn.editabletypes.text);
|
|
Tel.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
|
|
tpl: '<input type="tel">'
|
|
});
|
|
$.fn.editabletypes.tel = Tel;
|
|
}(window.jQuery));
|
|
|
|
|
|
/*
|
|
Number
|
|
*/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var NumberInput = function (options) {
|
|
this.init('number', options, NumberInput.defaults);
|
|
};
|
|
$.fn.editableutils.inherit(NumberInput, $.fn.editabletypes.text);
|
|
$.extend(NumberInput.prototype, {
|
|
render: function () {
|
|
NumberInput.superclass.render.call(this);
|
|
this.setAttr('min');
|
|
this.setAttr('max');
|
|
this.setAttr('step');
|
|
},
|
|
postrender: function() {
|
|
if(this.$clear) {
|
|
//increase right ffset for up/down arrows
|
|
this.$clear.css({right: 24});
|
|
/*
|
|
//can position clear button only here, when form is shown and height can be calculated
|
|
var h = this.$input.outerHeight(true) || 20,
|
|
delta = (h - this.$clear.height()) / 2;
|
|
|
|
//add 12px to offset right for up/down arrows
|
|
this.$clear.css({top: delta, right: delta + 16});
|
|
*/
|
|
}
|
|
}
|
|
});
|
|
NumberInput.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
|
|
tpl: '<input type="number">',
|
|
inputclass: 'input-mini',
|
|
min: null,
|
|
max: null,
|
|
step: null
|
|
});
|
|
$.fn.editabletypes.number = NumberInput;
|
|
}(window.jQuery));
|
|
|
|
|
|
/*
|
|
Range (inherit from number)
|
|
*/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var Range = function (options) {
|
|
this.init('range', options, Range.defaults);
|
|
};
|
|
$.fn.editableutils.inherit(Range, $.fn.editabletypes.number);
|
|
$.extend(Range.prototype, {
|
|
render: function () {
|
|
this.$input = this.$tpl.filter('input');
|
|
|
|
this.setClass();
|
|
this.setAttr('min');
|
|
this.setAttr('max');
|
|
this.setAttr('step');
|
|
|
|
this.$input.on('input', function(){
|
|
$(this).siblings('output').text($(this).val());
|
|
});
|
|
},
|
|
activate: function() {
|
|
this.$input.focus();
|
|
}
|
|
});
|
|
Range.defaults = $.extend({}, $.fn.editabletypes.number.defaults, {
|
|
tpl: '<input type="range"><output style="width: 30px; display: inline-block"></output>',
|
|
inputclass: 'input-medium'
|
|
});
|
|
$.fn.editabletypes.range = Range;
|
|
}(window.jQuery));
|
|
|
|
/*
|
|
Time
|
|
*/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var Time = function (options) {
|
|
this.init('time', options, Time.defaults);
|
|
};
|
|
//inherit from abstract, as inheritance from text gives selection error.
|
|
$.fn.editableutils.inherit(Time, $.fn.editabletypes.abstractinput);
|
|
$.extend(Time.prototype, {
|
|
render: function() {
|
|
this.setClass();
|
|
}
|
|
});
|
|
Time.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
|
|
tpl: '<input type="time">'
|
|
});
|
|
$.fn.editabletypes.time = Time;
|
|
}(window.jQuery));
|
|
|
|
/**
|
|
Select2 input. Based on amazing work of Igor Vaynberg https://github.com/ivaynberg/select2.
|
|
Please see [original select2 docs](http://ivaynberg.github.com/select2) for detailed description and options.
|
|
|
|
You should manually download and include select2 distributive:
|
|
|
|
<link href="select2/select2.css" rel="stylesheet" type="text/css"></link>
|
|
<script src="select2/select2.js"></script>
|
|
|
|
To make it **bootstrap-styled** you can use css from [here](https://github.com/t0m/select2-bootstrap-css):
|
|
|
|
<link href="select2-bootstrap.css" rel="stylesheet" type="text/css"></link>
|
|
|
|
**Note:** currently `autotext` feature does not work for select2 with `ajax` remote source.
|
|
You need initially put both `data-value` and element's text youself:
|
|
|
|
<a href="#" data-type="select2" data-value="1">Text1</a>
|
|
|
|
|
|
@class select2
|
|
@extends abstractinput
|
|
@since 1.4.1
|
|
@final
|
|
@example
|
|
<a href="#" id="country" data-type="select2" data-pk="1" data-value="ru" data-url="/post" data-title="Select country"></a>
|
|
<script>
|
|
$(function(){
|
|
//local source
|
|
$('#country').editable({
|
|
source: [
|
|
{id: 'gb', text: 'Great Britain'},
|
|
{id: 'us', text: 'United States'},
|
|
{id: 'ru', text: 'Russia'}
|
|
],
|
|
select2: {
|
|
multiple: true
|
|
}
|
|
});
|
|
//remote source (simple)
|
|
$('#country').editable({
|
|
source: '/getCountries',
|
|
select2: {
|
|
placeholder: 'Select Country',
|
|
minimumInputLength: 1
|
|
}
|
|
});
|
|
//remote source (advanced)
|
|
$('#country').editable({
|
|
select2: {
|
|
placeholder: 'Select Country',
|
|
allowClear: true,
|
|
minimumInputLength: 3,
|
|
id: function (item) {
|
|
return item.CountryId;
|
|
},
|
|
ajax: {
|
|
url: '/getCountries',
|
|
dataType: 'json',
|
|
data: function (term, page) {
|
|
return { query: term };
|
|
},
|
|
results: function (data, page) {
|
|
return { results: data };
|
|
}
|
|
},
|
|
formatResult: function (item) {
|
|
return item.CountryName;
|
|
},
|
|
formatSelection: function (item) {
|
|
return item.CountryName;
|
|
},
|
|
initSelection: function (element, callback) {
|
|
return $.get('/getCountryById', { query: element.val() }, function (data) {
|
|
callback(data);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
**/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var Constructor = function (options) {
|
|
this.init('select2', options, Constructor.defaults);
|
|
|
|
options.select2 = options.select2 || {};
|
|
|
|
this.sourceData = null;
|
|
|
|
//placeholder
|
|
if(options.placeholder) {
|
|
options.select2.placeholder = options.placeholder;
|
|
}
|
|
|
|
//if not `tags` mode, use source
|
|
if(!options.select2.tags && options.source) {
|
|
var source = options.source;
|
|
//if source is function, call it (once!)
|
|
if ($.isFunction(options.source)) {
|
|
source = options.source.call(options.scope);
|
|
}
|
|
|
|
if (typeof source === 'string') {
|
|
options.select2.ajax = options.select2.ajax || {};
|
|
//some default ajax params
|
|
if(!options.select2.ajax.data) {
|
|
options.select2.ajax.data = function(term) {return { query:term };};
|
|
}
|
|
if(!options.select2.ajax.results) {
|
|
options.select2.ajax.results = function(data) { return {results:data };};
|
|
}
|
|
options.select2.ajax.url = source;
|
|
} else {
|
|
//check format and convert x-editable format to select2 format (if needed)
|
|
this.sourceData = this.convertSource(source);
|
|
options.select2.data = this.sourceData;
|
|
}
|
|
}
|
|
|
|
//overriding objects in config (as by default jQuery extend() is not recursive)
|
|
this.options.select2 = $.extend({}, Constructor.defaults.select2, options.select2);
|
|
|
|
//detect whether it is multi-valued
|
|
this.isMultiple = this.options.select2.tags || this.options.select2.multiple;
|
|
this.isRemote = ('ajax' in this.options.select2);
|
|
|
|
//store function returning ID of item
|
|
//should be here as used inautotext for local source
|
|
this.idFunc = this.options.select2.id;
|
|
if (typeof(this.idFunc) !== "function") {
|
|
var idKey = this.idFunc || 'id';
|
|
this.idFunc = function (e) { return e[idKey]; };
|
|
}
|
|
|
|
//store function that renders text in select2
|
|
this.formatSelection = this.options.select2.formatSelection;
|
|
if (typeof(this.formatSelection) !== "function") {
|
|
this.formatSelection = function (e) { return e.text; };
|
|
}
|
|
};
|
|
|
|
$.fn.editableutils.inherit(Constructor, $.fn.editabletypes.abstractinput);
|
|
|
|
$.extend(Constructor.prototype, {
|
|
render: function() {
|
|
this.setClass();
|
|
|
|
//can not apply select2 here as it calls initSelection
|
|
//over input that does not have correct value yet.
|
|
//apply select2 only in value2input
|
|
//this.$input.select2(this.options.select2);
|
|
|
|
//when data is loaded via ajax, we need to know when it's done to populate listData
|
|
if(this.isRemote) {
|
|
//listen to loaded event to populate data
|
|
this.$input.on('select2-loaded', $.proxy(function(e) {
|
|
this.sourceData = e.items.results;
|
|
}, this));
|
|
}
|
|
|
|
//trigger resize of editableform to re-position container in multi-valued mode
|
|
if(this.isMultiple) {
|
|
this.$input.on('change', function() {
|
|
$(this).closest('form').parent().triggerHandler('resize');
|
|
});
|
|
}
|
|
},
|
|
|
|
value2html: function(value, element) {
|
|
var text = '', data,
|
|
that = this;
|
|
|
|
if(this.options.select2.tags) { //in tags mode just assign value
|
|
data = value;
|
|
//data = $.fn.editableutils.itemsByValue(value, this.options.select2.tags, this.idFunc);
|
|
} else if(this.sourceData) {
|
|
data = $.fn.editableutils.itemsByValue(value, this.sourceData, this.idFunc);
|
|
} else {
|
|
//can not get list of possible values
|
|
//(e.g. autotext for select2 with ajax source)
|
|
}
|
|
|
|
//data may be array (when multiple values allowed)
|
|
if($.isArray(data)) {
|
|
//collect selected data and show with separator
|
|
text = [];
|
|
$.each(data, function(k, v){
|
|
text.push(v && typeof v === 'object' ? that.formatSelection(v) : v);
|
|
});
|
|
} else if(data) {
|
|
text = that.formatSelection(data);
|
|
}
|
|
|
|
text = $.isArray(text) ? text.join(this.options.viewseparator) : text;
|
|
|
|
//$(element).text(text);
|
|
Constructor.superclass.value2html.call(this, text, element);
|
|
},
|
|
|
|
html2value: function(html) {
|
|
return this.options.select2.tags ? this.str2value(html, this.options.viewseparator) : null;
|
|
},
|
|
|
|
value2input: function(value) {
|
|
// if value array => join it anyway
|
|
if($.isArray(value)) {
|
|
value = value.join(this.getSeparator());
|
|
}
|
|
|
|
//for remote source just set value, text is updated by initSelection
|
|
if(!this.$input.data('select2')) {
|
|
this.$input.val(value);
|
|
this.$input.select2(this.options.select2);
|
|
} else {
|
|
//second argument needed to separate initial change from user's click (for autosubmit)
|
|
this.$input.val(value).trigger('change', true);
|
|
|
|
//Uncaught Error: cannot call val() if initSelection() is not defined
|
|
//this.$input.select2('val', value);
|
|
}
|
|
|
|
// if defined remote source AND no multiple mode AND no user's initSelection provided -->
|
|
// we should somehow get text for provided id.
|
|
// The solution is to use element's text as text for that id (exclude empty)
|
|
if(this.isRemote && !this.isMultiple && !this.options.select2.initSelection) {
|
|
// customId and customText are methods to extract `id` and `text` from data object
|
|
// we can use this workaround only if user did not define these methods
|
|
// otherwise we cant construct data object
|
|
var customId = this.options.select2.id,
|
|
customText = this.options.select2.formatSelection;
|
|
|
|
if(!customId && !customText) {
|
|
var $el = $(this.options.scope);
|
|
if (!$el.data('editable').isEmpty) {
|
|
var data = {id: value, text: $el.text()};
|
|
this.$input.select2('data', data);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
input2value: function() {
|
|
return this.$input.select2('val');
|
|
},
|
|
|
|
str2value: function(str, separator) {
|
|
if(typeof str !== 'string' || !this.isMultiple) {
|
|
return str;
|
|
}
|
|
|
|
separator = separator || this.getSeparator();
|
|
|
|
var val, i, l;
|
|
|
|
if (str === null || str.length < 1) {
|
|
return null;
|
|
}
|
|
val = str.split(separator);
|
|
for (i = 0, l = val.length; i < l; i = i + 1) {
|
|
val[i] = $.trim(val[i]);
|
|
}
|
|
|
|
return val;
|
|
},
|
|
|
|
autosubmit: function() {
|
|
this.$input.on('change', function(e, isInitial){
|
|
if(!isInitial) {
|
|
$(this).closest('form').submit();
|
|
}
|
|
});
|
|
},
|
|
|
|
getSeparator: function() {
|
|
return this.options.select2.separator || $.fn.select2.defaults.separator;
|
|
},
|
|
|
|
/*
|
|
Converts source from x-editable format: {value: 1, text: "1"} to
|
|
select2 format: {id: 1, text: "1"}
|
|
*/
|
|
convertSource: function(source) {
|
|
if($.isArray(source) && source.length && source[0].value !== undefined) {
|
|
for(var i = 0; i<source.length; i++) {
|
|
if(source[i].value !== undefined) {
|
|
source[i].id = source[i].value;
|
|
delete source[i].value;
|
|
}
|
|
}
|
|
}
|
|
return source;
|
|
},
|
|
|
|
destroy: function() {
|
|
if(this.$input.data('select2')) {
|
|
this.$input.select2('destroy');
|
|
}
|
|
}
|
|
|
|
});
|
|
|
|
Constructor.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
|
|
/**
|
|
@property tpl
|
|
@default <input type="hidden">
|
|
**/
|
|
tpl:'<input type="hidden">',
|
|
/**
|
|
Configuration of select2. [Full list of options](http://ivaynberg.github.com/select2).
|
|
|
|
@property select2
|
|
@type object
|
|
@default null
|
|
**/
|
|
select2: null,
|
|
/**
|
|
Placeholder attribute of select
|
|
|
|
@property placeholder
|
|
@type string
|
|
@default null
|
|
**/
|
|
placeholder: null,
|
|
/**
|
|
Source data for select. It will be assigned to select2 `data` property and kept here just for convenience.
|
|
Please note, that format is different from simple `select` input: use 'id' instead of 'value'.
|
|
E.g. `[{id: 1, text: "text1"}, {id: 2, text: "text2"}, ...]`.
|
|
|
|
@property source
|
|
@type array|string|function
|
|
@default null
|
|
**/
|
|
source: null,
|
|
/**
|
|
Separator used to display tags.
|
|
|
|
@property viewseparator
|
|
@type string
|
|
@default ', '
|
|
**/
|
|
viewseparator: ', '
|
|
});
|
|
|
|
$.fn.editabletypes.select2 = Constructor;
|
|
|
|
}(window.jQuery));
|
|
|
|
/**
|
|
* Combodate - 1.0.5
|
|
* Dropdown date and time picker.
|
|
* Converts text input into dropdowns to pick day, month, year, hour, minute and second.
|
|
* Uses momentjs as datetime library http://momentjs.com.
|
|
* For i18n include corresponding file from https://github.com/timrwood/moment/tree/master/lang
|
|
*
|
|
* Confusion at noon and midnight - see http://en.wikipedia.org/wiki/12-hour_clock#Confusion_at_noon_and_midnight
|
|
* In combodate:
|
|
* 12:00 pm --> 12:00 (24-h format, midday)
|
|
* 12:00 am --> 00:00 (24-h format, midnight, start of day)
|
|
*
|
|
* Differs from momentjs parse rules:
|
|
* 00:00 pm, 12:00 pm --> 12:00 (24-h format, day not change)
|
|
* 00:00 am, 12:00 am --> 00:00 (24-h format, day not change)
|
|
*
|
|
*
|
|
* Author: Vitaliy Potapov
|
|
* Project page: http://github.com/vitalets/combodate
|
|
* Copyright (c) 2012 Vitaliy Potapov. Released under MIT License.
|
|
**/
|
|
(function ($) {
|
|
|
|
var Combodate = function (element, options) {
|
|
this.$element = $(element);
|
|
if(!this.$element.is('input')) {
|
|
$.error('Combodate should be applied to INPUT element');
|
|
return;
|
|
}
|
|
this.options = $.extend({}, $.fn.combodate.defaults, options, this.$element.data());
|
|
this.init();
|
|
};
|
|
|
|
Combodate.prototype = {
|
|
constructor: Combodate,
|
|
init: function () {
|
|
this.map = {
|
|
//key regexp moment.method
|
|
day: ['D', 'date'],
|
|
month: ['M', 'month'],
|
|
year: ['Y', 'year'],
|
|
hour: ['[Hh]', 'hours'],
|
|
minute: ['m', 'minutes'],
|
|
second: ['s', 'seconds'],
|
|
ampm: ['[Aa]', '']
|
|
};
|
|
|
|
this.$widget = $('<span class="combodate"></span>').html(this.getTemplate());
|
|
|
|
this.initCombos();
|
|
|
|
//update original input on change
|
|
this.$widget.on('change', 'select', $.proxy(function(e) {
|
|
this.$element.val(this.getValue()).change();
|
|
// update days count if month or year changes
|
|
if (this.options.smartDays) {
|
|
if ($(e.target).is('.month') || $(e.target).is('.year')) {
|
|
this.fillCombo('day');
|
|
}
|
|
}
|
|
}, this));
|
|
|
|
this.$widget.find('select').css('width', 'auto');
|
|
|
|
// hide original input and insert widget
|
|
this.$element.hide().after(this.$widget);
|
|
|
|
// set initial value
|
|
this.setValue(this.$element.val() || this.options.value);
|
|
},
|
|
|
|
/*
|
|
Replace tokens in template with <select> elements
|
|
*/
|
|
getTemplate: function() {
|
|
var tpl = this.options.template;
|
|
|
|
//first pass
|
|
$.each(this.map, function(k, v) {
|
|
v = v[0];
|
|
var r = new RegExp(v+'+'),
|
|
token = v.length > 1 ? v.substring(1, 2) : v;
|
|
|
|
tpl = tpl.replace(r, '{'+token+'}');
|
|
});
|
|
|
|
//replace spaces with
|
|
tpl = tpl.replace(/ /g, ' ');
|
|
|
|
//second pass
|
|
$.each(this.map, function(k, v) {
|
|
v = v[0];
|
|
var token = v.length > 1 ? v.substring(1, 2) : v;
|
|
|
|
tpl = tpl.replace('{'+token+'}', '<select class="'+k+'"></select>');
|
|
});
|
|
|
|
return tpl;
|
|
},
|
|
|
|
/*
|
|
Initialize combos that presents in template
|
|
*/
|
|
initCombos: function() {
|
|
for (var k in this.map) {
|
|
var $c = this.$widget.find('.'+k);
|
|
// set properties like this.$day, this.$month etc.
|
|
this['$'+k] = $c.length ? $c : null;
|
|
// fill with items
|
|
this.fillCombo(k);
|
|
}
|
|
},
|
|
|
|
/*
|
|
Fill combo with items
|
|
*/
|
|
fillCombo: function(k) {
|
|
var $combo = this['$'+k];
|
|
if (!$combo) {
|
|
return;
|
|
}
|
|
|
|
// define method name to fill items, e.g `fillDays`
|
|
var f = 'fill' + k.charAt(0).toUpperCase() + k.slice(1);
|
|
var items = this[f]();
|
|
var value = $combo.val();
|
|
|
|
$combo.empty();
|
|
for(var i=0; i<items.length; i++) {
|
|
$combo.append('<option value="'+items[i][0]+'">'+items[i][1]+'</option>');
|
|
}
|
|
|
|
$combo.val(value);
|
|
},
|
|
|
|
/*
|
|
Initialize items of combos. Handles `firstItem` option
|
|
*/
|
|
fillCommon: function(key) {
|
|
var values = [],
|
|
relTime;
|
|
|
|
if(this.options.firstItem === 'name') {
|
|
//need both to support moment ver < 2 and >= 2
|
|
relTime = moment.relativeTime || moment.langData()._relativeTime;
|
|
var header = typeof relTime[key] === 'function' ? relTime[key](1, true, key, false) : relTime[key];
|
|
//take last entry (see momentjs lang files structure)
|
|
header = header.split(' ').reverse()[0];
|
|
values.push(['', header]);
|
|
} else if(this.options.firstItem === 'empty') {
|
|
values.push(['', '']);
|
|
}
|
|
return values;
|
|
},
|
|
|
|
|
|
/*
|
|
fill day
|
|
*/
|
|
fillDay: function() {
|
|
var items = this.fillCommon('d'), name, i,
|
|
twoDigit = this.options.template.indexOf('DD') !== -1,
|
|
daysCount = 31;
|
|
|
|
// detect days count (depends on month and year)
|
|
// originally https://github.com/vitalets/combodate/pull/7
|
|
if (this.options.smartDays && this.$month && this.$year) {
|
|
var month = parseInt(this.$month.val(), 10);
|
|
var year = parseInt(this.$year.val(), 10);
|
|
|
|
if (!isNaN(month) && !isNaN(year)) {
|
|
daysCount = moment([year, month]).daysInMonth();
|
|
}
|
|
}
|
|
|
|
for (i = 1; i <= daysCount; i++) {
|
|
name = twoDigit ? this.leadZero(i) : i;
|
|
items.push([i, name]);
|
|
}
|
|
return items;
|
|
},
|
|
|
|
/*
|
|
fill month
|
|
*/
|
|
fillMonth: function() {
|
|
var items = this.fillCommon('M'), name, i,
|
|
longNames = this.options.template.indexOf('MMMM') !== -1,
|
|
shortNames = this.options.template.indexOf('MMM') !== -1,
|
|
twoDigit = this.options.template.indexOf('MM') !== -1;
|
|
|
|
for(i=0; i<=11; i++) {
|
|
if(longNames) {
|
|
//see https://github.com/timrwood/momentjs.com/pull/36
|
|
name = moment().date(1).month(i).format('MMMM');
|
|
} else if(shortNames) {
|
|
name = moment().date(1).month(i).format('MMM');
|
|
} else if(twoDigit) {
|
|
name = this.leadZero(i+1);
|
|
} else {
|
|
name = i+1;
|
|
}
|
|
items.push([i, name]);
|
|
}
|
|
return items;
|
|
},
|
|
|
|
/*
|
|
fill year
|
|
*/
|
|
fillYear: function() {
|
|
var items = [], name, i,
|
|
longNames = this.options.template.indexOf('YYYY') !== -1;
|
|
|
|
for(i=this.options.maxYear; i>=this.options.minYear; i--) {
|
|
name = longNames ? i : (i+'').substring(2);
|
|
items[this.options.yearDescending ? 'push' : 'unshift']([i, name]);
|
|
}
|
|
|
|
items = this.fillCommon('y').concat(items);
|
|
|
|
return items;
|
|
},
|
|
|
|
/*
|
|
fill hour
|
|
*/
|
|
fillHour: function() {
|
|
var items = this.fillCommon('h'), name, i,
|
|
h12 = this.options.template.indexOf('h') !== -1,
|
|
h24 = this.options.template.indexOf('H') !== -1,
|
|
twoDigit = this.options.template.toLowerCase().indexOf('hh') !== -1,
|
|
min = h12 ? 1 : 0,
|
|
max = h12 ? 12 : 23;
|
|
|
|
for(i=min; i<=max; i++) {
|
|
name = twoDigit ? this.leadZero(i) : i;
|
|
items.push([i, name]);
|
|
}
|
|
return items;
|
|
},
|
|
|
|
/*
|
|
fill minute
|
|
*/
|
|
fillMinute: function() {
|
|
var items = this.fillCommon('m'), name, i,
|
|
twoDigit = this.options.template.indexOf('mm') !== -1;
|
|
|
|
for(i=0; i<=59; i+= this.options.minuteStep) {
|
|
name = twoDigit ? this.leadZero(i) : i;
|
|
items.push([i, name]);
|
|
}
|
|
return items;
|
|
},
|
|
|
|
/*
|
|
fill second
|
|
*/
|
|
fillSecond: function() {
|
|
var items = this.fillCommon('s'), name, i,
|
|
twoDigit = this.options.template.indexOf('ss') !== -1;
|
|
|
|
for(i=0; i<=59; i+= this.options.secondStep) {
|
|
name = twoDigit ? this.leadZero(i) : i;
|
|
items.push([i, name]);
|
|
}
|
|
return items;
|
|
},
|
|
|
|
/*
|
|
fill ampm
|
|
*/
|
|
fillAmpm: function() {
|
|
var ampmL = this.options.template.indexOf('a') !== -1,
|
|
ampmU = this.options.template.indexOf('A') !== -1,
|
|
items = [
|
|
['am', ampmL ? 'am' : 'AM'],
|
|
['pm', ampmL ? 'pm' : 'PM']
|
|
];
|
|
return items;
|
|
},
|
|
|
|
/*
|
|
Returns current date value from combos.
|
|
If format not specified - `options.format` used.
|
|
If format = `null` - Moment object returned.
|
|
*/
|
|
getValue: function(format) {
|
|
var dt, values = {},
|
|
that = this,
|
|
notSelected = false;
|
|
|
|
//getting selected values
|
|
$.each(this.map, function(k, v) {
|
|
if(k === 'ampm') {
|
|
return;
|
|
}
|
|
var def = k === 'day' ? 1 : 0;
|
|
|
|
values[k] = that['$'+k] ? parseInt(that['$'+k].val(), 10) : def;
|
|
|
|
if(isNaN(values[k])) {
|
|
notSelected = true;
|
|
return false;
|
|
}
|
|
});
|
|
|
|
//if at least one visible combo not selected - return empty string
|
|
if(notSelected) {
|
|
return '';
|
|
}
|
|
|
|
//convert hours 12h --> 24h
|
|
if(this.$ampm) {
|
|
//12:00 pm --> 12:00 (24-h format, midday), 12:00 am --> 00:00 (24-h format, midnight, start of day)
|
|
if(values.hour === 12) {
|
|
values.hour = this.$ampm.val() === 'am' ? 0 : 12;
|
|
} else {
|
|
values.hour = this.$ampm.val() === 'am' ? values.hour : values.hour+12;
|
|
}
|
|
}
|
|
|
|
dt = moment([values.year, values.month, values.day, values.hour, values.minute, values.second]);
|
|
|
|
//highlight invalid date
|
|
this.highlight(dt);
|
|
|
|
format = format === undefined ? this.options.format : format;
|
|
if(format === null) {
|
|
return dt.isValid() ? dt : null;
|
|
} else {
|
|
return dt.isValid() ? dt.format(format) : '';
|
|
}
|
|
},
|
|
|
|
setValue: function(value) {
|
|
if(!value) {
|
|
return;
|
|
}
|
|
|
|
var dt = typeof value === 'string' ? moment(value, this.options.format) : moment(value),
|
|
that = this,
|
|
values = {};
|
|
|
|
//function to find nearest value in select options
|
|
function getNearest($select, value) {
|
|
var delta = {};
|
|
$select.children('option').each(function(i, opt){
|
|
var optValue = $(opt).attr('value'),
|
|
distance;
|
|
|
|
if(optValue === '') return;
|
|
distance = Math.abs(optValue - value);
|
|
if(typeof delta.distance === 'undefined' || distance < delta.distance) {
|
|
delta = {value: optValue, distance: distance};
|
|
}
|
|
});
|
|
return delta.value;
|
|
}
|
|
|
|
if(dt.isValid()) {
|
|
//read values from date object
|
|
$.each(this.map, function(k, v) {
|
|
if(k === 'ampm') {
|
|
return;
|
|
}
|
|
values[k] = dt[v[1]]();
|
|
});
|
|
|
|
if(this.$ampm) {
|
|
//12:00 pm --> 12:00 (24-h format, midday), 12:00 am --> 00:00 (24-h format, midnight, start of day)
|
|
if(values.hour >= 12) {
|
|
values.ampm = 'pm';
|
|
if(values.hour > 12) {
|
|
values.hour -= 12;
|
|
}
|
|
} else {
|
|
values.ampm = 'am';
|
|
if(values.hour === 0) {
|
|
values.hour = 12;
|
|
}
|
|
}
|
|
}
|
|
|
|
$.each(values, function(k, v) {
|
|
//call val() for each existing combo, e.g. this.$hour.val()
|
|
if(that['$'+k]) {
|
|
|
|
if(k === 'minute' && that.options.minuteStep > 1 && that.options.roundTime) {
|
|
v = getNearest(that['$'+k], v);
|
|
}
|
|
|
|
if(k === 'second' && that.options.secondStep > 1 && that.options.roundTime) {
|
|
v = getNearest(that['$'+k], v);
|
|
}
|
|
|
|
that['$'+k].val(v);
|
|
}
|
|
});
|
|
|
|
// update days count
|
|
if (this.options.smartDays) {
|
|
this.fillCombo('day');
|
|
}
|
|
|
|
this.$element.val(dt.format(this.options.format)).change();
|
|
}
|
|
},
|
|
|
|
/*
|
|
highlight combos if date is invalid
|
|
*/
|
|
highlight: function(dt) {
|
|
if(!dt.isValid()) {
|
|
if(this.options.errorClass) {
|
|
this.$widget.addClass(this.options.errorClass);
|
|
} else {
|
|
//store original border color
|
|
if(!this.borderColor) {
|
|
this.borderColor = this.$widget.find('select').css('border-color');
|
|
}
|
|
this.$widget.find('select').css('border-color', 'red');
|
|
}
|
|
} else {
|
|
if(this.options.errorClass) {
|
|
this.$widget.removeClass(this.options.errorClass);
|
|
} else {
|
|
this.$widget.find('select').css('border-color', this.borderColor);
|
|
}
|
|
}
|
|
},
|
|
|
|
leadZero: function(v) {
|
|
return v <= 9 ? '0' + v : v;
|
|
},
|
|
|
|
destroy: function() {
|
|
this.$widget.remove();
|
|
this.$element.removeData('combodate').show();
|
|
}
|
|
|
|
//todo: clear method
|
|
};
|
|
|
|
$.fn.combodate = function ( option ) {
|
|
var d, args = Array.apply(null, arguments);
|
|
args.shift();
|
|
|
|
//getValue returns date as string / object (not jQuery object)
|
|
if(option === 'getValue' && this.length && (d = this.eq(0).data('combodate'))) {
|
|
return d.getValue.apply(d, args);
|
|
}
|
|
|
|
return this.each(function () {
|
|
var $this = $(this),
|
|
data = $this.data('combodate'),
|
|
options = typeof option == 'object' && option;
|
|
if (!data) {
|
|
$this.data('combodate', (data = new Combodate(this, options)));
|
|
}
|
|
if (typeof option == 'string' && typeof data[option] == 'function') {
|
|
data[option].apply(data, args);
|
|
}
|
|
});
|
|
};
|
|
|
|
$.fn.combodate.defaults = {
|
|
//in this format value stored in original input
|
|
format: 'DD-MM-YYYY HH:mm',
|
|
//in this format items in dropdowns are displayed
|
|
template: 'D / MMM / YYYY H : mm',
|
|
//initial value, can be `new Date()`
|
|
value: null,
|
|
minYear: 1970,
|
|
maxYear: 2015,
|
|
yearDescending: true,
|
|
minuteStep: 5,
|
|
secondStep: 1,
|
|
firstItem: 'empty', //'name', 'empty', 'none'
|
|
errorClass: null,
|
|
roundTime: true, // whether to round minutes and seconds if step > 1
|
|
smartDays: false // whether days in combo depend on selected month: 31, 30, 28
|
|
};
|
|
|
|
}(window.jQuery));
|
|
/**
|
|
Combodate input - dropdown date and time picker.
|
|
Based on [combodate](http://vitalets.github.com/combodate) plugin (included). To use it you should manually include [momentjs](http://momentjs.com).
|
|
|
|
<script src="js/moment.min.js"></script>
|
|
|
|
Allows to input:
|
|
|
|
* only date
|
|
* only time
|
|
* both date and time
|
|
|
|
Please note, that format is taken from momentjs and **not compatible** with bootstrap-datepicker / jquery UI datepicker.
|
|
Internally value stored as `momentjs` object.
|
|
|
|
@class combodate
|
|
@extends abstractinput
|
|
@final
|
|
@since 1.4.0
|
|
@example
|
|
<a href="#" id="dob" data-type="combodate" data-pk="1" data-url="/post" data-value="1984-05-15" data-title="Select date"></a>
|
|
<script>
|
|
$(function(){
|
|
$('#dob').editable({
|
|
format: 'YYYY-MM-DD',
|
|
viewformat: 'DD.MM.YYYY',
|
|
template: 'D / MMMM / YYYY',
|
|
combodate: {
|
|
minYear: 2000,
|
|
maxYear: 2015,
|
|
minuteStep: 1
|
|
}
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
**/
|
|
|
|
/*global moment*/
|
|
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var Constructor = function (options) {
|
|
this.init('combodate', options, Constructor.defaults);
|
|
|
|
//by default viewformat equals to format
|
|
if(!this.options.viewformat) {
|
|
this.options.viewformat = this.options.format;
|
|
}
|
|
|
|
//try parse combodate config defined as json string in data-combodate
|
|
options.combodate = $.fn.editableutils.tryParseJson(options.combodate, true);
|
|
|
|
//overriding combodate config (as by default jQuery extend() is not recursive)
|
|
this.options.combodate = $.extend({}, Constructor.defaults.combodate, options.combodate, {
|
|
format: this.options.format,
|
|
template: this.options.template
|
|
});
|
|
};
|
|
|
|
$.fn.editableutils.inherit(Constructor, $.fn.editabletypes.abstractinput);
|
|
|
|
$.extend(Constructor.prototype, {
|
|
render: function () {
|
|
this.$input.combodate(this.options.combodate);
|
|
|
|
if($.fn.editableform.engine === 'bs3') {
|
|
this.$input.siblings().find('select').addClass('form-control');
|
|
}
|
|
|
|
if(this.options.inputclass) {
|
|
this.$input.siblings().find('select').addClass(this.options.inputclass);
|
|
}
|
|
//"clear" link
|
|
/*
|
|
if(this.options.clear) {
|
|
this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
this.clear();
|
|
}, this));
|
|
|
|
this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear));
|
|
}
|
|
*/
|
|
},
|
|
|
|
value2html: function(value, element) {
|
|
var text = value ? value.format(this.options.viewformat) : '';
|
|
//$(element).text(text);
|
|
Constructor.superclass.value2html.call(this, text, element);
|
|
},
|
|
|
|
html2value: function(html) {
|
|
return html ? moment(html, this.options.viewformat) : null;
|
|
},
|
|
|
|
value2str: function(value) {
|
|
return value ? value.format(this.options.format) : '';
|
|
},
|
|
|
|
str2value: function(str) {
|
|
return str ? moment(str, this.options.format) : null;
|
|
},
|
|
|
|
value2submit: function(value) {
|
|
return this.value2str(value);
|
|
},
|
|
|
|
value2input: function(value) {
|
|
this.$input.combodate('setValue', value);
|
|
},
|
|
|
|
input2value: function() {
|
|
return this.$input.combodate('getValue', null);
|
|
},
|
|
|
|
activate: function() {
|
|
this.$input.siblings('.combodate').find('select').eq(0).focus();
|
|
},
|
|
|
|
/*
|
|
clear: function() {
|
|
this.$input.data('datepicker').date = null;
|
|
this.$input.find('.active').removeClass('active');
|
|
},
|
|
*/
|
|
|
|
autosubmit: function() {
|
|
|
|
}
|
|
|
|
});
|
|
|
|
Constructor.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
|
|
/**
|
|
@property tpl
|
|
@default <input type="text">
|
|
**/
|
|
tpl:'<input type="text">',
|
|
/**
|
|
@property inputclass
|
|
@default null
|
|
**/
|
|
inputclass: null,
|
|
/**
|
|
Format used for sending value to server. Also applied when converting date from <code>data-value</code> attribute.<br>
|
|
See list of tokens in [momentjs docs](http://momentjs.com/docs/#/parsing/string-format)
|
|
|
|
@property format
|
|
@type string
|
|
@default YYYY-MM-DD
|
|
**/
|
|
format:'YYYY-MM-DD',
|
|
/**
|
|
Format used for displaying date. Also applied when converting date from element's text on init.
|
|
If not specified equals to `format`.
|
|
|
|
@property viewformat
|
|
@type string
|
|
@default null
|
|
**/
|
|
viewformat: null,
|
|
/**
|
|
Template used for displaying dropdowns.
|
|
|
|
@property template
|
|
@type string
|
|
@default D / MMM / YYYY
|
|
**/
|
|
template: 'D / MMM / YYYY',
|
|
/**
|
|
Configuration of combodate.
|
|
Full list of options: http://vitalets.github.com/combodate/#docs
|
|
|
|
@property combodate
|
|
@type object
|
|
@default null
|
|
**/
|
|
combodate: null
|
|
|
|
/*
|
|
(not implemented yet)
|
|
Text shown as clear date button.
|
|
If <code>false</code> clear button will not be rendered.
|
|
|
|
@property clear
|
|
@type boolean|string
|
|
@default 'x clear'
|
|
*/
|
|
//clear: '× clear'
|
|
});
|
|
|
|
$.fn.editabletypes.combodate = Constructor;
|
|
|
|
}(window.jQuery));
|
|
|
|
/*
|
|
Editableform based on Twitter Bootstrap 3
|
|
*/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
//store parent methods
|
|
var pInitInput = $.fn.editableform.Constructor.prototype.initInput;
|
|
|
|
$.extend($.fn.editableform.Constructor.prototype, {
|
|
initTemplate: function() {
|
|
this.$form = $($.fn.editableform.template);
|
|
this.$form.find('.control-group').addClass('form-group');
|
|
this.$form.find('.editable-error-block').addClass('help-block');
|
|
},
|
|
initInput: function() {
|
|
pInitInput.apply(this);
|
|
|
|
//for bs3 set default class `input-sm` to standard inputs
|
|
var emptyInputClass = this.input.options.inputclass === null || this.input.options.inputclass === false;
|
|
var defaultClass = 'input-sm';
|
|
|
|
//bs3 add `form-control` class to standard inputs
|
|
var stdtypes = 'text,select,textarea,password,email,url,tel,number,range,time,typeaheadjs'.split(',');
|
|
if(~$.inArray(this.input.type, stdtypes)) {
|
|
this.input.$input.addClass('form-control');
|
|
if(emptyInputClass) {
|
|
this.input.options.inputclass = defaultClass;
|
|
this.input.$input.addClass(defaultClass);
|
|
}
|
|
}
|
|
|
|
//apply bs3 size class also to buttons (to fit size of control)
|
|
var $btn = this.$form.find('.editable-buttons');
|
|
var classes = emptyInputClass ? [defaultClass] : this.input.options.inputclass.split(' ');
|
|
for(var i=0; i<classes.length; i++) {
|
|
// `btn-sm` is default now
|
|
/*
|
|
if(classes[i].toLowerCase() === 'input-sm') {
|
|
$btn.find('button').addClass('btn-sm');
|
|
}
|
|
*/
|
|
if(classes[i].toLowerCase() === 'input-lg') {
|
|
$btn.find('button').removeClass('btn-sm').addClass('btn-lg');
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
//buttons
|
|
$.fn.editableform.buttons =
|
|
'<button type="submit" class="btn btn-primary btn-sm editable-submit">'+
|
|
'<i class="glyphicon glyphicon-ok"></i>'+
|
|
'</button>'+
|
|
'<button type="button" class="btn btn-default btn-sm editable-cancel">'+
|
|
'<i class="glyphicon glyphicon-remove"></i>'+
|
|
'</button>';
|
|
|
|
//error classes
|
|
$.fn.editableform.errorGroupClass = 'has-error';
|
|
$.fn.editableform.errorBlockClass = null;
|
|
//engine
|
|
$.fn.editableform.engine = 'bs3';
|
|
}(window.jQuery));
|
|
/**
|
|
* Editable Popover3 (for Bootstrap 3)
|
|
* ---------------------
|
|
* requires bootstrap-popover.js
|
|
*/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
//extend methods
|
|
$.extend($.fn.editableContainer.Popup.prototype, {
|
|
containerName: 'popover',
|
|
containerDataName: 'bs.popover',
|
|
innerCss: '.popover-content',
|
|
defaults: $.fn.popover.Constructor.DEFAULTS,
|
|
|
|
initContainer: function(){
|
|
$.extend(this.containerOptions, {
|
|
trigger: 'manual',
|
|
selector: false,
|
|
content: ' ',
|
|
template: this.defaults.template
|
|
});
|
|
|
|
//as template property is used in inputs, hide it from popover
|
|
var t;
|
|
if(this.$element.data('template')) {
|
|
t = this.$element.data('template');
|
|
this.$element.removeData('template');
|
|
}
|
|
|
|
this.call(this.containerOptions);
|
|
|
|
if(t) {
|
|
//restore data('template')
|
|
this.$element.data('template', t);
|
|
}
|
|
},
|
|
|
|
/* show */
|
|
innerShow: function () {
|
|
this.call('show');
|
|
},
|
|
|
|
/* hide */
|
|
innerHide: function () {
|
|
this.call('hide');
|
|
},
|
|
|
|
/* destroy */
|
|
innerDestroy: function() {
|
|
this.call('destroy');
|
|
},
|
|
|
|
setContainerOption: function(key, value) {
|
|
this.container().options[key] = value;
|
|
},
|
|
|
|
/**
|
|
* move popover to new position. This function mainly copied from bootstrap-popover.
|
|
*/
|
|
/*jshint laxcomma: true, eqeqeq: false*/
|
|
setPosition: function () {
|
|
|
|
(function() {
|
|
/*
|
|
var $tip = this.tip()
|
|
, inside
|
|
, pos
|
|
, actualWidth
|
|
, actualHeight
|
|
, placement
|
|
, tp
|
|
, tpt
|
|
, tpb
|
|
, tpl
|
|
, tpr;
|
|
|
|
placement = typeof this.options.placement === 'function' ?
|
|
this.options.placement.call(this, $tip[0], this.$element[0]) :
|
|
this.options.placement;
|
|
|
|
inside = /in/.test(placement);
|
|
|
|
$tip
|
|
// .detach()
|
|
//vitalets: remove any placement class because otherwise they dont influence on re-positioning of visible popover
|
|
.removeClass('top right bottom left')
|
|
.css({ top: 0, left: 0, display: 'block' });
|
|
// .insertAfter(this.$element);
|
|
|
|
pos = this.getPosition(inside);
|
|
|
|
actualWidth = $tip[0].offsetWidth;
|
|
actualHeight = $tip[0].offsetHeight;
|
|
|
|
placement = inside ? placement.split(' ')[1] : placement;
|
|
|
|
tpb = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2};
|
|
tpt = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2};
|
|
tpl = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth};
|
|
tpr = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width};
|
|
|
|
switch (placement) {
|
|
case 'bottom':
|
|
if ((tpb.top + actualHeight) > ($(window).scrollTop() + $(window).height())) {
|
|
if (tpt.top > $(window).scrollTop()) {
|
|
placement = 'top';
|
|
} else if ((tpr.left + actualWidth) < ($(window).scrollLeft() + $(window).width())) {
|
|
placement = 'right';
|
|
} else if (tpl.left > $(window).scrollLeft()) {
|
|
placement = 'left';
|
|
} else {
|
|
placement = 'right';
|
|
}
|
|
}
|
|
break;
|
|
case 'top':
|
|
if (tpt.top < $(window).scrollTop()) {
|
|
if ((tpb.top + actualHeight) < ($(window).scrollTop() + $(window).height())) {
|
|
placement = 'bottom';
|
|
} else if ((tpr.left + actualWidth) < ($(window).scrollLeft() + $(window).width())) {
|
|
placement = 'right';
|
|
} else if (tpl.left > $(window).scrollLeft()) {
|
|
placement = 'left';
|
|
} else {
|
|
placement = 'right';
|
|
}
|
|
}
|
|
break;
|
|
case 'left':
|
|
if (tpl.left < $(window).scrollLeft()) {
|
|
if ((tpr.left + actualWidth) < ($(window).scrollLeft() + $(window).width())) {
|
|
placement = 'right';
|
|
} else if (tpt.top > $(window).scrollTop()) {
|
|
placement = 'top';
|
|
} else if (tpt.top > $(window).scrollTop()) {
|
|
placement = 'bottom';
|
|
} else {
|
|
placement = 'right';
|
|
}
|
|
}
|
|
break;
|
|
case 'right':
|
|
if ((tpr.left + actualWidth) > ($(window).scrollLeft() + $(window).width())) {
|
|
if (tpl.left > $(window).scrollLeft()) {
|
|
placement = 'left';
|
|
} else if (tpt.top > $(window).scrollTop()) {
|
|
placement = 'top';
|
|
} else if (tpt.top > $(window).scrollTop()) {
|
|
placement = 'bottom';
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
switch (placement) {
|
|
case 'bottom':
|
|
tp = tpb;
|
|
break;
|
|
case 'top':
|
|
tp = tpt;
|
|
break;
|
|
case 'left':
|
|
tp = tpl;
|
|
break;
|
|
case 'right':
|
|
tp = tpr;
|
|
break;
|
|
}
|
|
|
|
$tip
|
|
.offset(tp)
|
|
.addClass(placement)
|
|
.addClass('in');
|
|
*/
|
|
|
|
|
|
var $tip = this.tip();
|
|
|
|
var placement = typeof this.options.placement == 'function' ?
|
|
this.options.placement.call(this, $tip[0], this.$element[0]) :
|
|
this.options.placement;
|
|
|
|
var autoToken = /\s?auto?\s?/i;
|
|
var autoPlace = autoToken.test(placement);
|
|
if (autoPlace) {
|
|
placement = placement.replace(autoToken, '') || 'top';
|
|
}
|
|
|
|
|
|
var pos = this.getPosition();
|
|
var actualWidth = $tip[0].offsetWidth;
|
|
var actualHeight = $tip[0].offsetHeight;
|
|
|
|
if (autoPlace) {
|
|
var $parent = this.$element.parent();
|
|
|
|
var orgPlacement = placement;
|
|
var docScroll = document.documentElement.scrollTop || document.body.scrollTop;
|
|
var parentWidth = this.options.container == 'body' ? window.innerWidth : $parent.outerWidth();
|
|
var parentHeight = this.options.container == 'body' ? window.innerHeight : $parent.outerHeight();
|
|
var parentLeft = this.options.container == 'body' ? 0 : $parent.offset().left;
|
|
|
|
placement = placement == 'bottom' && pos.top + pos.height + actualHeight - docScroll > parentHeight ? 'top' :
|
|
placement == 'top' && pos.top - docScroll - actualHeight < 0 ? 'bottom' :
|
|
placement == 'right' && pos.right + actualWidth > parentWidth ? 'left' :
|
|
placement == 'left' && pos.left - actualWidth < parentLeft ? 'right' :
|
|
placement;
|
|
|
|
$tip
|
|
.removeClass(orgPlacement)
|
|
.addClass(placement);
|
|
}
|
|
|
|
|
|
var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight);
|
|
|
|
this.applyPlacement(calculatedOffset, placement);
|
|
|
|
|
|
}).call(this.container());
|
|
/*jshint laxcomma: false, eqeqeq: true*/
|
|
}
|
|
});
|
|
|
|
}(window.jQuery));
|
|
|
|
/* =========================================================
|
|
* bootstrap-datepicker.js
|
|
* http://www.eyecon.ro/bootstrap-datepicker
|
|
* =========================================================
|
|
* Copyright 2012 Stefan Petre
|
|
* Improvements by Andrew Rowls
|
|
*
|
|
* 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( $ ) {
|
|
|
|
function UTCDate(){
|
|
return new Date(Date.UTC.apply(Date, arguments));
|
|
}
|
|
function UTCToday(){
|
|
var today = new Date();
|
|
return UTCDate(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate());
|
|
}
|
|
|
|
// Picker object
|
|
|
|
var Datepicker = function(element, options) {
|
|
var that = this;
|
|
|
|
this._process_options(options);
|
|
|
|
this.element = $(element);
|
|
this.isInline = false;
|
|
this.isInput = this.element.is('input');
|
|
this.component = this.element.is('.date') ? this.element.find('.add-on, .btn') : false;
|
|
this.hasInput = this.component && this.element.find('input').length;
|
|
if(this.component && this.component.length === 0)
|
|
this.component = false;
|
|
|
|
this.picker = $(DPGlobal.template);
|
|
this._buildEvents();
|
|
this._attachEvents();
|
|
|
|
if(this.isInline) {
|
|
this.picker.addClass('datepicker-inline').appendTo(this.element);
|
|
} else {
|
|
this.picker.addClass('datepicker-dropdown dropdown-menu');
|
|
}
|
|
|
|
if (this.o.rtl){
|
|
this.picker.addClass('datepicker-rtl');
|
|
this.picker.find('.prev i, .next i')
|
|
.toggleClass('icon-arrow-left icon-arrow-right');
|
|
}
|
|
|
|
|
|
this.viewMode = this.o.startView;
|
|
|
|
if (this.o.calendarWeeks)
|
|
this.picker.find('tfoot th.today')
|
|
.attr('colspan', function(i, val){
|
|
return parseInt(val) + 1;
|
|
});
|
|
|
|
this._allow_update = false;
|
|
|
|
this.setStartDate(this.o.startDate);
|
|
this.setEndDate(this.o.endDate);
|
|
this.setDaysOfWeekDisabled(this.o.daysOfWeekDisabled);
|
|
|
|
this.fillDow();
|
|
this.fillMonths();
|
|
|
|
this._allow_update = true;
|
|
|
|
this.update();
|
|
this.showMode();
|
|
|
|
if(this.isInline) {
|
|
this.show();
|
|
}
|
|
};
|
|
|
|
Datepicker.prototype = {
|
|
constructor: Datepicker,
|
|
|
|
_process_options: function(opts){
|
|
// Store raw options for reference
|
|
this._o = $.extend({}, this._o, opts);
|
|
// Processed options
|
|
var o = this.o = $.extend({}, this._o);
|
|
|
|
// Check if "de-DE" style date is available, if not language should
|
|
// fallback to 2 letter code eg "de"
|
|
var lang = o.language;
|
|
if (!dates[lang]) {
|
|
lang = lang.split('-')[0];
|
|
if (!dates[lang])
|
|
lang = defaults.language;
|
|
}
|
|
o.language = lang;
|
|
|
|
switch(o.startView){
|
|
case 2:
|
|
case 'decade':
|
|
o.startView = 2;
|
|
break;
|
|
case 1:
|
|
case 'year':
|
|
o.startView = 1;
|
|
break;
|
|
default:
|
|
o.startView = 0;
|
|
}
|
|
|
|
switch (o.minViewMode) {
|
|
case 1:
|
|
case 'months':
|
|
o.minViewMode = 1;
|
|
break;
|
|
case 2:
|
|
case 'years':
|
|
o.minViewMode = 2;
|
|
break;
|
|
default:
|
|
o.minViewMode = 0;
|
|
}
|
|
|
|
o.startView = Math.max(o.startView, o.minViewMode);
|
|
|
|
o.weekStart %= 7;
|
|
o.weekEnd = ((o.weekStart + 6) % 7);
|
|
|
|
var format = DPGlobal.parseFormat(o.format)
|
|
if (o.startDate !== -Infinity) {
|
|
o.startDate = DPGlobal.parseDate(o.startDate, format, o.language);
|
|
}
|
|
if (o.endDate !== Infinity) {
|
|
o.endDate = DPGlobal.parseDate(o.endDate, format, o.language);
|
|
}
|
|
|
|
o.daysOfWeekDisabled = o.daysOfWeekDisabled||[];
|
|
if (!$.isArray(o.daysOfWeekDisabled))
|
|
o.daysOfWeekDisabled = o.daysOfWeekDisabled.split(/[,\s]*/);
|
|
o.daysOfWeekDisabled = $.map(o.daysOfWeekDisabled, function (d) {
|
|
return parseInt(d, 10);
|
|
});
|
|
},
|
|
_events: [],
|
|
_secondaryEvents: [],
|
|
_applyEvents: function(evs){
|
|
for (var i=0, el, ev; i<evs.length; i++){
|
|
el = evs[i][0];
|
|
ev = evs[i][1];
|
|
el.on(ev);
|
|
}
|
|
},
|
|
_unapplyEvents: function(evs){
|
|
for (var i=0, el, ev; i<evs.length; i++){
|
|
el = evs[i][0];
|
|
ev = evs[i][1];
|
|
el.off(ev);
|
|
}
|
|
},
|
|
_buildEvents: function(){
|
|
if (this.isInput) { // single input
|
|
this._events = [
|
|
[this.element, {
|
|
focus: $.proxy(this.show, this),
|
|
keyup: $.proxy(this.update, this),
|
|
keydown: $.proxy(this.keydown, this)
|
|
}]
|
|
];
|
|
}
|
|
else if (this.component && this.hasInput){ // component: input + button
|
|
this._events = [
|
|
// For components that are not readonly, allow keyboard nav
|
|
[this.element.find('input'), {
|
|
focus: $.proxy(this.show, this),
|
|
keyup: $.proxy(this.update, this),
|
|
keydown: $.proxy(this.keydown, this)
|
|
}],
|
|
[this.component, {
|
|
click: $.proxy(this.show, this)
|
|
}]
|
|
];
|
|
}
|
|
else if (this.element.is('div')) { // inline datepicker
|
|
this.isInline = true;
|
|
}
|
|
else {
|
|
this._events = [
|
|
[this.element, {
|
|
click: $.proxy(this.show, this)
|
|
}]
|
|
];
|
|
}
|
|
|
|
this._secondaryEvents = [
|
|
[this.picker, {
|
|
click: $.proxy(this.click, this)
|
|
}],
|
|
[$(window), {
|
|
resize: $.proxy(this.place, this)
|
|
}],
|
|
[$(document), {
|
|
mousedown: $.proxy(function (e) {
|
|
// Clicked outside the datepicker, hide it
|
|
if (!(
|
|
this.element.is(e.target) ||
|
|
this.element.find(e.target).size() ||
|
|
this.picker.is(e.target) ||
|
|
this.picker.find(e.target).size()
|
|
)) {
|
|
this.hide();
|
|
}
|
|
}, this)
|
|
}]
|
|
];
|
|
},
|
|
_attachEvents: function(){
|
|
this._detachEvents();
|
|
this._applyEvents(this._events);
|
|
},
|
|
_detachEvents: function(){
|
|
this._unapplyEvents(this._events);
|
|
},
|
|
_attachSecondaryEvents: function(){
|
|
this._detachSecondaryEvents();
|
|
this._applyEvents(this._secondaryEvents);
|
|
},
|
|
_detachSecondaryEvents: function(){
|
|
this._unapplyEvents(this._secondaryEvents);
|
|
},
|
|
_trigger: function(event, altdate){
|
|
var date = altdate || this.date,
|
|
local_date = new Date(date.getTime() + (date.getTimezoneOffset()*60000));
|
|
|
|
this.element.trigger({
|
|
type: event,
|
|
date: local_date,
|
|
format: $.proxy(function(altformat){
|
|
var format = altformat || this.o.format;
|
|
return DPGlobal.formatDate(date, format, this.o.language);
|
|
}, this)
|
|
});
|
|
},
|
|
|
|
show: function(e) {
|
|
if (!this.isInline)
|
|
this.picker.appendTo('body');
|
|
this.picker.show();
|
|
this.height = this.component ? this.component.outerHeight() : this.element.outerHeight();
|
|
this.place();
|
|
this._attachSecondaryEvents();
|
|
if (e) {
|
|
e.preventDefault();
|
|
}
|
|
this._trigger('show');
|
|
},
|
|
|
|
hide: function(e){
|
|
if(this.isInline) return;
|
|
if (!this.picker.is(':visible')) return;
|
|
this.picker.hide().detach();
|
|
this._detachSecondaryEvents();
|
|
this.viewMode = this.o.startView;
|
|
this.showMode();
|
|
|
|
if (
|
|
this.o.forceParse &&
|
|
(
|
|
this.isInput && this.element.val() ||
|
|
this.hasInput && this.element.find('input').val()
|
|
)
|
|
)
|
|
this.setValue();
|
|
this._trigger('hide');
|
|
},
|
|
|
|
remove: function() {
|
|
this.hide();
|
|
this._detachEvents();
|
|
this._detachSecondaryEvents();
|
|
this.picker.remove();
|
|
delete this.element.data().datepicker;
|
|
if (!this.isInput) {
|
|
delete this.element.data().date;
|
|
}
|
|
},
|
|
|
|
getDate: function() {
|
|
var d = this.getUTCDate();
|
|
return new Date(d.getTime() + (d.getTimezoneOffset()*60000));
|
|
},
|
|
|
|
getUTCDate: function() {
|
|
return this.date;
|
|
},
|
|
|
|
setDate: function(d) {
|
|
this.setUTCDate(new Date(d.getTime() - (d.getTimezoneOffset()*60000)));
|
|
},
|
|
|
|
setUTCDate: function(d) {
|
|
this.date = d;
|
|
this.setValue();
|
|
},
|
|
|
|
setValue: function() {
|
|
var formatted = this.getFormattedDate();
|
|
if (!this.isInput) {
|
|
if (this.component){
|
|
this.element.find('input').val(formatted);
|
|
}
|
|
} else {
|
|
this.element.val(formatted);
|
|
}
|
|
},
|
|
|
|
getFormattedDate: function(format) {
|
|
if (format === undefined)
|
|
format = this.o.format;
|
|
return DPGlobal.formatDate(this.date, format, this.o.language);
|
|
},
|
|
|
|
setStartDate: function(startDate){
|
|
this._process_options({startDate: startDate});
|
|
this.update();
|
|
this.updateNavArrows();
|
|
},
|
|
|
|
setEndDate: function(endDate){
|
|
this._process_options({endDate: endDate});
|
|
this.update();
|
|
this.updateNavArrows();
|
|
},
|
|
|
|
setDaysOfWeekDisabled: function(daysOfWeekDisabled){
|
|
this._process_options({daysOfWeekDisabled: daysOfWeekDisabled});
|
|
this.update();
|
|
this.updateNavArrows();
|
|
},
|
|
|
|
place: function(){
|
|
if(this.isInline) return;
|
|
var zIndex = parseInt(this.element.parents().filter(function() {
|
|
return $(this).css('z-index') != 'auto';
|
|
}).first().css('z-index'))+10;
|
|
var offset = this.component ? this.component.parent().offset() : this.element.offset();
|
|
var height = this.component ? this.component.outerHeight(true) : this.element.outerHeight(true);
|
|
this.picker.css({
|
|
top: offset.top + height,
|
|
left: offset.left,
|
|
zIndex: zIndex
|
|
});
|
|
},
|
|
|
|
_allow_update: true,
|
|
update: function(){
|
|
if (!this._allow_update) return;
|
|
|
|
var date, fromArgs = false;
|
|
if(arguments && arguments.length && (typeof arguments[0] === 'string' || arguments[0] instanceof Date)) {
|
|
date = arguments[0];
|
|
fromArgs = true;
|
|
} else {
|
|
date = this.isInput ? this.element.val() : this.element.data('date') || this.element.find('input').val();
|
|
delete this.element.data().date;
|
|
}
|
|
|
|
this.date = DPGlobal.parseDate(date, this.o.format, this.o.language);
|
|
|
|
if(fromArgs) this.setValue();
|
|
|
|
if (this.date < this.o.startDate) {
|
|
this.viewDate = new Date(this.o.startDate);
|
|
} else if (this.date > this.o.endDate) {
|
|
this.viewDate = new Date(this.o.endDate);
|
|
} else {
|
|
this.viewDate = new Date(this.date);
|
|
}
|
|
this.fill();
|
|
},
|
|
|
|
fillDow: function(){
|
|
var dowCnt = this.o.weekStart,
|
|
html = '<tr>';
|
|
if(this.o.calendarWeeks){
|
|
var cell = '<th class="cw"> </th>';
|
|
html += cell;
|
|
this.picker.find('.datepicker-days thead tr:first-child').prepend(cell);
|
|
}
|
|
while (dowCnt < this.o.weekStart + 7) {
|
|
html += '<th class="dow">'+dates[this.o.language].daysMin[(dowCnt++)%7]+'</th>';
|
|
}
|
|
html += '</tr>';
|
|
this.picker.find('.datepicker-days thead').append(html);
|
|
},
|
|
|
|
fillMonths: function(){
|
|
var html = '',
|
|
i = 0;
|
|
while (i < 12) {
|
|
html += '<span class="month">'+dates[this.o.language].monthsShort[i++]+'</span>';
|
|
}
|
|
this.picker.find('.datepicker-months td').html(html);
|
|
},
|
|
|
|
setRange: function(range){
|
|
if (!range || !range.length)
|
|
delete this.range;
|
|
else
|
|
this.range = $.map(range, function(d){ return d.valueOf(); });
|
|
this.fill();
|
|
},
|
|
|
|
getClassNames: function(date){
|
|
var cls = [],
|
|
year = this.viewDate.getUTCFullYear(),
|
|
month = this.viewDate.getUTCMonth(),
|
|
currentDate = this.date.valueOf(),
|
|
today = new Date();
|
|
if (date.getUTCFullYear() < year || (date.getUTCFullYear() == year && date.getUTCMonth() < month)) {
|
|
cls.push('old');
|
|
} else if (date.getUTCFullYear() > year || (date.getUTCFullYear() == year && date.getUTCMonth() > month)) {
|
|
cls.push('new');
|
|
}
|
|
// Compare internal UTC date with local today, not UTC today
|
|
if (this.o.todayHighlight &&
|
|
date.getUTCFullYear() == today.getFullYear() &&
|
|
date.getUTCMonth() == today.getMonth() &&
|
|
date.getUTCDate() == today.getDate()) {
|
|
cls.push('today');
|
|
}
|
|
if (currentDate && date.valueOf() == currentDate) {
|
|
cls.push('active');
|
|
}
|
|
if (date.valueOf() < this.o.startDate || date.valueOf() > this.o.endDate ||
|
|
$.inArray(date.getUTCDay(), this.o.daysOfWeekDisabled) !== -1) {
|
|
cls.push('disabled');
|
|
}
|
|
if (this.range){
|
|
if (date > this.range[0] && date < this.range[this.range.length-1]){
|
|
cls.push('range');
|
|
}
|
|
if ($.inArray(date.valueOf(), this.range) != -1){
|
|
cls.push('selected');
|
|
}
|
|
}
|
|
return cls;
|
|
},
|
|
|
|
fill: function() {
|
|
var d = new Date(this.viewDate),
|
|
year = d.getUTCFullYear(),
|
|
month = d.getUTCMonth(),
|
|
startYear = this.o.startDate !== -Infinity ? this.o.startDate.getUTCFullYear() : -Infinity,
|
|
startMonth = this.o.startDate !== -Infinity ? this.o.startDate.getUTCMonth() : -Infinity,
|
|
endYear = this.o.endDate !== Infinity ? this.o.endDate.getUTCFullYear() : Infinity,
|
|
endMonth = this.o.endDate !== Infinity ? this.o.endDate.getUTCMonth() : Infinity,
|
|
currentDate = this.date && this.date.valueOf(),
|
|
tooltip;
|
|
this.picker.find('.datepicker-days thead th.datepicker-switch')
|
|
.text(dates[this.o.language].months[month]+' '+year);
|
|
this.picker.find('tfoot th.today')
|
|
.text(dates[this.o.language].today)
|
|
.toggle(this.o.todayBtn !== false);
|
|
this.picker.find('tfoot th.clear')
|
|
.text(dates[this.o.language].clear)
|
|
.toggle(this.o.clearBtn !== false);
|
|
this.updateNavArrows();
|
|
this.fillMonths();
|
|
var prevMonth = UTCDate(year, month-1, 28,0,0,0,0),
|
|
day = DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth());
|
|
prevMonth.setUTCDate(day);
|
|
prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.o.weekStart + 7)%7);
|
|
var nextMonth = new Date(prevMonth);
|
|
nextMonth.setUTCDate(nextMonth.getUTCDate() + 42);
|
|
nextMonth = nextMonth.valueOf();
|
|
var html = [];
|
|
var clsName;
|
|
while(prevMonth.valueOf() < nextMonth) {
|
|
if (prevMonth.getUTCDay() == this.o.weekStart) {
|
|
html.push('<tr>');
|
|
if(this.o.calendarWeeks){
|
|
// ISO 8601: First week contains first thursday.
|
|
// ISO also states week starts on Monday, but we can be more abstract here.
|
|
var
|
|
// Start of current week: based on weekstart/current date
|
|
ws = new Date(+prevMonth + (this.o.weekStart - prevMonth.getUTCDay() - 7) % 7 * 864e5),
|
|
// Thursday of this week
|
|
th = new Date(+ws + (7 + 4 - ws.getUTCDay()) % 7 * 864e5),
|
|
// First Thursday of year, year from thursday
|
|
yth = new Date(+(yth = UTCDate(th.getUTCFullYear(), 0, 1)) + (7 + 4 - yth.getUTCDay())%7*864e5),
|
|
// Calendar week: ms between thursdays, div ms per day, div 7 days
|
|
calWeek = (th - yth) / 864e5 / 7 + 1;
|
|
html.push('<td class="cw">'+ calWeek +'</td>');
|
|
|
|
}
|
|
}
|
|
clsName = this.getClassNames(prevMonth);
|
|
clsName.push('day');
|
|
|
|
var before = this.o.beforeShowDay(prevMonth);
|
|
if (before === undefined)
|
|
before = {};
|
|
else if (typeof(before) === 'boolean')
|
|
before = {enabled: before};
|
|
else if (typeof(before) === 'string')
|
|
before = {classes: before};
|
|
if (before.enabled === false)
|
|
clsName.push('disabled');
|
|
if (before.classes)
|
|
clsName = clsName.concat(before.classes.split(/\s+/));
|
|
if (before.tooltip)
|
|
tooltip = before.tooltip;
|
|
|
|
clsName = $.unique(clsName);
|
|
html.push('<td class="'+clsName.join(' ')+'"' + (tooltip ? ' title="'+tooltip+'"' : '') + '>'+prevMonth.getUTCDate() + '</td>');
|
|
if (prevMonth.getUTCDay() == this.o.weekEnd) {
|
|
html.push('</tr>');
|
|
}
|
|
prevMonth.setUTCDate(prevMonth.getUTCDate()+1);
|
|
}
|
|
this.picker.find('.datepicker-days tbody').empty().append(html.join(''));
|
|
var currentYear = this.date && this.date.getUTCFullYear();
|
|
|
|
var months = this.picker.find('.datepicker-months')
|
|
.find('th:eq(1)')
|
|
.text(year)
|
|
.end()
|
|
.find('span').removeClass('active');
|
|
if (currentYear && currentYear == year) {
|
|
months.eq(this.date.getUTCMonth()).addClass('active');
|
|
}
|
|
if (year < startYear || year > endYear) {
|
|
months.addClass('disabled');
|
|
}
|
|
if (year == startYear) {
|
|
months.slice(0, startMonth).addClass('disabled');
|
|
}
|
|
if (year == endYear) {
|
|
months.slice(endMonth+1).addClass('disabled');
|
|
}
|
|
|
|
html = '';
|
|
year = parseInt(year/10, 10) * 10;
|
|
var yearCont = this.picker.find('.datepicker-years')
|
|
.find('th:eq(1)')
|
|
.text(year + '-' + (year + 9))
|
|
.end()
|
|
.find('td');
|
|
year -= 1;
|
|
for (var i = -1; i < 11; i++) {
|
|
html += '<span class="year'+(i == -1 ? ' old' : i == 10 ? ' new' : '')+(currentYear == year ? ' active' : '')+(year < startYear || year > endYear ? ' disabled' : '')+'">'+year+'</span>';
|
|
year += 1;
|
|
}
|
|
yearCont.html(html);
|
|
},
|
|
|
|
updateNavArrows: function() {
|
|
if (!this._allow_update) return;
|
|
|
|
var d = new Date(this.viewDate),
|
|
year = d.getUTCFullYear(),
|
|
month = d.getUTCMonth();
|
|
switch (this.viewMode) {
|
|
case 0:
|
|
if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear() && month <= this.o.startDate.getUTCMonth()) {
|
|
this.picker.find('.prev').css({visibility: 'hidden'});
|
|
} else {
|
|
this.picker.find('.prev').css({visibility: 'visible'});
|
|
}
|
|
if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear() && month >= this.o.endDate.getUTCMonth()) {
|
|
this.picker.find('.next').css({visibility: 'hidden'});
|
|
} else {
|
|
this.picker.find('.next').css({visibility: 'visible'});
|
|
}
|
|
break;
|
|
case 1:
|
|
case 2:
|
|
if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear()) {
|
|
this.picker.find('.prev').css({visibility: 'hidden'});
|
|
} else {
|
|
this.picker.find('.prev').css({visibility: 'visible'});
|
|
}
|
|
if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear()) {
|
|
this.picker.find('.next').css({visibility: 'hidden'});
|
|
} else {
|
|
this.picker.find('.next').css({visibility: 'visible'});
|
|
}
|
|
break;
|
|
}
|
|
},
|
|
|
|
click: function(e) {
|
|
e.preventDefault();
|
|
var target = $(e.target).closest('span, td, th');
|
|
if (target.length == 1) {
|
|
switch(target[0].nodeName.toLowerCase()) {
|
|
case 'th':
|
|
switch(target[0].className) {
|
|
case 'datepicker-switch':
|
|
this.showMode(1);
|
|
break;
|
|
case 'prev':
|
|
case 'next':
|
|
var dir = DPGlobal.modes[this.viewMode].navStep * (target[0].className == 'prev' ? -1 : 1);
|
|
switch(this.viewMode){
|
|
case 0:
|
|
this.viewDate = this.moveMonth(this.viewDate, dir);
|
|
break;
|
|
case 1:
|
|
case 2:
|
|
this.viewDate = this.moveYear(this.viewDate, dir);
|
|
break;
|
|
}
|
|
this.fill();
|
|
break;
|
|
case 'today':
|
|
var date = new Date();
|
|
date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
|
|
|
|
this.showMode(-2);
|
|
var which = this.o.todayBtn == 'linked' ? null : 'view';
|
|
this._setDate(date, which);
|
|
break;
|
|
case 'clear':
|
|
var element;
|
|
if (this.isInput)
|
|
element = this.element;
|
|
else if (this.component)
|
|
element = this.element.find('input');
|
|
if (element)
|
|
element.val("").change();
|
|
this._trigger('changeDate');
|
|
this.update();
|
|
if (this.o.autoclose)
|
|
this.hide();
|
|
break;
|
|
}
|
|
break;
|
|
case 'span':
|
|
if (!target.is('.disabled')) {
|
|
this.viewDate.setUTCDate(1);
|
|
if (target.is('.month')) {
|
|
var day = 1;
|
|
var month = target.parent().find('span').index(target);
|
|
var year = this.viewDate.getUTCFullYear();
|
|
this.viewDate.setUTCMonth(month);
|
|
this._trigger('changeMonth', this.viewDate);
|
|
if (this.o.minViewMode === 1) {
|
|
this._setDate(UTCDate(year, month, day,0,0,0,0));
|
|
}
|
|
} else {
|
|
var year = parseInt(target.text(), 10)||0;
|
|
var day = 1;
|
|
var month = 0;
|
|
this.viewDate.setUTCFullYear(year);
|
|
this._trigger('changeYear', this.viewDate);
|
|
if (this.o.minViewMode === 2) {
|
|
this._setDate(UTCDate(year, month, day,0,0,0,0));
|
|
}
|
|
}
|
|
this.showMode(-1);
|
|
this.fill();
|
|
}
|
|
break;
|
|
case 'td':
|
|
if (target.is('.day') && !target.is('.disabled')){
|
|
var day = parseInt(target.text(), 10)||1;
|
|
var year = this.viewDate.getUTCFullYear(),
|
|
month = this.viewDate.getUTCMonth();
|
|
if (target.is('.old')) {
|
|
if (month === 0) {
|
|
month = 11;
|
|
year -= 1;
|
|
} else {
|
|
month -= 1;
|
|
}
|
|
} else if (target.is('.new')) {
|
|
if (month == 11) {
|
|
month = 0;
|
|
year += 1;
|
|
} else {
|
|
month += 1;
|
|
}
|
|
}
|
|
this._setDate(UTCDate(year, month, day,0,0,0,0));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
|
|
_setDate: function(date, which){
|
|
if (!which || which == 'date')
|
|
this.date = new Date(date);
|
|
if (!which || which == 'view')
|
|
this.viewDate = new Date(date);
|
|
this.fill();
|
|
this.setValue();
|
|
this._trigger('changeDate');
|
|
var element;
|
|
if (this.isInput) {
|
|
element = this.element;
|
|
} else if (this.component){
|
|
element = this.element.find('input');
|
|
}
|
|
if (element) {
|
|
element.change();
|
|
if (this.o.autoclose && (!which || which == 'date')) {
|
|
this.hide();
|
|
}
|
|
}
|
|
},
|
|
|
|
moveMonth: function(date, dir){
|
|
if (!dir) return date;
|
|
var new_date = new Date(date.valueOf()),
|
|
day = new_date.getUTCDate(),
|
|
month = new_date.getUTCMonth(),
|
|
mag = Math.abs(dir),
|
|
new_month, test;
|
|
dir = dir > 0 ? 1 : -1;
|
|
if (mag == 1){
|
|
test = dir == -1
|
|
// If going back one month, make sure month is not current month
|
|
// (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02)
|
|
? function(){ return new_date.getUTCMonth() == month; }
|
|
// If going forward one month, make sure month is as expected
|
|
// (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02)
|
|
: function(){ return new_date.getUTCMonth() != new_month; };
|
|
new_month = month + dir;
|
|
new_date.setUTCMonth(new_month);
|
|
// Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11
|
|
if (new_month < 0 || new_month > 11)
|
|
new_month = (new_month + 12) % 12;
|
|
} else {
|
|
// For magnitudes >1, move one month at a time...
|
|
for (var i=0; i<mag; i++)
|
|
// ...which might decrease the day (eg, Jan 31 to Feb 28, etc)...
|
|
new_date = this.moveMonth(new_date, dir);
|
|
// ...then reset the day, keeping it in the new month
|
|
new_month = new_date.getUTCMonth();
|
|
new_date.setUTCDate(day);
|
|
test = function(){ return new_month != new_date.getUTCMonth(); };
|
|
}
|
|
// Common date-resetting loop -- if date is beyond end of month, make it
|
|
// end of month
|
|
while (test()){
|
|
new_date.setUTCDate(--day);
|
|
new_date.setUTCMonth(new_month);
|
|
}
|
|
return new_date;
|
|
},
|
|
|
|
moveYear: function(date, dir){
|
|
return this.moveMonth(date, dir*12);
|
|
},
|
|
|
|
dateWithinRange: function(date){
|
|
return date >= this.o.startDate && date <= this.o.endDate;
|
|
},
|
|
|
|
keydown: function(e){
|
|
if (this.picker.is(':not(:visible)')){
|
|
if (e.keyCode == 27) // allow escape to hide and re-show picker
|
|
this.show();
|
|
return;
|
|
}
|
|
var dateChanged = false,
|
|
dir, day, month,
|
|
newDate, newViewDate;
|
|
switch(e.keyCode){
|
|
case 27: // escape
|
|
this.hide();
|
|
e.preventDefault();
|
|
break;
|
|
case 37: // left
|
|
case 39: // right
|
|
if (!this.o.keyboardNavigation) break;
|
|
dir = e.keyCode == 37 ? -1 : 1;
|
|
if (e.ctrlKey){
|
|
newDate = this.moveYear(this.date, dir);
|
|
newViewDate = this.moveYear(this.viewDate, dir);
|
|
} else if (e.shiftKey){
|
|
newDate = this.moveMonth(this.date, dir);
|
|
newViewDate = this.moveMonth(this.viewDate, dir);
|
|
} else {
|
|
newDate = new Date(this.date);
|
|
newDate.setUTCDate(this.date.getUTCDate() + dir);
|
|
newViewDate = new Date(this.viewDate);
|
|
newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir);
|
|
}
|
|
if (this.dateWithinRange(newDate)){
|
|
this.date = newDate;
|
|
this.viewDate = newViewDate;
|
|
this.setValue();
|
|
this.update();
|
|
e.preventDefault();
|
|
dateChanged = true;
|
|
}
|
|
break;
|
|
case 38: // up
|
|
case 40: // down
|
|
if (!this.o.keyboardNavigation) break;
|
|
dir = e.keyCode == 38 ? -1 : 1;
|
|
if (e.ctrlKey){
|
|
newDate = this.moveYear(this.date, dir);
|
|
newViewDate = this.moveYear(this.viewDate, dir);
|
|
} else if (e.shiftKey){
|
|
newDate = this.moveMonth(this.date, dir);
|
|
newViewDate = this.moveMonth(this.viewDate, dir);
|
|
} else {
|
|
newDate = new Date(this.date);
|
|
newDate.setUTCDate(this.date.getUTCDate() + dir * 7);
|
|
newViewDate = new Date(this.viewDate);
|
|
newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir * 7);
|
|
}
|
|
if (this.dateWithinRange(newDate)){
|
|
this.date = newDate;
|
|
this.viewDate = newViewDate;
|
|
this.setValue();
|
|
this.update();
|
|
e.preventDefault();
|
|
dateChanged = true;
|
|
}
|
|
break;
|
|
case 13: // enter
|
|
this.hide();
|
|
e.preventDefault();
|
|
break;
|
|
case 9: // tab
|
|
this.hide();
|
|
break;
|
|
}
|
|
if (dateChanged){
|
|
this._trigger('changeDate');
|
|
var element;
|
|
if (this.isInput) {
|
|
element = this.element;
|
|
} else if (this.component){
|
|
element = this.element.find('input');
|
|
}
|
|
if (element) {
|
|
element.change();
|
|
}
|
|
}
|
|
},
|
|
|
|
showMode: function(dir) {
|
|
if (dir) {
|
|
this.viewMode = Math.max(this.o.minViewMode, Math.min(2, this.viewMode + dir));
|
|
}
|
|
/*
|
|
vitalets: fixing bug of very special conditions:
|
|
jquery 1.7.1 + webkit + show inline datepicker in bootstrap popover.
|
|
Method show() does not set display css correctly and datepicker is not shown.
|
|
Changed to .css('display', 'block') solve the problem.
|
|
See https://github.com/vitalets/x-editable/issues/37
|
|
|
|
In jquery 1.7.2+ everything works fine.
|
|
*/
|
|
//this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show();
|
|
this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).css('display', 'block');
|
|
this.updateNavArrows();
|
|
}
|
|
};
|
|
|
|
var DateRangePicker = function(element, options){
|
|
this.element = $(element);
|
|
this.inputs = $.map(options.inputs, function(i){ return i.jquery ? i[0] : i; });
|
|
delete options.inputs;
|
|
|
|
$(this.inputs)
|
|
.datepicker(options)
|
|
.bind('changeDate', $.proxy(this.dateUpdated, this));
|
|
|
|
this.pickers = $.map(this.inputs, function(i){ return $(i).data('datepicker'); });
|
|
this.updateDates();
|
|
};
|
|
DateRangePicker.prototype = {
|
|
updateDates: function(){
|
|
this.dates = $.map(this.pickers, function(i){ return i.date; });
|
|
this.updateRanges();
|
|
},
|
|
updateRanges: function(){
|
|
var range = $.map(this.dates, function(d){ return d.valueOf(); });
|
|
$.each(this.pickers, function(i, p){
|
|
p.setRange(range);
|
|
});
|
|
},
|
|
dateUpdated: function(e){
|
|
var dp = $(e.target).data('datepicker'),
|
|
new_date = dp.getUTCDate(),
|
|
i = $.inArray(e.target, this.inputs),
|
|
l = this.inputs.length;
|
|
if (i == -1) return;
|
|
|
|
if (new_date < this.dates[i]){
|
|
// Date being moved earlier/left
|
|
while (i>=0 && new_date < this.dates[i]){
|
|
this.pickers[i--].setUTCDate(new_date);
|
|
}
|
|
}
|
|
else if (new_date > this.dates[i]){
|
|
// Date being moved later/right
|
|
while (i<l && new_date > this.dates[i]){
|
|
this.pickers[i++].setUTCDate(new_date);
|
|
}
|
|
}
|
|
this.updateDates();
|
|
},
|
|
remove: function(){
|
|
$.map(this.pickers, function(p){ p.remove(); });
|
|
delete this.element.data().datepicker;
|
|
}
|
|
};
|
|
|
|
function opts_from_el(el, prefix){
|
|
// Derive options from element data-attrs
|
|
var data = $(el).data(),
|
|
out = {}, inkey,
|
|
replace = new RegExp('^' + prefix.toLowerCase() + '([A-Z])'),
|
|
prefix = new RegExp('^' + prefix.toLowerCase());
|
|
for (var key in data)
|
|
if (prefix.test(key)){
|
|
inkey = key.replace(replace, function(_,a){ return a.toLowerCase(); });
|
|
out[inkey] = data[key];
|
|
}
|
|
return out;
|
|
}
|
|
|
|
function opts_from_locale(lang){
|
|
// Derive options from locale plugins
|
|
var out = {};
|
|
// Check if "de-DE" style date is available, if not language should
|
|
// fallback to 2 letter code eg "de"
|
|
if (!dates[lang]) {
|
|
lang = lang.split('-')[0]
|
|
if (!dates[lang])
|
|
return;
|
|
}
|
|
var d = dates[lang];
|
|
$.each(locale_opts, function(i,k){
|
|
if (k in d)
|
|
out[k] = d[k];
|
|
});
|
|
return out;
|
|
}
|
|
|
|
var old = $.fn.datepicker;
|
|
var datepicker = $.fn.datepicker = function ( option ) {
|
|
var args = Array.apply(null, arguments);
|
|
args.shift();
|
|
var internal_return,
|
|
this_return;
|
|
this.each(function () {
|
|
var $this = $(this),
|
|
data = $this.data('datepicker'),
|
|
options = typeof option == 'object' && option;
|
|
if (!data) {
|
|
var elopts = opts_from_el(this, 'date'),
|
|
// Preliminary otions
|
|
xopts = $.extend({}, defaults, elopts, options),
|
|
locopts = opts_from_locale(xopts.language),
|
|
// Options priority: js args, data-attrs, locales, defaults
|
|
opts = $.extend({}, defaults, locopts, elopts, options);
|
|
if ($this.is('.input-daterange') || opts.inputs){
|
|
var ropts = {
|
|
inputs: opts.inputs || $this.find('input').toArray()
|
|
};
|
|
$this.data('datepicker', (data = new DateRangePicker(this, $.extend(opts, ropts))));
|
|
}
|
|
else{
|
|
$this.data('datepicker', (data = new Datepicker(this, opts)));
|
|
}
|
|
}
|
|
if (typeof option == 'string' && typeof data[option] == 'function') {
|
|
internal_return = data[option].apply(data, args);
|
|
if (internal_return !== undefined)
|
|
return false;
|
|
}
|
|
});
|
|
if (internal_return !== undefined)
|
|
return internal_return;
|
|
else
|
|
return this;
|
|
};
|
|
|
|
var defaults = $.fn.datepicker.defaults = {
|
|
autoclose: false,
|
|
beforeShowDay: $.noop,
|
|
calendarWeeks: false,
|
|
clearBtn: false,
|
|
daysOfWeekDisabled: [],
|
|
endDate: Infinity,
|
|
forceParse: true,
|
|
format: 'mm/dd/yyyy',
|
|
keyboardNavigation: true,
|
|
language: 'en',
|
|
minViewMode: 0,
|
|
rtl: false,
|
|
startDate: -Infinity,
|
|
startView: 0,
|
|
todayBtn: false,
|
|
todayHighlight: false,
|
|
weekStart: 0
|
|
};
|
|
var locale_opts = $.fn.datepicker.locale_opts = [
|
|
'format',
|
|
'rtl',
|
|
'weekStart'
|
|
];
|
|
$.fn.datepicker.Constructor = Datepicker;
|
|
var dates = $.fn.datepicker.dates = {
|
|
en: {
|
|
days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
|
|
daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
|
|
daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
|
|
months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
|
|
monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
|
|
today: "Today",
|
|
clear: "Clear"
|
|
}
|
|
};
|
|
|
|
var DPGlobal = {
|
|
modes: [
|
|
{
|
|
clsName: 'days',
|
|
navFnc: 'Month',
|
|
navStep: 1
|
|
},
|
|
{
|
|
clsName: 'months',
|
|
navFnc: 'FullYear',
|
|
navStep: 1
|
|
},
|
|
{
|
|
clsName: 'years',
|
|
navFnc: 'FullYear',
|
|
navStep: 10
|
|
}],
|
|
isLeapYear: function (year) {
|
|
return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0));
|
|
},
|
|
getDaysInMonth: function (year, month) {
|
|
return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
|
|
},
|
|
validParts: /dd?|DD?|mm?|MM?|yy(?:yy)?/g,
|
|
nonpunctuation: /[^ -\/:-@\[\u3400-\u9fff-`{-~\t\n\r]+/g,
|
|
parseFormat: function(format){
|
|
// IE treats \0 as a string end in inputs (truncating the value),
|
|
// so it's a bad format delimiter, anyway
|
|
var separators = format.replace(this.validParts, '\0').split('\0'),
|
|
parts = format.match(this.validParts);
|
|
if (!separators || !separators.length || !parts || parts.length === 0){
|
|
throw new Error("Invalid date format.");
|
|
}
|
|
return {separators: separators, parts: parts};
|
|
},
|
|
parseDate: function(date, format, language) {
|
|
if (date instanceof Date) return date;
|
|
if (typeof format === 'string')
|
|
format = DPGlobal.parseFormat(format);
|
|
if (/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/.test(date)) {
|
|
var part_re = /([\-+]\d+)([dmwy])/,
|
|
parts = date.match(/([\-+]\d+)([dmwy])/g),
|
|
part, dir;
|
|
date = new Date();
|
|
for (var i=0; i<parts.length; i++) {
|
|
part = part_re.exec(parts[i]);
|
|
dir = parseInt(part[1]);
|
|
switch(part[2]){
|
|
case 'd':
|
|
date.setUTCDate(date.getUTCDate() + dir);
|
|
break;
|
|
case 'm':
|
|
date = Datepicker.prototype.moveMonth.call(Datepicker.prototype, date, dir);
|
|
break;
|
|
case 'w':
|
|
date.setUTCDate(date.getUTCDate() + dir * 7);
|
|
break;
|
|
case 'y':
|
|
date = Datepicker.prototype.moveYear.call(Datepicker.prototype, date, dir);
|
|
break;
|
|
}
|
|
}
|
|
return UTCDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0);
|
|
}
|
|
var parts = date && date.match(this.nonpunctuation) || [],
|
|
date = new Date(),
|
|
parsed = {},
|
|
setters_order = ['yyyy', 'yy', 'M', 'MM', 'm', 'mm', 'd', 'dd'],
|
|
setters_map = {
|
|
yyyy: function(d,v){ return d.setUTCFullYear(v); },
|
|
yy: function(d,v){ return d.setUTCFullYear(2000+v); },
|
|
m: function(d,v){
|
|
v -= 1;
|
|
while (v<0) v += 12;
|
|
v %= 12;
|
|
d.setUTCMonth(v);
|
|
while (d.getUTCMonth() != v)
|
|
d.setUTCDate(d.getUTCDate()-1);
|
|
return d;
|
|
},
|
|
d: function(d,v){ return d.setUTCDate(v); }
|
|
},
|
|
val, filtered, part;
|
|
setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m'];
|
|
setters_map['dd'] = setters_map['d'];
|
|
date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
|
|
var fparts = format.parts.slice();
|
|
// Remove noop parts
|
|
if (parts.length != fparts.length) {
|
|
fparts = $(fparts).filter(function(i,p){
|
|
return $.inArray(p, setters_order) !== -1;
|
|
}).toArray();
|
|
}
|
|
// Process remainder
|
|
if (parts.length == fparts.length) {
|
|
for (var i=0, cnt = fparts.length; i < cnt; i++) {
|
|
val = parseInt(parts[i], 10);
|
|
part = fparts[i];
|
|
if (isNaN(val)) {
|
|
switch(part) {
|
|
case 'MM':
|
|
filtered = $(dates[language].months).filter(function(){
|
|
var m = this.slice(0, parts[i].length),
|
|
p = parts[i].slice(0, m.length);
|
|
return m == p;
|
|
});
|
|
val = $.inArray(filtered[0], dates[language].months) + 1;
|
|
break;
|
|
case 'M':
|
|
filtered = $(dates[language].monthsShort).filter(function(){
|
|
var m = this.slice(0, parts[i].length),
|
|
p = parts[i].slice(0, m.length);
|
|
return m == p;
|
|
});
|
|
val = $.inArray(filtered[0], dates[language].monthsShort) + 1;
|
|
break;
|
|
}
|
|
}
|
|
parsed[part] = val;
|
|
}
|
|
for (var i=0, s; i<setters_order.length; i++){
|
|
s = setters_order[i];
|
|
if (s in parsed && !isNaN(parsed[s]))
|
|
setters_map[s](date, parsed[s]);
|
|
}
|
|
}
|
|
return date;
|
|
},
|
|
formatDate: function(date, format, language){
|
|
if (typeof format === 'string')
|
|
format = DPGlobal.parseFormat(format);
|
|
var val = {
|
|
d: date.getUTCDate(),
|
|
D: dates[language].daysShort[date.getUTCDay()],
|
|
DD: dates[language].days[date.getUTCDay()],
|
|
m: date.getUTCMonth() + 1,
|
|
M: dates[language].monthsShort[date.getUTCMonth()],
|
|
MM: dates[language].months[date.getUTCMonth()],
|
|
yy: date.getUTCFullYear().toString().substring(2),
|
|
yyyy: date.getUTCFullYear()
|
|
};
|
|
val.dd = (val.d < 10 ? '0' : '') + val.d;
|
|
val.mm = (val.m < 10 ? '0' : '') + val.m;
|
|
var date = [],
|
|
seps = $.extend([], format.separators);
|
|
for (var i=0, cnt = format.parts.length; i <= cnt; i++) {
|
|
if (seps.length)
|
|
date.push(seps.shift());
|
|
date.push(val[format.parts[i]]);
|
|
}
|
|
return date.join('');
|
|
},
|
|
headTemplate: '<thead>'+
|
|
'<tr>'+
|
|
'<th class="prev"><i class="icon-arrow-left"/></th>'+
|
|
'<th colspan="5" class="datepicker-switch"></th>'+
|
|
'<th class="next"><i class="icon-arrow-right"/></th>'+
|
|
'</tr>'+
|
|
'</thead>',
|
|
contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>',
|
|
footTemplate: '<tfoot><tr><th colspan="7" class="today"></th></tr><tr><th colspan="7" class="clear"></th></tr></tfoot>'
|
|
};
|
|
DPGlobal.template = '<div class="datepicker">'+
|
|
'<div class="datepicker-days">'+
|
|
'<table class=" table-condensed">'+
|
|
DPGlobal.headTemplate+
|
|
'<tbody></tbody>'+
|
|
DPGlobal.footTemplate+
|
|
'</table>'+
|
|
'</div>'+
|
|
'<div class="datepicker-months">'+
|
|
'<table class="table-condensed">'+
|
|
DPGlobal.headTemplate+
|
|
DPGlobal.contTemplate+
|
|
DPGlobal.footTemplate+
|
|
'</table>'+
|
|
'</div>'+
|
|
'<div class="datepicker-years">'+
|
|
'<table class="table-condensed">'+
|
|
DPGlobal.headTemplate+
|
|
DPGlobal.contTemplate+
|
|
DPGlobal.footTemplate+
|
|
'</table>'+
|
|
'</div>'+
|
|
'</div>';
|
|
|
|
$.fn.datepicker.DPGlobal = DPGlobal;
|
|
|
|
|
|
/* DATEPICKER NO CONFLICT
|
|
* =================== */
|
|
|
|
$.fn.datepicker.noConflict = function(){
|
|
$.fn.datepicker = old;
|
|
return this;
|
|
};
|
|
|
|
|
|
/* DATEPICKER DATA-API
|
|
* ================== */
|
|
|
|
$(document).on(
|
|
'focus.datepicker.data-api click.datepicker.data-api',
|
|
'[data-provide="datepicker"]',
|
|
function(e){
|
|
var $this = $(this);
|
|
if ($this.data('datepicker')) return;
|
|
e.preventDefault();
|
|
// component click requires us to explicitly show it
|
|
datepicker.call($this, 'show');
|
|
}
|
|
);
|
|
$(function(){
|
|
//$('[data-provide="datepicker-inline"]').datepicker();
|
|
//vit: changed to support noConflict()
|
|
datepicker.call($('[data-provide="datepicker-inline"]'));
|
|
});
|
|
|
|
}( window.jQuery ));
|
|
|
|
/**
|
|
Bootstrap-datepicker.
|
|
Description and examples: https://github.com/eternicode/bootstrap-datepicker.
|
|
For **i18n** you should include js file from here: https://github.com/eternicode/bootstrap-datepicker/tree/master/js/locales
|
|
and set `language` option.
|
|
Since 1.4.0 date has different appearance in **popup** and **inline** modes.
|
|
|
|
@class date
|
|
@extends abstractinput
|
|
@final
|
|
@example
|
|
<a href="#" id="dob" data-type="date" data-pk="1" data-url="/post" data-title="Select date">15/05/1984</a>
|
|
<script>
|
|
$(function(){
|
|
$('#dob').editable({
|
|
format: 'yyyy-mm-dd',
|
|
viewformat: 'dd/mm/yyyy',
|
|
datepicker: {
|
|
weekStart: 1
|
|
}
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
**/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
//store bootstrap-datepicker as bdateicker to exclude conflict with jQuery UI one
|
|
$.fn.bdatepicker = $.fn.datepicker.noConflict();
|
|
if(!$.fn.datepicker) { //if there were no other datepickers, keep also original name
|
|
$.fn.datepicker = $.fn.bdatepicker;
|
|
}
|
|
|
|
var Date = function (options) {
|
|
this.init('date', options, Date.defaults);
|
|
this.initPicker(options, Date.defaults);
|
|
};
|
|
|
|
$.fn.editableutils.inherit(Date, $.fn.editabletypes.abstractinput);
|
|
|
|
$.extend(Date.prototype, {
|
|
initPicker: function(options, defaults) {
|
|
//'format' is set directly from settings or data-* attributes
|
|
|
|
//by default viewformat equals to format
|
|
if(!this.options.viewformat) {
|
|
this.options.viewformat = this.options.format;
|
|
}
|
|
|
|
//try parse datepicker config defined as json string in data-datepicker
|
|
options.datepicker = $.fn.editableutils.tryParseJson(options.datepicker, true);
|
|
|
|
//overriding datepicker config (as by default jQuery extend() is not recursive)
|
|
//since 1.4 datepicker internally uses viewformat instead of format. Format is for submit only
|
|
this.options.datepicker = $.extend({}, defaults.datepicker, options.datepicker, {
|
|
format: this.options.viewformat
|
|
});
|
|
|
|
//language
|
|
this.options.datepicker.language = this.options.datepicker.language || 'en';
|
|
|
|
//store DPglobal
|
|
this.dpg = $.fn.bdatepicker.DPGlobal;
|
|
|
|
//store parsed formats
|
|
this.parsedFormat = this.dpg.parseFormat(this.options.format);
|
|
this.parsedViewFormat = this.dpg.parseFormat(this.options.viewformat);
|
|
},
|
|
|
|
render: function () {
|
|
this.$input.bdatepicker(this.options.datepicker);
|
|
|
|
//"clear" link
|
|
if(this.options.clear) {
|
|
this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
this.clear();
|
|
}, this));
|
|
|
|
this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear));
|
|
}
|
|
},
|
|
|
|
value2html: function(value, element) {
|
|
var text = value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : '';
|
|
Date.superclass.value2html.call(this, text, element);
|
|
},
|
|
|
|
html2value: function(html) {
|
|
return this.parseDate(html, this.parsedViewFormat);
|
|
},
|
|
|
|
value2str: function(value) {
|
|
return value ? this.dpg.formatDate(value, this.parsedFormat, this.options.datepicker.language) : '';
|
|
},
|
|
|
|
str2value: function(str) {
|
|
return this.parseDate(str, this.parsedFormat);
|
|
},
|
|
|
|
value2submit: function(value) {
|
|
return this.value2str(value);
|
|
},
|
|
|
|
value2input: function(value) {
|
|
this.$input.bdatepicker('update', value);
|
|
},
|
|
|
|
input2value: function() {
|
|
return this.$input.data('datepicker').date;
|
|
},
|
|
|
|
activate: function() {
|
|
},
|
|
|
|
clear: function() {
|
|
this.$input.data('datepicker').date = null;
|
|
this.$input.find('.active').removeClass('active');
|
|
if(!this.options.showbuttons) {
|
|
this.$input.closest('form').submit();
|
|
}
|
|
},
|
|
|
|
autosubmit: function() {
|
|
this.$input.on('mouseup', '.day', function(e){
|
|
if($(e.currentTarget).is('.old') || $(e.currentTarget).is('.new')) {
|
|
return;
|
|
}
|
|
var $form = $(this).closest('form');
|
|
setTimeout(function() {
|
|
$form.submit();
|
|
}, 200);
|
|
});
|
|
//changedate is not suitable as it triggered when showing datepicker. see #149
|
|
/*
|
|
this.$input.on('changeDate', function(e){
|
|
var $form = $(this).closest('form');
|
|
setTimeout(function() {
|
|
$form.submit();
|
|
}, 200);
|
|
});
|
|
*/
|
|
},
|
|
|
|
/*
|
|
For incorrect date bootstrap-datepicker returns current date that is not suitable
|
|
for datefield.
|
|
This function returns null for incorrect date.
|
|
*/
|
|
parseDate: function(str, format) {
|
|
var date = null, formattedBack;
|
|
if(str) {
|
|
date = this.dpg.parseDate(str, format, this.options.datepicker.language);
|
|
if(typeof str === 'string') {
|
|
formattedBack = this.dpg.formatDate(date, format, this.options.datepicker.language);
|
|
if(str !== formattedBack) {
|
|
date = null;
|
|
}
|
|
}
|
|
}
|
|
return date;
|
|
}
|
|
|
|
});
|
|
|
|
Date.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
|
|
/**
|
|
@property tpl
|
|
@default <div></div>
|
|
**/
|
|
tpl:'<div class="editable-date well"></div>',
|
|
/**
|
|
@property inputclass
|
|
@default null
|
|
**/
|
|
inputclass: null,
|
|
/**
|
|
Format used for sending value to server. Also applied when converting date from <code>data-value</code> attribute.<br>
|
|
Possible tokens are: <code>d, dd, m, mm, yy, yyyy</code>
|
|
|
|
@property format
|
|
@type string
|
|
@default yyyy-mm-dd
|
|
**/
|
|
format:'yyyy-mm-dd',
|
|
/**
|
|
Format used for displaying date. Also applied when converting date from element's text on init.
|
|
If not specified equals to <code>format</code>
|
|
|
|
@property viewformat
|
|
@type string
|
|
@default null
|
|
**/
|
|
viewformat: null,
|
|
/**
|
|
Configuration of datepicker.
|
|
Full list of options: http://bootstrap-datepicker.readthedocs.org/en/latest/options.html
|
|
|
|
@property datepicker
|
|
@type object
|
|
@default {
|
|
weekStart: 0,
|
|
startView: 0,
|
|
minViewMode: 0,
|
|
autoclose: false
|
|
}
|
|
**/
|
|
datepicker:{
|
|
weekStart: 0,
|
|
startView: 0,
|
|
minViewMode: 0,
|
|
autoclose: false
|
|
},
|
|
/**
|
|
Text shown as clear date button.
|
|
If <code>false</code> clear button will not be rendered.
|
|
|
|
@property clear
|
|
@type boolean|string
|
|
@default 'x clear'
|
|
**/
|
|
clear: '× clear'
|
|
});
|
|
|
|
$.fn.editabletypes.date = Date;
|
|
|
|
}(window.jQuery));
|
|
|
|
/**
|
|
Bootstrap datefield input - modification for inline mode.
|
|
Shows normal <input type="text"> and binds popup datepicker.
|
|
Automatically shown in inline mode.
|
|
|
|
@class datefield
|
|
@extends date
|
|
|
|
@since 1.4.0
|
|
**/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var DateField = function (options) {
|
|
this.init('datefield', options, DateField.defaults);
|
|
this.initPicker(options, DateField.defaults);
|
|
};
|
|
|
|
$.fn.editableutils.inherit(DateField, $.fn.editabletypes.date);
|
|
|
|
$.extend(DateField.prototype, {
|
|
render: function () {
|
|
this.$input = this.$tpl.find('input');
|
|
this.setClass();
|
|
this.setAttr('placeholder');
|
|
|
|
//bootstrap-datepicker is set `bdateicker` to exclude conflict with jQuery UI one. (in date.js)
|
|
this.$tpl.bdatepicker(this.options.datepicker);
|
|
|
|
//need to disable original event handlers
|
|
this.$input.off('focus keydown');
|
|
|
|
//update value of datepicker
|
|
this.$input.keyup($.proxy(function(){
|
|
this.$tpl.removeData('date');
|
|
this.$tpl.bdatepicker('update');
|
|
}, this));
|
|
|
|
},
|
|
|
|
value2input: function(value) {
|
|
this.$input.val(value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : '');
|
|
this.$tpl.bdatepicker('update');
|
|
},
|
|
|
|
input2value: function() {
|
|
return this.html2value(this.$input.val());
|
|
},
|
|
|
|
activate: function() {
|
|
$.fn.editabletypes.text.prototype.activate.call(this);
|
|
},
|
|
|
|
autosubmit: function() {
|
|
//reset autosubmit to empty
|
|
}
|
|
});
|
|
|
|
DateField.defaults = $.extend({}, $.fn.editabletypes.date.defaults, {
|
|
/**
|
|
@property tpl
|
|
**/
|
|
tpl:'<div class="input-append date"><input type="text"/><span class="add-on"><i class="icon-th"></i></span></div>',
|
|
/**
|
|
@property inputclass
|
|
@default 'input-small'
|
|
**/
|
|
inputclass: 'input-small',
|
|
|
|
/* datepicker config */
|
|
datepicker: {
|
|
weekStart: 0,
|
|
startView: 0,
|
|
minViewMode: 0,
|
|
autoclose: true
|
|
}
|
|
});
|
|
|
|
$.fn.editabletypes.datefield = DateField;
|
|
|
|
}(window.jQuery));
|
|
/**
|
|
Bootstrap-datetimepicker.
|
|
Based on [smalot bootstrap-datetimepicker plugin](https://github.com/smalot/bootstrap-datetimepicker).
|
|
Before usage you should manually include dependent js and css:
|
|
|
|
<link href="css/datetimepicker.css" rel="stylesheet" type="text/css"></link>
|
|
<script src="js/bootstrap-datetimepicker.js"></script>
|
|
|
|
For **i18n** you should include js file from here: https://github.com/smalot/bootstrap-datetimepicker/tree/master/js/locales
|
|
and set `language` option.
|
|
|
|
@class datetime
|
|
@extends abstractinput
|
|
@final
|
|
@since 1.4.4
|
|
@example
|
|
<a href="#" id="last_seen" data-type="datetime" data-pk="1" data-url="/post" title="Select date & time">15/03/2013 12:45</a>
|
|
<script>
|
|
$(function(){
|
|
$('#last_seen').editable({
|
|
format: 'yyyy-mm-dd hh:ii',
|
|
viewformat: 'dd/mm/yyyy hh:ii',
|
|
datetimepicker: {
|
|
weekStart: 1
|
|
}
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
**/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var DateTime = function (options) {
|
|
this.init('datetime', options, DateTime.defaults);
|
|
this.initPicker(options, DateTime.defaults);
|
|
};
|
|
|
|
$.fn.editableutils.inherit(DateTime, $.fn.editabletypes.abstractinput);
|
|
|
|
$.extend(DateTime.prototype, {
|
|
initPicker: function(options, defaults) {
|
|
//'format' is set directly from settings or data-* attributes
|
|
|
|
//by default viewformat equals to format
|
|
if(!this.options.viewformat) {
|
|
this.options.viewformat = this.options.format;
|
|
}
|
|
|
|
//try parse datetimepicker config defined as json string in data-datetimepicker
|
|
options.datetimepicker = $.fn.editableutils.tryParseJson(options.datetimepicker, true);
|
|
|
|
//overriding datetimepicker config (as by default jQuery extend() is not recursive)
|
|
//since 1.4 datetimepicker internally uses viewformat instead of format. Format is for submit only
|
|
this.options.datetimepicker = $.extend({}, defaults.datetimepicker, options.datetimepicker, {
|
|
format: this.options.viewformat
|
|
});
|
|
|
|
//language
|
|
this.options.datetimepicker.language = this.options.datetimepicker.language || 'en';
|
|
|
|
//store DPglobal
|
|
this.dpg = $.fn.datetimepicker.DPGlobal;
|
|
|
|
//store parsed formats
|
|
this.parsedFormat = this.dpg.parseFormat(this.options.format, this.options.formatType);
|
|
this.parsedViewFormat = this.dpg.parseFormat(this.options.viewformat, this.options.formatType);
|
|
},
|
|
|
|
render: function () {
|
|
this.$input.datetimepicker(this.options.datetimepicker);
|
|
|
|
//adjust container position when viewMode changes
|
|
//see https://github.com/smalot/bootstrap-datetimepicker/pull/80
|
|
this.$input.on('changeMode', function(e) {
|
|
var f = $(this).closest('form').parent();
|
|
//timeout here, otherwise container changes position before form has new size
|
|
setTimeout(function(){
|
|
f.triggerHandler('resize');
|
|
}, 0);
|
|
});
|
|
|
|
//"clear" link
|
|
if(this.options.clear) {
|
|
this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
this.clear();
|
|
}, this));
|
|
|
|
this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear));
|
|
}
|
|
},
|
|
|
|
value2html: function(value, element) {
|
|
//formatDate works with UTCDate!
|
|
var text = value ? this.dpg.formatDate(this.toUTC(value), this.parsedViewFormat, this.options.datetimepicker.language, this.options.formatType) : '';
|
|
if(element) {
|
|
DateTime.superclass.value2html.call(this, text, element);
|
|
} else {
|
|
return text;
|
|
}
|
|
},
|
|
|
|
html2value: function(html) {
|
|
//parseDate return utc date!
|
|
var value = this.parseDate(html, this.parsedViewFormat);
|
|
return value ? this.fromUTC(value) : null;
|
|
},
|
|
|
|
value2str: function(value) {
|
|
//formatDate works with UTCDate!
|
|
return value ? this.dpg.formatDate(this.toUTC(value), this.parsedFormat, this.options.datetimepicker.language, this.options.formatType) : '';
|
|
},
|
|
|
|
str2value: function(str) {
|
|
//parseDate return utc date!
|
|
var value = this.parseDate(str, this.parsedFormat);
|
|
return value ? this.fromUTC(value) : null;
|
|
},
|
|
|
|
value2submit: function(value) {
|
|
return this.value2str(value);
|
|
},
|
|
|
|
value2input: function(value) {
|
|
if(value) {
|
|
this.$input.data('datetimepicker').setDate(value);
|
|
}
|
|
},
|
|
|
|
input2value: function() {
|
|
//date may be cleared, in that case getDate() triggers error
|
|
var dt = this.$input.data('datetimepicker');
|
|
return dt.date ? dt.getDate() : null;
|
|
},
|
|
|
|
activate: function() {
|
|
},
|
|
|
|
clear: function() {
|
|
this.$input.data('datetimepicker').date = null;
|
|
this.$input.find('.active').removeClass('active');
|
|
if(!this.options.showbuttons) {
|
|
this.$input.closest('form').submit();
|
|
}
|
|
},
|
|
|
|
autosubmit: function() {
|
|
this.$input.on('mouseup', '.minute', function(e){
|
|
var $form = $(this).closest('form');
|
|
setTimeout(function() {
|
|
$form.submit();
|
|
}, 200);
|
|
});
|
|
},
|
|
|
|
//convert date from local to utc
|
|
toUTC: function(value) {
|
|
return value ? new Date(value.valueOf() - value.getTimezoneOffset() * 60000) : value;
|
|
},
|
|
|
|
//convert date from utc to local
|
|
fromUTC: function(value) {
|
|
return value ? new Date(value.valueOf() + value.getTimezoneOffset() * 60000) : value;
|
|
},
|
|
|
|
/*
|
|
For incorrect date bootstrap-datetimepicker returns current date that is not suitable
|
|
for datetimefield.
|
|
This function returns null for incorrect date.
|
|
*/
|
|
parseDate: function(str, format) {
|
|
var date = null, formattedBack;
|
|
if(str) {
|
|
date = this.dpg.parseDate(str, format, this.options.datetimepicker.language, this.options.formatType);
|
|
if(typeof str === 'string') {
|
|
formattedBack = this.dpg.formatDate(date, format, this.options.datetimepicker.language, this.options.formatType);
|
|
if(str !== formattedBack) {
|
|
date = null;
|
|
}
|
|
}
|
|
}
|
|
return date;
|
|
}
|
|
|
|
});
|
|
|
|
DateTime.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
|
|
/**
|
|
@property tpl
|
|
@default <div></div>
|
|
**/
|
|
tpl:'<div class="editable-date well"></div>',
|
|
/**
|
|
@property inputclass
|
|
@default null
|
|
**/
|
|
inputclass: null,
|
|
/**
|
|
Format used for sending value to server. Also applied when converting date from <code>data-value</code> attribute.<br>
|
|
Possible tokens are: <code>d, dd, m, mm, yy, yyyy, h, i</code>
|
|
|
|
@property format
|
|
@type string
|
|
@default yyyy-mm-dd hh:ii
|
|
**/
|
|
format:'yyyy-mm-dd hh:ii',
|
|
formatType:'standard',
|
|
/**
|
|
Format used for displaying date. Also applied when converting date from element's text on init.
|
|
If not specified equals to <code>format</code>
|
|
|
|
@property viewformat
|
|
@type string
|
|
@default null
|
|
**/
|
|
viewformat: null,
|
|
/**
|
|
Configuration of datetimepicker.
|
|
Full list of options: https://github.com/smalot/bootstrap-datetimepicker
|
|
|
|
@property datetimepicker
|
|
@type object
|
|
@default { }
|
|
**/
|
|
datetimepicker:{
|
|
todayHighlight: false,
|
|
autoclose: false
|
|
},
|
|
/**
|
|
Text shown as clear date button.
|
|
If <code>false</code> clear button will not be rendered.
|
|
|
|
@property clear
|
|
@type boolean|string
|
|
@default 'x clear'
|
|
**/
|
|
clear: '× clear'
|
|
});
|
|
|
|
$.fn.editabletypes.datetime = DateTime;
|
|
|
|
}(window.jQuery));
|
|
/**
|
|
Bootstrap datetimefield input - datetime input for inline mode.
|
|
Shows normal <input type="text"> and binds popup datetimepicker.
|
|
Automatically shown in inline mode.
|
|
|
|
@class datetimefield
|
|
@extends datetime
|
|
|
|
**/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var DateTimeField = function (options) {
|
|
this.init('datetimefield', options, DateTimeField.defaults);
|
|
this.initPicker(options, DateTimeField.defaults);
|
|
};
|
|
|
|
$.fn.editableutils.inherit(DateTimeField, $.fn.editabletypes.datetime);
|
|
|
|
$.extend(DateTimeField.prototype, {
|
|
render: function () {
|
|
this.$input = this.$tpl.find('input');
|
|
this.setClass();
|
|
this.setAttr('placeholder');
|
|
|
|
this.$tpl.datetimepicker(this.options.datetimepicker);
|
|
|
|
//need to disable original event handlers
|
|
this.$input.off('focus keydown');
|
|
|
|
//update value of datepicker
|
|
this.$input.keyup($.proxy(function(){
|
|
this.$tpl.removeData('date');
|
|
this.$tpl.datetimepicker('update');
|
|
}, this));
|
|
|
|
},
|
|
|
|
value2input: function(value) {
|
|
this.$input.val(this.value2html(value));
|
|
this.$tpl.datetimepicker('update');
|
|
},
|
|
|
|
input2value: function() {
|
|
return this.html2value(this.$input.val());
|
|
},
|
|
|
|
activate: function() {
|
|
$.fn.editabletypes.text.prototype.activate.call(this);
|
|
},
|
|
|
|
autosubmit: function() {
|
|
//reset autosubmit to empty
|
|
}
|
|
});
|
|
|
|
DateTimeField.defaults = $.extend({}, $.fn.editabletypes.datetime.defaults, {
|
|
/**
|
|
@property tpl
|
|
**/
|
|
tpl:'<div class="input-append date"><input type="text"/><span class="add-on"><i class="icon-th"></i></span></div>',
|
|
/**
|
|
@property inputclass
|
|
@default 'input-medium'
|
|
**/
|
|
inputclass: 'input-medium',
|
|
|
|
/* datetimepicker config */
|
|
datetimepicker:{
|
|
todayHighlight: false,
|
|
autoclose: true
|
|
}
|
|
});
|
|
|
|
$.fn.editabletypes.datetimefield = DateTimeField;
|
|
|
|
}(window.jQuery));
|
|
/*! version : 4.17.47
|
|
=========================================================
|
|
bootstrap-datetimejs
|
|
https://github.com/Eonasdan/bootstrap-datetimepicker
|
|
Copyright (c) 2015 Jonathan Peterson
|
|
=========================================================
|
|
*/
|
|
/*
|
|
The MIT License (MIT)
|
|
|
|
Copyright (c) 2015 Jonathan Peterson
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
THE SOFTWARE.
|
|
*/
|
|
/*global define:false */
|
|
/*global exports:false */
|
|
/*global require:false */
|
|
/*global jQuery:false */
|
|
/*global moment:false */
|
|
(function (factory) {
|
|
'use strict';
|
|
if (typeof define === 'function' && define.amd) {
|
|
// AMD is used - Register as an anonymous module.
|
|
define(['jquery', 'moment'], factory);
|
|
} else if (typeof exports === 'object') {
|
|
module.exports = factory(require('jquery'), require('moment'));
|
|
} else {
|
|
// Neither AMD nor CommonJS used. Use global variables.
|
|
if (typeof jQuery === 'undefined') {
|
|
throw 'bootstrap-datetimepicker requires jQuery to be loaded first';
|
|
}
|
|
if (typeof moment === 'undefined') {
|
|
throw 'bootstrap-datetimepicker requires Moment.js to be loaded first';
|
|
}
|
|
factory(jQuery, moment);
|
|
}
|
|
}(function ($, moment) {
|
|
'use strict';
|
|
if (!moment) {
|
|
throw new Error('bootstrap-datetimepicker requires Moment.js to be loaded first');
|
|
}
|
|
|
|
var dateTimePicker = function (element, options) {
|
|
var picker = {},
|
|
date,
|
|
viewDate,
|
|
unset = true,
|
|
input,
|
|
component = false,
|
|
widget = false,
|
|
use24Hours,
|
|
minViewModeNumber = 0,
|
|
actualFormat,
|
|
parseFormats,
|
|
currentViewMode,
|
|
datePickerModes = [
|
|
{
|
|
clsName: 'days',
|
|
navFnc: 'M',
|
|
navStep: 1
|
|
},
|
|
{
|
|
clsName: 'months',
|
|
navFnc: 'y',
|
|
navStep: 1
|
|
},
|
|
{
|
|
clsName: 'years',
|
|
navFnc: 'y',
|
|
navStep: 10
|
|
},
|
|
{
|
|
clsName: 'decades',
|
|
navFnc: 'y',
|
|
navStep: 100
|
|
}
|
|
],
|
|
viewModes = ['days', 'months', 'years', 'decades'],
|
|
verticalModes = ['top', 'bottom', 'auto'],
|
|
horizontalModes = ['left', 'right', 'auto'],
|
|
toolbarPlacements = ['default', 'top', 'bottom'],
|
|
keyMap = {
|
|
'up': 38,
|
|
38: 'up',
|
|
'down': 40,
|
|
40: 'down',
|
|
'left': 37,
|
|
37: 'left',
|
|
'right': 39,
|
|
39: 'right',
|
|
'tab': 9,
|
|
9: 'tab',
|
|
'escape': 27,
|
|
27: 'escape',
|
|
'enter': 13,
|
|
13: 'enter',
|
|
'pageUp': 33,
|
|
33: 'pageUp',
|
|
'pageDown': 34,
|
|
34: 'pageDown',
|
|
'shift': 16,
|
|
16: 'shift',
|
|
'control': 17,
|
|
17: 'control',
|
|
'space': 32,
|
|
32: 'space',
|
|
't': 84,
|
|
84: 't',
|
|
'delete': 46,
|
|
46: 'delete'
|
|
},
|
|
keyState = {},
|
|
|
|
/********************************************************************************
|
|
*
|
|
* Private functions
|
|
*
|
|
********************************************************************************/
|
|
|
|
hasTimeZone = function () {
|
|
return moment.tz !== undefined && options.timeZone !== undefined && options.timeZone !== null && options.timeZone !== '';
|
|
},
|
|
|
|
getMoment = function (d) {
|
|
var returnMoment;
|
|
|
|
if (d === undefined || d === null) {
|
|
returnMoment = moment(); //TODO should this use format? and locale?
|
|
} else if (moment.isDate(d) || moment.isMoment(d)) {
|
|
// If the date that is passed in is already a Date() or moment() object,
|
|
// pass it directly to moment.
|
|
returnMoment = moment(d);
|
|
} else if (hasTimeZone()) { // There is a string to parse and a default time zone
|
|
// parse with the tz function which takes a default time zone if it is not in the format string
|
|
returnMoment = moment.tz(d, parseFormats, options.useStrict, options.timeZone);
|
|
} else {
|
|
returnMoment = moment(d, parseFormats, options.useStrict);
|
|
}
|
|
|
|
if (hasTimeZone()) {
|
|
returnMoment.tz(options.timeZone);
|
|
}
|
|
|
|
return returnMoment;
|
|
},
|
|
|
|
isEnabled = function (granularity) {
|
|
if (typeof granularity !== 'string' || granularity.length > 1) {
|
|
throw new TypeError('isEnabled expects a single character string parameter');
|
|
}
|
|
switch (granularity) {
|
|
case 'y':
|
|
return actualFormat.indexOf('Y') !== -1;
|
|
case 'M':
|
|
return actualFormat.indexOf('M') !== -1;
|
|
case 'd':
|
|
return actualFormat.toLowerCase().indexOf('d') !== -1;
|
|
case 'h':
|
|
case 'H':
|
|
return actualFormat.toLowerCase().indexOf('h') !== -1;
|
|
case 'm':
|
|
return actualFormat.indexOf('m') !== -1;
|
|
case 's':
|
|
return actualFormat.indexOf('s') !== -1;
|
|
default:
|
|
return false;
|
|
}
|
|
},
|
|
|
|
hasTime = function () {
|
|
return (isEnabled('h') || isEnabled('m') || isEnabled('s'));
|
|
},
|
|
|
|
hasDate = function () {
|
|
return (isEnabled('y') || isEnabled('M') || isEnabled('d'));
|
|
},
|
|
|
|
getDatePickerTemplate = function () {
|
|
var headTemplate = $('<thead>')
|
|
.append($('<tr>')
|
|
.append($('<th>').addClass('prev').attr('data-action', 'previous')
|
|
.append($('<span>').addClass(options.icons.previous))
|
|
)
|
|
.append($('<th>').addClass('picker-switch').attr('data-action', 'pickerSwitch').attr('colspan', (options.calendarWeeks ? '6' : '5')))
|
|
.append($('<th>').addClass('next').attr('data-action', 'next')
|
|
.append($('<span>').addClass(options.icons.next))
|
|
)
|
|
),
|
|
contTemplate = $('<tbody>')
|
|
.append($('<tr>')
|
|
.append($('<td>').attr('colspan', (options.calendarWeeks ? '8' : '7')))
|
|
);
|
|
|
|
return [
|
|
$('<div>').addClass('datepicker-days')
|
|
.append($('<table>').addClass('table-condensed')
|
|
.append(headTemplate)
|
|
.append($('<tbody>'))
|
|
),
|
|
$('<div>').addClass('datepicker-months')
|
|
.append($('<table>').addClass('table-condensed')
|
|
.append(headTemplate.clone())
|
|
.append(contTemplate.clone())
|
|
),
|
|
$('<div>').addClass('datepicker-years')
|
|
.append($('<table>').addClass('table-condensed')
|
|
.append(headTemplate.clone())
|
|
.append(contTemplate.clone())
|
|
),
|
|
$('<div>').addClass('datepicker-decades')
|
|
.append($('<table>').addClass('table-condensed')
|
|
.append(headTemplate.clone())
|
|
.append(contTemplate.clone())
|
|
)
|
|
];
|
|
},
|
|
|
|
getTimePickerMainTemplate = function () {
|
|
var topRow = $('<tr>'),
|
|
middleRow = $('<tr>'),
|
|
bottomRow = $('<tr>');
|
|
|
|
if (isEnabled('h')) {
|
|
topRow.append($('<td>')
|
|
.append($('<a>').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.incrementHour }).addClass('btn').attr('data-action', 'incrementHours').append($('<span>').addClass(options.icons.up))));
|
|
middleRow.append($('<td>')
|
|
.append($('<span>').addClass('timepicker-hour').attr({ 'data-time-component': 'hours', 'title': options.tooltips.pickHour }).attr('data-action', 'showHours')));
|
|
bottomRow.append($('<td>')
|
|
.append($('<a>').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.decrementHour }).addClass('btn').attr('data-action', 'decrementHours').append($('<span>').addClass(options.icons.down))));
|
|
}
|
|
if (isEnabled('m')) {
|
|
if (isEnabled('h')) {
|
|
topRow.append($('<td>').addClass('separator'));
|
|
middleRow.append($('<td>').addClass('separator').html(':'));
|
|
bottomRow.append($('<td>').addClass('separator'));
|
|
}
|
|
topRow.append($('<td>')
|
|
.append($('<a>').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.incrementMinute }).addClass('btn').attr('data-action', 'incrementMinutes')
|
|
.append($('<span>').addClass(options.icons.up))));
|
|
middleRow.append($('<td>')
|
|
.append($('<span>').addClass('timepicker-minute').attr({ 'data-time-component': 'minutes', 'title': options.tooltips.pickMinute }).attr('data-action', 'showMinutes')));
|
|
bottomRow.append($('<td>')
|
|
.append($('<a>').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.decrementMinute }).addClass('btn').attr('data-action', 'decrementMinutes')
|
|
.append($('<span>').addClass(options.icons.down))));
|
|
}
|
|
if (isEnabled('s')) {
|
|
if (isEnabled('m')) {
|
|
topRow.append($('<td>').addClass('separator'));
|
|
middleRow.append($('<td>').addClass('separator').html(':'));
|
|
bottomRow.append($('<td>').addClass('separator'));
|
|
}
|
|
topRow.append($('<td>')
|
|
.append($('<a>').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.incrementSecond }).addClass('btn').attr('data-action', 'incrementSeconds')
|
|
.append($('<span>').addClass(options.icons.up))));
|
|
middleRow.append($('<td>')
|
|
.append($('<span>').addClass('timepicker-second').attr({ 'data-time-component': 'seconds', 'title': options.tooltips.pickSecond }).attr('data-action', 'showSeconds')));
|
|
bottomRow.append($('<td>')
|
|
.append($('<a>').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.decrementSecond }).addClass('btn').attr('data-action', 'decrementSeconds')
|
|
.append($('<span>').addClass(options.icons.down))));
|
|
}
|
|
|
|
if (!use24Hours) {
|
|
topRow.append($('<td>').addClass('separator'));
|
|
middleRow.append($('<td>')
|
|
.append($('<button>').addClass('btn btn-primary').attr({ 'data-action': 'togglePeriod', tabindex: '-1', 'title': options.tooltips.togglePeriod })));
|
|
bottomRow.append($('<td>').addClass('separator'));
|
|
}
|
|
|
|
return $('<div>').addClass('timepicker-picker')
|
|
.append($('<table>').addClass('table-condensed')
|
|
.append([topRow, middleRow, bottomRow]));
|
|
},
|
|
|
|
getTimePickerTemplate = function () {
|
|
var hoursView = $('<div>').addClass('timepicker-hours')
|
|
.append($('<table>').addClass('table-condensed')),
|
|
minutesView = $('<div>').addClass('timepicker-minutes')
|
|
.append($('<table>').addClass('table-condensed')),
|
|
secondsView = $('<div>').addClass('timepicker-seconds')
|
|
.append($('<table>').addClass('table-condensed')),
|
|
ret = [getTimePickerMainTemplate()];
|
|
|
|
if (isEnabled('h')) {
|
|
ret.push(hoursView);
|
|
}
|
|
if (isEnabled('m')) {
|
|
ret.push(minutesView);
|
|
}
|
|
if (isEnabled('s')) {
|
|
ret.push(secondsView);
|
|
}
|
|
|
|
return ret;
|
|
},
|
|
|
|
getToolbar = function () {
|
|
var row = [];
|
|
if (options.showTodayButton) {
|
|
row.push($('<td>').append($('<a>').attr({ 'data-action': 'today', 'title': options.tooltips.today }).append($('<span>').addClass(options.icons.today))));
|
|
}
|
|
if (!options.sideBySide && hasDate() && hasTime()) {
|
|
row.push($('<td>').append($('<a>').attr({ 'data-action': 'togglePicker', 'title': options.tooltips.selectTime }).append($('<span>').addClass(options.icons.time))));
|
|
}
|
|
if (options.showClear) {
|
|
row.push($('<td>').append($('<a>').attr({ 'data-action': 'clear', 'title': options.tooltips.clear }).append($('<span>').addClass(options.icons.clear))));
|
|
}
|
|
if (options.showClose) {
|
|
row.push($('<td>').append($('<a>').attr({ 'data-action': 'close', 'title': options.tooltips.close }).append($('<span>').addClass(options.icons.close))));
|
|
}
|
|
return $('<table>').addClass('table-condensed').append($('<tbody>').append($('<tr>').append(row)));
|
|
},
|
|
|
|
getTemplate = function () {
|
|
var template = $('<div>').addClass('bootstrap-datetimepicker-widget dropdown-menu'),
|
|
dateView = $('<div>').addClass('datepicker').append(getDatePickerTemplate()),
|
|
timeView = $('<div>').addClass('timepicker').append(getTimePickerTemplate()),
|
|
content = $('<ul>').addClass('list-unstyled'),
|
|
toolbar = $('<li>').addClass('picker-switch' + (options.collapse ? ' accordion-toggle' : '')).append(getToolbar());
|
|
|
|
if (options.inline) {
|
|
template.removeClass('dropdown-menu');
|
|
}
|
|
|
|
if (use24Hours) {
|
|
template.addClass('usetwentyfour');
|
|
}
|
|
|
|
if (isEnabled('s') && !use24Hours) {
|
|
template.addClass('wider');
|
|
}
|
|
|
|
if (options.sideBySide && hasDate() && hasTime()) {
|
|
template.addClass('timepicker-sbs');
|
|
if (options.toolbarPlacement === 'top') {
|
|
template.append(toolbar);
|
|
}
|
|
template.append(
|
|
$('<div>').addClass('row')
|
|
.append(dateView.addClass('col-md-6'))
|
|
.append(timeView.addClass('col-md-6'))
|
|
);
|
|
if (options.toolbarPlacement === 'bottom') {
|
|
template.append(toolbar);
|
|
}
|
|
return template;
|
|
}
|
|
|
|
if (options.toolbarPlacement === 'top') {
|
|
content.append(toolbar);
|
|
}
|
|
if (hasDate()) {
|
|
content.append($('<li>').addClass((options.collapse && hasTime() ? 'collapse in' : '')).append(dateView));
|
|
}
|
|
if (options.toolbarPlacement === 'default') {
|
|
content.append(toolbar);
|
|
}
|
|
if (hasTime()) {
|
|
content.append($('<li>').addClass((options.collapse && hasDate() ? 'collapse' : '')).append(timeView));
|
|
}
|
|
if (options.toolbarPlacement === 'bottom') {
|
|
content.append(toolbar);
|
|
}
|
|
return template.append(content);
|
|
},
|
|
|
|
dataToOptions = function () {
|
|
var eData,
|
|
dataOptions = {};
|
|
|
|
if (element.is('input') || options.inline) {
|
|
eData = element.data();
|
|
} else {
|
|
eData = element.find('input').data();
|
|
}
|
|
|
|
if (eData.dateOptions && eData.dateOptions instanceof Object) {
|
|
dataOptions = $.extend(true, dataOptions, eData.dateOptions);
|
|
}
|
|
|
|
$.each(options, function (key) {
|
|
var attributeName = 'date' + key.charAt(0).toUpperCase() + key.slice(1);
|
|
if (eData[attributeName] !== undefined) {
|
|
dataOptions[key] = eData[attributeName];
|
|
}
|
|
});
|
|
return dataOptions;
|
|
},
|
|
|
|
place = function () {
|
|
var position = (component || element).position(),
|
|
offset = (component || element).offset(),
|
|
vertical = options.widgetPositioning.vertical,
|
|
horizontal = options.widgetPositioning.horizontal,
|
|
parent;
|
|
|
|
if (options.widgetParent) {
|
|
parent = options.widgetParent.append(widget);
|
|
} else if (element.is('input')) {
|
|
parent = element.after(widget).parent();
|
|
} else if (options.inline) {
|
|
parent = element.append(widget);
|
|
return;
|
|
} else {
|
|
parent = element;
|
|
element.children().first().after(widget);
|
|
}
|
|
|
|
// Top and bottom logic
|
|
if (vertical === 'auto') {
|
|
if (offset.top + widget.height() * 1.5 >= $(window).height() + $(window).scrollTop() &&
|
|
widget.height() + element.outerHeight() < offset.top) {
|
|
vertical = 'top';
|
|
} else {
|
|
vertical = 'bottom';
|
|
}
|
|
}
|
|
|
|
// Left and right logic
|
|
if (horizontal === 'auto') {
|
|
if (parent.width() < offset.left + widget.outerWidth() / 2 &&
|
|
offset.left + widget.outerWidth() > $(window).width()) {
|
|
horizontal = 'right';
|
|
} else {
|
|
horizontal = 'left';
|
|
}
|
|
}
|
|
|
|
if (vertical === 'top') {
|
|
widget.addClass('top').removeClass('bottom');
|
|
} else {
|
|
widget.addClass('bottom').removeClass('top');
|
|
}
|
|
|
|
if (horizontal === 'right') {
|
|
widget.addClass('pull-right');
|
|
} else {
|
|
widget.removeClass('pull-right');
|
|
}
|
|
|
|
// find the first parent element that has a non-static css positioning
|
|
if (parent.css('position') === 'static') {
|
|
parent = parent.parents().filter(function () {
|
|
return $(this).css('position') !== 'static';
|
|
}).first();
|
|
}
|
|
|
|
if (parent.length === 0) {
|
|
throw new Error('datetimepicker component should be placed within a non-static positioned container');
|
|
}
|
|
|
|
widget.css({
|
|
top: vertical === 'top' ? 'auto' : position.top + element.outerHeight(),
|
|
bottom: vertical === 'top' ? parent.outerHeight() - (parent === element ? 0 : position.top) : 'auto',
|
|
left: horizontal === 'left' ? (parent === element ? 0 : position.left) : 'auto',
|
|
right: horizontal === 'left' ? 'auto' : parent.outerWidth() - element.outerWidth() - (parent === element ? 0 : position.left)
|
|
});
|
|
},
|
|
|
|
notifyEvent = function (e) {
|
|
if (e.type === 'dp.change' && ((e.date && e.date.isSame(e.oldDate)) || (!e.date && !e.oldDate))) {
|
|
return;
|
|
}
|
|
element.trigger(e);
|
|
},
|
|
|
|
viewUpdate = function (e) {
|
|
if (e === 'y') {
|
|
e = 'YYYY';
|
|
}
|
|
notifyEvent({
|
|
type: 'dp.update',
|
|
change: e,
|
|
viewDate: viewDate.clone()
|
|
});
|
|
},
|
|
|
|
showMode = function (dir) {
|
|
if (!widget) {
|
|
return;
|
|
}
|
|
if (dir) {
|
|
currentViewMode = Math.max(minViewModeNumber, Math.min(3, currentViewMode + dir));
|
|
}
|
|
widget.find('.datepicker > div').hide().filter('.datepicker-' + datePickerModes[currentViewMode].clsName).show();
|
|
},
|
|
|
|
fillDow = function () {
|
|
var row = $('<tr>'),
|
|
currentDate = viewDate.clone().startOf('w').startOf('d');
|
|
|
|
if (options.calendarWeeks === true) {
|
|
row.append($('<th>').addClass('cw').text('#'));
|
|
}
|
|
|
|
while (currentDate.isBefore(viewDate.clone().endOf('w'))) {
|
|
row.append($('<th>').addClass('dow').text(currentDate.format('dd')));
|
|
currentDate.add(1, 'd');
|
|
}
|
|
widget.find('.datepicker-days thead').append(row);
|
|
},
|
|
|
|
isInDisabledDates = function (testDate) {
|
|
return options.disabledDates[testDate.format('YYYY-MM-DD')] === true;
|
|
},
|
|
|
|
isInEnabledDates = function (testDate) {
|
|
return options.enabledDates[testDate.format('YYYY-MM-DD')] === true;
|
|
},
|
|
|
|
isInDisabledHours = function (testDate) {
|
|
return options.disabledHours[testDate.format('H')] === true;
|
|
},
|
|
|
|
isInEnabledHours = function (testDate) {
|
|
return options.enabledHours[testDate.format('H')] === true;
|
|
},
|
|
|
|
isValid = function (targetMoment, granularity) {
|
|
if (!targetMoment.isValid()) {
|
|
return false;
|
|
}
|
|
if (options.disabledDates && granularity === 'd' && isInDisabledDates(targetMoment)) {
|
|
return false;
|
|
}
|
|
if (options.enabledDates && granularity === 'd' && !isInEnabledDates(targetMoment)) {
|
|
return false;
|
|
}
|
|
if (options.minDate && targetMoment.isBefore(options.minDate, granularity)) {
|
|
return false;
|
|
}
|
|
if (options.maxDate && targetMoment.isAfter(options.maxDate, granularity)) {
|
|
return false;
|
|
}
|
|
if (options.daysOfWeekDisabled && granularity === 'd' && options.daysOfWeekDisabled.indexOf(targetMoment.day()) !== -1) {
|
|
return false;
|
|
}
|
|
if (options.disabledHours && (granularity === 'h' || granularity === 'm' || granularity === 's') && isInDisabledHours(targetMoment)) {
|
|
return false;
|
|
}
|
|
if (options.enabledHours && (granularity === 'h' || granularity === 'm' || granularity === 's') && !isInEnabledHours(targetMoment)) {
|
|
return false;
|
|
}
|
|
if (options.disabledTimeIntervals && (granularity === 'h' || granularity === 'm' || granularity === 's')) {
|
|
var found = false;
|
|
$.each(options.disabledTimeIntervals, function () {
|
|
if (targetMoment.isBetween(this[0], this[1])) {
|
|
found = true;
|
|
return false;
|
|
}
|
|
});
|
|
if (found) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
},
|
|
|
|
fillMonths = function () {
|
|
var spans = [],
|
|
monthsShort = viewDate.clone().startOf('y').startOf('d');
|
|
while (monthsShort.isSame(viewDate, 'y')) {
|
|
spans.push($('<span>').attr('data-action', 'selectMonth').addClass('month').text(monthsShort.format('MMM')));
|
|
monthsShort.add(1, 'M');
|
|
}
|
|
widget.find('.datepicker-months td').empty().append(spans);
|
|
},
|
|
|
|
updateMonths = function () {
|
|
var monthsView = widget.find('.datepicker-months'),
|
|
monthsViewHeader = monthsView.find('th'),
|
|
months = monthsView.find('tbody').find('span');
|
|
|
|
monthsViewHeader.eq(0).find('span').attr('title', options.tooltips.prevYear);
|
|
monthsViewHeader.eq(1).attr('title', options.tooltips.selectYear);
|
|
monthsViewHeader.eq(2).find('span').attr('title', options.tooltips.nextYear);
|
|
|
|
monthsView.find('.disabled').removeClass('disabled');
|
|
|
|
if (!isValid(viewDate.clone().subtract(1, 'y'), 'y')) {
|
|
monthsViewHeader.eq(0).addClass('disabled');
|
|
}
|
|
|
|
monthsViewHeader.eq(1).text(viewDate.year());
|
|
|
|
if (!isValid(viewDate.clone().add(1, 'y'), 'y')) {
|
|
monthsViewHeader.eq(2).addClass('disabled');
|
|
}
|
|
|
|
months.removeClass('active');
|
|
if (date.isSame(viewDate, 'y') && !unset) {
|
|
months.eq(date.month()).addClass('active');
|
|
}
|
|
|
|
months.each(function (index) {
|
|
if (!isValid(viewDate.clone().month(index), 'M')) {
|
|
$(this).addClass('disabled');
|
|
}
|
|
});
|
|
},
|
|
|
|
updateYears = function () {
|
|
var yearsView = widget.find('.datepicker-years'),
|
|
yearsViewHeader = yearsView.find('th'),
|
|
startYear = viewDate.clone().subtract(5, 'y'),
|
|
endYear = viewDate.clone().add(6, 'y'),
|
|
html = '';
|
|
|
|
yearsViewHeader.eq(0).find('span').attr('title', options.tooltips.prevDecade);
|
|
yearsViewHeader.eq(1).attr('title', options.tooltips.selectDecade);
|
|
yearsViewHeader.eq(2).find('span').attr('title', options.tooltips.nextDecade);
|
|
|
|
yearsView.find('.disabled').removeClass('disabled');
|
|
|
|
if (options.minDate && options.minDate.isAfter(startYear, 'y')) {
|
|
yearsViewHeader.eq(0).addClass('disabled');
|
|
}
|
|
|
|
yearsViewHeader.eq(1).text(startYear.year() + '-' + endYear.year());
|
|
|
|
if (options.maxDate && options.maxDate.isBefore(endYear, 'y')) {
|
|
yearsViewHeader.eq(2).addClass('disabled');
|
|
}
|
|
|
|
while (!startYear.isAfter(endYear, 'y')) {
|
|
html += '<span data-action="selectYear" class="year' + (startYear.isSame(date, 'y') && !unset ? ' active' : '') + (!isValid(startYear, 'y') ? ' disabled' : '') + '">' + startYear.year() + '</span>';
|
|
startYear.add(1, 'y');
|
|
}
|
|
|
|
yearsView.find('td').html(html);
|
|
},
|
|
|
|
updateDecades = function () {
|
|
var decadesView = widget.find('.datepicker-decades'),
|
|
decadesViewHeader = decadesView.find('th'),
|
|
startDecade = moment({ y: viewDate.year() - (viewDate.year() % 100) - 1 }),
|
|
endDecade = startDecade.clone().add(100, 'y'),
|
|
startedAt = startDecade.clone(),
|
|
minDateDecade = false,
|
|
maxDateDecade = false,
|
|
endDecadeYear,
|
|
html = '';
|
|
|
|
decadesViewHeader.eq(0).find('span').attr('title', options.tooltips.prevCentury);
|
|
decadesViewHeader.eq(2).find('span').attr('title', options.tooltips.nextCentury);
|
|
|
|
decadesView.find('.disabled').removeClass('disabled');
|
|
|
|
if (startDecade.isSame(moment({ y: 1900 })) || (options.minDate && options.minDate.isAfter(startDecade, 'y'))) {
|
|
decadesViewHeader.eq(0).addClass('disabled');
|
|
}
|
|
|
|
decadesViewHeader.eq(1).text(startDecade.year() + '-' + endDecade.year());
|
|
|
|
if (startDecade.isSame(moment({ y: 2000 })) || (options.maxDate && options.maxDate.isBefore(endDecade, 'y'))) {
|
|
decadesViewHeader.eq(2).addClass('disabled');
|
|
}
|
|
|
|
while (!startDecade.isAfter(endDecade, 'y')) {
|
|
endDecadeYear = startDecade.year() + 12;
|
|
minDateDecade = options.minDate && options.minDate.isAfter(startDecade, 'y') && options.minDate.year() <= endDecadeYear;
|
|
maxDateDecade = options.maxDate && options.maxDate.isAfter(startDecade, 'y') && options.maxDate.year() <= endDecadeYear;
|
|
html += '<span data-action="selectDecade" class="decade' + (date.isAfter(startDecade) && date.year() <= endDecadeYear ? ' active' : '') +
|
|
(!isValid(startDecade, 'y') && !minDateDecade && !maxDateDecade ? ' disabled' : '') + '" data-selection="' + (startDecade.year() + 6) + '">' + (startDecade.year() + 1) + ' - ' + (startDecade.year() + 12) + '</span>';
|
|
startDecade.add(12, 'y');
|
|
}
|
|
html += '<span></span><span></span><span></span>'; //push the dangling block over, at least this way it's even
|
|
|
|
decadesView.find('td').html(html);
|
|
decadesViewHeader.eq(1).text((startedAt.year() + 1) + '-' + (startDecade.year()));
|
|
},
|
|
|
|
fillDate = function () {
|
|
var daysView = widget.find('.datepicker-days'),
|
|
daysViewHeader = daysView.find('th'),
|
|
currentDate,
|
|
html = [],
|
|
row,
|
|
clsNames = [],
|
|
i;
|
|
|
|
if (!hasDate()) {
|
|
return;
|
|
}
|
|
|
|
daysViewHeader.eq(0).find('span').attr('title', options.tooltips.prevMonth);
|
|
daysViewHeader.eq(1).attr('title', options.tooltips.selectMonth);
|
|
daysViewHeader.eq(2).find('span').attr('title', options.tooltips.nextMonth);
|
|
|
|
daysView.find('.disabled').removeClass('disabled');
|
|
daysViewHeader.eq(1).text(viewDate.format(options.dayViewHeaderFormat));
|
|
|
|
if (!isValid(viewDate.clone().subtract(1, 'M'), 'M')) {
|
|
daysViewHeader.eq(0).addClass('disabled');
|
|
}
|
|
if (!isValid(viewDate.clone().add(1, 'M'), 'M')) {
|
|
daysViewHeader.eq(2).addClass('disabled');
|
|
}
|
|
|
|
currentDate = viewDate.clone().startOf('M').startOf('w').startOf('d');
|
|
|
|
for (i = 0; i < 42; i++) { //always display 42 days (should show 6 weeks)
|
|
if (currentDate.weekday() === 0) {
|
|
row = $('<tr>');
|
|
if (options.calendarWeeks) {
|
|
row.append('<td class="cw">' + currentDate.week() + '</td>');
|
|
}
|
|
html.push(row);
|
|
}
|
|
clsNames = ['day'];
|
|
if (currentDate.isBefore(viewDate, 'M')) {
|
|
clsNames.push('old');
|
|
}
|
|
if (currentDate.isAfter(viewDate, 'M')) {
|
|
clsNames.push('new');
|
|
}
|
|
if (currentDate.isSame(date, 'd') && !unset) {
|
|
clsNames.push('active');
|
|
}
|
|
if (!isValid(currentDate, 'd')) {
|
|
clsNames.push('disabled');
|
|
}
|
|
if (currentDate.isSame(getMoment(), 'd')) {
|
|
clsNames.push('today');
|
|
}
|
|
if (currentDate.day() === 0 || currentDate.day() === 6) {
|
|
clsNames.push('weekend');
|
|
}
|
|
notifyEvent({
|
|
type: 'dp.classify',
|
|
date: currentDate,
|
|
classNames: clsNames
|
|
});
|
|
row.append('<td data-action="selectDay" data-day="' + currentDate.format('L') + '" class="' + clsNames.join(' ') + '">' + currentDate.date() + '</td>');
|
|
currentDate.add(1, 'd');
|
|
}
|
|
|
|
daysView.find('tbody').empty().append(html);
|
|
|
|
updateMonths();
|
|
|
|
updateYears();
|
|
|
|
updateDecades();
|
|
},
|
|
|
|
fillHours = function () {
|
|
var table = widget.find('.timepicker-hours table'),
|
|
currentHour = viewDate.clone().startOf('d'),
|
|
html = [],
|
|
row = $('<tr>');
|
|
|
|
if (viewDate.hour() > 11 && !use24Hours) {
|
|
currentHour.hour(12);
|
|
}
|
|
while (currentHour.isSame(viewDate, 'd') && (use24Hours || (viewDate.hour() < 12 && currentHour.hour() < 12) || viewDate.hour() > 11)) {
|
|
if (currentHour.hour() % 4 === 0) {
|
|
row = $('<tr>');
|
|
html.push(row);
|
|
}
|
|
row.append('<td data-action="selectHour" class="hour' + (!isValid(currentHour, 'h') ? ' disabled' : '') + '">' + currentHour.format(use24Hours ? 'HH' : 'hh') + '</td>');
|
|
currentHour.add(1, 'h');
|
|
}
|
|
table.empty().append(html);
|
|
},
|
|
|
|
fillMinutes = function () {
|
|
var table = widget.find('.timepicker-minutes table'),
|
|
currentMinute = viewDate.clone().startOf('h'),
|
|
html = [],
|
|
row = $('<tr>'),
|
|
step = options.stepping === 1 ? 5 : options.stepping;
|
|
|
|
while (viewDate.isSame(currentMinute, 'h')) {
|
|
if (currentMinute.minute() % (step * 4) === 0) {
|
|
row = $('<tr>');
|
|
html.push(row);
|
|
}
|
|
row.append('<td data-action="selectMinute" class="minute' + (!isValid(currentMinute, 'm') ? ' disabled' : '') + '">' + currentMinute.format('mm') + '</td>');
|
|
currentMinute.add(step, 'm');
|
|
}
|
|
table.empty().append(html);
|
|
},
|
|
|
|
fillSeconds = function () {
|
|
var table = widget.find('.timepicker-seconds table'),
|
|
currentSecond = viewDate.clone().startOf('m'),
|
|
html = [],
|
|
row = $('<tr>');
|
|
|
|
while (viewDate.isSame(currentSecond, 'm')) {
|
|
if (currentSecond.second() % 20 === 0) {
|
|
row = $('<tr>');
|
|
html.push(row);
|
|
}
|
|
row.append('<td data-action="selectSecond" class="second' + (!isValid(currentSecond, 's') ? ' disabled' : '') + '">' + currentSecond.format('ss') + '</td>');
|
|
currentSecond.add(5, 's');
|
|
}
|
|
|
|
table.empty().append(html);
|
|
},
|
|
|
|
fillTime = function () {
|
|
var toggle, newDate, timeComponents = widget.find('.timepicker span[data-time-component]');
|
|
|
|
if (!use24Hours) {
|
|
toggle = widget.find('.timepicker [data-action=togglePeriod]');
|
|
newDate = date.clone().add((date.hours() >= 12) ? -12 : 12, 'h');
|
|
|
|
toggle.text(date.format('A'));
|
|
|
|
if (isValid(newDate, 'h')) {
|
|
toggle.removeClass('disabled');
|
|
} else {
|
|
toggle.addClass('disabled');
|
|
}
|
|
}
|
|
timeComponents.filter('[data-time-component=hours]').text(date.format(use24Hours ? 'HH' : 'hh'));
|
|
timeComponents.filter('[data-time-component=minutes]').text(date.format('mm'));
|
|
timeComponents.filter('[data-time-component=seconds]').text(date.format('ss'));
|
|
|
|
fillHours();
|
|
fillMinutes();
|
|
fillSeconds();
|
|
},
|
|
|
|
update = function () {
|
|
if (!widget) {
|
|
return;
|
|
}
|
|
fillDate();
|
|
fillTime();
|
|
},
|
|
|
|
setValue = function (targetMoment) {
|
|
var oldDate = unset ? null : date;
|
|
|
|
// case of calling setValue(null or false)
|
|
if (!targetMoment) {
|
|
unset = true;
|
|
input.val('');
|
|
element.data('date', '');
|
|
notifyEvent({
|
|
type: 'dp.change',
|
|
date: false,
|
|
oldDate: oldDate
|
|
});
|
|
update();
|
|
return;
|
|
}
|
|
|
|
targetMoment = targetMoment.clone().locale(options.locale);
|
|
|
|
if (hasTimeZone()) {
|
|
targetMoment.tz(options.timeZone);
|
|
}
|
|
|
|
if (options.stepping !== 1) {
|
|
targetMoment.minutes((Math.round(targetMoment.minutes() / options.stepping) * options.stepping)).seconds(0);
|
|
|
|
while (options.minDate && targetMoment.isBefore(options.minDate)) {
|
|
targetMoment.add(options.stepping, 'minutes');
|
|
}
|
|
}
|
|
|
|
if (isValid(targetMoment)) {
|
|
date = targetMoment;
|
|
viewDate = date.clone();
|
|
input.val(date.format(actualFormat));
|
|
element.data('date', date.format(actualFormat));
|
|
unset = false;
|
|
update();
|
|
notifyEvent({
|
|
type: 'dp.change',
|
|
date: date.clone(),
|
|
oldDate: oldDate
|
|
});
|
|
} else {
|
|
if (!options.keepInvalid) {
|
|
input.val(unset ? '' : date.format(actualFormat));
|
|
} else {
|
|
notifyEvent({
|
|
type: 'dp.change',
|
|
date: targetMoment,
|
|
oldDate: oldDate
|
|
});
|
|
}
|
|
notifyEvent({
|
|
type: 'dp.error',
|
|
date: targetMoment,
|
|
oldDate: oldDate
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Hides the widget. Possibly will emit dp.hide
|
|
*/
|
|
hide = function () {
|
|
var transitioning = false;
|
|
if (!widget) {
|
|
return picker;
|
|
}
|
|
// Ignore event if in the middle of a picker transition
|
|
widget.find('.collapse').each(function () {
|
|
var collapseData = $(this).data('collapse');
|
|
if (collapseData && collapseData.transitioning) {
|
|
transitioning = true;
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
if (transitioning) {
|
|
return picker;
|
|
}
|
|
if (component && component.hasClass('btn')) {
|
|
component.toggleClass('active');
|
|
}
|
|
widget.hide();
|
|
|
|
$(window).off('resize', place);
|
|
widget.off('click', '[data-action]');
|
|
widget.off('mousedown', false);
|
|
|
|
widget.remove();
|
|
widget = false;
|
|
|
|
notifyEvent({
|
|
type: 'dp.hide',
|
|
date: date.clone()
|
|
});
|
|
|
|
input.blur();
|
|
|
|
viewDate = date.clone();
|
|
|
|
return picker;
|
|
},
|
|
|
|
clear = function () {
|
|
setValue(null);
|
|
},
|
|
|
|
parseInputDate = function (inputDate) {
|
|
if (options.parseInputDate === undefined) {
|
|
if (!moment.isMoment(inputDate) || inputDate instanceof Date) {
|
|
inputDate = getMoment(inputDate);
|
|
}
|
|
} else {
|
|
inputDate = options.parseInputDate(inputDate);
|
|
}
|
|
//inputDate.locale(options.locale);
|
|
return inputDate;
|
|
},
|
|
|
|
/********************************************************************************
|
|
*
|
|
* Widget UI interaction functions
|
|
*
|
|
********************************************************************************/
|
|
actions = {
|
|
next: function () {
|
|
var navFnc = datePickerModes[currentViewMode].navFnc;
|
|
viewDate.add(datePickerModes[currentViewMode].navStep, navFnc);
|
|
fillDate();
|
|
viewUpdate(navFnc);
|
|
},
|
|
|
|
previous: function () {
|
|
var navFnc = datePickerModes[currentViewMode].navFnc;
|
|
viewDate.subtract(datePickerModes[currentViewMode].navStep, navFnc);
|
|
fillDate();
|
|
viewUpdate(navFnc);
|
|
},
|
|
|
|
pickerSwitch: function () {
|
|
showMode(1);
|
|
},
|
|
|
|
selectMonth: function (e) {
|
|
var month = $(e.target).closest('tbody').find('span').index($(e.target));
|
|
viewDate.month(month);
|
|
if (currentViewMode === minViewModeNumber) {
|
|
setValue(date.clone().year(viewDate.year()).month(viewDate.month()));
|
|
if (!options.inline) {
|
|
hide();
|
|
}
|
|
} else {
|
|
showMode(-1);
|
|
fillDate();
|
|
}
|
|
viewUpdate('M');
|
|
},
|
|
|
|
selectYear: function (e) {
|
|
var year = parseInt($(e.target).text(), 10) || 0;
|
|
viewDate.year(year);
|
|
if (currentViewMode === minViewModeNumber) {
|
|
setValue(date.clone().year(viewDate.year()));
|
|
if (!options.inline) {
|
|
hide();
|
|
}
|
|
} else {
|
|
showMode(-1);
|
|
fillDate();
|
|
}
|
|
viewUpdate('YYYY');
|
|
},
|
|
|
|
selectDecade: function (e) {
|
|
var year = parseInt($(e.target).data('selection'), 10) || 0;
|
|
viewDate.year(year);
|
|
if (currentViewMode === minViewModeNumber) {
|
|
setValue(date.clone().year(viewDate.year()));
|
|
if (!options.inline) {
|
|
hide();
|
|
}
|
|
} else {
|
|
showMode(-1);
|
|
fillDate();
|
|
}
|
|
viewUpdate('YYYY');
|
|
},
|
|
|
|
selectDay: function (e) {
|
|
var day = viewDate.clone();
|
|
if ($(e.target).is('.old')) {
|
|
day.subtract(1, 'M');
|
|
}
|
|
if ($(e.target).is('.new')) {
|
|
day.add(1, 'M');
|
|
}
|
|
setValue(day.date(parseInt($(e.target).text(), 10)));
|
|
if (!hasTime() && !options.keepOpen && !options.inline) {
|
|
hide();
|
|
}
|
|
},
|
|
|
|
incrementHours: function () {
|
|
var newDate = date.clone().add(1, 'h');
|
|
if (isValid(newDate, 'h')) {
|
|
setValue(newDate);
|
|
}
|
|
},
|
|
|
|
incrementMinutes: function () {
|
|
var newDate = date.clone().add(options.stepping, 'm');
|
|
if (isValid(newDate, 'm')) {
|
|
setValue(newDate);
|
|
}
|
|
},
|
|
|
|
incrementSeconds: function () {
|
|
var newDate = date.clone().add(1, 's');
|
|
if (isValid(newDate, 's')) {
|
|
setValue(newDate);
|
|
}
|
|
},
|
|
|
|
decrementHours: function () {
|
|
var newDate = date.clone().subtract(1, 'h');
|
|
if (isValid(newDate, 'h')) {
|
|
setValue(newDate);
|
|
}
|
|
},
|
|
|
|
decrementMinutes: function () {
|
|
var newDate = date.clone().subtract(options.stepping, 'm');
|
|
if (isValid(newDate, 'm')) {
|
|
setValue(newDate);
|
|
}
|
|
},
|
|
|
|
decrementSeconds: function () {
|
|
var newDate = date.clone().subtract(1, 's');
|
|
if (isValid(newDate, 's')) {
|
|
setValue(newDate);
|
|
}
|
|
},
|
|
|
|
togglePeriod: function () {
|
|
setValue(date.clone().add((date.hours() >= 12) ? -12 : 12, 'h'));
|
|
},
|
|
|
|
togglePicker: function (e) {
|
|
var $this = $(e.target),
|
|
$parent = $this.closest('ul'),
|
|
expanded = $parent.find('.in'),
|
|
closed = $parent.find('.collapse:not(.in)'),
|
|
collapseData;
|
|
|
|
if (expanded && expanded.length) {
|
|
collapseData = expanded.data('collapse');
|
|
if (collapseData && collapseData.transitioning) {
|
|
return;
|
|
}
|
|
if (expanded.collapse) { // if collapse plugin is available through bootstrap.js then use it
|
|
expanded.collapse('hide');
|
|
closed.collapse('show');
|
|
} else { // otherwise just toggle in class on the two views
|
|
expanded.removeClass('in');
|
|
closed.addClass('in');
|
|
}
|
|
if ($this.is('span')) {
|
|
$this.toggleClass(options.icons.time + ' ' + options.icons.date);
|
|
} else {
|
|
$this.find('span').toggleClass(options.icons.time + ' ' + options.icons.date);
|
|
}
|
|
|
|
// NOTE: uncomment if toggled state will be restored in show()
|
|
//if (component) {
|
|
// component.find('span').toggleClass(options.icons.time + ' ' + options.icons.date);
|
|
//}
|
|
}
|
|
},
|
|
|
|
showPicker: function () {
|
|
widget.find('.timepicker > div:not(.timepicker-picker)').hide();
|
|
widget.find('.timepicker .timepicker-picker').show();
|
|
},
|
|
|
|
showHours: function () {
|
|
widget.find('.timepicker .timepicker-picker').hide();
|
|
widget.find('.timepicker .timepicker-hours').show();
|
|
},
|
|
|
|
showMinutes: function () {
|
|
widget.find('.timepicker .timepicker-picker').hide();
|
|
widget.find('.timepicker .timepicker-minutes').show();
|
|
},
|
|
|
|
showSeconds: function () {
|
|
widget.find('.timepicker .timepicker-picker').hide();
|
|
widget.find('.timepicker .timepicker-seconds').show();
|
|
},
|
|
|
|
selectHour: function (e) {
|
|
var hour = parseInt($(e.target).text(), 10);
|
|
|
|
if (!use24Hours) {
|
|
if (date.hours() >= 12) {
|
|
if (hour !== 12) {
|
|
hour += 12;
|
|
}
|
|
} else {
|
|
if (hour === 12) {
|
|
hour = 0;
|
|
}
|
|
}
|
|
}
|
|
setValue(date.clone().hours(hour));
|
|
actions.showPicker.call(picker);
|
|
},
|
|
|
|
selectMinute: function (e) {
|
|
setValue(date.clone().minutes(parseInt($(e.target).text(), 10)));
|
|
actions.showPicker.call(picker);
|
|
},
|
|
|
|
selectSecond: function (e) {
|
|
setValue(date.clone().seconds(parseInt($(e.target).text(), 10)));
|
|
actions.showPicker.call(picker);
|
|
},
|
|
|
|
clear: clear,
|
|
|
|
today: function () {
|
|
var todaysDate = getMoment();
|
|
if (isValid(todaysDate, 'd')) {
|
|
setValue(todaysDate);
|
|
}
|
|
},
|
|
|
|
close: hide
|
|
},
|
|
|
|
doAction = function (e) {
|
|
if ($(e.currentTarget).is('.disabled')) {
|
|
return false;
|
|
}
|
|
actions[$(e.currentTarget).data('action')].apply(picker, arguments);
|
|
return false;
|
|
},
|
|
|
|
/**
|
|
* Shows the widget. Possibly will emit dp.show and dp.change
|
|
*/
|
|
show = function () {
|
|
var currentMoment,
|
|
useCurrentGranularity = {
|
|
'year': function (m) {
|
|
return m.month(0).date(1).hours(0).seconds(0).minutes(0);
|
|
},
|
|
'month': function (m) {
|
|
return m.date(1).hours(0).seconds(0).minutes(0);
|
|
},
|
|
'day': function (m) {
|
|
return m.hours(0).seconds(0).minutes(0);
|
|
},
|
|
'hour': function (m) {
|
|
return m.seconds(0).minutes(0);
|
|
},
|
|
'minute': function (m) {
|
|
return m.seconds(0);
|
|
}
|
|
};
|
|
|
|
if (input.prop('disabled') || (!options.ignoreReadonly && input.prop('readonly')) || widget) {
|
|
return picker;
|
|
}
|
|
if (input.val() !== undefined && input.val().trim().length !== 0) {
|
|
setValue(parseInputDate(input.val().trim()));
|
|
} else if (unset && options.useCurrent && (options.inline || (input.is('input') && input.val().trim().length === 0))) {
|
|
currentMoment = getMoment();
|
|
if (typeof options.useCurrent === 'string') {
|
|
currentMoment = useCurrentGranularity[options.useCurrent](currentMoment);
|
|
}
|
|
setValue(currentMoment);
|
|
}
|
|
widget = getTemplate();
|
|
|
|
fillDow();
|
|
fillMonths();
|
|
|
|
widget.find('.timepicker-hours').hide();
|
|
widget.find('.timepicker-minutes').hide();
|
|
widget.find('.timepicker-seconds').hide();
|
|
|
|
update();
|
|
showMode();
|
|
|
|
$(window).on('resize', place);
|
|
widget.on('click', '[data-action]', doAction); // this handles clicks on the widget
|
|
widget.on('mousedown', false);
|
|
|
|
if (component && component.hasClass('btn')) {
|
|
component.toggleClass('active');
|
|
}
|
|
place();
|
|
widget.show();
|
|
if (options.focusOnShow && !input.is(':focus')) {
|
|
input.focus();
|
|
}
|
|
|
|
notifyEvent({
|
|
type: 'dp.show'
|
|
});
|
|
return picker;
|
|
},
|
|
|
|
/**
|
|
* Shows or hides the widget
|
|
*/
|
|
toggle = function () {
|
|
return (widget ? hide() : show());
|
|
},
|
|
|
|
keydown = function (e) {
|
|
var handler = null,
|
|
index,
|
|
index2,
|
|
pressedKeys = [],
|
|
pressedModifiers = {},
|
|
currentKey = e.which,
|
|
keyBindKeys,
|
|
allModifiersPressed,
|
|
pressed = 'p';
|
|
|
|
keyState[currentKey] = pressed;
|
|
|
|
for (index in keyState) {
|
|
if (keyState.hasOwnProperty(index) && keyState[index] === pressed) {
|
|
pressedKeys.push(index);
|
|
if (parseInt(index, 10) !== currentKey) {
|
|
pressedModifiers[index] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (index in options.keyBinds) {
|
|
if (options.keyBinds.hasOwnProperty(index) && typeof (options.keyBinds[index]) === 'function') {
|
|
keyBindKeys = index.split(' ');
|
|
if (keyBindKeys.length === pressedKeys.length && keyMap[currentKey] === keyBindKeys[keyBindKeys.length - 1]) {
|
|
allModifiersPressed = true;
|
|
for (index2 = keyBindKeys.length - 2; index2 >= 0; index2--) {
|
|
if (!(keyMap[keyBindKeys[index2]] in pressedModifiers)) {
|
|
allModifiersPressed = false;
|
|
break;
|
|
}
|
|
}
|
|
if (allModifiersPressed) {
|
|
handler = options.keyBinds[index];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (handler) {
|
|
handler.call(picker, widget);
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
}
|
|
},
|
|
|
|
keyup = function (e) {
|
|
keyState[e.which] = 'r';
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
},
|
|
|
|
change = function (e) {
|
|
var val = $(e.target).val().trim(),
|
|
parsedDate = val ? parseInputDate(val) : null;
|
|
setValue(parsedDate);
|
|
e.stopImmediatePropagation();
|
|
return false;
|
|
},
|
|
|
|
attachDatePickerElementEvents = function () {
|
|
input.on({
|
|
'change': change,
|
|
'blur': options.debug ? '' : hide,
|
|
'keydown': keydown,
|
|
'keyup': keyup,
|
|
'focus': options.allowInputToggle ? show : ''
|
|
});
|
|
|
|
if (element.is('input')) {
|
|
input.on({
|
|
'focus': show
|
|
});
|
|
} else if (component) {
|
|
component.on('click', toggle);
|
|
component.on('mousedown', false);
|
|
}
|
|
},
|
|
|
|
detachDatePickerElementEvents = function () {
|
|
input.off({
|
|
'change': change,
|
|
'blur': blur,
|
|
'keydown': keydown,
|
|
'keyup': keyup,
|
|
'focus': options.allowInputToggle ? hide : ''
|
|
});
|
|
|
|
if (element.is('input')) {
|
|
input.off({
|
|
'focus': show
|
|
});
|
|
} else if (component) {
|
|
component.off('click', toggle);
|
|
component.off('mousedown', false);
|
|
}
|
|
},
|
|
|
|
indexGivenDates = function (givenDatesArray) {
|
|
// Store given enabledDates and disabledDates as keys.
|
|
// This way we can check their existence in O(1) time instead of looping through whole array.
|
|
// (for example: options.enabledDates['2014-02-27'] === true)
|
|
var givenDatesIndexed = {};
|
|
$.each(givenDatesArray, function () {
|
|
var dDate = parseInputDate(this);
|
|
if (dDate.isValid()) {
|
|
givenDatesIndexed[dDate.format('YYYY-MM-DD')] = true;
|
|
}
|
|
});
|
|
return (Object.keys(givenDatesIndexed).length) ? givenDatesIndexed : false;
|
|
},
|
|
|
|
indexGivenHours = function (givenHoursArray) {
|
|
// Store given enabledHours and disabledHours as keys.
|
|
// This way we can check their existence in O(1) time instead of looping through whole array.
|
|
// (for example: options.enabledHours['2014-02-27'] === true)
|
|
var givenHoursIndexed = {};
|
|
$.each(givenHoursArray, function () {
|
|
givenHoursIndexed[this] = true;
|
|
});
|
|
return (Object.keys(givenHoursIndexed).length) ? givenHoursIndexed : false;
|
|
},
|
|
|
|
initFormatting = function () {
|
|
var format = options.format || 'L LT';
|
|
|
|
actualFormat = format.replace(/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, function (formatInput) {
|
|
var newinput = date.localeData().longDateFormat(formatInput) || formatInput;
|
|
return newinput.replace(/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, function (formatInput2) { //temp fix for #740
|
|
return date.localeData().longDateFormat(formatInput2) || formatInput2;
|
|
});
|
|
});
|
|
|
|
|
|
parseFormats = options.extraFormats ? options.extraFormats.slice() : [];
|
|
if (parseFormats.indexOf(format) < 0 && parseFormats.indexOf(actualFormat) < 0) {
|
|
parseFormats.push(actualFormat);
|
|
}
|
|
|
|
use24Hours = (actualFormat.toLowerCase().indexOf('a') < 1 && actualFormat.replace(/\[.*?\]/g, '').indexOf('h') < 1);
|
|
|
|
if (isEnabled('y')) {
|
|
minViewModeNumber = 2;
|
|
}
|
|
if (isEnabled('M')) {
|
|
minViewModeNumber = 1;
|
|
}
|
|
if (isEnabled('d')) {
|
|
minViewModeNumber = 0;
|
|
}
|
|
|
|
currentViewMode = Math.max(minViewModeNumber, currentViewMode);
|
|
|
|
if (!unset) {
|
|
setValue(date);
|
|
}
|
|
};
|
|
|
|
/********************************************************************************
|
|
*
|
|
* Public API functions
|
|
* =====================
|
|
*
|
|
* Important: Do not expose direct references to private objects or the options
|
|
* object to the outer world. Always return a clone when returning values or make
|
|
* a clone when setting a private variable.
|
|
*
|
|
********************************************************************************/
|
|
picker.destroy = function () {
|
|
///<summary>Destroys the widget and removes all attached event listeners</summary>
|
|
hide();
|
|
detachDatePickerElementEvents();
|
|
element.removeData('DateTimePicker');
|
|
element.removeData('date');
|
|
};
|
|
|
|
picker.toggle = toggle;
|
|
|
|
picker.show = show;
|
|
|
|
picker.hide = hide;
|
|
|
|
picker.disable = function () {
|
|
///<summary>Disables the input element, the component is attached to, by adding a disabled="true" attribute to it.
|
|
///If the widget was visible before that call it is hidden. Possibly emits dp.hide</summary>
|
|
hide();
|
|
if (component && component.hasClass('btn')) {
|
|
component.addClass('disabled');
|
|
}
|
|
input.prop('disabled', true);
|
|
return picker;
|
|
};
|
|
|
|
picker.enable = function () {
|
|
///<summary>Enables the input element, the component is attached to, by removing disabled attribute from it.</summary>
|
|
if (component && component.hasClass('btn')) {
|
|
component.removeClass('disabled');
|
|
}
|
|
input.prop('disabled', false);
|
|
return picker;
|
|
};
|
|
|
|
picker.ignoreReadonly = function (ignoreReadonly) {
|
|
if (arguments.length === 0) {
|
|
return options.ignoreReadonly;
|
|
}
|
|
if (typeof ignoreReadonly !== 'boolean') {
|
|
throw new TypeError('ignoreReadonly () expects a boolean parameter');
|
|
}
|
|
options.ignoreReadonly = ignoreReadonly;
|
|
return picker;
|
|
};
|
|
|
|
picker.options = function (newOptions) {
|
|
if (arguments.length === 0) {
|
|
return $.extend(true, {}, options);
|
|
}
|
|
|
|
if (!(newOptions instanceof Object)) {
|
|
throw new TypeError('options() options parameter should be an object');
|
|
}
|
|
$.extend(true, options, newOptions);
|
|
$.each(options, function (key, value) {
|
|
if (picker[key] !== undefined) {
|
|
picker[key](value);
|
|
} else {
|
|
throw new TypeError('option ' + key + ' is not recognized!');
|
|
}
|
|
});
|
|
return picker;
|
|
};
|
|
|
|
picker.date = function (newDate) {
|
|
///<signature helpKeyword="$.fn.datetimepicker.date">
|
|
///<summary>Returns the component's model current date, a moment object or null if not set.</summary>
|
|
///<returns type="Moment">date.clone()</returns>
|
|
///</signature>
|
|
///<signature>
|
|
///<summary>Sets the components model current moment to it. Passing a null value unsets the components model current moment. Parsing of the newDate parameter is made using moment library with the options.format and options.useStrict components configuration.</summary>
|
|
///<param name="newDate" locid="$.fn.datetimepicker.date_p:newDate">Takes string, Date, moment, null parameter.</param>
|
|
///</signature>
|
|
if (arguments.length === 0) {
|
|
if (unset) {
|
|
return null;
|
|
}
|
|
return date.clone();
|
|
}
|
|
|
|
if (newDate !== null && typeof newDate !== 'string' && !moment.isMoment(newDate) && !(newDate instanceof Date)) {
|
|
throw new TypeError('date() parameter must be one of [null, string, moment or Date]');
|
|
}
|
|
|
|
setValue(newDate === null ? null : parseInputDate(newDate));
|
|
return picker;
|
|
};
|
|
|
|
picker.format = function (newFormat) {
|
|
///<summary>test su</summary>
|
|
///<param name="newFormat">info about para</param>
|
|
///<returns type="string|boolean">returns foo</returns>
|
|
if (arguments.length === 0) {
|
|
return options.format;
|
|
}
|
|
|
|
if ((typeof newFormat !== 'string') && ((typeof newFormat !== 'boolean') || (newFormat !== false))) {
|
|
throw new TypeError('format() expects a string or boolean:false parameter ' + newFormat);
|
|
}
|
|
|
|
options.format = newFormat;
|
|
if (actualFormat) {
|
|
initFormatting(); // reinit formatting
|
|
}
|
|
return picker;
|
|
};
|
|
|
|
picker.timeZone = function (newZone) {
|
|
if (arguments.length === 0) {
|
|
return options.timeZone;
|
|
}
|
|
|
|
if (typeof newZone !== 'string') {
|
|
throw new TypeError('newZone() expects a string parameter');
|
|
}
|
|
|
|
options.timeZone = newZone;
|
|
|
|
return picker;
|
|
};
|
|
|
|
picker.dayViewHeaderFormat = function (newFormat) {
|
|
if (arguments.length === 0) {
|
|
return options.dayViewHeaderFormat;
|
|
}
|
|
|
|
if (typeof newFormat !== 'string') {
|
|
throw new TypeError('dayViewHeaderFormat() expects a string parameter');
|
|
}
|
|
|
|
options.dayViewHeaderFormat = newFormat;
|
|
return picker;
|
|
};
|
|
|
|
picker.extraFormats = function (formats) {
|
|
if (arguments.length === 0) {
|
|
return options.extraFormats;
|
|
}
|
|
|
|
if (formats !== false && !(formats instanceof Array)) {
|
|
throw new TypeError('extraFormats() expects an array or false parameter');
|
|
}
|
|
|
|
options.extraFormats = formats;
|
|
if (parseFormats) {
|
|
initFormatting(); // reinit formatting
|
|
}
|
|
return picker;
|
|
};
|
|
|
|
picker.disabledDates = function (dates) {
|
|
///<signature helpKeyword="$.fn.datetimepicker.disabledDates">
|
|
///<summary>Returns an array with the currently set disabled dates on the component.</summary>
|
|
///<returns type="array">options.disabledDates</returns>
|
|
///</signature>
|
|
///<signature>
|
|
///<summary>Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of
|
|
///options.enabledDates if such exist.</summary>
|
|
///<param name="dates" locid="$.fn.datetimepicker.disabledDates_p:dates">Takes an [ string or Date or moment ] of values and allows the user to select only from those days.</param>
|
|
///</signature>
|
|
if (arguments.length === 0) {
|
|
return (options.disabledDates ? $.extend({}, options.disabledDates) : options.disabledDates);
|
|
}
|
|
|
|
if (!dates) {
|
|
options.disabledDates = false;
|
|
update();
|
|
return picker;
|
|
}
|
|
if (!(dates instanceof Array)) {
|
|
throw new TypeError('disabledDates() expects an array parameter');
|
|
}
|
|
options.disabledDates = indexGivenDates(dates);
|
|
options.enabledDates = false;
|
|
update();
|
|
return picker;
|
|
};
|
|
|
|
picker.enabledDates = function (dates) {
|
|
///<signature helpKeyword="$.fn.datetimepicker.enabledDates">
|
|
///<summary>Returns an array with the currently set enabled dates on the component.</summary>
|
|
///<returns type="array">options.enabledDates</returns>
|
|
///</signature>
|
|
///<signature>
|
|
///<summary>Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of options.disabledDates if such exist.</summary>
|
|
///<param name="dates" locid="$.fn.datetimepicker.enabledDates_p:dates">Takes an [ string or Date or moment ] of values and allows the user to select only from those days.</param>
|
|
///</signature>
|
|
if (arguments.length === 0) {
|
|
return (options.enabledDates ? $.extend({}, options.enabledDates) : options.enabledDates);
|
|
}
|
|
|
|
if (!dates) {
|
|
options.enabledDates = false;
|
|
update();
|
|
return picker;
|
|
}
|
|
if (!(dates instanceof Array)) {
|
|
throw new TypeError('enabledDates() expects an array parameter');
|
|
}
|
|
options.enabledDates = indexGivenDates(dates);
|
|
options.disabledDates = false;
|
|
update();
|
|
return picker;
|
|
};
|
|
|
|
picker.daysOfWeekDisabled = function (daysOfWeekDisabled) {
|
|
if (arguments.length === 0) {
|
|
return options.daysOfWeekDisabled.splice(0);
|
|
}
|
|
|
|
if ((typeof daysOfWeekDisabled === 'boolean') && !daysOfWeekDisabled) {
|
|
options.daysOfWeekDisabled = false;
|
|
update();
|
|
return picker;
|
|
}
|
|
|
|
if (!(daysOfWeekDisabled instanceof Array)) {
|
|
throw new TypeError('daysOfWeekDisabled() expects an array parameter');
|
|
}
|
|
options.daysOfWeekDisabled = daysOfWeekDisabled.reduce(function (previousValue, currentValue) {
|
|
currentValue = parseInt(currentValue, 10);
|
|
if (currentValue > 6 || currentValue < 0 || isNaN(currentValue)) {
|
|
return previousValue;
|
|
}
|
|
if (previousValue.indexOf(currentValue) === -1) {
|
|
previousValue.push(currentValue);
|
|
}
|
|
return previousValue;
|
|
}, []).sort();
|
|
if (options.useCurrent && !options.keepInvalid) {
|
|
var tries = 0;
|
|
while (!isValid(date, 'd')) {
|
|
date.add(1, 'd');
|
|
if (tries === 31) {
|
|
throw 'Tried 31 times to find a valid date';
|
|
}
|
|
tries++;
|
|
}
|
|
setValue(date);
|
|
}
|
|
update();
|
|
return picker;
|
|
};
|
|
|
|
picker.maxDate = function (maxDate) {
|
|
if (arguments.length === 0) {
|
|
return options.maxDate ? options.maxDate.clone() : options.maxDate;
|
|
}
|
|
|
|
if ((typeof maxDate === 'boolean') && maxDate === false) {
|
|
options.maxDate = false;
|
|
update();
|
|
return picker;
|
|
}
|
|
|
|
if (typeof maxDate === 'string') {
|
|
if (maxDate === 'now' || maxDate === 'moment') {
|
|
maxDate = getMoment();
|
|
}
|
|
}
|
|
|
|
var parsedDate = parseInputDate(maxDate);
|
|
|
|
if (!parsedDate.isValid()) {
|
|
throw new TypeError('maxDate() Could not parse date parameter: ' + maxDate);
|
|
}
|
|
if (options.minDate && parsedDate.isBefore(options.minDate)) {
|
|
throw new TypeError('maxDate() date parameter is before options.minDate: ' + parsedDate.format(actualFormat));
|
|
}
|
|
options.maxDate = parsedDate;
|
|
if (options.useCurrent && !options.keepInvalid && date.isAfter(maxDate)) {
|
|
setValue(options.maxDate);
|
|
}
|
|
if (viewDate.isAfter(parsedDate)) {
|
|
viewDate = parsedDate.clone().subtract(options.stepping, 'm');
|
|
}
|
|
update();
|
|
return picker;
|
|
};
|
|
|
|
picker.minDate = function (minDate) {
|
|
if (arguments.length === 0) {
|
|
return options.minDate ? options.minDate.clone() : options.minDate;
|
|
}
|
|
|
|
if ((typeof minDate === 'boolean') && minDate === false) {
|
|
options.minDate = false;
|
|
update();
|
|
return picker;
|
|
}
|
|
|
|
if (typeof minDate === 'string') {
|
|
if (minDate === 'now' || minDate === 'moment') {
|
|
minDate = getMoment();
|
|
}
|
|
}
|
|
|
|
var parsedDate = parseInputDate(minDate);
|
|
|
|
if (!parsedDate.isValid()) {
|
|
throw new TypeError('minDate() Could not parse date parameter: ' + minDate);
|
|
}
|
|
if (options.maxDate && parsedDate.isAfter(options.maxDate)) {
|
|
throw new TypeError('minDate() date parameter is after options.maxDate: ' + parsedDate.format(actualFormat));
|
|
}
|
|
options.minDate = parsedDate;
|
|
if (options.useCurrent && !options.keepInvalid && date.isBefore(minDate)) {
|
|
setValue(options.minDate);
|
|
}
|
|
if (viewDate.isBefore(parsedDate)) {
|
|
viewDate = parsedDate.clone().add(options.stepping, 'm');
|
|
}
|
|
update();
|
|
return picker;
|
|
};
|
|
|
|
picker.defaultDate = function (defaultDate) {
|
|
///<signature helpKeyword="$.fn.datetimepicker.defaultDate">
|
|
///<summary>Returns a moment with the options.defaultDate option configuration or false if not set</summary>
|
|
///<returns type="Moment">date.clone()</returns>
|
|
///</signature>
|
|
///<signature>
|
|
///<summary>Will set the picker's inital date. If a boolean:false value is passed the options.defaultDate parameter is cleared.</summary>
|
|
///<param name="defaultDate" locid="$.fn.datetimepicker.defaultDate_p:defaultDate">Takes a string, Date, moment, boolean:false</param>
|
|
///</signature>
|
|
if (arguments.length === 0) {
|
|
return options.defaultDate ? options.defaultDate.clone() : options.defaultDate;
|
|
}
|
|
if (!defaultDate) {
|
|
options.defaultDate = false;
|
|
return picker;
|
|
}
|
|
|
|
if (typeof defaultDate === 'string') {
|
|
if (defaultDate === 'now' || defaultDate === 'moment') {
|
|
defaultDate = getMoment();
|
|
} else {
|
|
defaultDate = getMoment(defaultDate);
|
|
}
|
|
}
|
|
|
|
var parsedDate = parseInputDate(defaultDate);
|
|
if (!parsedDate.isValid()) {
|
|
throw new TypeError('defaultDate() Could not parse date parameter: ' + defaultDate);
|
|
}
|
|
if (!isValid(parsedDate)) {
|
|
throw new TypeError('defaultDate() date passed is invalid according to component setup validations');
|
|
}
|
|
|
|
options.defaultDate = parsedDate;
|
|
|
|
if ((options.defaultDate && options.inline) || input.val().trim() === '') {
|
|
setValue(options.defaultDate);
|
|
}
|
|
return picker;
|
|
};
|
|
|
|
picker.locale = function (locale) {
|
|
if (arguments.length === 0) {
|
|
return options.locale;
|
|
}
|
|
|
|
if (!moment.localeData(locale)) {
|
|
throw new TypeError('locale() locale ' + locale + ' is not loaded from moment locales!');
|
|
}
|
|
|
|
options.locale = locale;
|
|
date.locale(options.locale);
|
|
viewDate.locale(options.locale);
|
|
|
|
if (actualFormat) {
|
|
initFormatting(); // reinit formatting
|
|
}
|
|
if (widget) {
|
|
hide();
|
|
show();
|
|
}
|
|
return picker;
|
|
};
|
|
|
|
picker.stepping = function (stepping) {
|
|
if (arguments.length === 0) {
|
|
return options.stepping;
|
|
}
|
|
|
|
stepping = parseInt(stepping, 10);
|
|
if (isNaN(stepping) || stepping < 1) {
|
|
stepping = 1;
|
|
}
|
|
options.stepping = stepping;
|
|
return picker;
|
|
};
|
|
|
|
picker.useCurrent = function (useCurrent) {
|
|
var useCurrentOptions = ['year', 'month', 'day', 'hour', 'minute'];
|
|
if (arguments.length === 0) {
|
|
return options.useCurrent;
|
|
}
|
|
|
|
if ((typeof useCurrent !== 'boolean') && (typeof useCurrent !== 'string')) {
|
|
throw new TypeError('useCurrent() expects a boolean or string parameter');
|
|
}
|
|
if (typeof useCurrent === 'string' && useCurrentOptions.indexOf(useCurrent.toLowerCase()) === -1) {
|
|
throw new TypeError('useCurrent() expects a string parameter of ' + useCurrentOptions.join(', '));
|
|
}
|
|
options.useCurrent = useCurrent;
|
|
return picker;
|
|
};
|
|
|
|
picker.collapse = function (collapse) {
|
|
if (arguments.length === 0) {
|
|
return options.collapse;
|
|
}
|
|
|
|
if (typeof collapse !== 'boolean') {
|
|
throw new TypeError('collapse() expects a boolean parameter');
|
|
}
|
|
if (options.collapse === collapse) {
|
|
return picker;
|
|
}
|
|
options.collapse = collapse;
|
|
if (widget) {
|
|
hide();
|
|
show();
|
|
}
|
|
return picker;
|
|
};
|
|
|
|
picker.icons = function (icons) {
|
|
if (arguments.length === 0) {
|
|
return $.extend({}, options.icons);
|
|
}
|
|
|
|
if (!(icons instanceof Object)) {
|
|
throw new TypeError('icons() expects parameter to be an Object');
|
|
}
|
|
$.extend(options.icons, icons);
|
|
if (widget) {
|
|
hide();
|
|
show();
|
|
}
|
|
return picker;
|
|
};
|
|
|
|
picker.tooltips = function (tooltips) {
|
|
if (arguments.length === 0) {
|
|
return $.extend({}, options.tooltips);
|
|
}
|
|
|
|
if (!(tooltips instanceof Object)) {
|
|
throw new TypeError('tooltips() expects parameter to be an Object');
|
|
}
|
|
$.extend(options.tooltips, tooltips);
|
|
if (widget) {
|
|
hide();
|
|
show();
|
|
}
|
|
return picker;
|
|
};
|
|
|
|
picker.useStrict = function (useStrict) {
|
|
if (arguments.length === 0) {
|
|
return options.useStrict;
|
|
}
|
|
|
|
if (typeof useStrict !== 'boolean') {
|
|
throw new TypeError('useStrict() expects a boolean parameter');
|
|
}
|
|
options.useStrict = useStrict;
|
|
return picker;
|
|
};
|
|
|
|
picker.sideBySide = function (sideBySide) {
|
|
if (arguments.length === 0) {
|
|
return options.sideBySide;
|
|
}
|
|
|
|
if (typeof sideBySide !== 'boolean') {
|
|
throw new TypeError('sideBySide() expects a boolean parameter');
|
|
}
|
|
options.sideBySide = sideBySide;
|
|
if (widget) {
|
|
hide();
|
|
show();
|
|
}
|
|
return picker;
|
|
};
|
|
|
|
picker.viewMode = function (viewMode) {
|
|
if (arguments.length === 0) {
|
|
return options.viewMode;
|
|
}
|
|
|
|
if (typeof viewMode !== 'string') {
|
|
throw new TypeError('viewMode() expects a string parameter');
|
|
}
|
|
|
|
if (viewModes.indexOf(viewMode) === -1) {
|
|
throw new TypeError('viewMode() parameter must be one of (' + viewModes.join(', ') + ') value');
|
|
}
|
|
|
|
options.viewMode = viewMode;
|
|
currentViewMode = Math.max(viewModes.indexOf(viewMode), minViewModeNumber);
|
|
|
|
showMode();
|
|
return picker;
|
|
};
|
|
|
|
picker.toolbarPlacement = function (toolbarPlacement) {
|
|
if (arguments.length === 0) {
|
|
return options.toolbarPlacement;
|
|
}
|
|
|
|
if (typeof toolbarPlacement !== 'string') {
|
|
throw new TypeError('toolbarPlacement() expects a string parameter');
|
|
}
|
|
if (toolbarPlacements.indexOf(toolbarPlacement) === -1) {
|
|
throw new TypeError('toolbarPlacement() parameter must be one of (' + toolbarPlacements.join(', ') + ') value');
|
|
}
|
|
options.toolbarPlacement = toolbarPlacement;
|
|
|
|
if (widget) {
|
|
hide();
|
|
show();
|
|
}
|
|
return picker;
|
|
};
|
|
|
|
picker.widgetPositioning = function (widgetPositioning) {
|
|
if (arguments.length === 0) {
|
|
return $.extend({}, options.widgetPositioning);
|
|
}
|
|
|
|
if (({}).toString.call(widgetPositioning) !== '[object Object]') {
|
|
throw new TypeError('widgetPositioning() expects an object variable');
|
|
}
|
|
if (widgetPositioning.horizontal) {
|
|
if (typeof widgetPositioning.horizontal !== 'string') {
|
|
throw new TypeError('widgetPositioning() horizontal variable must be a string');
|
|
}
|
|
widgetPositioning.horizontal = widgetPositioning.horizontal.toLowerCase();
|
|
if (horizontalModes.indexOf(widgetPositioning.horizontal) === -1) {
|
|
throw new TypeError('widgetPositioning() expects horizontal parameter to be one of (' + horizontalModes.join(', ') + ')');
|
|
}
|
|
options.widgetPositioning.horizontal = widgetPositioning.horizontal;
|
|
}
|
|
if (widgetPositioning.vertical) {
|
|
if (typeof widgetPositioning.vertical !== 'string') {
|
|
throw new TypeError('widgetPositioning() vertical variable must be a string');
|
|
}
|
|
widgetPositioning.vertical = widgetPositioning.vertical.toLowerCase();
|
|
if (verticalModes.indexOf(widgetPositioning.vertical) === -1) {
|
|
throw new TypeError('widgetPositioning() expects vertical parameter to be one of (' + verticalModes.join(', ') + ')');
|
|
}
|
|
options.widgetPositioning.vertical = widgetPositioning.vertical;
|
|
}
|
|
update();
|
|
return picker;
|
|
};
|
|
|
|
picker.calendarWeeks = function (calendarWeeks) {
|
|
if (arguments.length === 0) {
|
|
return options.calendarWeeks;
|
|
}
|
|
|
|
if (typeof calendarWeeks !== 'boolean') {
|
|
throw new TypeError('calendarWeeks() expects parameter to be a boolean value');
|
|
}
|
|
|
|
options.calendarWeeks = calendarWeeks;
|
|
update();
|
|
return picker;
|
|
};
|
|
|
|
picker.showTodayButton = function (showTodayButton) {
|
|
if (arguments.length === 0) {
|
|
return options.showTodayButton;
|
|
}
|
|
|
|
if (typeof showTodayButton !== 'boolean') {
|
|
throw new TypeError('showTodayButton() expects a boolean parameter');
|
|
}
|
|
|
|
options.showTodayButton = showTodayButton;
|
|
if (widget) {
|
|
hide();
|
|
show();
|
|
}
|
|
return picker;
|
|
};
|
|
|
|
picker.showClear = function (showClear) {
|
|
if (arguments.length === 0) {
|
|
return options.showClear;
|
|
}
|
|
|
|
if (typeof showClear !== 'boolean') {
|
|
throw new TypeError('showClear() expects a boolean parameter');
|
|
}
|
|
|
|
options.showClear = showClear;
|
|
if (widget) {
|
|
hide();
|
|
show();
|
|
}
|
|
return picker;
|
|
};
|
|
|
|
picker.widgetParent = function (widgetParent) {
|
|
if (arguments.length === 0) {
|
|
return options.widgetParent;
|
|
}
|
|
|
|
if (typeof widgetParent === 'string') {
|
|
widgetParent = $(widgetParent);
|
|
}
|
|
|
|
if (widgetParent !== null && (typeof widgetParent !== 'string' && !(widgetParent instanceof $))) {
|
|
throw new TypeError('widgetParent() expects a string or a jQuery object parameter');
|
|
}
|
|
|
|
options.widgetParent = widgetParent;
|
|
if (widget) {
|
|
hide();
|
|
show();
|
|
}
|
|
return picker;
|
|
};
|
|
|
|
picker.keepOpen = function (keepOpen) {
|
|
if (arguments.length === 0) {
|
|
return options.keepOpen;
|
|
}
|
|
|
|
if (typeof keepOpen !== 'boolean') {
|
|
throw new TypeError('keepOpen() expects a boolean parameter');
|
|
}
|
|
|
|
options.keepOpen = keepOpen;
|
|
return picker;
|
|
};
|
|
|
|
picker.focusOnShow = function (focusOnShow) {
|
|
if (arguments.length === 0) {
|
|
return options.focusOnShow;
|
|
}
|
|
|
|
if (typeof focusOnShow !== 'boolean') {
|
|
throw new TypeError('focusOnShow() expects a boolean parameter');
|
|
}
|
|
|
|
options.focusOnShow = focusOnShow;
|
|
return picker;
|
|
};
|
|
|
|
picker.inline = function (inline) {
|
|
if (arguments.length === 0) {
|
|
return options.inline;
|
|
}
|
|
|
|
if (typeof inline !== 'boolean') {
|
|
throw new TypeError('inline() expects a boolean parameter');
|
|
}
|
|
|
|
options.inline = inline;
|
|
return picker;
|
|
};
|
|
|
|
picker.clear = function () {
|
|
clear();
|
|
return picker;
|
|
};
|
|
|
|
picker.keyBinds = function (keyBinds) {
|
|
if (arguments.length === 0) {
|
|
return options.keyBinds;
|
|
}
|
|
|
|
options.keyBinds = keyBinds;
|
|
return picker;
|
|
};
|
|
|
|
picker.getMoment = function (d) {
|
|
return getMoment(d);
|
|
};
|
|
|
|
picker.debug = function (debug) {
|
|
if (typeof debug !== 'boolean') {
|
|
throw new TypeError('debug() expects a boolean parameter');
|
|
}
|
|
|
|
options.debug = debug;
|
|
return picker;
|
|
};
|
|
|
|
picker.allowInputToggle = function (allowInputToggle) {
|
|
if (arguments.length === 0) {
|
|
return options.allowInputToggle;
|
|
}
|
|
|
|
if (typeof allowInputToggle !== 'boolean') {
|
|
throw new TypeError('allowInputToggle() expects a boolean parameter');
|
|
}
|
|
|
|
options.allowInputToggle = allowInputToggle;
|
|
return picker;
|
|
};
|
|
|
|
picker.showClose = function (showClose) {
|
|
if (arguments.length === 0) {
|
|
return options.showClose;
|
|
}
|
|
|
|
if (typeof showClose !== 'boolean') {
|
|
throw new TypeError('showClose() expects a boolean parameter');
|
|
}
|
|
|
|
options.showClose = showClose;
|
|
return picker;
|
|
};
|
|
|
|
picker.keepInvalid = function (keepInvalid) {
|
|
if (arguments.length === 0) {
|
|
return options.keepInvalid;
|
|
}
|
|
|
|
if (typeof keepInvalid !== 'boolean') {
|
|
throw new TypeError('keepInvalid() expects a boolean parameter');
|
|
}
|
|
options.keepInvalid = keepInvalid;
|
|
return picker;
|
|
};
|
|
|
|
picker.datepickerInput = function (datepickerInput) {
|
|
if (arguments.length === 0) {
|
|
return options.datepickerInput;
|
|
}
|
|
|
|
if (typeof datepickerInput !== 'string') {
|
|
throw new TypeError('datepickerInput() expects a string parameter');
|
|
}
|
|
|
|
options.datepickerInput = datepickerInput;
|
|
return picker;
|
|
};
|
|
|
|
picker.parseInputDate = function (parseInputDate) {
|
|
if (arguments.length === 0) {
|
|
return options.parseInputDate;
|
|
}
|
|
|
|
if (typeof parseInputDate !== 'function') {
|
|
throw new TypeError('parseInputDate() sholud be as function');
|
|
}
|
|
|
|
options.parseInputDate = parseInputDate;
|
|
|
|
return picker;
|
|
};
|
|
|
|
picker.disabledTimeIntervals = function (disabledTimeIntervals) {
|
|
///<signature helpKeyword="$.fn.datetimepicker.disabledTimeIntervals">
|
|
///<summary>Returns an array with the currently set disabled dates on the component.</summary>
|
|
///<returns type="array">options.disabledTimeIntervals</returns>
|
|
///</signature>
|
|
///<signature>
|
|
///<summary>Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of
|
|
///options.enabledDates if such exist.</summary>
|
|
///<param name="dates" locid="$.fn.datetimepicker.disabledTimeIntervals_p:dates">Takes an [ string or Date or moment ] of values and allows the user to select only from those days.</param>
|
|
///</signature>
|
|
if (arguments.length === 0) {
|
|
return (options.disabledTimeIntervals ? $.extend({}, options.disabledTimeIntervals) : options.disabledTimeIntervals);
|
|
}
|
|
|
|
if (!disabledTimeIntervals) {
|
|
options.disabledTimeIntervals = false;
|
|
update();
|
|
return picker;
|
|
}
|
|
if (!(disabledTimeIntervals instanceof Array)) {
|
|
throw new TypeError('disabledTimeIntervals() expects an array parameter');
|
|
}
|
|
options.disabledTimeIntervals = disabledTimeIntervals;
|
|
update();
|
|
return picker;
|
|
};
|
|
|
|
picker.disabledHours = function (hours) {
|
|
///<signature helpKeyword="$.fn.datetimepicker.disabledHours">
|
|
///<summary>Returns an array with the currently set disabled hours on the component.</summary>
|
|
///<returns type="array">options.disabledHours</returns>
|
|
///</signature>
|
|
///<signature>
|
|
///<summary>Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of
|
|
///options.enabledHours if such exist.</summary>
|
|
///<param name="hours" locid="$.fn.datetimepicker.disabledHours_p:hours">Takes an [ int ] of values and disallows the user to select only from those hours.</param>
|
|
///</signature>
|
|
if (arguments.length === 0) {
|
|
return (options.disabledHours ? $.extend({}, options.disabledHours) : options.disabledHours);
|
|
}
|
|
|
|
if (!hours) {
|
|
options.disabledHours = false;
|
|
update();
|
|
return picker;
|
|
}
|
|
if (!(hours instanceof Array)) {
|
|
throw new TypeError('disabledHours() expects an array parameter');
|
|
}
|
|
options.disabledHours = indexGivenHours(hours);
|
|
options.enabledHours = false;
|
|
if (options.useCurrent && !options.keepInvalid) {
|
|
var tries = 0;
|
|
while (!isValid(date, 'h')) {
|
|
date.add(1, 'h');
|
|
if (tries === 24) {
|
|
throw 'Tried 24 times to find a valid date';
|
|
}
|
|
tries++;
|
|
}
|
|
setValue(date);
|
|
}
|
|
update();
|
|
return picker;
|
|
};
|
|
|
|
picker.enabledHours = function (hours) {
|
|
///<signature helpKeyword="$.fn.datetimepicker.enabledHours">
|
|
///<summary>Returns an array with the currently set enabled hours on the component.</summary>
|
|
///<returns type="array">options.enabledHours</returns>
|
|
///</signature>
|
|
///<signature>
|
|
///<summary>Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of options.disabledHours if such exist.</summary>
|
|
///<param name="hours" locid="$.fn.datetimepicker.enabledHours_p:hours">Takes an [ int ] of values and allows the user to select only from those hours.</param>
|
|
///</signature>
|
|
if (arguments.length === 0) {
|
|
return (options.enabledHours ? $.extend({}, options.enabledHours) : options.enabledHours);
|
|
}
|
|
|
|
if (!hours) {
|
|
options.enabledHours = false;
|
|
update();
|
|
return picker;
|
|
}
|
|
if (!(hours instanceof Array)) {
|
|
throw new TypeError('enabledHours() expects an array parameter');
|
|
}
|
|
options.enabledHours = indexGivenHours(hours);
|
|
options.disabledHours = false;
|
|
if (options.useCurrent && !options.keepInvalid) {
|
|
var tries = 0;
|
|
while (!isValid(date, 'h')) {
|
|
date.add(1, 'h');
|
|
if (tries === 24) {
|
|
throw 'Tried 24 times to find a valid date';
|
|
}
|
|
tries++;
|
|
}
|
|
setValue(date);
|
|
}
|
|
update();
|
|
return picker;
|
|
};
|
|
/**
|
|
* Returns the component's model current viewDate, a moment object or null if not set. Passing a null value unsets the components model current moment. Parsing of the newDate parameter is made using moment library with the options.format and options.useStrict components configuration.
|
|
* @param {Takes string, viewDate, moment, null parameter.} newDate
|
|
* @returns {viewDate.clone()}
|
|
*/
|
|
picker.viewDate = function (newDate) {
|
|
if (arguments.length === 0) {
|
|
return viewDate.clone();
|
|
}
|
|
|
|
if (!newDate) {
|
|
viewDate = date.clone();
|
|
return picker;
|
|
}
|
|
|
|
if (typeof newDate !== 'string' && !moment.isMoment(newDate) && !(newDate instanceof Date)) {
|
|
throw new TypeError('viewDate() parameter must be one of [string, moment or Date]');
|
|
}
|
|
|
|
viewDate = parseInputDate(newDate);
|
|
viewUpdate();
|
|
return picker;
|
|
};
|
|
|
|
// initializing element and component attributes
|
|
if (element.is('input')) {
|
|
input = element;
|
|
} else {
|
|
input = element.find(options.datepickerInput);
|
|
if (input.length === 0) {
|
|
input = element.find('input');
|
|
} else if (!input.is('input')) {
|
|
throw new Error('CSS class "' + options.datepickerInput + '" cannot be applied to non input element');
|
|
}
|
|
}
|
|
|
|
if (element.hasClass('input-group')) {
|
|
// in case there is more then one 'input-group-addon' Issue #48
|
|
if (element.find('.datepickerbutton').length === 0) {
|
|
component = element.find('.input-group-addon');
|
|
} else {
|
|
component = element.find('.datepickerbutton');
|
|
}
|
|
}
|
|
|
|
if (!options.inline && !input.is('input')) {
|
|
throw new Error('Could not initialize DateTimePicker without an input element');
|
|
}
|
|
|
|
// Set defaults for date here now instead of in var declaration
|
|
date = getMoment();
|
|
viewDate = date.clone();
|
|
|
|
$.extend(true, options, dataToOptions());
|
|
|
|
picker.options(options);
|
|
|
|
initFormatting();
|
|
|
|
attachDatePickerElementEvents();
|
|
|
|
if (input.prop('disabled')) {
|
|
picker.disable();
|
|
}
|
|
if (input.is('input') && input.val().trim().length !== 0) {
|
|
setValue(parseInputDate(input.val().trim()));
|
|
}
|
|
else if (options.defaultDate && input.attr('placeholder') === undefined) {
|
|
setValue(options.defaultDate);
|
|
}
|
|
if (options.inline) {
|
|
show();
|
|
}
|
|
return picker;
|
|
};
|
|
|
|
/********************************************************************************
|
|
*
|
|
* jQuery plugin constructor and defaults object
|
|
*
|
|
********************************************************************************/
|
|
|
|
/**
|
|
* See (http://jquery.com/).
|
|
* @name jQuery
|
|
* @class
|
|
* See the jQuery Library (http://jquery.com/) for full details. This just
|
|
* documents the function and classes that are added to jQuery by this plug-in.
|
|
*/
|
|
/**
|
|
* See (http://jquery.com/)
|
|
* @name fn
|
|
* @class
|
|
* See the jQuery Library (http://jquery.com/) for full details. This just
|
|
* documents the function and classes that are added to jQuery by this plug-in.
|
|
* @memberOf jQuery
|
|
*/
|
|
/**
|
|
* Show comments
|
|
* @class datetimepicker
|
|
* @memberOf jQuery.fn
|
|
*/
|
|
$.fn.datetimepicker = function (options) {
|
|
options = options || {};
|
|
|
|
var args = Array.prototype.slice.call(arguments, 1),
|
|
isInstance = true,
|
|
thisMethods = ['destroy', 'hide', 'show', 'toggle'],
|
|
returnValue;
|
|
|
|
if (typeof options === 'object') {
|
|
return this.each(function () {
|
|
var $this = $(this),
|
|
_options;
|
|
if (!$this.data('DateTimePicker')) {
|
|
// create a private copy of the defaults object
|
|
_options = $.extend(true, {}, $.fn.datetimepicker.defaults, options);
|
|
$this.data('DateTimePicker', dateTimePicker($this, _options));
|
|
}
|
|
});
|
|
} else if (typeof options === 'string') {
|
|
this.each(function () {
|
|
var $this = $(this),
|
|
instance = $this.data('DateTimePicker');
|
|
if (!instance) {
|
|
throw new Error('bootstrap-datetimepicker("' + options + '") method was called on an element that is not using DateTimePicker');
|
|
}
|
|
|
|
returnValue = instance[options].apply(instance, args);
|
|
isInstance = returnValue === instance;
|
|
});
|
|
|
|
if (isInstance || $.inArray(options, thisMethods) > -1) {
|
|
return this;
|
|
}
|
|
|
|
return returnValue;
|
|
}
|
|
|
|
throw new TypeError('Invalid arguments for DateTimePicker: ' + options);
|
|
};
|
|
|
|
$.fn.datetimepicker.defaults = {
|
|
timeZone: '',
|
|
format: false,
|
|
dayViewHeaderFormat: 'MMMM YYYY',
|
|
extraFormats: false,
|
|
stepping: 1,
|
|
minDate: false,
|
|
maxDate: false,
|
|
useCurrent: true,
|
|
collapse: true,
|
|
locale: moment.locale(),
|
|
defaultDate: false,
|
|
disabledDates: false,
|
|
enabledDates: false,
|
|
icons: {
|
|
time: 'glyphicon glyphicon-time',
|
|
date: 'glyphicon glyphicon-calendar',
|
|
up: 'glyphicon glyphicon-chevron-up',
|
|
down: 'glyphicon glyphicon-chevron-down',
|
|
previous: 'glyphicon glyphicon-chevron-left',
|
|
next: 'glyphicon glyphicon-chevron-right',
|
|
today: 'glyphicon glyphicon-screenshot',
|
|
clear: 'glyphicon glyphicon-trash',
|
|
close: 'glyphicon glyphicon-remove'
|
|
},
|
|
tooltips: {
|
|
today: 'Go to today',
|
|
clear: 'Clear selection',
|
|
close: 'Close the picker',
|
|
selectMonth: 'Select Month',
|
|
prevMonth: 'Previous Month',
|
|
nextMonth: 'Next Month',
|
|
selectYear: 'Select Year',
|
|
prevYear: 'Previous Year',
|
|
nextYear: 'Next Year',
|
|
selectDecade: 'Select Decade',
|
|
prevDecade: 'Previous Decade',
|
|
nextDecade: 'Next Decade',
|
|
prevCentury: 'Previous Century',
|
|
nextCentury: 'Next Century',
|
|
pickHour: 'Pick Hour',
|
|
incrementHour: 'Increment Hour',
|
|
decrementHour: 'Decrement Hour',
|
|
pickMinute: 'Pick Minute',
|
|
incrementMinute: 'Increment Minute',
|
|
decrementMinute: 'Decrement Minute',
|
|
pickSecond: 'Pick Second',
|
|
incrementSecond: 'Increment Second',
|
|
decrementSecond: 'Decrement Second',
|
|
togglePeriod: 'Toggle Period',
|
|
selectTime: 'Select Time'
|
|
},
|
|
useStrict: false,
|
|
sideBySide: false,
|
|
daysOfWeekDisabled: false,
|
|
calendarWeeks: false,
|
|
viewMode: 'days',
|
|
toolbarPlacement: 'default',
|
|
showTodayButton: false,
|
|
showClear: false,
|
|
showClose: false,
|
|
widgetPositioning: {
|
|
horizontal: 'auto',
|
|
vertical: 'auto'
|
|
},
|
|
widgetParent: null,
|
|
ignoreReadonly: false,
|
|
keepOpen: false,
|
|
focusOnShow: true,
|
|
inline: false,
|
|
keepInvalid: false,
|
|
datepickerInput: '.datepickerinput',
|
|
keyBinds: {
|
|
up: function (widget) {
|
|
if (!widget) {
|
|
return;
|
|
}
|
|
var d = this.date() || this.getMoment();
|
|
if (widget.find('.datepicker').is(':visible')) {
|
|
this.date(d.clone().subtract(7, 'd'));
|
|
} else {
|
|
this.date(d.clone().add(this.stepping(), 'm'));
|
|
}
|
|
},
|
|
down: function (widget) {
|
|
if (!widget) {
|
|
this.show();
|
|
return;
|
|
}
|
|
var d = this.date() || this.getMoment();
|
|
if (widget.find('.datepicker').is(':visible')) {
|
|
this.date(d.clone().add(7, 'd'));
|
|
} else {
|
|
this.date(d.clone().subtract(this.stepping(), 'm'));
|
|
}
|
|
},
|
|
'control up': function (widget) {
|
|
if (!widget) {
|
|
return;
|
|
}
|
|
var d = this.date() || this.getMoment();
|
|
if (widget.find('.datepicker').is(':visible')) {
|
|
this.date(d.clone().subtract(1, 'y'));
|
|
} else {
|
|
this.date(d.clone().add(1, 'h'));
|
|
}
|
|
},
|
|
'control down': function (widget) {
|
|
if (!widget) {
|
|
return;
|
|
}
|
|
var d = this.date() || this.getMoment();
|
|
if (widget.find('.datepicker').is(':visible')) {
|
|
this.date(d.clone().add(1, 'y'));
|
|
} else {
|
|
this.date(d.clone().subtract(1, 'h'));
|
|
}
|
|
},
|
|
left: function (widget) {
|
|
if (!widget) {
|
|
return;
|
|
}
|
|
var d = this.date() || this.getMoment();
|
|
if (widget.find('.datepicker').is(':visible')) {
|
|
this.date(d.clone().subtract(1, 'd'));
|
|
}
|
|
},
|
|
right: function (widget) {
|
|
if (!widget) {
|
|
return;
|
|
}
|
|
var d = this.date() || this.getMoment();
|
|
if (widget.find('.datepicker').is(':visible')) {
|
|
this.date(d.clone().add(1, 'd'));
|
|
}
|
|
},
|
|
pageUp: function (widget) {
|
|
if (!widget) {
|
|
return;
|
|
}
|
|
var d = this.date() || this.getMoment();
|
|
if (widget.find('.datepicker').is(':visible')) {
|
|
this.date(d.clone().subtract(1, 'M'));
|
|
}
|
|
},
|
|
pageDown: function (widget) {
|
|
if (!widget) {
|
|
return;
|
|
}
|
|
var d = this.date() || this.getMoment();
|
|
if (widget.find('.datepicker').is(':visible')) {
|
|
this.date(d.clone().add(1, 'M'));
|
|
}
|
|
},
|
|
enter: function () {
|
|
this.hide();
|
|
},
|
|
escape: function () {
|
|
this.hide();
|
|
},
|
|
//tab: function (widget) { //this break the flow of the form. disabling for now
|
|
// var toggle = widget.find('.picker-switch a[data-action="togglePicker"]');
|
|
// if(toggle.length > 0) toggle.click();
|
|
//},
|
|
'control space': function (widget) {
|
|
if (!widget) {
|
|
return;
|
|
}
|
|
if (widget.find('.timepicker').is(':visible')) {
|
|
widget.find('.btn[data-action="togglePeriod"]').click();
|
|
}
|
|
},
|
|
t: function () {
|
|
this.date(this.getMoment());
|
|
},
|
|
'delete': function () {
|
|
this.clear();
|
|
}
|
|
},
|
|
debug: false,
|
|
allowInputToggle: false,
|
|
disabledTimeIntervals: false,
|
|
disabledHours: false,
|
|
enabledHours: false,
|
|
viewDate: false
|
|
};
|
|
|
|
return $.fn.datetimepicker;
|
|
}));
|
|
|
|
/*!
|
|
* Copyright 2012, Chris Wanstrath
|
|
* Released under the MIT License
|
|
* https://github.com/defunkt/jquery-pjax
|
|
*/
|
|
|
|
(function($){
|
|
|
|
// When called on a container with a selector, fetches the href with
|
|
// ajax into the container or with the data-pjax attribute on the link
|
|
// itself.
|
|
//
|
|
// Tries to make sure the back button and ctrl+click work the way
|
|
// you'd expect.
|
|
//
|
|
// Exported as $.fn.pjax
|
|
//
|
|
// Accepts a jQuery ajax options object that may include these
|
|
// pjax specific options:
|
|
//
|
|
//
|
|
// container - String selector for the element where to place the response body.
|
|
// push - Whether to pushState the URL. Defaults to true (of course).
|
|
// replace - Want to use replaceState instead? That's cool.
|
|
//
|
|
// For convenience the second parameter can be either the container or
|
|
// the options object.
|
|
//
|
|
// Returns the jQuery object
|
|
function fnPjax(selector, container, options) {
|
|
options = optionsFor(container, options)
|
|
return this.on('click.pjax', selector, function(event) {
|
|
var opts = options
|
|
if (!opts.container) {
|
|
opts = $.extend({}, options)
|
|
opts.container = $(this).attr('data-pjax')
|
|
}
|
|
handleClick(event, opts)
|
|
})
|
|
}
|
|
|
|
// Public: pjax on click handler
|
|
//
|
|
// Exported as $.pjax.click.
|
|
//
|
|
// event - "click" jQuery.Event
|
|
// options - pjax options
|
|
//
|
|
// Examples
|
|
//
|
|
// $(document).on('click', 'a', $.pjax.click)
|
|
// // is the same as
|
|
// $(document).pjax('a')
|
|
//
|
|
// Returns nothing.
|
|
function handleClick(event, container, options) {
|
|
options = optionsFor(container, options)
|
|
|
|
var link = event.currentTarget
|
|
var $link = $(link)
|
|
|
|
if (link.tagName.toUpperCase() !== 'A')
|
|
throw "$.fn.pjax or $.pjax.click requires an anchor element"
|
|
|
|
// Middle click, cmd click, and ctrl click should open
|
|
// links in a new tab as normal.
|
|
if ( event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey )
|
|
return
|
|
|
|
// Ignore cross origin links
|
|
if ( location.protocol !== link.protocol || location.hostname !== link.hostname )
|
|
return
|
|
|
|
// Ignore case when a hash is being tacked on the current URL
|
|
if ( link.href.indexOf('#') > -1 && stripHash(link) == stripHash(location) )
|
|
return
|
|
|
|
// Ignore event with default prevented
|
|
if (event.isDefaultPrevented())
|
|
return
|
|
|
|
var defaults = {
|
|
url: link.href,
|
|
container: $link.attr('data-pjax'),
|
|
target: link
|
|
}
|
|
|
|
var opts = $.extend({}, defaults, options)
|
|
var clickEvent = $.Event('pjax:click')
|
|
$link.trigger(clickEvent, [opts])
|
|
|
|
if (!clickEvent.isDefaultPrevented()) {
|
|
pjax(opts)
|
|
event.preventDefault()
|
|
$link.trigger('pjax:clicked', [opts])
|
|
}
|
|
}
|
|
|
|
// Public: pjax on form submit handler
|
|
//
|
|
// Exported as $.pjax.submit
|
|
//
|
|
// event - "click" jQuery.Event
|
|
// options - pjax options
|
|
//
|
|
// Examples
|
|
//
|
|
// $(document).on('submit', 'form', function(event) {
|
|
// $.pjax.submit(event, '[data-pjax-container]')
|
|
// })
|
|
//
|
|
// Returns nothing.
|
|
function handleSubmit(event, container, options) {
|
|
options = optionsFor(container, options)
|
|
|
|
var form = event.currentTarget
|
|
var $form = $(form)
|
|
|
|
if (form.tagName.toUpperCase() !== 'FORM')
|
|
throw "$.pjax.submit requires a form element"
|
|
|
|
var defaults = {
|
|
type: ($form.attr('method') || 'GET').toUpperCase(),
|
|
url: $form.attr('action'),
|
|
container: $form.attr('data-pjax'),
|
|
target: form
|
|
}
|
|
|
|
if (defaults.type !== 'GET' && window.FormData !== undefined) {
|
|
defaults.data = new FormData(form)
|
|
defaults.processData = false
|
|
defaults.contentType = false
|
|
} else {
|
|
// Can't handle file uploads, exit
|
|
if ($form.find(':file').length) {
|
|
return
|
|
}
|
|
|
|
// Fallback to manually serializing the fields
|
|
defaults.data = $form.serializeArray()
|
|
}
|
|
|
|
pjax($.extend({}, defaults, options))
|
|
|
|
event.preventDefault()
|
|
}
|
|
|
|
// Loads a URL with ajax, puts the response body inside a container,
|
|
// then pushState()'s the loaded URL.
|
|
//
|
|
// Works just like $.ajax in that it accepts a jQuery ajax
|
|
// settings object (with keys like url, type, data, etc).
|
|
//
|
|
// Accepts these extra keys:
|
|
//
|
|
// container - String selector for where to stick the response body.
|
|
// push - Whether to pushState the URL. Defaults to true (of course).
|
|
// replace - Want to use replaceState instead? That's cool.
|
|
//
|
|
// Use it just like $.ajax:
|
|
//
|
|
// var xhr = $.pjax({ url: this.href, container: '#main' })
|
|
// console.log( xhr.readyState )
|
|
//
|
|
// Returns whatever $.ajax returns.
|
|
function pjax(options) {
|
|
options = $.extend(true, {}, $.ajaxSettings, pjax.defaults, options)
|
|
|
|
if ($.isFunction(options.url)) {
|
|
options.url = options.url()
|
|
}
|
|
|
|
var hash = parseURL(options.url).hash
|
|
|
|
var containerType = $.type(options.container)
|
|
if (containerType !== 'string') {
|
|
throw "expected string value for 'container' option; got " + containerType
|
|
}
|
|
var context = options.context = $(options.container)
|
|
if (!context.length) {
|
|
throw "the container selector '" + options.container + "' did not match anything"
|
|
}
|
|
|
|
// We want the browser to maintain two separate internal caches: one
|
|
// for pjax'd partial page loads and one for normal page loads.
|
|
// Without adding this secret parameter, some browsers will often
|
|
// confuse the two.
|
|
if (!options.data) options.data = {}
|
|
if ($.isArray(options.data)) {
|
|
options.data.push({name: '_pjax', value: options.container})
|
|
} else {
|
|
options.data._pjax = options.container
|
|
}
|
|
|
|
function fire(type, args, props) {
|
|
if (!props) props = {}
|
|
props.relatedTarget = options.target
|
|
var event = $.Event(type, props)
|
|
context.trigger(event, args)
|
|
return !event.isDefaultPrevented()
|
|
}
|
|
|
|
var timeoutTimer
|
|
|
|
options.beforeSend = function(xhr, settings) {
|
|
// No timeout for non-GET requests
|
|
// Its not safe to request the resource again with a fallback method.
|
|
if (settings.type !== 'GET') {
|
|
settings.timeout = 0
|
|
}
|
|
|
|
xhr.setRequestHeader('X-PJAX', 'true')
|
|
xhr.setRequestHeader('X-PJAX-Container', options.container)
|
|
|
|
if (!fire('pjax:beforeSend', [xhr, settings]))
|
|
return false
|
|
|
|
if (settings.timeout > 0) {
|
|
timeoutTimer = setTimeout(function() {
|
|
if (fire('pjax:timeout', [xhr, options]))
|
|
xhr.abort('timeout')
|
|
}, settings.timeout)
|
|
|
|
// Clear timeout setting so jquerys internal timeout isn't invoked
|
|
settings.timeout = 0
|
|
}
|
|
|
|
var url = parseURL(settings.url)
|
|
if (hash) url.hash = hash
|
|
options.requestUrl = stripInternalParams(url)
|
|
}
|
|
|
|
options.complete = function(xhr, textStatus) {
|
|
if (timeoutTimer)
|
|
clearTimeout(timeoutTimer)
|
|
|
|
fire('pjax:complete', [xhr, textStatus, options])
|
|
|
|
fire('pjax:end', [xhr, options])
|
|
}
|
|
|
|
options.error = function(xhr, textStatus, errorThrown) {
|
|
var container = extractContainer("", xhr, options)
|
|
|
|
var allowed = fire('pjax:error', [xhr, textStatus, errorThrown, options])
|
|
if (options.type == 'GET' && textStatus !== 'abort' && allowed) {
|
|
locationReplace(container.url)
|
|
}
|
|
}
|
|
|
|
options.success = function(data, status, xhr) {
|
|
var previousState = pjax.state
|
|
|
|
// If $.pjax.defaults.version is a function, invoke it first.
|
|
// Otherwise it can be a static string.
|
|
var currentVersion = typeof $.pjax.defaults.version === 'function' ?
|
|
$.pjax.defaults.version() :
|
|
$.pjax.defaults.version
|
|
|
|
var latestVersion = xhr.getResponseHeader('X-PJAX-Version')
|
|
|
|
var container = extractContainer(data, xhr, options)
|
|
|
|
var url = parseURL(container.url)
|
|
if (hash) {
|
|
url.hash = hash
|
|
container.url = url.href
|
|
}
|
|
|
|
// If there is a layout version mismatch, hard load the new url
|
|
if (currentVersion && latestVersion && currentVersion !== latestVersion) {
|
|
locationReplace(container.url)
|
|
return
|
|
}
|
|
|
|
// If the new response is missing a body, hard load the page
|
|
if (!container.contents) {
|
|
locationReplace(container.url)
|
|
return
|
|
}
|
|
|
|
pjax.state = {
|
|
id: options.id || uniqueId(),
|
|
url: container.url,
|
|
title: container.title,
|
|
container: options.container,
|
|
fragment: options.fragment,
|
|
timeout: options.timeout
|
|
}
|
|
|
|
if (options.push || options.replace) {
|
|
window.history.replaceState(pjax.state, container.title, container.url)
|
|
}
|
|
|
|
// Only blur the focus if the focused element is within the container.
|
|
var blurFocus = $.contains(context, document.activeElement)
|
|
|
|
// Clear out any focused controls before inserting new page contents.
|
|
if (blurFocus) {
|
|
try {
|
|
document.activeElement.blur()
|
|
} catch (e) { /* ignore */ }
|
|
}
|
|
|
|
if (container.title) document.title = container.title
|
|
|
|
fire('pjax:beforeReplace', [container.contents, options], {
|
|
state: pjax.state,
|
|
previousState: previousState
|
|
})
|
|
context.html(container.contents)
|
|
|
|
// FF bug: Won't autofocus fields that are inserted via JS.
|
|
// This behavior is incorrect. So if theres no current focus, autofocus
|
|
// the last field.
|
|
//
|
|
// http://www.w3.org/html/wg/drafts/html/master/forms.html
|
|
var autofocusEl = context.find('input[autofocus], textarea[autofocus]').last()[0]
|
|
if (autofocusEl && document.activeElement !== autofocusEl) {
|
|
autofocusEl.focus()
|
|
}
|
|
|
|
executeScriptTags(container.scripts)
|
|
|
|
var scrollTo = options.scrollTo
|
|
|
|
// Ensure browser scrolls to the element referenced by the URL anchor
|
|
if (hash) {
|
|
var name = decodeURIComponent(hash.slice(1))
|
|
var target = document.getElementById(name) || document.getElementsByName(name)[0]
|
|
if (target) scrollTo = $(target).offset().top
|
|
}
|
|
|
|
if (typeof scrollTo == 'number') $(window).scrollTop(scrollTo)
|
|
|
|
fire('pjax:success', [data, status, xhr, options])
|
|
}
|
|
|
|
|
|
// Initialize pjax.state for the initial page load. Assume we're
|
|
// using the container and options of the link we're loading for the
|
|
// back button to the initial page. This ensures good back button
|
|
// behavior.
|
|
if (!pjax.state) {
|
|
pjax.state = {
|
|
id: uniqueId(),
|
|
url: window.location.href,
|
|
title: document.title,
|
|
container: options.container,
|
|
fragment: options.fragment,
|
|
timeout: options.timeout
|
|
}
|
|
window.history.replaceState(pjax.state, document.title)
|
|
}
|
|
|
|
// Cancel the current request if we're already pjaxing
|
|
abortXHR(pjax.xhr)
|
|
|
|
pjax.options = options
|
|
var xhr = pjax.xhr = $.ajax(options)
|
|
|
|
if (xhr.readyState > 0) {
|
|
if (options.push && !options.replace) {
|
|
// Cache current container element before replacing it
|
|
cachePush(pjax.state.id, [options.container, cloneContents(context)])
|
|
|
|
window.history.pushState(null, "", options.requestUrl)
|
|
}
|
|
|
|
fire('pjax:start', [xhr, options])
|
|
fire('pjax:send', [xhr, options])
|
|
}
|
|
|
|
return pjax.xhr
|
|
}
|
|
|
|
// Public: Reload current page with pjax.
|
|
//
|
|
// Returns whatever $.pjax returns.
|
|
function pjaxReload(container, options) {
|
|
var defaults = {
|
|
url: window.location.href,
|
|
push: false,
|
|
replace: true,
|
|
scrollTo: false
|
|
}
|
|
|
|
return pjax($.extend(defaults, optionsFor(container, options)))
|
|
}
|
|
|
|
// Internal: Hard replace current state with url.
|
|
//
|
|
// Work for around WebKit
|
|
// https://bugs.webkit.org/show_bug.cgi?id=93506
|
|
//
|
|
// Returns nothing.
|
|
function locationReplace(url) {
|
|
window.history.replaceState(null, "", pjax.state.url)
|
|
window.location.replace(url)
|
|
}
|
|
|
|
|
|
var initialPop = true
|
|
var initialURL = window.location.href
|
|
var initialState = window.history.state
|
|
|
|
// Initialize $.pjax.state if possible
|
|
// Happens when reloading a page and coming forward from a different
|
|
// session history.
|
|
if (initialState && initialState.container) {
|
|
pjax.state = initialState
|
|
}
|
|
|
|
// Non-webkit browsers don't fire an initial popstate event
|
|
if ('state' in window.history) {
|
|
initialPop = false
|
|
}
|
|
|
|
// popstate handler takes care of the back and forward buttons
|
|
//
|
|
// You probably shouldn't use pjax on pages with other pushState
|
|
// stuff yet.
|
|
function onPjaxPopstate(event) {
|
|
|
|
// Hitting back or forward should override any pending PJAX request.
|
|
if (!initialPop) {
|
|
abortXHR(pjax.xhr)
|
|
}
|
|
|
|
var previousState = pjax.state
|
|
var state = event.state
|
|
var direction
|
|
|
|
if (state && state.container) {
|
|
// When coming forward from a separate history session, will get an
|
|
// initial pop with a state we are already at. Skip reloading the current
|
|
// page.
|
|
if (initialPop && initialURL == state.url) return
|
|
|
|
if (previousState) {
|
|
// If popping back to the same state, just skip.
|
|
// Could be clicking back from hashchange rather than a pushState.
|
|
if (previousState.id === state.id) return
|
|
|
|
// Since state IDs always increase, we can deduce the navigation direction
|
|
direction = previousState.id < state.id ? 'forward' : 'back'
|
|
}
|
|
|
|
var cache = cacheMapping[state.id] || []
|
|
var containerSelector = cache[0] || state.container
|
|
var container = $(containerSelector), contents = cache[1]
|
|
|
|
if (container.length) {
|
|
if (previousState) {
|
|
// Cache current container before replacement and inform the
|
|
// cache which direction the history shifted.
|
|
cachePop(direction, previousState.id, [containerSelector, cloneContents(container)])
|
|
}
|
|
|
|
var popstateEvent = $.Event('pjax:popstate', {
|
|
state: state,
|
|
direction: direction
|
|
})
|
|
container.trigger(popstateEvent)
|
|
|
|
var options = {
|
|
id: state.id,
|
|
url: state.url,
|
|
container: containerSelector,
|
|
push: false,
|
|
fragment: state.fragment,
|
|
timeout: state.timeout,
|
|
scrollTo: false
|
|
}
|
|
|
|
if (contents) {
|
|
container.trigger('pjax:start', [null, options])
|
|
|
|
pjax.state = state
|
|
if (state.title) document.title = state.title
|
|
var beforeReplaceEvent = $.Event('pjax:beforeReplace', {
|
|
state: state,
|
|
previousState: previousState
|
|
})
|
|
container.trigger(beforeReplaceEvent, [contents, options])
|
|
container.html(contents)
|
|
|
|
container.trigger('pjax:end', [null, options])
|
|
} else {
|
|
pjax(options)
|
|
}
|
|
|
|
// Force reflow/relayout before the browser tries to restore the
|
|
// scroll position.
|
|
container[0].offsetHeight // eslint-disable-line no-unused-expressions
|
|
} else {
|
|
locationReplace(location.href)
|
|
}
|
|
}
|
|
initialPop = false
|
|
}
|
|
|
|
// Fallback version of main pjax function for browsers that don't
|
|
// support pushState.
|
|
//
|
|
// Returns nothing since it retriggers a hard form submission.
|
|
function fallbackPjax(options) {
|
|
var url = $.isFunction(options.url) ? options.url() : options.url,
|
|
method = options.type ? options.type.toUpperCase() : 'GET'
|
|
|
|
var form = $('<form>', {
|
|
method: method === 'GET' ? 'GET' : 'POST',
|
|
action: url,
|
|
style: 'display:none'
|
|
})
|
|
|
|
if (method !== 'GET' && method !== 'POST') {
|
|
form.append($('<input>', {
|
|
type: 'hidden',
|
|
name: '_method',
|
|
value: method.toLowerCase()
|
|
}))
|
|
}
|
|
|
|
var data = options.data
|
|
if (typeof data === 'string') {
|
|
$.each(data.split('&'), function(index, value) {
|
|
var pair = value.split('=')
|
|
form.append($('<input>', {type: 'hidden', name: pair[0], value: pair[1]}))
|
|
})
|
|
} else if ($.isArray(data)) {
|
|
$.each(data, function(index, value) {
|
|
form.append($('<input>', {type: 'hidden', name: value.name, value: value.value}))
|
|
})
|
|
} else if (typeof data === 'object') {
|
|
var key
|
|
for (key in data)
|
|
form.append($('<input>', {type: 'hidden', name: key, value: data[key]}))
|
|
}
|
|
|
|
$(document.body).append(form)
|
|
form.submit()
|
|
}
|
|
|
|
// Internal: Abort an XmlHttpRequest if it hasn't been completed,
|
|
// also removing its event handlers.
|
|
function abortXHR(xhr) {
|
|
if ( xhr && xhr.readyState < 4) {
|
|
xhr.onreadystatechange = $.noop
|
|
xhr.abort()
|
|
}
|
|
}
|
|
|
|
// Internal: Generate unique id for state object.
|
|
//
|
|
// Use a timestamp instead of a counter since ids should still be
|
|
// unique across page loads.
|
|
//
|
|
// Returns Number.
|
|
function uniqueId() {
|
|
return (new Date).getTime()
|
|
}
|
|
|
|
function cloneContents(container) {
|
|
var cloned = container.clone()
|
|
// Unmark script tags as already being eval'd so they can get executed again
|
|
// when restored from cache. HAXX: Uses jQuery internal method.
|
|
cloned.find('script').each(function(){
|
|
if (!this.src) $._data(this, 'globalEval', false)
|
|
})
|
|
return cloned.contents()
|
|
}
|
|
|
|
// Internal: Strip internal query params from parsed URL.
|
|
//
|
|
// Returns sanitized url.href String.
|
|
function stripInternalParams(url) {
|
|
url.search = url.search.replace(/([?&])(_pjax|_)=[^&]*/g, '').replace(/^&/, '')
|
|
return url.href.replace(/\?($|#)/, '$1')
|
|
}
|
|
|
|
// Internal: Parse URL components and returns a Locationish object.
|
|
//
|
|
// url - String URL
|
|
//
|
|
// Returns HTMLAnchorElement that acts like Location.
|
|
function parseURL(url) {
|
|
var a = document.createElement('a')
|
|
a.href = url
|
|
return a
|
|
}
|
|
|
|
// Internal: Return the `href` component of given URL object with the hash
|
|
// portion removed.
|
|
//
|
|
// location - Location or HTMLAnchorElement
|
|
//
|
|
// Returns String
|
|
function stripHash(location) {
|
|
return location.href.replace(/#.*/, '')
|
|
}
|
|
|
|
// Internal: Build options Object for arguments.
|
|
//
|
|
// For convenience the first parameter can be either the container or
|
|
// the options object.
|
|
//
|
|
// Examples
|
|
//
|
|
// optionsFor('#container')
|
|
// // => {container: '#container'}
|
|
//
|
|
// optionsFor('#container', {push: true})
|
|
// // => {container: '#container', push: true}
|
|
//
|
|
// optionsFor({container: '#container', push: true})
|
|
// // => {container: '#container', push: true}
|
|
//
|
|
// Returns options Object.
|
|
function optionsFor(container, options) {
|
|
if (container && options) {
|
|
options = $.extend({}, options)
|
|
options.container = container
|
|
return options
|
|
} else if ($.isPlainObject(container)) {
|
|
return container
|
|
} else {
|
|
return {container: container}
|
|
}
|
|
}
|
|
|
|
// Internal: Filter and find all elements matching the selector.
|
|
//
|
|
// Where $.fn.find only matches descendants, findAll will test all the
|
|
// top level elements in the jQuery object as well.
|
|
//
|
|
// elems - jQuery object of Elements
|
|
// selector - String selector to match
|
|
//
|
|
// Returns a jQuery object.
|
|
function findAll(elems, selector) {
|
|
return elems.filter(selector).add(elems.find(selector))
|
|
}
|
|
|
|
function parseHTML(html) {
|
|
return $.parseHTML(html, document, true)
|
|
}
|
|
|
|
// Internal: Extracts container and metadata from response.
|
|
//
|
|
// 1. Extracts X-PJAX-URL header if set
|
|
// 2. Extracts inline <title> tags
|
|
// 3. Builds response Element and extracts fragment if set
|
|
//
|
|
// data - String response data
|
|
// xhr - XHR response
|
|
// options - pjax options Object
|
|
//
|
|
// Returns an Object with url, title, and contents keys.
|
|
function extractContainer(data, xhr, options) {
|
|
var obj = {}, fullDocument = /<html/i.test(data)
|
|
|
|
// Prefer X-PJAX-URL header if it was set, otherwise fallback to
|
|
// using the original requested url.
|
|
var serverUrl = xhr.getResponseHeader('X-PJAX-URL')
|
|
obj.url = serverUrl ? stripInternalParams(parseURL(serverUrl)) : options.requestUrl
|
|
|
|
var $head, $body
|
|
// Attempt to parse response html into elements
|
|
if (fullDocument) {
|
|
$body = $(parseHTML(data.match(/<body[^>]*>([\s\S.]*)<\/body>/i)[0]))
|
|
var head = data.match(/<head[^>]*>([\s\S.]*)<\/head>/i)
|
|
$head = head != null ? $(parseHTML(head[0])) : $body
|
|
} else {
|
|
$head = $body = $(parseHTML(data))
|
|
}
|
|
|
|
// If response data is empty, return fast
|
|
if ($body.length === 0)
|
|
return obj
|
|
|
|
// If there's a <title> tag in the header, use it as
|
|
// the page's title.
|
|
obj.title = findAll($head, 'title').last().text()
|
|
|
|
if (options.fragment) {
|
|
var $fragment = $body
|
|
// If they specified a fragment, look for it in the response
|
|
// and pull it out.
|
|
if (options.fragment !== 'body') {
|
|
$fragment = findAll($fragment, options.fragment).first()
|
|
}
|
|
|
|
if ($fragment.length) {
|
|
obj.contents = options.fragment === 'body' ? $fragment : $fragment.contents()
|
|
|
|
// If there's no title, look for data-title and title attributes
|
|
// on the fragment
|
|
if (!obj.title)
|
|
obj.title = $fragment.attr('title') || $fragment.data('title')
|
|
}
|
|
|
|
} else if (!fullDocument) {
|
|
obj.contents = $body
|
|
}
|
|
|
|
// Clean up any <title> tags
|
|
if (obj.contents) {
|
|
// Remove any parent title elements
|
|
obj.contents = obj.contents.not(function() { return $(this).is('title') })
|
|
|
|
// Then scrub any titles from their descendants
|
|
obj.contents.find('title').remove()
|
|
|
|
// Gather all script[src] elements
|
|
obj.scripts = findAll(obj.contents, 'script[src]').remove()
|
|
obj.contents = obj.contents.not(obj.scripts)
|
|
}
|
|
|
|
// Trim any whitespace off the title
|
|
if (obj.title) obj.title = $.trim(obj.title)
|
|
|
|
return obj
|
|
}
|
|
|
|
// Load an execute scripts using standard script request.
|
|
//
|
|
// Avoids jQuery's traditional $.getScript which does a XHR request and
|
|
// globalEval.
|
|
//
|
|
// scripts - jQuery object of script Elements
|
|
//
|
|
// Returns nothing.
|
|
function executeScriptTags(scripts) {
|
|
if (!scripts) return
|
|
|
|
var existingScripts = $('script[src]')
|
|
|
|
scripts.each(function() {
|
|
var src = this.src
|
|
var matchedScripts = existingScripts.filter(function() {
|
|
return this.src === src
|
|
})
|
|
if (matchedScripts.length) return
|
|
|
|
var script = document.createElement('script')
|
|
var type = $(this).attr('type')
|
|
if (type) script.type = type
|
|
script.src = $(this).attr('src')
|
|
document.head.appendChild(script)
|
|
})
|
|
}
|
|
|
|
// Internal: History DOM caching class.
|
|
var cacheMapping = {}
|
|
var cacheForwardStack = []
|
|
var cacheBackStack = []
|
|
|
|
// Push previous state id and container contents into the history
|
|
// cache. Should be called in conjunction with `pushState` to save the
|
|
// previous container contents.
|
|
//
|
|
// id - State ID Number
|
|
// value - DOM Element to cache
|
|
//
|
|
// Returns nothing.
|
|
function cachePush(id, value) {
|
|
cacheMapping[id] = value
|
|
cacheBackStack.push(id)
|
|
|
|
// Remove all entries in forward history stack after pushing a new page.
|
|
trimCacheStack(cacheForwardStack, 0)
|
|
|
|
// Trim back history stack to max cache length.
|
|
trimCacheStack(cacheBackStack, pjax.defaults.maxCacheLength)
|
|
}
|
|
|
|
// Shifts cache from directional history cache. Should be
|
|
// called on `popstate` with the previous state id and container
|
|
// contents.
|
|
//
|
|
// direction - "forward" or "back" String
|
|
// id - State ID Number
|
|
// value - DOM Element to cache
|
|
//
|
|
// Returns nothing.
|
|
function cachePop(direction, id, value) {
|
|
var pushStack, popStack
|
|
cacheMapping[id] = value
|
|
|
|
if (direction === 'forward') {
|
|
pushStack = cacheBackStack
|
|
popStack = cacheForwardStack
|
|
} else {
|
|
pushStack = cacheForwardStack
|
|
popStack = cacheBackStack
|
|
}
|
|
|
|
pushStack.push(id)
|
|
id = popStack.pop()
|
|
if (id) delete cacheMapping[id]
|
|
|
|
// Trim whichever stack we just pushed to to max cache length.
|
|
trimCacheStack(pushStack, pjax.defaults.maxCacheLength)
|
|
}
|
|
|
|
// Trim a cache stack (either cacheBackStack or cacheForwardStack) to be no
|
|
// longer than the specified length, deleting cached DOM elements as necessary.
|
|
//
|
|
// stack - Array of state IDs
|
|
// length - Maximum length to trim to
|
|
//
|
|
// Returns nothing.
|
|
function trimCacheStack(stack, length) {
|
|
while (stack.length > length)
|
|
delete cacheMapping[stack.shift()]
|
|
}
|
|
|
|
// Public: Find version identifier for the initial page load.
|
|
//
|
|
// Returns String version or undefined.
|
|
function findVersion() {
|
|
return $('meta').filter(function() {
|
|
var name = $(this).attr('http-equiv')
|
|
return name && name.toUpperCase() === 'X-PJAX-VERSION'
|
|
}).attr('content')
|
|
}
|
|
|
|
// Install pjax functions on $.pjax to enable pushState behavior.
|
|
//
|
|
// Does nothing if already enabled.
|
|
//
|
|
// Examples
|
|
//
|
|
// $.pjax.enable()
|
|
//
|
|
// Returns nothing.
|
|
function enable() {
|
|
$.fn.pjax = fnPjax
|
|
$.pjax = pjax
|
|
$.pjax.enable = $.noop
|
|
$.pjax.disable = disable
|
|
$.pjax.click = handleClick
|
|
$.pjax.submit = handleSubmit
|
|
$.pjax.reload = pjaxReload
|
|
$.pjax.defaults = {
|
|
timeout: 650,
|
|
push: true,
|
|
replace: false,
|
|
type: 'GET',
|
|
dataType: 'html',
|
|
scrollTo: 0,
|
|
maxCacheLength: 20,
|
|
version: findVersion
|
|
}
|
|
$(window).on('popstate.pjax', onPjaxPopstate)
|
|
}
|
|
|
|
// Disable pushState behavior.
|
|
//
|
|
// This is the case when a browser doesn't support pushState. It is
|
|
// sometimes useful to disable pushState for debugging on a modern
|
|
// browser.
|
|
//
|
|
// Examples
|
|
//
|
|
// $.pjax.disable()
|
|
//
|
|
// Returns nothing.
|
|
function disable() {
|
|
$.fn.pjax = function() { return this }
|
|
$.pjax = fallbackPjax
|
|
$.pjax.enable = enable
|
|
$.pjax.disable = $.noop
|
|
$.pjax.click = $.noop
|
|
$.pjax.submit = $.noop
|
|
$.pjax.reload = function() { window.location.reload() }
|
|
|
|
$(window).off('popstate.pjax', onPjaxPopstate)
|
|
}
|
|
|
|
|
|
// Add the state property to jQuery's event object so we can use it in
|
|
// $(window).bind('popstate')
|
|
if ($.event.props && $.inArray('state', $.event.props) < 0) {
|
|
$.event.props.push('state')
|
|
} else if (!('state' in $.Event.prototype)) {
|
|
$.event.addProp('state')
|
|
}
|
|
|
|
// Is pjax supported by this browser?
|
|
$.support.pjax =
|
|
window.history && window.history.pushState && window.history.replaceState &&
|
|
// pushState isn't reliable on iOS until 5.
|
|
!navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]\D|WebApps\/.+CFNetwork)/)
|
|
|
|
if ($.support.pjax) {
|
|
enable()
|
|
} else {
|
|
disable()
|
|
}
|
|
|
|
})(jQuery)
|