//! moment.js
//! version : 2.9.0
//! authors : Tim Wood, Iskren Chernev, Moment.js contributors
//! license : MIT
//! momentjs.com
(function (a) {
    function b(a, b, c) { switch (arguments.length) { case 2: return null != a ? a : b; case 3: return null != a ? a : null != b ? b : c; default: throw new Error("Implement me") } } function c(a, b) { return Bb.call(a, b) } function d() { return { empty: !1, unusedTokens: [], unusedInput: [], overflow: -2, charsLeftOver: 0, nullInput: !1, invalidMonth: null, invalidFormat: !1, userInvalidated: !1, iso: !1 } } function e(a) { vb.suppressDeprecationWarnings === !1 && "undefined" != typeof console && console.warn && console.warn("Deprecation warning: " + a) } function f(a, b) { var c = !0; return o(function () { return c && (e(a), c = !1), b.apply(this, arguments) }, b) } function g(a, b) { sc[a] || (e(b), sc[a] = !0) } function h(a, b) { return function (c) { return r(a.call(this, c), b) } } function i(a, b) { return function (c) { return this.localeData().ordinal(a.call(this, c), b) } } function j(a, b) { var c, d, e = 12 * (b.year() - a.year()) + (b.month() - a.month()), f = a.clone().add(e, "months"); return 0 > b - f ? (c = a.clone().add(e - 1, "months"), d = (b - f) / (f - c)) : (c = a.clone().add(e + 1, "months"), d = (b - f) / (c - f)), -(e + d) } function k(a, b, c) { var d; return null == c ? b : null != a.meridiemHour ? a.meridiemHour(b, c) : null != a.isPM ? (d = a.isPM(c), d && 12 > b && (b += 12), d || 12 !== b || (b = 0), b) : b } function l() { } function m(a, b) { b !== !1 && H(a), p(this, a), this._d = new Date(+a._d), uc === !1 && (uc = !0, vb.updateOffset(this), uc = !1) } function n(a) { var b = A(a), c = b.year || 0, d = b.quarter || 0, e = b.month || 0, f = b.week || 0, g = b.day || 0, h = b.hour || 0, i = b.minute || 0, j = b.second || 0, k = b.millisecond || 0; this._milliseconds = +k + 1e3 * j + 6e4 * i + 36e5 * h, this._days = +g + 7 * f, this._months = +e + 3 * d + 12 * c, this._data = {}, this._locale = vb.localeData(), this._bubble() } function o(a, b) { for (var d in b) c(b, d) && (a[d] = b[d]); return c(b, "toString") && (a.toString = b.toString), c(b, "valueOf") && (a.valueOf = b.valueOf), a } function p(a, b) { var c, d, e; if ("undefined" != typeof b._isAMomentObject && (a._isAMomentObject = b._isAMomentObject), "undefined" != typeof b._i && (a._i = b._i), "undefined" != typeof b._f && (a._f = b._f), "undefined" != typeof b._l && (a._l = b._l), "undefined" != typeof b._strict && (a._strict = b._strict), "undefined" != typeof b._tzm && (a._tzm = b._tzm), "undefined" != typeof b._isUTC && (a._isUTC = b._isUTC), "undefined" != typeof b._offset && (a._offset = b._offset), "undefined" != typeof b._pf && (a._pf = b._pf), "undefined" != typeof b._locale && (a._locale = b._locale), Kb.length > 0) for (c in Kb) d = Kb[c], e = b[d], "undefined" != typeof e && (a[d] = e); return a } function q(a) { return 0 > a ? Math.ceil(a) : Math.floor(a) } function r(a, b, c) { for (var d = "" + Math.abs(a), e = a >= 0; d.length < b;) d = "0" + d; return (e ? c ? "+" : "" : "-") + d } function s(a, b) { var c = { milliseconds: 0, months: 0 }; return c.months = b.month() - a.month() + 12 * (b.year() - a.year()), a.clone().add(c.months, "M").isAfter(b) && --c.months, c.milliseconds = +b - +a.clone().add(c.months, "M"), c } function t(a, b) { var c; return b = M(b, a), a.isBefore(b) ? c = s(a, b) : (c = s(b, a), c.milliseconds = -c.milliseconds, c.months = -c.months), c } function u(a, b) { return function (c, d) { var e, f; return null === d || isNaN(+d) || (g(b, "moment()." + b + "(period, number) is deprecated. Please use moment()." + b + "(number, period)."), f = c, c = d, d = f), c = "string" == typeof c ? +c : c, e = vb.duration(c, d), v(this, e, a), this } } function v(a, b, c, d) { var e = b._milliseconds, f = b._days, g = b._months; d = null == d ? !0 : d, e && a._d.setTime(+a._d + e * c), f && pb(a, "Date", ob(a, "Date") + f * c), g && nb(a, ob(a, "Month") + g * c), d && vb.updateOffset(a, f || g) } function w(a) { return "[object Array]" === Object.prototype.toString.call(a) } function x(a) { return "[object Date]" === Object.prototype.toString.call(a) || a instanceof Date } function y(a, b, c) { var d, e = Math.min(a.length, b.length), f = Math.abs(a.length - b.length), g = 0; for (d = 0; e > d; d++) (c && a[d] !== b[d] || !c && C(a[d]) !== C(b[d])) && g++; return g + f } function z(a) { if (a) { var b = a.toLowerCase().replace(/(.)s$/, "$1"); a = lc[a] || mc[b] || b } return a } function A(a) { var b, d, e = {}; for (d in a) c(a, d) && (b = z(d), b && (e[b] = a[d])); return e } function B(b) { var c, d; if (0 === b.indexOf("week")) c = 7, d = "day"; else { if (0 !== b.indexOf("month")) return; c = 12, d = "month" } vb[b] = function (e, f) { var g, h, i = vb._locale[b], j = []; if ("number" == typeof e && (f = e, e = a), h = function (a) { var b = vb().utc().set(d, a); return i.call(vb._locale, b, e || "") }, null != f) return h(f); for (g = 0; c > g; g++) j.push(h(g)); return j } } function C(a) { var b = +a, c = 0; return 0 !== b && isFinite(b) && (c = b >= 0 ? Math.floor(b) : Math.ceil(b)), c } function D(a, b) { return new Date(Date.UTC(a, b + 1, 0)).getUTCDate() } function E(a, b, c) { return jb(vb([a, 11, 31 + b - c]), b, c).week } function F(a) { return G(a) ? 366 : 365 } function G(a) { return a % 4 === 0 && a % 100 !== 0 || a % 400 === 0 } function H(a) { var b; a._a && -2 === a._pf.overflow && (b = a._a[Db] < 0 || a._a[Db] > 11 ? Db : a._a[Eb] < 1 || a._a[Eb] > D(a._a[Cb], a._a[Db]) ? Eb : a._a[Fb] < 0 || a._a[Fb] > 24 || 24 === a._a[Fb] && (0 !== a._a[Gb] || 0 !== a._a[Hb] || 0 !== a._a[Ib]) ? Fb : a._a[Gb] < 0 || a._a[Gb] > 59 ? Gb : a._a[Hb] < 0 || a._a[Hb] > 59 ? Hb : a._a[Ib] < 0 || a._a[Ib] > 999 ? Ib : -1, a._pf._overflowDayOfYear && (Cb > b || b > Eb) && (b = Eb), a._pf.overflow = b) } function I(b) { return null == b._isValid && (b._isValid = !isNaN(b._d.getTime()) && b._pf.overflow < 0 && !b._pf.empty && !b._pf.invalidMonth && !b._pf.nullInput && !b._pf.invalidFormat && !b._pf.userInvalidated, b._strict && (b._isValid = b._isValid && 0 === b._pf.charsLeftOver && 0 === b._pf.unusedTokens.length && b._pf.bigHour === a)), b._isValid } function J(a) { return a ? a.toLowerCase().replace("_", "-") : a } function K(a) { for (var b, c, d, e, f = 0; f < a.length;) { for (e = J(a[f]).split("-"), b = e.length, c = J(a[f + 1]), c = c ? c.split("-") : null; b > 0;) { if (d = L(e.slice(0, b).join("-"))) return d; if (c && c.length >= b && y(e, c, !0) >= b - 1) break; b-- } f++ } return null } function L(a) { var b = null; if (!Jb[a] && Lb) try { b = vb.locale(), require("./locale/" + a), vb.locale(b) } catch (c) { } return Jb[a] } function M(a, b) { var c, d; return b._isUTC ? (c = b.clone(), d = (vb.isMoment(a) || x(a) ? +a : +vb(a)) - +c, c._d.setTime(+c._d + d), vb.updateOffset(c, !1), c) : vb(a).local() } function N(a) { return a.match(/\[[\s\S]/) ? a.replace(/^\[|\]$/g, "") : a.replace(/\\/g, "") } function O(a) { var b, c, d = a.match(Pb); for (b = 0, c = d.length; c > b; b++) d[b] = rc[d[b]] ? rc[d[b]] : N(d[b]); return function (e) { var f = ""; for (b = 0; c > b; b++) f += d[b] instanceof Function ? d[b].call(e, a) : d[b]; return f } } function P(a, b) { return a.isValid() ? (b = Q(b, a.localeData()), nc[b] || (nc[b] = O(b)), nc[b](a)) : a.localeData().invalidDate() } function Q(a, b) { function c(a) { return b.longDateFormat(a) || a } var d = 5; for (Qb.lastIndex = 0; d >= 0 && Qb.test(a) ;) a = a.replace(Qb, c), Qb.lastIndex = 0, d -= 1; return a } function R(a, b) { var c, d = b._strict; switch (a) { case "Q": return _b; case "DDDD": return bc; case "YYYY": case "GGGG": case "gggg": return d ? cc : Tb; case "Y": case "G": case "g": return ec; case "YYYYYY": case "YYYYY": case "GGGGG": case "ggggg": return d ? dc : Ub; case "S": if (d) return _b; case "SS": if (d) return ac; case "SSS": if (d) return bc; case "DDD": return Sb; case "MMM": case "MMMM": case "dd": case "ddd": case "dddd": return Wb; case "a": case "A": return b._locale._meridiemParse; case "x": return Zb; case "X": return $b; case "Z": case "ZZ": return Xb; case "T": return Yb; case "SSSS": return Vb; case "MM": case "DD": case "YY": case "GG": case "gg": case "HH": case "hh": case "mm": case "ss": case "ww": case "WW": return d ? ac : Rb; case "M": case "D": case "d": case "H": case "h": case "m": case "s": case "w": case "W": case "e": case "E": return Rb; case "Do": return d ? b._locale._ordinalParse : b._locale._ordinalParseLenient; default: return c = new RegExp($(Z(a.replace("\\", "")), "i")) } } function S(a) { a = a || ""; var b = a.match(Xb) || [], c = b[b.length - 1] || [], d = (c + "").match(jc) || ["-", 0, 0], e = +(60 * d[1]) + C(d[2]); return "+" === d[0] ? e : -e } function T(a, b, c) { var d, e = c._a; switch (a) { case "Q": null != b && (e[Db] = 3 * (C(b) - 1)); break; case "M": case "MM": null != b && (e[Db] = C(b) - 1); break; case "MMM": case "MMMM": d = c._locale.monthsParse(b, a, c._strict), null != d ? e[Db] = d : c._pf.invalidMonth = b; break; case "D": case "DD": null != b && (e[Eb] = C(b)); break; case "Do": null != b && (e[Eb] = C(parseInt(b.match(/\d{1,2}/)[0], 10))); break; case "DDD": case "DDDD": null != b && (c._dayOfYear = C(b)); break; case "YY": e[Cb] = vb.parseTwoDigitYear(b); break; case "YYYY": case "YYYYY": case "YYYYYY": e[Cb] = C(b); break; case "a": case "A": c._meridiem = b; break; case "h": case "hh": c._pf.bigHour = !0; case "H": case "HH": e[Fb] = C(b); break; case "m": case "mm": e[Gb] = C(b); break; case "s": case "ss": e[Hb] = C(b); break; case "S": case "SS": case "SSS": case "SSSS": e[Ib] = C(1e3 * ("0." + b)); break; case "x": c._d = new Date(C(b)); break; case "X": c._d = new Date(1e3 * parseFloat(b)); break; case "Z": case "ZZ": c._useUTC = !0, c._tzm = S(b); break; case "dd": case "ddd": case "dddd": d = c._locale.weekdaysParse(b), null != d ? (c._w = c._w || {}, c._w.d = d) : c._pf.invalidWeekday = b; break; case "w": case "ww": case "W": case "WW": case "d": case "e": case "E": a = a.substr(0, 1); case "gggg": case "GGGG": case "GGGGG": a = a.substr(0, 2), b && (c._w = c._w || {}, c._w[a] = C(b)); break; case "gg": case "GG": c._w = c._w || {}, c._w[a] = vb.parseTwoDigitYear(b) } } function U(a) { var c, d, e, f, g, h, i; c = a._w, null != c.GG || null != c.W || null != c.E ? (g = 1, h = 4, d = b(c.GG, a._a[Cb], jb(vb(), 1, 4).year), e = b(c.W, 1), f = b(c.E, 1)) : (g = a._locale._week.dow, h = a._locale._week.doy, d = b(c.gg, a._a[Cb], jb(vb(), g, h).year), e = b(c.w, 1), null != c.d ? (f = c.d, g > f && ++e) : f = null != c.e ? c.e + g : g), i = kb(d, e, f, h, g), a._a[Cb] = i.year, a._dayOfYear = i.dayOfYear } function V(a) { var c, d, e, f, g = []; if (!a._d) { for (e = X(a), a._w && null == a._a[Eb] && null == a._a[Db] && U(a), a._dayOfYear && (f = b(a._a[Cb], e[Cb]), a._dayOfYear > F(f) && (a._pf._overflowDayOfYear = !0), d = fb(f, 0, a._dayOfYear), a._a[Db] = d.getUTCMonth(), a._a[Eb] = d.getUTCDate()), c = 0; 3 > c && null == a._a[c]; ++c) a._a[c] = g[c] = e[c]; for (; 7 > c; c++) a._a[c] = g[c] = null == a._a[c] ? 2 === c ? 1 : 0 : a._a[c]; 24 === a._a[Fb] && 0 === a._a[Gb] && 0 === a._a[Hb] && 0 === a._a[Ib] && (a._nextDay = !0, a._a[Fb] = 0), a._d = (a._useUTC ? fb : eb).apply(null, g), null != a._tzm && a._d.setUTCMinutes(a._d.getUTCMinutes() - a._tzm), a._nextDay && (a._a[Fb] = 24) } } function W(a) { var b; a._d || (b = A(a._i), a._a = [b.year, b.month, b.day || b.date, b.hour, b.minute, b.second, b.millisecond], V(a)) } function X(a) { var b = new Date; return a._useUTC ? [b.getUTCFullYear(), b.getUTCMonth(), b.getUTCDate()] : [b.getFullYear(), b.getMonth(), b.getDate()] } function Y(b) { if (b._f === vb.ISO_8601) return void ab(b); b._a = [], b._pf.empty = !0; var c, d, e, f, g, h = "" + b._i, i = h.length, j = 0; for (e = Q(b._f, b._locale).match(Pb) || [], c = 0; c < e.length; c++) f = e[c], d = (h.match(R(f, b)) || [])[0], d && (g = h.substr(0, h.indexOf(d)), g.length > 0 && b._pf.unusedInput.push(g), h = h.slice(h.indexOf(d) + d.length), j += d.length), rc[f] ? (d ? b._pf.empty = !1 : b._pf.unusedTokens.push(f), T(f, d, b)) : b._strict && !d && b._pf.unusedTokens.push(f); b._pf.charsLeftOver = i - j, h.length > 0 && b._pf.unusedInput.push(h), b._pf.bigHour === !0 && b._a[Fb] <= 12 && (b._pf.bigHour = a), b._a[Fb] = k(b._locale, b._a[Fb], b._meridiem), V(b), H(b) } function Z(a) { return a.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (a, b, c, d, e) { return b || c || d || e }) } function $(a) { return a.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&") } function _(a) { var b, c, e, f, g; if (0 === a._f.length) return a._pf.invalidFormat = !0, void (a._d = new Date(0 / 0)); for (f = 0; f < a._f.length; f++) g = 0, b = p({}, a), null != a._useUTC && (b._useUTC = a._useUTC), b._pf = d(), b._f = a._f[f], Y(b), I(b) && (g += b._pf.charsLeftOver, g += 10 * b._pf.unusedTokens.length, b._pf.score = g, (null == e || e > g) && (e = g, c = b)); o(a, c || b) } function ab(a) { var b, c, d = a._i, e = fc.exec(d); if (e) { for (a._pf.iso = !0, b = 0, c = hc.length; c > b; b++) if (hc[b][1].exec(d)) { a._f = hc[b][0] + (e[6] || " "); break } for (b = 0, c = ic.length; c > b; b++) if (ic[b][1].exec(d)) { a._f += ic[b][0]; break } d.match(Xb) && (a._f += "Z"), Y(a) } else a._isValid = !1 } function bb(a) { ab(a), a._isValid === !1 && (delete a._isValid, vb.createFromInputFallback(a)) } function cb(a, b) { var c, d = []; for (c = 0; c < a.length; ++c) d.push(b(a[c], c)); return d } function db(b) { var c, d = b._i; d === a ? b._d = new Date : x(d) ? b._d = new Date(+d) : null !== (c = Mb.exec(d)) ? b._d = new Date(+c[1]) : "string" == typeof d ? bb(b) : w(d) ? (b._a = cb(d.slice(0), function (a) { return parseInt(a, 10) }), V(b)) : "object" == typeof d ? W(b) : "number" == typeof d ? b._d = new Date(d) : vb.createFromInputFallback(b) } function eb(a, b, c, d, e, f, g) { var h = new Date(a, b, c, d, e, f, g); return 1970 > a && h.setFullYear(a), h } function fb(a) { var b = new Date(Date.UTC.apply(null, arguments)); return 1970 > a && b.setUTCFullYear(a), b } function gb(a, b) { if ("string" == typeof a) if (isNaN(a)) { if (a = b.weekdaysParse(a), "number" != typeof a) return null } else a = parseInt(a, 10); return a } function hb(a, b, c, d, e) { return e.relativeTime(b || 1, !!c, a, d) } function ib(a, b, c) { var d = vb.duration(a).abs(), e = Ab(d.as("s")), f = Ab(d.as("m")), g = Ab(d.as("h")), h = Ab(d.as("d")), i = Ab(d.as("M")), j = Ab(d.as("y")), k = e < oc.s && ["s", e] || 1 === f && ["m"] || f < oc.m && ["mm", f] || 1 === g && ["h"] || g < oc.h && ["hh", g] || 1 === h && ["d"] || h < oc.d && ["dd", h] || 1 === i && ["M"] || i < oc.M && ["MM", i] || 1 === j && ["y"] || ["yy", j]; return k[2] = b, k[3] = +a > 0, k[4] = c, hb.apply({}, k) } function jb(a, b, c) { var d, e = c - b, f = c - a.day(); return f > e && (f -= 7), e - 7 > f && (f += 7), d = vb(a).add(f, "d"), { week: Math.ceil(d.dayOfYear() / 7), year: d.year() } } function kb(a, b, c, d, e) { var f, g, h = fb(a, 0, 1).getUTCDay(); return h = 0 === h ? 7 : h, c = null != c ? c : e, f = e - h + (h > d ? 7 : 0) - (e > h ? 7 : 0), g = 7 * (b - 1) + (c - e) + f + 1, { year: g > 0 ? a : a - 1, dayOfYear: g > 0 ? g : F(a - 1) + g } } function lb(b) { var c, d = b._i, e = b._f; return b._locale = b._locale || vb.localeData(b._l), null === d || e === a && "" === d ? vb.invalid({ nullInput: !0 }) : ("string" == typeof d && (b._i = d = b._locale.preparse(d)), vb.isMoment(d) ? new m(d, !0) : (e ? w(e) ? _(b) : Y(b) : db(b), c = new m(b), c._nextDay && (c.add(1, "d"), c._nextDay = a), c)) } function mb(a, b) { var c, d; if (1 === b.length && w(b[0]) && (b = b[0]), !b.length) return vb(); for (c = b[0], d = 1; d < b.length; ++d) b[d][a](c) && (c = b[d]); return c } function nb(a, b) { var c; return "string" == typeof b && (b = a.localeData().monthsParse(b), "number" != typeof b) ? a : (c = Math.min(a.date(), D(a.year(), b)), a._d["set" + (a._isUTC ? "UTC" : "") + "Month"](b, c), a) } function ob(a, b) { return a._d["get" + (a._isUTC ? "UTC" : "") + b]() } function pb(a, b, c) { return "Month" === b ? nb(a, c) : a._d["set" + (a._isUTC ? "UTC" : "") + b](c) } function qb(a, b) { return function (c) { return null != c ? (pb(this, a, c), vb.updateOffset(this, b), this) : ob(this, a) } } function rb(a) { return 400 * a / 146097 } function sb(a) { return 146097 * a / 400 } function tb(a) { vb.duration.fn[a] = function () { return this._data[a] } } function ub(a) { "undefined" == typeof ender && (wb = zb.moment, zb.moment = a ? f("Accessing Moment through the global scope is deprecated, and will be removed in an upcoming release.", vb) : vb) } for (var vb, wb, xb, yb = "2.9.0", zb = "undefined" == typeof global || "undefined" != typeof window && window !== global.window ? this : global, Ab = Math.round, Bb = Object.prototype.hasOwnProperty, Cb = 0, Db = 1, Eb = 2, Fb = 3, Gb = 4, Hb = 5, Ib = 6, Jb = {}, Kb = [], Lb = "undefined" != typeof module && module && module.exports, Mb = /^\/?Date\((\-?\d+)/i, Nb = /(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/, Ob = /^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/, Pb = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|x|X|zz?|ZZ?|.)/g, Qb = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, Rb = /\d\d?/, Sb = /\d{1,3}/, Tb = /\d{1,4}/, Ub = /[+\-]?\d{1,6}/, Vb = /\d+/, Wb = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i, Xb = /Z|[\+\-]\d\d:?\d\d/gi, Yb = /T/i, Zb = /[\+\-]?\d+/, $b = /[\+\-]?\d+(\.\d{1,3})?/, _b = /\d/, ac = /\d\d/, bc = /\d{3}/, cc = /\d{4}/, dc = /[+-]?\d{6}/, ec = /[+-]?\d+/, fc = /^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/, gc = "YYYY-MM-DDTHH:mm:ssZ", hc = [["YYYYYY-MM-DD", /[+-]\d{6}-\d{2}-\d{2}/], ["YYYY-MM-DD", /\d{4}-\d{2}-\d{2}/], ["GGGG-[W]WW-E", /\d{4}-W\d{2}-\d/], ["GGGG-[W]WW", /\d{4}-W\d{2}/], ["YYYY-DDD", /\d{4}-\d{3}/]], ic = [["HH:mm:ss.SSSS", /(T| )\d\d:\d\d:\d\d\.\d+/], ["HH:mm:ss", /(T| )\d\d:\d\d:\d\d/], ["HH:mm", /(T| )\d\d:\d\d/], ["HH", /(T| )\d\d/]], jc = /([\+\-]|\d\d)/gi, kc = ("Date|Hours|Minutes|Seconds|Milliseconds".split("|"), { Milliseconds: 1, Seconds: 1e3, Minutes: 6e4, Hours: 36e5, Days: 864e5, Months: 2592e6, Years: 31536e6 }), lc = { ms: "millisecond", s: "second", m: "minute", h: "hour", d: "day", D: "date", w: "week", W: "isoWeek", M: "month", Q: "quarter", y: "year", DDD: "dayOfYear", e: "weekday", E: "isoWeekday", gg: "weekYear", GG: "isoWeekYear" }, mc = { dayofyear: "dayOfYear", isoweekday: "isoWeekday", isoweek: "isoWeek", weekyear: "weekYear", isoweekyear: "isoWeekYear" }, nc = {}, oc = { s: 45, m: 45, h: 22, d: 26, M: 11 }, pc = "DDD w W M D d".split(" "), qc = "M D H h m s w W".split(" "), rc = { M: function () { return this.month() + 1 }, MMM: function (a) { return this.localeData().monthsShort(this, a) }, MMMM: function (a) { return this.localeData().months(this, a) }, D: function () { return this.date() }, DDD: function () { return this.dayOfYear() }, d: function () { return this.day() }, dd: function (a) { return this.localeData().weekdaysMin(this, a) }, ddd: function (a) { return this.localeData().weekdaysShort(this, a) }, dddd: function (a) { return this.localeData().weekdays(this, a) }, w: function () { return this.week() }, W: function () { return this.isoWeek() }, YY: function () { return r(this.year() % 100, 2) }, YYYY: function () { return r(this.year(), 4) }, YYYYY: function () { return r(this.year(), 5) }, YYYYYY: function () { var a = this.year(), b = a >= 0 ? "+" : "-"; return b + r(Math.abs(a), 6) }, gg: function () { return r(this.weekYear() % 100, 2) }, gggg: function () { return r(this.weekYear(), 4) }, ggggg: function () { return r(this.weekYear(), 5) }, GG: function () { return r(this.isoWeekYear() % 100, 2) }, GGGG: function () { return r(this.isoWeekYear(), 4) }, GGGGG: function () { return r(this.isoWeekYear(), 5) }, e: function () { return this.weekday() }, E: function () { return this.isoWeekday() }, a: function () { return this.localeData().meridiem(this.hours(), this.minutes(), !0) }, A: function () { return this.localeData().meridiem(this.hours(), this.minutes(), !1) }, H: function () { return this.hours() }, h: function () { return this.hours() % 12 || 12 }, m: function () { return this.minutes() }, s: function () { return this.seconds() }, S: function () { return C(this.milliseconds() / 100) }, SS: function () { return r(C(this.milliseconds() / 10), 2) }, SSS: function () { return r(this.milliseconds(), 3) }, SSSS: function () { return r(this.milliseconds(), 3) }, Z: function () { var a = this.utcOffset(), b = "+"; return 0 > a && (a = -a, b = "-"), b + r(C(a / 60), 2) + ":" + r(C(a) % 60, 2) }, ZZ: function () { var a = this.utcOffset(), b = "+"; return 0 > a && (a = -a, b = "-"), b + r(C(a / 60), 2) + r(C(a) % 60, 2) }, z: function () { return this.zoneAbbr() }, zz: function () { return this.zoneName() }, x: function () { return this.valueOf() }, X: function () { return this.unix() }, Q: function () { return this.quarter() } }, sc = {}, tc = ["months", "monthsShort", "weekdays", "weekdaysShort", "weekdaysMin"], uc = !1; pc.length;) xb = pc.pop(), rc[xb + "o"] = i(rc[xb], xb); for (; qc.length;) xb = qc.pop(), rc[xb + xb] = h(rc[xb], 2); rc.DDDD = h(rc.DDD, 3), o(l.prototype, { set: function (a) { var b, c; for (c in a) b = a[c], "function" == typeof b ? this[c] = b : this["_" + c] = b; this._ordinalParseLenient = new RegExp(this._ordinalParse.source + "|" + /\d{1,2}/.source) }, _months: "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), months: function (a) { return this._months[a.month()] }, _monthsShort: "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"), monthsShort: function (a) { return this._monthsShort[a.month()] }, monthsParse: function (a, b, c) { var d, e, f; for (this._monthsParse || (this._monthsParse = [], this._longMonthsParse = [], this._shortMonthsParse = []), d = 0; 12 > d; d++) { if (e = vb.utc([2e3, d]), c && !this._longMonthsParse[d] && (this._longMonthsParse[d] = new RegExp("^" + this.months(e, "").replace(".", "") + "$", "i"), this._shortMonthsParse[d] = new RegExp("^" + this.monthsShort(e, "").replace(".", "") + "$", "i")), c || this._monthsParse[d] || (f = "^" + this.months(e, "") + "|^" + this.monthsShort(e, ""), this._monthsParse[d] = new RegExp(f.replace(".", ""), "i")), c && "MMMM" === b && this._longMonthsParse[d].test(a)) return d; if (c && "MMM" === b && this._shortMonthsParse[d].test(a)) return d; if (!c && this._monthsParse[d].test(a)) return d } }, _weekdays: "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), weekdays: function (a) { return this._weekdays[a.day()] }, _weekdaysShort: "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"), weekdaysShort: function (a) { return this._weekdaysShort[a.day()] }, _weekdaysMin: "Su_Mo_Tu_We_Th_Fr_Sa".split("_"), weekdaysMin: function (a) { return this._weekdaysMin[a.day()] }, weekdaysParse: function (a) { var b, c, d; for (this._weekdaysParse || (this._weekdaysParse = []), b = 0; 7 > b; b++) if (this._weekdaysParse[b] || (c = vb([2e3, 1]).day(b), d = "^" + this.weekdays(c, "") + "|^" + this.weekdaysShort(c, "") + "|^" + this.weekdaysMin(c, ""), this._weekdaysParse[b] = new RegExp(d.replace(".", ""), "i")), this._weekdaysParse[b].test(a)) return b }, _longDateFormat: { LTS: "h:mm:ss A", LT: "h:mm A", L: "MM/DD/YYYY", LL: "MMMM D, YYYY", LLL: "MMMM D, YYYY LT", LLLL: "dddd, MMMM D, YYYY LT" }, longDateFormat: function (a) { var b = this._longDateFormat[a]; return !b && this._longDateFormat[a.toUpperCase()] && (b = this._longDateFormat[a.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (a) { return a.slice(1) }), this._longDateFormat[a] = b), b }, isPM: function (a) { return "p" === (a + "").toLowerCase().charAt(0) }, _meridiemParse: /[ap]\.?m?\.?/i, meridiem: function (a, b, c) { return a > 11 ? c ? "pm" : "PM" : c ? "am" : "AM" }, _calendar: { sameDay: "[Today at] LT", nextDay: "[Tomorrow at] LT", nextWeek: "dddd [at] LT", lastDay: "[Yesterday at] LT", lastWeek: "[Last] dddd [at] LT", sameElse: "L" }, calendar: function (a, b, c) { var d = this._calendar[a]; return "function" == typeof d ? d.apply(b, [c]) : d }, _relativeTime: { future: "in %s", past: "%s ago", s: "a few seconds", m: "a minute", mm: "%d minutes", h: "an hour", hh: "%d hours", d: "a day", dd: "%d days", M: "a month", MM: "%d months", y: "a year", yy: "%d years" }, relativeTime: function (a, b, c, d) { var e = this._relativeTime[c]; return "function" == typeof e ? e(a, b, c, d) : e.replace(/%d/i, a) }, pastFuture: function (a, b) { var c = this._relativeTime[a > 0 ? "future" : "past"]; return "function" == typeof c ? c(b) : c.replace(/%s/i, b) }, ordinal: function (a) { return this._ordinal.replace("%d", a) }, _ordinal: "%d", _ordinalParse: /\d{1,2}/, preparse: function (a) { return a }, postformat: function (a) { return a }, week: function (a) { return jb(a, this._week.dow, this._week.doy).week }, _week: { dow: 0, doy: 6 }, firstDayOfWeek: function () { return this._week.dow }, firstDayOfYear: function () { return this._week.doy }, _invalidDate: "Invalid date", invalidDate: function () { return this._invalidDate } }), vb = function (b, c, e, f) { var g; return "boolean" == typeof e && (f = e, e = a), g = {}, g._isAMomentObject = !0, g._i = b, g._f = c, g._l = e, g._strict = f, g._isUTC = !1, g._pf = d(), lb(g) }, vb.suppressDeprecationWarnings = !1, vb.createFromInputFallback = f("moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.", function (a) { a._d = new Date(a._i + (a._useUTC ? " UTC" : "")) }), vb.min = function () { var a = [].slice.call(arguments, 0); return mb("isBefore", a) }, vb.max = function () { var a = [].slice.call(arguments, 0); return mb("isAfter", a) }, vb.utc = function (b, c, e, f) { var g; return "boolean" == typeof e && (f = e, e = a), g = {}, g._isAMomentObject = !0, g._useUTC = !0, g._isUTC = !0, g._l = e, g._i = b, g._f = c, g._strict = f, g._pf = d(), lb(g).utc() }, vb.unix = function (a) { return vb(1e3 * a) }, vb.duration = function (a, b) { var d, e, f, g, h = a, i = null; return vb.isDuration(a) ? h = { ms: a._milliseconds, d: a._days, M: a._months } : "number" == typeof a ? (h = {}, b ? h[b] = a : h.milliseconds = a) : (i = Nb.exec(a)) ? (d = "-" === i[1] ? -1 : 1, h = { y: 0, d: C(i[Eb]) * d, h: C(i[Fb]) * d, m: C(i[Gb]) * d, s: C(i[Hb]) * d, ms: C(i[Ib]) * d }) : (i = Ob.exec(a)) ? (d = "-" === i[1] ? -1 : 1, f = function (a) { var b = a && parseFloat(a.replace(",", ".")); return (isNaN(b) ? 0 : b) * d }, h = { y: f(i[2]), M: f(i[3]), d: f(i[4]), h: f(i[5]), m: f(i[6]), s: f(i[7]), w: f(i[8]) }) : null == h ? h = {} : "object" == typeof h && ("from" in h || "to" in h) && (g = t(vb(h.from), vb(h.to)), h = {}, h.ms = g.milliseconds, h.M = g.months), e = new n(h), vb.isDuration(a) && c(a, "_locale") && (e._locale = a._locale), e }, vb.version = yb, vb.defaultFormat = gc, vb.ISO_8601 = function () { }, vb.momentProperties = Kb, vb.updateOffset = function () { }, vb.relativeTimeThreshold = function (b, c) { return oc[b] === a ? !1 : c === a ? oc[b] : (oc[b] = c, !0) }, vb.lang = f("moment.lang is deprecated. Use moment.locale instead.", function (a, b) { return vb.locale(a, b) }), vb.locale = function (a, b) { var c; return a && (c = "undefined" != typeof b ? vb.defineLocale(a, b) : vb.localeData(a), c && (vb.duration._locale = vb._locale = c)), vb._locale._abbr }, vb.defineLocale = function (a, b) { return null !== b ? (b.abbr = a, Jb[a] || (Jb[a] = new l), Jb[a].set(b), vb.locale(a), Jb[a]) : (delete Jb[a], null) }, vb.langData = f("moment.langData is deprecated. Use moment.localeData instead.", function (a) { return vb.localeData(a) }), vb.localeData = function (a) { var b; if (a && a._locale && a._locale._abbr && (a = a._locale._abbr), !a) return vb._locale; if (!w(a)) { if (b = L(a)) return b; a = [a] } return K(a) }, vb.isMoment = function (a) { return a instanceof m || null != a && c(a, "_isAMomentObject") }, vb.isDuration = function (a) { return a instanceof n }; for (xb = tc.length - 1; xb >= 0; --xb) B(tc[xb]); vb.normalizeUnits = function (a) { return z(a) }, vb.invalid = function (a) { var b = vb.utc(0 / 0); return null != a ? o(b._pf, a) : b._pf.userInvalidated = !0, b }, vb.parseZone = function () { return vb.apply(null, arguments).parseZone() }, vb.parseTwoDigitYear = function (a) { return C(a) + (C(a) > 68 ? 1900 : 2e3) }, vb.isDate = x, o(vb.fn = m.prototype, { clone: function () { return vb(this) }, valueOf: function () { return +this._d - 6e4 * (this._offset || 0) }, unix: function () { return Math.floor(+this / 1e3) }, toString: function () { return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ") }, toDate: function () { return this._offset ? new Date(+this) : this._d }, toISOString: function () { var a = vb(this).utc(); return 0 < a.year() && a.year() <= 9999 ? "function" == typeof Date.prototype.toISOString ? this.toDate().toISOString() : P(a, "YYYY-MM-DD[T]HH:mm:ss.SSS[Z]") : P(a, "YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]") }, toArray: function () { var a = this; return [a.year(), a.month(), a.date(), a.hours(), a.minutes(), a.seconds(), a.milliseconds()] }, isValid: function () { return I(this) }, isDSTShifted: function () { return this._a ? this.isValid() && y(this._a, (this._isUTC ? vb.utc(this._a) : vb(this._a)).toArray()) > 0 : !1 }, parsingFlags: function () { return o({}, this._pf) }, invalidAt: function () { return this._pf.overflow }, utc: function (a) { return this.utcOffset(0, a) }, local: function (a) { return this._isUTC && (this.utcOffset(0, a), this._isUTC = !1, a && this.subtract(this._dateUtcOffset(), "m")), this }, format: function (a) { var b = P(this, a || vb.defaultFormat); return this.localeData().postformat(b) }, add: u(1, "add"), subtract: u(-1, "subtract"), diff: function (a, b, c) { var d, e, f = M(a, this), g = 6e4 * (f.utcOffset() - this.utcOffset()); return b = z(b), "year" === b || "month" === b || "quarter" === b ? (e = j(this, f), "quarter" === b ? e /= 3 : "year" === b && (e /= 12)) : (d = this - f, e = "second" === b ? d / 1e3 : "minute" === b ? d / 6e4 : "hour" === b ? d / 36e5 : "day" === b ? (d - g) / 864e5 : "week" === b ? (d - g) / 6048e5 : d), c ? e : q(e) }, from: function (a, b) { return vb.duration({ to: this, from: a }).locale(this.locale()).humanize(!b) }, fromNow: function (a) { return this.from(vb(), a) }, calendar: function (a) { var b = a || vb(), c = M(b, this).startOf("day"), d = this.diff(c, "days", !0), e = -6 > d ? "sameElse" : -1 > d ? "lastWeek" : 0 > d ? "lastDay" : 1 > d ? "sameDay" : 2 > d ? "nextDay" : 7 > d ? "nextWeek" : "sameElse"; return this.format(this.localeData().calendar(e, this, vb(b))) }, isLeapYear: function () { return G(this.year()) }, isDST: function () { return this.utcOffset() > this.clone().month(0).utcOffset() || this.utcOffset() > this.clone().month(5).utcOffset() }, day: function (a) { var b = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); return null != a ? (a = gb(a, this.localeData()), this.add(a - b, "d")) : b }, month: qb("Month", !0), startOf: function (a) { switch (a = z(a)) { case "year": this.month(0); case "quarter": case "month": this.date(1); case "week": case "isoWeek": case "day": this.hours(0); case "hour": this.minutes(0); case "minute": this.seconds(0); case "second": this.milliseconds(0) } return "week" === a ? this.weekday(0) : "isoWeek" === a && this.isoWeekday(1), "quarter" === a && this.month(3 * Math.floor(this.month() / 3)), this }, endOf: function (b) { return b = z(b), b === a || "millisecond" === b ? this : this.startOf(b).add(1, "isoWeek" === b ? "week" : b).subtract(1, "ms") }, isAfter: function (a, b) { var c; return b = z("undefined" != typeof b ? b : "millisecond"), "millisecond" === b ? (a = vb.isMoment(a) ? a : vb(a), +this > +a) : (c = vb.isMoment(a) ? +a : +vb(a), c < +this.clone().startOf(b)) }, isBefore: function (a, b) { var c; return b = z("undefined" != typeof b ? b : "millisecond"), "millisecond" === b ? (a = vb.isMoment(a) ? a : vb(a), +a > +this) : (c = vb.isMoment(a) ? +a : +vb(a), +this.clone().endOf(b) < c) }, isBetween: function (a, b, c) { return this.isAfter(a, c) && this.isBefore(b, c) }, isSame: function (a, b) { var c; return b = z(b || "millisecond"), "millisecond" === b ? (a = vb.isMoment(a) ? a : vb(a), +this === +a) : (c = +vb(a), +this.clone().startOf(b) <= c && c <= +this.clone().endOf(b)) }, min: f("moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548", function (a) { return a = vb.apply(null, arguments), this > a ? this : a }), max: f("moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548", function (a) { return a = vb.apply(null, arguments), a > this ? this : a }), zone: f("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779", function (a, b) { return null != a ? ("string" != typeof a && (a = -a), this.utcOffset(a, b), this) : -this.utcOffset() }), utcOffset: function (a, b) { var c, d = this._offset || 0; return null != a ? ("string" == typeof a && (a = S(a)), Math.abs(a) < 16 && (a = 60 * a), !this._isUTC && b && (c = this._dateUtcOffset()), this._offset = a, this._isUTC = !0, null != c && this.add(c, "m"), d !== a && (!b || this._changeInProgress ? v(this, vb.duration(a - d, "m"), 1, !1) : this._changeInProgress || (this._changeInProgress = !0, vb.updateOffset(this, !0), this._changeInProgress = null)), this) : this._isUTC ? d : this._dateUtcOffset() }, isLocal: function () { return !this._isUTC }, isUtcOffset: function () { return this._isUTC }, isUtc: function () { return this._isUTC && 0 === this._offset }, zoneAbbr: function () { return this._isUTC ? "UTC" : "" }, zoneName: function () { return this._isUTC ? "Coordinated Universal Time" : "" }, parseZone: function () { return this._tzm ? this.utcOffset(this._tzm) : "string" == typeof this._i && this.utcOffset(S(this._i)), this }, hasAlignedHourOffset: function (a) { return a = a ? vb(a).utcOffset() : 0, (this.utcOffset() - a) % 60 === 0 }, daysInMonth: function () { return D(this.year(), this.month()) }, dayOfYear: function (a) { var b = Ab((vb(this).startOf("day") - vb(this).startOf("year")) / 864e5) + 1; return null == a ? b : this.add(a - b, "d") }, quarter: function (a) { return null == a ? Math.ceil((this.month() + 1) / 3) : this.month(3 * (a - 1) + this.month() % 3) }, weekYear: function (a) { var b = jb(this, this.localeData()._week.dow, this.localeData()._week.doy).year; return null == a ? b : this.add(a - b, "y") }, isoWeekYear: function (a) { var b = jb(this, 1, 4).year; return null == a ? b : this.add(a - b, "y") }, week: function (a) { var b = this.localeData().week(this); return null == a ? b : this.add(7 * (a - b), "d") }, isoWeek: function (a) { var b = jb(this, 1, 4).week; return null == a ? b : this.add(7 * (a - b), "d") }, weekday: function (a) { var b = (this.day() + 7 - this.localeData()._week.dow) % 7; return null == a ? b : this.add(a - b, "d") }, isoWeekday: function (a) { return null == a ? this.day() || 7 : this.day(this.day() % 7 ? a : a - 7) }, isoWeeksInYear: function () { return E(this.year(), 1, 4) }, weeksInYear: function () { var a = this.localeData()._week; return E(this.year(), a.dow, a.doy) }, get: function (a) { return a = z(a), this[a]() }, set: function (a, b) { var c; if ("object" == typeof a) for (c in a) this.set(c, a[c]); else a = z(a), "function" == typeof this[a] && this[a](b); return this }, locale: function (b) { var c; return b === a ? this._locale._abbr : (c = vb.localeData(b), null != c && (this._locale = c), this) }, lang: f("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.", function (b) { return b === a ? this.localeData() : this.locale(b) }), localeData: function () { return this._locale }, _dateUtcOffset: function () { return 15 * -Math.round(this._d.getTimezoneOffset() / 15) } }), vb.fn.millisecond = vb.fn.milliseconds = qb("Milliseconds", !1), vb.fn.second = vb.fn.seconds = qb("Seconds", !1), vb.fn.minute = vb.fn.minutes = qb("Minutes", !1), vb.fn.hour = vb.fn.hours = qb("Hours", !0), vb.fn.date = qb("Date", !0), vb.fn.dates = f("dates accessor is deprecated. Use date instead.", qb("Date", !0)), vb.fn.year = qb("FullYear", !0), vb.fn.years = f("years accessor is deprecated. Use year instead.", qb("FullYear", !0)), vb.fn.days = vb.fn.day, vb.fn.months = vb.fn.month, vb.fn.weeks = vb.fn.week, vb.fn.isoWeeks = vb.fn.isoWeek, vb.fn.quarters = vb.fn.quarter, vb.fn.toJSON = vb.fn.toISOString, vb.fn.isUTC = vb.fn.isUtc, o(vb.duration.fn = n.prototype, {
        _bubble: function () { var a, b, c, d = this._milliseconds, e = this._days, f = this._months, g = this._data, h = 0; g.milliseconds = d % 1e3, a = q(d / 1e3), g.seconds = a % 60, b = q(a / 60), g.minutes = b % 60, c = q(b / 60), g.hours = c % 24, e += q(c / 24), h = q(rb(e)), e -= q(sb(h)), f += q(e / 30), e %= 30, h += q(f / 12), f %= 12, g.days = e, g.months = f, g.years = h }, abs: function () { return this._milliseconds = Math.abs(this._milliseconds), this._days = Math.abs(this._days), this._months = Math.abs(this._months), this._data.milliseconds = Math.abs(this._data.milliseconds), this._data.seconds = Math.abs(this._data.seconds), this._data.minutes = Math.abs(this._data.minutes), this._data.hours = Math.abs(this._data.hours), this._data.months = Math.abs(this._data.months), this._data.years = Math.abs(this._data.years), this }, weeks: function () { return q(this.days() / 7) }, valueOf: function () {
            return this._milliseconds + 864e5 * this._days + this._months % 12 * 2592e6 + 31536e6 * C(this._months / 12)
        }, humanize: function (a) { var b = ib(this, !a, this.localeData()); return a && (b = this.localeData().pastFuture(+this, b)), this.localeData().postformat(b) }, add: function (a, b) { var c = vb.duration(a, b); return this._milliseconds += c._milliseconds, this._days += c._days, this._months += c._months, this._bubble(), this }, subtract: function (a, b) { var c = vb.duration(a, b); return this._milliseconds -= c._milliseconds, this._days -= c._days, this._months -= c._months, this._bubble(), this }, get: function (a) { return a = z(a), this[a.toLowerCase() + "s"]() }, as: function (a) { var b, c; if (a = z(a), "month" === a || "year" === a) return b = this._days + this._milliseconds / 864e5, c = this._months + 12 * rb(b), "month" === a ? c : c / 12; switch (b = this._days + Math.round(sb(this._months / 12)), a) { case "week": return b / 7 + this._milliseconds / 6048e5; case "day": return b + this._milliseconds / 864e5; case "hour": return 24 * b + this._milliseconds / 36e5; case "minute": return 24 * b * 60 + this._milliseconds / 6e4; case "second": return 24 * b * 60 * 60 + this._milliseconds / 1e3; case "millisecond": return Math.floor(24 * b * 60 * 60 * 1e3) + this._milliseconds; default: throw new Error("Unknown unit " + a) } }, lang: vb.fn.lang, locale: vb.fn.locale, toIsoString: f("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)", function () { return this.toISOString() }), toISOString: function () { var a = Math.abs(this.years()), b = Math.abs(this.months()), c = Math.abs(this.days()), d = Math.abs(this.hours()), e = Math.abs(this.minutes()), f = Math.abs(this.seconds() + this.milliseconds() / 1e3); return this.asSeconds() ? (this.asSeconds() < 0 ? "-" : "") + "P" + (a ? a + "Y" : "") + (b ? b + "M" : "") + (c ? c + "D" : "") + (d || e || f ? "T" : "") + (d ? d + "H" : "") + (e ? e + "M" : "") + (f ? f + "S" : "") : "P0D" }, localeData: function () { return this._locale }, toJSON: function () { return this.toISOString() }
    }), vb.duration.fn.toString = vb.duration.fn.toISOString; for (xb in kc) c(kc, xb) && tb(xb.toLowerCase()); vb.duration.fn.asMilliseconds = function () { return this.as("ms") }, vb.duration.fn.asSeconds = function () { return this.as("s") }, vb.duration.fn.asMinutes = function () { return this.as("m") }, vb.duration.fn.asHours = function () { return this.as("h") }, vb.duration.fn.asDays = function () { return this.as("d") }, vb.duration.fn.asWeeks = function () { return this.as("weeks") }, vb.duration.fn.asMonths = function () { return this.as("M") }, vb.duration.fn.asYears = function () { return this.as("y") }, vb.locale("en", { ordinalParse: /\d{1,2}(th|st|nd|rd)/, ordinal: function (a) { var b = a % 10, c = 1 === C(a % 100 / 10) ? "th" : 1 === b ? "st" : 2 === b ? "nd" : 3 === b ? "rd" : "th"; return a + c } }), Lb ? module.exports = vb : "function" == typeof define && define.amd ? (define(function (a, b, c) { return c.config && c.config() && c.config().noGlobal === !0 && (zb.moment = wb), vb }), ub(!0)) : ub()
}).call(this);
/*! Hammer.JS - v2.0.8 - 2016-04-23
 * http://hammerjs.github.io/
 *
 * Copyright (c) 2016 Jorik Tangelder;
 * Licensed under the MIT license */
!function(a,b,c,d){"use strict";function e(a,b,c){return setTimeout(j(a,c),b)}function f(a,b,c){return Array.isArray(a)?(g(a,c[b],c),!0):!1}function g(a,b,c){var e;if(a)if(a.forEach)a.forEach(b,c);else if(a.length!==d)for(e=0;e<a.length;)b.call(c,a[e],e,a),e++;else for(e in a)a.hasOwnProperty(e)&&b.call(c,a[e],e,a)}function h(b,c,d){var e="DEPRECATED METHOD: "+c+"\n"+d+" AT \n";return function(){var c=new Error("get-stack-trace"),d=c&&c.stack?c.stack.replace(/^[^\(]+?[\n$]/gm,"").replace(/^\s+at\s+/gm,"").replace(/^Object.<anonymous>\s*\(/gm,"{anonymous}()@"):"Unknown Stack Trace",f=a.console&&(a.console.warn||a.console.log);return f&&f.call(a.console,e,d),b.apply(this,arguments)}}function i(a,b,c){var d,e=b.prototype;d=a.prototype=Object.create(e),d.constructor=a,d._super=e,c&&la(d,c)}function j(a,b){return function(){return a.apply(b,arguments)}}function k(a,b){return typeof a==oa?a.apply(b?b[0]||d:d,b):a}function l(a,b){return a===d?b:a}function m(a,b,c){g(q(b),function(b){a.addEventListener(b,c,!1)})}function n(a,b,c){g(q(b),function(b){a.removeEventListener(b,c,!1)})}function o(a,b){for(;a;){if(a==b)return!0;a=a.parentNode}return!1}function p(a,b){return a.indexOf(b)>-1}function q(a){return a.trim().split(/\s+/g)}function r(a,b,c){if(a.indexOf&&!c)return a.indexOf(b);for(var d=0;d<a.length;){if(c&&a[d][c]==b||!c&&a[d]===b)return d;d++}return-1}function s(a){return Array.prototype.slice.call(a,0)}function t(a,b,c){for(var d=[],e=[],f=0;f<a.length;){var g=b?a[f][b]:a[f];r(e,g)<0&&d.push(a[f]),e[f]=g,f++}return c&&(d=b?d.sort(function(a,c){return a[b]>c[b]}):d.sort()),d}function u(a,b){for(var c,e,f=b[0].toUpperCase()+b.slice(1),g=0;g<ma.length;){if(c=ma[g],e=c?c+f:b,e in a)return e;g++}return d}function v(){return ua++}function w(b){var c=b.ownerDocument||b;return c.defaultView||c.parentWindow||a}function x(a,b){var c=this;this.manager=a,this.callback=b,this.element=a.element,this.target=a.options.inputTarget,this.domHandler=function(b){k(a.options.enable,[a])&&c.handler(b)},this.init()}function y(a){var b,c=a.options.inputClass;return new(b=c?c:xa?M:ya?P:wa?R:L)(a,z)}function z(a,b,c){var d=c.pointers.length,e=c.changedPointers.length,f=b&Ea&&d-e===0,g=b&(Ga|Ha)&&d-e===0;c.isFirst=!!f,c.isFinal=!!g,f&&(a.session={}),c.eventType=b,A(a,c),a.emit("hammer.input",c),a.recognize(c),a.session.prevInput=c}function A(a,b){var c=a.session,d=b.pointers,e=d.length;c.firstInput||(c.firstInput=D(b)),e>1&&!c.firstMultiple?c.firstMultiple=D(b):1===e&&(c.firstMultiple=!1);var f=c.firstInput,g=c.firstMultiple,h=g?g.center:f.center,i=b.center=E(d);b.timeStamp=ra(),b.deltaTime=b.timeStamp-f.timeStamp,b.angle=I(h,i),b.distance=H(h,i),B(c,b),b.offsetDirection=G(b.deltaX,b.deltaY);var j=F(b.deltaTime,b.deltaX,b.deltaY);b.overallVelocityX=j.x,b.overallVelocityY=j.y,b.overallVelocity=qa(j.x)>qa(j.y)?j.x:j.y,b.scale=g?K(g.pointers,d):1,b.rotation=g?J(g.pointers,d):0,b.maxPointers=c.prevInput?b.pointers.length>c.prevInput.maxPointers?b.pointers.length:c.prevInput.maxPointers:b.pointers.length,C(c,b);var k=a.element;o(b.srcEvent.target,k)&&(k=b.srcEvent.target),b.target=k}function B(a,b){var c=b.center,d=a.offsetDelta||{},e=a.prevDelta||{},f=a.prevInput||{};b.eventType!==Ea&&f.eventType!==Ga||(e=a.prevDelta={x:f.deltaX||0,y:f.deltaY||0},d=a.offsetDelta={x:c.x,y:c.y}),b.deltaX=e.x+(c.x-d.x),b.deltaY=e.y+(c.y-d.y)}function C(a,b){var c,e,f,g,h=a.lastInterval||b,i=b.timeStamp-h.timeStamp;if(b.eventType!=Ha&&(i>Da||h.velocity===d)){var j=b.deltaX-h.deltaX,k=b.deltaY-h.deltaY,l=F(i,j,k);e=l.x,f=l.y,c=qa(l.x)>qa(l.y)?l.x:l.y,g=G(j,k),a.lastInterval=b}else c=h.velocity,e=h.velocityX,f=h.velocityY,g=h.direction;b.velocity=c,b.velocityX=e,b.velocityY=f,b.direction=g}function D(a){for(var b=[],c=0;c<a.pointers.length;)b[c]={clientX:pa(a.pointers[c].clientX),clientY:pa(a.pointers[c].clientY)},c++;return{timeStamp:ra(),pointers:b,center:E(b),deltaX:a.deltaX,deltaY:a.deltaY}}function E(a){var b=a.length;if(1===b)return{x:pa(a[0].clientX),y:pa(a[0].clientY)};for(var c=0,d=0,e=0;b>e;)c+=a[e].clientX,d+=a[e].clientY,e++;return{x:pa(c/b),y:pa(d/b)}}function F(a,b,c){return{x:b/a||0,y:c/a||0}}function G(a,b){return a===b?Ia:qa(a)>=qa(b)?0>a?Ja:Ka:0>b?La:Ma}function H(a,b,c){c||(c=Qa);var d=b[c[0]]-a[c[0]],e=b[c[1]]-a[c[1]];return Math.sqrt(d*d+e*e)}function I(a,b,c){c||(c=Qa);var d=b[c[0]]-a[c[0]],e=b[c[1]]-a[c[1]];return 180*Math.atan2(e,d)/Math.PI}function J(a,b){return I(b[1],b[0],Ra)+I(a[1],a[0],Ra)}function K(a,b){return H(b[0],b[1],Ra)/H(a[0],a[1],Ra)}function L(){this.evEl=Ta,this.evWin=Ua,this.pressed=!1,x.apply(this,arguments)}function M(){this.evEl=Xa,this.evWin=Ya,x.apply(this,arguments),this.store=this.manager.session.pointerEvents=[]}function N(){this.evTarget=$a,this.evWin=_a,this.started=!1,x.apply(this,arguments)}function O(a,b){var c=s(a.touches),d=s(a.changedTouches);return b&(Ga|Ha)&&(c=t(c.concat(d),"identifier",!0)),[c,d]}function P(){this.evTarget=bb,this.targetIds={},x.apply(this,arguments)}function Q(a,b){var c=s(a.touches),d=this.targetIds;if(b&(Ea|Fa)&&1===c.length)return d[c[0].identifier]=!0,[c,c];var e,f,g=s(a.changedTouches),h=[],i=this.target;if(f=c.filter(function(a){return o(a.target,i)}),b===Ea)for(e=0;e<f.length;)d[f[e].identifier]=!0,e++;for(e=0;e<g.length;)d[g[e].identifier]&&h.push(g[e]),b&(Ga|Ha)&&delete d[g[e].identifier],e++;return h.length?[t(f.concat(h),"identifier",!0),h]:void 0}function R(){x.apply(this,arguments);var a=j(this.handler,this);this.touch=new P(this.manager,a),this.mouse=new L(this.manager,a),this.primaryTouch=null,this.lastTouches=[]}function S(a,b){a&Ea?(this.primaryTouch=b.changedPointers[0].identifier,T.call(this,b)):a&(Ga|Ha)&&T.call(this,b)}function T(a){var b=a.changedPointers[0];if(b.identifier===this.primaryTouch){var c={x:b.clientX,y:b.clientY};this.lastTouches.push(c);var d=this.lastTouches,e=function(){var a=d.indexOf(c);a>-1&&d.splice(a,1)};setTimeout(e,cb)}}function U(a){for(var b=a.srcEvent.clientX,c=a.srcEvent.clientY,d=0;d<this.lastTouches.length;d++){var e=this.lastTouches[d],f=Math.abs(b-e.x),g=Math.abs(c-e.y);if(db>=f&&db>=g)return!0}return!1}function V(a,b){this.manager=a,this.set(b)}function W(a){if(p(a,jb))return jb;var b=p(a,kb),c=p(a,lb);return b&&c?jb:b||c?b?kb:lb:p(a,ib)?ib:hb}function X(){if(!fb)return!1;var b={},c=a.CSS&&a.CSS.supports;return["auto","manipulation","pan-y","pan-x","pan-x pan-y","none"].forEach(function(d){b[d]=c?a.CSS.supports("touch-action",d):!0}),b}function Y(a){this.options=la({},this.defaults,a||{}),this.id=v(),this.manager=null,this.options.enable=l(this.options.enable,!0),this.state=nb,this.simultaneous={},this.requireFail=[]}function Z(a){return a&sb?"cancel":a&qb?"end":a&pb?"move":a&ob?"start":""}function $(a){return a==Ma?"down":a==La?"up":a==Ja?"left":a==Ka?"right":""}function _(a,b){var c=b.manager;return c?c.get(a):a}function aa(){Y.apply(this,arguments)}function ba(){aa.apply(this,arguments),this.pX=null,this.pY=null}function ca(){aa.apply(this,arguments)}function da(){Y.apply(this,arguments),this._timer=null,this._input=null}function ea(){aa.apply(this,arguments)}function fa(){aa.apply(this,arguments)}function ga(){Y.apply(this,arguments),this.pTime=!1,this.pCenter=!1,this._timer=null,this._input=null,this.count=0}function ha(a,b){return b=b||{},b.recognizers=l(b.recognizers,ha.defaults.preset),new ia(a,b)}function ia(a,b){this.options=la({},ha.defaults,b||{}),this.options.inputTarget=this.options.inputTarget||a,this.handlers={},this.session={},this.recognizers=[],this.oldCssProps={},this.element=a,this.input=y(this),this.touchAction=new V(this,this.options.touchAction),ja(this,!0),g(this.options.recognizers,function(a){var b=this.add(new a[0](a[1]));a[2]&&b.recognizeWith(a[2]),a[3]&&b.requireFailure(a[3])},this)}function ja(a,b){var c=a.element;if(c.style){var d;g(a.options.cssProps,function(e,f){d=u(c.style,f),b?(a.oldCssProps[d]=c.style[d],c.style[d]=e):c.style[d]=a.oldCssProps[d]||""}),b||(a.oldCssProps={})}}function ka(a,c){var d=b.createEvent("Event");d.initEvent(a,!0,!0),d.gesture=c,c.target.dispatchEvent(d)}var la,ma=["","webkit","Moz","MS","ms","o"],na=b.createElement("div"),oa="function",pa=Math.round,qa=Math.abs,ra=Date.now;la="function"!=typeof Object.assign?function(a){if(a===d||null===a)throw new TypeError("Cannot convert undefined or null to object");for(var b=Object(a),c=1;c<arguments.length;c++){var e=arguments[c];if(e!==d&&null!==e)for(var f in e)e.hasOwnProperty(f)&&(b[f]=e[f])}return b}:Object.assign;var sa=h(function(a,b,c){for(var e=Object.keys(b),f=0;f<e.length;)(!c||c&&a[e[f]]===d)&&(a[e[f]]=b[e[f]]),f++;return a},"extend","Use `assign`."),ta=h(function(a,b){return sa(a,b,!0)},"merge","Use `assign`."),ua=1,va=/mobile|tablet|ip(ad|hone|od)|android/i,wa="ontouchstart"in a,xa=u(a,"PointerEvent")!==d,ya=wa&&va.test(navigator.userAgent),za="touch",Aa="pen",Ba="mouse",Ca="kinect",Da=25,Ea=1,Fa=2,Ga=4,Ha=8,Ia=1,Ja=2,Ka=4,La=8,Ma=16,Na=Ja|Ka,Oa=La|Ma,Pa=Na|Oa,Qa=["x","y"],Ra=["clientX","clientY"];x.prototype={handler:function(){},init:function(){this.evEl&&m(this.element,this.evEl,this.domHandler),this.evTarget&&m(this.target,this.evTarget,this.domHandler),this.evWin&&m(w(this.element),this.evWin,this.domHandler)},destroy:function(){this.evEl&&n(this.element,this.evEl,this.domHandler),this.evTarget&&n(this.target,this.evTarget,this.domHandler),this.evWin&&n(w(this.element),this.evWin,this.domHandler)}};var Sa={mousedown:Ea,mousemove:Fa,mouseup:Ga},Ta="mousedown",Ua="mousemove mouseup";i(L,x,{handler:function(a){var b=Sa[a.type];b&Ea&&0===a.button&&(this.pressed=!0),b&Fa&&1!==a.which&&(b=Ga),this.pressed&&(b&Ga&&(this.pressed=!1),this.callback(this.manager,b,{pointers:[a],changedPointers:[a],pointerType:Ba,srcEvent:a}))}});var Va={pointerdown:Ea,pointermove:Fa,pointerup:Ga,pointercancel:Ha,pointerout:Ha},Wa={2:za,3:Aa,4:Ba,5:Ca},Xa="pointerdown",Ya="pointermove pointerup pointercancel";a.MSPointerEvent&&!a.PointerEvent&&(Xa="MSPointerDown",Ya="MSPointerMove MSPointerUp MSPointerCancel"),i(M,x,{handler:function(a){var b=this.store,c=!1,d=a.type.toLowerCase().replace("ms",""),e=Va[d],f=Wa[a.pointerType]||a.pointerType,g=f==za,h=r(b,a.pointerId,"pointerId");e&Ea&&(0===a.button||g)?0>h&&(b.push(a),h=b.length-1):e&(Ga|Ha)&&(c=!0),0>h||(b[h]=a,this.callback(this.manager,e,{pointers:b,changedPointers:[a],pointerType:f,srcEvent:a}),c&&b.splice(h,1))}});var Za={touchstart:Ea,touchmove:Fa,touchend:Ga,touchcancel:Ha},$a="touchstart",_a="touchstart touchmove touchend touchcancel";i(N,x,{handler:function(a){var b=Za[a.type];if(b===Ea&&(this.started=!0),this.started){var c=O.call(this,a,b);b&(Ga|Ha)&&c[0].length-c[1].length===0&&(this.started=!1),this.callback(this.manager,b,{pointers:c[0],changedPointers:c[1],pointerType:za,srcEvent:a})}}});var ab={touchstart:Ea,touchmove:Fa,touchend:Ga,touchcancel:Ha},bb="touchstart touchmove touchend touchcancel";i(P,x,{handler:function(a){var b=ab[a.type],c=Q.call(this,a,b);c&&this.callback(this.manager,b,{pointers:c[0],changedPointers:c[1],pointerType:za,srcEvent:a})}});var cb=2500,db=25;i(R,x,{handler:function(a,b,c){var d=c.pointerType==za,e=c.pointerType==Ba;if(!(e&&c.sourceCapabilities&&c.sourceCapabilities.firesTouchEvents)){if(d)S.call(this,b,c);else if(e&&U.call(this,c))return;this.callback(a,b,c)}},destroy:function(){this.touch.destroy(),this.mouse.destroy()}});var eb=u(na.style,"touchAction"),fb=eb!==d,gb="compute",hb="auto",ib="manipulation",jb="none",kb="pan-x",lb="pan-y",mb=X();V.prototype={set:function(a){a==gb&&(a=this.compute()),fb&&this.manager.element.style&&mb[a]&&(this.manager.element.style[eb]=a),this.actions=a.toLowerCase().trim()},update:function(){this.set(this.manager.options.touchAction)},compute:function(){var a=[];return g(this.manager.recognizers,function(b){k(b.options.enable,[b])&&(a=a.concat(b.getTouchAction()))}),W(a.join(" "))},preventDefaults:function(a){var b=a.srcEvent,c=a.offsetDirection;if(this.manager.session.prevented)return void b.preventDefault();var d=this.actions,e=p(d,jb)&&!mb[jb],f=p(d,lb)&&!mb[lb],g=p(d,kb)&&!mb[kb];if(e){var h=1===a.pointers.length,i=a.distance<2,j=a.deltaTime<250;if(h&&i&&j)return}return g&&f?void 0:e||f&&c&Na||g&&c&Oa?this.preventSrc(b):void 0},preventSrc:function(a){this.manager.session.prevented=!0,a.preventDefault()}};var nb=1,ob=2,pb=4,qb=8,rb=qb,sb=16,tb=32;Y.prototype={defaults:{},set:function(a){return la(this.options,a),this.manager&&this.manager.touchAction.update(),this},recognizeWith:function(a){if(f(a,"recognizeWith",this))return this;var b=this.simultaneous;return a=_(a,this),b[a.id]||(b[a.id]=a,a.recognizeWith(this)),this},dropRecognizeWith:function(a){return f(a,"dropRecognizeWith",this)?this:(a=_(a,this),delete this.simultaneous[a.id],this)},requireFailure:function(a){if(f(a,"requireFailure",this))return this;var b=this.requireFail;return a=_(a,this),-1===r(b,a)&&(b.push(a),a.requireFailure(this)),this},dropRequireFailure:function(a){if(f(a,"dropRequireFailure",this))return this;a=_(a,this);var b=r(this.requireFail,a);return b>-1&&this.requireFail.splice(b,1),this},hasRequireFailures:function(){return this.requireFail.length>0},canRecognizeWith:function(a){return!!this.simultaneous[a.id]},emit:function(a){function b(b){c.manager.emit(b,a)}var c=this,d=this.state;qb>d&&b(c.options.event+Z(d)),b(c.options.event),a.additionalEvent&&b(a.additionalEvent),d>=qb&&b(c.options.event+Z(d))},tryEmit:function(a){return this.canEmit()?this.emit(a):void(this.state=tb)},canEmit:function(){for(var a=0;a<this.requireFail.length;){if(!(this.requireFail[a].state&(tb|nb)))return!1;a++}return!0},recognize:function(a){var b=la({},a);return k(this.options.enable,[this,b])?(this.state&(rb|sb|tb)&&(this.state=nb),this.state=this.process(b),void(this.state&(ob|pb|qb|sb)&&this.tryEmit(b))):(this.reset(),void(this.state=tb))},process:function(a){},getTouchAction:function(){},reset:function(){}},i(aa,Y,{defaults:{pointers:1},attrTest:function(a){var b=this.options.pointers;return 0===b||a.pointers.length===b},process:function(a){var b=this.state,c=a.eventType,d=b&(ob|pb),e=this.attrTest(a);return d&&(c&Ha||!e)?b|sb:d||e?c&Ga?b|qb:b&ob?b|pb:ob:tb}}),i(ba,aa,{defaults:{event:"pan",threshold:10,pointers:1,direction:Pa},getTouchAction:function(){var a=this.options.direction,b=[];return a&Na&&b.push(lb),a&Oa&&b.push(kb),b},directionTest:function(a){var b=this.options,c=!0,d=a.distance,e=a.direction,f=a.deltaX,g=a.deltaY;return e&b.direction||(b.direction&Na?(e=0===f?Ia:0>f?Ja:Ka,c=f!=this.pX,d=Math.abs(a.deltaX)):(e=0===g?Ia:0>g?La:Ma,c=g!=this.pY,d=Math.abs(a.deltaY))),a.direction=e,c&&d>b.threshold&&e&b.direction},attrTest:function(a){return aa.prototype.attrTest.call(this,a)&&(this.state&ob||!(this.state&ob)&&this.directionTest(a))},emit:function(a){this.pX=a.deltaX,this.pY=a.deltaY;var b=$(a.direction);b&&(a.additionalEvent=this.options.event+b),this._super.emit.call(this,a)}}),i(ca,aa,{defaults:{event:"pinch",threshold:0,pointers:2},getTouchAction:function(){return[jb]},attrTest:function(a){return this._super.attrTest.call(this,a)&&(Math.abs(a.scale-1)>this.options.threshold||this.state&ob)},emit:function(a){if(1!==a.scale){var b=a.scale<1?"in":"out";a.additionalEvent=this.options.event+b}this._super.emit.call(this,a)}}),i(da,Y,{defaults:{event:"press",pointers:1,time:251,threshold:9},getTouchAction:function(){return[hb]},process:function(a){var b=this.options,c=a.pointers.length===b.pointers,d=a.distance<b.threshold,f=a.deltaTime>b.time;if(this._input=a,!d||!c||a.eventType&(Ga|Ha)&&!f)this.reset();else if(a.eventType&Ea)this.reset(),this._timer=e(function(){this.state=rb,this.tryEmit()},b.time,this);else if(a.eventType&Ga)return rb;return tb},reset:function(){clearTimeout(this._timer)},emit:function(a){this.state===rb&&(a&&a.eventType&Ga?this.manager.emit(this.options.event+"up",a):(this._input.timeStamp=ra(),this.manager.emit(this.options.event,this._input)))}}),i(ea,aa,{defaults:{event:"rotate",threshold:0,pointers:2},getTouchAction:function(){return[jb]},attrTest:function(a){return this._super.attrTest.call(this,a)&&(Math.abs(a.rotation)>this.options.threshold||this.state&ob)}}),i(fa,aa,{defaults:{event:"swipe",threshold:10,velocity:.3,direction:Na|Oa,pointers:1},getTouchAction:function(){return ba.prototype.getTouchAction.call(this)},attrTest:function(a){var b,c=this.options.direction;return c&(Na|Oa)?b=a.overallVelocity:c&Na?b=a.overallVelocityX:c&Oa&&(b=a.overallVelocityY),this._super.attrTest.call(this,a)&&c&a.offsetDirection&&a.distance>this.options.threshold&&a.maxPointers==this.options.pointers&&qa(b)>this.options.velocity&&a.eventType&Ga},emit:function(a){var b=$(a.offsetDirection);b&&this.manager.emit(this.options.event+b,a),this.manager.emit(this.options.event,a)}}),i(ga,Y,{defaults:{event:"tap",pointers:1,taps:1,interval:300,time:250,threshold:9,posThreshold:10},getTouchAction:function(){return[ib]},process:function(a){var b=this.options,c=a.pointers.length===b.pointers,d=a.distance<b.threshold,f=a.deltaTime<b.time;if(this.reset(),a.eventType&Ea&&0===this.count)return this.failTimeout();if(d&&f&&c){if(a.eventType!=Ga)return this.failTimeout();var g=this.pTime?a.timeStamp-this.pTime<b.interval:!0,h=!this.pCenter||H(this.pCenter,a.center)<b.posThreshold;this.pTime=a.timeStamp,this.pCenter=a.center,h&&g?this.count+=1:this.count=1,this._input=a;var i=this.count%b.taps;if(0===i)return this.hasRequireFailures()?(this._timer=e(function(){this.state=rb,this.tryEmit()},b.interval,this),ob):rb}return tb},failTimeout:function(){return this._timer=e(function(){this.state=tb},this.options.interval,this),tb},reset:function(){clearTimeout(this._timer)},emit:function(){this.state==rb&&(this._input.tapCount=this.count,this.manager.emit(this.options.event,this._input))}}),ha.VERSION="2.0.8",ha.defaults={domEvents:!1,touchAction:gb,enable:!0,inputTarget:null,inputClass:null,preset:[[ea,{enable:!1}],[ca,{enable:!1},["rotate"]],[fa,{direction:Na}],[ba,{direction:Na},["swipe"]],[ga],[ga,{event:"doubletap",taps:2},["tap"]],[da]],cssProps:{userSelect:"none",touchSelect:"none",touchCallout:"none",contentZooming:"none",userDrag:"none",tapHighlightColor:"rgba(0,0,0,0)"}};var ub=1,vb=2;ia.prototype={set:function(a){return la(this.options,a),a.touchAction&&this.touchAction.update(),a.inputTarget&&(this.input.destroy(),this.input.target=a.inputTarget,this.input.init()),this},stop:function(a){this.session.stopped=a?vb:ub},recognize:function(a){var b=this.session;if(!b.stopped){this.touchAction.preventDefaults(a);var c,d=this.recognizers,e=b.curRecognizer;(!e||e&&e.state&rb)&&(e=b.curRecognizer=null);for(var f=0;f<d.length;)c=d[f],b.stopped===vb||e&&c!=e&&!c.canRecognizeWith(e)?c.reset():c.recognize(a),!e&&c.state&(ob|pb|qb)&&(e=b.curRecognizer=c),f++}},get:function(a){if(a instanceof Y)return a;for(var b=this.recognizers,c=0;c<b.length;c++)if(b[c].options.event==a)return b[c];return null},add:function(a){if(f(a,"add",this))return this;var b=this.get(a.options.event);return b&&this.remove(b),this.recognizers.push(a),a.manager=this,this.touchAction.update(),a},remove:function(a){if(f(a,"remove",this))return this;if(a=this.get(a)){var b=this.recognizers,c=r(b,a);-1!==c&&(b.splice(c,1),this.touchAction.update())}return this},on:function(a,b){if(a!==d&&b!==d){var c=this.handlers;return g(q(a),function(a){c[a]=c[a]||[],c[a].push(b)}),this}},off:function(a,b){if(a!==d){var c=this.handlers;return g(q(a),function(a){b?c[a]&&c[a].splice(r(c[a],b),1):delete c[a]}),this}},emit:function(a,b){this.options.domEvents&&ka(a,b);var c=this.handlers[a]&&this.handlers[a].slice();if(c&&c.length){b.type=a,b.preventDefault=function(){b.srcEvent.preventDefault()};for(var d=0;d<c.length;)c[d](b),d++}},destroy:function(){this.element&&ja(this,!1),this.handlers={},this.session={},this.input.destroy(),this.element=null}},la(ha,{INPUT_START:Ea,INPUT_MOVE:Fa,INPUT_END:Ga,INPUT_CANCEL:Ha,STATE_POSSIBLE:nb,STATE_BEGAN:ob,STATE_CHANGED:pb,STATE_ENDED:qb,STATE_RECOGNIZED:rb,STATE_CANCELLED:sb,STATE_FAILED:tb,DIRECTION_NONE:Ia,DIRECTION_LEFT:Ja,DIRECTION_RIGHT:Ka,DIRECTION_UP:La,DIRECTION_DOWN:Ma,DIRECTION_HORIZONTAL:Na,DIRECTION_VERTICAL:Oa,DIRECTION_ALL:Pa,Manager:ia,Input:x,TouchAction:V,TouchInput:P,MouseInput:L,PointerEventInput:M,TouchMouseInput:R,SingleTouchInput:N,Recognizer:Y,AttrRecognizer:aa,Tap:ga,Pan:ba,Swipe:fa,Pinch:ca,Rotate:ea,Press:da,on:m,off:n,each:g,merge:ta,extend:sa,assign:la,inherit:i,bindFn:j,prefixed:u});var wb="undefined"!=typeof a?a:"undefined"!=typeof self?self:{};wb.Hammer=ha,"function"==typeof define&&define.amd?define(function(){return ha}):"undefined"!=typeof module&&module.exports?module.exports=ha:a[c]=ha}(window,document,"Hammer");
//# sourceMappingURL=hammer.min.js.map
/*global self, document, DOMException */

/*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js */

// Full polyfill for browsers with no classList support
if (!("classList" in document.createElement("_"))) {
  (function (view) {

  "use strict";

  if (!('Element' in view)) return;

  var
      classListProp = "classList"
    , protoProp = "prototype"
    , elemCtrProto = view.Element[protoProp]
    , objCtr = Object
    , strTrim = String[protoProp].trim || function () {
      return this.replace(/^\s+|\s+$/g, "");
    }
    , arrIndexOf = Array[protoProp].indexOf || function (item) {
      var
          i = 0
        , len = this.length
      ;
      for (; i < len; i++) {
        if (i in this && this[i] === item) {
          return i;
        }
      }
      return -1;
    }
    // Vendors: please allow content code to instantiate DOMExceptions
    , DOMEx = function (type, message) {
      this.name = type;
      this.code = DOMException[type];
      this.message = message;
    }
    , checkTokenAndGetIndex = function (classList, token) {
      if (token === "") {
        throw new DOMEx(
            "SYNTAX_ERR"
          , "An invalid or illegal string was specified"
        );
      }
      if (/\s/.test(token)) {
        throw new DOMEx(
            "INVALID_CHARACTER_ERR"
          , "String contains an invalid character"
        );
      }
      return arrIndexOf.call(classList, token);
    }
    , ClassList = function (elem) {
      var
          trimmedClasses = strTrim.call(elem.getAttribute("class") || "")
        , classes = trimmedClasses ? trimmedClasses.split(/\s+/) : []
        , i = 0
        , len = classes.length
      ;
      for (; i < len; i++) {
        this.push(classes[i]);
      }
      this._updateClassName = function () {
        elem.setAttribute("class", this.toString());
      };
    }
    , classListProto = ClassList[protoProp] = []
    , classListGetter = function () {
      return new ClassList(this);
    }
  ;
  // Most DOMException implementations don't allow calling DOMException's toString()
  // on non-DOMExceptions. Error's toString() is sufficient here.
  DOMEx[protoProp] = Error[protoProp];
  classListProto.item = function (i) {
    return this[i] || null;
  };
  classListProto.contains = function (token) {
    token += "";
    return checkTokenAndGetIndex(this, token) !== -1;
  };
  classListProto.add = function () {
    var
        tokens = arguments
      , i = 0
      , l = tokens.length
      , token
      , updated = false
    ;
    do {
      token = tokens[i] + "";
      if (checkTokenAndGetIndex(this, token) === -1) {
        this.push(token);
        updated = true;
      }
    }
    while (++i < l);

    if (updated) {
      this._updateClassName();
    }
  };
  classListProto.remove = function () {
    var
        tokens = arguments
      , i = 0
      , l = tokens.length
      , token
      , updated = false
      , index
    ;
    do {
      token = tokens[i] + "";
      index = checkTokenAndGetIndex(this, token);
      while (index !== -1) {
        this.splice(index, 1);
        updated = true;
        index = checkTokenAndGetIndex(this, token);
      }
    }
    while (++i < l);

    if (updated) {
      this._updateClassName();
    }
  };
  classListProto.toggle = function (token, force) {
    token += "";

    var
        result = this.contains(token)
      , method = result ?
        force !== true && "remove"
      :
        force !== false && "add"
    ;

    if (method) {
      this[method](token);
    }

    if (force === true || force === false) {
      return force;
    } else {
      return !result;
    }
  };
  classListProto.toString = function () {
    return this.join(" ");
  };

  if (objCtr.defineProperty) {
    var classListPropDesc = {
        get: classListGetter
      , enumerable: true
      , configurable: true
    };
    try {
      objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
    } catch (ex) { // IE 8 doesn't support enumerable:true
      if (ex.number === -0x7FF5EC54) {
        classListPropDesc.enumerable = false;
        objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
      }
    }
  } else if (objCtr[protoProp].__defineGetter__) {
    elemCtrProto.__defineGetter__(classListProp, classListGetter);
  }

  }(self));
}

/* Blob.js
 * A Blob implementation.
 * 2014-07-24
 *
 * By Eli Grey, http://eligrey.com
 * By Devin Samarin, https://github.com/dsamarin
 * License: X11/MIT
 *   See https://github.com/eligrey/Blob.js/blob/master/LICENSE.md
 */

/*global self, unescape */
/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
  plusplus: true */

/*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */

(function (view) {
  "use strict";

  view.URL = view.URL || view.webkitURL;

  if (view.Blob && view.URL) {
    try {
      new Blob;
      return;
    } catch (e) {}
  }

  // Internally we use a BlobBuilder implementation to base Blob off of
  // in order to support older browsers that only have BlobBuilder
  var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || (function(view) {
    var
        get_class = function(object) {
        return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1];
      }
      , FakeBlobBuilder = function BlobBuilder() {
        this.data = [];
      }
      , FakeBlob = function Blob(data, type, encoding) {
        this.data = data;
        this.size = data.length;
        this.type = type;
        this.encoding = encoding;
      }
      , FBB_proto = FakeBlobBuilder.prototype
      , FB_proto = FakeBlob.prototype
      , FileReaderSync = view.FileReaderSync
      , FileException = function(type) {
        this.code = this[this.name = type];
      }
      , file_ex_codes = (
          "NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR "
        + "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR"
      ).split(" ")
      , file_ex_code = file_ex_codes.length
      , real_URL = view.URL || view.webkitURL || view
      , real_create_object_URL = real_URL.createObjectURL
      , real_revoke_object_URL = real_URL.revokeObjectURL
      , URL = real_URL
      , btoa = view.btoa
      , atob = view.atob

      , ArrayBuffer = view.ArrayBuffer
      , Uint8Array = view.Uint8Array

      , origin = /^[\w-]+:\/*\[?[\w\.:-]+\]?(?::[0-9]+)?/
    ;
    FakeBlob.fake = FB_proto.fake = true;
    while (file_ex_code--) {
      FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1;
    }
    // Polyfill URL
    if (!real_URL.createObjectURL) {
      URL = view.URL = function(uri) {
        var
            uri_info = document.createElementNS("http://www.w3.org/1999/xhtml", "a")
          , uri_origin
        ;
        uri_info.href = uri;
        if (!("origin" in uri_info)) {
          if (uri_info.protocol.toLowerCase() === "data:") {
            uri_info.origin = null;
          } else {
            uri_origin = uri.match(origin);
            uri_info.origin = uri_origin && uri_origin[1];
          }
        }
        return uri_info;
      };
    }
    URL.createObjectURL = function(blob) {
      var
          type = blob.type
        , data_URI_header
      ;
      if (type === null) {
        type = "application/octet-stream";
      }
      if (blob instanceof FakeBlob) {
        data_URI_header = "data:" + type;
        if (blob.encoding === "base64") {
          return data_URI_header + ";base64," + blob.data;
        } else if (blob.encoding === "URI") {
          return data_URI_header + "," + decodeURIComponent(blob.data);
        } if (btoa) {
          return data_URI_header + ";base64," + btoa(blob.data);
        } else {
          return data_URI_header + "," + encodeURIComponent(blob.data);
        }
      } else if (real_create_object_URL) {
        return real_create_object_URL.call(real_URL, blob);
      }
    };
    URL.revokeObjectURL = function(object_URL) {
      if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) {
        real_revoke_object_URL.call(real_URL, object_URL);
      }
    };
    FBB_proto.append = function(data/*, endings*/) {
      var bb = this.data;
      // decode data to a binary string
      if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) {
        var
            str = ""
          , buf = new Uint8Array(data)
          , i = 0
          , buf_len = buf.length
        ;
        for (; i < buf_len; i++) {
          str += String.fromCharCode(buf[i]);
        }
        bb.push(str);
      } else if (get_class(data) === "Blob" || get_class(data) === "File") {
        if (FileReaderSync) {
          var fr = new FileReaderSync;
          bb.push(fr.readAsBinaryString(data));
        } else {
          // async FileReader won't work as BlobBuilder is sync
          throw new FileException("NOT_READABLE_ERR");
        }
      } else if (data instanceof FakeBlob) {
        if (data.encoding === "base64" && atob) {
          bb.push(atob(data.data));
        } else if (data.encoding === "URI") {
          bb.push(decodeURIComponent(data.data));
        } else if (data.encoding === "raw") {
          bb.push(data.data);
        }
      } else {
        if (typeof data !== "string") {
          data += ""; // convert unsupported types to strings
        }
        // decode UTF-16 to binary string
        bb.push(unescape(encodeURIComponent(data)));
      }
    };
    FBB_proto.getBlob = function(type) {
      if (!arguments.length) {
        type = null;
      }
      return new FakeBlob(this.data.join(""), type, "raw");
    };
    FBB_proto.toString = function() {
      return "[object BlobBuilder]";
    };
    FB_proto.slice = function(start, end, type) {
      var args = arguments.length;
      if (args < 3) {
        type = null;
      }
      return new FakeBlob(
          this.data.slice(start, args > 1 ? end : this.data.length)
        , type
        , this.encoding
      );
    };
    FB_proto.toString = function() {
      return "[object Blob]";
    };
    FB_proto.close = function() {
      this.size = 0;
      delete this.data;
    };
    return FakeBlobBuilder;
  }(view));

  view.Blob = function(blobParts, options) {
    var type = options ? (options.type || "") : "";
    var builder = new BlobBuilder();
    if (blobParts) {
      for (var i = 0, len = blobParts.length; i < len; i++) {
        if (Uint8Array && blobParts[i] instanceof Uint8Array) {
          builder.append(blobParts[i].buffer);
        }
        else {
          builder.append(blobParts[i]);
        }
      }
    }
    var blob = builder.getBlob(type);
    if (!blob.slice && blob.webkitSlice) {
      blob.slice = blob.webkitSlice;
    }
    return blob;
  };

  var getPrototypeOf = Object.getPrototypeOf || function(object) {
    return object.__proto__;
  };
  view.Blob.prototype = getPrototypeOf(new view.Blob());
}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this));

(function (root, factory) {
    'use strict';
    var isElectron = typeof module === 'object' && typeof process !== 'undefined' && process && process.versions && process.versions.electron;
    if (!isElectron && typeof module === 'object') {
        module.exports = factory;
    } else if (typeof define === 'function' && define.amd) {
        define(function () {
            return factory;
        });
    } else {
        root.MediumEditor = factory;
    }
}(this, function () {

    'use strict';

function MediumEditor(elements, options) {
    'use strict';
    return this.init(elements, options);
}

MediumEditor.extensions = {};
/*jshint unused: true */
(function (window) {
    'use strict';

    function copyInto(overwrite, dest) {
        var prop,
            sources = Array.prototype.slice.call(arguments, 2);
        dest = dest || {};
        for (var i = 0; i < sources.length; i++) {
            var source = sources[i];
            if (source) {
                for (prop in source) {
                    if (source.hasOwnProperty(prop) &&
                        typeof source[prop] !== 'undefined' &&
                        (overwrite || dest.hasOwnProperty(prop) === false)) {
                        dest[prop] = source[prop];
                    }
                }
            }
        }
        return dest;
    }

    // https://developer.mozilla.org/en-US/docs/Web/API/Node/contains
    // Some browsers (including phantom) don't return true for Node.contains(child)
    // if child is a text node.  Detect these cases here and use a fallback
    // for calls to Util.isDescendant()
    var nodeContainsWorksWithTextNodes = false;
    try {
        var testParent = document.createElement('div'),
            testText = document.createTextNode(' ');
        testParent.appendChild(testText);
        nodeContainsWorksWithTextNodes = testParent.contains(testText);
    } catch (exc) {}

    var Util = {

        // http://stackoverflow.com/questions/17907445/how-to-detect-ie11#comment30165888_17907562
        // by rg89
        isIE: ((navigator.appName === 'Microsoft Internet Explorer') || ((navigator.appName === 'Netscape') && (new RegExp('Trident/.*rv:([0-9]{1,}[.0-9]{0,})').exec(navigator.userAgent) !== null))),

        isEdge: (/Edge\/\d+/).exec(navigator.userAgent) !== null,

        // if firefox
        isFF: (navigator.userAgent.toLowerCase().indexOf('firefox') > -1),

        // http://stackoverflow.com/a/11752084/569101
        isMac: (window.navigator.platform.toUpperCase().indexOf('MAC') >= 0),

        // https://github.com/jashkenas/underscore
        // Lonely letter MUST USE the uppercase code
        keyCode: {
            BACKSPACE: 8,
            TAB: 9,
            ENTER: 13,
            ESCAPE: 27,
            SPACE: 32,
            DELETE: 46,
            K: 75, // K keycode, and not k
            M: 77,
            V: 86
        },

        /**
         * Returns true if it's metaKey on Mac, or ctrlKey on non-Mac.
         * See #591
         */
        isMetaCtrlKey: function (event) {
            if ((Util.isMac && event.metaKey) || (!Util.isMac && event.ctrlKey)) {
                return true;
            }

            return false;
        },

        /**
         * Returns true if the key associated to the event is inside keys array
         *
         * @see : https://github.com/jquery/jquery/blob/0705be475092aede1eddae01319ec931fb9c65fc/src/event.js#L473-L484
         * @see : http://stackoverflow.com/q/4471582/569101
         */
        isKey: function (event, keys) {
            var keyCode = Util.getKeyCode(event);

            // it's not an array let's just compare strings!
            if (false === Array.isArray(keys)) {
                return keyCode === keys;
            }

            if (-1 === keys.indexOf(keyCode)) {
                return false;
            }

            return true;
        },

        getKeyCode: function (event) {
            var keyCode = event.which;

            // getting the key code from event
            if (null === keyCode) {
                keyCode = event.charCode !== null ? event.charCode : event.keyCode;
            }

            return keyCode;
        },

        blockContainerElementNames: [
            // elements our editor generates
            'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'pre', 'ul', 'li', 'ol',
            // all other known block elements
            'address', 'article', 'aside', 'audio', 'canvas', 'dd', 'dl', 'dt', 'fieldset',
            'figcaption', 'figure', 'footer', 'form', 'header', 'hgroup', 'main', 'nav',
            'noscript', 'output', 'section', 'video',
            'table', 'thead', 'tbody', 'tfoot', 'tr', 'th', 'td'
        ],

        emptyElementNames: ['br', 'col', 'colgroup', 'hr', 'img', 'input', 'source', 'wbr'],

        extend: function extend(/* dest, source1, source2, ...*/) {
            var args = [true].concat(Array.prototype.slice.call(arguments));
            return copyInto.apply(this, args);
        },

        defaults: function defaults(/*dest, source1, source2, ...*/) {
            var args = [false].concat(Array.prototype.slice.call(arguments));
            return copyInto.apply(this, args);
        },

        /*
         * Create a link around the provided text nodes which must be adjacent to each other and all be
         * descendants of the same closest block container. If the preconditions are not met, unexpected
         * behavior will result.
         */
        createLink: function (document, textNodes, href, target) {
            var anchor = document.createElement('a');
            Util.moveTextRangeIntoElement(textNodes[0], textNodes[textNodes.length - 1], anchor);
            anchor.setAttribute('href', href);
            if (target) {
                anchor.setAttribute('target', target);
            }
            return anchor;
        },

        /*
         * Given the provided match in the format {start: 1, end: 2} where start and end are indices into the
         * textContent of the provided element argument, modify the DOM inside element to ensure that the text
         * identified by the provided match can be returned as text nodes that contain exactly that text, without
         * any additional text at the beginning or end of the returned array of adjacent text nodes.
         *
         * The only DOM manipulation performed by this function is splitting the text nodes, non-text nodes are
         * not affected in any way.
         */
        findOrCreateMatchingTextNodes: function (document, element, match) {
            var treeWalker = document.createTreeWalker(element, NodeFilter.SHOW_ALL, null, false),
                matchedNodes = [],
                currentTextIndex = 0,
                startReached = false,
                currentNode = null,
                newNode = null;

            while ((currentNode = treeWalker.nextNode()) !== null) {
                if (currentNode.nodeType > 3) {
                    continue;
                } else if (currentNode.nodeType === 3) {
                    if (!startReached && match.start < (currentTextIndex + currentNode.nodeValue.length)) {
                        startReached = true;
                        newNode = Util.splitStartNodeIfNeeded(currentNode, match.start, currentTextIndex);
                    }
                    if (startReached) {
                        Util.splitEndNodeIfNeeded(currentNode, newNode, match.end, currentTextIndex);
                    }
                    if (startReached && currentTextIndex === match.end) {
                        break; // Found the node(s) corresponding to the link. Break out and move on to the next.
                    } else if (startReached && currentTextIndex > (match.end + 1)) {
                        throw new Error('PerformLinking overshot the target!'); // should never happen...
                    }

                    if (startReached) {
                        matchedNodes.push(newNode || currentNode);
                    }

                    currentTextIndex += currentNode.nodeValue.length;
                    if (newNode !== null) {
                        currentTextIndex += newNode.nodeValue.length;
                        // Skip the newNode as we'll already have pushed it to the matches
                        treeWalker.nextNode();
                    }
                    newNode = null;
                } else if (currentNode.tagName.toLowerCase() === 'img') {
                    if (!startReached && (match.start <= currentTextIndex)) {
                        startReached = true;
                    }
                    if (startReached) {
                        matchedNodes.push(currentNode);
                    }
                }
            }
            return matchedNodes;
        },

        /*
         * Given the provided text node and text coordinates, split the text node if needed to make it align
         * precisely with the coordinates.
         *
         * This function is intended to be called from Util.findOrCreateMatchingTextNodes.
         */
        splitStartNodeIfNeeded: function (currentNode, matchStartIndex, currentTextIndex) {
            if (matchStartIndex !== currentTextIndex) {
                return currentNode.splitText(matchStartIndex - currentTextIndex);
            }
            return null;
        },

        /*
         * Given the provided text node and text coordinates, split the text node if needed to make it align
         * precisely with the coordinates. The newNode argument should from the result of Util.splitStartNodeIfNeeded,
         * if that function has been called on the same currentNode.
         *
         * This function is intended to be called from Util.findOrCreateMatchingTextNodes.
         */
        splitEndNodeIfNeeded: function (currentNode, newNode, matchEndIndex, currentTextIndex) {
            var textIndexOfEndOfFarthestNode,
                endSplitPoint;
            textIndexOfEndOfFarthestNode = currentTextIndex + currentNode.nodeValue.length +
                    (newNode ? newNode.nodeValue.length : 0) - 1;
            endSplitPoint = matchEndIndex - currentTextIndex -
                    (newNode ? currentNode.nodeValue.length : 0);
            if (textIndexOfEndOfFarthestNode >= matchEndIndex &&
                    currentTextIndex !== textIndexOfEndOfFarthestNode &&
                    endSplitPoint !== 0) {
                (newNode || currentNode).splitText(endSplitPoint);
            }
        },

        /*
        * Take an element, and break up all of its text content into unique pieces such that:
         * 1) All text content of the elements are in separate blocks. No piece of text content should span
         *    across multiple blocks. This means no element return by this function should have
         *    any blocks as children.
         * 2) The union of the textcontent of all of the elements returned here covers all
         *    of the text within the element.
         *
         *
         * EXAMPLE:
         * In the event that we have something like:
         *
         * <blockquote>
         *   <p>Some Text</p>
         *   <ol>
         *     <li>List Item 1</li>
         *     <li>List Item 2</li>
         *   </ol>
         * </blockquote>
         *
         * This function would return these elements as an array:
         *   [ <p>Some Text</p>, <li>List Item 1</li>, <li>List Item 2</li> ]
         *
         * Since the <blockquote> and <ol> elements contain blocks within them they are not returned.
         * Since the <p> and <li>'s don't contain block elements and cover all the text content of the
         * <blockquote> container, they are the elements returned.
         */
        splitByBlockElements: function (element) {
            if (element.nodeType !== 3 && element.nodeType !== 1) {
                return [];
            }

            var toRet = [],
                blockElementQuery = MediumEditor.util.blockContainerElementNames.join(',');

            if (element.nodeType === 3 || element.querySelectorAll(blockElementQuery).length === 0) {
                return [element];
            }

            for (var i = 0; i < element.childNodes.length; i++) {
                var child = element.childNodes[i];
                if (child.nodeType === 3) {
                    toRet.push(child);
                } else if (child.nodeType === 1) {
                    var blockElements = child.querySelectorAll(blockElementQuery);
                    if (blockElements.length === 0) {
                        toRet.push(child);
                    } else {
                        toRet = toRet.concat(MediumEditor.util.splitByBlockElements(child));
                    }
                }
            }

            return toRet;
        },

        // Find the next node in the DOM tree that represents any text that is being
        // displayed directly next to the targetNode (passed as an argument)
        // Text that appears directly next to the current node can be:
        //  - A sibling text node
        //  - A descendant of a sibling element
        //  - A sibling text node of an ancestor
        //  - A descendant of a sibling element of an ancestor
        findAdjacentTextNodeWithContent: function findAdjacentTextNodeWithContent(rootNode, targetNode, ownerDocument) {
            var pastTarget = false,
                nextNode,
                nodeIterator = ownerDocument.createNodeIterator(rootNode, NodeFilter.SHOW_TEXT, null, false);

            // Use a native NodeIterator to iterate over all the text nodes that are descendants
            // of the rootNode.  Once past the targetNode, choose the first non-empty text node
            nextNode = nodeIterator.nextNode();
            while (nextNode) {
                if (nextNode === targetNode) {
                    pastTarget = true;
                } else if (pastTarget) {
                    if (nextNode.nodeType === 3 && nextNode.nodeValue && nextNode.nodeValue.trim().length > 0) {
                        break;
                    }
                }
                nextNode = nodeIterator.nextNode();
            }

            return nextNode;
        },

        // Find an element's previous sibling within a medium-editor element
        // If one doesn't exist, find the closest ancestor's previous sibling
        findPreviousSibling: function (node) {
            if (!node || Util.isMediumEditorElement(node)) {
                return false;
            }

            var previousSibling = node.previousSibling;
            while (!previousSibling && !Util.isMediumEditorElement(node.parentNode)) {
                node = node.parentNode;
                previousSibling = node.previousSibling;
            }

            return previousSibling;
        },

        isDescendant: function isDescendant(parent, child, checkEquality) {
            if (!parent || !child) {
                return false;
            }
            if (parent === child) {
                return !!checkEquality;
            }
            // If parent is not an element, it can't have any descendants
            if (parent.nodeType !== 1) {
                return false;
            }
            if (nodeContainsWorksWithTextNodes || child.nodeType !== 3) {
                return parent.contains(child);
            }
            var node = child.parentNode;
            while (node !== null) {
                if (node === parent) {
                    return true;
                }
                node = node.parentNode;
            }
            return false;
        },

        // https://github.com/jashkenas/underscore
        isElement: function isElement(obj) {
            return !!(obj && obj.nodeType === 1);
        },

        // https://github.com/jashkenas/underscore
        throttle: function (func, wait) {
            var THROTTLE_INTERVAL = 50,
                context,
                args,
                result,
                timeout = null,
                previous = 0,
                later = function () {
                    previous = Date.now();
                    timeout = null;
                    result = func.apply(context, args);
                    if (!timeout) {
                        context = args = null;
                    }
                };

            if (!wait && wait !== 0) {
                wait = THROTTLE_INTERVAL;
            }

            return function () {
                var now = Date.now(),
                    remaining = wait - (now - previous);

                context = this;
                args = arguments;
                if (remaining <= 0 || remaining > wait) {
                    if (timeout) {
                        clearTimeout(timeout);
                        timeout = null;
                    }
                    previous = now;
                    result = func.apply(context, args);
                    if (!timeout) {
                        context = args = null;
                    }
                } else if (!timeout) {
                    timeout = setTimeout(later, remaining);
                }
                return result;
            };
        },

        traverseUp: function (current, testElementFunction) {
            if (!current) {
                return false;
            }

            do {
                if (current.nodeType === 1) {
                    if (testElementFunction(current)) {
                        return current;
                    }
                    // do not traverse upwards past the nearest containing editor
                    if (Util.isMediumEditorElement(current)) {
                        return false;
                    }
                }

                current = current.parentNode;
            } while (current);

            return false;
        },

        htmlEntities: function (str) {
            // converts special characters (like <) into their escaped/encoded values (like &lt;).
            // This allows you to show to display the string without the browser reading it as HTML.
            return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
        },

        // http://stackoverflow.com/questions/6690752/insert-html-at-caret-in-a-contenteditable-div
        insertHTMLCommand: function (doc, html) {
            var selection, range, el, fragment, node, lastNode, toReplace,
                res = false,
                ecArgs = ['insertHTML', false, html];

            /* Edge's implementation of insertHTML is just buggy right now:
             * - Doesn't allow leading white space at the beginning of an element
             * - Found a case when a <font size="2"> tag was inserted when calling alignCenter inside a blockquote
             *
             * There are likely other bugs, these are just the ones we found so far.
             * For now, let's just use the same fallback we did for IE
             */
            if (!MediumEditor.util.isEdge && doc.queryCommandSupported('insertHTML')) {
                try {
                    return doc.execCommand.apply(doc, ecArgs);
                } catch (ignore) {}
            }

            selection = doc.getSelection();
            if (selection.rangeCount) {
                range = selection.getRangeAt(0);
                toReplace = range.commonAncestorContainer;

                // https://github.com/yabwe/medium-editor/issues/748
                // If the selection is an empty editor element, create a temporary text node inside of the editor
                // and select it so that we don't delete the editor element
                if (Util.isMediumEditorElement(toReplace) && !toReplace.firstChild) {
                    range.selectNode(toReplace.appendChild(doc.createTextNode('')));
                } else if ((toReplace.nodeType === 3 && range.startOffset === 0 && range.endOffset === toReplace.nodeValue.length) ||
                        (toReplace.nodeType !== 3 && toReplace.innerHTML === range.toString())) {
                    // Ensure range covers maximum amount of nodes as possible
                    // By moving up the DOM and selecting ancestors whose only child is the range
                    while (!Util.isMediumEditorElement(toReplace) &&
                            toReplace.parentNode &&
                            toReplace.parentNode.childNodes.length === 1 &&
                            !Util.isMediumEditorElement(toReplace.parentNode)) {
                        toReplace = toReplace.parentNode;
                    }
                    range.selectNode(toReplace);
                }
                range.deleteContents();

                el = doc.createElement('div');
                el.innerHTML = html;
                fragment = doc.createDocumentFragment();
                while (el.firstChild) {
                    node = el.firstChild;
                    lastNode = fragment.appendChild(node);
                }
                range.insertNode(fragment);

                // Preserve the selection:
                if (lastNode) {
                    range = range.cloneRange();
                    range.setStartAfter(lastNode);
                    range.collapse(true);
                    MediumEditor.selection.selectRange(doc, range);
                }
                res = true;
            }

            // https://github.com/yabwe/medium-editor/issues/992
            // If we're monitoring calls to execCommand, notify listeners as if a real call had happened
            if (doc.execCommand.callListeners) {
                doc.execCommand.callListeners(ecArgs, res);
            }
            return res;
        },

        execFormatBlock: function (doc, tagName) {
            // Get the top level block element that contains the selection
            var blockContainer = Util.getTopBlockContainer(MediumEditor.selection.getSelectionStart(doc)),
                childNodes;

            // Special handling for blockquote
            if (tagName === 'blockquote') {
                if (blockContainer) {
                    childNodes = Array.prototype.slice.call(blockContainer.childNodes);
                    // Check if the blockquote has a block element as a child (nested blocks)
                    if (childNodes.some(function (childNode) {
                        return Util.isBlockContainer(childNode);
                    })) {
                        // FF handles blockquote differently on formatBlock
                        // allowing nesting, we need to use outdent
                        // https://developer.mozilla.org/en-US/docs/Rich-Text_Editing_in_Mozilla
                        return doc.execCommand('outdent', false, null);
                    }
                }

                // When IE blockquote needs to be called as indent
                // http://stackoverflow.com/questions/1816223/rich-text-editor-with-blockquote-function/1821777#1821777
                if (Util.isIE) {
                    return doc.execCommand('indent', false, tagName);
                }
            }

            // If the blockContainer is already the element type being passed in
            // treat it as 'undo' formatting and just convert it to a <p>
            if (blockContainer && tagName === blockContainer.nodeName.toLowerCase()) {
                tagName = 'p';
            }

            // When IE we need to add <> to heading elements
            // http://stackoverflow.com/questions/10741831/execcommand-formatblock-headings-in-ie
            if (Util.isIE) {
                tagName = '<' + tagName + '>';
            }

            // When FF, IE and Edge, we have to handle blockquote node seperately as 'formatblock' does not work.
            // https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand#Commands
            if (blockContainer && blockContainer.nodeName.toLowerCase() === 'blockquote') {
                // For IE, just use outdent
                if (Util.isIE && tagName === '<p>') {
                    return doc.execCommand('outdent', false, tagName);
                }

                // For Firefox and Edge, make sure there's a nested block element before calling outdent
                if ((Util.isFF || Util.isEdge) && tagName === 'p') {
                    childNodes = Array.prototype.slice.call(blockContainer.childNodes);
                    // If there are some non-block elements we need to wrap everything in a <p> before we outdent
                    if (childNodes.some(function (childNode) {
                        return !Util.isBlockContainer(childNode);
                    })) {
                        doc.execCommand('formatBlock', false, tagName);
                    }
                    return doc.execCommand('outdent', false, tagName);
                }
            }

            return doc.execCommand('formatBlock', false, tagName);
        },

        /**
         * Set target to blank on the given el element
         *
         * TODO: not sure if this should be here
         *
         * When creating a link (using core -> createLink) the selection returned by Firefox will be the parent of the created link
         * instead of the created link itself (as it is for Chrome for example), so we retrieve all "a" children to grab the good one by
         * using `anchorUrl` to ensure that we are adding target="_blank" on the good one.
         * This isn't a bulletproof solution anyway ..
         */
        setTargetBlank: function (el, anchorUrl) {
            var i, url = anchorUrl || false;
            if (el.nodeName.toLowerCase() === 'a') {
                el.target = '_blank';
            } else {
                el = el.getElementsByTagName('a');

                for (i = 0; i < el.length; i += 1) {
                    if (false === url || url === el[i].attributes.href.value) {
                        el[i].target = '_blank';
                    }
                }
            }
        },

        /*
         * this function is called to explicitly remove the target='_blank' as FF holds on to _blank value even
         * after unchecking the checkbox on anchor form
         */
        removeTargetBlank: function (el, anchorUrl) {
            var i;
            if (el.nodeName.toLowerCase() === 'a') {
                el.removeAttribute('target');
            } else {
                el = el.getElementsByTagName('a');

                for (i = 0; i < el.length; i += 1) {
                    if (anchorUrl === el[i].attributes.href.value) {
                        el[i].removeAttribute('target');
                    }
                }
            }
        },

        /*
         * this function adds one or several classes on an a element.
         * if el parameter is not an a, it will look for a children of el.
         * if no a children are found, it will look for the a parent.
         */
        addClassToAnchors: function (el, buttonClass) {
            var classes = buttonClass.split(' '),
                i,
                j;
            if (el.nodeName.toLowerCase() === 'a') {
                for (j = 0; j < classes.length; j += 1) {
                    el.classList.add(classes[j]);
                }
            } else {
                var aChildren = el.getElementsByTagName('a');
                if (aChildren.length === 0) {
                    var parentAnchor = Util.getClosestTag(el, 'a');
                    el = parentAnchor ? [parentAnchor] : [];
                } else {
                    el = aChildren;
                }
                for (i = 0; i < el.length; i += 1) {
                    for (j = 0; j < classes.length; j += 1) {
                        el[i].classList.add(classes[j]);
                    }
                }
            }
        },

        isListItem: function (node) {
            if (!node) {
                return false;
            }
            if (node.nodeName.toLowerCase() === 'li') {
                return true;
            }

            var parentNode = node.parentNode,
                tagName = parentNode.nodeName.toLowerCase();
            while (tagName === 'li' || (!Util.isBlockContainer(parentNode) && tagName !== 'div')) {
                if (tagName === 'li') {
                    return true;
                }
                parentNode = parentNode.parentNode;
                if (parentNode) {
                    tagName = parentNode.nodeName.toLowerCase();
                } else {
                    return false;
                }
            }
            return false;
        },

        cleanListDOM: function (ownerDocument, element) {
            if (element.nodeName.toLowerCase() !== 'li') {
                return;
            }

            var list = element.parentElement;

            if (list.parentElement.nodeName.toLowerCase() === 'p') { // yes we need to clean up
                Util.unwrap(list.parentElement, ownerDocument);

                // move cursor at the end of the text inside the list
                // for some unknown reason, the cursor is moved to end of the "visual" line
                MediumEditor.selection.moveCursor(ownerDocument, element.firstChild, element.firstChild.textContent.length);
            }
        },

        /* splitDOMTree
         *
         * Given a root element some descendant element, split the root element
         * into its own element containing the descendant element and all elements
         * on the left or right side of the descendant ('right' is default)
         *
         * example:
         *
         *         <div>
         *      /    |   \
         *  <span> <span> <span>
         *   / \    / \    / \
         *  1   2  3   4  5   6
         *
         *  If I wanted to split this tree given the <div> as the root and "4" as the leaf
         *  the result would be (the prime ' marks indicates nodes that are created as clones):
         *
         *   SPLITTING OFF 'RIGHT' TREE       SPLITTING OFF 'LEFT' TREE
         *
         *     <div>            <div>'              <div>'      <div>
         *      / \              / \                 / \          |
         * <span> <span>   <span>' <span>       <span> <span>   <span>
         *   / \    |        |      / \           /\     /\       /\
         *  1   2   3        4     5   6         1  2   3  4     5  6
         *
         *  The above example represents splitting off the 'right' or 'left' part of a tree, where
         *  the <div>' would be returned as an element not appended to the DOM, and the <div>
         *  would remain in place where it was
         *
        */
        splitOffDOMTree: function (rootNode, leafNode, splitLeft) {
            var splitOnNode = leafNode,
                createdNode = null,
                splitRight = !splitLeft;

            // loop until we hit the root
            while (splitOnNode !== rootNode) {
                var currParent = splitOnNode.parentNode,
                    newParent = currParent.cloneNode(false),
                    targetNode = (splitRight ? splitOnNode : currParent.firstChild),
                    appendLast;

                // Create a new parent element which is a clone of the current parent
                if (createdNode) {
                    if (splitRight) {
                        // If we're splitting right, add previous created element before siblings
                        newParent.appendChild(createdNode);
                    } else {
                        // If we're splitting left, add previous created element last
                        appendLast = createdNode;
                    }
                }
                createdNode = newParent;

                while (targetNode) {
                    var sibling = targetNode.nextSibling;
                    // Special handling for the 'splitNode'
                    if (targetNode === splitOnNode) {
                        if (!targetNode.hasChildNodes()) {
                            targetNode.parentNode.removeChild(targetNode);
                        } else {
                            // For the node we're splitting on, if it has children, we need to clone it
                            // and not just move it
                            targetNode = targetNode.cloneNode(false);
                        }
                        // If the resulting split node has content, add it
                        if (targetNode.textContent) {
                            createdNode.appendChild(targetNode);
                        }

                        targetNode = (splitRight ? sibling : null);
                    } else {
                        // For general case, just remove the element and only
                        // add it to the split tree if it contains something
                        targetNode.parentNode.removeChild(targetNode);
                        if (targetNode.hasChildNodes() || targetNode.textContent) {
                            createdNode.appendChild(targetNode);
                        }

                        targetNode = sibling;
                    }
                }

                // If we had an element we wanted to append at the end, do that now
                if (appendLast) {
                    createdNode.appendChild(appendLast);
                }

                splitOnNode = currParent;
            }

            return createdNode;
        },

        moveTextRangeIntoElement: function (startNode, endNode, newElement) {
            if (!startNode || !endNode) {
                return false;
            }

            var rootNode = Util.findCommonRoot(startNode, endNode);
            if (!rootNode) {
                return false;
            }

            if (endNode === startNode) {
                var temp = startNode.parentNode,
                    sibling = startNode.nextSibling;
                temp.removeChild(startNode);
                newElement.appendChild(startNode);
                if (sibling) {
                    temp.insertBefore(newElement, sibling);
                } else {
                    temp.appendChild(newElement);
                }
                return newElement.hasChildNodes();
            }

            // create rootChildren array which includes all the children
            // we care about
            var rootChildren = [],
                firstChild,
                lastChild,
                nextNode;
            for (var i = 0; i < rootNode.childNodes.length; i++) {
                nextNode = rootNode.childNodes[i];
                if (!firstChild) {
                    if (Util.isDescendant(nextNode, startNode, true)) {
                        firstChild = nextNode;
                    }
                } else {
                    if (Util.isDescendant(nextNode, endNode, true)) {
                        lastChild = nextNode;
                        break;
                    } else {
                        rootChildren.push(nextNode);
                    }
                }
            }

            var afterLast = lastChild.nextSibling,
                fragment = rootNode.ownerDocument.createDocumentFragment();

            // build up fragment on startNode side of tree
            if (firstChild === startNode) {
                firstChild.parentNode.removeChild(firstChild);
                fragment.appendChild(firstChild);
            } else {
                fragment.appendChild(Util.splitOffDOMTree(firstChild, startNode));
            }

            // add any elements between firstChild & lastChild
            rootChildren.forEach(function (element) {
                element.parentNode.removeChild(element);
                fragment.appendChild(element);
            });

            // build up fragment on endNode side of the tree
            if (lastChild === endNode) {
                lastChild.parentNode.removeChild(lastChild);
                fragment.appendChild(lastChild);
            } else {
                fragment.appendChild(Util.splitOffDOMTree(lastChild, endNode, true));
            }

            // Add fragment into passed in element
            newElement.appendChild(fragment);

            if (lastChild.parentNode === rootNode) {
                // If last child is in the root, insert newElement in front of it
                rootNode.insertBefore(newElement, lastChild);
            } else if (afterLast) {
                // If last child was removed, but it had a sibling, insert in front of it
                rootNode.insertBefore(newElement, afterLast);
            } else {
                // lastChild was removed and was the last actual element just append
                rootNode.appendChild(newElement);
            }

            return newElement.hasChildNodes();
        },

        /* based on http://stackoverflow.com/a/6183069 */
        depthOfNode: function (inNode) {
            var theDepth = 0,
                node = inNode;
            while (node.parentNode !== null) {
                node = node.parentNode;
                theDepth++;
            }
            return theDepth;
        },

        findCommonRoot: function (inNode1, inNode2) {
            var depth1 = Util.depthOfNode(inNode1),
                depth2 = Util.depthOfNode(inNode2),
                node1 = inNode1,
                node2 = inNode2;

            while (depth1 !== depth2) {
                if (depth1 > depth2) {
                    node1 = node1.parentNode;
                    depth1 -= 1;
                } else {
                    node2 = node2.parentNode;
                    depth2 -= 1;
                }
            }

            while (node1 !== node2) {
                node1 = node1.parentNode;
                node2 = node2.parentNode;
            }

            return node1;
        },
        /* END - based on http://stackoverflow.com/a/6183069 */

        isElementAtBeginningOfBlock: function (node) {
            var textVal,
                sibling;
            while (!Util.isBlockContainer(node) && !Util.isMediumEditorElement(node)) {
                sibling = node;
                while (sibling = sibling.previousSibling) {
                    textVal = sibling.nodeType === 3 ? sibling.nodeValue : sibling.textContent;
                    if (textVal.length > 0) {
                        return false;
                    }
                }
                node = node.parentNode;
            }
            return true;
        },

        isMediumEditorElement: function (element) {
            return element && element.getAttribute && !!element.getAttribute('data-medium-editor-element');
        },

        getContainerEditorElement: function (element) {
            return Util.traverseUp(element, function (node) {
                return Util.isMediumEditorElement(node);
            });
        },

        isBlockContainer: function (element) {
            return element && element.nodeType !== 3 && Util.blockContainerElementNames.indexOf(element.nodeName.toLowerCase()) !== -1;
        },

        /* Finds the closest ancestor which is a block container element
         * If element is within editor element but not within any other block element,
         * the editor element is returned
         */
        getClosestBlockContainer: function (node) {
            return Util.traverseUp(node, function (node) {
                return Util.isBlockContainer(node) || Util.isMediumEditorElement(node);
            });
        },

        /* Finds highest level ancestor element which is a block container element
         * If element is within editor element but not within any other block element,
         * the editor element is returned
         */
        getTopBlockContainer: function (element) {
            var topBlock = Util.isBlockContainer(element) ? element : false;
            Util.traverseUp(element, function (el) {
                if (Util.isBlockContainer(el)) {
                    topBlock = el;
                }
                if (!topBlock && Util.isMediumEditorElement(el)) {
                    topBlock = el;
                    return true;
                }
                return false;
            });
            return topBlock;
        },

        getFirstSelectableLeafNode: function (element) {
            while (element && element.firstChild) {
                element = element.firstChild;
            }

            // We don't want to set the selection to an element that can't have children, this messes up Gecko.
            element = Util.traverseUp(element, function (el) {
                return Util.emptyElementNames.indexOf(el.nodeName.toLowerCase()) === -1;
            });
            // Selecting at the beginning of a table doesn't work in PhantomJS.
            if (element.nodeName.toLowerCase() === 'table') {
                var firstCell = element.querySelector('th, td');
                if (firstCell) {
                    element = firstCell;
                }
            }
            return element;
        },

        // TODO: remove getFirstTextNode AND _getFirstTextNode when jumping in 6.0.0 (no code references)
        getFirstTextNode: function (element) {
            Util.warn('getFirstTextNode is deprecated and will be removed in version 6.0.0');
            return Util._getFirstTextNode(element);
        },

        _getFirstTextNode: function (element) {
            if (element.nodeType === 3) {
                return element;
            }

            for (var i = 0; i < element.childNodes.length; i++) {
                var textNode = Util._getFirstTextNode(element.childNodes[i]);
                if (textNode !== null) {
                    return textNode;
                }
            }
            return null;
        },

        ensureUrlHasProtocol: function (url) {
            if (url.indexOf('://') === -1) {
                return 'http://' + url;
            }
            return url;
        },

        warn: function () {
            if (window.console !== undefined && typeof window.console.warn === 'function') {
                window.console.warn.apply(window.console, arguments);
            }
        },

        deprecated: function (oldName, newName, version) {
            // simple deprecation warning mechanism.
            var m = oldName + ' is deprecated, please use ' + newName + ' instead.';
            if (version) {
                m += ' Will be removed in ' + version;
            }
            Util.warn(m);
        },

        deprecatedMethod: function (oldName, newName, args, version) {
            // run the replacement and warn when someone calls a deprecated method
            Util.deprecated(oldName, newName, version);
            if (typeof this[newName] === 'function') {
                this[newName].apply(this, args);
            }
        },

        cleanupAttrs: function (el, attrs) {
            attrs.forEach(function (attr) {
                el.removeAttribute(attr);
            });
        },

        cleanupTags: function (el, tags) {
            if (tags.indexOf(el.nodeName.toLowerCase()) !== -1) {
                el.parentNode.removeChild(el);
            }
        },

        unwrapTags: function (el, tags) {
            if (tags.indexOf(el.nodeName.toLowerCase()) !== -1) {
                MediumEditor.util.unwrap(el, document);
            }
        },

        // get the closest parent
        getClosestTag: function (el, tag) {
            return Util.traverseUp(el, function (element) {
                return element.nodeName.toLowerCase() === tag.toLowerCase();
            });
        },

        unwrap: function (el, doc) {
            var fragment = doc.createDocumentFragment(),
                nodes = Array.prototype.slice.call(el.childNodes);

            // cast nodeList to array since appending child
            // to a different node will alter length of el.childNodes
            for (var i = 0; i < nodes.length; i++) {
                fragment.appendChild(nodes[i]);
            }

            if (fragment.childNodes.length) {
                el.parentNode.replaceChild(fragment, el);
            } else {
                el.parentNode.removeChild(el);
            }
        },

        guid: function () {
            function _s4() {
                return Math
                    .floor((1 + Math.random()) * 0x10000)
                    .toString(16)
                    .substring(1);
            }

            return _s4() + _s4() + '-' + _s4() + '-' + _s4() + '-' + _s4() + '-' + _s4() + _s4() + _s4();
        }
    };

    MediumEditor.util = Util;
}(window));

(function () {
    'use strict';

    var Extension = function (options) {
        MediumEditor.util.extend(this, options);
    };

    Extension.extend = function (protoProps) {
        // magic extender thinger. mostly borrowed from backbone/goog.inherits
        // place this function on some thing you want extend-able.
        //
        // example:
        //
        //      function Thing(args){
        //          this.options = args;
        //      }
        //
        //      Thing.prototype = { foo: "bar" };
        //      Thing.extend = extenderify;
        //
        //      var ThingTwo = Thing.extend({ foo: "baz" });
        //
        //      var thingOne = new Thing(); // foo === "bar"
        //      var thingTwo = new ThingTwo(); // foo === "baz"
        //
        //      which seems like some simply shallow copy nonsense
        //      at first, but a lot more is going on there.
        //
        //      passing a `constructor` to the extend props
        //      will cause the instance to instantiate through that
        //      instead of the parent's constructor.

        var parent = this,
            child;

        // The constructor function for the new subclass is either defined by you
        // (the "constructor" property in your `extend` definition), or defaulted
        // by us to simply call the parent's constructor.

        if (protoProps && protoProps.hasOwnProperty('constructor')) {
            child = protoProps.constructor;
        } else {
            child = function () {
                return parent.apply(this, arguments);
            };
        }

        // das statics (.extend comes over, so your subclass can have subclasses too)
        MediumEditor.util.extend(child, parent);

        // Set the prototype chain to inherit from `parent`, without calling
        // `parent`'s constructor function.
        var Surrogate = function () {
            this.constructor = child;
        };
        Surrogate.prototype = parent.prototype;
        child.prototype = new Surrogate();

        if (protoProps) {
            MediumEditor.util.extend(child.prototype, protoProps);
        }

        // todo: $super?

        return child;
    };

    Extension.prototype = {
        /* init: [function]
         *
         * Called by MediumEditor during initialization.
         * The .base property will already have been set to
         * current instance of MediumEditor when this is called.
         * All helper methods will exist as well
         */
        init: function () {},

        /* base: [MediumEditor instance]
         *
         * If not overriden, this will be set to the current instance
         * of MediumEditor, before the init method is called
         */
        base: undefined,

        /* name: [string]
         *
         * 'name' of the extension, used for retrieving the extension.
         * If not set, MediumEditor will set this to be the key
         * used when passing the extension into MediumEditor via the
         * 'extensions' option
         */
        name: undefined,

        /* checkState: [function (node)]
         *
         * If implemented, this function will be called one or more times
         * the state of the editor & toolbar are updated.
         * When the state is updated, the editor does the following:
         *
         * 1) Find the parent node containing the current selection
         * 2) Call checkState on the extension, passing the node as an argument
         * 3) Get the parent node of the previous node
         * 4) Repeat steps #2 and #3 until we move outside the parent contenteditable
         */
        checkState: undefined,

        /* destroy: [function ()]
         *
         * This method should remove any created html, custom event handlers
         * or any other cleanup tasks that should be performed.
         * If implemented, this function will be called when MediumEditor's
         * destroy method has been called.
         */
        destroy: undefined,

        /* As alternatives to checkState, these functions provide a more structured
         * path to updating the state of an extension (usually a button) whenever
         * the state of the editor & toolbar are updated.
         */

        /* queryCommandState: [function ()]
         *
         * If implemented, this function will be called once on each extension
         * when the state of the editor/toolbar is being updated.
         *
         * If this function returns a non-null value, the extension will
         * be ignored as the code climbs the dom tree.
         *
         * If this function returns true, and the setActive() function is defined
         * setActive() will be called
         */
        queryCommandState: undefined,

        /* isActive: [function ()]
         *
         * If implemented, this function will be called when MediumEditor
         * has determined that this extension is 'active' for the current selection.
         * This may be called when the editor & toolbar are being updated,
         * but only if queryCommandState() or isAlreadyApplied() functions
         * are implemented, and when called, return true.
         */
        isActive: undefined,

        /* isAlreadyApplied: [function (node)]
         *
         * If implemented, this function is similar to checkState() in
         * that it will be called repeatedly as MediumEditor moves up
         * the DOM to update the editor & toolbar after a state change.
         *
         * NOTE: This function will NOT be called if checkState() has
         * been implemented. This function will NOT be called if
         * queryCommandState() is implemented and returns a non-null
         * value when called
         */
        isAlreadyApplied: undefined,

        /* setActive: [function ()]
         *
         * If implemented, this function is called when MediumEditor knows
         * that this extension is currently enabled.  Currently, this
         * function is called when updating the editor & toolbar, and
         * only if queryCommandState() or isAlreadyApplied(node) return
         * true when called
         */
        setActive: undefined,

        /* setInactive: [function ()]
         *
         * If implemented, this function is called when MediumEditor knows
         * that this extension is currently disabled.  Curently, this
         * is called at the beginning of each state change for
         * the editor & toolbar. After calling this, MediumEditor
         * will attempt to update the extension, either via checkState()
         * or the combination of queryCommandState(), isAlreadyApplied(node),
         * isActive(), and setActive()
         */
        setInactive: undefined,

        /* getInteractionElements: [function ()]
         *
         * If the extension renders any elements that the user can interact with,
         * this method should be implemented and return the root element or an array
         * containing all of the root elements. MediumEditor will call this function
         * during interaction to see if the user clicked on something outside of the editor.
         * The elements are used to check if the target element of a click or
         * other user event is a descendant of any extension elements.
         * This way, the editor can also count user interaction within editor elements as
         * interactions with the editor, and thus not trigger 'blur'
         */
        getInteractionElements: undefined,

        /************************ Helpers ************************
         * The following are helpers that are either set by MediumEditor
         * during initialization, or are helper methods which either
         * route calls to the MediumEditor instance or provide common
         * functionality for all extensions
         *********************************************************/

        /* window: [Window]
         *
         * If not overriden, this will be set to the window object
         * to be used by MediumEditor and its extensions.  This is
         * passed via the 'contentWindow' option to MediumEditor
         * and is the global 'window' object by default
         */
        'window': undefined,

        /* document: [Document]
         *
         * If not overriden, this will be set to the document object
         * to be used by MediumEditor and its extensions. This is
         * passed via the 'ownerDocument' optin to MediumEditor
         * and is the global 'document' object by default
         */
        'document': undefined,

        /* getEditorElements: [function ()]
         *
         * Helper function which returns an array containing
         * all the contenteditable elements for this instance
         * of MediumEditor
         */
        getEditorElements: function () {
            return this.base.elements;
        },

        /* getEditorId: [function ()]
         *
         * Helper function which returns a unique identifier
         * for this instance of MediumEditor
         */
        getEditorId: function () {
            return this.base.id;
        },

        /* getEditorOptions: [function (option)]
         *
         * Helper function which returns the value of an option
         * used to initialize this instance of MediumEditor
         */
        getEditorOption: function (option) {
            return this.base.options[option];
        }
    };

    /* List of method names to add to the prototype of Extension
     * Each of these methods will be defined as helpers that
     * just call directly into the MediumEditor instance.
     *
     * example for 'on' method:
     * Extension.prototype.on = function () {
     *     return this.base.on.apply(this.base, arguments);
     * }
     */
    [
        // general helpers
        'execAction',

        // event handling
        'on',
        'off',
        'subscribe',
        'trigger'

    ].forEach(function (helper) {
        Extension.prototype[helper] = function () {
            return this.base[helper].apply(this.base, arguments);
        };
    });

    MediumEditor.Extension = Extension;
})();

(function () {
    'use strict';

    function filterOnlyParentElements(node) {
        if (MediumEditor.util.isBlockContainer(node)) {
            return NodeFilter.FILTER_ACCEPT;
        } else {
            return NodeFilter.FILTER_SKIP;
        }
    }

    var Selection = {
        findMatchingSelectionParent: function (testElementFunction, contentWindow) {
            var selection = contentWindow.getSelection(),
                range,
                current;

            if (selection.rangeCount === 0) {
                return false;
            }

            range = selection.getRangeAt(0);
            current = range.commonAncestorContainer;

            return MediumEditor.util.traverseUp(current, testElementFunction);
        },

        getSelectionElement: function (contentWindow) {
            return this.findMatchingSelectionParent(function (el) {
                return MediumEditor.util.isMediumEditorElement(el);
            }, contentWindow);
        },

        // http://stackoverflow.com/questions/17678843/cant-restore-selection-after-html-modify-even-if-its-the-same-html
        // Tim Down
        exportSelection: function (root, doc) {
            if (!root) {
                return null;
            }

            var selectionState = null,
                selection = doc.getSelection();

            if (selection.rangeCount > 0) {
                var range = selection.getRangeAt(0),
                    preSelectionRange = range.cloneRange(),
                    start;

                preSelectionRange.selectNodeContents(root);
                preSelectionRange.setEnd(range.startContainer, range.startOffset);
                start = preSelectionRange.toString().length;

                selectionState = {
                    start: start,
                    end: start + range.toString().length
                };

                // Check to see if the selection starts with any images
                // if so we need to make sure the the beginning of the selection is
                // set correctly when importing selection
                if (this.doesRangeStartWithImages(range, doc)) {
                    selectionState.startsWithImage = true;
                }

                // Check to see if the selection has any trailing images
                // if so, this this means we need to look for them when we import selection
                var trailingImageCount = this.getTrailingImageCount(root, selectionState, range.endContainer, range.endOffset);
                if (trailingImageCount) {
                    selectionState.trailingImageCount = trailingImageCount;
                }

                // If start = 0 there may still be an empty paragraph before it, but we don't care.
                if (start !== 0) {
                    var emptyBlocksIndex = this.getIndexRelativeToAdjacentEmptyBlocks(doc, root, range.startContainer, range.startOffset);
                    if (emptyBlocksIndex !== -1) {
                        selectionState.emptyBlocksIndex = emptyBlocksIndex;
                    }
                }
            }

            return selectionState;
        },

        // http://stackoverflow.com/questions/17678843/cant-restore-selection-after-html-modify-even-if-its-the-same-html
        // Tim Down
        //
        // {object} selectionState - the selection to import
        // {DOMElement} root - the root element the selection is being restored inside of
        // {Document} doc - the document to use for managing selection
        // {boolean} [favorLaterSelectionAnchor] - defaults to false. If true, import the cursor immediately
        //      subsequent to an anchor tag if it would otherwise be placed right at the trailing edge inside the
        //      anchor. This cursor positioning, even though visually equivalent to the user, can affect behavior
        //      in MS IE.
        importSelection: function (selectionState, root, doc, favorLaterSelectionAnchor) {
            if (!selectionState || !root) {
                return;
            }

            var range = doc.createRange();
            range.setStart(root, 0);
            range.collapse(true);

            var node = root,
                nodeStack = [],
                charIndex = 0,
                foundStart = false,
                foundEnd = false,
                trailingImageCount = 0,
                stop = false,
                nextCharIndex,
                allowRangeToStartAtEndOfNode = false,
                lastTextNode = null;

            // When importing selection, the start of the selection may lie at the end of an element
            // or at the beginning of an element.  Since visually there is no difference between these 2
            // we will try to move the selection to the beginning of an element since this is generally
            // what users will expect and it's a more predictable behavior.
            //
            // However, there are some specific cases when we don't want to do this:
            //  1) We're attempting to move the cursor outside of the end of an anchor [favorLaterSelectionAnchor = true]
            //  2) The selection starts with an image, which is special since an image doesn't have any 'content'
            //     as far as selection and ranges are concerned
            //  3) The selection starts after a specified number of empty block elements (selectionState.emptyBlocksIndex)
            //
            // For these cases, we want the selection to start at a very specific location, so we should NOT
            // automatically move the cursor to the beginning of the first actual chunk of text
            if (favorLaterSelectionAnchor || selectionState.startsWithImage || typeof selectionState.emptyBlocksIndex !== 'undefined') {
                allowRangeToStartAtEndOfNode = true;
            }

            while (!stop && node) {
                // Only iterate over elements and text nodes
                if (node.nodeType > 3) {
                    node = nodeStack.pop();
                    continue;
                }

                // If we hit a text node, we need to add the amount of characters to the overall count
                if (node.nodeType === 3 && !foundEnd) {
                    nextCharIndex = charIndex + node.length;
                    // Check if we're at or beyond the start of the selection we're importing
                    if (!foundStart && selectionState.start >= charIndex && selectionState.start <= nextCharIndex) {
                        // NOTE: We only want to allow a selection to start at the END of an element if
                        //  allowRangeToStartAtEndOfNode is true
                        if (allowRangeToStartAtEndOfNode || selectionState.start < nextCharIndex) {
                            range.setStart(node, selectionState.start - charIndex);
                            foundStart = true;
                        }
                        // We're at the end of a text node where the selection could start but we shouldn't
                        // make the selection start here because allowRangeToStartAtEndOfNode is false.
                        // However, we should keep a reference to this node in case there aren't any more
                        // text nodes after this, so that we have somewhere to import the selection to
                        else {
                            lastTextNode = node;
                        }
                    }
                    // We've found the start of the selection, check if we're at or beyond the end of the selection we're importing
                    if (foundStart && selectionState.end >= charIndex && selectionState.end <= nextCharIndex) {
                        if (!selectionState.trailingImageCount) {
                            range.setEnd(node, selectionState.end - charIndex);
                            stop = true;
                        } else {
                            foundEnd = true;
                        }
                    }
                    charIndex = nextCharIndex;
                } else {
                    if (selectionState.trailingImageCount && foundEnd) {
                        if (node.nodeName.toLowerCase() === 'img') {
                            trailingImageCount++;
                        }
                        if (trailingImageCount === selectionState.trailingImageCount) {
                            // Find which index the image is in its parent's children
                            var endIndex = 0;
                            while (node.parentNode.childNodes[endIndex] !== node) {
                                endIndex++;
                            }
                            range.setEnd(node.parentNode, endIndex + 1);
                            stop = true;
                        }
                    }

                    if (!stop && node.nodeType === 1) {
                        // this is an element
                        // add all its children to the stack
                        var i = node.childNodes.length - 1;
                        while (i >= 0) {
                            nodeStack.push(node.childNodes[i]);
                            i -= 1;
                        }
                    }
                }

                if (!stop) {
                    node = nodeStack.pop();
                }
            }

            // If we've gone through the entire text but didn't find the beginning of a text node
            // to make the selection start at, we should fall back to starting the selection
            // at the END of the last text node we found
            if (!foundStart && lastTextNode) {
                range.setStart(lastTextNode, lastTextNode.length);
                range.setEnd(lastTextNode, lastTextNode.length);
            }

            if (typeof selectionState.emptyBlocksIndex !== 'undefined') {
                range = this.importSelectionMoveCursorPastBlocks(doc, root, selectionState.emptyBlocksIndex, range);
            }

            // If the selection is right at the ending edge of a link, put it outside the anchor tag instead of inside.
            if (favorLaterSelectionAnchor) {
                range = this.importSelectionMoveCursorPastAnchor(selectionState, range);
            }

            this.selectRange(doc, range);
        },

        // Utility method called from importSelection only
        importSelectionMoveCursorPastAnchor: function (selectionState, range) {
            var nodeInsideAnchorTagFunction = function (node) {
                return node.nodeName.toLowerCase() === 'a';
            };
            if (selectionState.start === selectionState.end &&
                    range.startContainer.nodeType === 3 &&
                    range.startOffset === range.startContainer.nodeValue.length &&
                    MediumEditor.util.traverseUp(range.startContainer, nodeInsideAnchorTagFunction)) {
                var prevNode = range.startContainer,
                    currentNode = range.startContainer.parentNode;
                while (currentNode !== null && currentNode.nodeName.toLowerCase() !== 'a') {
                    if (currentNode.childNodes[currentNode.childNodes.length - 1] !== prevNode) {
                        currentNode = null;
                    } else {
                        prevNode = currentNode;
                        currentNode = currentNode.parentNode;
                    }
                }
                if (currentNode !== null && currentNode.nodeName.toLowerCase() === 'a') {
                    var currentNodeIndex = null;
                    for (var i = 0; currentNodeIndex === null && i < currentNode.parentNode.childNodes.length; i++) {
                        if (currentNode.parentNode.childNodes[i] === currentNode) {
                            currentNodeIndex = i;
                        }
                    }
                    range.setStart(currentNode.parentNode, currentNodeIndex + 1);
                    range.collapse(true);
                }
            }
            return range;
        },

        // Uses the emptyBlocksIndex calculated by getIndexRelativeToAdjacentEmptyBlocks
        // to move the cursor back to the start of the correct paragraph
        importSelectionMoveCursorPastBlocks: function (doc, root, index, range) {
            var treeWalker = doc.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, filterOnlyParentElements, false),
                startContainer = range.startContainer,
                startBlock,
                targetNode,
                currIndex = 0;
            index = index || 1; // If index is 0, we still want to move to the next block

            // Chrome counts newlines and spaces that separate block elements as actual elements.
            // If the selection is inside one of these text nodes, and it has a previous sibling
            // which is a block element, we want the treewalker to start at the previous sibling
            // and NOT at the parent of the textnode
            if (startContainer.nodeType === 3 && MediumEditor.util.isBlockContainer(startContainer.previousSibling)) {
                startBlock = startContainer.previousSibling;
            } else {
                startBlock = MediumEditor.util.getClosestBlockContainer(startContainer);
            }

            // Skip over empty blocks until we hit the block we want the selection to be in
            while (treeWalker.nextNode()) {
                if (!targetNode) {
                    // Loop through all blocks until we hit the starting block element
                    if (startBlock === treeWalker.currentNode) {
                        targetNode = treeWalker.currentNode;
                    }
                } else {
                    targetNode = treeWalker.currentNode;
                    currIndex++;
                    // We hit the target index, bail
                    if (currIndex === index) {
                        break;
                    }
                    // If we find a non-empty block, ignore the emptyBlocksIndex and just put selection here
                    if (targetNode.textContent.length > 0) {
                        break;
                    }
                }
            }

            if (!targetNode) {
                targetNode = startBlock;
            }

            // We're selecting a high-level block node, so make sure the cursor gets moved into the deepest
            // element at the beginning of the block
            range.setStart(MediumEditor.util.getFirstSelectableLeafNode(targetNode), 0);

            return range;
        },

        // Returns -1 unless the cursor is at the beginning of a paragraph/block
        // If the paragraph/block is preceeded by empty paragraphs/block (with no text)
        // it will return the number of empty paragraphs before the cursor.
        // Otherwise, it will return 0, which indicates the cursor is at the beginning
        // of a paragraph/block, and not at the end of the paragraph/block before it
        getIndexRelativeToAdjacentEmptyBlocks: function (doc, root, cursorContainer, cursorOffset) {
            // If there is text in front of the cursor, that means there isn't only empty blocks before it
            if (cursorContainer.textContent.length > 0 && cursorOffset > 0) {
                return -1;
            }

            // Check if the block that contains the cursor has any other text in front of the cursor
            var node = cursorContainer;
            if (node.nodeType !== 3) {
                node = cursorContainer.childNodes[cursorOffset];
            }
            if (node) {
                // The element isn't at the beginning of a block, so it has content before it
                if (!MediumEditor.util.isElementAtBeginningOfBlock(node)) {
                    return -1;
                }

                var previousSibling = MediumEditor.util.findPreviousSibling(node);
                // If there is no previous sibling, this is the first text element in the editor
                if (!previousSibling) {
                    return -1;
                }
                // If the previous sibling has text, then there are no empty blocks before this
                else if (previousSibling.nodeValue) {
                    return -1;
                }
            }

            // Walk over block elements, counting number of empty blocks between last piece of text
            // and the block the cursor is in
            var closestBlock = MediumEditor.util.getClosestBlockContainer(cursorContainer),
                treeWalker = doc.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, filterOnlyParentElements, false),
                emptyBlocksCount = 0;
            while (treeWalker.nextNode()) {
                var blockIsEmpty = treeWalker.currentNode.textContent === '';
                if (blockIsEmpty || emptyBlocksCount > 0) {
                    emptyBlocksCount += 1;
                }
                if (treeWalker.currentNode === closestBlock) {
                    return emptyBlocksCount;
                }
                if (!blockIsEmpty) {
                    emptyBlocksCount = 0;
                }
            }

            return emptyBlocksCount;
        },

        // Returns true if the selection range begins with an image tag
        // Returns false if the range starts with any non empty text nodes
        doesRangeStartWithImages: function (range, doc) {
            if (range.startOffset !== 0 || range.startContainer.nodeType !== 1) {
                return false;
            }

            if (range.startContainer.nodeName.toLowerCase() === 'img') {
                return true;
            }

            var img = range.startContainer.querySelector('img');
            if (!img) {
                return false;
            }

            var treeWalker = doc.createTreeWalker(range.startContainer, NodeFilter.SHOW_ALL, null, false);
            while (treeWalker.nextNode()) {
                var next = treeWalker.currentNode;
                // If we hit the image, then there isn't any text before the image so
                // the image is at the beginning of the range
                if (next === img) {
                    break;
                }
                // If we haven't hit the iamge, but found text that contains content
                // then the range doesn't start with an image
                if (next.nodeValue) {
                    return false;
                }
            }

            return true;
        },

        getTrailingImageCount: function (root, selectionState, endContainer, endOffset) {
            // If the endOffset of a range is 0, the endContainer doesn't contain images
            // If the endContainer is a text node, there are no trailing images
            if (endOffset === 0 || endContainer.nodeType !== 1) {
                return 0;
            }

            // If the endContainer isn't an image, and doesn't have an image descendants
            // there are no trailing images
            if (endContainer.nodeName.toLowerCase() !== 'img' && !endContainer.querySelector('img')) {
                return 0;
            }

            var lastNode = endContainer.childNodes[endOffset - 1];
            while (lastNode.hasChildNodes()) {
                lastNode = lastNode.lastChild;
            }

            var node = root,
                nodeStack = [],
                charIndex = 0,
                foundStart = false,
                foundEnd = false,
                stop = false,
                nextCharIndex,
                trailingImages = 0;

            while (!stop && node) {
                // Only iterate over elements and text nodes
                if (node.nodeType > 3) {
                    node = nodeStack.pop();
                    continue;
                }

                if (node.nodeType === 3 && !foundEnd) {
                    trailingImages = 0;
                    nextCharIndex = charIndex + node.length;
                    if (!foundStart && selectionState.start >= charIndex && selectionState.start <= nextCharIndex) {
                        foundStart = true;
                    }
                    if (foundStart && selectionState.end >= charIndex && selectionState.end <= nextCharIndex) {
                        foundEnd = true;
                    }
                    charIndex = nextCharIndex;
                } else {
                    if (node.nodeName.toLowerCase() === 'img') {
                        trailingImages++;
                    }

                    if (node === lastNode) {
                        stop = true;
                    } else if (node.nodeType === 1) {
                        // this is an element
                        // add all its children to the stack
                        var i = node.childNodes.length - 1;
                        while (i >= 0) {
                            nodeStack.push(node.childNodes[i]);
                            i -= 1;
                        }
                    }
                }

                if (!stop) {
                    node = nodeStack.pop();
                }
            }

            return trailingImages;
        },

        // determine if the current selection contains any 'content'
        // content being any non-white space text or an image
        selectionContainsContent: function (doc) {
            var sel = doc.getSelection();

            // collapsed selection or selection withour range doesn't contain content
            if (!sel || sel.isCollapsed || !sel.rangeCount) {
                return false;
            }

            // if toString() contains any text, the selection contains some content
            if (sel.toString().trim() !== '') {
                return true;
            }

            // if selection contains only image(s), it will return empty for toString()
            // so check for an image manually
            var selectionNode = this.getSelectedParentElement(sel.getRangeAt(0));
            if (selectionNode) {
                if (selectionNode.nodeName.toLowerCase() === 'img' ||
                    (selectionNode.nodeType === 1 && selectionNode.querySelector('img'))) {
                    return true;
                }
            }

            return false;
        },

        selectionInContentEditableFalse: function (contentWindow) {
            // determine if the current selection is exclusively inside
            // a contenteditable="false", though treat the case of an
            // explicit contenteditable="true" inside a "false" as false.
            var sawtrue,
                sawfalse = this.findMatchingSelectionParent(function (el) {
                    var ce = el && el.getAttribute('contenteditable');
                    if (ce === 'true') {
                        sawtrue = true;
                    }
                    return el.nodeName !== '#text' && ce === 'false';
                }, contentWindow);

            return !sawtrue && sawfalse;
        },

        // http://stackoverflow.com/questions/4176923/html-of-selected-text
        // by Tim Down
        getSelectionHtml: function getSelectionHtml(doc) {
            var i,
                html = '',
                sel = doc.getSelection(),
                len,
                container;
            if (sel.rangeCount) {
                container = doc.createElement('div');
                for (i = 0, len = sel.rangeCount; i < len; i += 1) {
                    container.appendChild(sel.getRangeAt(i).cloneContents());
                }
                html = container.innerHTML;
            }
            return html;
        },

        /**
         *  Find the caret position within an element irrespective of any inline tags it may contain.
         *
         *  @param {DOMElement} An element containing the cursor to find offsets relative to.
         *  @param {Range} A Range representing cursor position. Will window.getSelection if none is passed.
         *  @return {Object} 'left' and 'right' attributes contain offsets from begining and end of Element
         */
        getCaretOffsets: function getCaretOffsets(element, range) {
            var preCaretRange, postCaretRange;

            if (!range) {
                range = window.getSelection().getRangeAt(0);
            }

            preCaretRange = range.cloneRange();
            postCaretRange = range.cloneRange();

            preCaretRange.selectNodeContents(element);
            preCaretRange.setEnd(range.endContainer, range.endOffset);

            postCaretRange.selectNodeContents(element);
            postCaretRange.setStart(range.endContainer, range.endOffset);

            return {
                left: preCaretRange.toString().length,
                right: postCaretRange.toString().length
            };
        },

        // http://stackoverflow.com/questions/15867542/range-object-get-selection-parent-node-chrome-vs-firefox
        rangeSelectsSingleNode: function (range) {
            var startNode = range.startContainer;
            return startNode === range.endContainer &&
                startNode.hasChildNodes() &&
                range.endOffset === range.startOffset + 1;
        },

        getSelectedParentElement: function (range) {
            if (!range) {
                return null;
            }

            // Selection encompasses a single element
            if (this.rangeSelectsSingleNode(range) && range.startContainer.childNodes[range.startOffset].nodeType !== 3) {
                return range.startContainer.childNodes[range.startOffset];
            }

            // Selection range starts inside a text node, so get its parent
            if (range.startContainer.nodeType === 3) {
                return range.startContainer.parentNode;
            }

            // Selection starts inside an element
            return range.startContainer;
        },

        getSelectedElements: function (doc) {
            var selection = doc.getSelection(),
                range,
                toRet,
                currNode;

            if (!selection.rangeCount || selection.isCollapsed || !selection.getRangeAt(0).commonAncestorContainer) {
                return [];
            }

            range = selection.getRangeAt(0);

            if (range.commonAncestorContainer.nodeType === 3) {
                toRet = [];
                currNode = range.commonAncestorContainer;
                while (currNode.parentNode && currNode.parentNode.childNodes.length === 1) {
                    toRet.push(currNode.parentNode);
                    currNode = currNode.parentNode;
                }

                return toRet;
            }

            return [].filter.call(range.commonAncestorContainer.getElementsByTagName('*'), function (el) {
                return (typeof selection.containsNode === 'function') ? selection.containsNode(el, true) : true;
            });
        },

        selectNode: function (node, doc) {
            var range = doc.createRange();
            range.selectNodeContents(node);
            this.selectRange(doc, range);
        },

        select: function (doc, startNode, startOffset, endNode, endOffset) {
            var range = doc.createRange();
            range.setStart(startNode, startOffset);
            if (endNode) {
                range.setEnd(endNode, endOffset);
            } else {
                range.collapse(true);
            }
            this.selectRange(doc, range);
            return range;
        },

        /**
         *  Clear the current highlighted selection and set the caret to the start or the end of that prior selection, defaults to end.
         *
         *  @param {DomDocument} doc            Current document
         *  @param {boolean} moveCursorToStart  A boolean representing whether or not to set the caret to the beginning of the prior selection.
         */
        clearSelection: function (doc, moveCursorToStart) {
            if (moveCursorToStart) {
                doc.getSelection().collapseToStart();
            } else {
                doc.getSelection().collapseToEnd();
            }
        },

        /**
         * Move cursor to the given node with the given offset.
         *
         * @param  {DomDocument} doc     Current document
         * @param  {DomElement}  node    Element where to jump
         * @param  {integer}     offset  Where in the element should we jump, 0 by default
         */
        moveCursor: function (doc, node, offset) {
            this.select(doc, node, offset);
        },

        getSelectionRange: function (ownerDocument) {
            var selection = ownerDocument.getSelection();
            if (selection.rangeCount === 0) {
                return null;
            }
            return selection.getRangeAt(0);
        },

        selectRange: function (ownerDocument, range) {
            var selection = ownerDocument.getSelection();

            selection.removeAllRanges();
            selection.addRange(range);
        },

        // http://stackoverflow.com/questions/1197401/how-can-i-get-the-element-the-caret-is-in-with-javascript-when-using-contentedi
        // by You
        getSelectionStart: function (ownerDocument) {
            var node = ownerDocument.getSelection().anchorNode,
                startNode = (node && node.nodeType === 3 ? node.parentNode : node);

            return startNode;
        }
    };

    MediumEditor.selection = Selection;
}());

(function () {
    'use strict';

    function isElementDescendantOfExtension(extensions, element) {
        return extensions.some(function (extension) {
            if (typeof extension.getInteractionElements !== 'function') {
                return false;
            }

            var extensionElements = extension.getInteractionElements();
            if (!extensionElements) {
                return false;
            }

            if (!Array.isArray(extensionElements)) {
                extensionElements = [extensionElements];
            }
            return extensionElements.some(function (el) {
                return MediumEditor.util.isDescendant(el, element, true);
            });
        });
    }

    var Events = function (instance) {
        this.base = instance;
        this.options = this.base.options;
        this.events = [];
        this.disabledEvents = {};
        this.customEvents = {};
        this.listeners = {};
    };

    Events.prototype = {
        InputEventOnContenteditableSupported: !MediumEditor.util.isIE && !MediumEditor.util.isEdge,

        // Helpers for event handling

        attachDOMEvent: function (targets, event, listener, useCapture) {
            var win = this.base.options.contentWindow,
                doc = this.base.options.ownerDocument;

            targets = MediumEditor.util.isElement(targets) || [win, doc].indexOf(targets) > -1 ? [targets] : targets;

            Array.prototype.forEach.call(targets, function (target) {
                target.addEventListener(event, listener, useCapture);
                this.events.push([target, event, listener, useCapture]);
            }.bind(this));
        },

        detachDOMEvent: function (targets, event, listener, useCapture) {
            var index, e,
                win = this.base.options.contentWindow,
                doc = this.base.options.ownerDocument;

            if (targets !== null) {
                targets = MediumEditor.util.isElement(targets) || [win, doc].indexOf(targets) > -1 ? [targets] : targets;

                Array.prototype.forEach.call(targets, function (target) {
                    index = this.indexOfListener(target, event, listener, useCapture);
                    if (index !== -1) {
                        e = this.events.splice(index, 1)[0];
                        e[0].removeEventListener(e[1], e[2], e[3]);
                    }
                }.bind(this));
            }
        },

        indexOfListener: function (target, event, listener, useCapture) {
            var i, n, item;
            for (i = 0, n = this.events.length; i < n; i = i + 1) {
                item = this.events[i];
                if (item[0] === target && item[1] === event && item[2] === listener && item[3] === useCapture) {
                    return i;
                }
            }
            return -1;
        },

        detachAllDOMEvents: function () {
            var e = this.events.pop();
            while (e) {
                e[0].removeEventListener(e[1], e[2], e[3]);
                e = this.events.pop();
            }
        },

        detachAllEventsFromElement: function (element) {
            var filtered = this.events.filter(function (e) {
                return e && e[0].getAttribute && e[0].getAttribute('medium-editor-index') === element.getAttribute('medium-editor-index');
            });

            for (var i = 0, len = filtered.length; i < len; i++) {
                var e = filtered[i];
                this.detachDOMEvent(e[0], e[1], e[2], e[3]);
            }
        },

        // Attach all existing handlers to a new element
        attachAllEventsToElement: function (element) {
            if (this.listeners['editableInput']) {
                this.contentCache[element.getAttribute('medium-editor-index')] = element.innerHTML;
            }

            if (this.eventsCache) {
                this.eventsCache.forEach(function (e) {
                    this.attachDOMEvent(element, e['name'], e['handler'].bind(this));
                }, this);
            }
        },

        enableCustomEvent: function (event) {
            if (this.disabledEvents[event] !== undefined) {
                delete this.disabledEvents[event];
            }
        },

        disableCustomEvent: function (event) {
            this.disabledEvents[event] = true;
        },

        // custom events
        attachCustomEvent: function (event, listener) {
            this.setupListener(event);
            if (!this.customEvents[event]) {
                this.customEvents[event] = [];
            }
            this.customEvents[event].push(listener);
        },

        detachCustomEvent: function (event, listener) {
            var index = this.indexOfCustomListener(event, listener);
            if (index !== -1) {
                this.customEvents[event].splice(index, 1);
                // TODO: If array is empty, should detach internal listeners via destroyListener()
            }
        },

        indexOfCustomListener: function (event, listener) {
            if (!this.customEvents[event] || !this.customEvents[event].length) {
                return -1;
            }

            return this.customEvents[event].indexOf(listener);
        },

        detachAllCustomEvents: function () {
            this.customEvents = {};
            // TODO: Should detach internal listeners here via destroyListener()
        },

        triggerCustomEvent: function (name, data, editable) {
            if (this.customEvents[name] && !this.disabledEvents[name]) {
                this.customEvents[name].forEach(function (listener) {
                    listener(data, editable);
                });
            }
        },

        // Cleaning up

        destroy: function () {
            this.detachAllDOMEvents();
            this.detachAllCustomEvents();
            this.detachExecCommand();

            if (this.base.elements) {
                this.base.elements.forEach(function (element) {
                    element.removeAttribute('data-medium-focused');
                });
            }
        },

        // Listening to calls to document.execCommand

        // Attach a listener to be notified when document.execCommand is called
        attachToExecCommand: function () {
            if (this.execCommandListener) {
                return;
            }

            // Store an instance of the listener so:
            // 1) We only attach to execCommand once
            // 2) We can remove the listener later
            this.execCommandListener = function (execInfo) {
                this.handleDocumentExecCommand(execInfo);
            }.bind(this);

            // Ensure that execCommand has been wrapped correctly
            this.wrapExecCommand();

            // Add listener to list of execCommand listeners
            this.options.ownerDocument.execCommand.listeners.push(this.execCommandListener);
        },

        // Remove our listener for calls to document.execCommand
        detachExecCommand: function () {
            var doc = this.options.ownerDocument;
            if (!this.execCommandListener || !doc.execCommand.listeners) {
                return;
            }

            // Find the index of this listener in the array of listeners so it can be removed
            var index = doc.execCommand.listeners.indexOf(this.execCommandListener);
            if (index !== -1) {
                doc.execCommand.listeners.splice(index, 1);
            }

            // If the list of listeners is now empty, put execCommand back to its original state
            if (!doc.execCommand.listeners.length) {
                this.unwrapExecCommand();
            }
        },

        // Wrap document.execCommand in a custom method so we can listen to calls to it
        wrapExecCommand: function () {
            var doc = this.options.ownerDocument;

            // Ensure all instance of MediumEditor only wrap execCommand once
            if (doc.execCommand.listeners) {
                return;
            }

            // Helper method to call all listeners to execCommand
            var callListeners = function (args, result) {
                if (doc.execCommand.listeners) {
                    doc.execCommand.listeners.forEach(function (listener) {
                        listener({
                            command: args[0],
                            value: args[2],
                            args: args,
                            result: result
                        });
                    });
                }
            },

                // Create a wrapper method for execCommand which will:
                // 1) Call document.execCommand with the correct arguments
                // 2) Loop through any listeners and notify them that execCommand was called
                //    passing extra info on the call
                // 3) Return the result
                wrapper = function () {
                    var result = doc.execCommand.orig.apply(this, arguments);

                    if (!doc.execCommand.listeners) {
                        return result;
                    }

                    var args = Array.prototype.slice.call(arguments);
                    callListeners(args, result);

                    return result;
                };

            // Store a reference to the original execCommand
            wrapper.orig = doc.execCommand;

            // Attach an array for storing listeners
            wrapper.listeners = [];

            // Helper for notifying listeners
            wrapper.callListeners = callListeners;

            // Overwrite execCommand
            doc.execCommand = wrapper;
        },

        // Revert document.execCommand back to its original self
        unwrapExecCommand: function () {
            var doc = this.options.ownerDocument;
            if (!doc.execCommand.orig) {
                return;
            }

            // Use the reference to the original execCommand to revert back
            doc.execCommand = doc.execCommand.orig;
        },

        // Listening to browser events to emit events medium-editor cares about
        setupListener: function (name) {
            if (this.listeners[name]) {
                return;
            }

            switch (name) {
                case 'externalInteraction':
                    // Detecting when user has interacted with elements outside of MediumEditor
                    this.attachDOMEvent(this.options.ownerDocument.body, 'mousedown', this.handleBodyMousedown.bind(this), true);
                    this.attachDOMEvent(this.options.ownerDocument.body, 'click', this.handleBodyClick.bind(this), true);
                    this.attachDOMEvent(this.options.ownerDocument.body, 'focus', this.handleBodyFocus.bind(this), true);
                    break;
                case 'blur':
                    // Detecting when focus is lost
                    this.setupListener('externalInteraction');
                    break;
                case 'focus':
                    // Detecting when focus moves into some part of MediumEditor
                    this.setupListener('externalInteraction');
                    break;
                case 'editableInput':
                    // setup cache for knowing when the content has changed
                    this.contentCache = {};
                    this.base.elements.forEach(function (element) {
                        this.contentCache[element.getAttribute('medium-editor-index')] = element.innerHTML;
                    }, this);

                    // Attach to the 'oninput' event, handled correctly by most browsers
                    if (this.InputEventOnContenteditableSupported) {
                        this.attachToEachElement('input', this.handleInput);
                    }

                    // For browsers which don't support the input event on contenteditable (IE)
                    // we'll attach to 'selectionchange' on the document and 'keypress' on the editables
                    if (!this.InputEventOnContenteditableSupported) {
                        this.setupListener('editableKeypress');
                        this.keypressUpdateInput = true;
                        this.attachDOMEvent(document, 'selectionchange', this.handleDocumentSelectionChange.bind(this));
                        // Listen to calls to execCommand
                        this.attachToExecCommand();
                    }
                    break;
                case 'editableClick':
                    // Detecting click in the contenteditables
                    this.attachToEachElement('click', this.handleClick);
                    break;
                case 'editableBlur':
                    // Detecting blur in the contenteditables
                    this.attachToEachElement('blur', this.handleBlur);
                    break;
                case 'editableKeypress':
                    // Detecting keypress in the contenteditables
                    this.attachToEachElement('keypress', this.handleKeypress);
                    break;
                case 'editableKeyup':
                    // Detecting keyup in the contenteditables
                    this.attachToEachElement('keyup', this.handleKeyup);
                    break;
                case 'editableKeydown':
                    // Detecting keydown on the contenteditables
                    this.attachToEachElement('keydown', this.handleKeydown);
                    break;
                case 'editableKeydownSpace':
                    // Detecting keydown for SPACE on the contenteditables
                    this.setupListener('editableKeydown');
                    break;
                case 'editableKeydownEnter':
                    // Detecting keydown for ENTER on the contenteditables
                    this.setupListener('editableKeydown');
                    break;
                case 'editableKeydownTab':
                    // Detecting keydown for TAB on the contenteditable
                    this.setupListener('editableKeydown');
                    break;
                case 'editableKeydownDelete':
                    // Detecting keydown for DELETE/BACKSPACE on the contenteditables
                    this.setupListener('editableKeydown');
                    break;
                case 'editableMouseover':
                    // Detecting mouseover on the contenteditables
                    this.attachToEachElement('mouseover', this.handleMouseover);
                    break;
                case 'editableDrag':
                    // Detecting dragover and dragleave on the contenteditables
                    this.attachToEachElement('dragover', this.handleDragging);
                    this.attachToEachElement('dragleave', this.handleDragging);
                    break;
                case 'editableDrop':
                    // Detecting drop on the contenteditables
                    this.attachToEachElement('drop', this.handleDrop);
                    break;
                // TODO: We need to have a custom 'paste' event separate from 'editablePaste'
                // Need to think about the way to introduce this without breaking folks
                case 'editablePaste':
                    // Detecting paste on the contenteditables
                    this.attachToEachElement('paste', this.handlePaste);
                    break;
            }
            this.listeners[name] = true;
        },

        attachToEachElement: function (name, handler) {
            // build our internal cache to know which element got already what handler attached
            if (!this.eventsCache) {
                this.eventsCache = [];
            }

            this.base.elements.forEach(function (element) {
                this.attachDOMEvent(element, name, handler.bind(this));
            }, this);

            this.eventsCache.push({ 'name': name, 'handler': handler });
        },

        cleanupElement: function (element) {
            var index = element.getAttribute('medium-editor-index');
            if (index) {
                this.detachAllEventsFromElement(element);
                if (this.contentCache) {
                    delete this.contentCache[index];
                }
            }
        },

        focusElement: function (element) {
            element.focus();
            this.updateFocus(element, { target: element, type: 'focus' });
        },

        updateFocus: function (target, eventObj) {
            var hadFocus = this.base.getFocusedElement(),
                toFocus;

            // For clicks, we need to know if the mousedown that caused the click happened inside the existing focused element
            // or one of the extension elements.  If so, we don't want to focus another element
            if (hadFocus &&
                eventObj.type === 'click' &&
                this.lastMousedownTarget &&
                (MediumEditor.util.isDescendant(hadFocus, this.lastMousedownTarget, true) ||
                    isElementDescendantOfExtension(this.base.extensions, this.lastMousedownTarget))) {
                toFocus = hadFocus;
            }

            if (!toFocus) {
                this.base.elements.some(function (element) {
                    // If the target is part of an editor element, this is the element getting focus
                    if (!toFocus && (MediumEditor.util.isDescendant(element, target, true))) {
                        toFocus = element;
                    }

                    // bail if we found an element that's getting focus
                    return !!toFocus;
                }, this);
            }

            // Check if the target is external (not part of the editor, toolbar, or any other extension)
            var externalEvent = !MediumEditor.util.isDescendant(hadFocus, target, true) &&
                !isElementDescendantOfExtension(this.base.extensions, target);

            if (toFocus !== hadFocus) {
                // If element has focus, and focus is going outside of editor
                // Don't blur focused element if clicking on editor, toolbar, or anchorpreview
                if (hadFocus && externalEvent) {
                    // Trigger blur on the editable that has lost focus
                    hadFocus.removeAttribute('data-medium-focused');
                    this.triggerCustomEvent('blur', eventObj, hadFocus);
                }

                // If focus is going into an editor element
                if (toFocus) {
                    // Trigger focus on the editable that now has focus
                    toFocus.setAttribute('data-medium-focused', true);
                    this.triggerCustomEvent('focus', eventObj, toFocus);
                }
            }

            if (externalEvent) {
                this.triggerCustomEvent('externalInteraction', eventObj);
            }
        },

        updateInput: function (target, eventObj) {
            if (!this.contentCache) {
                return;
            }
            // An event triggered which signifies that the user may have changed someting
            // Look in our cache of input for the contenteditables to see if something changed
            var index = target.getAttribute('medium-editor-index'),
                html = target.innerHTML;

            if (html !== this.contentCache[index]) {
                // The content has changed since the last time we checked, fire the event
                this.triggerCustomEvent('editableInput', eventObj, target);
            }
            this.contentCache[index] = html;
        },

        handleDocumentSelectionChange: function (event) {
            // When selectionchange fires, target and current target are set
            // to document, since this is where the event is handled
            // However, currentTarget will have an 'activeElement' property
            // which will point to whatever element has focus.
            if (event.currentTarget && event.currentTarget.activeElement) {
                var activeElement = event.currentTarget.activeElement,
                    currentTarget;
                // We can look at the 'activeElement' to determine if the selectionchange has
                // happened within a contenteditable owned by this instance of MediumEditor
                this.base.elements.some(function (element) {
                    if (MediumEditor.util.isDescendant(element, activeElement, true)) {
                        currentTarget = element;
                        return true;
                    }
                    return false;
                }, this);

                // We know selectionchange fired within one of our contenteditables
                if (currentTarget) {
                    this.updateInput(currentTarget, { target: activeElement, currentTarget: currentTarget });
                }
            }
        },

        handleDocumentExecCommand: function () {
            // document.execCommand has been called
            // If one of our contenteditables currently has focus, we should
            // attempt to trigger the 'editableInput' event
            var target = this.base.getFocusedElement();
            if (target) {
                this.updateInput(target, { target: target, currentTarget: target });
            }
        },

        handleBodyClick: function (event) {
            this.updateFocus(event.target, event);
        },

        handleBodyFocus: function (event) {
            this.updateFocus(event.target, event);
        },

        handleBodyMousedown: function (event) {
            this.lastMousedownTarget = event.target;
        },

        handleInput: function (event) {
            this.updateInput(event.currentTarget, event);
        },

        handleClick: function (event) {
            this.triggerCustomEvent('editableClick', event, event.currentTarget);
        },

        handleBlur: function (event) {
            this.triggerCustomEvent('editableBlur', event, event.currentTarget);
        },

        handleKeypress: function (event) {
            this.triggerCustomEvent('editableKeypress', event, event.currentTarget);

            // If we're doing manual detection of the editableInput event we need
            // to check for input changes during 'keypress'
            if (this.keypressUpdateInput) {
                var eventObj = { target: event.target, currentTarget: event.currentTarget };

                // In IE, we need to let the rest of the event stack complete before we detect
                // changes to input, so using setTimeout here
                setTimeout(function () {
                    this.updateInput(eventObj.currentTarget, eventObj);
                }.bind(this), 0);
            }
        },

        handleKeyup: function (event) {
            this.triggerCustomEvent('editableKeyup', event, event.currentTarget);
        },

        handleMouseover: function (event) {
            this.triggerCustomEvent('editableMouseover', event, event.currentTarget);
        },

        handleDragging: function (event) {
            this.triggerCustomEvent('editableDrag', event, event.currentTarget);
        },

        handleDrop: function (event) {
            this.triggerCustomEvent('editableDrop', event, event.currentTarget);
        },

        handlePaste: function (event) {
            this.triggerCustomEvent('editablePaste', event, event.currentTarget);
        },

        handleKeydown: function (event) {

            this.triggerCustomEvent('editableKeydown', event, event.currentTarget);

            if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.SPACE)) {
                return this.triggerCustomEvent('editableKeydownSpace', event, event.currentTarget);
            }

            if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.ENTER) || (event.ctrlKey && MediumEditor.util.isKey(event, MediumEditor.util.keyCode.M))) {
                return this.triggerCustomEvent('editableKeydownEnter', event, event.currentTarget);
            }

            if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.TAB)) {
                return this.triggerCustomEvent('editableKeydownTab', event, event.currentTarget);
            }

            if (MediumEditor.util.isKey(event, [MediumEditor.util.keyCode.DELETE, MediumEditor.util.keyCode.BACKSPACE])) {
                return this.triggerCustomEvent('editableKeydownDelete', event, event.currentTarget);
            }
        }
    };

    MediumEditor.Events = Events;
}());

(function () {
    'use strict';

    var Button = MediumEditor.Extension.extend({

        /* Button Options */

        /* action: [string]
         * The action argument to pass to MediumEditor.execAction()
         * when the button is clicked
         */
        action: undefined,

        /* aria: [string]
         * The value to add as the aria-label attribute of the button
         * element displayed in the toolbar.
         * This is also used as the tooltip for the button
         */
        aria: undefined,

        /* tagNames: [Array]
         * NOTE: This is not used if useQueryState is set to true.
         *
         * Array of element tag names that would indicate that this
         * button has already been applied. If this action has already
         * been applied, the button will be displayed as 'active' in the toolbar
         *
         * Example:
         * For 'bold', if the text is ever within a <b> or <strong>
         * tag that indicates the text is already bold. So the array
         * of tagNames for bold would be: ['b', 'strong']
         */
        tagNames: undefined,

        /* style: [Object]
         * NOTE: This is not used if useQueryState is set to true.
         *
         * A pair of css property & value(s) that indicate that this
         * button has already been applied. If this action has already
         * been applied, the button will be displayed as 'active' in the toolbar
         * Properties of the object:
         *   prop [String]: name of the css property
         *   value [String]: value(s) of the css property
         *                   multiple values can be separated by a '|'
         *
         * Example:
         * For 'bold', if the text is ever within an element with a 'font-weight'
         * style property set to '700' or 'bold', that indicates the text
         * is already bold.  So the style object for bold would be:
         * { prop: 'font-weight', value: '700|bold' }
         */
        style: undefined,

        /* useQueryState: [boolean]
         * Enables/disables whether this button should use the built-in
         * document.queryCommandState() method to determine whether
         * the action has already been applied.  If the action has already
         * been applied, the button will be displayed as 'active' in the toolbar
         *
         * Example:
         * For 'bold', if this is set to true, the code will call:
         * document.queryCommandState('bold') which will return true if the
         * browser thinks the text is already bold, and false otherwise
         */
        useQueryState: undefined,

        /* contentDefault: [string]
         * Default innerHTML to put inside the button
         */
        contentDefault: undefined,

        /* contentFA: [string]
         * The innerHTML to use for the content of the button
         * if the `buttonLabels` option for MediumEditor is set to 'fontawesome'
         */
        contentFA: undefined,

        /* classList: [Array]
         * An array of classNames (strings) to be added to the button
         */
        classList: undefined,

        /* attrs: [object]
         * A set of key-value pairs to add to the button as custom attributes
         */
        attrs: undefined,

        // The button constructor can optionally accept the name of a built-in button
        // (ie 'bold', 'italic', etc.)
        // When the name of a button is passed, it will initialize itself with the
        // configuration for that button
        constructor: function (options) {
            if (Button.isBuiltInButton(options)) {
                MediumEditor.Extension.call(this, this.defaults[options]);
            } else {
                MediumEditor.Extension.call(this, options);
            }
        },

        init: function () {
            MediumEditor.Extension.prototype.init.apply(this, arguments);

            this.button = this.createButton();
            this.on(this.button, 'click', this.handleClick.bind(this));
        },

        /* getButton: [function ()]
         *
         * If implemented, this function will be called when
         * the toolbar is being created.  The DOM Element returned
         * by this function will be appended to the toolbar along
         * with any other buttons.
         */
        getButton: function () {
            return this.button;
        },

        getAction: function () {
            return (typeof this.action === 'function') ? this.action(this.base.options) : this.action;
        },

        getAria: function () {
            return (typeof this.aria === 'function') ? this.aria(this.base.options) : this.aria;
        },

        getTagNames: function () {
            return (typeof this.tagNames === 'function') ? this.tagNames(this.base.options) : this.tagNames;
        },

        createButton: function () {
            var button = this.document.createElement('button'),
                content = this.contentDefault,
                ariaLabel = this.getAria(),
                buttonLabels = this.getEditorOption('buttonLabels');
            // Add class names
            button.classList.add('medium-editor-action');
            button.classList.add('medium-editor-action-' + this.name);
            if (this.classList) {
                this.classList.forEach(function (className) {
                    button.classList.add(className);
                });
            }

            // Add attributes
            button.setAttribute('data-action', this.getAction());
            if (ariaLabel) {
                button.setAttribute('title', ariaLabel);
                button.setAttribute('aria-label', ariaLabel);
            }
            if (this.attrs) {
                Object.keys(this.attrs).forEach(function (attr) {
                    button.setAttribute(attr, this.attrs[attr]);
                }, this);
            }

            if (buttonLabels === 'fontawesome' && this.contentFA) {
                content = this.contentFA;
            }
            button.innerHTML = content;
            return button;
        },

        handleClick: function (event) {
            event.preventDefault();
            event.stopPropagation();

            var action = this.getAction();

            if (action) {
                this.execAction(action);
            }
        },

        isActive: function () {
            return this.button.classList.contains(this.getEditorOption('activeButtonClass'));
        },

        setInactive: function () {
            this.button.classList.remove(this.getEditorOption('activeButtonClass'));
            delete this.knownState;
        },

        setActive: function () {
            this.button.classList.add(this.getEditorOption('activeButtonClass'));
            delete this.knownState;
        },

        queryCommandState: function () {
            var queryState = null;
            if (this.useQueryState) {
                queryState = this.base.queryCommandState(this.getAction());
            }
            return queryState;
        },

        isAlreadyApplied: function (node) {
            var isMatch = false,
                tagNames = this.getTagNames(),
                styleVals,
                computedStyle;

            if (this.knownState === false || this.knownState === true) {
                return this.knownState;
            }

            if (tagNames && tagNames.length > 0) {
                isMatch = tagNames.indexOf(node.nodeName.toLowerCase()) !== -1;
            }

            if (!isMatch && this.style) {
                styleVals = this.style.value.split('|');
                computedStyle = this.window.getComputedStyle(node, null).getPropertyValue(this.style.prop);
                styleVals.forEach(function (val) {
                    if (!this.knownState) {
                        isMatch = (computedStyle.indexOf(val) !== -1);
                        // text-decoration is not inherited by default
                        // so if the computed style for text-decoration doesn't match
                        // don't write to knownState so we can fallback to other checks
                        if (isMatch || this.style.prop !== 'text-decoration') {
                            this.knownState = isMatch;
                        }
                    }
                }, this);
            }

            return isMatch;
        }
    });

    Button.isBuiltInButton = function (name) {
        return (typeof name === 'string') && MediumEditor.extensions.button.prototype.defaults.hasOwnProperty(name);
    };

    MediumEditor.extensions.button = Button;
}());

(function () {
    'use strict';

    /* MediumEditor.extensions.button.defaults: [Object]
     * Set of default config options for all of the built-in MediumEditor buttons
     */
    MediumEditor.extensions.button.prototype.defaults = {
        'bold': {
            name: 'bold',
            action: 'bold',
            aria: 'bold',
            tagNames: ['b', 'strong'],
            style: {
                prop: 'font-weight',
                value: '700|bold'
            },
            useQueryState: true,
            contentDefault: '<b>B</b>',
            contentFA: '<i class="fa fa-bold"></i>'
        },
        'italic': {
            name: 'italic',
            action: 'italic',
            aria: 'italic',
            tagNames: ['i', 'em'],
            style: {
                prop: 'font-style',
                value: 'italic'
            },
            useQueryState: true,
            contentDefault: '<b><i>I</i></b>',
            contentFA: '<i class="fa fa-italic"></i>'
        },
        'underline': {
            name: 'underline',
            action: 'underline',
            aria: 'underline',
            tagNames: ['u'],
            style: {
                prop: 'text-decoration',
                value: 'underline'
            },
            useQueryState: true,
            contentDefault: '<b><u>U</u></b>',
            contentFA: '<i class="fa fa-underline"></i>'
        },
        'strikethrough': {
            name: 'strikethrough',
            action: 'strikethrough',
            aria: 'strike through',
            tagNames: ['strike'],
            style: {
                prop: 'text-decoration',
                value: 'line-through'
            },
            useQueryState: true,
            contentDefault: '<s>A</s>',
            contentFA: '<i class="fa fa-strikethrough"></i>'
        },
        'superscript': {
            name: 'superscript',
            action: 'superscript',
            aria: 'superscript',
            tagNames: ['sup'],
            /* firefox doesn't behave the way we want it to, so we CAN'T use queryCommandState for superscript
               https://github.com/guardian/scribe/blob/master/BROWSERINCONSISTENCIES.md#documentquerycommandstate */
            // useQueryState: true
            contentDefault: '<b>x<sup>1</sup></b>',
            contentFA: '<i class="fa fa-superscript"></i>'
        },
        'subscript': {
            name: 'subscript',
            action: 'subscript',
            aria: 'subscript',
            tagNames: ['sub'],
            /* firefox doesn't behave the way we want it to, so we CAN'T use queryCommandState for subscript
               https://github.com/guardian/scribe/blob/master/BROWSERINCONSISTENCIES.md#documentquerycommandstate */
            // useQueryState: true
            contentDefault: '<b>x<sub>1</sub></b>',
            contentFA: '<i class="fa fa-subscript"></i>'
        },
        'image': {
            name: 'image',
            action: 'image',
            aria: 'image',
            tagNames: ['img'],
            contentDefault: '<b>image</b>',
            contentFA: '<i class="fa fa-picture-o"></i>'
        },
        'html': {
            name: 'html',
            action: 'html',
            aria: 'evaluate html',
            tagNames: ['iframe', 'object'],
            contentDefault: '<b>html</b>',
            contentFA: '<i class="fa fa-code"></i>'
        },
        'orderedlist': {
            name: 'orderedlist',
            action: 'insertorderedlist',
            aria: 'ordered list',
            tagNames: ['ol'],
            useQueryState: true,
            contentDefault: '<b>1.</b>',
            contentFA: '<i class="fa fa-list-ol"></i>'
        },
        'unorderedlist': {
            name: 'unorderedlist',
            action: 'insertunorderedlist',
            aria: 'unordered list',
            tagNames: ['ul'],
            useQueryState: true,
            contentDefault: '<b>&bull;</b>',
            contentFA: '<i class="fa fa-list-ul"></i>'
        },
        'indent': {
            name: 'indent',
            action: 'indent',
            aria: 'indent',
            tagNames: [],
            contentDefault: '<b>&rarr;</b>',
            contentFA: '<i class="fa fa-indent"></i>'
        },
        'outdent': {
            name: 'outdent',
            action: 'outdent',
            aria: 'outdent',
            tagNames: [],
            contentDefault: '<b>&larr;</b>',
            contentFA: '<i class="fa fa-outdent"></i>'
        },
        'justifyCenter': {
            name: 'justifyCenter',
            action: 'justifyCenter',
            aria: 'center justify',
            tagNames: [],
            style: {
                prop: 'text-align',
                value: 'center'
            },
            contentDefault: '<b>C</b>',
            contentFA: '<i class="fa fa-align-center"></i>'
        },
        'justifyFull': {
            name: 'justifyFull',
            action: 'justifyFull',
            aria: 'full justify',
            tagNames: [],
            style: {
                prop: 'text-align',
                value: 'justify'
            },
            contentDefault: '<b>J</b>',
            contentFA: '<i class="fa fa-align-justify"></i>'
        },
        'justifyLeft': {
            name: 'justifyLeft',
            action: 'justifyLeft',
            aria: 'left justify',
            tagNames: [],
            style: {
                prop: 'text-align',
                value: 'left'
            },
            contentDefault: '<b>L</b>',
            contentFA: '<i class="fa fa-align-left"></i>'
        },
        'justifyRight': {
            name: 'justifyRight',
            action: 'justifyRight',
            aria: 'right justify',
            tagNames: [],
            style: {
                prop: 'text-align',
                value: 'right'
            },
            contentDefault: '<b>R</b>',
            contentFA: '<i class="fa fa-align-right"></i>'
        },
        // Known inline elements that are not removed, or not removed consistantly across browsers:
        // <span>, <label>, <br>
        'removeFormat': {
            name: 'removeFormat',
            aria: 'remove formatting',
            action: 'removeFormat',
            contentDefault: '<b>X</b>',
            contentFA: '<i class="fa fa-eraser"></i>'
        },

        /***** Buttons for appending block elements (append-<element> action) *****/

        'quote': {
            name: 'quote',
            action: 'append-blockquote',
            aria: 'blockquote',
            tagNames: ['blockquote'],
            contentDefault: '<b>&ldquo;</b>',
            contentFA: '<i class="fa fa-quote-right"></i>'
        },
        'pre': {
            name: 'pre',
            action: 'append-pre',
            aria: 'preformatted text',
            tagNames: ['pre'],
            contentDefault: '<b>0101</b>',
            contentFA: '<i class="fa fa-code fa-lg"></i>'
        },
        'h1': {
            name: 'h1',
            action: 'append-h1',
            aria: 'header type one',
            tagNames: ['h1'],
            contentDefault: '<b>H1</b>',
            contentFA: '<i class="fa fa-header"><sup>1</sup>'
        },
        'h2': {
            name: 'h2',
            action: 'append-h2',
            aria: 'header type two',
            tagNames: ['h2'],
            contentDefault: '<b>H2</b>',
            contentFA: '<i class="fa fa-header"><sup>2</sup>'
        },
        'h3': {
            name: 'h3',
            action: 'append-h3',
            aria: 'header type three',
            tagNames: ['h3'],
            contentDefault: '<b>H3</b>',
            contentFA: '<i class="fa fa-header"><sup>3</sup>'
        },
        'h4': {
            name: 'h4',
            action: 'append-h4',
            aria: 'header type four',
            tagNames: ['h4'],
            contentDefault: '<b>H4</b>',
            contentFA: '<i class="fa fa-header"><sup>4</sup>'
        },
        'h5': {
            name: 'h5',
            action: 'append-h5',
            aria: 'header type five',
            tagNames: ['h5'],
            contentDefault: '<b>H5</b>',
            contentFA: '<i class="fa fa-header"><sup>5</sup>'
        },
        'h6': {
            name: 'h6',
            action: 'append-h6',
            aria: 'header type six',
            tagNames: ['h6'],
            contentDefault: '<b>H6</b>',
            contentFA: '<i class="fa fa-header"><sup>6</sup>'
        }
    };

})();

(function () {
    'use strict';

    /* Base functionality for an extension which will display
     * a 'form' inside the toolbar
     */
    var FormExtension = MediumEditor.extensions.button.extend({

        init: function () {
            MediumEditor.extensions.button.prototype.init.apply(this, arguments);
        },

        // default labels for the form buttons
        formSaveLabel: '&#10003;',
        formCloseLabel: '&times;',

        /* activeClass: [string]
         * set class which added to shown form
         */
        activeClass: 'medium-editor-toolbar-form-active',

        /* hasForm: [boolean]
         *
         * Setting this to true will cause getForm() to be called
         * when the toolbar is created, so the form can be appended
         * inside the toolbar container
         */
        hasForm: true,

        /* getForm: [function ()]
         *
         * When hasForm is true, this function must be implemented
         * and return a DOM Element which will be appended to
         * the toolbar container. The form should start hidden, and
         * the extension can choose when to hide/show it
         */
        getForm: function () {},

        /* isDisplayed: [function ()]
         *
         * This function should return true/false reflecting
         * whether the form is currently displayed
         */
        isDisplayed: function () {
            if (this.hasForm) {
                return this.getForm().classList.contains(this.activeClass);
            }
            return false;
        },

        /* hideForm: [function ()]
         *
         * This function should show the form element inside
         * the toolbar container
         */
        showForm: function () {
            if (this.hasForm) {
                this.getForm().classList.add(this.activeClass);
            }
        },

        /* hideForm: [function ()]
         *
         * This function should hide the form element inside
         * the toolbar container
         */
        hideForm: function () {
            if (this.hasForm) {
                this.getForm().classList.remove(this.activeClass);
            }
        },

        /************************ Helpers ************************
         * The following are helpers that are either set by MediumEditor
         * during initialization, or are helper methods which either
         * route calls to the MediumEditor instance or provide common
         * functionality for all form extensions
         *********************************************************/

        /* showToolbarDefaultActions: [function ()]
         *
         * Helper method which will turn back the toolbar after canceling
         * the customized form
         */
        showToolbarDefaultActions: function () {
            var toolbar = this.base.getExtensionByName('toolbar');
            if (toolbar) {
                toolbar.showToolbarDefaultActions();
            }
        },

        /* hideToolbarDefaultActions: [function ()]
         *
         * Helper function which will hide the default contents of the
         * toolbar, but leave the toolbar container in the same state
         * to allow a form to display its custom contents inside the toolbar
         */
        hideToolbarDefaultActions: function () {
            var toolbar = this.base.getExtensionByName('toolbar');
            if (toolbar) {
                toolbar.hideToolbarDefaultActions();
            }
        },

        /* setToolbarPosition: [function ()]
         *
         * Helper function which will update the size and position
         * of the toolbar based on the toolbar content and the current
         * position of the user's selection
         */
        setToolbarPosition: function () {
            var toolbar = this.base.getExtensionByName('toolbar');
            if (toolbar) {
                toolbar.setToolbarPosition();
            }
        }
    });

    MediumEditor.extensions.form = FormExtension;
})();
(function () {
    'use strict';

    var AnchorForm = MediumEditor.extensions.form.extend({
        /* Anchor Form Options */

        /* customClassOption: [string]  (previously options.anchorButton + options.anchorButtonClass)
         * Custom class name the user can optionally have added to their created links (ie 'button').
         * If passed as a non-empty string, a checkbox will be displayed allowing the user to choose
         * whether to have the class added to the created link or not.
         */
        customClassOption: null,

        /* customClassOptionText: [string]
         * text to be shown in the checkbox when the __customClassOption__ is being used.
         */
        customClassOptionText: 'Button',

        /* linkValidation: [boolean]  (previously options.checkLinkFormat)
         * enables/disables check for common URL protocols on anchor links.
         */
        linkValidation: false,

        /* placeholderText: [string]  (previously options.anchorInputPlaceholder)
         * text to be shown as placeholder of the anchor input.
         */
        placeholderText: 'Paste or type a link',

        /* targetCheckbox: [boolean]  (previously options.anchorTarget)
         * enables/disables displaying a "Open in new window" checkbox, which when checked
         * changes the `target` attribute of the created link.
         */
        targetCheckbox: false,

        /* targetCheckboxText: [string]  (previously options.anchorInputCheckboxLabel)
         * text to be shown in the checkbox enabled via the __targetCheckbox__ option.
         */
        targetCheckboxText: 'Open in new window',

        // Options for the Button base class
        name: 'anchor',
        action: 'createLink',
        aria: 'link',
        tagNames: ['a'],
        contentDefault: '<b>#</b>',
        contentFA: '<i class="fa fa-link"></i>',

        init: function () {
            MediumEditor.extensions.form.prototype.init.apply(this, arguments);

            this.subscribe('editableKeydown', this.handleKeydown.bind(this));
        },

        // Called when the button the toolbar is clicked
        // Overrides ButtonExtension.handleClick
        handleClick: function (event) {
            event.preventDefault();
            event.stopPropagation();

            var range = MediumEditor.selection.getSelectionRange(this.document);

            if (range.startContainer.nodeName.toLowerCase() === 'a' ||
                range.endContainer.nodeName.toLowerCase() === 'a' ||
                MediumEditor.util.getClosestTag(MediumEditor.selection.getSelectedParentElement(range), 'a')) {
                return this.execAction('unlink');
            }

            if (!this.isDisplayed()) {
                this.showForm();
            }

            return false;
        },

        // Called when user hits the defined shortcut (CTRL / COMMAND + K)
        handleKeydown: function (event) {
            if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.K) && MediumEditor.util.isMetaCtrlKey(event) && !event.shiftKey) {
                this.handleClick(event);
            }
        },

        // Called by medium-editor to append form to the toolbar
        getForm: function () {
            if (!this.form) {
                this.form = this.createForm();
            }
            return this.form;
        },

        getTemplate: function () {
            var template = [
                '<input type="text" class="medium-editor-toolbar-input" placeholder="', this.placeholderText, '">'
            ];

            template.push(
                '<a href="#" class="medium-editor-toolbar-save">',
                this.getEditorOption('buttonLabels') === 'fontawesome' ? '<i class="fa fa-check"></i>' : this.formSaveLabel,
                '</a>'
            );

            template.push('<a href="#" class="medium-editor-toolbar-close">',
                this.getEditorOption('buttonLabels') === 'fontawesome' ? '<i class="fa fa-times"></i>' : this.formCloseLabel,
                '</a>');

            // both of these options are slightly moot with the ability to
            // override the various form buildup/serialize functions.

            if (this.targetCheckbox) {
                // fixme: ideally, this targetCheckboxText would be a formLabel too,
                // figure out how to deprecate? also consider `fa-` icon default implcations.
                template.push(
                    '<div class="medium-editor-toolbar-form-row">',
                    '<input type="checkbox" class="medium-editor-toolbar-anchor-target">',
                    '<label>',
                    this.targetCheckboxText,
                    '</label>',
                    '</div>'
                );
            }

            if (this.customClassOption) {
                // fixme: expose this `Button` text as a formLabel property, too
                // and provide similar access to a `fa-` icon default.
                template.push(
                    '<div class="medium-editor-toolbar-form-row">',
                    '<input type="checkbox" class="medium-editor-toolbar-anchor-button">',
                    '<label>',
                    this.customClassOptionText,
                    '</label>',
                    '</div>'
                );
            }

            return template.join('');

        },

        // Used by medium-editor when the default toolbar is to be displayed
        isDisplayed: function () {
            return MediumEditor.extensions.form.prototype.isDisplayed.apply(this);
        },

        hideForm: function () {
            MediumEditor.extensions.form.prototype.hideForm.apply(this);
            this.getInput().value = '';
        },

        showForm: function (opts) {
            var input = this.getInput(),
                targetCheckbox = this.getAnchorTargetCheckbox(),
                buttonCheckbox = this.getAnchorButtonCheckbox();

            opts = opts || { value: '' };
            // TODO: This is for backwards compatability
            // We don't need to support the 'string' argument in 6.0.0
            if (typeof opts === 'string') {
                opts = {
                    value: opts
                };
            }

            this.base.saveSelection();
            this.hideToolbarDefaultActions();
            MediumEditor.extensions.form.prototype.showForm.apply(this);
            this.setToolbarPosition();

            input.value = opts.value;
            input.focus();

            // If we have a target checkbox, we want it to be checked/unchecked
            // based on whether the existing link has target=_blank
            if (targetCheckbox) {
                targetCheckbox.checked = opts.target === '_blank';
            }

            // If we have a custom class checkbox, we want it to be checked/unchecked
            // based on whether an existing link already has the class
            if (buttonCheckbox) {
                var classList = opts.buttonClass ? opts.buttonClass.split(' ') : [];
                buttonCheckbox.checked = (classList.indexOf(this.customClassOption) !== -1);
            }
        },

        // Called by core when tearing down medium-editor (destroy)
        destroy: function () {
            if (!this.form) {
                return false;
            }

            if (this.form.parentNode) {
                this.form.parentNode.removeChild(this.form);
            }

            delete this.form;
        },

        // core methods

        getFormOpts: function () {
            // no notion of private functions? wanted `_getFormOpts`
            var targetCheckbox = this.getAnchorTargetCheckbox(),
                buttonCheckbox = this.getAnchorButtonCheckbox(),
                opts = {
                    value: this.getInput().value.trim()
                };

            if (this.linkValidation) {
                opts.value = this.checkLinkFormat(opts.value);
            }

            opts.target = '_self';
            if (targetCheckbox && targetCheckbox.checked) {
                opts.target = '_blank';
            }

            if (buttonCheckbox && buttonCheckbox.checked) {
                opts.buttonClass = this.customClassOption;
            }

            return opts;
        },

        doFormSave: function () {
            var opts = this.getFormOpts();
            this.completeFormSave(opts);
        },

        completeFormSave: function (opts) {
            this.base.restoreSelection();
            this.execAction(this.action, opts);
            this.base.checkSelection();
        },

        ensureEncodedUri: function (str) {
            return str === decodeURI(str) ? encodeURI(str) : str;
        },

        ensureEncodedUriComponent: function (str) {
            return str === decodeURIComponent(str) ? encodeURIComponent(str) : str;
        },

        ensureEncodedParam: function (param) {
            var split = param.split('='),
                key = split[0],
                val = split[1];

            return key + (val === undefined ? '' : '=' + this.ensureEncodedUriComponent(val));
        },

        ensureEncodedQuery: function (queryString) {
            return queryString.split('&').map(this.ensureEncodedParam.bind(this)).join('&');
        },

        checkLinkFormat: function (value) {
            // Matches any alphabetical characters followed by ://
            // Matches protocol relative "//"
            // Matches common external protocols "mailto:" "tel:" "maps:"
            // Matches relative hash link, begins with "#"
            var urlSchemeRegex = /^([a-z]+:)?\/\/|^(mailto|tel|maps):|^\#/i,
                hasScheme = urlSchemeRegex.test(value),
                scheme = '',
                // telRegex is a regex for checking if the string is a telephone number
                telRegex = /^\+?\s?\(?(?:\d\s?\-?\)?){3,20}$/,
                urlParts = value.match(/^(.*?)(?:\?(.*?))?(?:#(.*))?$/),
                path = urlParts[1],
                query = urlParts[2],
                fragment = urlParts[3];

            if (telRegex.test(value)) {
                return 'tel:' + value;
            }

            if (!hasScheme) {
                var host = path.split('/')[0];
                // if the host part of the path looks like a hostname
                if (host.match(/.+(\.|:).+/) || host === 'localhost') {
                    scheme = 'http://';
                }
            }

            return scheme +
                // Ensure path is encoded
                this.ensureEncodedUri(path) +
                // Ensure query is encoded
                (query === undefined ? '' : '?' + this.ensureEncodedQuery(query)) +
                // Include fragment unencoded as encodeUriComponent is too
                // heavy handed for the many characters allowed in a fragment
                (fragment === undefined ? '' : '#' + fragment);
        },

        doFormCancel: function () {
            this.base.restoreSelection();
            this.base.checkSelection();
        },

        // form creation and event handling
        attachFormEvents: function (form) {
            var close = form.querySelector('.medium-editor-toolbar-close'),
                save = form.querySelector('.medium-editor-toolbar-save'),
                input = form.querySelector('.medium-editor-toolbar-input');

            // Handle clicks on the form itself
            this.on(form, 'click', this.handleFormClick.bind(this));

            // Handle typing in the textbox
            this.on(input, 'keyup', this.handleTextboxKeyup.bind(this));

            // Handle close button clicks
            this.on(close, 'click', this.handleCloseClick.bind(this));

            // Handle save button clicks (capture)
            this.on(save, 'click', this.handleSaveClick.bind(this), true);

        },

        createForm: function () {
            var doc = this.document,
                form = doc.createElement('div');

            // Anchor Form (div)
            form.className = 'medium-editor-toolbar-form';
            form.id = 'medium-editor-toolbar-form-anchor-' + this.getEditorId();
            form.innerHTML = this.getTemplate();
            this.attachFormEvents(form);

            return form;
        },

        getInput: function () {
            return this.getForm().querySelector('input.medium-editor-toolbar-input');
        },

        getAnchorTargetCheckbox: function () {
            return this.getForm().querySelector('.medium-editor-toolbar-anchor-target');
        },

        getAnchorButtonCheckbox: function () {
            return this.getForm().querySelector('.medium-editor-toolbar-anchor-button');
        },

        handleTextboxKeyup: function (event) {
            // For ENTER -> create the anchor
            if (event.keyCode === MediumEditor.util.keyCode.ENTER) {
                event.preventDefault();
                this.doFormSave();
                return;
            }

            // For ESCAPE -> close the form
            if (event.keyCode === MediumEditor.util.keyCode.ESCAPE) {
                event.preventDefault();
                this.doFormCancel();
            }
        },

        handleFormClick: function (event) {
            // make sure not to hide form when clicking inside the form
            event.stopPropagation();
        },

        handleSaveClick: function (event) {
            // Clicking Save -> create the anchor
            event.preventDefault();
            this.doFormSave();
        },

        handleCloseClick: function (event) {
            // Click Close -> close the form
            event.preventDefault();
            this.doFormCancel();
        }
    });

    MediumEditor.extensions.anchor = AnchorForm;
}());

(function () {
    'use strict';

    var AnchorPreview = MediumEditor.Extension.extend({
        name: 'anchor-preview',

        // Anchor Preview Options

        /* hideDelay: [number]  (previously options.anchorPreviewHideDelay)
         * time in milliseconds to show the anchor tag preview after the mouse has left the anchor tag.
         */
        hideDelay: 500,

        /* previewValueSelector: [string]
         * the default selector to locate where to put the activeAnchor value in the preview
         */
        previewValueSelector: 'a',

        /* showWhenToolbarIsVisible: [boolean]
         * determines whether the anchor tag preview shows up when the toolbar is visible
         */
        showWhenToolbarIsVisible: false,

        /* showOnEmptyLinks: [boolean]
        * determines whether the anchor tag preview shows up on links with href="" or href="#something"
        */
        showOnEmptyLinks: true,

        init: function () {
            this.anchorPreview = this.createPreview();

            this.getEditorOption('elementsContainer').appendChild(this.anchorPreview);

            this.attachToEditables();
        },

        getInteractionElements: function () {
            return this.getPreviewElement();
        },

        // TODO: Remove this function in 6.0.0
        getPreviewElement: function () {
            return this.anchorPreview;
        },

        createPreview: function () {
            var el = this.document.createElement('div');

            el.id = 'medium-editor-anchor-preview-' + this.getEditorId();
            el.className = 'medium-editor-anchor-preview';
            el.innerHTML = this.getTemplate();

            this.on(el, 'click', this.handleClick.bind(this));

            return el;
        },

        getTemplate: function () {
            return '<div class="medium-editor-toolbar-anchor-preview" id="medium-editor-toolbar-anchor-preview">' +
                '    <a class="medium-editor-toolbar-anchor-preview-inner"></a>' +
                '</div>';
        },

        destroy: function () {
            if (this.anchorPreview) {
                if (this.anchorPreview.parentNode) {
                    this.anchorPreview.parentNode.removeChild(this.anchorPreview);
                }
                delete this.anchorPreview;
            }
        },

        hidePreview: function () {
            if (this.anchorPreview) {
                this.anchorPreview.classList.remove('medium-editor-anchor-preview-active');
            }
            this.activeAnchor = null;
        },

        showPreview: function (anchorEl) {
            if (this.anchorPreview.classList.contains('medium-editor-anchor-preview-active') ||
                    anchorEl.getAttribute('data-disable-preview')) {
                return true;
            }

            if (this.previewValueSelector) {
                this.anchorPreview.querySelector(this.previewValueSelector).textContent = anchorEl.attributes.href.value;
                this.anchorPreview.querySelector(this.previewValueSelector).href = anchorEl.attributes.href.value;
            }

            this.anchorPreview.classList.add('medium-toolbar-arrow-over');
            this.anchorPreview.classList.remove('medium-toolbar-arrow-under');

            if (!this.anchorPreview.classList.contains('medium-editor-anchor-preview-active')) {
                this.anchorPreview.classList.add('medium-editor-anchor-preview-active');
            }

            this.activeAnchor = anchorEl;

            this.positionPreview();
            this.attachPreviewHandlers();

            return this;
        },

        positionPreview: function (activeAnchor) {
            activeAnchor = activeAnchor || this.activeAnchor;
            var containerWidth = this.window.innerWidth,
                buttonHeight = this.anchorPreview.offsetHeight,
                boundary = activeAnchor.getBoundingClientRect(),
                diffLeft = this.diffLeft,
                diffTop = this.diffTop,
                elementsContainer = this.getEditorOption('elementsContainer'),
                elementsContainerAbsolute = ['absolute', 'fixed'].indexOf(window.getComputedStyle(elementsContainer).getPropertyValue('position')) > -1,
                relativeBoundary = {},
                halfOffsetWidth, defaultLeft, middleBoundary, elementsContainerBoundary, top;

            halfOffsetWidth = this.anchorPreview.offsetWidth / 2;
            var toolbarExtension = this.base.getExtensionByName('toolbar');
            if (toolbarExtension) {
                diffLeft = toolbarExtension.diffLeft;
                diffTop = toolbarExtension.diffTop;
            }
            defaultLeft = diffLeft - halfOffsetWidth;

            // If container element is absolute / fixed, recalculate boundaries to be relative to the container
            if (elementsContainerAbsolute) {
                elementsContainerBoundary = elementsContainer.getBoundingClientRect();
                ['top', 'left'].forEach(function (key) {
                    relativeBoundary[key] = boundary[key] - elementsContainerBoundary[key];
                });

                relativeBoundary.width = boundary.width;
                relativeBoundary.height = boundary.height;
                boundary = relativeBoundary;

                containerWidth = elementsContainerBoundary.width;

                // Adjust top position according to container scroll position
                top = elementsContainer.scrollTop;
            } else {
                // Adjust top position according to window scroll position
                top = this.window.pageYOffset;
            }

            middleBoundary = boundary.left + boundary.width / 2;
            top += buttonHeight + boundary.top + boundary.height - diffTop - this.anchorPreview.offsetHeight;

            this.anchorPreview.style.top = Math.round(top) + 'px';
            this.anchorPreview.style.right = 'initial';
            if (middleBoundary < halfOffsetWidth) {
                this.anchorPreview.style.left = defaultLeft + halfOffsetWidth + 'px';
                this.anchorPreview.style.right = 'initial';
            } else if ((containerWidth - middleBoundary) < halfOffsetWidth) {
                this.anchorPreview.style.left = 'auto';
                this.anchorPreview.style.right = 0;
            } else {
                this.anchorPreview.style.left = defaultLeft + middleBoundary + 'px';
                this.anchorPreview.style.right = 'initial';
            }
        },

        attachToEditables: function () {
            this.subscribe('editableMouseover', this.handleEditableMouseover.bind(this));
            this.subscribe('positionedToolbar', this.handlePositionedToolbar.bind(this));
        },

        handlePositionedToolbar: function () {
            // If the toolbar is visible and positioned, we don't need to hide the preview
            // when showWhenToolbarIsVisible is true
            if (!this.showWhenToolbarIsVisible) {
                this.hidePreview();
            }
        },

        handleClick: function (event) {
            var anchorExtension = this.base.getExtensionByName('anchor'),
                activeAnchor = this.activeAnchor;

            if (anchorExtension && activeAnchor) {
                event.preventDefault();

                this.base.selectElement(this.activeAnchor);

                // Using setTimeout + delay because:
                // We may actually be displaying the anchor form, which should be controlled by delay
                this.base.delay(function () {
                    if (activeAnchor) {
                        var opts = {
                            value: activeAnchor.attributes.href.value,
                            target: activeAnchor.getAttribute('target'),
                            buttonClass: activeAnchor.getAttribute('class')
                        };
                        anchorExtension.showForm(opts);
                        activeAnchor = null;
                    }
                }.bind(this));
            }

            this.hidePreview();
        },

        handleAnchorMouseout: function () {
            this.anchorToPreview = null;
            this.off(this.activeAnchor, 'mouseout', this.instanceHandleAnchorMouseout);
            this.instanceHandleAnchorMouseout = null;
        },

        handleEditableMouseover: function (event) {
            var target = MediumEditor.util.getClosestTag(event.target, 'a');

            if (false === target) {
                return;
            }

            // Detect empty href attributes
            // The browser will make href="" or href="#top"
            // into absolute urls when accessed as event.target.href, so check the html
            if (!this.showOnEmptyLinks &&
                (!/href=["']\S+["']/.test(target.outerHTML) || /href=["']#\S+["']/.test(target.outerHTML))) {
                return true;
            }

            // only show when toolbar is not present
            var toolbar = this.base.getExtensionByName('toolbar');
            if (!this.showWhenToolbarIsVisible && toolbar && toolbar.isDisplayed && toolbar.isDisplayed()) {
                return true;
            }

            // detach handler for other anchor in case we hovered multiple anchors quickly
            if (this.activeAnchor && this.activeAnchor !== target) {
                this.detachPreviewHandlers();
            }

            this.anchorToPreview = target;

            this.instanceHandleAnchorMouseout = this.handleAnchorMouseout.bind(this);
            this.on(this.anchorToPreview, 'mouseout', this.instanceHandleAnchorMouseout);
            // Using setTimeout + delay because:
            // - We're going to show the anchor preview according to the configured delay
            //   if the mouse has not left the anchor tag in that time
            this.base.delay(function () {
                if (this.anchorToPreview) {
                    this.showPreview(this.anchorToPreview);
                }
            }.bind(this));
        },

        handlePreviewMouseover: function () {
            this.lastOver = (new Date()).getTime();
            this.hovering = true;
        },

        handlePreviewMouseout: function (event) {
            if (!event.relatedTarget || !/anchor-preview/.test(event.relatedTarget.className)) {
                this.hovering = false;
            }
        },

        updatePreview: function () {
            if (this.hovering) {
                return true;
            }
            var durr = (new Date()).getTime() - this.lastOver;
            if (durr > this.hideDelay) {
                // hide the preview 1/2 second after mouse leaves the link
                this.detachPreviewHandlers();
            }
        },

        detachPreviewHandlers: function () {
            // cleanup
            clearInterval(this.intervalTimer);
            if (this.instanceHandlePreviewMouseover) {
                this.off(this.anchorPreview, 'mouseover', this.instanceHandlePreviewMouseover);
                this.off(this.anchorPreview, 'mouseout', this.instanceHandlePreviewMouseout);
                if (this.activeAnchor) {
                    this.off(this.activeAnchor, 'mouseover', this.instanceHandlePreviewMouseover);
                    this.off(this.activeAnchor, 'mouseout', this.instanceHandlePreviewMouseout);
                }
            }

            this.hidePreview();

            this.hovering = this.instanceHandlePreviewMouseover = this.instanceHandlePreviewMouseout = null;
        },

        // TODO: break up method and extract out handlers
        attachPreviewHandlers: function () {
            this.lastOver = (new Date()).getTime();
            this.hovering = true;

            this.instanceHandlePreviewMouseover = this.handlePreviewMouseover.bind(this);
            this.instanceHandlePreviewMouseout = this.handlePreviewMouseout.bind(this);

            this.intervalTimer = setInterval(this.updatePreview.bind(this), 200);

            this.on(this.anchorPreview, 'mouseover', this.instanceHandlePreviewMouseover);
            this.on(this.anchorPreview, 'mouseout', this.instanceHandlePreviewMouseout);
            this.on(this.activeAnchor, 'mouseover', this.instanceHandlePreviewMouseover);
            this.on(this.activeAnchor, 'mouseout', this.instanceHandlePreviewMouseout);
        }
    });

    MediumEditor.extensions.anchorPreview = AnchorPreview;
}());

(function () {
    'use strict';

    var WHITESPACE_CHARS,
        KNOWN_TLDS_FRAGMENT,
        LINK_REGEXP_TEXT,
        KNOWN_TLDS_REGEXP,
        LINK_REGEXP;

    WHITESPACE_CHARS = [' ', '\t', '\n', '\r', '\u00A0', '\u2000', '\u2001', '\u2002', '\u2003',
                                    '\u2028', '\u2029'];
    KNOWN_TLDS_FRAGMENT = 'com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|' +
        'xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|' +
        'bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|' +
        'fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|' +
        'is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|' +
        'mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|' +
        'pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|ja|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|' +
        'tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw';

    LINK_REGEXP_TEXT =
        '(' +
        // Version of Gruber URL Regexp optimized for JS: http://stackoverflow.com/a/17733640
        '((?:(https?://|ftps?://|nntp://)|www\\d{0,3}[.]|[a-z0-9.\\-]+[.](' + KNOWN_TLDS_FRAGMENT + ')\\\/)\\S+(?:[^\\s`!\\[\\]{};:\'\".,?\u00AB\u00BB\u201C\u201D\u2018\u2019]))' +
        // Addition to above Regexp to support bare domains/one level subdomains with common non-i18n TLDs and without www prefix:
        ')|(([a-z0-9\\-]+\\.)?[a-z0-9\\-]+\\.(' + KNOWN_TLDS_FRAGMENT + '))';

    KNOWN_TLDS_REGEXP = new RegExp('^(' + KNOWN_TLDS_FRAGMENT + ')$', 'i');

    LINK_REGEXP = new RegExp(LINK_REGEXP_TEXT, 'gi');

    function nodeIsNotInsideAnchorTag(node) {
        return !MediumEditor.util.getClosestTag(node, 'a');
    }

    var AutoLink = MediumEditor.Extension.extend({
        init: function () {
            MediumEditor.Extension.prototype.init.apply(this, arguments);

            this.disableEventHandling = false;
            this.subscribe('editableKeypress', this.onKeypress.bind(this));
            this.subscribe('editableBlur', this.onBlur.bind(this));
            // MS IE has it's own auto-URL detect feature but ours is better in some ways. Be consistent.
            this.document.execCommand('AutoUrlDetect', false, false);
        },

        isLastInstance: function () {
            var activeInstances = 0;
            for (var i = 0; i < this.window._mediumEditors.length; i++) {
                var editor = this.window._mediumEditors[i];
                if (editor !== null && editor.getExtensionByName('autoLink') !== undefined) {
                    activeInstances++;
                }
            }
            return activeInstances === 1;
        },

        destroy: function () {
            // Turn AutoUrlDetect back on
            if (this.document.queryCommandSupported('AutoUrlDetect') && this.isLastInstance()) {
                this.document.execCommand('AutoUrlDetect', false, true);
            }
        },

        onBlur: function (blurEvent, editable) {
            this.performLinking(editable);
        },

        onKeypress: function (keyPressEvent) {
            if (this.disableEventHandling) {
                return;
            }

            if (MediumEditor.util.isKey(keyPressEvent, [MediumEditor.util.keyCode.SPACE, MediumEditor.util.keyCode.ENTER])) {
                clearTimeout(this.performLinkingTimeout);
                // Saving/restoring the selection in the middle of a keypress doesn't work well...
                this.performLinkingTimeout = setTimeout(function () {
                    try {
                        var sel = this.base.exportSelection();
                        if (this.performLinking(keyPressEvent.target)) {
                            // pass true for favorLaterSelectionAnchor - this is needed for links at the end of a
                            // paragraph in MS IE, or MS IE causes the link to be deleted right after adding it.
                            this.base.importSelection(sel, true);
                        }
                    } catch (e) {
                        if (window.console) {
                            window.console.error('Failed to perform linking', e);
                        }
                        this.disableEventHandling = true;
                    }
                }.bind(this), 0);
            }
        },

        performLinking: function (contenteditable) {
            /*
            Perform linking on blockElement basis, blockElements are HTML elements with text content and without
            child element.

            Example:
            - HTML content
            <blockquote>
              <p>link.</p>
              <p>my</p>
            </blockquote>

            - blockElements
            [<p>link.</p>, <p>my</p>]

            otherwise the detection can wrongly find the end of one paragraph and the beginning of another paragraph
            to constitute a link, such as a paragraph ending "link." and the next paragraph beginning with "my" is
            interpreted into "link.my" and the code tries to create a link across blockElements - which doesn't work
            and is terrible.
            (Medium deletes the spaces/returns between P tags so the textContent ends up without paragraph spacing)
            */
            var blockElements = MediumEditor.util.splitByBlockElements(contenteditable),
                documentModified = false;
            if (blockElements.length === 0) {
                blockElements = [contenteditable];
            }
            for (var i = 0; i < blockElements.length; i++) {
                documentModified = this.removeObsoleteAutoLinkSpans(blockElements[i]) || documentModified;
                documentModified = this.performLinkingWithinElement(blockElements[i]) || documentModified;
            }
            this.base.events.updateInput(contenteditable, { target: contenteditable, currentTarget: contenteditable });
            return documentModified;
        },

        removeObsoleteAutoLinkSpans: function (element) {
            if (!element || element.nodeType === 3) {
                return false;
            }

            var spans = element.querySelectorAll('span[data-auto-link="true"]'),
                documentModified = false;

            for (var i = 0; i < spans.length; i++) {
                var textContent = spans[i].textContent;
                if (textContent.indexOf('://') === -1) {
                    textContent = MediumEditor.util.ensureUrlHasProtocol(textContent);
                }
                if (spans[i].getAttribute('data-href') !== textContent && nodeIsNotInsideAnchorTag(spans[i])) {
                    documentModified = true;
                    var trimmedTextContent = textContent.replace(/\s+$/, '');
                    if (spans[i].getAttribute('data-href') === trimmedTextContent) {
                        var charactersTrimmed = textContent.length - trimmedTextContent.length,
                            subtree = MediumEditor.util.splitOffDOMTree(spans[i], this.splitTextBeforeEnd(spans[i], charactersTrimmed));
                        spans[i].parentNode.insertBefore(subtree, spans[i].nextSibling);
                    } else {
                        // Some editing has happened to the span, so just remove it entirely. The user can put it back
                        // around just the href content if they need to prevent it from linking
                        MediumEditor.util.unwrap(spans[i], this.document);
                    }
                }
            }
            return documentModified;
        },

        splitTextBeforeEnd: function (element, characterCount) {
            var treeWalker = this.document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false),
                lastChildNotExhausted = true;

            // Start the tree walker at the last descendant of the span
            while (lastChildNotExhausted) {
                lastChildNotExhausted = treeWalker.lastChild() !== null;
            }

            var currentNode,
                currentNodeValue,
                previousNode;
            while (characterCount > 0 && previousNode !== null) {
                currentNode = treeWalker.currentNode;
                currentNodeValue = currentNode.nodeValue;
                if (currentNodeValue.length > characterCount) {
                    previousNode = currentNode.splitText(currentNodeValue.length - characterCount);
                    characterCount = 0;
                } else {
                    previousNode = treeWalker.previousNode();
                    characterCount -= currentNodeValue.length;
                }
            }
            return previousNode;
        },

        performLinkingWithinElement: function (element) {
            var matches = this.findLinkableText(element),
                linkCreated = false;

            for (var matchIndex = 0; matchIndex < matches.length; matchIndex++) {
                var matchingTextNodes = MediumEditor.util.findOrCreateMatchingTextNodes(this.document, element,
                        matches[matchIndex]);
                if (this.shouldNotLink(matchingTextNodes)) {
                    continue;
                }
                this.createAutoLink(matchingTextNodes, matches[matchIndex].href);
            }
            return linkCreated;
        },

        shouldNotLink: function (textNodes) {
            var shouldNotLink = false;
            for (var i = 0; i < textNodes.length && shouldNotLink === false; i++) {
                // Do not link if the text node is either inside an anchor or inside span[data-auto-link]
                shouldNotLink = !!MediumEditor.util.traverseUp(textNodes[i], function (node) {
                    return node.nodeName.toLowerCase() === 'a' ||
                        (node.getAttribute && node.getAttribute('data-auto-link') === 'true');
                });
            }
            return shouldNotLink;
        },

        findLinkableText: function (contenteditable) {
            var textContent = contenteditable.textContent,
                match = null,
                matches = [];

            while ((match = LINK_REGEXP.exec(textContent)) !== null) {
                var matchOk = true,
                    matchEnd = match.index + match[0].length;
                // If the regexp detected something as a link that has text immediately preceding/following it, bail out.
                matchOk = (match.index === 0 || WHITESPACE_CHARS.indexOf(textContent[match.index - 1]) !== -1) &&
                    (matchEnd === textContent.length || WHITESPACE_CHARS.indexOf(textContent[matchEnd]) !== -1);
                // If the regexp detected a bare domain that doesn't use one of our expected TLDs, bail out.
                matchOk = matchOk && (match[0].indexOf('/') !== -1 ||
                    KNOWN_TLDS_REGEXP.test(match[0].split('.').pop().split('?').shift()));

                if (matchOk) {
                    matches.push({
                        href: match[0],
                        start: match.index,
                        end: matchEnd
                    });
                }
            }
            return matches;
        },

        createAutoLink: function (textNodes, href) {
            href = MediumEditor.util.ensureUrlHasProtocol(href);
            var anchor = MediumEditor.util.createLink(this.document, textNodes, href, this.getEditorOption('targetBlank') ? '_blank' : null),
                span = this.document.createElement('span');
            span.setAttribute('data-auto-link', 'true');
            span.setAttribute('data-href', href);
            anchor.insertBefore(span, anchor.firstChild);
            while (anchor.childNodes.length > 1) {
                span.appendChild(anchor.childNodes[1]);
            }
        }

    });

    MediumEditor.extensions.autoLink = AutoLink;
}());

(function () {
    'use strict';

    var CLASS_DRAG_OVER = 'medium-editor-dragover';

    function clearClassNames(element) {
        var editable = MediumEditor.util.getContainerEditorElement(element),
            existing = Array.prototype.slice.call(editable.parentElement.querySelectorAll('.' + CLASS_DRAG_OVER));

        existing.forEach(function (el) {
            el.classList.remove(CLASS_DRAG_OVER);
        });
    }

    var FileDragging = MediumEditor.Extension.extend({
        name: 'fileDragging',

        allowedTypes: ['image'],

        init: function () {
            MediumEditor.Extension.prototype.init.apply(this, arguments);

            this.subscribe('editableDrag', this.handleDrag.bind(this));
            this.subscribe('editableDrop', this.handleDrop.bind(this));
        },

        handleDrag: function (event) {
            event.preventDefault();
            event.dataTransfer.dropEffect = 'copy';

            var target = event.target.classList ? event.target : event.target.parentElement;

            // Ensure the class gets removed from anything that had it before
            clearClassNames(target);

            if (event.type === 'dragover') {
                target.classList.add(CLASS_DRAG_OVER);
            }
        },

        handleDrop: function (event) {
            // Prevent file from opening in the current window
            event.preventDefault();
            event.stopPropagation();
            // Select the dropping target, and set the selection to the end of the target
            // https://github.com/yabwe/medium-editor/issues/980
            this.base.selectElement(event.target);
            var selection = this.base.exportSelection();
            selection.start = selection.end;
            this.base.importSelection(selection);
            // IE9 does not support the File API, so prevent file from opening in the window
            // but also don't try to actually get the file
            if (event.dataTransfer.files) {
                Array.prototype.slice.call(event.dataTransfer.files).forEach(function (file) {
                    if (this.isAllowedFile(file)) {
                        if (file.type.match('image')) {
                            this.insertImageFile(file);
                        }
                    }
                }, this);
            }

            // Make sure we remove our class from everything
            clearClassNames(event.target);
        },

        isAllowedFile: function (file) {
            return this.allowedTypes.some(function (fileType) {
                return !!file.type.match(fileType);
            });
        },

        insertImageFile: function (file) {
            if (typeof FileReader !== 'function') {
                return;
            }
            var fileReader = new FileReader();
            fileReader.readAsDataURL(file);

            // attach the onload event handler, makes it easier to listen in with jasmine
            fileReader.addEventListener('load', function (e) {
                var addImageElement = this.document.createElement('img');
                addImageElement.src = e.target.result;
                MediumEditor.util.insertHTMLCommand(this.document, addImageElement.outerHTML);
            }.bind(this));
        }
    });

    MediumEditor.extensions.fileDragging = FileDragging;
}());

(function () {
    'use strict';

    var KeyboardCommands = MediumEditor.Extension.extend({
        name: 'keyboard-commands',

        /* KeyboardCommands Options */

        /* commands: [Array]
         * Array of objects describing each command and the combination of keys that will trigger it
         * Required for each object:
         *   command [String] (argument passed to editor.execAction())
         *   key [String] (keyboard character that triggers this command)
         *   meta [boolean] (whether the ctrl/meta key has to be active or inactive)
         *   shift [boolean] (whether the shift key has to be active or inactive)
         *   alt [boolean] (whether the alt key has to be active or inactive)
         */
        commands: [
            {
                command: 'bold',
                key: 'B',
                meta: true,
                shift: false,
                alt: false
            },
            {
                command: 'italic',
                key: 'I',
                meta: true,
                shift: false,
                alt: false
            },
            {
                command: 'underline',
                key: 'U',
                meta: true,
                shift: false,
                alt: false
            }
        ],

        init: function () {
            MediumEditor.Extension.prototype.init.apply(this, arguments);

            this.subscribe('editableKeydown', this.handleKeydown.bind(this));
            this.keys = {};
            this.commands.forEach(function (command) {
                var keyCode = command.key.charCodeAt(0);
                if (!this.keys[keyCode]) {
                    this.keys[keyCode] = [];
                }
                this.keys[keyCode].push(command);
            }, this);
        },

        handleKeydown: function (event) {
            var keyCode = MediumEditor.util.getKeyCode(event);
            if (!this.keys[keyCode]) {
                return;
            }

            var isMeta = MediumEditor.util.isMetaCtrlKey(event),
                isShift = !!event.shiftKey,
                isAlt = !!event.altKey;

            this.keys[keyCode].forEach(function (data) {
                if (data.meta === isMeta &&
                    data.shift === isShift &&
                    (data.alt === isAlt ||
                     undefined === data.alt)) { // TODO deprecated: remove check for undefined === data.alt when jumping to 6.0.0
                    event.preventDefault();
                    event.stopPropagation();

                    // command can be a function to execute
                    if (typeof data.command === 'function') {
                        data.command.apply(this);
                    }
                    // command can be false so the shortcut is just disabled
                    else if (false !== data.command) {
                        this.execAction(data.command);
                    }
                }
            }, this);
        }
    });

    MediumEditor.extensions.keyboardCommands = KeyboardCommands;
}());

(function () {
    'use strict';

    var FontNameForm = MediumEditor.extensions.form.extend({

        name: 'fontname',
        action: 'fontName',
        aria: 'change font name',
        contentDefault: '&#xB1;', // ±
        contentFA: '<i class="fa fa-font"></i>',

        fonts: ['', 'Arial', 'Verdana', 'Times New Roman'],

        init: function () {
            MediumEditor.extensions.form.prototype.init.apply(this, arguments);
        },

        // Called when the button the toolbar is clicked
        // Overrides ButtonExtension.handleClick
        handleClick: function (event) {
            event.preventDefault();
            event.stopPropagation();

            if (!this.isDisplayed()) {
                // Get FontName of current selection (convert to string since IE returns this as number)
                var fontName = this.document.queryCommandValue('fontName') + '';
                this.showForm(fontName);
            }

            return false;
        },

        // Called by medium-editor to append form to the toolbar
        getForm: function () {
            if (!this.form) {
                this.form = this.createForm();
            }
            return this.form;
        },

        // Used by medium-editor when the default toolbar is to be displayed
        isDisplayed: function () {
            return this.getForm().style.display === 'block';
        },

        hideForm: function () {
            this.getForm().style.display = 'none';
            this.getSelect().value = '';
        },

        showForm: function (fontName) {
            var select = this.getSelect();

            this.base.saveSelection();
            this.hideToolbarDefaultActions();
            this.getForm().style.display = 'block';
            this.setToolbarPosition();

            select.value = fontName || '';
            select.focus();
        },

        // Called by core when tearing down medium-editor (destroy)
        destroy: function () {
            if (!this.form) {
                return false;
            }

            if (this.form.parentNode) {
                this.form.parentNode.removeChild(this.form);
            }

            delete this.form;
        },

        // core methods

        doFormSave: function () {
            this.base.restoreSelection();
            this.base.checkSelection();
        },

        doFormCancel: function () {
            this.base.restoreSelection();
            this.clearFontName();
            this.base.checkSelection();
        },

        // form creation and event handling
        createForm: function () {
            var doc = this.document,
                form = doc.createElement('div'),
                select = doc.createElement('select'),
                close = doc.createElement('a'),
                save = doc.createElement('a'),
                option;

            // Font Name Form (div)
            form.className = 'medium-editor-toolbar-form';
            form.id = 'medium-editor-toolbar-form-fontname-' + this.getEditorId();

            // Handle clicks on the form itself
            this.on(form, 'click', this.handleFormClick.bind(this));

            // Add font names
            for (var i = 0; i<this.fonts.length; i++) {
                option = doc.createElement('option');
                option.innerHTML = this.fonts[i];
                option.value = this.fonts[i];
                select.appendChild(option);
            }

            select.className = 'medium-editor-toolbar-select';
            form.appendChild(select);

            // Handle typing in the textbox
            this.on(select, 'change', this.handleFontChange.bind(this));

            // Add save buton
            save.setAttribute('href', '#');
            save.className = 'medium-editor-toobar-save';
            save.innerHTML = this.getEditorOption('buttonLabels') === 'fontawesome' ?
                             '<i class="fa fa-check"></i>' :
                             '&#10003;';
            form.appendChild(save);

            // Handle save button clicks (capture)
            this.on(save, 'click', this.handleSaveClick.bind(this), true);

            // Add close button
            close.setAttribute('href', '#');
            close.className = 'medium-editor-toobar-close';
            close.innerHTML = this.getEditorOption('buttonLabels') === 'fontawesome' ?
                              '<i class="fa fa-times"></i>' :
                              '&times;';
            form.appendChild(close);

            // Handle close button clicks
            this.on(close, 'click', this.handleCloseClick.bind(this));

            return form;
        },

        getSelect: function () {
            return this.getForm().querySelector('select.medium-editor-toolbar-select');
        },

        clearFontName: function () {
            MediumEditor.selection.getSelectedElements(this.document).forEach(function (el) {
                if (el.nodeName.toLowerCase() === 'font' && el.hasAttribute('face')) {
                    el.removeAttribute('face');
                }
            });
        },

        handleFontChange: function () {
            var font = this.getSelect().value;
            if (font === '') {
                this.clearFontName();
            } else {
                this.execAction('fontName', { value: font });
            }
        },

        handleFormClick: function (event) {
            // make sure not to hide form when clicking inside the form
            event.stopPropagation();
        },

        handleSaveClick: function (event) {
            // Clicking Save -> create the font size
            event.preventDefault();
            this.doFormSave();
        },

        handleCloseClick: function (event) {
            // Click Close -> close the form
            event.preventDefault();
            this.doFormCancel();
        }
    });

    MediumEditor.extensions.fontName = FontNameForm;
}());

(function () {
    'use strict';

    var FontSizeForm = MediumEditor.extensions.form.extend({

        name: 'fontsize',
        action: 'fontSize',
        aria: 'increase/decrease font size',
        contentDefault: '&#xB1;', // ±
        contentFA: '<i class="fa fa-text-height"></i>',

        init: function () {
            MediumEditor.extensions.form.prototype.init.apply(this, arguments);
        },

        // Called when the button the toolbar is clicked
        // Overrides ButtonExtension.handleClick
        handleClick: function (event) {
            event.preventDefault();
            event.stopPropagation();

            if (!this.isDisplayed()) {
                // Get fontsize of current selection (convert to string since IE returns this as number)
                var fontSize = this.document.queryCommandValue('fontSize') + '';
                this.showForm(fontSize);
            }

            return false;
        },

        // Called by medium-editor to append form to the toolbar
        getForm: function () {
            if (!this.form) {
                this.form = this.createForm();
            }
            return this.form;
        },

        // Used by medium-editor when the default toolbar is to be displayed
        isDisplayed: function () {
            return this.getForm().style.display === 'block';
        },

        hideForm: function () {
            this.getForm().style.display = 'none';
            this.getInput().value = '';
        },

        showForm: function (fontSize) {
            var input = this.getInput();

            this.base.saveSelection();
            this.hideToolbarDefaultActions();
            this.getForm().style.display = 'block';
            this.setToolbarPosition();

            input.value = fontSize || '';
            input.focus();
        },

        // Called by core when tearing down medium-editor (destroy)
        destroy: function () {
            if (!this.form) {
                return false;
            }

            if (this.form.parentNode) {
                this.form.parentNode.removeChild(this.form);
            }

            delete this.form;
        },

        // core methods

        doFormSave: function () {
            this.base.restoreSelection();
            this.base.checkSelection();
        },

        doFormCancel: function () {
            this.base.restoreSelection();
            this.clearFontSize();
            this.base.checkSelection();
        },

        // form creation and event handling
        createForm: function () {
            var doc = this.document,
                form = doc.createElement('div'),
                input = doc.createElement('input'),
                close = doc.createElement('a'),
                save = doc.createElement('a');

            // Font Size Form (div)
            form.className = 'medium-editor-toolbar-form';
            form.id = 'medium-editor-toolbar-form-fontsize-' + this.getEditorId();

            // Handle clicks on the form itself
            this.on(form, 'click', this.handleFormClick.bind(this));

            // Add font size slider
            input.setAttribute('type', 'range');
            input.setAttribute('min', '1');
            input.setAttribute('max', '7');
            input.className = 'medium-editor-toolbar-input';
            form.appendChild(input);

            // Handle typing in the textbox
            this.on(input, 'change', this.handleSliderChange.bind(this));

            // Add save buton
            save.setAttribute('href', '#');
            save.className = 'medium-editor-toobar-save';
            save.innerHTML = this.getEditorOption('buttonLabels') === 'fontawesome' ?
                             '<i class="fa fa-check"></i>' :
                             '&#10003;';
            form.appendChild(save);

            // Handle save button clicks (capture)
            this.on(save, 'click', this.handleSaveClick.bind(this), true);

            // Add close button
            close.setAttribute('href', '#');
            close.className = 'medium-editor-toobar-close';
            close.innerHTML = this.getEditorOption('buttonLabels') === 'fontawesome' ?
                              '<i class="fa fa-times"></i>' :
                              '&times;';
            form.appendChild(close);

            // Handle close button clicks
            this.on(close, 'click', this.handleCloseClick.bind(this));

            return form;
        },

        getInput: function () {
            return this.getForm().querySelector('input.medium-editor-toolbar-input');
        },

        clearFontSize: function () {
            MediumEditor.selection.getSelectedElements(this.document).forEach(function (el) {
                if (el.nodeName.toLowerCase() === 'font' && el.hasAttribute('size')) {
                    el.removeAttribute('size');
                }
            });
        },

        handleSliderChange: function () {
            var size = this.getInput().value;
            if (size === '4') {
                this.clearFontSize();
            } else {
                this.execAction('fontSize', { value: size });
            }
        },

        handleFormClick: function (event) {
            // make sure not to hide form when clicking inside the form
            event.stopPropagation();
        },

        handleSaveClick: function (event) {
            // Clicking Save -> create the font size
            event.preventDefault();
            this.doFormSave();
        },

        handleCloseClick: function (event) {
            // Click Close -> close the form
            event.preventDefault();
            this.doFormCancel();
        }
    });

    MediumEditor.extensions.fontSize = FontSizeForm;
}());
(function () {
    'use strict';

    /* Helpers and internal variables that don't need to be members of actual paste handler */

    var pasteBinDefaultContent = '%ME_PASTEBIN%',
        lastRange = null,
        keyboardPasteEditable = null,
        stopProp = function (event) {
            event.stopPropagation();
        };

    /*jslint regexp: true*/
    /*
        jslint does not allow character negation, because the negation
        will not match any unicode characters. In the regexes in this
        block, negation is used specifically to match the end of an html
        tag, and in fact unicode characters *should* be allowed.
    */
    function createReplacements() {
        return [
            // Remove anything but the contents within the BODY element
            [new RegExp(/^[\s\S]*<body[^>]*>\s*|\s*<\/body[^>]*>[\s\S]*$/g), ''],

            // cleanup comments added by Chrome when pasting html
            [new RegExp(/<!--StartFragment-->|<!--EndFragment-->/g), ''],

            // Trailing BR elements
            [new RegExp(/<br>$/i), ''],

            // replace two bogus tags that begin pastes from google docs
            [new RegExp(/<[^>]*docs-internal-guid[^>]*>/gi), ''],
            [new RegExp(/<\/b>(<br[^>]*>)?$/gi), ''],

             // un-html spaces and newlines inserted by OS X
            [new RegExp(/<span class="Apple-converted-space">\s+<\/span>/g), ' '],
            [new RegExp(/<br class="Apple-interchange-newline">/g), '<br>'],

            // replace google docs italics+bold with a span to be replaced once the html is inserted
            [new RegExp(/<span[^>]*(font-style:italic;font-weight:(bold|700)|font-weight:(bold|700);font-style:italic)[^>]*>/gi), '<span class="replace-with italic bold">'],

            // replace google docs italics with a span to be replaced once the html is inserted
            [new RegExp(/<span[^>]*font-style:italic[^>]*>/gi), '<span class="replace-with italic">'],

            //[replace google docs bolds with a span to be replaced once the html is inserted
            [new RegExp(/<span[^>]*font-weight:(bold|700)[^>]*>/gi), '<span class="replace-with bold">'],

             // replace manually entered b/i/a tags with real ones
            [new RegExp(/&lt;(\/?)(i|b|a)&gt;/gi), '<$1$2>'],

             // replace manually a tags with real ones, converting smart-quotes from google docs
            [new RegExp(/&lt;a(?:(?!href).)+href=(?:&quot;|&rdquo;|&ldquo;|"|“|”)(((?!&quot;|&rdquo;|&ldquo;|"|“|”).)*)(?:&quot;|&rdquo;|&ldquo;|"|“|”)(?:(?!&gt;).)*&gt;/gi), '<a href="$1">'],

            // Newlines between paragraphs in html have no syntactic value,
            // but then have a tendency to accidentally become additional paragraphs down the line
            [new RegExp(/<\/p>\n+/gi), '</p>'],
            [new RegExp(/\n+<p/gi), '<p'],

            // Microsoft Word makes these odd tags, like <o:p></o:p>
            [new RegExp(/<\/?o:[a-z]*>/gi), ''],

            // Microsoft Word adds some special elements around list items
            [new RegExp(/<!\[if !supportLists\]>(((?!<!).)*)<!\[endif]\>/gi), '$1']
        ];
    }
    /*jslint regexp: false*/

    /**
     * Gets various content types out of the Clipboard API. It will also get the
     * plain text using older IE and WebKit API.
     *
     * @param {event} event Event fired on paste.
     * @param {win} reference to window
     * @param {doc} reference to document
     * @return {Object} Object with mime types and data for those mime types.
     */
    function getClipboardContent(event, win, doc) {
        var dataTransfer = event.clipboardData || win.clipboardData || doc.dataTransfer,
            data = {};

        if (!dataTransfer) {
            return data;
        }

        // Use old WebKit/IE API
        if (dataTransfer.getData) {
            var legacyText = dataTransfer.getData('Text');
            if (legacyText && legacyText.length > 0) {
                data['text/plain'] = legacyText;
            }
        }

        if (dataTransfer.types) {
            for (var i = 0; i < dataTransfer.types.length; i++) {
                var contentType = dataTransfer.types[i];
                data[contentType] = dataTransfer.getData(contentType);
            }
        }

        return data;
    }

    var PasteHandler = MediumEditor.Extension.extend({
        /* Paste Options */

        /* forcePlainText: [boolean]
         * Forces pasting as plain text.
         */
        forcePlainText: true,

        /* cleanPastedHTML: [boolean]
         * cleans pasted content from different sources, like google docs etc.
         */
        cleanPastedHTML: false,

        /* preCleanReplacements: [Array]
         * custom pairs (2 element arrays) of RegExp and replacement text to use during past when
         * __forcePlainText__ or __cleanPastedHTML__ are `true` OR when calling `cleanPaste(text)` helper method.
         * These replacements are executed before any medium editor defined replacements.
         */
        preCleanReplacements: [],

        /* cleanReplacements: [Array]
         * custom pairs (2 element arrays) of RegExp and replacement text to use during paste when
         * __forcePlainText__ or __cleanPastedHTML__ are `true` OR when calling `cleanPaste(text)` helper method.
         * These replacements are executed after any medium editor defined replacements.
         */
        cleanReplacements: [],

        /* cleanAttrs:: [Array]
         * list of element attributes to remove during paste when __cleanPastedHTML__ is `true` or when
         * calling `cleanPaste(text)` or `pasteHTML(html, options)` helper methods.
         */
        cleanAttrs: ['class', 'style', 'dir'],

        /* cleanTags: [Array]
         * list of element tag names to remove during paste when __cleanPastedHTML__ is `true` or when
         * calling `cleanPaste(text)` or `pasteHTML(html, options)` helper methods.
         */
        cleanTags: ['meta'],

        /* unwrapTags: [Array]
         * list of element tag names to unwrap (remove the element tag but retain its child elements)
         * during paste when __cleanPastedHTML__ is `true` or when
         * calling `cleanPaste(text)` or `pasteHTML(html, options)` helper methods.
         */
        unwrapTags: [],

        init: function () {
            MediumEditor.Extension.prototype.init.apply(this, arguments);

            if (this.forcePlainText || this.cleanPastedHTML) {
                this.subscribe('editableKeydown', this.handleKeydown.bind(this));
                // We need access to the full event data in paste
                // so we can't use the editablePaste event here
                this.getEditorElements().forEach(function (element) {
                    this.on(element, 'paste', this.handlePaste.bind(this));
                }, this);
                this.subscribe('addElement', this.handleAddElement.bind(this));
            }
        },

        handleAddElement: function (event, editable) {
            this.on(editable, 'paste', this.handlePaste.bind(this));
        },

        destroy: function () {
            // Make sure pastebin is destroyed in case it's still around for some reason
            if (this.forcePlainText || this.cleanPastedHTML) {
                this.removePasteBin();
            }
        },

        handlePaste: function (event, editable) {
            if (event.defaultPrevented) {
                return;
            }

            var clipboardContent = getClipboardContent(event, this.window, this.document),
                pastedHTML = clipboardContent['text/html'],
                pastedPlain = clipboardContent['text/plain'];

            if (this.window.clipboardData && event.clipboardData === undefined && !pastedHTML) {
                // If window.clipboardData exists, but event.clipboardData doesn't exist,
                // we're probably in IE. IE only has two possibilities for clipboard
                // data format: 'Text' and 'URL'.
                //
                // For IE, we'll fallback to 'Text' for text/html
                pastedHTML = pastedPlain;
            }

            if (pastedHTML || pastedPlain) {
                event.preventDefault();

                this.doPaste(pastedHTML, pastedPlain, editable);
            }
        },

        doPaste: function (pastedHTML, pastedPlain, editable) {
            var paragraphs,
                html = '',
                p;

            if (this.cleanPastedHTML && pastedHTML) {
                return this.cleanPaste(pastedHTML);
            }

            if (!(this.getEditorOption('disableReturn') || (editable && editable.getAttribute('data-disable-return')))) {
                paragraphs = pastedPlain.split(/[\r\n]+/g);
                // If there are no \r\n in data, don't wrap in <p>
                if (paragraphs.length > 1) {
                    for (p = 0; p < paragraphs.length; p += 1) {
                        if (paragraphs[p] !== '') {
                            html += '<p>' + MediumEditor.util.htmlEntities(paragraphs[p]) + '</p>';
                        }
                    }
                } else {
                    html = MediumEditor.util.htmlEntities(paragraphs[0]);
                }
            } else {
                html = MediumEditor.util.htmlEntities(pastedPlain);
            }
            MediumEditor.util.insertHTMLCommand(this.document, html);
        },

        handlePasteBinPaste: function (event) {
            if (event.defaultPrevented) {
                this.removePasteBin();
                return;
            }

            var clipboardContent = getClipboardContent(event, this.window, this.document),
                pastedHTML = clipboardContent['text/html'],
                pastedPlain = clipboardContent['text/plain'],
                editable = keyboardPasteEditable;

            // If we have valid html already, or we're not in cleanPastedHTML mode
            // we can ignore the paste bin and just paste now
            if (!this.cleanPastedHTML || pastedHTML) {
                event.preventDefault();
                this.removePasteBin();
                this.doPaste(pastedHTML, pastedPlain, editable);

                // The event handling code listens for paste on the editable element
                // in order to trigger the editablePaste event.  Since this paste event
                // is happening on the pastebin, the event handling code never knows about it
                // So, we have to trigger editablePaste manually
                this.trigger('editablePaste', { currentTarget: editable, target: editable }, editable);
                return;
            }

            // We need to look at the paste bin, so do a setTimeout to let the paste
            // fall through into the paste bin
            setTimeout(function () {
                // Only look for HTML if we're in cleanPastedHTML mode
                if (this.cleanPastedHTML) {
                    // If clipboard didn't have HTML, try the paste bin
                    pastedHTML = this.getPasteBinHtml();
                }

                // If we needed the paste bin, we're done with it now, remove it
                this.removePasteBin();

                // Handle the paste with the html from the paste bin
                this.doPaste(pastedHTML, pastedPlain, editable);

                // The event handling code listens for paste on the editable element
                // in order to trigger the editablePaste event.  Since this paste event
                // is happening on the pastebin, the event handling code never knows about it
                // So, we have to trigger editablePaste manually
                this.trigger('editablePaste', { currentTarget: editable, target: editable }, editable);
            }.bind(this), 0);
        },

        handleKeydown: function (event, editable) {
            // if it's not Ctrl+V, do nothing
            if (!(MediumEditor.util.isKey(event, MediumEditor.util.keyCode.V) && MediumEditor.util.isMetaCtrlKey(event))) {
                return;
            }

            event.stopImmediatePropagation();

            this.removePasteBin();
            this.createPasteBin(editable);
        },

        createPasteBin: function (editable) {
            var rects,
                range = MediumEditor.selection.getSelectionRange(this.document),
                top = this.window.pageYOffset;

            keyboardPasteEditable = editable;

            if (range) {
                rects = range.getClientRects();

                // on empty line, rects is empty so we grab information from the first container of the range
                if (rects.length) {
                    top += rects[0].top;
                } else {
                    top += range.startContainer.getBoundingClientRect().top;
                }
            }

            lastRange = range;

            var pasteBinElm = this.document.createElement('div');
            pasteBinElm.id = this.pasteBinId = 'medium-editor-pastebin-' + (+Date.now());
            pasteBinElm.setAttribute('style', 'border: 1px red solid; position: absolute; top: ' + top + 'px; width: 10px; height: 10px; overflow: hidden; opacity: 0');
            pasteBinElm.setAttribute('contentEditable', true);
            pasteBinElm.innerHTML = pasteBinDefaultContent;

            this.document.body.appendChild(pasteBinElm);

            // avoid .focus() to stop other event (actually the paste event)
            this.on(pasteBinElm, 'focus', stopProp);
            this.on(pasteBinElm, 'focusin', stopProp);
            this.on(pasteBinElm, 'focusout', stopProp);

            pasteBinElm.focus();

            MediumEditor.selection.selectNode(pasteBinElm, this.document);

            if (!this.boundHandlePaste) {
                this.boundHandlePaste = this.handlePasteBinPaste.bind(this);
            }

            this.on(pasteBinElm, 'paste', this.boundHandlePaste);
        },

        removePasteBin: function () {
            if (null !== lastRange) {
                MediumEditor.selection.selectRange(this.document, lastRange);
                lastRange = null;
            }

            if (null !== keyboardPasteEditable) {
                keyboardPasteEditable = null;
            }

            var pasteBinElm = this.getPasteBin();
            if (!pasteBinElm) {
                return;
            }

            if (pasteBinElm) {
                this.off(pasteBinElm, 'focus', stopProp);
                this.off(pasteBinElm, 'focusin', stopProp);
                this.off(pasteBinElm, 'focusout', stopProp);
                this.off(pasteBinElm, 'paste', this.boundHandlePaste);
                pasteBinElm.parentElement.removeChild(pasteBinElm);
            }
        },

        getPasteBin: function () {
            return this.document.getElementById(this.pasteBinId);
        },

        getPasteBinHtml: function () {
            var pasteBinElm = this.getPasteBin();

            if (!pasteBinElm) {
                return false;
            }

            // WebKit has a nice bug where it clones the paste bin if you paste from for example notepad
            // so we need to force plain text mode in this case
            if (pasteBinElm.firstChild && pasteBinElm.firstChild.id === 'mcepastebin') {
                return false;
            }

            var pasteBinHtml = pasteBinElm.innerHTML;

            // If paste bin is empty try using plain text mode
            // since that is better than nothing right
            if (!pasteBinHtml || pasteBinHtml === pasteBinDefaultContent) {
                return false;
            }

            return pasteBinHtml;
        },

        cleanPaste: function (text) {
            var i, elList, tmp, workEl,
                multiline = /<p|<br|<div/.test(text),
                replacements = [].concat(
                    this.preCleanReplacements || [],
                    createReplacements(),
                    this.cleanReplacements || []);

            for (i = 0; i < replacements.length; i += 1) {
                text = text.replace(replacements[i][0], replacements[i][1]);
            }

            if (!multiline) {
                return this.pasteHTML(text);
            }

            // create a temporary div to cleanup block elements
            tmp = this.document.createElement('div');

            // double br's aren't converted to p tags, but we want paragraphs.
            tmp.innerHTML = '<p>' + text.split('<br><br>').join('</p><p>') + '</p>';

            // block element cleanup
            elList = tmp.querySelectorAll('a,p,div,br');
            for (i = 0; i < elList.length; i += 1) {
                workEl = elList[i];

                // Microsoft Word replaces some spaces with newlines.
                // While newlines between block elements are meaningless, newlines within
                // elements are sometimes actually spaces.
                workEl.innerHTML = workEl.innerHTML.replace(/\n/gi, ' ');

                switch (workEl.nodeName.toLowerCase()) {
                    case 'p':
                    case 'div':
                        this.filterCommonBlocks(workEl);
                        break;
                    case 'br':
                        this.filterLineBreak(workEl);
                        break;
                }
            }

            this.pasteHTML(tmp.innerHTML);
        },

        pasteHTML: function (html, options) {
            options = MediumEditor.util.defaults({}, options, {
                cleanAttrs: this.cleanAttrs,
                cleanTags: this.cleanTags,
                unwrapTags: this.unwrapTags
            });

            var elList, workEl, i, fragmentBody, pasteBlock = this.document.createDocumentFragment();

            pasteBlock.appendChild(this.document.createElement('body'));

            fragmentBody = pasteBlock.querySelector('body');
            fragmentBody.innerHTML = html;

            this.cleanupSpans(fragmentBody);

            elList = fragmentBody.querySelectorAll('*');
            for (i = 0; i < elList.length; i += 1) {
                workEl = elList[i];

                if ('a' === workEl.nodeName.toLowerCase() && this.getEditorOption('targetBlank')) {
                    MediumEditor.util.setTargetBlank(workEl);
                }

                MediumEditor.util.cleanupAttrs(workEl, options.cleanAttrs);
                MediumEditor.util.cleanupTags(workEl, options.cleanTags);
                MediumEditor.util.unwrapTags(workEl, options.unwrapTags);
            }

            MediumEditor.util.insertHTMLCommand(this.document, fragmentBody.innerHTML.replace(/&nbsp;/g, ' '));
        },

        // TODO (6.0): Make this an internal helper instead of member of paste handler
        isCommonBlock: function (el) {
            return (el && (el.nodeName.toLowerCase() === 'p' || el.nodeName.toLowerCase() === 'div'));
        },

        // TODO (6.0): Make this an internal helper instead of member of paste handler
        filterCommonBlocks: function (el) {
            if (/^\s*$/.test(el.textContent) && el.parentNode) {
                el.parentNode.removeChild(el);
            }
        },

        // TODO (6.0): Make this an internal helper instead of member of paste handler
        filterLineBreak: function (el) {
            if (this.isCommonBlock(el.previousElementSibling)) {
                // remove stray br's following common block elements
                this.removeWithParent(el);
            } else if (this.isCommonBlock(el.parentNode) && (el.parentNode.firstChild === el || el.parentNode.lastChild === el)) {
                // remove br's just inside open or close tags of a div/p
                this.removeWithParent(el);
            } else if (el.parentNode && el.parentNode.childElementCount === 1 && el.parentNode.textContent === '') {
                // and br's that are the only child of elements other than div/p
                this.removeWithParent(el);
            }
        },

        // TODO (6.0): Make this an internal helper instead of member of paste handler
        // remove an element, including its parent, if it is the only element within its parent
        removeWithParent: function (el) {
            if (el && el.parentNode) {
                if (el.parentNode.parentNode && el.parentNode.childElementCount === 1) {
                    el.parentNode.parentNode.removeChild(el.parentNode);
                } else {
                    el.parentNode.removeChild(el);
                }
            }
        },

        // TODO (6.0): Make this an internal helper instead of member of paste handler
        cleanupSpans: function (containerEl) {
            var i,
                el,
                newEl,
                spans = containerEl.querySelectorAll('.replace-with'),
                isCEF = function (el) {
                    return (el && el.nodeName !== '#text' && el.getAttribute('contenteditable') === 'false');
                };

            for (i = 0; i < spans.length; i += 1) {
                el = spans[i];
                newEl = this.document.createElement(el.classList.contains('bold') ? 'b' : 'i');

                if (el.classList.contains('bold') && el.classList.contains('italic')) {
                    // add an i tag as well if this has both italics and bold
                    newEl.innerHTML = '<i>' + el.innerHTML + '</i>';
                } else {
                    newEl.innerHTML = el.innerHTML;
                }
                el.parentNode.replaceChild(newEl, el);
            }

            spans = containerEl.querySelectorAll('span');
            for (i = 0; i < spans.length; i += 1) {
                el = spans[i];

                // bail if span is in contenteditable = false
                if (MediumEditor.util.traverseUp(el, isCEF)) {
                    return false;
                }

                // remove empty spans, replace others with their contents
                MediumEditor.util.unwrap(el, this.document);
            }
        }
    });

    MediumEditor.extensions.paste = PasteHandler;
}());

(function () {
    'use strict';

    var Placeholder = MediumEditor.Extension.extend({
        name: 'placeholder',

        /* Placeholder Options */

        /* text: [string]
         * Text to display in the placeholder
         */
        text: 'Type your text',

        /* hideOnClick: [boolean]
         * Should we hide the placeholder on click (true) or when user starts typing (false)
         */
        hideOnClick: true,

        init: function () {
            MediumEditor.Extension.prototype.init.apply(this, arguments);

            this.initPlaceholders();
            this.attachEventHandlers();
        },

        initPlaceholders: function () {
            this.getEditorElements().forEach(this.initElement, this);
        },

        handleAddElement: function (event, editable) {
            this.initElement(editable);
        },

        initElement: function (el) {
            if (!el.getAttribute('data-placeholder')) {
                el.setAttribute('data-placeholder', this.text);
            }
            this.updatePlaceholder(el);
        },

        destroy: function () {
            this.getEditorElements().forEach(this.cleanupElement, this);
        },

        handleRemoveElement: function (event, editable) {
            this.cleanupElement(editable);
        },

        cleanupElement: function (el) {
            if (el.getAttribute('data-placeholder') === this.text) {
                el.removeAttribute('data-placeholder');
            }
        },

        showPlaceholder: function (el) {
            if (el) {
                // https://github.com/yabwe/medium-editor/issues/234
                // In firefox, styling the placeholder with an absolutely positioned
                // pseudo element causes the cursor to appear in a bad location
                // when the element is completely empty, so apply a different class to
                // style it with a relatively positioned pseudo element
                if (MediumEditor.util.isFF && el.childNodes.length === 0) {
                    el.classList.add('medium-editor-placeholder-relative');
                    el.classList.remove('medium-editor-placeholder');
                } else {
                    el.classList.add('medium-editor-placeholder');
                    el.classList.remove('medium-editor-placeholder-relative');
                }
            }
        },

        hidePlaceholder: function (el) {
            if (el) {
                el.classList.remove('medium-editor-placeholder');
                el.classList.remove('medium-editor-placeholder-relative');
            }
        },

        updatePlaceholder: function (el, dontShow) {
            // If the element has content, hide the placeholder
            if (el.querySelector('img, blockquote, ul, ol, table') || (el.textContent.replace(/^\s+|\s+$/g, '') !== '')) {
                return this.hidePlaceholder(el);
            }

            if (!dontShow) {
                this.showPlaceholder(el);
            }
        },

        attachEventHandlers: function () {
            if (this.hideOnClick) {
                // For the 'hideOnClick' option, the placeholder should always be hidden on focus
                this.subscribe('focus', this.handleFocus.bind(this));
            }

            // If the editor has content, it should always hide the placeholder
            this.subscribe('editableInput', this.handleInput.bind(this));

            // When the editor loses focus, check if the placeholder should be visible
            this.subscribe('blur', this.handleBlur.bind(this));

            // Need to know when elements are added/removed from the editor
            this.subscribe('addElement', this.handleAddElement.bind(this));
            this.subscribe('removeElement', this.handleRemoveElement.bind(this));
        },

        handleInput: function (event, element) {
            // If the placeholder should be hidden on focus and the
            // element has focus, don't show the placeholder
            var dontShow = this.hideOnClick && (element === this.base.getFocusedElement());

            // Editor's content has changed, check if the placeholder should be hidden
            this.updatePlaceholder(element, dontShow);
        },

        handleFocus: function (event, element) {
            // Editor has focus, hide the placeholder
            this.hidePlaceholder(element);
        },

        handleBlur: function (event, element) {
            // Editor has lost focus, check if the placeholder should be shown
            this.updatePlaceholder(element);
        }
    });

    MediumEditor.extensions.placeholder = Placeholder;
}());

(function () {
    'use strict';

    var Toolbar = MediumEditor.Extension.extend({
        name: 'toolbar',

        /* Toolbar Options */

        /* align: ['left'|'center'|'right']
         * When the __static__ option is true, this aligns the static toolbar
         * relative to the medium-editor element.
         */
        align: 'center',

        /* allowMultiParagraphSelection: [boolean]
         * enables/disables whether the toolbar should be displayed when
         * selecting multiple paragraphs/block elements
         */
        allowMultiParagraphSelection: true,

        /* buttons: [Array]
         * the names of the set of buttons to display on the toolbar.
         */
        buttons: ['bold', 'italic', 'underline', 'anchor', 'h2', 'h3', 'quote'],

        /* diffLeft: [Number]
         * value in pixels to be added to the X axis positioning of the toolbar.
         */
        diffLeft: 0,

        /* diffTop: [Number]
         * value in pixels to be added to the Y axis positioning of the toolbar.
         */
        diffTop: -10,

        /* firstButtonClass: [string]
         * CSS class added to the first button in the toolbar.
         */
        firstButtonClass: 'medium-editor-button-first',

        /* lastButtonClass: [string]
         * CSS class added to the last button in the toolbar.
         */
        lastButtonClass: 'medium-editor-button-last',

        /* standardizeSelectionStart: [boolean]
         * enables/disables standardizing how the beginning of a range is decided
         * between browsers whenever the selected text is analyzed for updating toolbar buttons status.
         */
        standardizeSelectionStart: false,

        /* static: [boolean]
         * enable/disable the toolbar always displaying in the same location
         * relative to the medium-editor element.
         */
        static: false,

        /* sticky: [boolean]
         * When the __static__ option is true, this enables/disables the toolbar
         * "sticking" to the viewport and staying visible on the screen while
         * the page scrolls.
         */
        sticky: false,

        /* stickyTopOffset: [Number]
         * Value in pixel of the top offset above the toolbar
         */
        stickyTopOffset: 0,

        /* updateOnEmptySelection: [boolean]
         * When the __static__ option is true, this enables/disables updating
         * the state of the toolbar buttons even when the selection is collapsed
         * (there is no selection, just a cursor).
         */
        updateOnEmptySelection: false,

        /* relativeContainer: [node]
         * appending the toolbar to a given node instead of body
         */
        relativeContainer: null,

        init: function () {
            MediumEditor.Extension.prototype.init.apply(this, arguments);

            this.initThrottledMethods();

            if (!this.relativeContainer) {
                this.getEditorOption('elementsContainer').appendChild(this.getToolbarElement());
            } else {
                this.relativeContainer.appendChild(this.getToolbarElement());
            }
        },

        // Helper method to execute method for every extension, but ignoring the toolbar extension
        forEachExtension: function (iterator, context) {
            return this.base.extensions.forEach(function (command) {
                if (command === this) {
                    return;
                }
                return iterator.apply(context || this, arguments);
            }, this);
        },

        // Toolbar creation/deletion

        createToolbar: function () {
            var toolbar = this.document.createElement('div');

            toolbar.id = 'medium-editor-toolbar-' + this.getEditorId();
            toolbar.className = 'medium-editor-toolbar';

            if (this.static) {
                toolbar.className += ' static-toolbar';
            } else if (this.relativeContainer) {
                toolbar.className += ' medium-editor-relative-toolbar';
            } else {
                toolbar.className += ' medium-editor-stalker-toolbar';
            }

            toolbar.appendChild(this.createToolbarButtons());

            // Add any forms that extensions may have
            this.forEachExtension(function (extension) {
                if (extension.hasForm) {
                    toolbar.appendChild(extension.getForm());
                }
            });

            this.attachEventHandlers();

            return toolbar;
        },

        createToolbarButtons: function () {
            var ul = this.document.createElement('ul'),
                li,
                btn,
                buttons,
                extension,
                buttonName,
                buttonOpts;

            ul.id = 'medium-editor-toolbar-actions' + this.getEditorId();
            ul.className = 'medium-editor-toolbar-actions';
            ul.style.display = 'block';

            this.buttons.forEach(function (button) {
                if (typeof button === 'string') {
                    buttonName = button;
                    buttonOpts = null;
                } else {
                    buttonName = button.name;
                    buttonOpts = button;
                }

                // If the button already exists as an extension, it'll be returned
                // othwerise it'll create the default built-in button
                extension = this.base.addBuiltInExtension(buttonName, buttonOpts);

                if (extension && typeof extension.getButton === 'function') {
                    btn = extension.getButton(this.base);
                    li = this.document.createElement('li');
                    if (MediumEditor.util.isElement(btn)) {
                        li.appendChild(btn);
                    } else {
                        li.innerHTML = btn;
                    }
                    ul.appendChild(li);
                }
            }, this);

            buttons = ul.querySelectorAll('button');
            if (buttons.length > 0) {
                buttons[0].classList.add(this.firstButtonClass);
                buttons[buttons.length - 1].classList.add(this.lastButtonClass);
            }

            return ul;
        },

        destroy: function () {
            if (this.toolbar) {
                if (this.toolbar.parentNode) {
                    this.toolbar.parentNode.removeChild(this.toolbar);
                }
                delete this.toolbar;
            }
        },

        // Toolbar accessors

        getInteractionElements: function () {
            return this.getToolbarElement();
        },

        getToolbarElement: function () {
            if (!this.toolbar) {
                this.toolbar = this.createToolbar();
            }

            return this.toolbar;
        },

        getToolbarActionsElement: function () {
            return this.getToolbarElement().querySelector('.medium-editor-toolbar-actions');
        },

        // Toolbar event handlers

        initThrottledMethods: function () {
            // throttledPositionToolbar is throttled because:
            // - It will be called when the browser is resizing, which can fire many times very quickly
            // - For some event (like resize) a slight lag in UI responsiveness is OK and provides performance benefits
            this.throttledPositionToolbar = MediumEditor.util.throttle(function () {
                if (this.base.isActive) {
                    this.positionToolbarIfShown();
                }
            }.bind(this));
        },

        attachEventHandlers: function () {
            // MediumEditor custom events for when user beings and ends interaction with a contenteditable and its elements
            this.subscribe('blur', this.handleBlur.bind(this));
            this.subscribe('focus', this.handleFocus.bind(this));

            // Updating the state of the toolbar as things change
            this.subscribe('editableClick', this.handleEditableClick.bind(this));
            this.subscribe('editableKeyup', this.handleEditableKeyup.bind(this));

            // Handle mouseup on document for updating the selection in the toolbar
            this.on(this.document.documentElement, 'mouseup', this.handleDocumentMouseup.bind(this));

            // Add a scroll event for sticky toolbar
            if (this.static && this.sticky) {
                // On scroll (capture), re-position the toolbar
                this.on(this.window, 'scroll', this.handleWindowScroll.bind(this), true);
            }

            // On resize, re-position the toolbar
            this.on(this.window, 'resize', this.handleWindowResize.bind(this));
        },

        handleWindowScroll: function () {
            this.positionToolbarIfShown();
        },

        handleWindowResize: function () {
            this.throttledPositionToolbar();
        },

        handleDocumentMouseup: function (event) {
            // Do not trigger checkState when mouseup fires over the toolbar
            if (event &&
                    event.target &&
                    MediumEditor.util.isDescendant(this.getToolbarElement(), event.target)) {
                return false;
            }
            this.checkState();
        },

        handleEditableClick: function () {
            // Delay the call to checkState to handle bug where selection is empty
            // immediately after clicking inside a pre-existing selection
            setTimeout(function () {
                this.checkState();
            }.bind(this), 0);
        },

        handleEditableKeyup: function () {
            this.checkState();
        },

        handleBlur: function () {
            // Kill any previously delayed calls to hide the toolbar
            clearTimeout(this.hideTimeout);

            // Blur may fire even if we have a selection, so we want to prevent any delayed showToolbar
            // calls from happening in this specific case
            clearTimeout(this.delayShowTimeout);

            // Delay the call to hideToolbar to handle bug with multiple editors on the page at once
            this.hideTimeout = setTimeout(function () {
                this.hideToolbar();
            }.bind(this), 1);
        },

        handleFocus: function () {
            this.checkState();
        },

        // Hiding/showing toolbar

        isDisplayed: function () {
            return this.getToolbarElement().classList.contains('medium-editor-toolbar-active');
        },

        showToolbar: function () {
            clearTimeout(this.hideTimeout);
            if (!this.isDisplayed()) {
                this.getToolbarElement().classList.add('medium-editor-toolbar-active');
                this.trigger('showToolbar', {}, this.base.getFocusedElement());
            }
        },

        hideToolbar: function () {
            if (this.isDisplayed()) {
                this.getToolbarElement().classList.remove('medium-editor-toolbar-active');
                this.trigger('hideToolbar', {}, this.base.getFocusedElement());
            }
        },

        isToolbarDefaultActionsDisplayed: function () {
            return this.getToolbarActionsElement().style.display === 'block';
        },

        hideToolbarDefaultActions: function () {
            if (this.isToolbarDefaultActionsDisplayed()) {
                this.getToolbarActionsElement().style.display = 'none';
            }
        },

        showToolbarDefaultActions: function () {
            this.hideExtensionForms();

            if (!this.isToolbarDefaultActionsDisplayed()) {
                this.getToolbarActionsElement().style.display = 'block';
            }

            // Using setTimeout + options.delay because:
            // We will actually be displaying the toolbar, which should be controlled by options.delay
            this.delayShowTimeout = this.base.delay(function () {
                this.showToolbar();
            }.bind(this));
        },

        hideExtensionForms: function () {
            // Hide all extension forms
            this.forEachExtension(function (extension) {
                if (extension.hasForm && extension.isDisplayed()) {
                    extension.hideForm();
                }
            });
        },

        // Responding to changes in user selection

        // Checks for existance of multiple block elements in the current selection
        multipleBlockElementsSelected: function () {
            var regexEmptyHTMLTags = /<[^\/>][^>]*><\/[^>]+>/gim, // http://stackoverflow.com/questions/3129738/remove-empty-tags-using-regex
                regexBlockElements = new RegExp('<(' + MediumEditor.util.blockContainerElementNames.join('|') + ')[^>]*>', 'g'),
                selectionHTML = MediumEditor.selection.getSelectionHtml(this.document).replace(regexEmptyHTMLTags, ''), // Filter out empty blocks from selection
                hasMultiParagraphs = selectionHTML.match(regexBlockElements); // Find how many block elements are within the html

            return !!hasMultiParagraphs && hasMultiParagraphs.length > 1;
        },

        modifySelection: function () {
            var selection = this.window.getSelection(),
                selectionRange = selection.getRangeAt(0);

            /*
            * In firefox, there are cases (ie doubleclick of a word) where the selectionRange start
            * will be at the very end of an element.  In other browsers, the selectionRange start
            * would instead be at the very beginning of an element that actually has content.
            * example:
            *   <span>foo</span><span>bar</span>
            *
            * If the text 'bar' is selected, most browsers will have the selectionRange start at the beginning
            * of the 'bar' span.  However, there are cases where firefox will have the selectionRange start
            * at the end of the 'foo' span.  The contenteditable behavior will be ok, but if there are any
            * properties on the 'bar' span, they won't be reflected accurately in the toolbar
            * (ie 'Bold' button wouldn't be active)
            *
            * So, for cases where the selectionRange start is at the end of an element/node, find the next
            * adjacent text node that actually has content in it, and move the selectionRange start there.
            */
            if (this.standardizeSelectionStart &&
                    selectionRange.startContainer.nodeValue &&
                    (selectionRange.startOffset === selectionRange.startContainer.nodeValue.length)) {
                var adjacentNode = MediumEditor.util.findAdjacentTextNodeWithContent(MediumEditor.selection.getSelectionElement(this.window), selectionRange.startContainer, this.document);
                if (adjacentNode) {
                    var offset = 0;
                    while (adjacentNode.nodeValue.substr(offset, 1).trim().length === 0) {
                        offset = offset + 1;
                    }
                    selectionRange = MediumEditor.selection.select(this.document, adjacentNode, offset,
                        selectionRange.endContainer, selectionRange.endOffset);
                }
            }
        },

        checkState: function () {
            if (this.base.preventSelectionUpdates) {
                return;
            }

            // If no editable has focus OR selection is inside contenteditable = false
            // hide toolbar
            if (!this.base.getFocusedElement() ||
                    MediumEditor.selection.selectionInContentEditableFalse(this.window)) {
                return this.hideToolbar();
            }

            // If there's no selection element, selection element doesn't belong to this editor
            // or toolbar is disabled for this selection element
            // hide toolbar
            var selectionElement = MediumEditor.selection.getSelectionElement(this.window);
            if (!selectionElement ||
                    this.getEditorElements().indexOf(selectionElement) === -1 ||
                    selectionElement.getAttribute('data-disable-toolbar')) {
                return this.hideToolbar();
            }

            // Now we know there's a focused editable with a selection

            // If the updateOnEmptySelection option is true, show the toolbar
            if (this.updateOnEmptySelection && this.static) {
                return this.showAndUpdateToolbar();
            }

            // If we don't have a 'valid' selection -> hide toolbar
            if (!MediumEditor.selection.selectionContainsContent(this.document) ||
                (this.allowMultiParagraphSelection === false && this.multipleBlockElementsSelected())) {
                return this.hideToolbar();
            }

            this.showAndUpdateToolbar();
        },

        // Updating the toolbar

        showAndUpdateToolbar: function () {
            this.modifySelection();
            this.setToolbarButtonStates();
            this.trigger('positionToolbar', {}, this.base.getFocusedElement());
            this.showToolbarDefaultActions();
            this.setToolbarPosition();
        },

        setToolbarButtonStates: function () {
            this.forEachExtension(function (extension) {
                if (typeof extension.isActive === 'function' &&
                    typeof extension.setInactive === 'function') {
                    extension.setInactive();
                }
            });

            this.checkActiveButtons();
        },

        checkActiveButtons: function () {
            var manualStateChecks = [],
                queryState = null,
                selectionRange = MediumEditor.selection.getSelectionRange(this.document),
                parentNode,
                updateExtensionState = function (extension) {
                    if (typeof extension.checkState === 'function') {
                        extension.checkState(parentNode);
                    } else if (typeof extension.isActive === 'function' &&
                               typeof extension.isAlreadyApplied === 'function' &&
                               typeof extension.setActive === 'function') {
                        if (!extension.isActive() && extension.isAlreadyApplied(parentNode)) {
                            extension.setActive();
                        }
                    }
                };

            if (!selectionRange) {
                return;
            }

            // Loop through all extensions
            this.forEachExtension(function (extension) {
                // For those extensions where we can use document.queryCommandState(), do so
                if (typeof extension.queryCommandState === 'function') {
                    queryState = extension.queryCommandState();
                    // If queryCommandState returns a valid value, we can trust the browser
                    // and don't need to do our manual checks
                    if (queryState !== null) {
                        if (queryState && typeof extension.setActive === 'function') {
                            extension.setActive();
                        }
                        return;
                    }
                }
                // We can't use queryCommandState for this extension, so add to manualStateChecks
                manualStateChecks.push(extension);
            });

            parentNode = MediumEditor.selection.getSelectedParentElement(selectionRange);

            // Make sure the selection parent isn't outside of the contenteditable
            if (!this.getEditorElements().some(function (element) {
                    return MediumEditor.util.isDescendant(element, parentNode, true);
                })) {
                return;
            }

            // Climb up the DOM and do manual checks for whether a certain extension is currently enabled for this node
            while (parentNode) {
                manualStateChecks.forEach(updateExtensionState);

                // we can abort the search upwards if we leave the contentEditable element
                if (MediumEditor.util.isMediumEditorElement(parentNode)) {
                    break;
                }
                parentNode = parentNode.parentNode;
            }
        },

        // Positioning toolbar

        positionToolbarIfShown: function () {
            if (this.isDisplayed()) {
                this.setToolbarPosition();
            }
        },

        setToolbarPosition: function () {
            var container = this.base.getFocusedElement(),
                selection = this.window.getSelection();

            // If there isn't a valid selection, bail
            if (!container) {
                return this;
            }

            if (this.static || !selection.isCollapsed) {
                this.showToolbar();

                // we don't need any absolute positioning if relativeContainer is set
                if (!this.relativeContainer) {
                    if (this.static) {
                        this.positionStaticToolbar(container);
                    } else {
                        this.positionToolbar(selection);
                    }
                }

                this.trigger('positionedToolbar', {}, this.base.getFocusedElement());
            }
        },

        positionStaticToolbar: function (container) {
            // position the toolbar at left 0, so we can get the real width of the toolbar
            this.getToolbarElement().style.left = '0';

            // document.documentElement for IE 9
            var scrollTop = (this.document.documentElement && this.document.documentElement.scrollTop) || this.document.body.scrollTop,
                windowWidth = this.window.innerWidth,
                toolbarElement = this.getToolbarElement(),
                containerRect = container.getBoundingClientRect(),
                containerTop = containerRect.top + scrollTop,
                containerCenter = (containerRect.left + (containerRect.width / 2)),
                toolbarHeight = toolbarElement.offsetHeight,
                toolbarWidth = toolbarElement.offsetWidth,
                halfOffsetWidth = toolbarWidth / 2,
                targetLeft;

            if (this.sticky) {
                // If it's beyond the height of the editor, position it at the bottom of the editor
                if (scrollTop > (containerTop + container.offsetHeight - toolbarHeight - this.stickyTopOffset)) {
                    toolbarElement.style.top = (containerTop + container.offsetHeight - toolbarHeight) + 'px';
                    toolbarElement.classList.remove('medium-editor-sticky-toolbar');
                // Stick the toolbar to the top of the window
                } else if (scrollTop > (containerTop - toolbarHeight - this.stickyTopOffset)) {
                    toolbarElement.classList.add('medium-editor-sticky-toolbar');
                    toolbarElement.style.top = this.stickyTopOffset + 'px';
                // Normal static toolbar position
                } else {
                    toolbarElement.classList.remove('medium-editor-sticky-toolbar');
                    toolbarElement.style.top = containerTop - toolbarHeight + 'px';
                }
            } else {
                toolbarElement.style.top = containerTop - toolbarHeight + 'px';
            }

            switch (this.align) {
                case 'left':
                    targetLeft = containerRect.left;
                    break;

                case 'right':
                    targetLeft = containerRect.right - toolbarWidth;
                    break;

                case 'center':
                    targetLeft = containerCenter - halfOffsetWidth;
                    break;
            }

            if (targetLeft < 0) {
                targetLeft = 0;
            } else if ((targetLeft + toolbarWidth) > windowWidth) {
                targetLeft = (windowWidth - Math.ceil(toolbarWidth) - 1);
            }

            toolbarElement.style.left = targetLeft + 'px';
        },

        positionToolbar: function (selection) {
            // position the toolbar at left 0, so we can get the real width of the toolbar
            this.getToolbarElement().style.left = '0';
            this.getToolbarElement().style.right = 'initial';

            var range = selection.getRangeAt(0),
                boundary = range.getBoundingClientRect();

            // Handle selections with just images
            if (!boundary || ((boundary.height === 0 && boundary.width === 0) && range.startContainer === range.endContainer)) {
                // If there's a nested image, use that for the bounding rectangle
                if (range.startContainer.nodeType === 1 && range.startContainer.querySelector('img')) {
                    boundary = range.startContainer.querySelector('img').getBoundingClientRect();
                } else {
                    boundary = range.startContainer.getBoundingClientRect();
                }
            }

            var containerWidth = this.window.innerWidth,
                toolbarElement = this.getToolbarElement(),
                toolbarHeight = toolbarElement.offsetHeight,
                toolbarWidth = toolbarElement.offsetWidth,
                halfOffsetWidth = toolbarWidth / 2,
                buttonHeight = 50,
                defaultLeft = this.diffLeft - halfOffsetWidth,
                elementsContainer = this.getEditorOption('elementsContainer'),
                elementsContainerAbsolute = ['absolute', 'fixed'].indexOf(window.getComputedStyle(elementsContainer).getPropertyValue('position')) > -1,
                positions = {},
                relativeBoundary = {},
                middleBoundary, elementsContainerBoundary;

            // If container element is absolute / fixed, recalculate boundaries to be relative to the container
            if (elementsContainerAbsolute) {
                elementsContainerBoundary = elementsContainer.getBoundingClientRect();
                ['top', 'left'].forEach(function (key) {
                    relativeBoundary[key] = boundary[key] - elementsContainerBoundary[key];
                });

                relativeBoundary.width = boundary.width;
                relativeBoundary.height = boundary.height;
                boundary = relativeBoundary;

                containerWidth = elementsContainerBoundary.width;

                // Adjust top position according to container scroll position
                positions.top = elementsContainer.scrollTop;
            } else {
                // Adjust top position according to window scroll position
                positions.top = this.window.pageYOffset;
            }

            middleBoundary = boundary.left + boundary.width / 2;
            positions.top += boundary.top - toolbarHeight;

            if (boundary.top < buttonHeight) {
                toolbarElement.classList.add('medium-toolbar-arrow-over');
                toolbarElement.classList.remove('medium-toolbar-arrow-under');
                positions.top += buttonHeight + boundary.height - this.diffTop;
            } else {
                toolbarElement.classList.add('medium-toolbar-arrow-under');
                toolbarElement.classList.remove('medium-toolbar-arrow-over');
                positions.top += this.diffTop;
            }

            if (middleBoundary < halfOffsetWidth) {
                positions.left = defaultLeft + halfOffsetWidth;
                positions.right = 'initial';
            } else if ((containerWidth - middleBoundary) < halfOffsetWidth) {
                positions.left = 'auto';
                positions.right = 0;
            } else {
                positions.left = defaultLeft + middleBoundary;
                positions.right = 'initial';
            }

            ['top', 'left', 'right'].forEach(function (key) {
                toolbarElement.style[key] = positions[key] + (isNaN(positions[key]) ? '' : 'px');
            });
        }
    });

    MediumEditor.extensions.toolbar = Toolbar;
}());

(function () {
    'use strict';

    var ImageDragging = MediumEditor.Extension.extend({
        init: function () {
            MediumEditor.Extension.prototype.init.apply(this, arguments);

            this.subscribe('editableDrag', this.handleDrag.bind(this));
            this.subscribe('editableDrop', this.handleDrop.bind(this));
        },

        handleDrag: function (event) {
            var className = 'medium-editor-dragover';
            event.preventDefault();
            event.dataTransfer.dropEffect = 'copy';

            if (event.type === 'dragover') {
                event.target.classList.add(className);
            } else if (event.type === 'dragleave') {
                event.target.classList.remove(className);
            }
        },

        handleDrop: function (event) {
            var className = 'medium-editor-dragover',
                files;
            event.preventDefault();
            event.stopPropagation();

            // IE9 does not support the File API, so prevent file from opening in a new window
            // but also don't try to actually get the file
            if (event.dataTransfer.files) {
                files = Array.prototype.slice.call(event.dataTransfer.files, 0);
                files.some(function (file) {
                    if (file.type.match('image')) {
                        var fileReader, id;
                        fileReader = new FileReader();
                        fileReader.readAsDataURL(file);

                        id = 'medium-img-' + (+new Date());
                        MediumEditor.util.insertHTMLCommand(this.document, '<img class="medium-editor-image-loading" id="' + id + '" />');

                        fileReader.onload = function () {
                            var img = this.document.getElementById(id);
                            if (img) {
                                img.removeAttribute('id');
                                img.removeAttribute('class');
                                img.src = fileReader.result;
                            }
                        }.bind(this);
                    }
                }.bind(this));
            }
            event.target.classList.remove(className);
        }
    });

    MediumEditor.extensions.imageDragging = ImageDragging;
}());

(function () {
    'use strict';

    // Event handlers that shouldn't be exposed externally

    function handleDisableExtraSpaces(event) {
        var node = MediumEditor.selection.getSelectionStart(this.options.ownerDocument),
            textContent = node.textContent,
            caretPositions = MediumEditor.selection.getCaretOffsets(node);

        if ((textContent[caretPositions.left - 1] === undefined) || (textContent[caretPositions.left - 1].trim() === '') || (textContent[caretPositions.left] !== undefined && textContent[caretPositions.left].trim() === '')) {
            event.preventDefault();
        }
    }

    function handleDisabledEnterKeydown(event, element) {
        if (this.options.disableReturn || element.getAttribute('data-disable-return')) {
            event.preventDefault();
        } else if (this.options.disableDoubleReturn || element.getAttribute('data-disable-double-return')) {
            var node = MediumEditor.selection.getSelectionStart(this.options.ownerDocument);

            // if current text selection is empty OR previous sibling text is empty OR it is not a list
            if ((node && node.textContent.trim() === '' && node.nodeName.toLowerCase() !== 'li') ||
                (node.previousElementSibling && node.previousElementSibling.nodeName.toLowerCase() !== 'br' &&
                 node.previousElementSibling.textContent.trim() === '')) {
                event.preventDefault();
            }
        }
    }

    function handleTabKeydown(event) {
        // Override tab only for pre nodes
        var node = MediumEditor.selection.getSelectionStart(this.options.ownerDocument),
            tag = node && node.nodeName.toLowerCase();

        if (tag === 'pre') {
            event.preventDefault();
            MediumEditor.util.insertHTMLCommand(this.options.ownerDocument, '    ');
        }

        // Tab to indent list structures!
        if (MediumEditor.util.isListItem(node)) {
            event.preventDefault();

            // If Shift is down, outdent, otherwise indent
            if (event.shiftKey) {
                this.options.ownerDocument.execCommand('outdent', false, null);
            } else {
                this.options.ownerDocument.execCommand('indent', false, null);
            }
        }
    }

    function handleBlockDeleteKeydowns(event) {
        var p, node = MediumEditor.selection.getSelectionStart(this.options.ownerDocument),
            tagName = node.nodeName.toLowerCase(),
            isEmpty = /^(\s+|<br\/?>)?$/i,
            isHeader = /h\d/i;

        if (MediumEditor.util.isKey(event, [MediumEditor.util.keyCode.BACKSPACE, MediumEditor.util.keyCode.ENTER]) &&
                // has a preceeding sibling
                node.previousElementSibling &&
                // in a header
                isHeader.test(tagName) &&
                // at the very end of the block
                MediumEditor.selection.getCaretOffsets(node).left === 0) {
            if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.BACKSPACE) && isEmpty.test(node.previousElementSibling.innerHTML)) {
                // backspacing the begining of a header into an empty previous element will
                // change the tagName of the current node to prevent one
                // instead delete previous node and cancel the event.
                node.previousElementSibling.parentNode.removeChild(node.previousElementSibling);
                event.preventDefault();
            } else if (!this.options.disableDoubleReturn && MediumEditor.util.isKey(event, MediumEditor.util.keyCode.ENTER)) {
                // hitting return in the begining of a header will create empty header elements before the current one
                // instead, make "<p><br></p>" element, which are what happens if you hit return in an empty paragraph
                p = this.options.ownerDocument.createElement('p');
                p.innerHTML = '<br>';
                node.previousElementSibling.parentNode.insertBefore(p, node);
                event.preventDefault();
            }
        } else if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.DELETE) &&
                    // between two sibling elements
                    node.nextElementSibling &&
                    node.previousElementSibling &&
                    // not in a header
                    !isHeader.test(tagName) &&
                    // in an empty tag
                    isEmpty.test(node.innerHTML) &&
                    // when the next tag *is* a header
                    isHeader.test(node.nextElementSibling.nodeName.toLowerCase())) {
            // hitting delete in an empty element preceding a header, ex:
            //  <p>[CURSOR]</p><h1>Header</h1>
            // Will cause the h1 to become a paragraph.
            // Instead, delete the paragraph node and move the cursor to the begining of the h1

            // remove node and move cursor to start of header
            MediumEditor.selection.moveCursor(this.options.ownerDocument, node.nextElementSibling);

            node.previousElementSibling.parentNode.removeChild(node);

            event.preventDefault();
        } else if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.BACKSPACE) &&
                tagName === 'li' &&
                // hitting backspace inside an empty li
                isEmpty.test(node.innerHTML) &&
                // is first element (no preceeding siblings)
                !node.previousElementSibling &&
                // parent also does not have a sibling
                !node.parentElement.previousElementSibling &&
                // is not the only li in a list
                node.nextElementSibling &&
                node.nextElementSibling.nodeName.toLowerCase() === 'li') {
            // backspacing in an empty first list element in the first list (with more elements) ex:
            //  <ul><li>[CURSOR]</li><li>List Item 2</li></ul>
            // will remove the first <li> but add some extra element before (varies based on browser)
            // Instead, this will:
            // 1) remove the list element
            // 2) create a paragraph before the list
            // 3) move the cursor into the paragraph

            // create a paragraph before the list
            p = this.options.ownerDocument.createElement('p');
            p.innerHTML = '<br>';
            node.parentElement.parentElement.insertBefore(p, node.parentElement);

            // move the cursor into the new paragraph
            MediumEditor.selection.moveCursor(this.options.ownerDocument, p);

            // remove the list element
            node.parentElement.removeChild(node);

            event.preventDefault();
        } else if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.BACKSPACE) &&
                (MediumEditor.util.getClosestTag(node, 'blockquote') !== false) &&
                MediumEditor.selection.getCaretOffsets(node).left === 0) {

            // when cursor is at the begining of the element and the element is <blockquote>
            // then pressing backspace key should change the <blockquote> to a <p> tag
            event.preventDefault();
            MediumEditor.util.execFormatBlock(this.options.ownerDocument, 'p');
        } else if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.ENTER) &&
                (MediumEditor.util.getClosestTag(node, 'blockquote') !== false) &&
                MediumEditor.selection.getCaretOffsets(node).right === 0) {

            // when cursor is at the end of <blockquote>,
            // then pressing enter key should create <p> tag, not <blockquote>
            p = this.options.ownerDocument.createElement('p');
            p.innerHTML = '<br>';
            node.parentElement.insertBefore(p, node.nextSibling);

            // move the cursor into the new paragraph
            MediumEditor.selection.moveCursor(this.options.ownerDocument, p);

            event.preventDefault();
        } else if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.BACKSPACE) &&
                MediumEditor.util.isMediumEditorElement(node.parentElement) &&
                !node.previousElementSibling &&
                node.nextElementSibling &&
                isEmpty.test(node.innerHTML)) {

            // when cursor is in the first element, it's empty and user presses backspace,
            // do delete action instead to get rid of the first element and move caret to 2nd
            event.preventDefault();
            MediumEditor.selection.moveCursor(this.options.ownerDocument, node.nextSibling);
            node.parentElement.removeChild(node);
        }
    }

    function handleKeyup(event) {
        var node = MediumEditor.selection.getSelectionStart(this.options.ownerDocument),
            tagName;

        if (!node) {
            return;
        }

        // https://github.com/yabwe/medium-editor/issues/994
        // Firefox thrown an error when calling `formatBlock` on an empty editable blockContainer that's not a <div>
        if (MediumEditor.util.isMediumEditorElement(node) && node.children.length === 0 && !MediumEditor.util.isBlockContainer(node)) {
            this.options.ownerDocument.execCommand('formatBlock', false, 'p');
        }

        // https://github.com/yabwe/medium-editor/issues/834
        // https://github.com/yabwe/medium-editor/pull/382
        // Don't call format block if this is a block element (ie h1, figCaption, etc.)
        if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.ENTER) &&
            !MediumEditor.util.isListItem(node) &&
            !MediumEditor.util.isBlockContainer(node)) {

            tagName = node.nodeName.toLowerCase();
            // For anchor tags, unlink
            if (tagName === 'a') {
                this.options.ownerDocument.execCommand('unlink', false, null);
            } else if (!event.shiftKey && !event.ctrlKey) {
                this.options.ownerDocument.execCommand('formatBlock', false, 'p');
            }
        }
    }

    function handleEditableInput(event, editable) {
        var textarea = editable.parentNode.querySelector('textarea[medium-editor-textarea-id="' + editable.getAttribute('medium-editor-textarea-id') + '"]');
        if (textarea) {
            textarea.value = editable.innerHTML.trim();
        }
    }

    // Internal helper methods which shouldn't be exposed externally

    function addToEditors(win) {
        if (!win._mediumEditors) {
            // To avoid breaking users who are assuming that the unique id on
            // medium-editor elements will start at 1, inserting a 'null' in the
            // array so the unique-id can always map to the index of the editor instance
            win._mediumEditors = [null];
        }

        // If this already has a unique id, re-use it
        if (!this.id) {
            this.id = win._mediumEditors.length;
        }

        win._mediumEditors[this.id] = this;
    }

    function removeFromEditors(win) {
        if (!win._mediumEditors || !win._mediumEditors[this.id]) {
            return;
        }

        /* Setting the instance to null in the array instead of deleting it allows:
         * 1) Each instance to preserve its own unique-id, even after being destroyed
         *    and initialized again
         * 2) The unique-id to always correspond to an index in the array of medium-editor
         *    instances. Thus, we will be able to look at a contenteditable, and determine
         *    which instance it belongs to, by indexing into the global array.
         */
        win._mediumEditors[this.id] = null;
    }

    function createElementsArray(selector, doc, filterEditorElements) {
        var elements = [];

        if (!selector) {
            selector = [];
        }
        // If string, use as query selector
        if (typeof selector === 'string') {
            selector = doc.querySelectorAll(selector);
        }
        // If element, put into array
        if (MediumEditor.util.isElement(selector)) {
            selector = [selector];
        }

        if (filterEditorElements) {
            // Remove elements that have already been initialized by the editor
            // selecotr might not be an array (ie NodeList) so use for loop
            for (var i = 0; i < selector.length; i++) {
                var el = selector[i];
                if (MediumEditor.util.isElement(el) &&
                    !el.getAttribute('data-medium-editor-element') &&
                    !el.getAttribute('medium-editor-textarea-id')) {
                    elements.push(el);
                }
            }
        } else {
            // Convert NodeList (or other array like object) into an array
            elements = Array.prototype.slice.apply(selector);
        }

        return elements;
    }

    function cleanupTextareaElement(element) {
        var textarea = element.parentNode.querySelector('textarea[medium-editor-textarea-id="' + element.getAttribute('medium-editor-textarea-id') + '"]');
        if (textarea) {
            // Un-hide the textarea
            textarea.classList.remove('medium-editor-hidden');
            textarea.removeAttribute('medium-editor-textarea-id');
        }
        if (element.parentNode) {
            element.parentNode.removeChild(element);
        }
    }

    function setExtensionDefaults(extension, defaults) {
        Object.keys(defaults).forEach(function (prop) {
            if (extension[prop] === undefined) {
                extension[prop] = defaults[prop];
            }
        });
        return extension;
    }

    function initExtension(extension, name, instance) {
        var extensionDefaults = {
            'window': instance.options.contentWindow,
            'document': instance.options.ownerDocument,
            'base': instance
        };

        // Add default options into the extension
        extension = setExtensionDefaults(extension, extensionDefaults);

        // Call init on the extension
        if (typeof extension.init === 'function') {
            extension.init();
        }

        // Set extension name (if not already set)
        if (!extension.name) {
            extension.name = name;
        }
        return extension;
    }

    function isToolbarEnabled() {
        // If any of the elements don't have the toolbar disabled
        // We need a toolbar
        if (this.elements.every(function (element) {
                return !!element.getAttribute('data-disable-toolbar');
            })) {
            return false;
        }

        return this.options.toolbar !== false;
    }

    function isAnchorPreviewEnabled() {
        // If toolbar is disabled, don't add
        if (!isToolbarEnabled.call(this)) {
            return false;
        }

        return this.options.anchorPreview !== false;
    }

    function isPlaceholderEnabled() {
        return this.options.placeholder !== false;
    }

    function isAutoLinkEnabled() {
        return this.options.autoLink !== false;
    }

    function isImageDraggingEnabled() {
        return this.options.imageDragging !== false;
    }

    function isKeyboardCommandsEnabled() {
        return this.options.keyboardCommands !== false;
    }

    function shouldUseFileDraggingExtension() {
        // Since the file-dragging extension replaces the image-dragging extension,
        // we need to check if the user passed an overrided image-dragging extension.
        // If they have, to avoid breaking users, we won't use file-dragging extension.
        return !this.options.extensions['imageDragging'];
    }

    function createContentEditable(textarea) {
        var div = this.options.ownerDocument.createElement('div'),
            now = Date.now(),
            uniqueId = 'medium-editor-' + now,
            atts = textarea.attributes;

        // Some browsers can move pretty fast, since we're using a timestamp
        // to make a unique-id, ensure that the id is actually unique on the page
        while (this.options.ownerDocument.getElementById(uniqueId)) {
            now++;
            uniqueId = 'medium-editor-' + now;
        }

        div.className = textarea.className;
        div.id = uniqueId;
        div.innerHTML = textarea.value;

        textarea.setAttribute('medium-editor-textarea-id', uniqueId);

        // re-create all attributes from the textearea to the new created div
        for (var i = 0, n = atts.length; i < n; i++) {
            // do not re-create existing attributes
            if (!div.hasAttribute(atts[i].nodeName)) {
                div.setAttribute(atts[i].nodeName, atts[i].nodeValue);
            }
        }

        // If textarea has a form, listen for reset on the form to clear
        // the content of the created div
        if (textarea.form) {
            this.on(textarea.form, 'reset', function (event) {
                if (!event.defaultPrevented) {
                    this.resetContent(this.options.ownerDocument.getElementById(uniqueId));
                }
            }.bind(this));
        }

        textarea.classList.add('medium-editor-hidden');
        textarea.parentNode.insertBefore(
            div,
            textarea
        );

        return div;
    }

    function initElement(element, editorId) {
        if (!element.getAttribute('data-medium-editor-element')) {
            if (element.nodeName.toLowerCase() === 'textarea') {
                element = createContentEditable.call(this, element);

                // Make sure we only attach to editableInput once for <textarea> elements
                if (!this.instanceHandleEditableInput) {
                    this.instanceHandleEditableInput = handleEditableInput.bind(this);
                    this.subscribe('editableInput', this.instanceHandleEditableInput);
                }
            }

            if (!this.options.disableEditing && !element.getAttribute('data-disable-editing')) {
                element.setAttribute('contentEditable', true);
                element.setAttribute('spellcheck', this.options.spellcheck);
            }

            // Make sure we only attach to editableKeydownEnter once for disable-return options
            if (!this.instanceHandleEditableKeydownEnter) {
                if (element.getAttribute('data-disable-return') || element.getAttribute('data-disable-double-return')) {
                    this.instanceHandleEditableKeydownEnter = handleDisabledEnterKeydown.bind(this);
                    this.subscribe('editableKeydownEnter', this.instanceHandleEditableKeydownEnter);
                }
            }

            // if we're not disabling return, add a handler to help handle cleanup
            // for certain cases when enter is pressed
            if (!this.options.disableReturn && !element.getAttribute('data-disable-return')) {
                this.on(element, 'keyup', handleKeyup.bind(this));
            }

            var elementId = MediumEditor.util.guid();

            element.setAttribute('data-medium-editor-element', true);
            element.classList.add('medium-editor-element');
            element.setAttribute('role', 'textbox');
            element.setAttribute('aria-multiline', true);
            element.setAttribute('data-medium-editor-editor-index', editorId);
            // TODO: Merge data-medium-editor-element and medium-editor-index attributes for 6.0.0
            // medium-editor-index is not named correctly anymore and can be re-purposed to signify
            // whether the element has been initialized or not
            element.setAttribute('medium-editor-index', elementId);
            initialContent[elementId] = element.innerHTML;

            this.events.attachAllEventsToElement(element);
        }

        return element;
    }

    function attachHandlers() {
        // attach to tabs
        this.subscribe('editableKeydownTab', handleTabKeydown.bind(this));

        // Bind keys which can create or destroy a block element: backspace, delete, return
        this.subscribe('editableKeydownDelete', handleBlockDeleteKeydowns.bind(this));
        this.subscribe('editableKeydownEnter', handleBlockDeleteKeydowns.bind(this));

        // Bind double space event
        if (this.options.disableExtraSpaces) {
            this.subscribe('editableKeydownSpace', handleDisableExtraSpaces.bind(this));
        }

        // Make sure we only attach to editableKeydownEnter once for disable-return options
        if (!this.instanceHandleEditableKeydownEnter) {
            // disabling return or double return
            if (this.options.disableReturn || this.options.disableDoubleReturn) {
                this.instanceHandleEditableKeydownEnter = handleDisabledEnterKeydown.bind(this);
                this.subscribe('editableKeydownEnter', this.instanceHandleEditableKeydownEnter);
            }
        }
    }

    function initExtensions() {

        this.extensions = [];

        // Passed in extensions
        Object.keys(this.options.extensions).forEach(function (name) {
            // Always save the toolbar extension for last
            if (name !== 'toolbar' && this.options.extensions[name]) {
                this.extensions.push(initExtension(this.options.extensions[name], name, this));
            }
        }, this);

        // 4 Cases for imageDragging + fileDragging extensons:
        //
        // 1. ImageDragging ON + No Custom Image Dragging Extension:
        //    * Use fileDragging extension (default options)
        // 2. ImageDragging OFF + No Custom Image Dragging Extension:
        //    * Use fileDragging extension w/ images turned off
        // 3. ImageDragging ON + Custom Image Dragging Extension:
        //    * Don't use fileDragging (could interfere with custom image dragging extension)
        // 4. ImageDragging OFF + Custom Image Dragging:
        //    * Don't use fileDragging (could interfere with custom image dragging extension)
        if (shouldUseFileDraggingExtension.call(this)) {
            var opts = this.options.fileDragging;
            if (!opts) {
                opts = {};

                // Image is in the 'allowedTypes' list by default.
                // If imageDragging is off override the 'allowedTypes' list with an empty one
                if (!isImageDraggingEnabled.call(this)) {
                    opts.allowedTypes = [];
                }
            }
            this.addBuiltInExtension('fileDragging', opts);
        }

        // Built-in extensions
        var builtIns = {
            paste: true,
            'anchor-preview': isAnchorPreviewEnabled.call(this),
            autoLink: isAutoLinkEnabled.call(this),
            keyboardCommands: isKeyboardCommandsEnabled.call(this),
            placeholder: isPlaceholderEnabled.call(this)
        };
        Object.keys(builtIns).forEach(function (name) {
            if (builtIns[name]) {
                this.addBuiltInExtension(name);
            }
        }, this);

        // Users can pass in a custom toolbar extension
        // so check for that first and if it's not present
        // just create the default toolbar
        var toolbarExtension = this.options.extensions['toolbar'];
        if (!toolbarExtension && isToolbarEnabled.call(this)) {
            // Backwards compatability
            var toolbarOptions = MediumEditor.util.extend({}, this.options.toolbar, {
                allowMultiParagraphSelection: this.options.allowMultiParagraphSelection // deprecated
            });
            toolbarExtension = new MediumEditor.extensions.toolbar(toolbarOptions);
        }

        // If the toolbar is not disabled, so we actually have an extension
        // initialize it and add it to the extensions array
        if (toolbarExtension) {
            this.extensions.push(initExtension(toolbarExtension, 'toolbar', this));
        }
    }

    function mergeOptions(defaults, options) {
        var deprecatedProperties = [
            ['allowMultiParagraphSelection', 'toolbar.allowMultiParagraphSelection']
        ];
        // warn about using deprecated properties
        if (options) {
            deprecatedProperties.forEach(function (pair) {
                if (options.hasOwnProperty(pair[0]) && options[pair[0]] !== undefined) {
                    MediumEditor.util.deprecated(pair[0], pair[1], 'v6.0.0');
                }
            });
        }

        return MediumEditor.util.defaults({}, options, defaults);
    }

    function execActionInternal(action, opts) {
        /*jslint regexp: true*/
        var appendAction = /^append-(.+)$/gi,
            justifyAction = /justify([A-Za-z]*)$/g, /* Detecting if is justifyCenter|Right|Left */
            match,
            cmdValueArgument;
        /*jslint regexp: false*/

        // Actions starting with 'append-' should attempt to format a block of text ('formatBlock') using a specific
        // type of block element (ie append-blockquote, append-h1, append-pre, etc.)
        match = appendAction.exec(action);
        if (match) {
            return MediumEditor.util.execFormatBlock(this.options.ownerDocument, match[1]);
        }

        if (action === 'fontSize') {
            // TODO: Deprecate support for opts.size in 6.0.0
            if (opts.size) {
                MediumEditor.util.deprecated('.size option for fontSize command', '.value', '6.0.0');
            }
            cmdValueArgument = opts.value || opts.size;
            return this.options.ownerDocument.execCommand('fontSize', false, cmdValueArgument);
        }

        if (action === 'fontName') {
            // TODO: Deprecate support for opts.name in 6.0.0
            if (opts.name) {
                MediumEditor.util.deprecated('.name option for fontName command', '.value', '6.0.0');
            }
            cmdValueArgument = opts.value || opts.name;
            return this.options.ownerDocument.execCommand('fontName', false, cmdValueArgument);
        }

        if (action === 'createLink') {
            return this.createLink(opts);
        }

        if (action === 'image') {
            var src = this.options.contentWindow.getSelection().toString().trim();
            return this.options.ownerDocument.execCommand('insertImage', false, src);
        }

        if (action === 'html') {
            var html = this.options.contentWindow.getSelection().toString().trim();
            return MediumEditor.util.insertHTMLCommand(this.options.ownerDocument, html);
        }

        /* Issue: https://github.com/yabwe/medium-editor/issues/595
         * If the action is to justify the text */
        if (justifyAction.exec(action)) {
            var result = this.options.ownerDocument.execCommand(action, false, null),
                parentNode = MediumEditor.selection.getSelectedParentElement(MediumEditor.selection.getSelectionRange(this.options.ownerDocument));
            if (parentNode) {
                cleanupJustifyDivFragments.call(this, MediumEditor.util.getTopBlockContainer(parentNode));
            }

            return result;
        }

        cmdValueArgument = opts && opts.value;
        return this.options.ownerDocument.execCommand(action, false, cmdValueArgument);
    }

    /* If we've just justified text within a container block
     * Chrome may have removed <br> elements and instead wrapped lines in <div> elements
     * with a text-align property.  If so, we want to fix this
     */
    function cleanupJustifyDivFragments(blockContainer) {
        if (!blockContainer) {
            return;
        }

        var textAlign,
            childDivs = Array.prototype.slice.call(blockContainer.childNodes).filter(function (element) {
                var isDiv = element.nodeName.toLowerCase() === 'div';
                if (isDiv && !textAlign) {
                    textAlign = element.style.textAlign;
                }
                return isDiv;
            });

        /* If we found child <div> elements with text-align style attributes
         * we should fix this by:
         *
         * 1) Unwrapping each <div> which has a text-align style
         * 2) Insert a <br> element after each set of 'unwrapped' div children
         * 3) Set the text-align style of the parent block element
         */
        if (childDivs.length) {
            // Since we're mucking with the HTML, preserve selection
            this.saveSelection();
            childDivs.forEach(function (div) {
                if (div.style.textAlign === textAlign) {
                    var lastChild = div.lastChild;
                    if (lastChild) {
                        // Instead of a div, extract the child elements and add a <br>
                        MediumEditor.util.unwrap(div, this.options.ownerDocument);
                        var br = this.options.ownerDocument.createElement('BR');
                        lastChild.parentNode.insertBefore(br, lastChild.nextSibling);
                    }
                }
            }, this);
            blockContainer.style.textAlign = textAlign;
            // We're done, so restore selection
            this.restoreSelection();
        }
    }

    var initialContent = {};

    MediumEditor.prototype = {
        // NOT DOCUMENTED - exposed for backwards compatability
        init: function (elements, options) {
            this.options = mergeOptions.call(this, this.defaults, options);
            this.origElements = elements;

            if (!this.options.elementsContainer) {
                this.options.elementsContainer = this.options.ownerDocument.body;
            }

            return this.setup();
        },

        setup: function () {
            if (this.isActive) {
                return;
            }

            addToEditors.call(this, this.options.contentWindow);
            this.events = new MediumEditor.Events(this);
            this.elements = [];

            this.addElements(this.origElements);

            if (this.elements.length === 0) {
                return;
            }

            this.isActive = true;

            // Call initialization helpers
            initExtensions.call(this);
            attachHandlers.call(this);
        },

        destroy: function () {
            if (!this.isActive) {
                return;
            }

            this.isActive = false;

            this.extensions.forEach(function (extension) {
                if (typeof extension.destroy === 'function') {
                    extension.destroy();
                }
            }, this);

            this.events.destroy();

            this.elements.forEach(function (element) {
                // Reset elements content, fix for issue where after editor destroyed the red underlines on spelling errors are left
                if (this.options.spellcheck) {
                    element.innerHTML = element.innerHTML;
                }

                // cleanup extra added attributes
                element.removeAttribute('contentEditable');
                element.removeAttribute('spellcheck');
                element.removeAttribute('data-medium-editor-element');
                element.classList.remove('medium-editor-element');
                element.removeAttribute('role');
                element.removeAttribute('aria-multiline');
                element.removeAttribute('medium-editor-index');
                element.removeAttribute('data-medium-editor-editor-index');

                // Remove any elements created for textareas
                if (element.getAttribute('medium-editor-textarea-id')) {
                    cleanupTextareaElement(element);
                }
            }, this);
            this.elements = [];
            this.instanceHandleEditableKeydownEnter = null;
            this.instanceHandleEditableInput = null;

            removeFromEditors.call(this, this.options.contentWindow);
        },

        on: function (target, event, listener, useCapture) {
            this.events.attachDOMEvent(target, event, listener, useCapture);

            return this;
        },

        off: function (target, event, listener, useCapture) {
            this.events.detachDOMEvent(target, event, listener, useCapture);

            return this;
        },

        subscribe: function (event, listener) {
            this.events.attachCustomEvent(event, listener);

            return this;
        },

        unsubscribe: function (event, listener) {
            this.events.detachCustomEvent(event, listener);

            return this;
        },

        trigger: function (name, data, editable) {
            this.events.triggerCustomEvent(name, data, editable);

            return this;
        },

        delay: function (fn) {
            var self = this;
            return setTimeout(function () {
                if (self.isActive) {
                    fn();
                }
            }, this.options.delay);
        },

        serialize: function () {
            var i,
                elementid,
                content = {},
                len = this.elements.length;

            for (i = 0; i < len; i += 1) {
                elementid = (this.elements[i].id !== '') ? this.elements[i].id : 'element-' + i;
                content[elementid] = {
                    value: this.elements[i].innerHTML.trim()
                };
            }
            return content;
        },

        getExtensionByName: function (name) {
            var extension;
            if (this.extensions && this.extensions.length) {
                this.extensions.some(function (ext) {
                    if (ext.name === name) {
                        extension = ext;
                        return true;
                    }
                    return false;
                });
            }
            return extension;
        },

        /**
         * NOT DOCUMENTED - exposed as a helper for other extensions to use
         */
        addBuiltInExtension: function (name, opts) {
            var extension = this.getExtensionByName(name),
                merged;
            if (extension) {
                return extension;
            }

            switch (name) {
                case 'anchor':
                    merged = MediumEditor.util.extend({}, this.options.anchor, opts);
                    extension = new MediumEditor.extensions.anchor(merged);
                    break;
                case 'anchor-preview':
                    extension = new MediumEditor.extensions.anchorPreview(this.options.anchorPreview);
                    break;
                case 'autoLink':
                    extension = new MediumEditor.extensions.autoLink();
                    break;
                case 'fileDragging':
                    extension = new MediumEditor.extensions.fileDragging(opts);
                    break;
                case 'fontname':
                    extension = new MediumEditor.extensions.fontName(this.options.fontName);
                    break;
                case 'fontsize':
                    extension = new MediumEditor.extensions.fontSize(opts);
                    break;
                case 'keyboardCommands':
                    extension = new MediumEditor.extensions.keyboardCommands(this.options.keyboardCommands);
                    break;
                case 'paste':
                    extension = new MediumEditor.extensions.paste(this.options.paste);
                    break;
                case 'placeholder':
                    extension = new MediumEditor.extensions.placeholder(this.options.placeholder);
                    break;
                default:
                    // All of the built-in buttons for MediumEditor are extensions
                    // so check to see if the extension we're creating is a built-in button
                    if (MediumEditor.extensions.button.isBuiltInButton(name)) {
                        if (opts) {
                            merged = MediumEditor.util.defaults({}, opts, MediumEditor.extensions.button.prototype.defaults[name]);
                            extension = new MediumEditor.extensions.button(merged);
                        } else {
                            extension = new MediumEditor.extensions.button(name);
                        }
                    }
            }

            if (extension) {
                this.extensions.push(initExtension(extension, name, this));
            }

            return extension;
        },

        stopSelectionUpdates: function () {
            this.preventSelectionUpdates = true;
        },

        startSelectionUpdates: function () {
            this.preventSelectionUpdates = false;
        },

        checkSelection: function () {
            var toolbar = this.getExtensionByName('toolbar');
            if (toolbar) {
                toolbar.checkState();
            }
            return this;
        },

        // Wrapper around document.queryCommandState for checking whether an action has already
        // been applied to the current selection
        queryCommandState: function (action) {
            var fullAction = /^full-(.+)$/gi,
                match,
                queryState = null;

            // Actions starting with 'full-' need to be modified since this is a medium-editor concept
            match = fullAction.exec(action);
            if (match) {
                action = match[1];
            }

            try {
                queryState = this.options.ownerDocument.queryCommandState(action);
            } catch (exc) {
                queryState = null;
            }

            return queryState;
        },

        execAction: function (action, opts) {
            /*jslint regexp: true*/
            var fullAction = /^full-(.+)$/gi,
                match,
                result;
            /*jslint regexp: false*/

            // Actions starting with 'full-' should be applied to to the entire contents of the editable element
            // (ie full-bold, full-append-pre, etc.)
            match = fullAction.exec(action);
            if (match) {
                // Store the current selection to be restored after applying the action
                this.saveSelection();
                // Select all of the contents before calling the action
                this.selectAllContents();
                result = execActionInternal.call(this, match[1], opts);
                // Restore the previous selection
                this.restoreSelection();
            } else {
                result = execActionInternal.call(this, action, opts);
            }

            // do some DOM clean-up for known browser issues after the action
            if (action === 'insertunorderedlist' || action === 'insertorderedlist') {
                MediumEditor.util.cleanListDOM(this.options.ownerDocument, this.getSelectedParentElement());
            }

            this.checkSelection();
            return result;
        },

        getSelectedParentElement: function (range) {
            if (range === undefined) {
                range = this.options.contentWindow.getSelection().getRangeAt(0);
            }
            return MediumEditor.selection.getSelectedParentElement(range);
        },

        selectAllContents: function () {
            var currNode = MediumEditor.selection.getSelectionElement(this.options.contentWindow);

            if (currNode) {
                // Move to the lowest descendant node that still selects all of the contents
                while (currNode.children.length === 1) {
                    currNode = currNode.children[0];
                }

                this.selectElement(currNode);
            }
        },

        selectElement: function (element) {
            MediumEditor.selection.selectNode(element, this.options.ownerDocument);

            var selElement = MediumEditor.selection.getSelectionElement(this.options.contentWindow);
            if (selElement) {
                this.events.focusElement(selElement);
            }
        },

        getFocusedElement: function () {
            var focused;
            this.elements.some(function (element) {
                // Find the element that has focus
                if (!focused && element.getAttribute('data-medium-focused')) {
                    focused = element;
                }

                // bail if we found the element that had focus
                return !!focused;
            }, this);

            return focused;
        },

        // Export the state of the selection in respect to one of this
        // instance of MediumEditor's elements
        exportSelection: function () {
            var selectionElement = MediumEditor.selection.getSelectionElement(this.options.contentWindow),
                editableElementIndex = this.elements.indexOf(selectionElement),
                selectionState = null;

            if (editableElementIndex >= 0) {
                selectionState = MediumEditor.selection.exportSelection(selectionElement, this.options.ownerDocument);
            }

            if (selectionState !== null && editableElementIndex !== 0) {
                selectionState.editableElementIndex = editableElementIndex;
            }

            return selectionState;
        },

        saveSelection: function () {
            this.selectionState = this.exportSelection();
        },

        // Restore a selection based on a selectionState returned by a call
        // to MediumEditor.exportSelection
        importSelection: function (selectionState, favorLaterSelectionAnchor) {
            if (!selectionState) {
                return;
            }

            var editableElement = this.elements[selectionState.editableElementIndex || 0];
            MediumEditor.selection.importSelection(selectionState, editableElement, this.options.ownerDocument, favorLaterSelectionAnchor);
        },

        restoreSelection: function () {
            this.importSelection(this.selectionState);
        },

        createLink: function (opts) {
            var currentEditor = MediumEditor.selection.getSelectionElement(this.options.contentWindow),
                customEvent = {},
                targetUrl;

            // Make sure the selection is within an element this editor is tracking
            if (this.elements.indexOf(currentEditor) === -1) {
                return;
            }

            try {
                this.events.disableCustomEvent('editableInput');
                // TODO: Deprecate support for opts.url in 6.0.0
                if (opts.url) {
                    MediumEditor.util.deprecated('.url option for createLink', '.value', '6.0.0');
                }
                targetUrl = opts.url || opts.value;
                if (targetUrl && targetUrl.trim().length > 0) {
                    var currentSelection = this.options.contentWindow.getSelection();
                    if (currentSelection) {
                        var currRange = currentSelection.getRangeAt(0),
                            commonAncestorContainer = currRange.commonAncestorContainer,
                            exportedSelection,
                            startContainerParentElement,
                            endContainerParentElement,
                            textNodes;

                        // If the selection is contained within a single text node
                        // and the selection starts at the beginning of the text node,
                        // MSIE still says the startContainer is the parent of the text node.
                        // If the selection is contained within a single text node, we
                        // want to just use the default browser 'createLink', so we need
                        // to account for this case and adjust the commonAncestorContainer accordingly
                        if (currRange.endContainer.nodeType === 3 &&
                            currRange.startContainer.nodeType !== 3 &&
                            currRange.startOffset === 0 &&
                            currRange.startContainer.firstChild === currRange.endContainer) {
                            commonAncestorContainer = currRange.endContainer;
                        }

                        startContainerParentElement = MediumEditor.util.getClosestBlockContainer(currRange.startContainer);
                        endContainerParentElement = MediumEditor.util.getClosestBlockContainer(currRange.endContainer);

                        // If the selection is not contained within a single text node
                        // but the selection is contained within the same block element
                        // we want to make sure we create a single link, and not multiple links
                        // which can happen with the built in browser functionality
                        if (commonAncestorContainer.nodeType !== 3 && commonAncestorContainer.textContent.length !== 0 && startContainerParentElement === endContainerParentElement) {
                            var parentElement = (startContainerParentElement || currentEditor),
                                fragment = this.options.ownerDocument.createDocumentFragment();

                            // since we are going to create a link from an extracted text,
                            // be sure that if we are updating a link, we won't let an empty link behind (see #754)
                            // (Workaroung for Chrome)
                            this.execAction('unlink');

                            exportedSelection = this.exportSelection();
                            fragment.appendChild(parentElement.cloneNode(true));

                            if (currentEditor === parentElement) {
                                // We have to avoid the editor itself being wiped out when it's the only block element,
                                // as our reference inside this.elements gets detached from the page when insertHTML runs.
                                // If we just use [parentElement, 0] and [parentElement, parentElement.childNodes.length]
                                // as the range boundaries, this happens whenever parentElement === currentEditor.
                                // The tradeoff to this workaround is that a orphaned tag can sometimes be left behind at
                                // the end of the editor's content.
                                // In Gecko:
                                // as an empty <strong></strong> if parentElement.lastChild is a <strong> tag.
                                // In WebKit:
                                // an invented <br /> tag at the end in the same situation
                                MediumEditor.selection.select(
                                    this.options.ownerDocument,
                                    parentElement.firstChild,
                                    0,
                                    parentElement.lastChild,
                                    parentElement.lastChild.nodeType === 3 ?
                                    parentElement.lastChild.nodeValue.length : parentElement.lastChild.childNodes.length
                                );
                            } else {
                                MediumEditor.selection.select(
                                    this.options.ownerDocument,
                                    parentElement,
                                    0,
                                    parentElement,
                                    parentElement.childNodes.length
                                );
                            }

                            var modifiedExportedSelection = this.exportSelection();

                            textNodes = MediumEditor.util.findOrCreateMatchingTextNodes(
                                this.options.ownerDocument,
                                fragment,
                                {
                                    start: exportedSelection.start - modifiedExportedSelection.start,
                                    end: exportedSelection.end - modifiedExportedSelection.start,
                                    editableElementIndex: exportedSelection.editableElementIndex
                                }
                            );
                            // If textNodes are not present, when changing link on images
                            // ex: <a><img src="http://image.test.com"></a>, change fragment to currRange.startContainer
                            // and set textNodes array to [imageElement, imageElement]
                            if (textNodes.length === 0) {
                                fragment = this.options.ownerDocument.createDocumentFragment();
                                fragment.appendChild(commonAncestorContainer.cloneNode(true));
                                textNodes = [fragment.firstChild.firstChild, fragment.firstChild.lastChild];
                            }

                            // Creates the link in the document fragment
                            MediumEditor.util.createLink(this.options.ownerDocument, textNodes, targetUrl.trim());

                            // Chrome trims the leading whitespaces when inserting HTML, which messes up restoring the selection.
                            var leadingWhitespacesCount = (fragment.firstChild.innerHTML.match(/^\s+/) || [''])[0].length;

                            // Now move the created link back into the original document in a way to preserve undo/redo history
                            MediumEditor.util.insertHTMLCommand(this.options.ownerDocument, fragment.firstChild.innerHTML.replace(/^\s+/, ''));
                            exportedSelection.start -= leadingWhitespacesCount;
                            exportedSelection.end -= leadingWhitespacesCount;

                            this.importSelection(exportedSelection);
                        } else {
                            this.options.ownerDocument.execCommand('createLink', false, targetUrl);
                        }

                        if (this.options.targetBlank || opts.target === '_blank') {
                            MediumEditor.util.setTargetBlank(MediumEditor.selection.getSelectionStart(this.options.ownerDocument), targetUrl);
                        } else {
                            MediumEditor.util.removeTargetBlank(MediumEditor.selection.getSelectionStart(this.options.ownerDocument), targetUrl);
                        }

                        if (opts.buttonClass) {
                            MediumEditor.util.addClassToAnchors(MediumEditor.selection.getSelectionStart(this.options.ownerDocument), opts.buttonClass);
                        }
                    }
                }
                // Fire input event for backwards compatibility if anyone was listening directly to the DOM input event
                if (this.options.targetBlank || opts.target === '_blank' || opts.buttonClass) {
                    customEvent = this.options.ownerDocument.createEvent('HTMLEvents');
                    customEvent.initEvent('input', true, true, this.options.contentWindow);
                    for (var i = 0, len = this.elements.length; i < len; i += 1) {
                        this.elements[i].dispatchEvent(customEvent);
                    }
                }
            } finally {
                this.events.enableCustomEvent('editableInput');
            }
            // Fire our custom editableInput event
            this.events.triggerCustomEvent('editableInput', customEvent, currentEditor);
        },

        cleanPaste: function (text) {
            this.getExtensionByName('paste').cleanPaste(text);
        },

        pasteHTML: function (html, options) {
            this.getExtensionByName('paste').pasteHTML(html, options);
        },

        setContent: function (html, index) {
            index = index || 0;

            if (this.elements[index]) {
                var target = this.elements[index];
                target.innerHTML = html;
                this.checkContentChanged(target);
            }
        },

        getContent: function (index) {
            index = index || 0;

            if (this.elements[index]) {
                return this.elements[index].innerHTML.trim();
            }
            return null;
        },

        checkContentChanged: function (editable) {
            editable = editable || MediumEditor.selection.getSelectionElement(this.options.contentWindow);
            this.events.updateInput(editable, { target: editable, currentTarget: editable });
        },

        resetContent: function (element) {
            // For all elements that exist in the this.elements array, we can assume:
            // - Its initial content has been set in the initialContent object
            // - It has a medium-editor-index attribute which is the key value in the initialContent object

            if (element) {
                var index = this.elements.indexOf(element);
                if (index !== -1) {
                    this.setContent(initialContent[element.getAttribute('medium-editor-index')], index);
                }
                return;
            }

            this.elements.forEach(function (el, idx) {
                this.setContent(initialContent[el.getAttribute('medium-editor-index')], idx);
            }, this);
        },

        addElements: function (selector) {
            // Convert elements into an array
            var elements = createElementsArray(selector, this.options.ownerDocument, true);

            // Do we have elements to add now?
            if (elements.length === 0) {
                return false;
            }

            elements.forEach(function (element) {
                // Initialize all new elements (we check that in those functions don't worry)
                element = initElement.call(this, element, this.id);

                // Add new elements to our internal elements array
                this.elements.push(element);

                // Trigger event so extensions can know when an element has been added
                this.trigger('addElement', { target: element, currentTarget: element }, element);
            }, this);
        },

        removeElements: function (selector) {
            // Convert elements into an array
            var elements = createElementsArray(selector, this.options.ownerDocument),
                toRemove = elements.map(function (el) {
                    // For textareas, make sure we're looking at the editor div and not the textarea itself
                    if (el.getAttribute('medium-editor-textarea-id') && el.parentNode) {
                        return el.parentNode.querySelector('div[medium-editor-textarea-id="' + el.getAttribute('medium-editor-textarea-id') + '"]');
                    } else {
                        return el;
                    }
                });

            this.elements = this.elements.filter(function (element) {
                // If this is an element we want to remove
                if (toRemove.indexOf(element) !== -1) {
                    this.events.cleanupElement(element);
                    if (element.getAttribute('medium-editor-textarea-id')) {
                        cleanupTextareaElement(element);
                    }
                    // Trigger event so extensions can clean-up elements that are being removed
                    this.trigger('removeElement', { target: element, currentTarget: element }, element);
                    return false;
                }
                return true;
            }, this);
        }
    };

    MediumEditor.getEditorFromElement = function (element) {
        var index = element.getAttribute('data-medium-editor-editor-index'),
            win = element && element.ownerDocument && (element.ownerDocument.defaultView || element.ownerDocument.parentWindow);
        if (win && win._mediumEditors && win._mediumEditors[index]) {
            return win._mediumEditors[index];
        }
        return null;
    };
}());

(function () {
    // summary: The default options hash used by the Editor

    MediumEditor.prototype.defaults = {
        activeButtonClass: 'medium-editor-button-active',
        buttonLabels: false,
        delay: 0,
        disableReturn: false,
        disableDoubleReturn: false,
        disableExtraSpaces: false,
        disableEditing: false,
        autoLink: false,
        elementsContainer: false,
        contentWindow: window,
        ownerDocument: document,
        targetBlank: false,
        extensions: {},
        spellcheck: true
    };
})();

MediumEditor.parseVersionString = function (release) {
    var split = release.split('-'),
        version = split[0].split('.'),
        preRelease = (split.length > 1) ? split[1] : '';
    return {
        major: parseInt(version[0], 10),
        minor: parseInt(version[1], 10),
        revision: parseInt(version[2], 10),
        preRelease: preRelease,
        toString: function () {
            return [version[0], version[1], version[2]].join('.') + (preRelease ? '-' + preRelease : '');
        }
    };
};

MediumEditor.version = MediumEditor.parseVersionString.call(this, ({
    // grunt-bump looks for this:
    'version': '5.23.0'
}).version);

    return MediumEditor;
}()));

(function () {
    /**
     * CustomHtml
     * Creates a new instance of CustomHtml extension.
     *
     * Licensed under the MIT license.
     * Copyright (c) 2014 jillix
     *
     * @name CustomHtml
     * @function
     * @param {Object} options An object containing the extension configuration. The
     * following fields should be provided:
     *  - buttonText: the text of the button (default: `</>`)
     *  - htmlToInsert: the HTML code that should be inserted
     */
    function CustomHtml(options) {
        this.button = document.createElement('button');
        this.button.className = 'medium-editor-action';
        if (this.button.innerText) {
            this.button.innerText = options.buttonText || "</>";
        } else {
            this.button.textContent = options.buttonText || "</>";
        }
        this.button.onclick = this.onClick.bind(this);
        this.options = options;
    }

    CustomHtml.insertHtmlAtCaret = function(html) {
        var sel, range;
        if (window.getSelection) {
            // IE9 and non-IE
            sel = window.getSelection();
            if (sel.getRangeAt && sel.rangeCount) {
                range = sel.getRangeAt(0);
                range.deleteContents();

                // Range.createContextualFragment() would be useful here but is
                // only relatively recently standardized and is not supported in
                // some browsers (IE9, for one)
                var el = document.createElement("div");
                el.innerHTML = html;
                var frag = document.createDocumentFragment(), node, lastNode;
                while ((node = el.firstChild)) {
                    lastNode = frag.appendChild(node);
                }
                range.insertNode(frag);

                // Preserve the selection
                if (lastNode) {
                    range = range.cloneRange();
                    range.setStartAfter(lastNode);
                    range.collapse(true);
                    sel.removeAllRanges();
                    sel.addRange(range);
                }
            }
        } else if (document.selection && document.selection.type != "Control") {
            // IE < 9
            document.selection.createRange().pasteHTML(html);
        }
    };

    /**
     * onClick
     * The click event handler that calls `insertHtmlAtCaret` method.
     *
     * @name onClick
     * @function
     */
    CustomHtml.prototype.onClick = function () {
        CustomHtml.insertHtmlAtCaret(this.options.htmlToInsert);
    };

    /**
     * getButton
     * This function is called by the Medium Editor and returns the button that is
     * added in the toolbar
     *
     * @name getButton
     * @function
     * @return {HTMLButtonElement} The button that is attached in the Medium Editor
     * toolbar
     */
    CustomHtml.prototype.getButton = function () {
        return this.button;
    };

    // declare public object
    window.CustomHtml = CustomHtml;
})();

(function () {
    'use strict';

    var FontNameForm = MediumEditor.extensions.form.extend({

        name: 'fontname',
        action: 'fontName',
        aria: 'change font name',
        contentDefault: '&#xB1;', // ±
        contentFA: '<i class="fa fa-font"></i>',

        fonts: ['', 'Arial', 'Verdana', 'Times New Roman'],

        init: function () {
            MediumEditor.extensions.form.prototype.init.apply(this, arguments);
        },

        // Called when the button the toolbar is clicked
        // Overrides ButtonExtension.handleClick
        handleClick: function (event) {
            event.preventDefault();
            event.stopPropagation();

            if (!this.isDisplayed()) {
                // Get FontName of current selection (convert to string since IE returns this as number)
                var fontName = this.document.queryCommandValue('fontName') + '';
                this.showForm(fontName);
            }

            return false;
        },

        // Called by medium-editor to append form to the toolbar
        getForm: function () {
            if (!this.form) {
                this.form = this.createForm();
            }
            return this.form;
        },

        // Used by medium-editor when the default toolbar is to be displayed
        isDisplayed: function () {
            return this.getForm().style.display === 'block';
        },

        hideForm: function () {
            this.getForm().style.display = 'none';
            this.getSelect().value = '';
        },

        showForm: function (fontName) {
            var select = this.getSelect();

            this.base.saveSelection();
            this.hideToolbarDefaultActions();
            this.getForm().style.display = 'block';
            this.setToolbarPosition();

            select.value = fontName || '';
            select.focus();
        },

        // Called by core when tearing down medium-editor (destroy)
        destroy: function () {
            if (!this.form) {
                return false;
            }

            if (this.form.parentNode) {
                this.form.parentNode.removeChild(this.form);
            }

            delete this.form;
        },

        // core methods

        doFormSave: function () {
            this.base.restoreSelection();
            this.base.checkSelection();
        },

        doFormCancel: function () {
            this.base.restoreSelection();
            this.clearFontName();
            this.base.checkSelection();
        },

        // form creation and event handling
        createForm: function () {
            var doc = this.document,
                form = doc.createElement('div'),
                select = doc.createElement('select'),
                close = doc.createElement('a'),
                save = doc.createElement('a'),
                option;

            // Font Name Form (div)
            form.className = 'medium-editor-toolbar-form';
            form.id = 'medium-editor-toolbar-form-fontname-' + this.getEditorId();

            // Handle clicks on the form itself
            this.on(form, 'click', this.handleFormClick.bind(this));

            // Add font names
            for (var i = 0; i < this.fonts.length; i++) {
                option = doc.createElement('option');
                option.innerHTML = this.fonts[i];
                option.value = this.fonts[i];
                select.appendChild(option);
            }

            select.className = 'medium-editor-toolbar-select';
            form.appendChild(select);

            // Handle typing in the textbox
            this.on(select, 'change', this.handleFontChange.bind(this));

            // Add save buton
            save.setAttribute('href', '#');
            save.className = 'medium-editor-toobar-save';
            save.innerHTML = this.getEditorOption('buttonLabels') === 'fontawesome' ?
                             '<i class="fa fa-check"></i>' :
                             '&#10003;';
            form.appendChild(save);

            // Handle save button clicks (capture)
            this.on(save, 'click', this.handleSaveClick.bind(this), true);

            // Add close button
            close.setAttribute('href', '#');
            close.className = 'medium-editor-toobar-close';
            close.innerHTML = this.getEditorOption('buttonLabels') === 'fontawesome' ?
                              '<i class="fa fa-times"></i>' :
                              '&times;';
            form.appendChild(close);

            // Handle close button clicks
            this.on(close, 'click', this.handleCloseClick.bind(this));

            return form;
        },

        getSelect: function () {
            return this.getForm().querySelector('select.medium-editor-toolbar-select');
        },

        clearFontName: function () {
            MediumEditor.selection.getSelectedElements(this.document).forEach(function (el) {
                if (el.nodeName.toLowerCase() === 'font' && el.hasAttribute('face')) {
                    el.removeAttribute('face');
                }
            });
        },

        handleFontChange: function () {
            var font = this.getSelect().value;
            if (font === '') {
                this.clearFontName();
            } else {
                this.execAction('fontName', { value: font });
            }
        },

        handleFormClick: function (event) {
            // make sure not to hide form when clicking inside the form
            event.stopPropagation();
        },

        handleSaveClick: function (event) {
            // Clicking Save -> create the font size
            event.preventDefault();
            this.doFormSave();
        },

        handleCloseClick: function (event) {
            // Click Close -> close the form
            event.preventDefault();
            this.doFormCancel();
        }
    });

    MediumEditor.extensions.fontName = FontNameForm;
}());
(function () {
    'use strict';

    var FontSizeForm = MediumEditor.extensions.form.extend({

        name: 'fontsize',
        action: 'fontSize',
        aria: 'increase/decrease font size',
        contentDefault: '&#xB1;', // ±
        contentFA: '<i class="fa fa-text-height"></i>',

        init: function () {
            MediumEditor.extensions.form.prototype.init.apply(this, arguments);
        },

        // Called when the button the toolbar is clicked
        // Overrides ButtonExtension.handleClick
        handleClick: function (event) {
            event.preventDefault();
            event.stopPropagation();

            if (!this.isDisplayed()) {
                // Get fontsize of current selection (convert to string since IE returns this as number)
                var fontSize = this.document.queryCommandValue('fontSize') + '';
                this.showForm(fontSize);
            }

            return false;
        },

        // Called by medium-editor to append form to the toolbar
        getForm: function () {
            if (!this.form) {
                this.form = this.createForm();
            }
            return this.form;
        },

        // Used by medium-editor when the default toolbar is to be displayed
        isDisplayed: function () {
            return this.getForm().style.display === 'block';
        },

        hideForm: function () {
            this.getForm().style.display = 'none';
            this.getInput().value = '';
        },

        showForm: function (fontSize) {
            var input = this.getInput();

            this.base.saveSelection();
            this.hideToolbarDefaultActions();
            this.getForm().style.display = 'block';
            this.setToolbarPosition();

            input.value = fontSize || '';
            input.focus();
        },

        // Called by core when tearing down medium-editor (destroy)
        destroy: function () {
            if (!this.form) {
                return false;
            }

            if (this.form.parentNode) {
                this.form.parentNode.removeChild(this.form);
            }

            delete this.form;
        },

        // core methods

        doFormSave: function () {
            this.base.restoreSelection();
            this.base.checkSelection();
        },

        doFormCancel: function () {
            this.base.restoreSelection();
            this.clearFontSize();
            this.base.checkSelection();
        },

        // form creation and event handling
        createForm: function () {
            var doc = this.document,
                form = doc.createElement('div'),
                input = doc.createElement('input'),
                close = doc.createElement('a'),
                save = doc.createElement('a');

            // Font Size Form (div)
            form.className = 'medium-editor-toolbar-form';
            form.id = 'medium-editor-toolbar-form-fontsize-' + this.getEditorId();

            // Handle clicks on the form itself
            this.on(form, 'click', this.handleFormClick.bind(this));

            // Add font size slider
            input.setAttribute('type', 'range');
            input.setAttribute('min', '1');
            input.setAttribute('max', '7');
            input.className = 'medium-editor-toolbar-input';
            form.appendChild(input);

            // Handle typing in the textbox
            this.on(input, 'change', this.handleSliderChange.bind(this));

            // Add save buton
            save.setAttribute('href', '#');
            save.className = 'medium-editor-toobar-save';
            save.innerHTML = this.getEditorOption('buttonLabels') === 'fontawesome' ?
                             '<i class="fa fa-check"></i>' :
                             '&#10003;';
            form.appendChild(save);

            // Handle save button clicks (capture)
            this.on(save, 'click', this.handleSaveClick.bind(this), true);

            // Add close button
            close.setAttribute('href', '#');
            close.className = 'medium-editor-toobar-close';
            close.innerHTML = this.getEditorOption('buttonLabels') === 'fontawesome' ?
                              '<i class="fa fa-times"></i>' :
                              '&times;';
            form.appendChild(close);

            // Handle close button clicks
            this.on(close, 'click', this.handleCloseClick.bind(this));

            return form;
        },

        getInput: function () {
            return this.getForm().querySelector('input.medium-editor-toolbar-input');
        },

        clearFontSize: function () {
            MediumEditor.selection.getSelectedElements(this.document).forEach(function (el) {
                if (el.nodeName.toLowerCase() === 'font' && el.hasAttribute('size')) {
                    el.removeAttribute('size');
                }
            });
        },

        handleSliderChange: function () {
            var size = this.getInput().value;
            if (size === '4') {
                this.clearFontSize();
            } else {
                this.execAction('fontSize', { value: size });
            }
        },

        handleFormClick: function (event) {
            // make sure not to hide form when clicking inside the form
            event.stopPropagation();
        },

        handleSaveClick: function (event) {
            // Clicking Save -> create the font size
            event.preventDefault();
            this.doFormSave();
        },

        handleCloseClick: function (event) {
            // Click Close -> close the form
            event.preventDefault();
            this.doFormCancel();
        }
    });

    MediumEditor.extensions.fontSize = FontSizeForm;
}());
/**
 *   MediumButton  1.0 (24.02.2015)
 *   MIT (c) Patrick Stillhart
 *  
 */
function MediumButton(options) {
    if (options.label === undefined || !/\S{1}/.test(options.label) || 
		options.start === undefined || !/\S{1}/.test(options.start) || 
		options.end === undefined || !/\S{1}/.test(options.end)) {
		
		if(options.label === undefined || !/\S{1}/.test(options.label) ||
		   options.action === undefined || !/\S{1}/.test(options.action)) {
			console.error('[Custom-Button] You need to specify "label", "start" and "end" OR "label" and "action"');	
			return;
		} 
    }
	
	options.start = (options.start === undefined) ? '' : options.start;
	options.end = (options.end === undefined) ? '' : options.end;
	
    this.options = options;
    this.button = document.createElement('button');
    this.button.className = 'medium-editor-action';
    this.button.innerHTML = options.label;
    this.button.onclick = function() {
        // Get Current Value
        var html = getCurrentSelection(), sel = window.getSelection();
		
        //Modify Content 
		var mark = true;
        if (options.start === undefined || html.indexOf(options.start) == -1 && html.indexOf(options.end) == -1) {
		
			if(options.action != undefined) html = options.action(html, true);
		
            html = options.start + html + options.end;

        } else { //clean old
			if(options.action != undefined) html = options.action(html, false);
			html = String(html).split(options.start).join('');
			html = String(html).split(options.end).join('');
        }
		
		
		
		
		var range;
        //Set new Content
        if (sel.getRangeAt && sel.rangeCount) {
            range = window.getSelection().getRangeAt(0);
            range.deleteContents();

            // Create a DocumentFragment to insert and populate it with HTML
            // Need to test for the existence of range.createContextualFragment
            // because it's non-standard and IE 9 does not support it
            if (range.createContextualFragment) {
                fragment = range.createContextualFragment(html);
            } else {
                var div = document.createElement('div');
                div.innerHTML = html;
                fragment = document.createDocumentFragment();
                while ((child = div.firstChild)) {
                    fragment.appendChild(child);
                }
				
            }
            var firstInsertedNode = fragment.firstChild;
            var lastInsertedNode = fragment.lastChild;
            range.insertNode(fragment);
            if (firstInsertedNode) {
                range.setStartBefore(firstInsertedNode);
                range.setEndAfter(lastInsertedNode);
            }
            sel.removeAllRanges();
            sel.addRange(range);
        }
	
    };

}

MediumButton.prototype.getButton = function() {
    return this.button;
};

MediumButton.prototype.checkState = function(node) {
	var html = getCurrentSelection();
    if (this.options.start != '' && html.indexOf(this.options.start) > -1 && html.indexOf(this.options.end) > -1) {
        this.button.classList.add('medium-editor-button-active');
    }

};

function getCurrentSelection() {

	var html = '', sel;
       if (typeof window.getSelection != 'undefined') {
           sel = window.getSelection();
           if (sel.rangeCount) {
               var container = document.createElement('div');
               for (var i = 0, len = sel.rangeCount; i < len; ++i) {
                   container.appendChild(sel.getRangeAt(i).cloneContents());
               }
               html = container.innerHTML;
           }
       } else if (typeof document.selection != 'undefined') {
           if (document.selection.type == 'Text') {
               html = document.selection.createRange().htmlText;
           }
       }
	
	return html;
	
}
/*! 
 * medium-editor-insert-plugin v2.5.0 - jQuery insert plugin for MediumEditor
 *
 * http://linkesch.com/medium-editor-insert-plugin
 * 
 * Copyright (c) 2014 Pavel Linkesch (http://linkesch.com)
 * Released under the MIT license
 */

(function (factory) {
    if (typeof define === 'function' && define.amd) {
        define(['jquery', 'handlebars/runtime', 'medium-editor', 'blueimp-file-upload', 'jquery-sortable'], factory);
    } else if (typeof module === 'object' && module.exports) {
        module.exports = function (jQuery) {
            if (typeof window === 'undefined') {
                throw new Error("medium-editor-insert-plugin runs only in a browser.")
            }

            if (jQuery === undefined) {
                jQuery = require('jquery');
            }
            window.jQuery = jQuery;

            Handlebars = require('handlebars/runtime');
            MediumEditor = require('medium-editor');
            require('jquery-sortable');
            require('blueimp-file-upload');

            factory(jQuery, Handlebars, MediumEditor);
            return jQuery;
        };
    } else {
        factory(jQuery, Handlebars, MediumEditor);
    }
}(function ($, Handlebars, MediumEditor) {

    this["MediumInsert"] = this["MediumInsert"] || {};
    this["MediumInsert"]["Templates"] = this["MediumInsert"]["Templates"] || {};

    this["MediumInsert"]["Templates"]["src/js/templates/core-buttons.hbs"] = Handlebars.template({
        "1": function (container, depth0, helpers, partials, data) {
            var stack1, helper, alias1 = depth0 != null ? depth0 : (container.nullContext || {}), alias2 = helpers.helperMissing, alias3 = "function";

            return "            <li><button data-addon=\""
              + container.escapeExpression(((helper = (helper = helpers.key || (data && data.key)) != null ? helper : alias2), (typeof helper === alias3 ? helper.call(alias1, { "name": "key", "hash": {}, "data": data }) : helper)))
              + "\" data-action=\"add\" class=\"medium-insert-action\" type=\"button\">"
              + ((stack1 = ((helper = (helper = helpers.label || (depth0 != null ? depth0.label : depth0)) != null ? helper : alias2), (typeof helper === alias3 ? helper.call(alias1, { "name": "label", "hash": {}, "data": data }) : helper))) != null ? stack1 : "")
              + "</button></li>\n";
        }, "compiler": [7, ">= 4.0.0"], "main": function (container, depth0, helpers, partials, data) {
            var stack1;

            return "<div class=\"medium-insert-buttons\" contenteditable=\"false\" style=\"display: none\">\n    <button class=\"medium-insert-buttons-show\" type=\"button\"><span>+</span></button>\n    <ul class=\"medium-insert-buttons-addons\" style=\"display: none\">\n"
              + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}), (depth0 != null ? depth0.addons : depth0), { "name": "each", "hash": {}, "fn": container.program(1, data, 0), "inverse": container.noop, "data": data })) != null ? stack1 : "")
              + "    </ul>\n</div>\n";
        }, "useData": true
    });

    this["MediumInsert"]["Templates"]["src/js/templates/core-caption.hbs"] = Handlebars.template({
        "compiler": [7, ">= 4.0.0"], "main": function (container, depth0, helpers, partials, data) {
            var helper;

            return "<figcaption contenteditable=\"true\" class=\"medium-insert-caption-placeholder\" data-placeholder=\""
              + container.escapeExpression(((helper = (helper = helpers.placeholder || (depth0 != null ? depth0.placeholder : depth0)) != null ? helper : helpers.helperMissing), (typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}), { "name": "placeholder", "hash": {}, "data": data }) : helper)))
              + "\"></figcaption>";
        }, "useData": true
    });

    this["MediumInsert"]["Templates"]["src/js/templates/core-empty-line.hbs"] = Handlebars.template({
        "compiler": [7, ">= 4.0.0"], "main": function (container, depth0, helpers, partials, data) {
            return "<p><br></p>\n";
        }, "useData": true
    });

    this["MediumInsert"]["Templates"]["src/js/templates/embeds-toolbar.hbs"] = Handlebars.template({
        "1": function (container, depth0, helpers, partials, data) {
            var stack1;

            return "    <div class=\"medium-insert-embeds-toolbar medium-editor-toolbar medium-toolbar-arrow-under medium-editor-toolbar-active\">\n        <ul class=\"medium-editor-toolbar-actions clearfix\">\n"
              + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}), (depth0 != null ? depth0.styles : depth0), { "name": "each", "hash": {}, "fn": container.program(2, data, 0), "inverse": container.noop, "data": data })) != null ? stack1 : "")
              + "        </ul>\n    </div>\n";
        }, "2": function (container, depth0, helpers, partials, data) {
            var stack1;

            return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}), (depth0 != null ? depth0.label : depth0), { "name": "if", "hash": {}, "fn": container.program(3, data, 0), "inverse": container.noop, "data": data })) != null ? stack1 : "");
        }, "3": function (container, depth0, helpers, partials, data) {
            var stack1, helper, alias1 = depth0 != null ? depth0 : (container.nullContext || {}), alias2 = helpers.helperMissing, alias3 = "function";

            return "                    <li>\n                        <button class=\"medium-editor-action\" data-action=\""
              + container.escapeExpression(((helper = (helper = helpers.key || (data && data.key)) != null ? helper : alias2), (typeof helper === alias3 ? helper.call(alias1, { "name": "key", "hash": {}, "data": data }) : helper)))
              + "\">"
              + ((stack1 = ((helper = (helper = helpers.label || (depth0 != null ? depth0.label : depth0)) != null ? helper : alias2), (typeof helper === alias3 ? helper.call(alias1, { "name": "label", "hash": {}, "data": data }) : helper))) != null ? stack1 : "")
              + "</button>\n                    </li>\n";
        }, "5": function (container, depth0, helpers, partials, data) {
            var stack1;

            return "    <div class=\"medium-insert-embeds-toolbar2 medium-editor-toolbar medium-editor-toolbar-active\">\n        <ul class=\"medium-editor-toolbar-actions clearfix\">\n"
              + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}), (depth0 != null ? depth0.actions : depth0), { "name": "each", "hash": {}, "fn": container.program(2, data, 0), "inverse": container.noop, "data": data })) != null ? stack1 : "")
              + "        </ul>\n    </div>\n";
        }, "compiler": [7, ">= 4.0.0"], "main": function (container, depth0, helpers, partials, data) {
            var stack1, alias1 = depth0 != null ? depth0 : (container.nullContext || {});

            return ((stack1 = helpers["if"].call(alias1, (depth0 != null ? depth0.styles : depth0), { "name": "if", "hash": {}, "fn": container.program(1, data, 0), "inverse": container.noop, "data": data })) != null ? stack1 : "")
              + "\n"
              + ((stack1 = helpers["if"].call(alias1, (depth0 != null ? depth0.actions : depth0), { "name": "if", "hash": {}, "fn": container.program(5, data, 0), "inverse": container.noop, "data": data })) != null ? stack1 : "");
        }, "useData": true
    });

    this["MediumInsert"]["Templates"]["src/js/templates/embeds-wrapper.hbs"] = Handlebars.template({
        "compiler": [7, ">= 4.0.0"], "main": function (container, depth0, helpers, partials, data) {
            var stack1, helper;

            return "<div class=\"medium-insert-embeds\" contenteditable=\"false\">\n	<figure>\n		<div class=\"medium-insert-embed\">\n			"
              + ((stack1 = ((helper = (helper = helpers.html || (depth0 != null ? depth0.html : depth0)) != null ? helper : helpers.helperMissing), (typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}), { "name": "html", "hash": {}, "data": data }) : helper))) != null ? stack1 : "")
              + "\n		</div>\n	</figure>\n	<div class=\"medium-insert-embeds-overlay\"></div>\n</div>";
        }, "useData": true
    });

    this["MediumInsert"]["Templates"]["src/js/templates/images-fileupload.hbs"] = Handlebars.template({
        "compiler": [7, ">= 4.0.0"], "main": function (container, depth0, helpers, partials, data) {
            return "<input type=\"file\" multiple>";
        }, "useData": true
    });

    this["MediumInsert"]["Templates"]["src/js/templates/images-image.hbs"] = Handlebars.template({
        "1": function (container, depth0, helpers, partials, data) {
            return "        <div class=\"medium-insert-images-progress\"></div>\n";
        }, "compiler": [7, ">= 4.0.0"], "main": function (container, depth0, helpers, partials, data) {
            var stack1, helper, alias1 = depth0 != null ? depth0 : (container.nullContext || {});

            return "<figure contenteditable=\"false\">\n    <img src=\""
              + container.escapeExpression(((helper = (helper = helpers.img || (depth0 != null ? depth0.img : depth0)) != null ? helper : helpers.helperMissing), (typeof helper === "function" ? helper.call(alias1, { "name": "img", "hash": {}, "data": data }) : helper)))
              + "\" alt=\"IMAGEALTERNATIVE\" />\n"
              + ((stack1 = helpers["if"].call(alias1, (depth0 != null ? depth0.progress : depth0), { "name": "if", "hash": {}, "fn": container.program(1, data, 0), "inverse": container.noop, "data": data })) != null ? stack1 : "")
              + "</figure>\n";
        }, "useData": true
    });

    this["MediumInsert"]["Templates"]["src/js/templates/images-progressbar.hbs"] = Handlebars.template({
        "compiler": [7, ">= 4.0.0"], "main": function (container, depth0, helpers, partials, data) {
            return "<progress min=\"0\" max=\"100\" value=\"0\">0</progress>";
        }, "useData": true
    });

    this["MediumInsert"]["Templates"]["src/js/templates/images-toolbar.hbs"] = Handlebars.template({
        "1": function (container, depth0, helpers, partials, data) {
            var stack1;

            return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}), (depth0 != null ? depth0.label : depth0), { "name": "if", "hash": {}, "fn": container.program(2, data, 0), "inverse": container.noop, "data": data })) != null ? stack1 : "");
        }, "2": function (container, depth0, helpers, partials, data) {
            var stack1, helper, alias1 = depth0 != null ? depth0 : (container.nullContext || {}), alias2 = helpers.helperMissing, alias3 = "function";

            return "                <li>\n                    <button class=\"medium-editor-action\" data-action=\""
              + container.escapeExpression(((helper = (helper = helpers.key || (data && data.key)) != null ? helper : alias2), (typeof helper === alias3 ? helper.call(alias1, { "name": "key", "hash": {}, "data": data }) : helper)))
              + "\">"
              + ((stack1 = ((helper = (helper = helpers.label || (depth0 != null ? depth0.label : depth0)) != null ? helper : alias2), (typeof helper === alias3 ? helper.call(alias1, { "name": "label", "hash": {}, "data": data }) : helper))) != null ? stack1 : "")
              + "</button>\n                </li>\n";
        }, "4": function (container, depth0, helpers, partials, data) {
            var stack1;

            return "	<div class=\"medium-insert-images-toolbar2 medium-editor-toolbar medium-editor-toolbar-active\">\n		<ul class=\"medium-editor-toolbar-actions clearfix\">\n"
              + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}), (depth0 != null ? depth0.actions : depth0), { "name": "each", "hash": {}, "fn": container.program(5, data, 0), "inverse": container.noop, "data": data })) != null ? stack1 : "")
              + "    	</ul>\n    </div>\n";
        }, "5": function (container, depth0, helpers, partials, data) {
            var stack1;

            return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}), (depth0 != null ? depth0.label : depth0), { "name": "if", "hash": {}, "fn": container.program(6, data, 0), "inverse": container.noop, "data": data })) != null ? stack1 : "");
        }, "6": function (container, depth0, helpers, partials, data) {
            var stack1, helper, alias1 = depth0 != null ? depth0 : (container.nullContext || {}), alias2 = helpers.helperMissing, alias3 = "function";

            return "        	        <li>\n        	            <button class=\"medium-editor-action\" data-action=\""
              + container.escapeExpression(((helper = (helper = helpers.key || (data && data.key)) != null ? helper : alias2), (typeof helper === alias3 ? helper.call(alias1, { "name": "key", "hash": {}, "data": data }) : helper)))
              + "\">"
              + ((stack1 = ((helper = (helper = helpers.label || (depth0 != null ? depth0.label : depth0)) != null ? helper : alias2), (typeof helper === alias3 ? helper.call(alias1, { "name": "label", "hash": {}, "data": data }) : helper))) != null ? stack1 : "")
              + "</button>\n        	        </li>\n";
        }, "compiler": [7, ">= 4.0.0"], "main": function (container, depth0, helpers, partials, data) {
            var stack1, alias1 = depth0 != null ? depth0 : (container.nullContext || {});

            return "<div class=\"medium-insert-images-toolbar medium-editor-toolbar medium-toolbar-arrow-under medium-editor-toolbar-active\">\n    <ul class=\"medium-editor-toolbar-actions clearfix\">\n"
              + ((stack1 = helpers.each.call(alias1, (depth0 != null ? depth0.styles : depth0), { "name": "each", "hash": {}, "fn": container.program(1, data, 0), "inverse": container.noop, "data": data })) != null ? stack1 : "")
              + "    </ul>\n</div>\n\n"
              + ((stack1 = helpers["if"].call(alias1, (depth0 != null ? depth0.actions : depth0), { "name": "if", "hash": {}, "fn": container.program(4, data, 0), "inverse": container.noop, "data": data })) != null ? stack1 : "");
        }, "useData": true
    });
    ; (function ($, window, document, undefined) {

        'use strict';

        /** Default values */
        var pluginName = 'mediumInsert',
            defaults = {
                editor: null,
                enabled: true,
                addons: {
                    images: true, // boolean or object containing configuration
                    embeds: true
                }
            };

        /**
         * Capitalize first character
         *
         * @param {string} str
         * @return {string}
         */

        function ucfirst(str) {
            return str.charAt(0).toUpperCase() + str.slice(1);
        }

        /**
         * Core plugin's object
         *
         * Sets options, variables and calls init() function
         *
         * @constructor
         * @param {DOM} el - DOM element to init the plugin on
         * @param {object} options - Options to override defaults
         * @return {void}
         */

        function Core(el, options) {
            var editor;

            this.el = el;
            this.$el = $(el);
            this.templates = window.MediumInsert.Templates;

            if (options) {
                // Fix #142
                // Avoid deep copying editor object, because since v2.3.0 it contains circular references which causes jQuery.extend to break
                // Instead copy editor object to this.options manually
                editor = options.editor;
                options.editor = null;
            }
            this.options = $.extend(true, {}, defaults, options);
            this.options.editor = editor;
            if (options) {
                options.editor = editor; // Restore original object definition
            }

            this._defaults = defaults;
            this._name = pluginName;

            // Extend editor's functions
            if (this.options && this.options.editor) {
                if (this.options.editor._serialize === undefined) {
                    this.options.editor._serialize = this.options.editor.serialize;
                }
                if (this.options.editor._destroy === undefined) {
                    this.options.editor._destroy = this.options.editor.destroy;
                }
                if (this.options.editor._setup === undefined) {
                    this.options.editor._setup = this.options.editor.setup;
                }
                this.options.editor._hideInsertButtons = this.hideButtons;

                this.options.editor.serialize = this.editorSerialize;
                this.options.editor.destroy = this.editorDestroy;
                this.options.editor.setup = this.editorSetup;

                if (this.options.editor.getExtensionByName('placeholder') !== undefined) {
                    this.options.editor.getExtensionByName('placeholder').updatePlaceholder = this.editorUpdatePlaceholder;
                }
            }
        }

        /**
         * Initialization
         *
         * @return {void}
         */

        Core.prototype.init = function () {
            this.$el.addClass('medium-editor-insert-plugin');

            if (typeof this.options.addons !== 'object' || Object.keys(this.options.addons).length === 0) {
                this.disable();
            }

            this.initAddons();
            this.clean();
            this.events();
        };

        /**
         * Event listeners
         *
         * @return {void}
         */

        Core.prototype.events = function () {
            var that = this;

            this.$el
                .on('dragover drop', function (e) {
                    e.preventDefault();
                })
                .on('keyup click', $.proxy(this, 'toggleButtons'))
                .on('selectstart mousedown', '.medium-insert, .medium-insert-buttons', $.proxy(this, 'disableSelection'))
                .on('click', '.medium-insert-buttons-show', $.proxy(this, 'toggleAddons'))
                .on('click', '.medium-insert-action', $.proxy(this, 'addonAction'))
                .on('paste', '.medium-insert-caption-placeholder', function (e) {
                    $.proxy(that, 'removeCaptionPlaceholder')($(e.target));
                });

            $(window).on('resize', $.proxy(this, 'positionButtons', null));
        };

        /**
         * Return editor instance
         *
         * @return {object} MediumEditor
         */

        Core.prototype.getEditor = function () {
            return this.options.editor;
        };

        /**
         * Extend editor's serialize function
         *
         * @return {object} Serialized data
         */

        Core.prototype.editorSerialize = function () {
            var data = this._serialize();

            $.each(data, function (key) {
                var $data = $('<div />').html(data[key].value);

                $data.find('.medium-insert-buttons').remove();
                $data.find('.medium-insert-active').removeClass('medium-insert-active');

                // Restore original embed code from embed wrapper attribute value.
                $data.find('[data-embed-code]').each(function () {
                    var $this = $(this),
                        html = $('<div />').html($this.attr('data-embed-code')).text();
                    $this.html(html);
                });

                data[key].value = $data.html();
            });

            return data;
        };

        /**
         * Extend editor's destroy function to deactivate this plugin too
         *
         * @return {void}
         */

        Core.prototype.editorDestroy = function () {
            $.each(this.elements, function (key, el) {
                if ($(el).data('plugin_' + pluginName) instanceof Core) {
                    $(el).data('plugin_' + pluginName).disable();
                }
            });

            this._destroy();
        };

        /**
         * Extend editor's setup function to activate this plugin too
         *
         * @return {void}
         */

        Core.prototype.editorSetup = function () {
            this._setup();

            $.each(this.elements, function (key, el) {
                if ($(el).data('plugin_' + pluginName) instanceof Core) {
                    $(el).data('plugin_' + pluginName).enable();
                }
            });
        };

        /**
         * Extend editor's placeholder.updatePlaceholder function to show placeholder dispite of the plugin buttons
         *
         * @return {void}
         */

        Core.prototype.editorUpdatePlaceholder = function (el, dontShow) {
            var contents = $(el).children()
                .not('.medium-insert-buttons').contents();

            if (!dontShow && contents.length === 1 && contents[0].nodeName.toLowerCase() === 'br') {
                this.showPlaceholder(el);
                this.base._hideInsertButtons($(el));
            } else {
                this.hidePlaceholder(el);
            }
        };

        /**
         * Trigger editableInput on editor
         *
         * @return {void}
         */

        Core.prototype.triggerInput = function () {
            if (this.getEditor()) {
                this.getEditor().trigger('editableInput', null, this.el);
            }
        };

        /**
         * Deselects selected text
         *
         * @return {void}
         */

        Core.prototype.deselect = function () {
            document.getSelection().removeAllRanges();
        };

        /**
         * Disables the plugin
         *
         * @return {void}
         */

        Core.prototype.disable = function () {
            this.options.enabled = false;

            this.$el.find('.medium-insert-buttons').addClass('hide');
        };

        /**
         * Enables the plugin
         *
         * @return {void}
         */

        Core.prototype.enable = function () {
            this.options.enabled = true;

            this.$el.find('.medium-insert-buttons').removeClass('hide');
        };

        /**
         * Disables selectstart mousedown events on plugin elements except images
         *
         * @return {void}
         */

        Core.prototype.disableSelection = function (e) {
            var $el = $(e.target);

            if ($el.is('img') === false || $el.hasClass('medium-insert-buttons-show')) {
                e.preventDefault();
            }
        };

        /**
         * Initialize addons
         *
         * @return {void}
         */

        Core.prototype.initAddons = function () {
            var that = this;

            if (!this.options.addons || this.options.addons.length === 0) {
                return;
            }

            $.each(this.options.addons, function (addon, options) {
                var addonName = pluginName + ucfirst(addon);

                if (options === false) {
                    delete that.options.addons[addon];
                    return;
                }

                that.$el[addonName](options);
                that.options.addons[addon] = that.$el.data('plugin_' + addonName).options;
            });
        };

        /**
         * Cleans a content of the editor
         *
         * @return {void}
         */

        Core.prototype.clean = function () {
            var that = this,
                $buttons, $lastEl, $text;

            if (this.options.enabled === false) {
                return;
            }

            if (this.$el.html().length === 0) {
                this.$el.html(this.templates['src/js/templates/core-empty-line.hbs']().trim());
            }

            // Fix #29
            // Wrap content text in <p></p> to avoid Firefox problems
            $text = this.$el
                .contents()
                .filter(function () {
                    return (this.nodeName === '#text' && $.trim($(this).text()) !== '') || this.nodeName.toLowerCase() === 'br';
                });

            $text.each(function () {
                $(this).wrap('<p />');

                // Fix #145
                // Move caret at the end of the element that's being wrapped
                that.moveCaret($(this).parent(), $(this).text().length);
            });

            this.addButtons();

            $buttons = this.$el.find('.medium-insert-buttons');
            $lastEl = $buttons.prev();
            if ($lastEl.attr('class') && $lastEl.attr('class').match(/medium\-insert(?!\-active)/)) {
                $buttons.before(this.templates['src/js/templates/core-empty-line.hbs']().trim());
            }
        };

        /**
         * Returns HTML template of buttons
         *
         * @return {string} HTML template of buttons
         */

        Core.prototype.getButtons = function () {
            if (this.options.enabled === false) {
                return;
            }

            return this.templates['src/js/templates/core-buttons.hbs']({
                addons: this.options.addons
            }).trim();
        };

        /**
         * Appends buttons at the end of the $el
         *
         * @return {void}
         */

        Core.prototype.addButtons = function () {
            if (this.$el.find('.medium-insert-buttons').length === 0) {
                this.$el.append(this.getButtons());
            }
        };

        /**
         * Move buttons to current active, empty paragraph and show them
         *
         * @return {void}
         */

        Core.prototype.toggleButtons = function (e) {
            var $el = $(e.target),
                selection = window.getSelection(),
                that = this,
                range, $current, $p, activeAddon;

            if (this.options.enabled === false) {
                return;
            }

            if (!selection || selection.rangeCount === 0) {
                $current = $el;
            } else {
                range = selection.getRangeAt(0);
                $current = $(range.commonAncestorContainer);
            }

            // When user clicks on  editor's placeholder in FF, $current el is editor itself, not the first paragraph as it should
            if ($current.hasClass('medium-editor-insert-plugin')) {
                $current = $current.find('p:first');
            }

            $p = $current.is('p') ? $current : $current.closest('p');

            this.clean();

            if ($el.hasClass('medium-editor-placeholder') === false && $el.closest('.medium-insert-buttons').length === 0 && $current.closest('.medium-insert-buttons').length === 0) {

                this.$el.find('.medium-insert-active').removeClass('medium-insert-active');

                $.each(this.options.addons, function (addon) {
                    if ($el.closest('.medium-insert-' + addon).length) {
                        $current = $el;
                    }

                    if ($current.closest('.medium-insert-' + addon).length) {
                        $p = $current.closest('.medium-insert-' + addon);
                        activeAddon = addon;
                        return;
                    }
                });

                if ($p.length && (($p.text().trim() === '' && !activeAddon) || activeAddon === 'images')) {
                    $p.addClass('medium-insert-active');

                    if (activeAddon === 'images') {
                        this.$el.find('.medium-insert-buttons').attr('data-active-addon', activeAddon);
                    } else {
                        this.$el.find('.medium-insert-buttons').removeAttr('data-active-addon');
                    }

                    // If buttons are displayed on addon paragraph, wait 100ms for possible captions to display
                    setTimeout(function () {
                        that.positionButtons(activeAddon);
                        that.showButtons(activeAddon);
                    }, activeAddon ? 100 : 0);
                } else {
                    this.hideButtons();
                }
            }
        };

        /**
         * Show buttons
         *
         * @param {string} activeAddon - Name of active addon
         * @returns {void}
         */

        Core.prototype.showButtons = function (activeAddon) {
            var $buttons = this.$el.find('.medium-insert-buttons');

            $buttons.show();
            $buttons.find('li').show();

            if (activeAddon) {
                $buttons.find('li').hide();
                $buttons.find('button[data-addon="' + activeAddon + '"]').parent().show();
            }
        };

        /**
         * Hides buttons
         *
         * @param {jQuery} $el - Editor element
         * @returns {void}
         */

        Core.prototype.hideButtons = function ($el) {
            $el = $el || this.$el;

            $el.find('.medium-insert-buttons').hide();
            $el.find('.medium-insert-buttons-addons').hide();
            $el.find('.medium-insert-buttons-show').removeClass('medium-insert-buttons-rotate');
        };

        /**
         * Position buttons
         *
         * @param {string} activeAddon - Name of active addon
         * @return {void}
         */

        Core.prototype.positionButtons = function (activeAddon) {
            var $buttons = this.$el.find('.medium-insert-buttons'),
                $p = this.$el.find('.medium-insert-active'),
                $lastCaption = $p.hasClass('medium-insert-images-grid') ? [] : $p.find('figure:last figcaption'),
                elementsContainer = this.getEditor() ? this.getEditor().options.elementsContainer : $('body').get(0),
                elementsContainerAbsolute = ['absolute', 'fixed'].indexOf(window.getComputedStyle(elementsContainer).getPropertyValue('position')) > -1,
                position = {};

            if ($p.length) {
                position.left = $p.position().left;
                position.top = $p.position().top;

                if (activeAddon) {
                    position.left += $p.width() - $buttons.find('.medium-insert-buttons-show').width() - 10;
                    position.top += $p.height() - 20 + ($lastCaption.length ? -$lastCaption.height() - parseInt($lastCaption.css('margin-top'), 10) : 10);
                } else {
                    position.left += -parseInt($buttons.find('.medium-insert-buttons-addons').css('left'), 10) - parseInt($buttons.find('.medium-insert-buttons-addons button:first').css('margin-left'), 10);
                    position.top += parseInt($p.css('margin-top'), 10);
                }

                if (elementsContainerAbsolute) {
                    position.top += elementsContainer.scrollTop;
                }

                if (this.$el.hasClass('medium-editor-placeholder') === false && position.left < 0) {
                    position.left = $p.position().left;
                }

                $buttons.css(position);
            }
        };

        /**
         * Toggles addons buttons
         *
         * @return {void}
         */

        Core.prototype.toggleAddons = function () {
            if (this.$el.find('.medium-insert-buttons').attr('data-active-addon') === 'images') {
                this.$el.find('.medium-insert-buttons').find('button[data-addon="images"]').click();
                return;
            }

            this.$el.find('.medium-insert-buttons-addons').fadeToggle();
            this.$el.find('.medium-insert-buttons-show').toggleClass('medium-insert-buttons-rotate');
        };

        /**
         * Hide addons buttons
         *
         * @return {void}
         */

        Core.prototype.hideAddons = function () {
            this.$el.find('.medium-insert-buttons-addons').hide();
            this.$el.find('.medium-insert-buttons-show').removeClass('medium-insert-buttons-rotate');
        };

        /**
         * Call addon's action
         *
         * @param {Event} e
         * @return {void}
         */

        Core.prototype.addonAction = function (e) {
            var $a = $(e.currentTarget),
                addon = $a.data('addon'),
                action = $a.data('action');

            this.$el.data('plugin_' + pluginName + ucfirst(addon))[action]();
        };

        /**
         * Move caret at the beginning of the empty paragraph
         *
         * @param {jQuery} $el Element where to place the caret
         * @param {integer} position Position where to move caret. Default: 0
         *
         * @return {void}
         */

        Core.prototype.moveCaret = function ($el, position) {
            var range, sel, el, textEl;

            position = position || 0;
            range = document.createRange();
            sel = window.getSelection();
            el = $el.get(0);

            if (!el.childNodes.length) {
                textEl = document.createTextNode(' ');
                el.appendChild(textEl);
            }

            range.setStart(el.childNodes[0], position);
            range.collapse(true);
            sel.removeAllRanges();
            sel.addRange(range);
        };

        /**
         * Add caption
         *
         * @param {jQuery Element} $el
         * @param {string} placeholder
         * @return {void}
         */

        Core.prototype.addCaption = function ($el, placeholder) {
            var $caption = $el.find('figcaption');

            if ($caption.length === 0) {
                $el.append(this.templates['src/js/templates/core-caption.hbs']({
                    placeholder: placeholder
                }));
            }
        };

        /**
         * Remove captions
         *
         * @param {jQuery Element} $ignore
         * @return {void}
         */

        Core.prototype.removeCaptions = function ($ignore) {
            var $captions = this.$el.find('figcaption');

            if ($ignore) {
                $captions = $captions.not($ignore);
            }

            $captions.each(function () {
                if ($(this).hasClass('medium-insert-caption-placeholder') || $(this).text().trim() === '') {
                    $(this).remove();
                }
            });
        };

        /**
         * Remove caption placeholder
         *
         * @param {jQuery Element} $el
         * @return {void}
         */

        Core.prototype.removeCaptionPlaceholder = function ($el) {
            var $caption = $el.is('figcaption') ? $el : $el.find('figcaption');

            if ($caption.length) {
                $caption
                    .removeClass('medium-insert-caption-placeholder')
                    .removeAttr('data-placeholder');
            }
        };

        /** Plugin initialization */

        $.fn[pluginName] = function (options) {
            return this.each(function () {
                var that = this,
                    textareaId;

                if ($(that).is('textarea')) {
                    textareaId = $(that).attr('medium-editor-textarea-id');
                    that = $(that).siblings('[medium-editor-textarea-id="' + textareaId + '"]').get(0);
                }

                if (!$.data(that, 'plugin_' + pluginName)) {
                    // Plugin initialization
                    $.data(that, 'plugin_' + pluginName, new Core(that, options));
                    $.data(that, 'plugin_' + pluginName).init();
                } else if (typeof options === 'string' && $.data(that, 'plugin_' + pluginName)[options]) {
                    // Method call
                    $.data(that, 'plugin_' + pluginName)[options]();
                }
            });
        };

    })(jQuery, window, document);

    ; (function ($, window, document, undefined) {

        'use strict';

        /** Default values */
        var pluginName = 'mediumInsert',
            addonName = 'Embeds', // first char is uppercase
            defaults = {
                label: '<span class="fa fa-youtube-play"></span>',
                placeholder: 'Paste a YouTube, Vimeo, Facebook, Twitter or Instagram link and press Enter',
                oembedProxy: 'http://medium.iframe.ly/api/oembed?iframe=1',
                captions: true,
                captionPlaceholder: 'Type caption (optional)',
                storeMeta: false,
                styles: {
                    wide: {
                        label: '<span class="fa fa-align-justify"></span>'
                        // added: function ($el) {},
                        // removed: function ($el) {}
                    },
                    left: {
                        label: '<span class="fa fa-align-left"></span>'
                        // added: function ($el) {},
                        // removed: function ($el) {}
                    },
                    right: {
                        label: '<span class="fa fa-align-right"></span>'
                        // added: function ($el) {},
                        // removed: function ($el) {}
                    }
                },
                actions: {
                    remove: {
                        label: '<span class="fa fa-times"></span>',
                        clicked: function () {
                            var $event = $.Event('keydown');

                            $event.which = 8;
                            $(document).trigger($event);
                        }
                    }
                },
                parseOnPaste: false
            };

        /**
         * Embeds object
         *
         * Sets options, variables and calls init() function
         *
         * @constructor
         * @param {DOM} el - DOM element to init the plugin on
         * @param {object} options - Options to override defaults
         * @return {void}
         */

        function Embeds(el, options) {
            this.el = el;
            this.$el = $(el);
            this.templates = window.MediumInsert.Templates;
            this.core = this.$el.data('plugin_' + pluginName);

            this.options = $.extend(true, {}, defaults, options);

            this._defaults = defaults;
            this._name = pluginName;

            // Extend editor's functions
            if (this.core.getEditor()) {
                this.core.getEditor()._serializePreEmbeds = this.core.getEditor().serialize;
                this.core.getEditor().serialize = this.editorSerialize;
            }

            this.init();
        }

        /**
         * Initialization
         *
         * @return {void}
         */

        Embeds.prototype.init = function () {
            var $embeds = this.$el.find('.medium-insert-embeds');

            $embeds.attr('contenteditable', false);
            $embeds.each(function () {
                if ($(this).find('.medium-insert-embeds-overlay').length === 0) {
                    $(this).append($('<div />').addClass('medium-insert-embeds-overlay'));
                }
            });

            this.events();
            this.backwardsCompatibility();
        };

        /**
         * Event listeners
         *
         * @return {void}
         */

        Embeds.prototype.events = function () {
            $(document)
                .on('click', $.proxy(this, 'unselectEmbed'))
                .on('keydown', $.proxy(this, 'removeEmbed'))
                .on('click', '.medium-insert-embeds-toolbar .medium-editor-action', $.proxy(this, 'toolbarAction'))
                .on('click', '.medium-insert-embeds-toolbar2 .medium-editor-action', $.proxy(this, 'toolbar2Action'));

            this.$el
                .on('keyup click paste', $.proxy(this, 'togglePlaceholder'))
                .on('keydown', $.proxy(this, 'processLink'))
                .on('click', '.medium-insert-embeds-overlay', $.proxy(this, 'selectEmbed'))
                .on('contextmenu', '.medium-insert-embeds-placeholder', $.proxy(this, 'fixRightClickOnPlaceholder'));

            if (this.options.parseOnPaste) {
                this.$el
                    .on('paste', $.proxy(this, 'processPasted'));
            }

            $(window)
                .on('resize', $.proxy(this, 'autoRepositionToolbars'));
        };

        /**
         * Replace v0.* class names with new ones, wrap embedded content to new structure
         *
         * @return {void}
         */

        Embeds.prototype.backwardsCompatibility = function () {
            var that = this;

            this.$el.find('.mediumInsert-embeds')
                .removeClass('mediumInsert-embeds')
                .addClass('medium-insert-embeds');

            this.$el.find('.medium-insert-embeds').each(function () {
                if ($(this).find('.medium-insert-embed').length === 0) {
                    $(this).after(that.templates['src/js/templates/embeds-wrapper.hbs']({
                        html: $(this).html()
                    }));
                    $(this).remove();
                }
            });
        };

        /**
         * Extend editor's serialize function
         *
         * @return {object} Serialized data
         */

        Embeds.prototype.editorSerialize = function () {
            var data = this._serializePreEmbeds();

            $.each(data, function (key) {
                var $data = $('<div />').html(data[key].value),
                    $embeds = $data.find('.medium-insert-embeds');

                $embeds.removeAttr('contenteditable');
                $embeds.find('figcaption').removeAttr('contenteditable');
                $data.find('.medium-insert-embeds-overlay').remove();

                data[key].value = $data.html();
            });

            return data;
        };

        /**
         * Add embedded element
         *
         * @return {void}
         */

        Embeds.prototype.add = function () {
            var $place = this.$el.find('.medium-insert-active');

            // Fix #132
            // Make sure that the content of the paragraph is empty and <br> is wrapped in <p></p> to avoid Firefox problems
            $place.html(this.templates['src/js/templates/core-empty-line.hbs']().trim());

            // Replace paragraph with div to prevent #124 issue with pasting in Chrome,
            // because medium editor wraps inserted content into paragraph and paragraphs can't be nested
            if ($place.is('p')) {
                $place.replaceWith('<div class="medium-insert-active">' + $place.html() + '</div>');
                $place = this.$el.find('.medium-insert-active');
                this.core.moveCaret($place);
            }

            $place.addClass('medium-insert-embeds medium-insert-embeds-input medium-insert-embeds-active');

            this.togglePlaceholder({ target: $place.get(0) });

            $place.click();
            this.core.hideButtons();
        };

        /**
         * Toggles placeholder
         *
         * @param {Event} e
         * @return {void}
         */

        Embeds.prototype.togglePlaceholder = function (e) {
            var $place = $(e.target),
                selection = window.getSelection(),
                range, $current, text;

            if (!selection || selection.rangeCount === 0) {
                return;
            }

            range = selection.getRangeAt(0);
            $current = $(range.commonAncestorContainer);

            if ($current.hasClass('medium-insert-embeds-active')) {
                $place = $current;
            } else if ($current.closest('.medium-insert-embeds-active').length) {
                $place = $current.closest('.medium-insert-embeds-active');
            }

            if ($place.hasClass('medium-insert-embeds-active')) {

                text = $place.text().trim();

                if (text === '' && $place.hasClass('medium-insert-embeds-placeholder') === false) {
                    $place
                        .addClass('medium-insert-embeds-placeholder')
                        .attr('data-placeholder', this.options.placeholder);
                } else if (text !== '' && $place.hasClass('medium-insert-embeds-placeholder')) {
                    $place
                        .removeClass('medium-insert-embeds-placeholder')
                        .removeAttr('data-placeholder');
                }

            } else {
                this.$el.find('.medium-insert-embeds-active').remove();
            }
        };

        /**
         * Right click on placeholder in Chrome selects whole line. Fix this by placing caret at the end of line
         *
         * @param {Event} e
         * @return {void}
         */

        Embeds.prototype.fixRightClickOnPlaceholder = function (e) {
            this.core.moveCaret($(e.target));
        };

        /**
         * Process link
         *
         * @param {Event} e
         * @return {void}
         */

        Embeds.prototype.processLink = function (e) {
            var $place = this.$el.find('.medium-insert-embeds-active'),
                url;

            if (!$place.length) {
                return;
            }

            url = $place.text().trim();

            // Return empty placeholder on backspace, delete or enter
            if (url === '' && [8, 46, 13].indexOf(e.which) !== -1) {
                $place.remove();
                return;
            }

            if (e.which === 13) {
                e.preventDefault();
                e.stopPropagation();

                if (this.options.oembedProxy) {
                    this.oembed(url);
                } else {
                    this.parseUrl(url);
                }
            }
        };

        /**
         * Process Pasted
         *
         * @param {Event} e
         * @return {void}
         */

        Embeds.prototype.processPasted = function (e) {
            var pastedUrl, linkRegEx;
            if ($(".medium-insert-embeds-active").length) {
                return;
            }

            pastedUrl = e.originalEvent.clipboardData.getData('text');
            linkRegEx = new RegExp('^(http(s?):)?\/\/', 'i');
            if (linkRegEx.test(pastedUrl)) {
                if (this.options.oembedProxy) {
                    this.oembed(pastedUrl, true);
                } else {
                    this.parseUrl(pastedUrl, true);
                }
            }
        };

        /**
         * Get HTML via oEmbed proxy
         *
         * @param {string} url
         * @return {void}
         */

        Embeds.prototype.oembed = function (url, pasted) {
            var that = this;

            $.support.cors = true;

            $.ajax({
                crossDomain: true,
                cache: false,
                url: this.options.oembedProxy,
                dataType: 'json',
                data: {
                    url: url
                },
                success: function (data) {
                    var html = data && data.html;

                    if (that.options.storeMeta) {
                        html += '<div class="medium-insert-embeds-meta"><script type="text/json">' + JSON.stringify(data) + '</script></div>';
                    }

                    if (data && !html && data.type === 'photo' && data.url) {
                        html = '<img src="' + data.url + '" alt="IMAGEALTERNATIVE">';
                    }

                    if (!html) {
                        // Prevent render empty embed.
                        $.proxy(that, 'convertBadEmbed', url)();
                        return;
                    }

                    if (pasted) {
                        $.proxy(that, 'embed', html, url)();
                    } else {
                        $.proxy(that, 'embed', html)();
                    }
                },
                error: function (jqXHR, textStatus, errorThrown) {
                    var responseJSON = (function () {
                        try {
                            return JSON.parse(jqXHR.responseText);
                        } catch (e) { }
                    })();

                    if (typeof window.console !== 'undefined') {
                        window.console.log((responseJSON && responseJSON.error) || jqXHR.status || errorThrown.message);
                    } else {
                        window.alert('Error requesting media from ' + that.options.oembedProxy + ' to insert: ' + errorThrown + ' (response status: ' + jqXHR.status + ')');
                    }

                    $.proxy(that, 'convertBadEmbed', url)();
                }
            });
        };

        /**
         * Get HTML using regexp
         *
         * @param {string} url
         * @param {bool} pasted
         * @return {void}
         */

        Embeds.prototype.parseUrl = function (url, pasted) {
            var html;

            if (!(new RegExp(['youtube', 'youtu.be', 'vimeo', 'instagram', 'twitter', 'facebook'].join('|')).test(url))) {
                $.proxy(this, 'convertBadEmbed', url)();
                return false;
            }

            html = url.replace(/\n?/g, '')
                .replace(/^((http(s)?:\/\/)?(www\.)?(youtube\.com|youtu\.be)\/(watch\?v=|v\/)?)([a-zA-Z0-9\-_]+)(.*)?$/, '<div class="video video-youtube"><iframe width="420" height="315" src="//www.youtube.com/embed/$7" frameborder="0" allowfullscreen></iframe></div>')
                .replace(/^https?:\/\/vimeo\.com(\/.+)?\/([0-9]+)$/, '<div class="video video-vimeo"><iframe src="//player.vimeo.com/video/$2" width="500" height="281" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe></div>')
                .replace(/^https:\/\/twitter\.com\/(\w+)\/status\/(\d+)\/?$/, '<blockquote class="twitter-tweet" align="center" lang="en"><a href="https://twitter.com/$1/statuses/$2"></a></blockquote><script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>')
                .replace(/^(https:\/\/www\.facebook\.com\/(.*))$/, '<script src="//connect.facebook.net/en_US/sdk.js#xfbml=1&amp;version=v2.2" async></script><div class="fb-post" data-href="$1"><div class="fb-xfbml-parse-ignore"><a href="$1">Loading Facebook post...</a></div></div>')
                .replace(/^https?:\/\/instagram\.com\/p\/(.+)\/?$/, '<span class="instagram"><iframe src="//instagram.com/p/$1/embed/" width="612" height="710" frameborder="0" scrolling="no" allowtransparency="true"></iframe></span>');

            if (this.options.storeMeta) {
                html += '<div class="medium-insert-embeds-meta"><script type="text/json">' + JSON.stringify({}) + '</script></div>';
            }

            if ((/<("[^"]*"|'[^']*'|[^'">])*>/).test(html) === false) {
                $.proxy(this, 'convertBadEmbed', url)();
                return false;
            }

            if (pasted) {
                this.embed(html, url);
            } else {
                this.embed(html);
            }
        };

        /**
         * Add html to page
         *
         * @param {string} html
         * @param {string} pastedUrl
         * @return {void}
         */

        Embeds.prototype.embed = function (html, pastedUrl) {
            var $place = this.$el.find('.medium-insert-embeds-active'),
                $div;

            if (!html) {
                alert('Incorrect URL format specified');
                return false;
            } else {
                if (html.indexOf('</script>') > -1) {
                    // Store embed code with <script> tag inside wrapper attribute value.
                    // Make nice attribute value escaping using jQuery.
                    $div = $('<div>')
                        .attr('data-embed-code', $('<div />').text(html).html())
                        .html(html);
                    html = $('<div>').append($div).html();
                }

                if (pastedUrl) {
                    // Get the element with the pasted url
                    // place the embed template and remove the pasted text
                    $place = this.$el.find(":not(iframe, script, style)")
                        .contents().filter(
                            function () {
                                return this.nodeType === 3 && this.textContent.indexOf(pastedUrl) > -1;
                            }).parent();

                    $place.after(this.templates['src/js/templates/embeds-wrapper.hbs']({
                        html: html
                    }));
                    $place.text($place.text().replace(pastedUrl, ''));
                } else {
                    $place.after(this.templates['src/js/templates/embeds-wrapper.hbs']({
                        html: html
                    }));
                    $place.remove();
                }


                this.core.triggerInput();

                if (html.indexOf('facebook') !== -1) {
                    if (typeof (FB) !== 'undefined') {
                        setTimeout(function () {
                            FB.XFBML.parse();
                        }, 2000);
                    }
                }
            }
        };

        /**
         * Convert bad oEmbed content to an actual line.
         * Instead of displaying the error message we convert the bad embed
         *
         * @param {string} content Bad content
         *
         * @return {void}
         */
        Embeds.prototype.convertBadEmbed = function (content) {
            var $place, $empty, $content,
                emptyTemplate = this.templates['src/js/templates/core-empty-line.hbs']().trim();

            $place = this.$el.find('.medium-insert-embeds-active');

            // convert embed node to an empty node and insert the bad embed inside
            $content = $(emptyTemplate);
            $place.before($content);
            $place.remove();
            $content.html(content);

            // add an new empty node right after to simulate Enter press
            $empty = $(emptyTemplate);
            $content.after($empty);

            this.core.triggerInput();

            this.core.moveCaret($empty);
        };

        /**
         * Select clicked embed
         *
         * @param {Event} e
         * @returns {void}
         */

        Embeds.prototype.selectEmbed = function (e) {
            var that = this,
                $embed;
            if (this.core.options.enabled) {
                $embed = $(e.target).hasClass('medium-insert-embeds') ? $(e.target) : $(e.target).closest('.medium-insert-embeds');

                $embed.addClass('medium-insert-embeds-selected');

                setTimeout(function () {
                    that.addToolbar();

                    if (that.options.captions) {
                        that.core.addCaption($embed.find('figure'), that.options.captionPlaceholder);
                    }
                }, 50);
            }
        };

        /**
         * Unselect selected embed
         *
         * @param {Event} e
         * @returns {void}
         */

        Embeds.prototype.unselectEmbed = function (e) {
            var $el = $(e.target).hasClass('medium-insert-embeds') ? $(e.target) : $(e.target).closest('.medium-insert-embeds'),
                $embed = this.$el.find('.medium-insert-embeds-selected');

            if ($el.hasClass('medium-insert-embeds-selected')) {
                $embed.not($el).removeClass('medium-insert-embeds-selected');
                $('.medium-insert-embeds-toolbar, .medium-insert-embeds-toolbar2').remove();
                this.core.removeCaptions($el.find('figcaption'));

                if ($(e.target).is('.medium-insert-caption-placeholder') || $(e.target).is('figcaption')) {
                    $el.removeClass('medium-insert-embeds-selected');
                    this.core.removeCaptionPlaceholder($el.find('figure'));
                }
                return;
            }

            $embed.removeClass('medium-insert-embeds-selected');
            $('.medium-insert-embeds-toolbar, .medium-insert-embeds-toolbar2').remove();

            if ($(e.target).is('.medium-insert-caption-placeholder')) {
                this.core.removeCaptionPlaceholder($el.find('figure'));
            } else if ($(e.target).is('figcaption') === false) {
                this.core.removeCaptions();
            }
        };

        /**
         * Remove embed
         *
         * @param {Event} e
         * @returns {void}
         */

        Embeds.prototype.removeEmbed = function (e) {
            var $embed, $empty;

            if (e.which === 8 || e.which === 46) {
                $embed = this.$el.find('.medium-insert-embeds-selected');

                if ($embed.length) {
                    e.preventDefault();

                    $('.medium-insert-embeds-toolbar, .medium-insert-embeds-toolbar2').remove();

                    $empty = $(this.templates['src/js/templates/core-empty-line.hbs']().trim());
                    $embed.before($empty);
                    $embed.remove();

                    // Hide addons
                    this.core.hideAddons();

                    this.core.moveCaret($empty);
                    this.core.triggerInput();
                }
            }
        };

        /**
         * Adds embed toolbar to editor
         *
         * @returns {void}
         */

        Embeds.prototype.addToolbar = function () {
            var $embed = this.$el.find('.medium-insert-embeds-selected'),
                active = false,
                $toolbar, $toolbar2, mediumEditor, toolbarContainer;

            if ($embed.length === 0) {
                return;
            }

            mediumEditor = this.core.getEditor();
            toolbarContainer = mediumEditor.options.elementsContainer || 'body';

            $(toolbarContainer).append(this.templates['src/js/templates/embeds-toolbar.hbs']({
                styles: this.options.styles,
                actions: this.options.actions
            }).trim());

            $toolbar = $('.medium-insert-embeds-toolbar');
            $toolbar2 = $('.medium-insert-embeds-toolbar2');

            $toolbar.find('button').each(function () {
                if ($embed.hasClass('medium-insert-embeds-' + $(this).data('action'))) {
                    $(this).addClass('medium-editor-button-active');
                    active = true;
                }
            });

            if (active === false) {
                $toolbar.find('button').first().addClass('medium-editor-button-active');
            }

            this.repositionToolbars();
            $toolbar.fadeIn();
            $toolbar2.fadeIn();
        };

        Embeds.prototype.autoRepositionToolbars = function () {
            setTimeout(function () {
                this.repositionToolbars();
                this.repositionToolbars();
            }.bind(this), 0);
        };

        Embeds.prototype.repositionToolbars = function () {
            var $toolbar = $('.medium-insert-embeds-toolbar'),
                $toolbar2 = $('.medium-insert-embeds-toolbar2'),
                $embed = this.$el.find('.medium-insert-embeds-selected'),
                elementsContainer = this.core.getEditor().options.elementsContainer,
                elementsContainerAbsolute = ['absolute', 'fixed'].indexOf(window.getComputedStyle(elementsContainer).getPropertyValue('position')) > -1,
                elementsContainerBoundary = elementsContainerAbsolute ? elementsContainer.getBoundingClientRect() : null,
                containerWidth = $(window).width(),
                position = {};

            if ($toolbar2.length) {
                position.top = $embed.offset().top + 2; // 2px - distance from a border
                position.left = $embed.offset().left + $embed.width() - $toolbar2.width() - 4; // 4px - distance from a border

                if (elementsContainerAbsolute) {
                    position.top += elementsContainer.scrollTop - elementsContainerBoundary.top;
                    position.left -= elementsContainerBoundary.left;
                    containerWidth = $(elementsContainer).width();
                }

                if (position.left + $toolbar2.width() > containerWidth) {
                    position.left = containerWidth - $toolbar2.width();
                }

                $toolbar2.css(position);
            }

            if ($toolbar.length) {
                position.left = $embed.offset().left + $embed.width() / 2 - $toolbar.width() / 2;
                position.top = $embed.offset().top - $toolbar.height() - 8 - 2 - 5; // 8px - hight of an arrow under toolbar, 2px - height of an embed outset, 5px - distance from an embed

                if (elementsContainerAbsolute) {
                    position.top += elementsContainer.scrollTop - elementsContainerBoundary.top;
                    position.left -= elementsContainerBoundary.left;
                }

                if (position.top < 0) {
                    position.top = 0;
                }

                $toolbar.css(position);
            }
        };

        /**
         * Fires toolbar action
         *
         * @param {Event} e
         * @returns {void}
         */

        Embeds.prototype.toolbarAction = function (e) {
            var $button = $(e.target).is('button') ? $(e.target) : $(e.target).closest('button'),
                $li = $button.closest('li'),
                $ul = $li.closest('ul'),
                $lis = $ul.find('li'),
                $embed = this.$el.find('.medium-insert-embeds-selected'),
                that = this;

            $button.addClass('medium-editor-button-active');
            $li.siblings().find('.medium-editor-button-active').removeClass('medium-editor-button-active');

            $lis.find('button').each(function () {
                var className = 'medium-insert-embeds-' + $(this).data('action');

                if ($(this).hasClass('medium-editor-button-active')) {
                    $embed.addClass(className);

                    if (that.options.styles[$(this).data('action')].added) {
                        that.options.styles[$(this).data('action')].added($embed);
                    }
                } else {
                    $embed.removeClass(className);

                    if (that.options.styles[$(this).data('action')].removed) {
                        that.options.styles[$(this).data('action')].removed($embed);
                    }
                }
            });

            this.core.triggerInput();
        };

        /**
         * Fires toolbar2 action
         *
         * @param {Event} e
         * @returns {void}
         */

        Embeds.prototype.toolbar2Action = function (e) {
            var $button = $(e.target).is('button') ? $(e.target) : $(e.target).closest('button'),
                callback = this.options.actions[$button.data('action')].clicked;

            if (callback) {
                callback(this.$el.find('.medium-insert-embeds-selected'));
            }

            this.core.triggerInput();
        };

        /** Plugin initialization */

        $.fn[pluginName + addonName] = function (options) {
            return this.each(function () {
                if (!$.data(this, 'plugin_' + pluginName + addonName)) {
                    $.data(this, 'plugin_' + pluginName + addonName, new Embeds(this, options));
                }
            });
        };

    })(jQuery, window, document);

    /*global MediumEditor*/

    ; (function ($, window, document, Util, undefined) {

        'use strict';

        /** Default values */
        var pluginName = 'mediumInsert',
            addonName = 'Images', // first char is uppercase
            defaults = {
                label: '<span class="fa fa-camera"></span>',
                deleteMethod: 'POST',
                deleteScript: 'delete.php',
                preview: true,
                captions: true,
                captionPlaceholder: 'Type caption for image (optional)',
                autoGrid: 3,
                fileUploadOptions: { // See https://github.com/blueimp/jQuery-File-Upload/wiki/Options
                    url: null,
                    acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i
                },
                fileDeleteOptions: {},
                styles: {
                    wide: {
                        label: '<span class="fa fa-align-justify"></span>'
                        // added: function ($el) {},
                        // removed: function ($el) {}
                    },
                    left: {
                        label: '<span class="fa fa-align-left"></span>'
                        // added: function ($el) {},
                        // removed: function ($el) {}
                    },
                    right: {
                        label: '<span class="fa fa-align-right"></span>'
                        // added: function ($el) {},
                        // removed: function ($el) {}
                    },
                    grid: {
                        label: '<span class="fa fa-th"></span>'
                        // added: function ($el) {},
                        // removed: function ($el) {}
                    }
                },
                actions: {
                    remove: {
                        label: '<span class="fa fa-times"></span>',
                        clicked: function () {
                            var $event = $.Event('keydown');

                            $event.which = 8;
                            $(document).trigger($event);
                        }
                    }
                },
                sorting: function () {
                    var that = this;

                    $('.medium-insert-images').sortable({
                        group: 'medium-insert-images',
                        containerSelector: '.medium-insert-images',
                        itemSelector: 'figure',
                        placeholder: '<figure class="placeholder">',
                        handle: 'img',
                        nested: false,
                        vertical: false,
                        afterMove: function () {
                            that.core.triggerInput();
                        }
                    });
                },
                messages: {
                    acceptFileTypesError: 'This file is not in a supported format: ',
                    maxFileSizeError: 'This file is too big: '
                }
                // uploadError: function($el, data) {}
                // uploadCompleted: function ($el, data) {}
            };

        /**
         * Images object
         *
         * Sets options, variables and calls init() function
         *
         * @constructor
         * @param {DOM} el - DOM element to init the plugin on
         * @param {object} options - Options to override defaults
         * @return {void}
         */

        function Images(el, options) {
            this.el = el;
            this.$el = $(el);
            this.$currentImage = null;
            this.templates = window.MediumInsert.Templates;
            this.core = this.$el.data('plugin_' + pluginName);

            this.options = $.extend(true, {}, defaults, options);

            this._defaults = defaults;
            this._name = pluginName;

            // Allow image preview only in browsers, that support's that
            if (this.options.preview && !window.FileReader) {
                this.options.preview = false;
            }

            // Extend editor's functions
            if (this.core.getEditor()) {
                this.core.getEditor()._serializePreImages = this.core.getEditor().serialize;
                this.core.getEditor().serialize = this.editorSerialize;
            }

            this.init();
        }

        /**
         * Initialization
         *
         * @return {void}
         */

        Images.prototype.init = function () {
            var $images = this.$el.find('.medium-insert-images');

            $images.find('figcaption').attr('contenteditable', true);
            $images.find('figure').attr('contenteditable', false);

            this.events();
            this.backwardsCompatibility();
            this.sorting();
        };

        /**
         * Event listeners
         *
         * @return {void}
         */

        Images.prototype.events = function () {
            $(document)
                .on('click', $.proxy(this, 'unselectImage'))
                .on('keydown', $.proxy(this, 'removeImage'))
                .on('click', '.medium-insert-images-toolbar .medium-editor-action', $.proxy(this, 'toolbarAction'))
                .on('click', '.medium-insert-images-toolbar2 .medium-editor-action', $.proxy(this, 'toolbar2Action'));

            this.$el
                .on('click', '.medium-insert-images img', $.proxy(this, 'selectImage'));

            $(window)
                .on('resize', $.proxy(this, 'autoRepositionToolbars'));
        };

        /**
         * Replace v0.* class names with new ones
         *
         * @return {void}
         */

        Images.prototype.backwardsCompatibility = function () {
            this.$el.find('.mediumInsert')
                .removeClass('mediumInsert')
                .addClass('medium-insert-images');

            this.$el.find('.medium-insert-images.small')
                .removeClass('small')
                .addClass('medium-insert-images-left');
        };

        /**
         * Extend editor's serialize function
         *
         * @return {object} Serialized data
         */

        Images.prototype.editorSerialize = function () {
            var data = this._serializePreImages();

            $.each(data, function (key) {
                var $data = $('<div />').html(data[key].value);

                $data.find('.medium-insert-images').find('figcaption, figure').removeAttr('contenteditable');
                $data.find('.medium-insert-images-progress').remove();

                data[key].value = $data.html();
            });

            return data;
        };

        /**
         * Add image
         *
         * @return {void}
         */

        Images.prototype.add = function () {
            var that = this,
                $file = $(this.templates['src/js/templates/images-fileupload.hbs']()),
                fileUploadOptions = {
                    dataType: 'json',
                    add: function (e, data) {
                        $.proxy(that, 'uploadAdd', e, data)();
                    },
                    done: function (e, data) {
                        $.proxy(that, 'uploadDone', e, data)();
                    }
                };

            // Only add progress callbacks for browsers that support XHR2,
            // and test for XHR2 per:
            // http://stackoverflow.com/questions/6767887/
            // what-is-the-best-way-to-check-for-xhr2-file-upload-support
            if (new XMLHttpRequest().upload) {
                fileUploadOptions.progress = function (e, data) {
                    $.proxy(that, 'uploadProgress', e, data)();
                };

                fileUploadOptions.progressall = function (e, data) {
                    $.proxy(that, 'uploadProgressall', e, data)();
                };
            }

            $file.fileupload($.extend(true, {}, this.options.fileUploadOptions, fileUploadOptions));

            $file.click();
        };

        /**
         * Callback invoked as soon as files are added to the fileupload widget - via file input selection, drag & drop or add API call.
         * https://github.com/blueimp/jQuery-File-Upload/wiki/Options#add
         *
         * @param {Event} e
         * @param {object} data
         * @return {void}
         */

        Images.prototype.uploadAdd = function (e, data) {
            var $place = this.$el.find('.medium-insert-active'),
                that = this,
                uploadErrors = [],
                file = data.files[0],
                acceptFileTypes = this.options.fileUploadOptions.acceptFileTypes,
                maxFileSize = this.options.fileUploadOptions.maxFileSize,
                reader;

            if (acceptFileTypes && !acceptFileTypes.test(file.type)) {
                uploadErrors.push(this.options.messages.acceptFileTypesError + file.name);
            } else if (maxFileSize && file.size > maxFileSize) {
                uploadErrors.push(this.options.messages.maxFileSizeError + file.name);
            }
            if (uploadErrors.length > 0) {
                if (this.options.uploadFailed && typeof this.options.uploadFailed === "function") {
                    this.options.uploadFailed(uploadErrors, data);

                    return;
                }

                alert(uploadErrors.join("\n"));

                return;
            }

            this.core.hideButtons();

            // Replace paragraph with div, because figure elements can't be inside paragraph
            if ($place.is('p')) {
                $place.replaceWith('<div class="medium-insert-active">' + $place.html() + '</div>');
                $place = this.$el.find('.medium-insert-active');
                if ($place.next().is('p')) {
                    this.core.moveCaret($place.next());
                } else {
                    $place.after('<p><br></p>'); // add empty paragraph so we can move the caret to the next line.
                    this.core.moveCaret($place.next());
                }
            }

            $place.addClass('medium-insert-images');

            if (this.options.preview === false && $place.find('progress').length === 0 && (new XMLHttpRequest().upload)) {
                $place.append(this.templates['src/js/templates/images-progressbar.hbs']());
            }

            if (data.autoUpload || (data.autoUpload !== false && $(e.target).fileupload('option', 'autoUpload'))) {
                data.process().done(function () {
                    // If preview is set to true, let the showImage handle the upload start
                    if (that.options.preview) {
                        reader = new FileReader();

                        reader.onload = function (e) {
                            $.proxy(that, 'showImage', e.target.result, data)();
                        };

                        reader.readAsDataURL(data.files[0]);
                    } else {
                        data.submit();
                    }
                });
            }
        };

        /**
         * Callback for global upload progress events
         * https://github.com/blueimp/jQuery-File-Upload/wiki/Options#progressall
         *
         * @param {Event} e
         * @param {object} data
         * @return {void}
         */

        Images.prototype.uploadProgressall = function (e, data) {
            var progress, $progressbar;

            if (this.options.preview === false) {
                progress = parseInt(data.loaded / data.total * 100, 10);
                $progressbar = this.$el.find('.medium-insert-active').find('progress');

                $progressbar
                    .attr('value', progress)
                    .text(progress);

                if (progress === 100) {
                    $progressbar.remove();
                }
            }
        };

        /**
         * Callback for upload progress events.
         * https://github.com/blueimp/jQuery-File-Upload/wiki/Options#progress
         *
         * @param {Event} e
         * @param {object} data
         * @return {void}
         */

        Images.prototype.uploadProgress = function (e, data) {
            var progress, $progressbar;

            if (this.options.preview) {
                progress = 100 - parseInt(data.loaded / data.total * 100, 10);
                $progressbar = data.context.find('.medium-insert-images-progress');

                $progressbar.css('width', progress + '%');

                if (progress === 0) {
                    $progressbar.remove();
                }
            }
        };

        /**
         * Callback for successful upload requests.
         * https://github.com/blueimp/jQuery-File-Upload/wiki/Options#done
         *
         * @param {Event} e
         * @param {object} data
         * @return {void}
         */

        Images.prototype.uploadDone = function (e, data) {
            $.proxy(this, 'showImage', data.result.object, data)();

            this.core.clean();
            this.sorting();
        };

        /**
         * Add uploaded / preview image to DOM
         *
         * @param {string} img
         * @returns {void}
         */

        Images.prototype.showImage = function (img, data) {
            var $place = this.$el.find('.medium-insert-active'),
                domImage,
                that;

            // Hide editor's placeholder
            $place.click();

            // If preview is allowed and preview image already exists,
            // replace it with uploaded image
            that = this;
            if (this.options.preview && data.context) {
                domImage = this.getDOMImage();
                domImage.onload = function () {
                    data.context.find('img').attr('src', img);

                    if (this.options.uploadCompleted) {
                        this.options.uploadCompleted(data.context, data);
                    }

                    that.core.triggerInput();
                }.bind(this);
                domImage.src = img;
            } else {
                data.context = $(this.templates['src/js/templates/images-image.hbs']({
                    img: img,
                    progress: this.options.preview
                })).appendTo($place);

                $place.find('br').remove();

                if (this.options.autoGrid && $place.find('figure').length >= this.options.autoGrid) {
                    $.each(this.options.styles, function (style, options) {
                        var className = 'medium-insert-images-' + style;

                        $place.removeClass(className);

                        if (options.removed) {
                            options.removed($place);
                        }
                    });

                    $place.addClass('medium-insert-images-grid');

                    if (this.options.styles.grid.added) {
                        this.options.styles.grid.added($place);
                    }
                }

                if (this.options.preview) {
                    data.submit();
                } else if (this.options.uploadCompleted) {
                    this.options.uploadCompleted(data.context, data);
                }
            }

            this.core.triggerInput();

            return data.context;
        };

        Images.prototype.getDOMImage = function () {
            return new window.Image();
        };

        /**
         * Select clicked image
         *
         * @param {Event} e
         * @returns {void}
         */

        Images.prototype.selectImage = function (e) {
            var that = this,
                $image;

            if (this.core.options.enabled) {
                $image = $(e.target);

                this.$currentImage = $image;

                // Hide keyboard on mobile devices
                this.$el.blur();

                $image.addClass('medium-insert-image-active');
                $image.closest('.medium-insert-images').addClass('medium-insert-active');

                setTimeout(function () {
                    that.addToolbar();

                    if (that.options.captions) {
                        that.core.addCaption($image.closest('figure'), that.options.captionPlaceholder);
                    }
                }, 50);
            }
        };

        /**
         * Unselect selected image
         *
         * @param {Event} e
         * @returns {void}
         */

        Images.prototype.unselectImage = function (e) {
            var $el = $(e.target),
                $image = this.$el.find('.medium-insert-image-active');

            if ($el.is('img') && $el.hasClass('medium-insert-image-active')) {
                $image.not($el).removeClass('medium-insert-image-active');
                $('.medium-insert-images-toolbar, .medium-insert-images-toolbar2').remove();
                this.core.removeCaptions($el);
                return;
            }

            $image.removeClass('medium-insert-image-active');
            $('.medium-insert-images-toolbar, .medium-insert-images-toolbar2').remove();

            if ($el.is('.medium-insert-caption-placeholder')) {
                this.core.removeCaptionPlaceholder($image.closest('figure'));
            } else if ($el.is('figcaption') === false) {
                this.core.removeCaptions();
            }
            this.$currentImage = null;
        };

        /**
         * Remove image
         *
         * @param {Event} e
         * @returns {void}
         */

        Images.prototype.removeImage = function (e) {
            var images = [],
                $selectedImage = this.$el.find('.medium-insert-image-active'),
                $parent, $empty, selection, range, current, caretPosition, $current, $sibling, selectedHtml, i;

            if (e.which === 8 || e.which === 46) {
                if ($selectedImage.length) {
                    images.push($selectedImage);
                }

                // Remove image even if it's not selected, but backspace/del is pressed in text
                selection = window.getSelection();
                if (selection && selection.rangeCount) {
                    range = selection.getRangeAt(0);
                    current = range.commonAncestorContainer;
                    $current = current.nodeName === '#text' || current.nodeName === 'BR' ? $(current).parent() : $(current);
                    caretPosition = MediumEditor.selection.getCaretOffsets(current).left;

                    // Is backspace pressed and caret is at the beginning of a paragraph, get previous element
                    if (e.which === 8 && caretPosition === 0) {
                        $sibling = $current.prev();
                        // Is del pressed and caret is at the end of a paragraph, get next element
                    } else if (e.which === 46 && caretPosition === $current.text().length) {
                        $sibling = $current.next();
                    }

                    if ($sibling && $sibling.hasClass('medium-insert-images')) {
                        images.push($sibling.find('img'));
                    }

                    // If text is selected, find images in the selection
                    selectedHtml = MediumEditor.selection.getSelectionHtml(document);
                    if (selectedHtml) {
                        $('<div></div>').html(selectedHtml).find('.medium-insert-images img').each(function () {
                            images.push($(this));
                        });
                    }
                }

                if (images.length) {
                    for (i = 0; i < images.length; i++) {
                        this.deleteFile(images[i].attr('src'), images[i]);

                        $parent = images[i].closest('.medium-insert-images');
                        images[i].closest('figure').remove();

                        if ($parent.find('figure').length === 0) {
                            $empty = $parent.next();
                            if ($empty.is('p') === false || $empty.text() !== '') {
                                $empty = $(this.templates['src/js/templates/core-empty-line.hbs']().trim());
                                $parent.before($empty);
                            }
                            $parent.remove();
                        }
                    }

                    // Hide addons
                    this.core.hideAddons();
                    if (!selectedHtml && $empty) {
                        e.preventDefault();
                        this.core.moveCaret($empty);
                    }

                    $('.medium-insert-images-toolbar, .medium-insert-images-toolbar2').remove();
                    this.core.triggerInput();
                }
            }
        };

        /**
         * Makes ajax call to deleteScript
         *
         * @param {string} file The name of the file to delete
         * @param {jQuery} $el The jQuery element of the file to delete
         * @returns {void}
         */

        Images.prototype.deleteFile = function (file, $el) {
            // only take action if there is a truthy value
            if (this.options.deleteScript) {
                // try to run it as a callback
                if (typeof this.options.deleteScript === 'function') {
                    this.options.deleteScript(file, $el);
                    // otherwise, it's probably a string, call it as ajax
                } else {
                    $.ajax($.extend(true, {}, {
                        url: this.options.deleteScript,
                        type: this.options.deleteMethod || 'POST',
                        data: { file: file }
                    }, this.options.fileDeleteOptions));
                }
            }
        };

        /**
         * Adds image toolbar to editor
         *
         * @returns {void}
         */

        Images.prototype.addToolbar = function () {
            var mediumEditor2 = this.core.getEditor();
            console.log(mediumEditor2);
            var $image = this.$el.find('.medium-insert-image-active'),
                $p = $image.closest('.medium-insert-images'),
                active = false,
                mediumEditor = this.core.getEditor(),
                toolbarContainer = mediumEditor.options.elementsContainer || 'body',
                $toolbar, $toolbar2;

            $(toolbarContainer).append(this.templates['src/js/templates/images-toolbar.hbs']({
                styles: this.options.styles,
                actions: this.options.actions
            }).trim());

            $toolbar = $('.medium-insert-images-toolbar');
            $toolbar2 = $('.medium-insert-images-toolbar2');

            $toolbar.find('button').each(function () {
                if ($p.hasClass('medium-insert-images-' + $(this).data('action'))) {
                    $(this).addClass('medium-editor-button-active');
                    active = true;
                }
            });

            if (active === false) {
                $toolbar.find('button').first().addClass('medium-editor-button-active');
            }

            this.repositionToolbars();

            $toolbar.fadeIn();
            $toolbar2.fadeIn();
        };

        Images.prototype.autoRepositionToolbars = function () {
            setTimeout(function () {
                this.repositionToolbars();
                this.repositionToolbars();
            }.bind(this), 0);
        };

        Images.prototype.repositionToolbars = function () {
            var $toolbar = $('.medium-insert-images-toolbar'),
                $toolbar2 = $('.medium-insert-images-toolbar2'),
                $image = this.$el.find('.medium-insert-image-active'),
                elementsContainer = this.core.getEditor().options.elementsContainer,
                elementsContainerAbsolute = ['absolute', 'fixed'].indexOf(window.getComputedStyle(elementsContainer).getPropertyValue('position')) > -1,
                elementsContainerBoundary = elementsContainerAbsolute ? elementsContainer.getBoundingClientRect() : null,
                containerWidth = $(window).width(),
                position = {};

            if ($toolbar2.length) {
                position.top = $image.offset().top + 2;
                position.left = $image.offset().left + $image.width() - $toolbar2.width() - 4; // 4px - distance from a border

                if (elementsContainerAbsolute) {
                    position.top += elementsContainer.scrollTop - elementsContainerBoundary.top;
                    position.left -= elementsContainerBoundary.left;
                    containerWidth = $(elementsContainer).width();
                }

                if (position.left + $toolbar2.width() > containerWidth) {
                    position.left = containerWidth - $toolbar2.width();
                }

                $toolbar2.css(position);
            }

            if ($toolbar.length) {
                if ($image.closest('.medium-insert-images-grid-active').length) {
                    $image = $image.closest('.medium-insert-images-grid-active');
                }

                position.top = $image.offset().top - $toolbar.height() - 8 - 2 - 5; // 8px - hight of an arrow under toolbar, 2px - height of an image outset, 5px - distance from an image
                position.left = $image.offset().left + $image.width() / 2 - $toolbar.width() / 2;

                if (elementsContainerAbsolute) {
                    position.top += elementsContainer.scrollTop - elementsContainerBoundary.top;
                    position.left -= elementsContainerBoundary.left;
                }

                if (position.top < 0) {
                    position.top = 0;
                }

                $toolbar.css(position);
            }
        };

        /**
         * Fires toolbar action
         *
         * @param {Event} e
         * @returns {void}
         */

        Images.prototype.toolbarAction = function (e) {
            var that = this,
                $button, $li, $ul, $lis, $p;

            if (this.$currentImage === null) {
                return;
            }

            $button = $(e.target).is('button') ? $(e.target) : $(e.target).closest('button');
            $li = $button.closest('li');
            $ul = $li.closest('ul');
            $lis = $ul.find('li');
            $p = this.$el.find('.medium-insert-active');

            $button.addClass('medium-editor-button-active');
            $li.siblings().find('.medium-editor-button-active').removeClass('medium-editor-button-active');

            $lis.find('button').each(function () {
                var className = 'medium-insert-images-' + $(this).data('action');

                if ($(this).hasClass('medium-editor-button-active')) {
                    $p.addClass(className);

                    if (that.options.styles[$(this).data('action')].added) {
                        that.options.styles[$(this).data('action')].added($p);
                    }
                } else {
                    $p.removeClass(className);

                    if (that.options.styles[$(this).data('action')].removed) {
                        that.options.styles[$(this).data('action')].removed($p);
                    }
                }
            });

            this.core.hideButtons();

            this.core.triggerInput();
        };

        /**
         * Fires toolbar2 action
         *
         * @param {Event} e
         * @returns {void}
         */

        Images.prototype.toolbar2Action = function (e) {
            var $button, callback;

            if (this.$currentImage === null) {
                return;
            }

            $button = $(e.target).is('button') ? $(e.target) : $(e.target).closest('button');
            callback = this.options.actions[$button.data('action')].clicked;

            if (callback) {
                callback(this.$el.find('.medium-insert-image-active'));
            }

            this.core.hideButtons();

            this.core.triggerInput();
        };

        /**
         * Initialize sorting
         *
         * @returns {void}
         */

        Images.prototype.sorting = function () {
            $.proxy(this.options.sorting, this)();
        };

        /** Plugin initialization */

        $.fn[pluginName + addonName] = function (options) {
            return this.each(function () {
                if (!$.data(this, 'plugin_' + pluginName + addonName)) {
                    $.data(this, 'plugin_' + pluginName + addonName, new Images(this, options));
                }
            });
        };

    })(jQuery, window, document, MediumEditor.util);

}));
/*
 * Toastr
 * Copyright 2012-2014 John Papa and Hans Fjällemark.
 * All Rights Reserved.
 * Use, reproduction, distribution, and modification of this code is subject to the terms and
 * conditions of the MIT license, available at http://www.opensource.org/licenses/mit-license.php
 *
 * Author: John Papa and Hans Fjällemark
 * ARIA Support: Greta Krafsig
 * Project: https://github.com/CodeSeven/toastr
 */
; (function (define) {
    define(['jquery'], function ($) {
        return (function () {
            var $container;
            var listener;
            var toastId = 0;
            var toastType = {
                error: 'error',
                info: 'info',
                success: 'success',
                warning: 'warning'
            };

            var toastr = {
                clear: clear,
                remove: remove,
                error: error,
                getContainer: getContainer,
                info: info,
                options: {},
                subscribe: subscribe,
                success: success,
                version: '2.0.3',
                warning: warning
            };

            return toastr;

            //#region Accessible Methods
            function error(message, title, optionsOverride) {
                return notify({
                    type: toastType.error,
                    iconClass: getOptions().iconClasses.error,
                    message: message,
                    optionsOverride: optionsOverride,
                    title: title
                });
            }

            function getContainer(options, create) {
                if (!options) { options = getOptions(); }
                $container = $('#' + options.containerId);
                if ($container.length) {
                    return $container;
                }
                if(create) {
                    $container = createContainer(options);
                }
                return $container;
            }

            function info(message, title, optionsOverride) {
                return notify({
                    type: toastType.info,
                    iconClass: getOptions().iconClasses.info,
                    message: message,
                    optionsOverride: optionsOverride,
                    title: title
                });
            }

            function subscribe(callback) {
                listener = callback;
            }

            function success(message, title, optionsOverride) {
                return notify({
                    type: toastType.success,
                    iconClass: getOptions().iconClasses.success,
                    message: message,
                    optionsOverride: optionsOverride,
                    title: title
                });
            }

            function warning(message, title, optionsOverride) {
                return notify({
                    type: toastType.warning,
                    iconClass: getOptions().iconClasses.warning,
                    message: message,
                    optionsOverride: optionsOverride,
                    title: title
                });
            }

            function clear($toastElement) {
                var options = getOptions();
                if (!$container) { getContainer(options); }
                if (!clearToast($toastElement, options)) {
                    clearContainer(options);
                }
            }

            function remove($toastElement) {
                var options = getOptions();
                if (!$container) { getContainer(options); }
                if ($toastElement && $(':focus', $toastElement).length === 0) {
                    removeToast($toastElement);
                    return;
                }
                if ($container.children().length) {
                    $container.remove();
                }
            }
            //#endregion

            //#region Internal Methods

            function clearContainer(options){
                var toastsToClear = $container.children();
                for (var i = toastsToClear.length - 1; i >= 0; i--) {
                    clearToast($(toastsToClear[i]), options);
                };
            }

            function clearToast($toastElement, options){
                if ($toastElement && $(':focus', $toastElement).length === 0) {
                    $toastElement[options.hideMethod]({
                        duration: options.hideDuration,
                        easing: options.hideEasing,
                        complete: function () { removeToast($toastElement); }
                    });
                    return true;
                }
                return false;
            }

            function createContainer(options) {
                $container = $('<div/>')
                    .attr('id', options.containerId)
                    .addClass(options.positionClass)
                    .attr('aria-live', 'polite')
                    .attr('role', 'alert');

                $container.appendTo($(options.target));
                return $container;
            }

            function getDefaults() {
                return {
                    tapToDismiss: true,
                    toastClass: 'toast',
                    containerId: 'toast-container',
                    debug: false,

                    showMethod: 'fadeIn', //fadeIn, slideDown, and show are built into jQuery
                    showDuration: 300,
                    showEasing: 'swing', //swing and linear are built into jQuery
                    onShown: undefined,
                    hideMethod: 'fadeOut',
                    hideDuration: 1000,
                    hideEasing: 'swing',
                    onHidden: undefined,

                    extendedTimeOut: 1000,
                    iconClasses: {
                        error: 'toast-error',
                        info: 'toast-info',
                        success: 'toast-success',
                        warning: 'toast-warning'
                    },
                    iconClass: 'toast-info',
                    positionClass: 'toast-top-right',
                    timeOut: 5000, // Set timeOut and extendedTimeout to 0 to make it sticky
                    titleClass: 'toast-title',
                    messageClass: 'toast-message',
                    target: 'body',
                    closeHtml: '<button>&times;</button>',
                    newestOnTop: true
                };
            }

            function publish(args) {
                if (!listener) { return; }
                listener(args);
            }

            function notify(map) {
                var options = getOptions(),
                    iconClass = map.iconClass || options.iconClass;

                if (typeof (map.optionsOverride) !== 'undefined') {
                    options = $.extend(options, map.optionsOverride);
                    iconClass = map.optionsOverride.iconClass || iconClass;
                }

                toastId++;

                $container = getContainer(options, true);
                var intervalId = null,
                    $toastElement = $('<div/>'),
                    $titleElement = $('<div/>'),
                    $messageElement = $('<div/>'),
                    $closeElement = $(options.closeHtml),
                    response = {
                        toastId: toastId,
                        state: 'visible',
                        startTime: new Date(),
                        options: options,
                        map: map
                    };

                if (map.iconClass) {
                    $toastElement.addClass(options.toastClass).addClass(iconClass);
                }

                if (map.title) {
                    $titleElement.append(map.title).addClass(options.titleClass);
                    $toastElement.append($titleElement);
                }

                if (map.message) {
                    $messageElement.append(map.message).addClass(options.messageClass);
                    $toastElement.append($messageElement);
                }

                if (options.closeButton) {
                    $closeElement.addClass('toast-close-button').attr("role", "button");
                    $toastElement.prepend($closeElement);
                }

                $toastElement.hide();
                if (options.newestOnTop) {
                    $container.prepend($toastElement);
                } else {
                    $container.append($toastElement);
                }


                $toastElement[options.showMethod](
                    { duration: options.showDuration, easing: options.showEasing, complete: options.onShown }
                );

                if (options.timeOut > 0) {
                    intervalId = setTimeout(hideToast, options.timeOut);
                }

                $toastElement.hover(stickAround, delayedHideToast);
                if (!options.onclick && options.tapToDismiss) {
                    $toastElement.click(hideToast);
                }

                if (options.closeButton && $closeElement) {
                    $closeElement.click(function (event) {
                        if( event.stopPropagation ) {
                            event.stopPropagation();
                        } else if( event.cancelBubble !== undefined && event.cancelBubble !== true ) {
                            event.cancelBubble = true;
                        }
                        hideToast(true);
                    });
                }

                if (options.onclick) {
                    $toastElement.click(function () {
                        options.onclick();
                        hideToast();
                    });
                }

                publish(response);

                if (options.debug && console) {
                    console.log(response);
                }

                return $toastElement;

                function hideToast(override) {
                    if ($(':focus', $toastElement).length && !override) {
                        return;
                    }
                    return $toastElement[options.hideMethod]({
                        duration: options.hideDuration,
                        easing: options.hideEasing,
                        complete: function () {
                            removeToast($toastElement);
                            if (options.onHidden && response.state !== 'hidden') {
                                options.onHidden();
                            }
                            response.state = 'hidden';
                            response.endTime = new Date();
                            publish(response);
                        }
                    });
                }

                function delayedHideToast() {
                    if (options.timeOut > 0 || options.extendedTimeOut > 0) {
                        intervalId = setTimeout(hideToast, options.extendedTimeOut);
                    }
                }

                function stickAround() {
                    clearTimeout(intervalId);
                    $toastElement.stop(true, true)[options.showMethod](
                        { duration: options.showDuration, easing: options.showEasing }
                    );
                }
            }

            function getOptions() {
                return $.extend({}, getDefaults(), toastr.options);
            }

            function removeToast($toastElement) {
                if (!$container) { $container = getContainer(); }
                if ($toastElement.is(':visible')) {
                    return;
                }
                $toastElement.remove();
                $toastElement = null;
                if ($container.children().length === 0) {
                    $container.remove();
                }
            }
            //#endregion

        })();
    });
}(typeof define === 'function' && define.amd ? define : function (deps, factory) {
    if (typeof module !== 'undefined' && module.exports) { //Node
        module.exports = factory(require('jquery'));
    } else {
        window['toastr'] = factory(window['jQuery']);
    }
}));
'use strict';

/*
 * AngularJS Toaster
 * Version: 0.4.3
 *
 * Copyright 2013 Jiri Kavulak.  
 * All Rights Reserved.  
 * Use, reproduction, distribution, and modification of this code is subject to the terms and 
 * conditions of the MIT license, available at http://www.opensource.org/licenses/mit-license.php
 *
 * Author: Jiri Kavulak
 * Related to project of John Papa and Hans Fjällemark
 */

angular.module('toaster', ['ngAnimate'])
.service('toaster', ['$rootScope', function ($rootScope) {
    this.pop = function (type, title, body, timeout, bodyOutputType) {
        this.toast = {
            type: type,
            title: title,
            body: body,
            timeout: timeout,
            bodyOutputType: bodyOutputType
        };
        $rootScope.$broadcast('toaster-newToast');
    };
    this.success = function (body, timeout, bodyOutputType) {
        this.toast = {
            type: "success",
            title: "",
            body: body,
            timeout: timeout,
            bodyOutputType: bodyOutputType
        };
        $rootScope.$broadcast('toaster-newToast');
    };
    this.error = function (body, timeout, bodyOutputType) {
        this.toast = {
            type: "error",
            title: "",
            body: body,
            timeout: timeout,
            bodyOutputType: bodyOutputType
        };
        $rootScope.$broadcast('toaster-newToast');
    };
    this.info = function (body, timeout, bodyOutputType) {
        this.toast = {
            type: "info",
            title: "",
            body: body,
            timeout: timeout,
            bodyOutputType: bodyOutputType
        };
        $rootScope.$broadcast('toaster-newToast');
    };
    this.warning = function (body, timeout, bodyOutputType) {
        this.toast = {
            type: "warning",
            title: "",
            body: body,
            timeout: timeout,
            bodyOutputType: bodyOutputType
        };
        $rootScope.$broadcast('toaster-newToast');
    };

    this.clear = function () {
        $rootScope.$broadcast('toaster-clearToasts');
    };
}])
.constant('toasterConfig', {
    'limit': 10,                   // limits max number of toasts 
    'tap-to-dismiss': true,
    'newest-on-top': true,
    //'fade-in': 1000,            // done in css
    //'on-fade-in': undefined,    // not implemented
    //'fade-out': 1000,           // done in css
    // 'on-fade-out': undefined,  // not implemented
    //'extended-time-out': 1000,    // not implemented
    'time-out': 5000, // Set timeOut and extendedTimeout to 0 to make it sticky
    'icon-classes': {
        error: 'toast-error',
        info: 'toast-info',
        success: 'toast-success',
        warning: 'toast-warning'
    },
    'body-output-type': '', // Options: '', 'trustedHtml', 'template'
    'body-template': 'toasterBodyTmpl.html',
    'icon-class': 'toast-info',
    'position-class': 'toast-top-right',
    'title-class': 'toast-title',
    'message-class': 'toast-message'
})
.directive('toasterContainer', ['$compile', '$timeout', '$sce', 'toasterConfig', 'toaster',
function ($compile, $timeout, $sce, toasterConfig, toaster) {
    return {
        replace: true,
        restrict: 'EA',
        link: function (scope, elm, attrs) {

            var id = 0;

            var mergedConfig = toasterConfig;
            if (attrs.toasterOptions) {
                angular.extend(mergedConfig, scope.$eval(attrs.toasterOptions));
            }

            scope.config = {
                position: mergedConfig['position-class'],
                title: mergedConfig['title-class'],
                message: mergedConfig['message-class'],
                tap: mergedConfig['tap-to-dismiss']
            };

            function addToast(toast) {
                toast.type = mergedConfig['icon-classes'][toast.type];
                if (!toast.type)
                    toast.type = mergedConfig['icon-class'];

                id++;
                angular.extend(toast, { id: id });

                // Set the toast.bodyOutputType to the default if it isn't set
                toast.bodyOutputType = toast.bodyOutputType || mergedConfig['body-output-type']
                switch (toast.bodyOutputType) {
                    case 'trustedHtml':
                        toast.html = $sce.trustAsHtml(toast.body);
                        break;
                    case 'template':
                        toast.bodyTemplate = mergedConfig['body-template'];
                        break;
                }

                var timeout = typeof (toast.timeout) == "number" ? toast.timeout : mergedConfig['time-out'];
                if (timeout > 0)
                    setTimeout(toast, timeout);

                if (mergedConfig['newest-on-top'] === true) {
                    scope.toasters.unshift(toast);
                    if (mergedConfig['limit'] > 0 && scope.toasters.length > mergedConfig['limit']) {
                        scope.toasters.pop();
                    }
                } else {
                    scope.toasters.push(toast);
                    if (mergedConfig['limit'] > 0 && scope.toasters.length > mergedConfig['limit']) {
                        scope.toasters.shift();
                    }
                }
            }

            function setTimeout(toast, time) {
                toast.timeout = $timeout(function () {
                    scope.removeToast(toast.id);
                }, time);
            }

            scope.toasters = [];
            scope.$on('toaster-newToast', function () {
                addToast(toaster.toast);
            });

            scope.$on('toaster-clearToasts', function () {
                scope.toasters.splice(0, scope.toasters.length);
            });
        },
        controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {

            $scope.stopTimer = function (toast) {
                if (toast.timeout)
                    $timeout.cancel(toast.timeout);
            };

            $scope.removeToast = function (id) {
                var i = 0;
                for (i; i < $scope.toasters.length; i++) {
                    if ($scope.toasters[i].id === id)
                        break;
                }
                $scope.toasters.splice(i, 1);
            };

            $scope.remove = function (id) {
                if ($scope.config.tap === true) {
                    $scope.removeToast(id);
                }
            };
        }],
        template:
        '<div  id="toast-container" ng-class="config.position">' +
            '<div ng-repeat="toaster in toasters" class="toast" ng-class="toaster.type" ng-click="remove(toaster.id)" ng-mouseover="stopTimer(toaster)">' +
              '<div ng-class="config.title">{{toaster.title}}</div>' +
              '<div ng-class="config.message" ng-switch on="toaster.bodyOutputType">' +
                '<div ng-switch-when="trustedHtml" ng-bind-html="toaster.html"></div>' +
                '<div ng-switch-when="template"><div ng-include="toaster.bodyTemplate"></div></div>' +
                '<div ng-switch-default >{{toaster.body}}</div>' +
              '</div>' +
            '</div>' +
        '</div>'
    };
}]);

/*! angular-breadcrumb - v0.3.3
* http://ncuillery.github.io/angular-breadcrumb
* Copyright (c) 2014 Nicolas Cuillery; Licensed MIT */

(function (window, angular, undefined) {
'use strict';

function isAOlderThanB(scopeA, scopeB) {
    if(angular.equals(scopeA.length, scopeB.length)) {
        return scopeA > scopeB;
    } else {
        return scopeA.length > scopeB.length;
    }
}

function parseStateRef(ref) {
    var parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/);
    if (!parsed || parsed.length !== 4) { throw new Error("Invalid state ref '" + ref + "'"); }
    return { state: parsed[1], paramExpr: parsed[3] || null };
}

function $Breadcrumb() {

    var $$options = {
        prefixStateName: null,
        template: 'bootstrap3',
        templateUrl: null,
        includeAbstract : false
    };

    this.setOptions = function(options) {
        angular.extend($$options, options);
    };

    this.$get = ['$state', '$stateParams', '$rootScope', function($state, $stateParams, $rootScope) {

        var $lastViewScope = $rootScope;

        // Early catch of $viewContentLoaded event
        $rootScope.$on('$viewContentLoaded', function (event) {
            // With nested views, the event occur several times, in "wrong" order
            if(isAOlderThanB(event.targetScope.$id, $lastViewScope.$id)) {
                $lastViewScope = event.targetScope;
            }
        });

        // Get the parent state
        var $$parentState = function(state) {
            // Check if state has explicit parent OR we try guess parent from its name
            var name = state.parent || (/^(.+)\.[^.]+$/.exec(state.name) || [])[1];
            // If we were able to figure out parent name then get this state
            return name;
        };

        // Add the state in the chain if not already in and if not abstract
        var $$addStateInChain = function(chain, stateRef) {
            var conf,
                parentParams,
                ref = parseStateRef(stateRef);

            for(var i=0, l=chain.length; i<l; i+=1) {
                if (chain[i].name === ref.state) {
                    return;
                }
            }

            conf = $state.get(ref.state);
            if((!conf.abstract || $$options.includeAbstract) && !(conf.ncyBreadcrumb && conf.ncyBreadcrumb.skip)) {
                if(ref.paramExpr) {
                    parentParams = $lastViewScope.$eval(ref.paramExpr);
                }

                conf.ncyBreadcrumbLink = $state.href(ref.state, parentParams || $stateParams || {});
                chain.unshift(conf);
            }
        };

        // Get the state for the parent step in the breadcrumb
        var $$breadcrumbParentState = function(stateRef) {
            var ref = parseStateRef(stateRef),
                conf = $state.get(ref.state);

            if(conf.ncyBreadcrumb && conf.ncyBreadcrumb.parent) {
                // Handle the "parent" property of the breadcrumb, override the parent/child relation of the state
                var isFunction = typeof conf.ncyBreadcrumb.parent === 'function';
                var parentStateRef = isFunction ? conf.ncyBreadcrumb.parent($lastViewScope) : conf.ncyBreadcrumb.parent;
                if(parentStateRef) {
                    return parentStateRef;
                }
            }

            return $$parentState(conf);
        };

        return {

            getTemplate: function(templates) {
                if($$options.templateUrl) {
                    // templateUrl takes precedence over template
                    return null;
                } else if(templates[$$options.template]) {
                    // Predefined templates (bootstrap, ...)
                    return templates[$$options.template];
                } else {
                    return $$options.template;
                }
            },

            getTemplateUrl: function() {
                return $$options.templateUrl;
            },

            getStatesChain: function(exitOnFirst) { // Deliberately undocumented param, see getLastStep
                var chain = [];

                // From current state to the root
                for(var stateRef = $state.$current.self.name; stateRef; stateRef=$$breadcrumbParentState(stateRef)) {
                    $$addStateInChain(chain, stateRef);
                    if(exitOnFirst && chain.length) {
                        return chain;
                    }
                }

                // Prefix state treatment
                if($$options.prefixStateName) {
                    $$addStateInChain(chain, $$options.prefixStateName);
                }

                return chain;
            },

            getLastStep: function() {
                var chain = this.getStatesChain(true);
                return chain.length ? chain[0] : undefined;
            },

            $getLastViewScope: function() {
                return $lastViewScope;
            }
        };
    }];
}

var getExpression = function(interpolationFunction) {
    if(interpolationFunction.expressions) {
        return interpolationFunction.expressions;
    } else {
        var expressions = [];
        angular.forEach(interpolationFunction.parts, function(part) {
            if(angular.isFunction(part)) {
                expressions.push(part.exp);
            }
        });
        return expressions;
    }
};

var registerWatchers = function(labelWatcherArray, interpolationFunction, viewScope, step) {
    angular.forEach(getExpression(interpolationFunction), function(expression) {
        var watcher = viewScope.$watch(expression, function() {
            step.ncyBreadcrumbLabel = interpolationFunction(viewScope);
        });
        labelWatcherArray.push(watcher);
    });

};

var deregisterWatchers = function(labelWatcherArray) {
    angular.forEach(labelWatcherArray, function(deregisterWatch) {
        deregisterWatch();
    });
    labelWatcherArray = [];
};

function BreadcrumbDirective($interpolate, $breadcrumb, $rootScope) {
    var $$templates = {
        bootstrap2: '<ul class="breadcrumb">' +
            '<li ng-repeat="step in steps" ng-switch="$last || !!step.abstract" ng-class="{active: $last}">' +
            '<a ng-switch-when="false" href="{{step.ncyBreadcrumbLink || \'/\'}}">{{step.ncyBreadcrumbLabel}}</a> ' +
            '<span ng-switch-when="true">{{step.ncyBreadcrumbLabel}}</span>' +
            '<span class="divider" ng-hide="$last">/</span>' +
            '</li>' +
            '</ul>',
        bootstrap3: '<ol class="breadcrumb">' +
            '<li ng-repeat="step in steps" ng-class="{active: $last}" ng-switch="$last || !!step.abstract">' +
            '<a ng-switch-when="false" href="{{step.ncyBreadcrumbLink || \'/\'}}">{{step.ncyBreadcrumbLabel}}</a> ' +
            '<span ng-switch-when="true">{{step.ncyBreadcrumbLabel}}</span>' +
            '</li>' +
            '</ol>'
    };

    return {
        restrict: 'AE',
        replace: true,
        scope: {},
        template: $breadcrumb.getTemplate($$templates),
        templateUrl: $breadcrumb.getTemplateUrl(),
        link: {
            post: function postLink(scope) {
                var labelWatchers = [];

                var renderBreadcrumb = function() {
                    deregisterWatchers(labelWatchers);
                    var viewScope = $breadcrumb.$getLastViewScope();
                    scope.steps = $breadcrumb.getStatesChain();
                    angular.forEach(scope.steps, function (step) {
                        //
                        if (step.ncyBreadcrumbLink == '#')
                            step.ncyBreadcrumbLink = '#/';
                        if (step.ncyBreadcrumb && step.ncyBreadcrumb.label) {
                            var parseLabel = $interpolate(step.ncyBreadcrumb.label);
                            step.ncyBreadcrumbLabel = parseLabel(viewScope);
                            // Watcher for further viewScope updates
                            registerWatchers(labelWatchers, parseLabel, viewScope, step);
                        } else {
                            step.ncyBreadcrumbLabel = step.name;
                        }
                    });
                };

                $rootScope.$on('$viewContentLoaded', function () {
                    renderBreadcrumb();
                });

                // View(s) may be already loaded while the directive's linking
                renderBreadcrumb();
            }
        }
    };
}
BreadcrumbDirective.$inject = ['$interpolate', '$breadcrumb', '$rootScope'];

function BreadcrumbLastDirective($interpolate, $breadcrumb, $rootScope) {

    return {
        restrict: 'A',
        scope: {},
        template: '{{ncyBreadcrumbLabel}}',
        compile: function(cElement, cAttrs) {

            // Override the default template if ncyBreadcrumbLast has a value
            var template = cElement.attr(cAttrs.$attr.ncyBreadcrumbLast);
            if(template) {
                cElement.html(template);
            }

            return {
                post: function postLink(scope) {
                    var labelWatchers = [];

                    var renderLabel = function() {
                        deregisterWatchers(labelWatchers);
                        var viewScope = $breadcrumb.$getLastViewScope();
                        var lastStep = $breadcrumb.getLastStep();
                        if(lastStep) {
                            scope.ncyBreadcrumbLink = lastStep.ncyBreadcrumbLink;
                            if (lastStep.ncyBreadcrumb && lastStep.ncyBreadcrumb.label) {
                                var parseLabel = $interpolate(lastStep.ncyBreadcrumb.label);
                                scope.ncyBreadcrumbLabel = parseLabel(viewScope);
                                // Watcher for further viewScope updates
                                // Tricky last arg: the last step is the entire scope of the directive !
                                registerWatchers(labelWatchers, parseLabel, viewScope, scope);
                            } else {
                                scope.ncyBreadcrumbLabel = lastStep.name;
                            }
                        }
                    };

                    $rootScope.$on('$viewContentLoaded', function () {
                        renderLabel();
                    });

                    // View(s) may be already loaded while the directive's linking
                    renderLabel();
                }
            };

        }
    };
}
BreadcrumbLastDirective.$inject = ['$interpolate', '$breadcrumb', '$rootScope'];

angular.module('ncy-angular-breadcrumb', ['ui.router.state'])
    .provider('$breadcrumb', $Breadcrumb)
    .directive('ncyBreadcrumb', BreadcrumbDirective)
    .directive('ncyBreadcrumbLast', BreadcrumbLastDirective);
})(window, window.angular);

/*
 * angular-ui-bootstrap
 * http://angular-ui.github.io/bootstrap/

 * Version: 0.12.0 - 2014-11-16
 * License: MIT
 */
angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.transition", "ui.bootstrap.collapse", "ui.bootstrap.accordion", "ui.bootstrap.alert", "ui.bootstrap.bindHtml", "ui.bootstrap.buttons", "ui.bootstrap.carousel", "ui.bootstrap.dateparser", "ui.bootstrap.position", "ui.bootstrap.datepicker", "ui.bootstrap.dropdown", "ui.bootstrap.modal", "ui.bootstrap.pagination", "ui.bootstrap.tooltip", "ui.bootstrap.popover", "ui.bootstrap.progressbar", "ui.bootstrap.rating", "ui.bootstrap.tabs", "ui.bootstrap.timepicker", "ui.bootstrap.typeahead"]);
angular.module("ui.bootstrap.tpls", ["template/accordion/accordion-group.html", "template/accordion/accordion.html", "template/alert/alert.html", "template/carousel/carousel.html", "template/carousel/slide.html", "template/datepicker/datepicker.html", "template/datepicker/day.html", "template/datepicker/month.html", "template/datepicker/popup.html", "template/datepicker/year.html", "template/modal/backdrop.html", "template/modal/window.html", "template/pagination/pager.html", "template/pagination/pagination.html", "template/tooltip/tooltip-html-unsafe-popup.html", "template/tooltip/tooltip-popup.html", "template/popover/popover.html", "template/progressbar/bar.html", "template/progressbar/progress.html", "template/progressbar/progressbar.html", "template/rating/rating.html", "template/tabs/tab.html", "template/tabs/tabset.html", "template/timepicker/timepicker.html", "template/typeahead/typeahead-match.html", "template/typeahead/typeahead-popup.html"]);
angular.module('ui.bootstrap.transition', [])

    /**
     * $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete.
     * @param  {DOMElement} element  The DOMElement that will be animated.
     * @param  {string|object|function} trigger  The thing that will cause the transition to start:
     *   - As a string, it represents the css class to be added to the element.
     *   - As an object, it represents a hash of style attributes to be applied to the element.
     *   - As a function, it represents a function to be called that will cause the transition to occur.
     * @return {Promise}  A promise that is resolved when the transition finishes.
     */
    .factory('$transition', ['$q', '$timeout', '$rootScope', function ($q, $timeout, $rootScope) {

        var $transition = function (element, trigger, options) {
            options = options || {};
            var deferred = $q.defer();
            var endEventName = $transition[options.animation ? 'animationEndEventName' : 'transitionEndEventName'];

            var transitionEndHandler = function (event) {
                $rootScope.$apply(function () {
                    element.unbind(endEventName, transitionEndHandler);
                    deferred.resolve(element);
                });
            };

            if (endEventName) {
                element.bind(endEventName, transitionEndHandler);
            }

            // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur
            $timeout(function () {
                if (angular.isString(trigger)) {
                    element.addClass(trigger);
                } else if (angular.isFunction(trigger)) {
                    trigger(element);
                } else if (angular.isObject(trigger)) {
                    element.css(trigger);
                }
                //If browser does not support transitions, instantly resolve
                if (!endEventName) {
                    deferred.resolve(element);
                }
            });

            // Add our custom cancel function to the promise that is returned
            // We can call this if we are about to run a new transition, which we know will prevent this transition from ending,
            // i.e. it will therefore never raise a transitionEnd event for that transition
            deferred.promise.cancel = function () {
                if (endEventName) {
                    element.unbind(endEventName, transitionEndHandler);
                }
                deferred.reject('Transition cancelled');
            };

            return deferred.promise;
        };

        // Work out the name of the transitionEnd event
        var transElement = document.createElement('trans');
        var transitionEndEventNames = {
            'WebkitTransition': 'webkitTransitionEnd',
            'MozTransition': 'transitionend',
            'OTransition': 'oTransitionEnd',
            'transition': 'transitionend'
        };
        var animationEndEventNames = {
            'WebkitTransition': 'webkitAnimationEnd',
            'MozTransition': 'animationend',
            'OTransition': 'oAnimationEnd',
            'transition': 'animationend'
        };
        function findEndEventName(endEventNames) {
            for (var name in endEventNames) {
                if (transElement.style[name] !== undefined) {
                    return endEventNames[name];
                }
            }
        }
        $transition.transitionEndEventName = findEndEventName(transitionEndEventNames);
        $transition.animationEndEventName = findEndEventName(animationEndEventNames);
        return $transition;
    }]);

angular.module('ui.bootstrap.collapse', ['ui.bootstrap.transition'])

    .directive('collapse', ['$transition', function ($transition) {

        return {
            link: function (scope, element, attrs) {

                var initialAnimSkip = true;
                var currentTransition;

                function doTransition(change) {
                    var newTransition = $transition(element, change);
                    if (currentTransition) {
                        currentTransition.cancel();
                    }
                    currentTransition = newTransition;
                    newTransition.then(newTransitionDone, newTransitionDone);
                    return newTransition;

                    function newTransitionDone() {
                        // Make sure it's this transition, otherwise, leave it alone.
                        if (currentTransition === newTransition) {
                            currentTransition = undefined;
                        }
                    }
                }

                function expand() {
                    if (initialAnimSkip) {
                        initialAnimSkip = false;
                        expandDone();
                    } else {
                        element.removeClass('collapse').addClass('collapsing');
                        doTransition({ height: element[0].scrollHeight + 'px' }).then(expandDone);
                    }
                }

                function expandDone() {
                    element.removeClass('collapsing');
                    element.addClass('collapse in');
                    element.css({ height: 'auto' });
                }

                function collapse() {
                    if (initialAnimSkip) {
                        initialAnimSkip = false;
                        collapseDone();
                        element.css({ height: 0 });
                    } else {
                        // CSS transitions don't work with height: auto, so we have to manually change the height to a specific value
                        element.css({ height: element[0].scrollHeight + 'px' });
                        //trigger reflow so a browser realizes that height was updated from auto to a specific value
                        var x = element[0].offsetWidth;

                        element.removeClass('collapse in').addClass('collapsing');

                        doTransition({ height: 0 }).then(collapseDone);
                    }
                }

                function collapseDone() {
                    element.removeClass('collapsing');
                    element.addClass('collapse');
                }

                scope.$watch(attrs.collapse, function (shouldCollapse) {
                    if (shouldCollapse) {
                        collapse();
                    } else {
                        expand();
                    }
                });
            }
        };
    }]);

angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])

    .constant('accordionConfig', {
        closeOthers: true
    })

    .controller('AccordionController', ['$scope', '$attrs', 'accordionConfig', function ($scope, $attrs, accordionConfig) {

        // This array keeps track of the accordion groups
        this.groups = [];

        // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to
        this.closeOthers = function (openGroup) {
            var closeOthers = angular.isDefined($attrs.closeOthers) ? $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers;
            if (closeOthers) {
                angular.forEach(this.groups, function (group) {
                    if (group !== openGroup) {
                        group.isOpen = false;
                    }
                });
            }
        };

        // This is called from the accordion-group directive to add itself to the accordion
        this.addGroup = function (groupScope) {
            var that = this;
            this.groups.push(groupScope);

            groupScope.$on('$destroy', function (event) {
                that.removeGroup(groupScope);
            });
        };

        // This is called from the accordion-group directive when to remove itself
        this.removeGroup = function (group) {
            var index = this.groups.indexOf(group);
            if (index !== -1) {
                this.groups.splice(index, 1);
            }
        };

    }])

    // The accordion directive simply sets up the directive controller
    // and adds an accordion CSS class to itself element.
    .directive('accordion', function () {
        return {
            restrict: 'EA',
            controller: 'AccordionController',
            transclude: true,
            replace: false,
            templateUrl: 'template/accordion/accordion.html'
        };
    })

    // The accordion-group directive indicates a block of html that will expand and collapse in an accordion
    .directive('accordionGroup', function () {
        return {
            require: '^accordion',         // We need this directive to be inside an accordion
            restrict: 'EA',
            transclude: true,              // It transcludes the contents of the directive into the template
            replace: true,                // The element containing the directive will be replaced with the template
            templateUrl: 'template/accordion/accordion-group.html',
            scope: {
                heading: '@',               // Interpolate the heading attribute onto this scope
                isOpen: '=?',
                isDisabled: '=?'
            },
            controller: function () {
                this.setHeading = function (element) {
                    this.heading = element;
                };
            },
            link: function (scope, element, attrs, accordionCtrl) {
                accordionCtrl.addGroup(scope);

                scope.$watch('isOpen', function (value) {
                    if (value) {
                        accordionCtrl.closeOthers(scope);
                    }
                });

                scope.toggleOpen = function () {
                    if (!scope.isDisabled) {
                        scope.isOpen = !scope.isOpen;
                    }
                };
            }
        };
    })

    // Use accordion-heading below an accordion-group to provide a heading containing HTML
    // <accordion-group>
    //   <accordion-heading>Heading containing HTML - <img src="..."></accordion-heading>
    // </accordion-group>
    .directive('accordionHeading', function () {
        return {
            restrict: 'EA',
            transclude: true,   // Grab the contents to be used as the heading
            template: '',       // In effect remove this element!
            replace: true,
            require: '^accordionGroup',
            link: function (scope, element, attr, accordionGroupCtrl, transclude) {
                // Pass the heading to the accordion-group controller
                // so that it can be transcluded into the right place in the template
                // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat]
                accordionGroupCtrl.setHeading(transclude(scope, function () { }));
            }
        };
    })

    // Use in the accordion-group template to indicate where you want the heading to be transcluded
    // You must provide the property on the accordion-group controller that will hold the transcluded element
    // <div class="accordion-group">
    //   <div class="accordion-heading" ><a ... accordion-transclude="heading">...</a></div>
    //   ...
    // </div>
    .directive('accordionTransclude', function () {
        return {
            require: '^accordionGroup',
            link: function (scope, element, attr, controller) {
                scope.$watch(function () { return controller[attr.accordionTransclude]; }, function (heading) {
                    if (heading) {
                        element.html('');
                        element.append(heading);
                    }
                });
            }
        };
    });

angular.module('ui.bootstrap.alert', [])

    .controller('AlertController', ['$scope', '$attrs', function ($scope, $attrs) {
        $scope.closeable = 'close' in $attrs;
        this.close = $scope.close;
    }])

    .directive('alert', function () {
        return {
            restrict: 'EA',
            controller: 'AlertController',
            templateUrl: 'template/alert/alert.html',
            transclude: true,
            replace: true,
            scope: {
                type: '@',
                close: '&'
            }
        };
    })

    .directive('dismissOnTimeout', ['$timeout', function ($timeout) {
        return {
            require: 'alert',
            link: function (scope, element, attrs, alertCtrl) {
                $timeout(function () {
                    alertCtrl.close();
                }, parseInt(attrs.dismissOnTimeout, 10));
            }
        };
    }]);

angular.module('ui.bootstrap.bindHtml', [])

    .directive('bindHtmlUnsafe', function () {
        return function (scope, element, attr) {
            element.addClass('ng-binding').data('$binding', attr.bindHtmlUnsafe);
            scope.$watch(attr.bindHtmlUnsafe, function bindHtmlUnsafeWatchAction(value) {
                element.html(value || '');
            });
        };
    });
angular.module('ui.bootstrap.buttons', [])

    .constant('buttonConfig', {
        activeClass: 'active',
        toggleEvent: 'click'
    })

    .controller('ButtonsController', ['buttonConfig', function (buttonConfig) {
        this.activeClass = buttonConfig.activeClass || 'active';
        this.toggleEvent = buttonConfig.toggleEvent || 'click';
    }])

    .directive('btnRadio', function () {
        return {
            require: ['btnRadio', 'ngModel'],
            controller: 'ButtonsController',
            link: function (scope, element, attrs, ctrls) {
                var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];

                //model -> UI
                ngModelCtrl.$render = function () {
                    element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.btnRadio)));
                };

                //ui->model
                element.bind(buttonsCtrl.toggleEvent, function () {
                    var isActive = element.hasClass(buttonsCtrl.activeClass);

                    if (!isActive || angular.isDefined(attrs.uncheckable)) {
                        scope.$apply(function () {
                            ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.btnRadio));
                            ngModelCtrl.$render();
                        });
                    }
                });
            }
        };
    })

    .directive('btnCheckbox', function () {
        return {
            require: ['btnCheckbox', 'ngModel'],
            controller: 'ButtonsController',
            link: function (scope, element, attrs, ctrls) {
                var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];

                function getTrueValue() {
                    return getCheckboxValue(attrs.btnCheckboxTrue, true);
                }

                function getFalseValue() {
                    return getCheckboxValue(attrs.btnCheckboxFalse, false);
                }

                function getCheckboxValue(attributeValue, defaultValue) {
                    var val = scope.$eval(attributeValue);
                    return angular.isDefined(val) ? val : defaultValue;
                }

                //model -> UI
                ngModelCtrl.$render = function () {
                    element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));
                };

                //ui->model
                element.bind(buttonsCtrl.toggleEvent, function () {
                    scope.$apply(function () {
                        ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue());
                        ngModelCtrl.$render();
                    });
                });
            }
        };
    });

/**
* @ngdoc overview
* @name ui.bootstrap.carousel
*
* @description
* AngularJS version of an image carousel.
*
*/
angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition'])
    .controller('CarouselController', ['$scope', '$timeout', '$interval', '$transition', function ($scope, $timeout, $interval, $transition) {
        var self = this,
            slides = self.slides = $scope.slides = [],
            currentIndex = -1,
            currentInterval, isPlaying;
        self.currentSlide = null;

        var destroyed = false;
        /* direction: "prev" or "next" */
        self.select = $scope.select = function (nextSlide, direction) {
            var nextIndex = slides.indexOf(nextSlide);
            //Decide direction if it's not given
            if (direction === undefined) {
                direction = nextIndex > currentIndex ? 'next' : 'prev';
            }
            if (nextSlide && nextSlide !== self.currentSlide) {
                if ($scope.$currentTransition) {
                    $scope.$currentTransition.cancel();
                    //Timeout so ng-class in template has time to fix classes for finished slide
                    $timeout(goNext);
                } else {
                    goNext();
                }
            }
            function goNext() {
                // Scope has been destroyed, stop here.
                if (destroyed) { return; }
                //If we have a slide to transition from and we have a transition type and we're allowed, go
                if (self.currentSlide && angular.isString(direction) && !$scope.noTransition && nextSlide.$element) {
                    //We shouldn't do class manip in here, but it's the same weird thing bootstrap does. need to fix sometime
                    nextSlide.$element.addClass(direction);
                    var reflow = nextSlide.$element[0].offsetWidth; //force reflow

                    //Set all other slides to stop doing their stuff for the new transition
                    angular.forEach(slides, function (slide) {
                        angular.extend(slide, { direction: '', entering: false, leaving: false, active: false });
                    });
                    angular.extend(nextSlide, { direction: direction, active: true, entering: true });
                    angular.extend(self.currentSlide || {}, { direction: direction, leaving: true });

                    $scope.$currentTransition = $transition(nextSlide.$element, {});
                    //We have to create new pointers inside a closure since next & current will change
                    (function (next, current) {
                        $scope.$currentTransition.then(
                            function () { transitionDone(next, current); },
                            function () { transitionDone(next, current); }
                        );
                    }(nextSlide, self.currentSlide));
                } else {
                    transitionDone(nextSlide, self.currentSlide);
                }
                self.currentSlide = nextSlide;
                currentIndex = nextIndex;
                //every time you change slides, reset the timer
                restartTimer();
            }
            function transitionDone(next, current) {
                angular.extend(next, { direction: '', active: true, leaving: false, entering: false });
                angular.extend(current || {}, { direction: '', active: false, leaving: false, entering: false });
                $scope.$currentTransition = null;
            }
        };
        $scope.$on('$destroy', function () {
            destroyed = true;
        });

        /* Allow outside people to call indexOf on slides array */
        self.indexOfSlide = function (slide) {
            return slides.indexOf(slide);
        };

        $scope.next = function () {
            var newIndex = (currentIndex + 1) % slides.length;

            //Prevent this user-triggered transition from occurring if there is already one in progress
            if (!$scope.$currentTransition) {
                return self.select(slides[newIndex], 'next');
            }
        };

        $scope.prev = function () {
            var newIndex = currentIndex - 1 < 0 ? slides.length - 1 : currentIndex - 1;

            //Prevent this user-triggered transition from occurring if there is already one in progress
            if (!$scope.$currentTransition) {
                return self.select(slides[newIndex], 'prev');
            }
        };

        $scope.isActive = function (slide) {
            return self.currentSlide === slide;
        };

        $scope.$watch('interval', restartTimer);
        $scope.$on('$destroy', resetTimer);

        function restartTimer() {
            resetTimer();
            var interval = +$scope.interval;
            if (!isNaN(interval) && interval > 0) {
                currentInterval = $interval(timerFn, interval);
            }
        }

        function resetTimer() {
            if (currentInterval) {
                $interval.cancel(currentInterval);
                currentInterval = null;
            }
        }

        function timerFn() {
            var interval = +$scope.interval;
            if (isPlaying && !isNaN(interval) && interval > 0) {
                $scope.next();
            } else {
                $scope.pause();
            }
        }

        $scope.play = function () {
            if (!isPlaying) {
                isPlaying = true;
                restartTimer();
            }
        };
        $scope.pause = function () {
            if (!$scope.noPause) {
                isPlaying = false;
                resetTimer();
            }
        };

        self.addSlide = function (slide, element) {
            slide.$element = element;
            slides.push(slide);
            //if this is the first slide or the slide is set to active, select it
            if (slides.length === 1 || slide.active) {
                self.select(slides[slides.length - 1]);
                if (slides.length == 1) {
                    $scope.play();
                }
            } else {
                slide.active = false;
            }
        };

        self.removeSlide = function (slide) {
            //get the index of the slide inside the carousel
            var index = slides.indexOf(slide);
            slides.splice(index, 1);
            if (slides.length > 0 && slide.active) {
                if (index >= slides.length) {
                    self.select(slides[index - 1]);
                } else {
                    self.select(slides[index]);
                }
            } else if (currentIndex > index) {
                currentIndex--;
            }
        };

    }])

    /**
     * @ngdoc directive
     * @name ui.bootstrap.carousel.directive:carousel
     * @restrict EA
     *
     * @description
     * Carousel is the outer container for a set of image 'slides' to showcase.
     *
     * @param {number=} interval The time, in milliseconds, that it will take the carousel to go to the next slide.
     * @param {boolean=} noTransition Whether to disable transitions on the carousel.
     * @param {boolean=} noPause Whether to disable pausing on the carousel (by default, the carousel interval pauses on hover).
     *
     * @example
    <example module="ui.bootstrap">
      <file name="index.html">
        <carousel>
          <slide>
            <img src="http://placekitten.com/150/150" style="margin:auto;">
            <div class="carousel-caption">
              <p>Beautiful!</p>
            </div>
          </slide>
          <slide>
            <img src="http://placekitten.com/100/150" style="margin:auto;">
            <div class="carousel-caption">
              <p>D'aww!</p>
            </div>
          </slide>
        </carousel>
      </file>
      <file name="demo.css">
        .carousel-indicators {
          top: auto;
          bottom: 15px;
        }
      </file>
    </example>
     */
    .directive('carousel', [function () {
        return {
            restrict: 'EA',
            transclude: true,
            replace: true,
            controller: 'CarouselController',
            require: 'carousel',
            // templateUrl: 'template/carousel/carousel.html',
            templateUrl: function (element, attrs) {
                return attrs.templateUrl || 'template/carousel/carousel.html';
            },
            scope: {
                interval: '=',
                noTransition: '=',
                noPause: '='
            }
        };
    }])

    /**
     * @ngdoc directive
     * @name ui.bootstrap.carousel.directive:slide
     * @restrict EA
     *
     * @description
     * Creates a slide inside a {@link ui.bootstrap.carousel.directive:carousel carousel}.  Must be placed as a child of a carousel element.
     *
     * @param {boolean=} active Model binding, whether or not this slide is currently active.
     *
     * @example
    <example module="ui.bootstrap">
      <file name="index.html">
    <div ng-controller="CarouselDemoCtrl">
      <carousel>
        <slide ng-repeat="slide in slides" active="slide.active">
          <img ng-src="{{slide.image}}" style="margin:auto;">
          <div class="carousel-caption">
            <h4>Slide {{$index}}</h4>
            <p>{{slide.text}}</p>
          </div>
        </slide>
      </carousel>
      Interval, in milliseconds: <input type="number" ng-model="myInterval">
      <br />Enter a negative number to stop the interval.
    </div>
      </file>
      <file name="script.js">
    function CarouselDemoCtrl($scope) {
      $scope.myInterval = 5000;
    }
      </file>
      <file name="demo.css">
        .carousel-indicators {
          top: auto;
          bottom: 15px;
        }
      </file>
    </example>
    */

    .directive('slidee', function () {
        return {
            require: '^carousel',
            restrict: 'EA',
            transclude: true,
            replace: true,
            templateUrl: 'template/carousel/slide.html',
            scope: {
                active: '=?'
            },
            link: function (scope, element, attrs, carouselCtrl) {
                carouselCtrl.addSlide(scope, element);
                //when the scope is destroyed then remove the slide from the current slides array
                scope.$on('$destroy', function () {
                    carouselCtrl.removeSlide(scope);
                });

                scope.$watch('active', function (active) {
                    if (active) {
                        carouselCtrl.select(scope);
                    }
                });
            }
        };
    });

angular.module('ui.bootstrap.dateparser', [])

    .service('dateParser', ['$locale', 'orderByFilter', function ($locale, orderByFilter) {

        this.parsers = {};

        var formatCodeToRegex = {
            'yyyy': {
                regex: '\\d{4}',
                apply: function (value) { this.year = +value; }
            },
            'yy': {
                regex: '\\d{2}',
                apply: function (value) { this.year = +value + 2000; }
            },
            'y': {
                regex: '\\d{1,4}',
                apply: function (value) { this.year = +value; }
            },
            'MMMM': {
                regex: $locale.DATETIME_FORMATS.MONTH.join('|'),
                apply: function (value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); }
            },
            'MMM': {
                regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'),
                apply: function (value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); }
            },
            'MM': {
                regex: '0[1-9]|1[0-2]',
                apply: function (value) { this.month = value - 1; }
            },
            'M': {
                regex: '[1-9]|1[0-2]',
                apply: function (value) { this.month = value - 1; }
            },
            'dd': {
                regex: '[0-2][0-9]{1}|3[0-1]{1}',
                apply: function (value) { this.date = +value; }
            },
            'd': {
                regex: '[1-2]?[0-9]{1}|3[0-1]{1}',
                apply: function (value) { this.date = +value; }
            },
            'EEEE': {
                regex: $locale.DATETIME_FORMATS.DAY.join('|')
            },
            'EEE': {
                regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|')
            }
        };

        function createParser(format) {
            var map = [], regex = format.split('');

            angular.forEach(formatCodeToRegex, function (data, code) {
                var index = format.indexOf(code);

                if (index > -1) {
                    format = format.split('');

                    regex[index] = '(' + data.regex + ')';
                    format[index] = '$'; // Custom symbol to define consumed part of format
                    for (var i = index + 1, n = index + code.length; i < n; i++) {
                        regex[i] = '';
                        format[i] = '$';
                    }
                    format = format.join('');

                    map.push({ index: index, apply: data.apply });
                }
            });

            return {
                regex: new RegExp('^' + regex.join('') + '$'),
                map: orderByFilter(map, 'index')
            };
        }

        this.parse = function (input, format) {
            if (!angular.isString(input) || !format) {
                return input;
            }

            format = $locale.DATETIME_FORMATS[format] || format;

            if (!this.parsers[format]) {
                this.parsers[format] = createParser(format);
            }

            var parser = this.parsers[format],
                regex = parser.regex,
                map = parser.map,
                results = input.match(regex);

            if (results && results.length) {
                var fields = { year: 1900, month: 0, date: 1, hours: 0 }, dt;

                for (var i = 1, n = results.length; i < n; i++) {
                    var mapper = map[i - 1];
                    if (mapper.apply) {
                        mapper.apply.call(fields, results[i]);
                    }
                }

                if (isValid(fields.year, fields.month, fields.date)) {
                    dt = new Date(fields.year, fields.month, fields.date, fields.hours);
                }

                return dt;
            }
        };

        // Check if date is valid for specific month (and year for February).
        // Month: 0 = Jan, 1 = Feb, etc
        function isValid(year, month, date) {
            if (month === 1 && date > 28) {
                return date === 29 && ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0);
            }

            if (month === 3 || month === 5 || month === 8 || month === 10) {
                return date < 31;
            }

            return true;
        }
    }]);

angular.module('ui.bootstrap.position', [])

    /**
     * A set of utility methods that can be use to retrieve position of DOM elements.
     * It is meant to be used where we need to absolute-position DOM elements in
     * relation to other, existing elements (this is the case for tooltips, popovers,
     * typeahead suggestions etc.).
     */
    .factory('$position', ['$document', '$window', function ($document, $window) {

        function getStyle(el, cssprop) {
            if (el.currentStyle) { //IE
                return el.currentStyle[cssprop];
            } else if ($window.getComputedStyle) {
                return $window.getComputedStyle(el)[cssprop];
            }
            // finally try and get inline style
            return el.style[cssprop];
        }

        /**
         * Checks if a given element is statically positioned
         * @param element - raw DOM element
         */
        function isStaticPositioned(element) {
            return (getStyle(element, 'position') || 'static') === 'static';
        }

        /**
         * returns the closest, non-statically positioned parentOffset of a given element
         * @param element
         */
        var parentOffsetEl = function (element) {
            var docDomEl = $document[0];
            var offsetParent = element.offsetParent || docDomEl;
            while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent)) {
                offsetParent = offsetParent.offsetParent;
            }
            return offsetParent || docDomEl;
        };

        return {
            /**
             * Provides read-only equivalent of jQuery's position function:
             * http://api.jquery.com/position/
             */
            position: function (element) {
                var elBCR = this.offset(element);
                var offsetParentBCR = { top: 0, left: 0 };
                var offsetParentEl = parentOffsetEl(element[0]);
                if (offsetParentEl != $document[0]) {
                    offsetParentBCR = this.offset(angular.element(offsetParentEl));
                    offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop;
                    offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft;
                }

                var boundingClientRect = element[0].getBoundingClientRect();
                return {
                    width: boundingClientRect.width || element.prop('offsetWidth'),
                    height: boundingClientRect.height || element.prop('offsetHeight'),
                    top: elBCR.top - offsetParentBCR.top,
                    left: elBCR.left - offsetParentBCR.left
                };
            },

            /**
             * Provides read-only equivalent of jQuery's offset function:
             * http://api.jquery.com/offset/
             */
            offset: function (element) {
                var boundingClientRect = element[0].getBoundingClientRect();
                return {
                    width: boundingClientRect.width || element.prop('offsetWidth'),
                    height: boundingClientRect.height || element.prop('offsetHeight'),
                    top: boundingClientRect.top + ($window.pageYOffset || $document[0].documentElement.scrollTop),
                    left: boundingClientRect.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft)
                };
            },

            /**
             * Provides coordinates for the targetEl in relation to hostEl
             */
            positionElements: function (hostEl, targetEl, positionStr, appendToBody) {

                var positionStrParts = positionStr.split('-');
                var pos0 = positionStrParts[0], pos1 = positionStrParts[1] || 'center';

                var hostElPos,
                    targetElWidth,
                    targetElHeight,
                    targetElPos;

                hostElPos = appendToBody ? this.offset(hostEl) : this.position(hostEl);

                targetElWidth = targetEl.prop('offsetWidth');
                targetElHeight = targetEl.prop('offsetHeight');

                var shiftWidth = {
                    center: function () {
                        return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2;
                    },
                    left: function () {
                        return hostElPos.left;
                    },
                    right: function () {
                        return hostElPos.left + hostElPos.width;
                    }
                };

                var shiftHeight = {
                    center: function () {
                        return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2;
                    },
                    top: function () {
                        return hostElPos.top;
                    },
                    bottom: function () {
                        return hostElPos.top + hostElPos.height;
                    }
                };

                switch (pos0) {
                    case 'right':
                        targetElPos = {
                            top: shiftHeight[pos1](),
                            left: shiftWidth[pos0]()
                        };
                        break;
                    case 'left':
                        targetElPos = {
                            top: shiftHeight[pos1](),
                            left: hostElPos.left - targetElWidth
                        };
                        break;
                    case 'bottom':
                        targetElPos = {
                            top: shiftHeight[pos0](),
                            left: shiftWidth[pos1]()
                        };
                        break;
                    default:
                        targetElPos = {
                            top: hostElPos.top - targetElHeight,
                            left: shiftWidth[pos1]()
                        };
                        break;
                }

                return targetElPos;
            }
        };
    }]);

angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.position'])

    .constant('datepickerConfig', {
        formatDay: 'dd',
        formatMonth: 'MMMM',
        formatYear: 'yyyy',
        formatDayHeader: 'EEE',
        formatDayTitle: 'MMMM yyyy',
        formatMonthTitle: 'yyyy',
        datepickerMode: 'day',
        minMode: 'day',
        maxMode: 'year',
        showWeeks: true,
        startingDay: 0,
        yearRange: 20,
        minDate: null,
        maxDate: null
    })

    .controller('DatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$timeout', '$log', 'dateFilter', 'datepickerConfig', function ($scope, $attrs, $parse, $interpolate, $timeout, $log, dateFilter, datepickerConfig) {
        var self = this,
            ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl;

        // Modes chain
        this.modes = ['day', 'month', 'year'];

        // Configuration attributes
        angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle',
            'minMode', 'maxMode', 'showWeeks', 'startingDay', 'yearRange'], function (key, index) {
                self[key] = angular.isDefined($attrs[key]) ? (index < 8 ? $interpolate($attrs[key])($scope.$parent) : $scope.$parent.$eval($attrs[key])) : datepickerConfig[key];
            });

        // Watchable date attributes
        angular.forEach(['minDate', 'maxDate'], function (key) {
            if ($attrs[key]) {
                $scope.$parent.$watch($parse($attrs[key]), function (value) {
                    self[key] = value ? new Date(value) : null;
                    self.refreshView();
                });
            } else {
                self[key] = datepickerConfig[key] ? new Date(datepickerConfig[key]) : null;
            }
        });

        $scope.datepickerMode = $scope.datepickerMode || datepickerConfig.datepickerMode;
        $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000);
        this.activeDate = angular.isDefined($attrs.initDate) ? $scope.$parent.$eval($attrs.initDate) : new Date();

        $scope.isActive = function (dateObject) {
            if (self.compare(dateObject.date, self.activeDate) === 0) {
                $scope.activeDateId = dateObject.uid;
                return true;
            }
            return false;
        };

        this.init = function (ngModelCtrl_) {
            ngModelCtrl = ngModelCtrl_;

            ngModelCtrl.$render = function () {
                self.render();
            };
        };

        this.render = function () {
            if (ngModelCtrl.$modelValue) {
                var date = new Date(ngModelCtrl.$modelValue),
                    isValid = !isNaN(date);

                if (isValid) {
                    this.activeDate = date;
                } else {
                    $log.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.');
                }
                ngModelCtrl.$setValidity('date', isValid);
            }
            this.refreshView();
        };

        this.refreshView = function () {
            if (this.element) {
                this._refreshView();

                var date = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : null;
                ngModelCtrl.$setValidity('date-disabled', !date || (this.element && !this.isDisabled(date)));
            }
        };

        this.createDateObject = function (date, format) {
            var model = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : null;
            return {
                date: date,
                label: dateFilter(date, format),
                selected: model && this.compare(date, model) === 0,
                disabled: this.isDisabled(date),
                current: this.compare(date, new Date()) === 0
            };
        };

        this.isDisabled = function (date) {
            return ((this.minDate && this.compare(date, this.minDate) < 0) || (this.maxDate && this.compare(date, this.maxDate) > 0) || ($attrs.dateDisabled && $scope.dateDisabled({ date: date, mode: $scope.datepickerMode })));
        };

        // Split array into smaller arrays
        this.split = function (arr, size) {
            var arrays = [];
            while (arr.length > 0) {
                arrays.push(arr.splice(0, size));
            }
            return arrays;
        };

        $scope.select = function (date) {
            if ($scope.datepickerMode === self.minMode) {
                var dt = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : new Date(0, 0, 0, 0, 0, 0, 0);
                dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
                ngModelCtrl.$setViewValue(dt);
                ngModelCtrl.$render();
            } else {
                self.activeDate = date;
                $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) - 1];
            }
        };

        $scope.move = function (direction) {
            var year = self.activeDate.getFullYear() + direction * (self.step.years || 0),
                month = self.activeDate.getMonth() + direction * (self.step.months || 0);
            self.activeDate.setFullYear(year, month, 1);
            self.refreshView();
        };

        $scope.toggleMode = function (direction) {
            direction = direction || 1;

            if (($scope.datepickerMode === self.maxMode && direction === 1) || ($scope.datepickerMode === self.minMode && direction === -1)) {
                return;
            }

            $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) + direction];
        };

        // Key event mapper
        $scope.keys = { 13: 'enter', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down' };

        var focusElement = function () {
            $timeout(function () {
                self.element[0].focus();
            }, 0, false);
        };

        // Listen for focus requests from popup directive
        $scope.$on('datepicker.focus', focusElement);

        $scope.keydown = function (evt) {
            var key = $scope.keys[evt.which];

            if (!key || evt.shiftKey || evt.altKey) {
                return;
            }

            evt.preventDefault();
            evt.stopPropagation();

            if (key === 'enter' || key === 'space') {
                if (self.isDisabled(self.activeDate)) {
                    return; // do nothing
                }
                $scope.select(self.activeDate);
                focusElement();
            } else if (evt.ctrlKey && (key === 'up' || key === 'down')) {
                $scope.toggleMode(key === 'up' ? 1 : -1);
                focusElement();
            } else {
                self.handleKeyDown(key, evt);
                self.refreshView();
            }
        };
    }])

    .directive('datepicker', function () {
        return {
            restrict: 'EA',
            replace: true,
            templateUrl: 'template/datepicker/datepicker.html',
            scope: {
                datepickerMode: '=?',
                dateDisabled: '&'
            },
            require: ['datepicker', '?^ngModel'],
            controller: 'DatepickerController',
            link: function (scope, element, attrs, ctrls) {
                var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];

                if (ngModelCtrl) {
                    datepickerCtrl.init(ngModelCtrl);
                }
            }
        };
    })

    .directive('daypicker', ['dateFilter', function (dateFilter) {
        return {
            restrict: 'EA',
            replace: true,
            templateUrl: 'template/datepicker/day.html',
            require: '^datepicker',
            link: function (scope, element, attrs, ctrl) {
                scope.showWeeks = ctrl.showWeeks;

                ctrl.step = { months: 1 };
                ctrl.element = element;

                var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
                function getDaysInMonth(year, month) {
                    return ((month === 1) && (year % 4 === 0) && ((year % 100 !== 0) || (year % 400 === 0))) ? 29 : DAYS_IN_MONTH[month];
                }

                function getDates(startDate, n) {
                    var dates = new Array(n), current = new Date(startDate), i = 0;
                    current.setHours(12); // Prevent repeated dates because of timezone bug
                    while (i < n) {
                        dates[i++] = new Date(current);
                        current.setDate(current.getDate() + 1);
                    }
                    return dates;
                }

                ctrl._refreshView = function () {
                    var year = ctrl.activeDate.getFullYear(),
                        month = ctrl.activeDate.getMonth(),
                        firstDayOfMonth = new Date(year, month, 1),
                        difference = ctrl.startingDay - firstDayOfMonth.getDay(),
                        numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : -difference,
                        firstDate = new Date(firstDayOfMonth);

                    if (numDisplayedFromPreviousMonth > 0) {
                        firstDate.setDate(-numDisplayedFromPreviousMonth + 1);
                    }

                    // 42 is the number of days on a six-month calendar
                    var days = getDates(firstDate, 42);
                    for (var i = 0; i < 42; i++) {
                        days[i] = angular.extend(ctrl.createDateObject(days[i], ctrl.formatDay), {
                            secondary: days[i].getMonth() !== month,
                            uid: scope.uniqueId + '-' + i
                        });
                    }

                    scope.labels = new Array(7);
                    for (var j = 0; j < 7; j++) {
                        scope.labels[j] = {
                            abbr: dateFilter(days[j].date, ctrl.formatDayHeader),
                            full: dateFilter(days[j].date, 'EEEE')
                        };
                    }

                    scope.title = dateFilter(ctrl.activeDate, ctrl.formatDayTitle);
                    scope.rows = ctrl.split(days, 7);

                    if (scope.showWeeks) {
                        scope.weekNumbers = [];
                        var weekNumber = getISO8601WeekNumber(scope.rows[0][0].date),
                            numWeeks = scope.rows.length;
                        while (scope.weekNumbers.push(weekNumber++) < numWeeks) { }
                    }
                };

                ctrl.compare = function (date1, date2) {
                    return (new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate()));
                };

                function getISO8601WeekNumber(date) {
                    var checkDate = new Date(date);
                    checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday
                    var time = checkDate.getTime();
                    checkDate.setMonth(0); // Compare with Jan 1
                    checkDate.setDate(1);
                    return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
                }

                ctrl.handleKeyDown = function (key, evt) {
                    var date = ctrl.activeDate.getDate();

                    if (key === 'left') {
                        date = date - 1;   // up
                    } else if (key === 'up') {
                        date = date - 7;   // down
                    } else if (key === 'right') {
                        date = date + 1;   // down
                    } else if (key === 'down') {
                        date = date + 7;
                    } else if (key === 'pageup' || key === 'pagedown') {
                        var month = ctrl.activeDate.getMonth() + (key === 'pageup' ? -1 : 1);
                        ctrl.activeDate.setMonth(month, 1);
                        date = Math.min(getDaysInMonth(ctrl.activeDate.getFullYear(), ctrl.activeDate.getMonth()), date);
                    } else if (key === 'home') {
                        date = 1;
                    } else if (key === 'end') {
                        date = getDaysInMonth(ctrl.activeDate.getFullYear(), ctrl.activeDate.getMonth());
                    }
                    ctrl.activeDate.setDate(date);
                };

                ctrl.refreshView();
            }
        };
    }])

    .directive('monthpicker', ['dateFilter', function (dateFilter) {
        return {
            restrict: 'EA',
            replace: true,
            templateUrl: 'template/datepicker/month.html',
            require: '^datepicker',
            link: function (scope, element, attrs, ctrl) {
                ctrl.step = { years: 1 };
                ctrl.element = element;

                ctrl._refreshView = function () {
                    var months = new Array(12),
                        year = ctrl.activeDate.getFullYear();

                    for (var i = 0; i < 12; i++) {
                        months[i] = angular.extend(ctrl.createDateObject(new Date(year, i, 1), ctrl.formatMonth), {
                            uid: scope.uniqueId + '-' + i
                        });
                    }

                    scope.title = dateFilter(ctrl.activeDate, ctrl.formatMonthTitle);
                    scope.rows = ctrl.split(months, 3);
                };

                ctrl.compare = function (date1, date2) {
                    return new Date(date1.getFullYear(), date1.getMonth()) - new Date(date2.getFullYear(), date2.getMonth());
                };

                ctrl.handleKeyDown = function (key, evt) {
                    var date = ctrl.activeDate.getMonth();

                    if (key === 'left') {
                        date = date - 1;   // up
                    } else if (key === 'up') {
                        date = date - 3;   // down
                    } else if (key === 'right') {
                        date = date + 1;   // down
                    } else if (key === 'down') {
                        date = date + 3;
                    } else if (key === 'pageup' || key === 'pagedown') {
                        var year = ctrl.activeDate.getFullYear() + (key === 'pageup' ? -1 : 1);
                        ctrl.activeDate.setFullYear(year);
                    } else if (key === 'home') {
                        date = 0;
                    } else if (key === 'end') {
                        date = 11;
                    }
                    ctrl.activeDate.setMonth(date);
                };

                ctrl.refreshView();
            }
        };
    }])

    .directive('yearpicker', ['dateFilter', function (dateFilter) {
        return {
            restrict: 'EA',
            replace: true,
            templateUrl: 'template/datepicker/year.html',
            require: '^datepicker',
            link: function (scope, element, attrs, ctrl) {
                var range = ctrl.yearRange;

                ctrl.step = { years: range };
                ctrl.element = element;

                function getStartingYear(year) {
                    return parseInt((year - 1) / range, 10) * range + 1;
                }

                ctrl._refreshView = function () {
                    var years = new Array(range);

                    for (var i = 0, start = getStartingYear(ctrl.activeDate.getFullYear()); i < range; i++) {
                        years[i] = angular.extend(ctrl.createDateObject(new Date(start + i, 0, 1), ctrl.formatYear), {
                            uid: scope.uniqueId + '-' + i
                        });
                    }

                    scope.title = [years[0].label, years[range - 1].label].join(' - ');
                    scope.rows = ctrl.split(years, 5);
                };

                ctrl.compare = function (date1, date2) {
                    return date1.getFullYear() - date2.getFullYear();
                };

                ctrl.handleKeyDown = function (key, evt) {
                    var date = ctrl.activeDate.getFullYear();

                    if (key === 'left') {
                        date = date - 1;   // up
                    } else if (key === 'up') {
                        date = date - 5;   // down
                    } else if (key === 'right') {
                        date = date + 1;   // down
                    } else if (key === 'down') {
                        date = date + 5;
                    } else if (key === 'pageup' || key === 'pagedown') {
                        date += (key === 'pageup' ? -1 : 1) * ctrl.step.years;
                    } else if (key === 'home') {
                        date = getStartingYear(ctrl.activeDate.getFullYear());
                    } else if (key === 'end') {
                        date = getStartingYear(ctrl.activeDate.getFullYear()) + range - 1;
                    }
                    ctrl.activeDate.setFullYear(date);
                };

                ctrl.refreshView();
            }
        };
    }])

    .constant('datepickerPopupConfig', {
        datepickerPopup: 'yyyy-MM-dd',
        currentText: 'Today',
        clearText: 'Clear',
        closeText: 'Done',
        closeOnDateSelection: true,
        appendToBody: false,
        showButtonBar: true
    })

    .directive('datepickerPopup', ['$compile', '$parse', '$document', '$position', 'dateFilter', 'dateParser', 'datepickerPopupConfig',
        function ($compile, $parse, $document, $position, dateFilter, dateParser, datepickerPopupConfig) {
            return {
                restrict: 'EA',
                require: 'ngModel',
                scope: {
                    isOpen: '=?',
                    currentText: '@',
                    clearText: '@',
                    closeText: '@',
                    dateDisabled: '&'
                },
                link: function (scope, element, attrs, ngModel) {
                    var dateFormat,
                        closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$parent.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection,
                        appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? scope.$parent.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody;

                    scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? scope.$parent.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar;

                    scope.getText = function (key) {
                        return scope[key + 'Text'] || datepickerPopupConfig[key + 'Text'];
                    };

                    attrs.$observe('datepickerPopup', function (value) {
                        dateFormat = value || datepickerPopupConfig.datepickerPopup;
                        ngModel.$render();
                    });

                    // popup element used to display calendar
                    var popupEl = angular.element('<div datepicker-popup-wrap><div datepicker></div></div>');
                    popupEl.attr({
                        'ng-model': 'date',
                        'ng-change': 'dateSelection()'
                    });

                    function cameltoDash(string) {
                        return string.replace(/([A-Z])/g, function ($1) { return '-' + $1.toLowerCase(); });
                    }

                    // datepicker element
                    var datepickerEl = angular.element(popupEl.children()[0]);
                    if (attrs.datepickerOptions) {
                        angular.forEach(scope.$parent.$eval(attrs.datepickerOptions), function (value, option) {
                            datepickerEl.attr(cameltoDash(option), value);
                        });
                    }

                    scope.watchData = {};
                    angular.forEach(['minDate', 'maxDate', 'datepickerMode'], function (key) {
                        if (attrs[key]) {
                            var getAttribute = $parse(attrs[key]);
                            scope.$parent.$watch(getAttribute, function (value) {
                                scope.watchData[key] = value;
                            });
                            datepickerEl.attr(cameltoDash(key), 'watchData.' + key);

                            // Propagate changes from datepicker to outside
                            if (key === 'datepickerMode') {
                                var setAttribute = getAttribute.assign;
                                scope.$watch('watchData.' + key, function (value, oldvalue) {
                                    if (value !== oldvalue) {
                                        setAttribute(scope.$parent, value);
                                    }
                                });
                            }
                        }
                    });
                    if (attrs.dateDisabled) {
                        datepickerEl.attr('date-disabled', 'dateDisabled({ date: date, mode: mode })');
                    }

                    function parseDate(viewValue) {
                        if (!viewValue) {
                            ngModel.$setValidity('date', true);
                            return null;
                        } else if (angular.isDate(viewValue) && !isNaN(viewValue)) {
                            ngModel.$setValidity('date', true);
                            return viewValue;
                        } else if (angular.isString(viewValue)) {
                            var date = dateParser.parse(viewValue, dateFormat) || new Date(viewValue);
                            if (isNaN(date)) {
                                ngModel.$setValidity('date', false);
                                return undefined;
                            } else {
                                ngModel.$setValidity('date', true);
                                return date;
                            }
                        } else {
                            ngModel.$setValidity('date', false);
                            return undefined;
                        }
                    }
                    ngModel.$parsers.unshift(parseDate);

                    // Inner change
                    scope.dateSelection = function (dt) {
                        if (angular.isDefined(dt)) {
                            scope.date = dt;
                        }
                        ngModel.$setViewValue(scope.date);
                        ngModel.$render();

                        if (closeOnDateSelection) {
                            scope.isOpen = false;
                            element[0].focus();
                        }
                    };

                    element.bind('input change keyup', function () {
                        scope.$apply(function () {
                            scope.date = ngModel.$modelValue;
                        });
                    });

                    // Outter change
                    ngModel.$render = function () {
                        var date = ngModel.$viewValue ? dateFilter(ngModel.$viewValue, dateFormat) : '';
                        element.val(date);
                        scope.date = parseDate(ngModel.$modelValue);
                    };

                    var documentClickBind = function (event) {
                        if (scope.isOpen && event.target !== element[0]) {
                            scope.$apply(function () {
                                scope.isOpen = false;
                            });
                        }
                    };

                    var keydown = function (evt, noApply) {
                        scope.keydown(evt);
                    };
                    element.bind('keydown', keydown);

                    scope.keydown = function (evt) {
                        if (evt.which === 27) {
                            evt.preventDefault();
                            evt.stopPropagation();
                            scope.close();
                        } else if (evt.which === 40 && !scope.isOpen) {
                            scope.isOpen = true;
                        }
                    };

                    scope.$watch('isOpen', function (value) {
                        if (value) {
                            scope.$broadcast('datepicker.focus');
                            scope.position = appendToBody ? $position.offset(element) : $position.position(element);
                            scope.position.top = scope.position.top + element.prop('offsetHeight');

                            $document.bind('click', documentClickBind);
                        } else {
                            $document.unbind('click', documentClickBind);
                        }
                    });

                    scope.select = function (date) {
                        if (date === 'today') {
                            var today = new Date();
                            if (angular.isDate(ngModel.$modelValue)) {
                                date = new Date(ngModel.$modelValue);
                                date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate());
                            } else {
                                date = new Date(today.setHours(0, 0, 0, 0));
                            }
                        }
                        scope.dateSelection(date);
                    };

                    scope.close = function () {
                        scope.isOpen = false;
                        element[0].focus();
                    };

                    var $popup = $compile(popupEl)(scope);
                    // Prevent jQuery cache memory leak (template is now redundant after linking)
                    popupEl.remove();

                    if (appendToBody) {
                        $document.find('body').append($popup);
                    } else {
                        element.after($popup);
                    }

                    scope.$on('$destroy', function () {
                        $popup.remove();
                        element.unbind('keydown', keydown);
                        $document.unbind('click', documentClickBind);
                    });
                }
            };
        }])

    .directive('datepickerPopupWrap', function () {
        return {
            restrict: 'EA',
            replace: true,
            transclude: true,
            templateUrl: 'template/datepicker/popup.html',
            link: function (scope, element, attrs) {
                element.bind('click', function (event) {
                    event.preventDefault();
                    event.stopPropagation();
                });
            }
        };
    });

angular.module('ui.bootstrap.dropdown', [])

    .constant('dropdownConfig', {
        openClass: 'open'
    })

    .service('dropdownService', ['$document', function ($document) {
        var openScope = null;

        this.open = function (dropdownScope) {
            if (!openScope) {
                $document.bind('click', closeDropdown);
                $document.bind('keydown', escapeKeyBind);
            }

            if (openScope && openScope !== dropdownScope) {
                openScope.isOpen = false;
            }

            openScope = dropdownScope;
        };

        this.close = function (dropdownScope) {
            if (openScope === dropdownScope) {
                openScope = null;
                $document.unbind('click', closeDropdown);
                $document.unbind('keydown', escapeKeyBind);
            }
        };

        var closeDropdown = function (evt) {
            // This method may still be called during the same mouse event that
            // unbound this event handler. So check openScope before proceeding.
            if (!openScope) { return; }

            if (evt && openScope.getAutoClose() === 'disabled') { return; }

            if (evt && evt.which === 3) { return; }

            var toggleElement = openScope.getToggleElement();
            if (evt && toggleElement && toggleElement[0].contains(evt.target)) {
                return;
            }

            var dropdownElement = openScope.getDropdownElement();
            if (evt && openScope.getAutoClose() === 'outsideClick' &&
                dropdownElement && dropdownElement[0].contains(evt.target)) {
                return;
            }

            openScope.$apply(function () {
                openScope.isOpen = false;
            });
        };

        var escapeKeyBind = function (evt) {
            if (evt.which === 27) {
                openScope.focusToggleElement();
                closeDropdown();
            }
        };

        this.keybindFilter = function (evt) {
            if (!openScope) {
                // see this.close as ESC could have been pressed which kills the scope so we can not proceed
                return;
            }

            var dropdownElement = openScope.getDropdownElement();
            var toggleElement = openScope.getToggleElement();
            var dropdownElementTargeted = dropdownElement && dropdownElement[0].contains(evt.target);
            var toggleElementTargeted = toggleElement && toggleElement[0].contains(evt.target);
            if (evt.which === 27) {
                evt.stopPropagation();
                openScope.focusToggleElement();
                closeDropdown();
            } else if (openScope.isKeynavEnabled() && [38, 40].indexOf(evt.which) !== -1 && openScope.isOpen && (dropdownElementTargeted || toggleElementTargeted)) {
                evt.preventDefault();
                evt.stopPropagation();
                openScope.focusDropdownEntry(evt.which);
            }
        };
    }])

    .controller('DropdownController', ['$scope', '$attrs', '$parse', 'dropdownConfig', 'dropdownService', '$animate', function ($scope, $attrs, $parse, dropdownConfig, dropdownService, $animate) {
        var self = this,
            scope = $scope.$new(), // create a child scope so we are not polluting original one
            openClass = dropdownConfig.openClass,
            getIsOpen,
            setIsOpen = angular.noop,
            toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop;

        this.init = function (element) {
            self.$element = element;

            if ($attrs.isOpen) {
                getIsOpen = $parse($attrs.isOpen);
                setIsOpen = getIsOpen.assign;

                $scope.$watch(getIsOpen, function (value) {
                    scope.isOpen = !!value;
                });
            }
        };

        this.toggle = function (open) {
            return scope.isOpen = arguments.length ? !!open : !scope.isOpen;
        };

        // Allow other directives to watch status
        this.isOpen = function () {
            return scope.isOpen;
        };

        scope.getToggleElement = function () {
            return self.toggleElement;
        };

        scope.getAutoClose = function () {
            return $attrs.autoClose || 'always'; //or 'outsideClick' or 'disabled'
        };

        scope.focusToggleElement = function () {
            if (self.toggleElement) {
                self.toggleElement[0].focus();
            }
        };

        scope.getDropdownElement = function () {
            return self.dropdownMenu;
        };

        scope.$watch('isOpen', function (isOpen, wasOpen) {
            $animate[isOpen ? 'addClass' : 'removeClass'](self.$element, openClass);

            if (isOpen) {
                scope.focusToggleElement();
                dropdownService.open(scope);
            } else {
                dropdownService.close(scope);
            }

            setIsOpen($scope, isOpen);
            if (angular.isDefined(isOpen) && isOpen !== wasOpen) {
                toggleInvoker($scope, { open: !!isOpen });
            }
        });

        $scope.$on('$locationChangeSuccess', function () {
            scope.isOpen = false;
        });

        $scope.$on('$destroy', function () {
            scope.$destroy();
        });
    }])

    .directive('dropdown', function () {
        return {
            controller: 'DropdownController',
            link: function (scope, element, attrs, dropdownCtrl) {
                dropdownCtrl.init(element);
            }
        };
    })
    .directive('dropdownMenu', function () {
        return {
            restrict: 'AC',
            require: '?^dropdown',
            link: function (scope, element, attrs, dropdownCtrl) {
                if (!dropdownCtrl || angular.isDefined(attrs.dropdownNested)) {
                    return;
                }

                element.addClass('dropdown-menu');

                var tplUrl = attrs.templateUrl;
                if (tplUrl) {
                    dropdownCtrl.dropdownMenuTemplateUrl = tplUrl;
                }

                if (!dropdownCtrl.dropdownMenu) {
                    dropdownCtrl.dropdownMenu = element;
                }
            }
        };
    })

    .directive('dropdownToggle', function () {
        return {
            require: '?^dropdown',
            link: function (scope, element, attrs, dropdownCtrl) {
                if (!dropdownCtrl) {
                    return;
                }

                dropdownCtrl.toggleElement = element;

                var toggleDropdown = function (event) {
                    event.preventDefault();

                    if (!element.hasClass('disabled') && !attrs.disabled) {
                        scope.$apply(function () {
                            dropdownCtrl.toggle();
                        });
                    }
                };

                element.bind('click', toggleDropdown);

                // WAI-ARIA
                element.attr({ 'aria-haspopup': true, 'aria-expanded': false });
                scope.$watch(dropdownCtrl.isOpen, function (isOpen) {
                    element.attr('aria-expanded', !!isOpen);
                });

                scope.$on('$destroy', function () {
                    element.unbind('click', toggleDropdown);
                });
            }
        };
    });

angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition'])

    /**
     * A helper, internal data structure that acts as a map but also allows getting / removing
     * elements in the LIFO order
     */
    .factory('$$stackedMap', function () {
        return {
            createNew: function () {
                var stack = [];

                return {
                    add: function (key, value) {
                        stack.push({
                            key: key,
                            value: value
                        });
                    },
                    get: function (key) {
                        for (var i = 0; i < stack.length; i++) {
                            if (key == stack[i].key) {
                                return stack[i];
                            }
                        }
                    },
                    keys: function () {
                        var keys = [];
                        for (var i = 0; i < stack.length; i++) {
                            keys.push(stack[i].key);
                        }
                        return keys;
                    },
                    top: function () {
                        return stack[stack.length - 1];
                    },
                    remove: function (key) {
                        var idx = -1;
                        for (var i = 0; i < stack.length; i++) {
                            if (key == stack[i].key) {
                                idx = i;
                                break;
                            }
                        }
                        return stack.splice(idx, 1)[0];
                    },
                    removeTop: function () {
                        return stack.splice(stack.length - 1, 1)[0];
                    },
                    length: function () {
                        return stack.length;
                    }
                };
            }
        };
    })

    /**
     * A helper directive for the $modal service. It creates a backdrop element.
     */
    .directive('modalBackdrop', ['$timeout', function ($timeout) {
        return {
            restrict: 'EA',
            replace: true,
            templateUrl: 'template/modal/backdrop.html',
            link: function (scope, element, attrs) {
                scope.backdropClass = attrs.backdropClass || '';

                scope.animate = false;

                //trigger CSS transitions
                $timeout(function () {
                    scope.animate = true;
                });
            }
        };
    }])

    .directive('modalWindow', ['$modalStack', '$timeout', function ($modalStack, $timeout) {
        return {
            restrict: 'EA',
            scope: {
                index: '@',
                animate: '='
            },
            replace: true,
            transclude: true,
            templateUrl: function (tElement, tAttrs) {
                return tAttrs.templateUrl || 'template/modal/window.html';
            },
            link: function (scope, element, attrs) {
                element.addClass(attrs.windowClass || '');
                scope.size = attrs.size;

                $timeout(function () {
                    // trigger CSS transitions
                    scope.animate = true;

                    /**
                     * Auto-focusing of a freshly-opened modal element causes any child elements
                     * with the autofocus attribute to lose focus. This is an issue on touch
                     * based devices which will show and then hide the onscreen keyboard.
                     * Attempts to refocus the autofocus element via JavaScript will not reopen
                     * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus
                     * the modal element if the modal does not contain an autofocus element.
                     */
                    if (!element[0].querySelectorAll('[autofocus]').length) {
                        element[0].focus();
                    }
                });

                scope.close = function (evt) {
                    var modal = $modalStack.getTop();
                    if (modal && modal.value.backdrop && modal.value.backdrop != 'static' && (evt.target === evt.currentTarget)) {
                        evt.preventDefault();
                        evt.stopPropagation();
                        $modalStack.dismiss(modal.key, 'backdrop click');
                    }
                };
            }
        };
    }])

    .directive('modalTransclude', function () {
        return {
            link: function ($scope, $element, $attrs, controller, $transclude) {
                $transclude($scope.$parent, function (clone) {
                    $element.empty();
                    $element.append(clone);
                });
            }
        };
    })

    .factory('$modalStack', ['$transition', '$timeout', '$document', '$compile', '$rootScope', '$$stackedMap',
        function ($transition, $timeout, $document, $compile, $rootScope, $$stackedMap) {

            var OPENED_MODAL_CLASS = 'modal-open';

            var backdropDomEl, backdropScope;
            var openedWindows = $$stackedMap.createNew();
            var $modalStack = {};

            function backdropIndex() {
                var topBackdropIndex = -1;
                var opened = openedWindows.keys();
                for (var i = 0; i < opened.length; i++) {
                    if (openedWindows.get(opened[i]).value.backdrop) {
                        topBackdropIndex = i;
                    }
                }
                return topBackdropIndex;
            }

            $rootScope.$watch(backdropIndex, function (newBackdropIndex) {
                if (backdropScope) {
                    backdropScope.index = newBackdropIndex;
                }
            });

            function removeModalWindow(modalInstance) {

                var body = $document.find('body').eq(0);
                var modalWindow = openedWindows.get(modalInstance).value;

                //clean up the stack
                openedWindows.remove(modalInstance);

                //remove window DOM element
                removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, 300, function () {
                    modalWindow.modalScope.$destroy();
                    body.toggleClass(OPENED_MODAL_CLASS, openedWindows.length() > 0);
                    checkRemoveBackdrop();
                });
            }

            function checkRemoveBackdrop() {
                //remove backdrop if no longer needed
                if (backdropDomEl && backdropIndex() == -1) {
                    var backdropScopeRef = backdropScope;
                    removeAfterAnimate(backdropDomEl, backdropScope, 150, function () {
                        backdropScopeRef.$destroy();
                        backdropScopeRef = null;
                    });
                    backdropDomEl = undefined;
                    backdropScope = undefined;
                }
            }

            function removeAfterAnimate(domEl, scope, emulateTime, done) {
                // Closing animation
                scope.animate = false;

                var transitionEndEventName = $transition.transitionEndEventName;
                if (transitionEndEventName) {
                    // transition out
                    var timeout = $timeout(afterAnimating, emulateTime);

                    domEl.bind(transitionEndEventName, function () {
                        $timeout.cancel(timeout);
                        afterAnimating();
                        scope.$apply();
                    });
                } else {
                    // Ensure this call is async
                    $timeout(afterAnimating);
                }

                function afterAnimating() {
                    if (afterAnimating.done) {
                        return;
                    }
                    afterAnimating.done = true;

                    domEl.remove();
                    if (done) {
                        done();
                    }
                }
            }

            $document.bind('keydown', function (evt) {
                var modal;

                if (evt.which === 27) {
                    modal = openedWindows.top();
                    if (modal && modal.value.keyboard) {
                        evt.preventDefault();
                        $rootScope.$apply(function () {
                            $modalStack.dismiss(modal.key, 'escape key press');
                        });
                    }
                }
            });

            $modalStack.open = function (modalInstance, modal) {

                openedWindows.add(modalInstance, {
                    deferred: modal.deferred,
                    modalScope: modal.scope,
                    backdrop: modal.backdrop,
                    keyboard: modal.keyboard
                });

                var body = $document.find('body').eq(0),
                    currBackdropIndex = backdropIndex();

                if (currBackdropIndex >= 0 && !backdropDomEl) {
                    backdropScope = $rootScope.$new(true);
                    backdropScope.index = currBackdropIndex;
                    var angularBackgroundDomEl = angular.element('<div modal-backdrop></div>');
                    angularBackgroundDomEl.attr('backdrop-class', modal.backdropClass);
                    backdropDomEl = $compile(angularBackgroundDomEl)(backdropScope);
                    body.append(backdropDomEl);
                }

                var angularDomEl = angular.element('<div modal-window></div>');
                angularDomEl.attr({
                    'template-url': modal.windowTemplateUrl,
                    'window-class': modal.windowClass,
                    'size': modal.size,
                    'index': openedWindows.length() - 1,
                    'animate': 'animate'
                }).html(modal.content);

                var modalDomEl = $compile(angularDomEl)(modal.scope);
                openedWindows.top().value.modalDomEl = modalDomEl;
                body.append(modalDomEl);
                body.addClass(OPENED_MODAL_CLASS);
            };

            $modalStack.close = function (modalInstance, result) {
                var modalWindow = openedWindows.get(modalInstance);
                if (modalWindow) {
                    modalWindow.value.deferred.resolve(result);
                    removeModalWindow(modalInstance);
                }
            };

            $modalStack.dismiss = function (modalInstance, reason) {
                var modalWindow = openedWindows.get(modalInstance);
                if (modalWindow) {
                    modalWindow.value.deferred.reject(reason);
                    removeModalWindow(modalInstance);
                }
            };

            $modalStack.dismissAll = function (reason) {
                var topModal = this.getTop();
                while (topModal) {
                    this.dismiss(topModal.key, reason);
                    topModal = this.getTop();
                }
            };

            $modalStack.getTop = function () {
                return openedWindows.top();
            };

            return $modalStack;
        }])

    .provider('$modal', function () {

        var $modalProvider = {
            options: {
                backdrop: true, //can be also false or 'static'
                keyboard: true
            },
            $get: ['$injector', '$rootScope', '$q', '$http', '$templateCache', '$controller', '$modalStack',
                function ($injector, $rootScope, $q, $http, $templateCache, $controller, $modalStack) {

                    var $modal = {};

                    function getTemplatePromise(options) {
                        return options.template ? $q.when(options.template) :
                            $http.get(angular.isFunction(options.templateUrl) ? (options.templateUrl)() : options.templateUrl,
                                { cache: $templateCache }).then(function (result) {
                                    return result.data;
                                });
                    }

                    function getResolvePromises(resolves) {
                        var promisesArr = [];
                        angular.forEach(resolves, function (value) {
                            if (angular.isFunction(value) || angular.isArray(value)) {
                                promisesArr.push($q.when($injector.invoke(value)));
                            }
                        });
                        return promisesArr;
                    }

                    $modal.open = function (modalOptions) {

                        var modalResultDeferred = $q.defer();
                        var modalOpenedDeferred = $q.defer();

                        //prepare an instance of a modal to be injected into controllers and returned to a caller
                        var modalInstance = {
                            result: modalResultDeferred.promise,
                            opened: modalOpenedDeferred.promise,
                            close: function (result) {
                                $modalStack.close(modalInstance, result);
                            },
                            dismiss: function (reason) {
                                $modalStack.dismiss(modalInstance, reason);
                            }
                        };

                        //merge and clean up options
                        modalOptions = angular.extend({}, $modalProvider.options, modalOptions);
                        modalOptions.resolve = modalOptions.resolve || {};

                        //verify options
                        if (!modalOptions.template && !modalOptions.templateUrl) {
                            throw new Error('One of template or templateUrl options is required.');
                        }

                        var templateAndResolvePromise =
                            $q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve)));


                        templateAndResolvePromise.then(function resolveSuccess(tplAndVars) {

                            var modalScope = (modalOptions.scope || $rootScope).$new();
                            modalScope.$close = modalInstance.close;
                            modalScope.$dismiss = modalInstance.dismiss;

                            var ctrlInstance, ctrlLocals = {};
                            var resolveIter = 1;

                            //controllers
                            if (modalOptions.controller) {
                                ctrlLocals.$scope = modalScope;
                                ctrlLocals.$modalInstance = modalInstance;
                                angular.forEach(modalOptions.resolve, function (value, key) {
                                    ctrlLocals[key] = tplAndVars[resolveIter++];
                                });

                                ctrlInstance = $controller(modalOptions.controller, ctrlLocals);
                                if (modalOptions.controllerAs) {
                                    modalScope[modalOptions.controllerAs] = ctrlInstance;
                                }
                            }

                            $modalStack.open(modalInstance, {
                                scope: modalScope,
                                deferred: modalResultDeferred,
                                content: tplAndVars[0],
                                backdrop: modalOptions.backdrop,
                                keyboard: modalOptions.keyboard,
                                backdropClass: modalOptions.backdropClass,
                                windowClass: modalOptions.windowClass,
                                windowTemplateUrl: modalOptions.windowTemplateUrl,
                                size: modalOptions.size
                            });

                        }, function resolveError(reason) {
                            modalResultDeferred.reject(reason);
                        });

                        templateAndResolvePromise.then(function () {
                            modalOpenedDeferred.resolve(true);
                        }, function () {
                            modalOpenedDeferred.reject(false);
                        });

                        return modalInstance;
                    };

                    return $modal;
                }]
        };

        return $modalProvider;
    });

angular.module('ui.bootstrap.pagination', [])

    .controller('PaginationController', ['$scope', '$attrs', '$parse', function ($scope, $attrs, $parse) {
        var self = this,
            ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl
            setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop;

        this.init = function (ngModelCtrl_, config) {
            ngModelCtrl = ngModelCtrl_;
            this.config = config;

            ngModelCtrl.$render = function () {
                self.render();
            };

            if ($attrs.itemsPerPage) {
                $scope.$parent.$watch($parse($attrs.itemsPerPage), function (value) {
                    self.itemsPerPage = parseInt(value, 10);
                    $scope.totalPages = self.calculateTotalPages();
                });
            } else {
                this.itemsPerPage = config.itemsPerPage;
            }
        };

        this.calculateTotalPages = function () {
            var totalPages = this.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / this.itemsPerPage);
            return Math.max(totalPages || 0, 1);
        };

        this.render = function () {
            $scope.page = parseInt(ngModelCtrl.$viewValue, 10) || 1;
        };

        $scope.selectPage = function (page) {
            if ($scope.page !== page && page > 0 && page <= $scope.totalPages) {
                ngModelCtrl.$setViewValue(page);
                ngModelCtrl.$render();
            }
        };

        $scope.getText = function (key) {
            return $scope[key + 'Text'] || self.config[key + 'Text'];
        };
        $scope.noPrevious = function () {
            return $scope.page === 1;
        };
        $scope.noNext = function () {
            return $scope.page === $scope.totalPages;
        };

        $scope.$watch('totalItems', function () {
            $scope.totalPages = self.calculateTotalPages();
        });

        $scope.$watch('totalPages', function (value) {
            setNumPages($scope.$parent, value); // Readonly variable

            if ($scope.page > value) {
                $scope.selectPage(value);
            } else {
                ngModelCtrl.$render();
            }
        });
    }])

    .constant('paginationConfig', {
        itemsPerPage: 10,
        boundaryLinks: false,
        directionLinks: true,
        firstText: 'First',
        previousText: 'Previous',
        nextText: 'Next',
        lastText: 'Last',
        rotate: true
    })

    .directive('pagination', ['$parse', 'paginationConfig', function ($parse, paginationConfig) {
        return {
            restrict: 'EA',
            scope: {
                totalItems: '=',
                firstText: '@',
                previousText: '@',
                nextText: '@',
                lastText: '@'
            },
            require: ['pagination', '?ngModel'],
            controller: 'PaginationController',
            templateUrl: 'template/pagination/pagination.html',
            replace: true,
            link: function (scope, element, attrs, ctrls) {
                var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];

                if (!ngModelCtrl) {
                    return; // do nothing if no ng-model
                }

                // Setup configuration parameters
                var maxSize = angular.isDefined(attrs.maxSize) ? scope.$parent.$eval(attrs.maxSize) : paginationConfig.maxSize,
                    rotate = angular.isDefined(attrs.rotate) ? scope.$parent.$eval(attrs.rotate) : paginationConfig.rotate;
                scope.boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$parent.$eval(attrs.boundaryLinks) : paginationConfig.boundaryLinks;
                scope.directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$parent.$eval(attrs.directionLinks) : paginationConfig.directionLinks;

                paginationCtrl.init(ngModelCtrl, paginationConfig);

                if (attrs.maxSize) {
                    scope.$parent.$watch($parse(attrs.maxSize), function (value) {
                        maxSize = parseInt(value, 10);
                        paginationCtrl.render();
                    });
                }

                // Create page object used in template
                function makePage(number, text, isActive) {
                    return {
                        number: number,
                        text: text,
                        active: isActive
                    };
                }

                function getPages(currentPage, totalPages) {
                    var pages = [];

                    // Default page limits
                    var startPage = 1, endPage = totalPages;
                    var isMaxSized = (angular.isDefined(maxSize) && maxSize < totalPages);

                    // recompute if maxSize
                    if (isMaxSized) {
                        if (rotate) {
                            // Current page is displayed in the middle of the visible ones
                            startPage = Math.max(currentPage - Math.floor(maxSize / 2), 1);
                            endPage = startPage + maxSize - 1;

                            // Adjust if limit is exceeded
                            if (endPage > totalPages) {
                                endPage = totalPages;
                                startPage = endPage - maxSize + 1;
                            }
                        } else {
                            // Visible pages are paginated with maxSize
                            startPage = ((Math.ceil(currentPage / maxSize) - 1) * maxSize) + 1;

                            // Adjust last page if limit is exceeded
                            endPage = Math.min(startPage + maxSize - 1, totalPages);
                        }
                    }

                    // Add page number links
                    for (var number = startPage; number <= endPage; number++) {
                        var page = makePage(number, number, number === currentPage);
                        pages.push(page);
                    }

                    // Add links to move between page sets
                    if (isMaxSized && !rotate) {
                        if (startPage > 1) {
                            var previousPageSet = makePage(startPage - 1, '...', false);
                            pages.unshift(previousPageSet);
                        }

                        if (endPage < totalPages) {
                            var nextPageSet = makePage(endPage + 1, '...', false);
                            pages.push(nextPageSet);
                        }
                    }

                    return pages;
                }

                var originalRender = paginationCtrl.render;
                paginationCtrl.render = function () {
                    originalRender();
                    if (scope.page > 0 && scope.page <= scope.totalPages) {
                        scope.pages = getPages(scope.page, scope.totalPages);
                    }
                };
            }
        };
    }])

    .constant('pagerConfig', {
        itemsPerPage: 10,
        previousText: '« Previous',
        nextText: 'Next »',
        align: true
    })

    .directive('pager', ['pagerConfig', function (pagerConfig) {
        return {
            restrict: 'EA',
            scope: {
                totalItems: '=',
                previousText: '@',
                nextText: '@'
            },
            require: ['pager', '?ngModel'],
            controller: 'PaginationController',
            templateUrl: 'template/pagination/pager.html',
            replace: true,
            link: function (scope, element, attrs, ctrls) {
                var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];

                if (!ngModelCtrl) {
                    return; // do nothing if no ng-model
                }

                scope.align = angular.isDefined(attrs.align) ? scope.$parent.$eval(attrs.align) : pagerConfig.align;
                paginationCtrl.init(ngModelCtrl, pagerConfig);
            }
        };
    }]);

/**
 * The following features are still outstanding: animation as a
 * function, placement as a function, inside, support for more triggers than
 * just mouse enter/leave, html tooltips, and selector delegation.
 */
angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.bindHtml'])

    /**
     * The $tooltip service creates tooltip- and popover-like directives as well as
     * houses global options for them.
     */
    .provider('$tooltip', function () {
        // The default options tooltip and popover.
        var defaultOptions = {
            placement: 'top',
            animation: true,
            popupDelay: 0
        };

        // Default hide triggers for each show trigger
        var triggerMap = {
            'mouseenter': 'mouseleave',
            'click': 'click',
            'focus': 'blur'
        };

        // The options specified to the provider globally.
        var globalOptions = {};

        /**
         * `options({})` allows global configuration of all tooltips in the
         * application.
         *
         *   var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) {
         *     // place tooltips left instead of top by default
         *     $tooltipProvider.options( { placement: 'left' } );
         *   });
         */
        this.options = function (value) {
            angular.extend(globalOptions, value);
        };

        /**
         * This allows you to extend the set of trigger mappings available. E.g.:
         *
         *   $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' );
         */
        this.setTriggers = function setTriggers(triggers) {
            angular.extend(triggerMap, triggers);
        };

        /**
         * This is a helper function for translating camel-case to snake-case.
         */
        function snake_case(name) {
            var regexp = /[A-Z]/g;
            var separator = '-';
            return name.replace(regexp, function (letter, pos) {
                return (pos ? separator : '') + letter.toLowerCase();
            });
        }

        /**
         * Returns the actual instance of the $tooltip service.
         * TODO support multiple triggers
         */
        this.$get = ['$window', '$compile', '$timeout', '$document', '$position', '$interpolate', function ($window, $compile, $timeout, $document, $position, $interpolate) {
            return function $tooltip(type, prefix, defaultTriggerShow) {
                var options = angular.extend({}, defaultOptions, globalOptions);

                /**
                 * Returns an object of show and hide triggers.
                 *
                 * If a trigger is supplied,
                 * it is used to show the tooltip; otherwise, it will use the `trigger`
                 * option passed to the `$tooltipProvider.options` method; else it will
                 * default to the trigger supplied to this directive factory.
                 *
                 * The hide trigger is based on the show trigger. If the `trigger` option
                 * was passed to the `$tooltipProvider.options` method, it will use the
                 * mapped trigger from `triggerMap` or the passed trigger if the map is
                 * undefined; otherwise, it uses the `triggerMap` value of the show
                 * trigger; else it will just use the show trigger.
                 */
                function getTriggers(trigger) {
                    var show = trigger || options.trigger || defaultTriggerShow;
                    var hide = triggerMap[show] || show;
                    return {
                        show: show,
                        hide: hide
                    };
                }

                var directiveName = snake_case(type);

                var startSym = $interpolate.startSymbol();
                var endSym = $interpolate.endSymbol();
                var template =
                    '<div ' + directiveName + '-popup ' +
                    'title="' + startSym + 'title' + endSym + '" ' +
                    'content="' + startSym + 'content' + endSym + '" ' +
                    'placement="' + startSym + 'placement' + endSym + '" ' +
                    'animation="animation" ' +
                    'is-open="isOpen"' +
                    '>' +
                    '</div>';

                return {
                    restrict: 'EA',
                    compile: function (tElem, tAttrs) {
                        var tooltipLinker = $compile(template);

                        return function link(scope, element, attrs) {
                            var tooltip;
                            var tooltipLinkedScope;
                            var transitionTimeout;
                            var popupTimeout;
                            var appendToBody = angular.isDefined(options.appendToBody) ? options.appendToBody : false;
                            var triggers = getTriggers(undefined);
                            var hasEnableExp = angular.isDefined(attrs[prefix + 'Enable']);
                            var ttScope = scope.$new(true);

                            var positionTooltip = function () {

                                var ttPosition = $position.positionElements(element, tooltip, ttScope.placement, appendToBody);
                                ttPosition.top += 'px';
                                ttPosition.left += 'px';

                                // Now set the calculated positioning.
                                tooltip.css(ttPosition);
                            };

                            // By default, the tooltip is not open.
                            // TODO add ability to start tooltip opened
                            ttScope.isOpen = false;

                            function toggleTooltipBind() {
                                if (!ttScope.isOpen) {
                                    showTooltipBind();
                                } else {
                                    hideTooltipBind();
                                }
                            }

                            // Show the tooltip with delay if specified, otherwise show it immediately
                            function showTooltipBind() {
                                if (hasEnableExp && !scope.$eval(attrs[prefix + 'Enable'])) {
                                    return;
                                }

                                prepareTooltip();

                                if (ttScope.popupDelay) {
                                    // Do nothing if the tooltip was already scheduled to pop-up.
                                    // This happens if show is triggered multiple times before any hide is triggered.
                                    if (!popupTimeout) {
                                        popupTimeout = $timeout(show, ttScope.popupDelay, false);
                                        popupTimeout.then(function (reposition) { reposition(); });
                                    }
                                } else {
                                    show()();
                                }
                            }

                            function hideTooltipBind() {
                                scope.$apply(function () {
                                    hide();
                                });
                            }

                            // Show the tooltip popup element.
                            function show() {

                                popupTimeout = null;

                                // If there is a pending remove transition, we must cancel it, lest the
                                // tooltip be mysteriously removed.
                                if (transitionTimeout) {
                                    $timeout.cancel(transitionTimeout);
                                    transitionTimeout = null;
                                }

                                // Don't show empty tooltips.
                                if (!ttScope.content) {
                                    return angular.noop;
                                }

                                createTooltip();

                                // Set the initial positioning.
                                tooltip.css({ top: 0, left: 0, display: 'block' });

                                // Now we add it to the DOM because need some info about it. But it's not
                                // visible yet anyway.
                                if (appendToBody) {
                                    $document.find('body').append(tooltip);
                                } else {
                                    element.after(tooltip);
                                }

                                positionTooltip();

                                // And show the tooltip.
                                ttScope.isOpen = true;
                                ttScope.$digest(); // digest required as $apply is not called

                                // Return positioning function as promise callback for correct
                                // positioning after draw.
                                return positionTooltip;
                            }

                            // Hide the tooltip popup element.
                            function hide() {
                                // First things first: we don't show it anymore.
                                ttScope.isOpen = false;

                                //if tooltip is going to be shown after delay, we must cancel this
                                $timeout.cancel(popupTimeout);
                                popupTimeout = null;

                                // And now we remove it from the DOM. However, if we have animation, we
                                // need to wait for it to expire beforehand.
                                // FIXME: this is a placeholder for a port of the transitions library.
                                if (ttScope.animation) {
                                    if (!transitionTimeout) {
                                        transitionTimeout = $timeout(removeTooltip, 500);
                                    }
                                } else {
                                    removeTooltip();
                                }
                            }

                            function createTooltip() {
                                // There can only be one tooltip element per directive shown at once.
                                if (tooltip) {
                                    removeTooltip();
                                }
                                tooltipLinkedScope = ttScope.$new();
                                tooltip = tooltipLinker(tooltipLinkedScope, angular.noop);
                            }

                            function removeTooltip() {
                                transitionTimeout = null;
                                if (tooltip) {
                                    tooltip.remove();
                                    tooltip = null;
                                }
                                if (tooltipLinkedScope) {
                                    tooltipLinkedScope.$destroy();
                                    tooltipLinkedScope = null;
                                }
                            }

                            function prepareTooltip() {
                                prepPlacement();
                                prepPopupDelay();
                            }

                            /**
                             * Observe the relevant attributes.
                             */
                            attrs.$observe(type, function (val) {
                                ttScope.content = val;

                                if (!val && ttScope.isOpen) {
                                    hide();
                                }
                            });

                            attrs.$observe(prefix + 'Title', function (val) {
                                ttScope.title = val;
                            });

                            function prepPlacement() {
                                var val = attrs[prefix + 'Placement'];
                                ttScope.placement = angular.isDefined(val) ? val : options.placement;
                            }

                            function prepPopupDelay() {
                                var val = attrs[prefix + 'PopupDelay'];
                                var delay = parseInt(val, 10);
                                ttScope.popupDelay = !isNaN(delay) ? delay : options.popupDelay;
                            }

                            var unregisterTriggers = function () {
                                element.unbind(triggers.show, showTooltipBind);
                                element.unbind(triggers.hide, hideTooltipBind);
                            };

                            function prepTriggers() {
                                var val = attrs[prefix + 'Trigger'];
                                unregisterTriggers();

                                triggers = getTriggers(val);

                                if (triggers.show === triggers.hide) {
                                    element.bind(triggers.show, toggleTooltipBind);
                                } else {
                                    element.bind(triggers.show, showTooltipBind);
                                    element.bind(triggers.hide, hideTooltipBind);
                                }
                            }
                            prepTriggers();

                            var animation = scope.$eval(attrs[prefix + 'Animation']);
                            ttScope.animation = angular.isDefined(animation) ? !!animation : options.animation;

                            var appendToBodyVal = scope.$eval(attrs[prefix + 'AppendToBody']);
                            appendToBody = angular.isDefined(appendToBodyVal) ? appendToBodyVal : appendToBody;

                            // if a tooltip is attached to <body> we need to remove it on
                            // location change as its parent scope will probably not be destroyed
                            // by the change.
                            if (appendToBody) {
                                scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess() {
                                    if (ttScope.isOpen) {
                                        hide();
                                    }
                                });
                            }

                            // Make sure tooltip is destroyed and removed.
                            scope.$on('$destroy', function onDestroyTooltip() {
                                $timeout.cancel(transitionTimeout);
                                $timeout.cancel(popupTimeout);
                                unregisterTriggers();
                                removeTooltip();
                                ttScope = null;
                            });
                        };
                    }
                };
            };
        }];
    })

    .directive('tooltipPopup', function () {
        return {
            restrict: 'EA',
            replace: true,
            scope: { content: '@', placement: '@', animation: '&', isOpen: '&' },
            templateUrl: 'template/tooltip/tooltip-popup.html'
        };
    })

    .directive('tooltip', ['$tooltip', function ($tooltip) {
        return $tooltip('tooltip', 'tooltip', 'mouseenter');
    }])

    .directive('tooltipHtmlUnsafePopup', function () {
        return {
            restrict: 'EA',
            replace: true,
            scope: { content: '@', placement: '@', animation: '&', isOpen: '&' },
            templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html'
        };
    })

    .directive('tooltipHtmlUnsafe', ['$tooltip', function ($tooltip) {
        return $tooltip('tooltipHtmlUnsafe', 'tooltip', 'mouseenter');
    }]);

/**
 * The following features are still outstanding: popup delay, animation as a
 * function, placement as a function, inside, support for more triggers than
 * just mouse enter/leave, html popovers, and selector delegatation.
 */
angular.module('ui.bootstrap.popover', ['ui.bootstrap.tooltip'])

    .directive('popoverPopup', function () {
        return {
            restrict: 'EA',
            replace: true,
            scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' },
            templateUrl: 'template/popover/popover.html'
        };
    })

    .directive('popover', ['$tooltip', function ($tooltip) {
        return $tooltip('popover', 'popover', 'click');
    }]);

angular.module('ui.bootstrap.progressbar', [])

    .constant('progressConfig', {
        animate: true,
        max: 100
    })

    .controller('ProgressController', ['$scope', '$attrs', 'progressConfig', function ($scope, $attrs, progressConfig) {
        var self = this,
            animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate;

        this.bars = [];
        $scope.max = angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : progressConfig.max;

        this.addBar = function (bar, element) {
            if (!animate) {
                element.css({ 'transition': 'none' });
            }

            this.bars.push(bar);

            bar.$watch('value', function (value) {
                bar.percent = +(100 * value / $scope.max).toFixed(2);
            });

            bar.$on('$destroy', function () {
                element = null;
                self.removeBar(bar);
            });
        };

        this.removeBar = function (bar) {
            this.bars.splice(this.bars.indexOf(bar), 1);
        };
    }])

    .directive('progress', function () {
        return {
            restrict: 'EA',
            replace: true,
            transclude: true,
            controller: 'ProgressController',
            require: 'progress',
            scope: {},
            templateUrl: 'template/progressbar/progress.html'
        };
    })

    .directive('bar', function () {
        return {
            restrict: 'EA',
            replace: true,
            transclude: true,
            require: '^progress',
            scope: {
                value: '=',
                type: '@'
            },
            templateUrl: 'template/progressbar/bar.html',
            link: function (scope, element, attrs, progressCtrl) {
                progressCtrl.addBar(scope, element);
            }
        };
    })

    .directive('progressbar', function () {
        return {
            restrict: 'EA',
            replace: true,
            transclude: true,
            controller: 'ProgressController',
            scope: {
                value: '=',
                type: '@'
            },
            templateUrl: 'template/progressbar/progressbar.html',
            link: function (scope, element, attrs, progressCtrl) {
                progressCtrl.addBar(scope, angular.element(element.children()[0]));
            }
        };
    });
angular.module('ui.bootstrap.rating', [])

    .constant('ratingConfig', {
        max: 5,
        stateOn: null,
        stateOff: null
    })

    .controller('RatingController', ['$scope', '$attrs', 'ratingConfig', function ($scope, $attrs, ratingConfig) {
        var ngModelCtrl = { $setViewValue: angular.noop };

        this.init = function (ngModelCtrl_) {
            ngModelCtrl = ngModelCtrl_;
            ngModelCtrl.$render = this.render;

            this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn;
            this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff;

            var ratingStates = angular.isDefined($attrs.ratingStates) ? $scope.$parent.$eval($attrs.ratingStates) :
                new Array(angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max);
            $scope.range = this.buildTemplateObjects(ratingStates);
        };

        this.buildTemplateObjects = function (states) {
            for (var i = 0, n = states.length; i < n; i++) {
                states[i] = angular.extend({ index: i }, { stateOn: this.stateOn, stateOff: this.stateOff }, states[i]);
            }
            return states;
        };

        $scope.rate = function (value) {
            if (!$scope.readonly && value >= 0 && value <= $scope.range.length) {
                ngModelCtrl.$setViewValue(value);
                ngModelCtrl.$render();
            }
        };

        $scope.enter = function (value) {
            if (!$scope.readonly) {
                $scope.value = value;
            }
            $scope.onHover({ value: value });
        };

        $scope.reset = function () {
            $scope.value = ngModelCtrl.$viewValue;
            $scope.onLeave();
        };

        $scope.onKeydown = function (evt) {
            if (/(37|38|39|40)/.test(evt.which)) {
                evt.preventDefault();
                evt.stopPropagation();
                $scope.rate($scope.value + (evt.which === 38 || evt.which === 39 ? 1 : -1));
            }
        };

        this.render = function () {
            $scope.value = ngModelCtrl.$viewValue;
        };
    }])

    .directive('rating', function () {
        return {
            restrict: 'EA',
            require: ['rating', 'ngModel'],
            scope: {
                readonly: '=?',
                onHover: '&',
                onLeave: '&'
            },
            controller: 'RatingController',
            templateUrl: 'template/rating/rating.html',
            replace: true,
            link: function (scope, element, attrs, ctrls) {
                var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1];

                if (ngModelCtrl) {
                    ratingCtrl.init(ngModelCtrl);
                }
            }
        };
    });

/**
 * @ngdoc overview
 * @name ui.bootstrap.tabs
 *
 * @description
 * AngularJS version of the tabs directive.
 */

angular.module('ui.bootstrap.tabs', [])

    .controller('TabsetController', ['$scope', function TabsetCtrl($scope) {
        var ctrl = this,
            tabs = ctrl.tabs = $scope.tabs = [];

        ctrl.select = function (selectedTab) {
            angular.forEach(tabs, function (tab) {
                if (tab.active && tab !== selectedTab) {
                    tab.active = false;
                    tab.onDeselect();
                }
            });
            selectedTab.active = true;
            selectedTab.onSelect();
        };

        ctrl.addTab = function addTab(tab) {
            tabs.push(tab);
            // we can't run the select function on the first tab
            // since that would select it twice
            if (tabs.length === 1) {
                tab.active = true;
            } else if (tab.active) {
                ctrl.select(tab);
            }
        };

        ctrl.removeTab = function removeTab(tab) {
            var index = tabs.indexOf(tab);
            //Select a new tab if the tab to be removed is selected and not destroyed
            if (tab.active && tabs.length > 1 && !destroyed) {
                //If this is the last tab, select the previous tab. else, the next tab.
                var newActiveIndex = index == tabs.length - 1 ? index - 1 : index + 1;
                ctrl.select(tabs[newActiveIndex]);
            }
            tabs.splice(index, 1);
        };

        var destroyed;
        $scope.$on('$destroy', function () {
            destroyed = true;
        });
    }])

    /**
     * @ngdoc directive
     * @name ui.bootstrap.tabs.directive:tabset
     * @restrict EA
     *
     * @description
     * Tabset is the outer container for the tabs directive
     *
     * @param {boolean=} vertical Whether or not to use vertical styling for the tabs.
     * @param {boolean=} justified Whether or not to use justified styling for the tabs.
     *
     * @example
    <example module="ui.bootstrap">
      <file name="index.html">
        <tabset>
          <tab heading="Tab 1"><b>First</b> Content!</tab>
          <tab heading="Tab 2"><i>Second</i> Content!</tab>
        </tabset>
        <hr />
        <tabset vertical="true">
          <tab heading="Vertical Tab 1"><b>First</b> Vertical Content!</tab>
          <tab heading="Vertical Tab 2"><i>Second</i> Vertical Content!</tab>
        </tabset>
        <tabset justified="true">
          <tab heading="Justified Tab 1"><b>First</b> Justified Content!</tab>
          <tab heading="Justified Tab 2"><i>Second</i> Justified Content!</tab>
        </tabset>
      </file>
    </example>
     */
    .directive('tabset', function () {
        return {
            restrict: 'EA',
            transclude: true,
            replace: true,
            scope: {
                type: '@'
            },
            controller: 'TabsetController',
            templateUrl: 'template/tabs/tabset.html',
            link: function (scope, element, attrs) {
                scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false;
                scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false;
            }
        };
    })

    /**
     * @ngdoc directive
     * @name ui.bootstrap.tabs.directive:tab
     * @restrict EA
     *
     * @param {string=} heading The visible heading, or title, of the tab. Set HTML headings with {@link ui.bootstrap.tabs.directive:tabHeading tabHeading}.
     * @param {string=} select An expression to evaluate when the tab is selected.
     * @param {boolean=} active A binding, telling whether or not this tab is selected.
     * @param {boolean=} disabled A binding, telling whether or not this tab is disabled.
     *
     * @description
     * Creates a tab with a heading and content. Must be placed within a {@link ui.bootstrap.tabs.directive:tabset tabset}.
     *
     * @example
    <example module="ui.bootstrap">
      <file name="index.html">
        <div ng-controller="TabsDemoCtrl">
          <button class="btn btn-small" ng-click="items[0].active = true">
            Select item 1, using active binding
          </button>
          <button class="btn btn-small" ng-click="items[1].disabled = !items[1].disabled">
            Enable/disable item 2, using disabled binding
          </button>
          <br />
          <tabset>
            <tab heading="Tab 1">First Tab</tab>
            <tab select="alertMe()">
              <tab-heading><i class="icon-bell"></i> Alert me!</tab-heading>
              Second Tab, with alert callback and html heading!
            </tab>
            <tab ng-repeat="item in items"
              heading="{{item.title}}"
              disabled="item.disabled"
              active="item.active">
              {{item.content}}
            </tab>
          </tabset>
        </div>
      </file>
      <file name="script.js">
        function TabsDemoCtrl($scope) {
          $scope.items = [
            { title:"Dynamic Title 1", content:"Dynamic Item 0" },
            { title:"Dynamic Title 2", content:"Dynamic Item 1", disabled: true }
          ];
    
          $scope.alertMe = function() {
            setTimeout(function() {
              alert("You've selected the alert tab!");
            });
          };
        };
      </file>
    </example>
     */

    /**
     * @ngdoc directive
     * @name ui.bootstrap.tabs.directive:tabHeading
     * @restrict EA
     *
     * @description
     * Creates an HTML heading for a {@link ui.bootstrap.tabs.directive:tab tab}. Must be placed as a child of a tab element.
     *
     * @example
    <example module="ui.bootstrap">
      <file name="index.html">
        <tabset>
          <tab>
            <tab-heading><b>HTML</b> in my titles?!</tab-heading>
            And some content, too!
          </tab>
          <tab>
            <tab-heading><i class="icon-heart"></i> Icon heading?!?</tab-heading>
            That's right.
          </tab>
        </tabset>
      </file>
    </example>
     */
    .directive('tab', ['$parse', function ($parse) {
        return {
            require: '^tabset',
            restrict: 'EA',
            replace: true,
            templateUrl: 'template/tabs/tab.html',
            transclude: true,
            scope: {
                active: '=?',
                heading: '@',
                onSelect: '&select', //This callback is called in contentHeadingTransclude
                //once it inserts the tab's content into the dom
                onDeselect: '&deselect'
            },
            controller: function () {
                //Empty controller so other directives can require being 'under' a tab
            },
            compile: function (elm, attrs, transclude) {
                return function postLink(scope, elm, attrs, tabsetCtrl) {
                    scope.$watch('active', function (active) {
                        if (active) {
                            tabsetCtrl.select(scope);
                        }
                    });

                    scope.disabled = false;
                    if (attrs.disabled) {
                        scope.$parent.$watch($parse(attrs.disabled), function (value) {
                            scope.disabled = !!value;
                        });
                    }

                    scope.select = function () {
                        if (!scope.disabled) {
                            scope.active = true;
                        }
                    };

                    tabsetCtrl.addTab(scope);
                    scope.$on('$destroy', function () {
                        tabsetCtrl.removeTab(scope);
                    });

                    //We need to transclude later, once the content container is ready.
                    //when this link happens, we're inside a tab heading.
                    scope.$transcludeFn = transclude;
                };
            }
        };
    }])

    .directive('tabHeadingTransclude', [function () {
        return {
            restrict: 'A',
            require: '^tab',
            link: function (scope, elm, attrs, tabCtrl) {
                scope.$watch('headingElement', function updateHeadingElement(heading) {
                    if (heading) {
                        elm.html('');
                        elm.append(heading);
                    }
                });
            }
        };
    }])

    .directive('tabContentTransclude', function () {
        return {
            restrict: 'A',
            require: '^tabset',
            link: function (scope, elm, attrs) {
                var tab = scope.$eval(attrs.tabContentTransclude);

                //Now our tab is ready to be transcluded: both the tab heading area
                //and the tab content area are loaded.  Transclude 'em both.
                tab.$transcludeFn(tab.$parent, function (contents) {
                    angular.forEach(contents, function (node) {
                        if (isTabHeading(node)) {
                            //Let tabHeadingTransclude know.
                            tab.headingElement = node;
                        } else {
                            elm.append(node);
                        }
                    });
                });
            }
        };
        function isTabHeading(node) {
            return node.tagName && (
                node.hasAttribute('tab-heading') ||
                node.hasAttribute('data-tab-heading') ||
                node.tagName.toLowerCase() === 'tab-heading' ||
                node.tagName.toLowerCase() === 'data-tab-heading'
            );
        }
    })

    ;

angular.module('ui.bootstrap.timepicker', [])

    .constant('timepickerConfig', {
        hourStep: 1,
        minuteStep: 1,
        showMeridian: true,
        meridians: null,
        readonlyInput: false,
        mousewheel: true
    })

    .controller('TimepickerController', ['$scope', '$attrs', '$parse', '$log', '$locale', 'timepickerConfig', function ($scope, $attrs, $parse, $log, $locale, timepickerConfig) {
        var selected = new Date(),
            ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl
            meridians = angular.isDefined($attrs.meridians) ? $scope.$parent.$eval($attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS;

        this.init = function (ngModelCtrl_, inputs) {
            ngModelCtrl = ngModelCtrl_;
            ngModelCtrl.$render = this.render;

            var hoursInputEl = inputs.eq(0),
                minutesInputEl = inputs.eq(1);

            var mousewheel = angular.isDefined($attrs.mousewheel) ? $scope.$parent.$eval($attrs.mousewheel) : timepickerConfig.mousewheel;
            if (mousewheel) {
                this.setupMousewheelEvents(hoursInputEl, minutesInputEl);
            }

            $scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput;
            this.setupInputEvents(hoursInputEl, minutesInputEl);
        };

        var hourStep = timepickerConfig.hourStep;
        if ($attrs.hourStep) {
            $scope.$parent.$watch($parse($attrs.hourStep), function (value) {
                hourStep = parseInt(value, 10);
            });
        }

        var minuteStep = timepickerConfig.minuteStep;
        if ($attrs.minuteStep) {
            $scope.$parent.$watch($parse($attrs.minuteStep), function (value) {
                minuteStep = parseInt(value, 10);
            });
        }

        // 12H / 24H mode
        $scope.showMeridian = timepickerConfig.showMeridian;
        if ($attrs.showMeridian) {
            $scope.$parent.$watch($parse($attrs.showMeridian), function (value) {
                $scope.showMeridian = !!value;

                if (ngModelCtrl.$error.time) {
                    // Evaluate from template
                    var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate();
                    if (angular.isDefined(hours) && angular.isDefined(minutes)) {
                        selected.setHours(hours);
                        refresh();
                    }
                } else {
                    updateTemplate();
                }
            });
        }

        // Get $scope.hours in 24H mode if valid
        function getHoursFromTemplate() {
            var hours = parseInt($scope.hours, 10);
            var valid = ($scope.showMeridian) ? (hours > 0 && hours < 13) : (hours >= 0 && hours < 24);
            if (!valid) {
                return undefined;
            }

            if ($scope.showMeridian) {
                if (hours === 12) {
                    hours = 0;
                }
                if ($scope.meridian === meridians[1]) {
                    hours = hours + 12;
                }
            }
            return hours;
        }

        function getMinutesFromTemplate() {
            var minutes = parseInt($scope.minutes, 10);
            return (minutes >= 0 && minutes < 60) ? minutes : undefined;
        }

        function pad(value) {
            return (angular.isDefined(value) && value.toString().length < 2) ? '0' + value : value;
        }

        // Respond on mousewheel spin
        this.setupMousewheelEvents = function (hoursInputEl, minutesInputEl) {
            var isScrollingUp = function (e) {
                if (e.originalEvent) {
                    e = e.originalEvent;
                }
                //pick correct delta variable depending on event
                var delta = (e.wheelDelta) ? e.wheelDelta : -e.deltaY;
                return (e.detail || delta > 0);
            };

            hoursInputEl.bind('mousewheel wheel', function (e) {
                $scope.$apply((isScrollingUp(e)) ? $scope.incrementHours() : $scope.decrementHours());
                e.preventDefault();
            });

            minutesInputEl.bind('mousewheel wheel', function (e) {
                $scope.$apply((isScrollingUp(e)) ? $scope.incrementMinutes() : $scope.decrementMinutes());
                e.preventDefault();
            });

        };

        this.setupInputEvents = function (hoursInputEl, minutesInputEl) {
            if ($scope.readonlyInput) {
                $scope.updateHours = angular.noop;
                $scope.updateMinutes = angular.noop;
                return;
            }

            var invalidate = function (invalidHours, invalidMinutes) {
                ngModelCtrl.$setViewValue(null);
                ngModelCtrl.$setValidity('time', false);
                if (angular.isDefined(invalidHours)) {
                    $scope.invalidHours = invalidHours;
                }
                if (angular.isDefined(invalidMinutes)) {
                    $scope.invalidMinutes = invalidMinutes;
                }
            };

            $scope.updateHours = function () {
                var hours = getHoursFromTemplate();

                if (angular.isDefined(hours)) {
                    selected.setHours(hours);
                    refresh('h');
                } else {
                    invalidate(true);
                }
            };

            hoursInputEl.bind('blur', function (e) {
                if (!$scope.invalidHours && $scope.hours < 10) {
                    $scope.$apply(function () {
                        $scope.hours = pad($scope.hours);
                    });
                }
            });

            $scope.updateMinutes = function () {
                var minutes = getMinutesFromTemplate();

                if (angular.isDefined(minutes)) {
                    selected.setMinutes(minutes);
                    refresh('m');
                } else {
                    invalidate(undefined, true);
                }
            };

            minutesInputEl.bind('blur', function (e) {
                if (!$scope.invalidMinutes && $scope.minutes < 10) {
                    $scope.$apply(function () {
                        $scope.minutes = pad($scope.minutes);
                    });
                }
            });

        };

        this.render = function () {
            var date = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : null;

            if (isNaN(date)) {
                ngModelCtrl.$setValidity('time', false);
                $log.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.');
            } else {
                if (date) {
                    selected = date;
                }
                makeValid();
                updateTemplate();
            }
        };

        // Call internally when we know that model is valid.
        function refresh(keyboardChange) {
            makeValid();
            ngModelCtrl.$setViewValue(new Date(selected));
            updateTemplate(keyboardChange);
        }

        function makeValid() {
            ngModelCtrl.$setValidity('time', true);
            $scope.invalidHours = false;
            $scope.invalidMinutes = false;
        }

        function updateTemplate(keyboardChange) {
            var hours = selected.getHours(), minutes = selected.getMinutes();

            if ($scope.showMeridian) {
                hours = (hours === 0 || hours === 12) ? 12 : hours % 12; // Convert 24 to 12 hour system
            }

            $scope.hours = keyboardChange === 'h' ? hours : pad(hours);
            $scope.minutes = keyboardChange === 'm' ? minutes : pad(minutes);
            $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
        }

        function addMinutes(minutes) {
            var dt = new Date(selected.getTime() + minutes * 60000);
            selected.setHours(dt.getHours(), dt.getMinutes());
            refresh();
        }

        $scope.incrementHours = function () {
            addMinutes(hourStep * 60);
        };
        $scope.decrementHours = function () {
            addMinutes(-hourStep * 60);
        };
        $scope.incrementMinutes = function () {
            addMinutes(minuteStep);
        };
        $scope.decrementMinutes = function () {
            addMinutes(-minuteStep);
        };
        $scope.toggleMeridian = function () {
            addMinutes(12 * 60 * ((selected.getHours() < 12) ? 1 : -1));
        };
    }])

    .directive('timepicker', function () {
        return {
            restrict: 'EA',
            require: ['timepicker', '?^ngModel'],
            controller: 'TimepickerController',
            replace: true,
            scope: {},
            templateUrl: 'template/timepicker/timepicker.html',
            link: function (scope, element, attrs, ctrls) {
                var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];

                if (ngModelCtrl) {
                    timepickerCtrl.init(ngModelCtrl, element.find('input'));
                }
            }
        };
    });

angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap.bindHtml'])

    /**
     * A helper service that can parse typeahead's syntax (string provided by users)
     * Extracted to a separate service for ease of unit testing
     */
    .factory('typeaheadParser', ['$parse', function ($parse) {

        //                      00000111000000000000022200000000000000003333333333333330000000000044000
        var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;

        return {
            parse: function (input) {

                var match = input.match(TYPEAHEAD_REGEXP);
                if (!match) {
                    throw new Error(
                        'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' +
                        ' but got "' + input + '".');
                }

                return {
                    itemName: match[3],
                    source: $parse(match[4]),
                    viewMapper: $parse(match[2] || match[1]),
                    modelMapper: $parse(match[1])
                };
            }
        };
    }])

    .directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$position', 'typeaheadParser',
        function ($compile, $parse, $q, $timeout, $document, $position, typeaheadParser) {

            var HOT_KEYS = [9, 13, 27, 38, 40];

            return {
                require: 'ngModel',
                link: function (originalScope, element, attrs, modelCtrl) {

                    originalScope.isDropdownTitle = typeof attrs.typeaheadTitle !== "undefined";
                    originalScope.dropdownTitle = attrs.typeaheadTitle;

                    //SUPPORTED ATTRIBUTES (OPTIONS)

                    //minimal no of characters that needs to be entered before typeahead kicks-in
                    var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1;

                    //minimal wait time after last character typed before typehead kicks-in
                    var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;

                    //should it restrict model values to the ones selected from the popup only?
                    var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false;

                    //binding to a variable that indicates if matches are being retrieved asynchronously
                    var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop;

                    //a callback executed when a match is selected
                    var onSelectCallback = $parse(attrs.typeaheadOnSelect);

                    var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;

                    var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false;

                    var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false;

                    //INTERNAL VARIABLES

                    //model setter executed upon match selection
                    var $setModelValue = $parse(attrs.ngModel).assign;

                    //expressions used by typeahead
                    var parserResult = typeaheadParser.parse(attrs.typeahead);

                    var hasFocus;

                    //create a child scope for the typeahead directive so we are not polluting original scope
                    //with typeahead-specific data (matches, query etc.)
                    var scope = originalScope.$new();
                    scope.inputValue = $setModelValue;
                    scope.isLoading = false;
                    originalScope.$on('$destroy', function () {
                        scope.$destroy();
                    });

                    // WAI-ARIA
                    var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000);
                    element.attr({
                        'aria-autocomplete': 'list',
                        'aria-expanded': false,
                        'aria-owns': popupId
                    });

                    //pop-up element used to display matches
                    var popUpEl = angular.element('<div typeahead-popup></div>');
                    popUpEl.attr({
                        id: popupId,
                        matches: 'matches',
                        active: 'activeIdx',
                        select: 'select(activeIdx)',
                        query: 'query',
                        position: 'position'
                    });
                    //custom item template
                    if (angular.isDefined(attrs.typeaheadTemplateUrl)) {
                        popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);
                    }

                    var resetMatches = function () {
                        scope.matches = [];
                        scope.activeIdx = -1;
                        element.attr('aria-expanded', false);
                    };

                    var getMatchId = function (index) {
                        return popupId + '-option-' + index;
                    };

                    // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead.
                    // This attribute is added or removed automatically when the `activeIdx` changes.
                    scope.$watch('activeIdx', function (index) {
                        if (index < 0) {
                            element.removeAttr('aria-activedescendant');
                        } else {
                            element.attr('aria-activedescendant', getMatchId(index));
                        }
                    });

                    var getMatchesAsync = function (inputValue) {
                        scope.queryTemp = inputValue;
                        var locals = { $viewValue: inputValue };
                        isLoadingSetter(originalScope, true);
                        scope.isLoading = true;
                        $q.when(parserResult.source(originalScope, locals)).then(function (matches) {

                            //it might happen that several async queries were in progress if a user were typing fast
                            //but we are interested only in responses that correspond to the current view value
                            var onCurrentRequest = (inputValue === modelCtrl.$viewValue);
                            if (onCurrentRequest && hasFocus) {
                                if (matches.length > 0) {

                                    scope.activeIdx = focusFirst ? 0 : -1;
                                    scope.matches.length = 0;

                                    //transform labels
                                    for (var i = 0; i < matches.length; i++) {
                                        locals[parserResult.itemName] = matches[i];
                                        scope.matches.push({
                                            id: getMatchId(i),
                                            label: parserResult.viewMapper(scope, locals),
                                            model: matches[i]
                                        });
                                    }

                                    scope.query = inputValue;
                                    //position pop-up with matches - we need to re-calculate its position each time we are opening a window
                                    //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page
                                    //due to other elements being rendered
                                    scope.position = appendToBody ? $position.offset(element) : $position.position(element);
                                    scope.position.top = scope.position.top + element.prop('offsetHeight');

                                    element.attr('aria-expanded', true);
                                } else {
                                    resetMatches();
                                }
                            }
                            if (onCurrentRequest) {
                                isLoadingSetter(originalScope, false);
                                scope.isLoading = false;
                            }
                        }, function () {
                            resetMatches();
                            isLoadingSetter(originalScope, false);
                            scope.isLoading = false;
                        });
                    };

                    resetMatches();

                    //we need to propagate user's query so we can higlight matches
                    scope.query = undefined;
                    scope.queryTemp = undefined;

                    //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later 
                    var timeoutPromise;

                    var scheduleSearchWithTimeout = function (inputValue) {
                        timeoutPromise = $timeout(function () {
                            getMatchesAsync(inputValue);
                        }, waitTime);
                    };

                    var cancelPreviousTimeout = function () {
                        if (timeoutPromise) {
                            $timeout.cancel(timeoutPromise);
                        }
                    };

                    //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
                    //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
                    modelCtrl.$parsers.unshift(function (inputValue) {

                        hasFocus = true;

                        if (inputValue && inputValue.length >= minSearch) {
                            if (waitTime > 0) {
                                cancelPreviousTimeout();
                                scheduleSearchWithTimeout(inputValue);
                            } else {
                                getMatchesAsync(inputValue);
                            }
                        } else {
                            isLoadingSetter(originalScope, false);
                            scope.isLoading = false;
                            cancelPreviousTimeout();
                            resetMatches();
                        }

                        if (isEditable) {
                            return inputValue;
                        } else {
                            if (!inputValue) {
                                // Reset in case user had typed something previously.
                                modelCtrl.$setValidity('editable', true);
                                return inputValue;
                            } else {
                                modelCtrl.$setValidity('editable', false);
                                return undefined;
                            }
                        }
                    });

                    modelCtrl.$formatters.push(function (modelValue) {

                        var candidateViewValue, emptyViewValue;
                        var locals = {};

                        if (inputFormatter) {

                            locals.$model = modelValue;
                            return inputFormatter(originalScope, locals);

                        } else {

                            //it might happen that we don't have enough info to properly render input value
                            //we need to check for this situation and simply return model value if we can't apply custom formatting
                            locals[parserResult.itemName] = modelValue;
                            candidateViewValue = parserResult.viewMapper(originalScope, locals);
                            locals[parserResult.itemName] = undefined;
                            emptyViewValue = parserResult.viewMapper(originalScope, locals);

                            return candidateViewValue !== emptyViewValue ? candidateViewValue : modelValue;
                        }
                    });

                    scope.select = function (activeIdx) {
                        //called from within the $digest() cycle
                        var locals = {};
                        var model, item;

                        locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
                        model = parserResult.modelMapper(originalScope, locals);
                        $setModelValue(originalScope, model);
                        modelCtrl.$setValidity('editable', true);
                        scope.queryTemp = undefined;

                        onSelectCallback(originalScope, {
                            $item: item,
                            $model: model,
                            $label: parserResult.viewMapper(originalScope, locals)
                        });

                        resetMatches();

                        //return focus to the input element if a match was selected via a mouse click event
                        // use timeout to avoid $rootScope:inprog error
                        $timeout(function () { element[0].focus(); }, 0, false);
                    };

                    //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
                    element.bind('keydown', function (evt) {

                        //typeahead is open and an "interesting" key was pressed
                        if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
                            return;
                        }

                        // if there's nothing selected (i.e. focusFirst) and enter is hit, don't do anything
                        if (scope.activeIdx == -1 && (evt.which === 13 || evt.which === 9)) {
                            return;
                        }

                        evt.preventDefault();

                        if (evt.which === 40) {
                            scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
                            scope.$digest();

                        } else if (evt.which === 38) {
                            scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 2;
                            scope.$digest();

                        } else if (evt.which === 13 || evt.which === 9) {
                            scope.$apply(function () {
                                scope.select(scope.activeIdx);
                            });

                        } else if (evt.which === 27) {
                            evt.stopPropagation();

                            resetMatches();
                            scope.$digest();
                        }
                    });

                    element.bind('blur', function (evt) {
                        hasFocus = false;
                    });

                    // Keep reference to click handler to unbind it.
                    var dismissClickHandler = function (evt) {
                        if (element[0] !== evt.target) {
                            resetMatches();
                            scope.$digest();
                        }
                    };

                    $document.bind('click', dismissClickHandler);

                    originalScope.$on('$destroy', function () {
                        $document.unbind('click', dismissClickHandler);
                        if (appendToBody) {
                            $popup.remove();
                        }
                    });

                    var $popup = $compile(popUpEl)(scope);
                    if (appendToBody) {
                        $document.find('body').append($popup);
                    } else {
                        element.after($popup);
                    }
                }
            };

        }])

    .directive('typeaheadPopup', function () {
        return {
            restrict: 'EA',
            scope: {
                matches: '=',
                query: '=',
                active: '=',
                position: '=',
                select: '&'
            },
            replace: true,
            templateUrl: 'template/typeahead/typeahead-popup.html',
            link: function (scope, element, attrs) {

                scope.templateUrl = attrs.templateUrl;

                scope.isOpen = function () {
                    return scope.matches.length > 0 || (typeof scope.$parent.queryTemp !== 'undefined' && scope.$parent.queryTemp.length > 1);
                };

                scope.isNoMatch = function () {
                    return scope.matches.length == 0 && !scope.$parent.isLoading && (typeof scope.$parent.queryTemp !== 'undefined' && scope.$parent.queryTemp.length > 1);
                };

                scope.isActive = function (matchIdx) {
                    return scope.active == matchIdx;
                };

                scope.selectActive = function (matchIdx) {
                    scope.active = matchIdx;
                };

                scope.selectMatch = function (activeIdx) {
                    scope.select({ activeIdx: activeIdx });
                };
            }
        };
    })

    //.directive('typeaheadMatch', ['$http', '$templateCache', '$compile', '$parse', function ($http, $templateCache, $compile, $parse) {
    //    return {
    //        restrict: 'EA',
    //        scope: {
    //            index: '=',
    //            match: '=',
    //            query: '='
    //        },
    //        link: function (scope, element, attrs) {
    //            var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html';
    //            $http.get(tplUrl, { cache: $templateCache }).then(function (tplContent) {
    //                element.replaceWith($compile(tplContent.trim())(scope));
    //            });
    //        }
    //    };
    //}])
    .directive('typeaheadMatch', ['$templateRequest', '$compile', '$parse', function ($templateRequest, $compile, $parse) {
        return {
            scope: {
                index: '=',
                match: '=',
                query: '='
            },
            link: function (scope, element, attrs) {
                var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html';
                $templateRequest(tplUrl).then(function (tplContent) {
                    var tplEl = angular.element(tplContent.trim());
                    element.replaceWith(tplEl);
                    $compile(tplEl)(scope);
                });
            }
        };
    }])

    .filter('typeaheadHighlight', function () {

        function escapeRegexp(queryToEscape) {
            return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
        }

        return function (matchItem, query) {
            return query ? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '<strong>$&</strong>') : matchItem;
        };
    });

angular.module("template/accordion/accordion-group.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("template/accordion/accordion-group.html",
        "<div class=\"panel panel-default\">\n" +
        "  <div class=\"panel-heading\">\n" +
        "    <h4 class=\"panel-title\">\n" +
        "      <a href class=\"accordion-toggle\" ng-click=\"toggleOpen()\" accordion-transclude=\"heading\"><span ng-class=\"{'text-muted': isDisabled}\">{{heading}}</span></a>\n" +
        "    </h4>\n" +
        "  </div>\n" +
        "  <div class=\"panel-collapse\" collapse=\"!isOpen\">\n" +
        "	  <div class=\"panel-body\" ng-transclude></div>\n" +
        "  </div>\n" +
        "</div>\n" +
        "");
}]);

angular.module("template/accordion/accordion.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("template/accordion/accordion.html",
        "<div class=\"panel-group\" ng-transclude></div>");
}]);

angular.module("template/alert/alert.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("template/alert/alert.html",
        "<div class=\"alert\" ng-class=\"['alert-' + (type || 'warning'), closeable ? 'alert-dismissable' : null]\" role=\"alert\">\n" +
        "    <button ng-show=\"closeable\" type=\"button\" class=\"close\" ng-click=\"close()\">\n" +
        "        <span aria-hidden=\"true\">&times;</span>\n" +
        "        <span class=\"sr-only\">Close</span>\n" +
        "    </button>\n" +
        "    <div ng-transclude></div>\n" +
        "</div>\n" +
        "");
}]);

angular.module("template/carousel/carousel.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("template/carousel/carousel.html",
        "<div ng-mouseenter=\"pause()\" ng-mouseleave=\"play()\" class=\"carousel\" ng-swipe-right=\"prev()\" ng-swipe-left=\"next()\">\n" +
        "    <ol class=\"carousel-indicators\" ng-show=\"slides.length > 1\">\n" +
        "        <li ng-repeat=\"slide in slides track by $index\" ng-class=\"{active: isActive(slide)}\" ng-click=\"select(slide)\"></li>\n" +
        "    </ol>\n" +
        "    <div class=\"carousel-inner\" ng-transclude></div>\n" +
        "    <a class=\"left carousel-control\" ng-click=\"prev()\" ng-show=\"slides.length > 1\"><span class=\"glyphicon glyphicon-chevron-left\"></span></a>\n" +
        "    <a class=\"right carousel-control\" ng-click=\"next()\" ng-show=\"slides.length > 1\"><span class=\"glyphicon glyphicon-chevron-right\"></span></a>\n" +
        "</div>\n" +
        "");
}]);

angular.module("template/carousel/slide.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("template/carousel/slide.html",
        "<div ng-class=\"{\n" +
        "    'active': leaving || (active && !entering),\n" +
        "    'prev': (next || active) && direction=='prev',\n" +
        "    'next': (next || active) && direction=='next',\n" +
        "    'right': direction=='prev',\n" +
        "    'left': direction=='next'\n" +
        "  }\" class=\"item text-center\" ng-transclude></div>\n" +
        "");
}]);

angular.module("template/datepicker/datepicker.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("template/datepicker/datepicker.html",
        "<div ng-switch=\"datepickerMode\" role=\"application\" ng-keydown=\"keydown($event)\">\n" +
        "  <daypicker ng-switch-when=\"day\" tabindex=\"0\"></daypicker>\n" +
        "  <monthpicker ng-switch-when=\"month\" tabindex=\"0\"></monthpicker>\n" +
        "  <yearpicker ng-switch-when=\"year\" tabindex=\"0\"></yearpicker>\n" +
        "</div>");
}]);

angular.module("template/datepicker/day.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("template/datepicker/day.html",
        "<table role=\"grid\" aria-labelledby=\"{{uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
        "  <thead>\n" +
        "    <tr>\n" +
        "      <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-left\"></i></button></th>\n" +
        "      <th colspan=\"{{5 + showWeeks}}\"><button id=\"{{uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm\" ng-click=\"toggleMode()\" tabindex=\"-1\" style=\"width:100%;\"><strong>{{title}}</strong></button></th>\n" +
        "      <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-right\"></i></button></th>\n" +
        "    </tr>\n" +
        "    <tr>\n" +
        "      <th ng-show=\"showWeeks\" class=\"text-center\"></th>\n" +
        "      <th ng-repeat=\"label in labels track by $index\" class=\"text-center\"><small aria-label=\"{{label.full}}\">{{label.abbr}}</small></th>\n" +
        "    </tr>\n" +
        "  </thead>\n" +
        "  <tbody>\n" +
        "    <tr ng-repeat=\"row in rows track by $index\">\n" +
        "      <td ng-show=\"showWeeks\" class=\"text-center h6\"><em>{{ weekNumbers[$index] }}</em></td>\n" +
        "      <td ng-repeat=\"dt in row track by dt.date\" class=\"text-center\" role=\"gridcell\" id=\"{{dt.uid}}\" aria-disabled=\"{{!!dt.disabled}}\">\n" +
        "        <button type=\"button\" style=\"width:100%;\" class=\"btn btn-default btn-sm\" ng-class=\"{'btn-info': dt.selected, active: isActive(dt)}\" ng-click=\"select(dt.date)\" ng-disabled=\"dt.disabled\" tabindex=\"-1\"><span ng-class=\"{'text-muted': dt.secondary, 'text-info': dt.current}\">{{dt.label}}</span></button>\n" +
        "      </td>\n" +
        "    </tr>\n" +
        "  </tbody>\n" +
        "</table>\n" +
        "");
}]);

angular.module("template/datepicker/month.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("template/datepicker/month.html",
        "<table role=\"grid\" aria-labelledby=\"{{uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
        "  <thead>\n" +
        "    <tr>\n" +
        "      <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-left\"></i></button></th>\n" +
        "      <th><button id=\"{{uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm\" ng-click=\"toggleMode()\" tabindex=\"-1\" style=\"width:100%;\"><strong>{{title}}</strong></button></th>\n" +
        "      <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-right\"></i></button></th>\n" +
        "    </tr>\n" +
        "  </thead>\n" +
        "  <tbody>\n" +
        "    <tr ng-repeat=\"row in rows track by $index\">\n" +
        "      <td ng-repeat=\"dt in row track by dt.date\" class=\"text-center\" role=\"gridcell\" id=\"{{dt.uid}}\" aria-disabled=\"{{!!dt.disabled}}\">\n" +
        "        <button type=\"button\" style=\"width:100%;\" class=\"btn btn-default\" ng-class=\"{'btn-info': dt.selected, active: isActive(dt)}\" ng-click=\"select(dt.date)\" ng-disabled=\"dt.disabled\" tabindex=\"-1\"><span ng-class=\"{'text-info': dt.current}\">{{dt.label}}</span></button>\n" +
        "      </td>\n" +
        "    </tr>\n" +
        "  </tbody>\n" +
        "</table>\n" +
        "");
}]);

angular.module("template/datepicker/popup.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("template/datepicker/popup.html",
        "<ul class=\"dropdown-menu bootstrap-date-picker\" ng-style=\"{display: (isOpen && 'block') || 'none', top: position.top+'px', left: position.left+'px'}\" ng-keydown=\"keydown($event)\">\n" +
        "	<li ng-transclude></li>\n" +
        "	<li ng-if=\"showButtonBar\" style=\"padding:10px 9px 2px\">\n" +
        "		<span class=\"btn-group pull-left\">\n" +
        "			<button type=\"button\" class=\"btn btn-sm btn-info\" ng-click=\"select('today')\">{{ getText('current') }}</button>\n" +
        "			<button type=\"button\" class=\"btn btn-sm btn-danger\" ng-click=\"select(null)\">{{ getText('clear') }}</button>\n" +
        "		</span>\n" +
        "		<button type=\"button\" class=\"btn btn-sm btn-success pull-right\" ng-click=\"close()\">{{ getText('close') }}</button>\n" +
        "	</li>\n" +
        "</ul>\n" +
        "");
}]);

angular.module("template/datepicker/year.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("template/datepicker/year.html",
        "<table role=\"grid\" aria-labelledby=\"{{uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
        "  <thead>\n" +
        "    <tr>\n" +
        "      <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-left\"></i></button></th>\n" +
        "      <th colspan=\"3\"><button id=\"{{uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm\" ng-click=\"toggleMode()\" tabindex=\"-1\" style=\"width:100%;\"><strong>{{title}}</strong></button></th>\n" +
        "      <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-right\"></i></button></th>\n" +
        "    </tr>\n" +
        "  </thead>\n" +
        "  <tbody>\n" +
        "    <tr ng-repeat=\"row in rows track by $index\">\n" +
        "      <td ng-repeat=\"dt in row track by dt.date\" class=\"text-center\" role=\"gridcell\" id=\"{{dt.uid}}\" aria-disabled=\"{{!!dt.disabled}}\">\n" +
        "        <button type=\"button\" style=\"width:100%;\" class=\"btn btn-default\" ng-class=\"{'btn-info': dt.selected, active: isActive(dt)}\" ng-click=\"select(dt.date)\" ng-disabled=\"dt.disabled\" tabindex=\"-1\"><span ng-class=\"{'text-info': dt.current}\">{{dt.label}}</span></button>\n" +
        "      </td>\n" +
        "    </tr>\n" +
        "  </tbody>\n" +
        "</table>\n" +
        "");
}]);

angular.module("template/modal/backdrop.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("template/modal/backdrop.html",
        "<div class=\"modal-backdrop fade {{ backdropClass }}\"\n" +
        "     ng-class=\"{in: animate}\"\n" +
        "     ng-style=\"{'z-index': 1040 + (index && 1 || 0) + index*10}\"\n" +
        "></div>\n" +
        "");
}]);

angular.module("template/modal/window.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("template/modal/window.html",
        "<div tabindex=\"-1\" role=\"dialog\" class=\"modal fade\" ng-class=\"{in: animate}\" ng-style=\"{'z-index': 1050 + index*10, display: 'block'}\" ng-click=\"close($event)\">\n" +
        "    <div class=\"modal-dialog\" ng-class=\"{'modal-sm': size == 'sm', 'modal-lg': size == 'lg' , 'modal-huge': size == 'huge'}\"><div class=\"modal-content\" modal-transclude></div></div>\n" +
        "</div>");
}]);

angular.module("template/pagination/pager.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("template/pagination/pager.html",
        "<ul class=\"pager\">\n" +
        "  <li ng-class=\"{disabled: noPrevious(), previous: align}\"><a href ng-click=\"selectPage(page - 1)\">{{getText('previous')}}</a></li>\n" +
        "  <li ng-class=\"{disabled: noNext(), next: align}\"><a href ng-click=\"selectPage(page + 1)\">{{getText('next')}}</a></li>\n" +
        "</ul>");
}]);

angular.module("template/pagination/pagination.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("template/pagination/pagination.html",
        "<ul class=\"pagination\">\n" +
        "  <li ng-if=\"boundaryLinks\" ng-class=\"{disabled: noPrevious()}\"><a href ng-click=\"selectPage(1)\">{{getText('first')}}</a></li>\n" +
        "  <li ng-if=\"directionLinks\" ng-class=\"{disabled: noPrevious()}\"><a href ng-click=\"selectPage(page - 1)\">{{getText('previous')}}</a></li>\n" +
        "  <li ng-repeat=\"page in pages track by $index\" ng-class=\"{active: page.active}\"><a href ng-click=\"selectPage(page.number)\">{{page.text}}</a></li>\n" +
        "  <li ng-if=\"directionLinks\" ng-class=\"{disabled: noNext()}\"><a href ng-click=\"selectPage(page + 1)\">{{getText('next')}}</a></li>\n" +
        "  <li ng-if=\"boundaryLinks\" ng-class=\"{disabled: noNext()}\"><a href ng-click=\"selectPage(totalPages)\">{{getText('last')}}</a></li>\n" +
        "</ul>");
}]);

angular.module("template/tooltip/tooltip-html-unsafe-popup.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("template/tooltip/tooltip-html-unsafe-popup.html",
        "<div class=\"tooltip {{placement}}\" ng-class=\"{ in: isOpen(), fade: animation() }\">\n" +
        "  <div class=\"tooltip-arrow\"></div>\n" +
        "  <div class=\"tooltip-inner\" bind-html-unsafe=\"content\"></div>\n" +
        "</div>\n" +
        "");
}]);

angular.module("template/tooltip/tooltip-popup.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("template/tooltip/tooltip-popup.html",
        "<div class=\"tooltip {{placement}}\" ng-class=\"{ in: isOpen(), fade: animation() }\">\n" +
        "  <div class=\"tooltip-arrow\"></div>\n" +
        "  <div class=\"tooltip-inner\" ng-bind=\"content\"></div>\n" +
        "</div>\n" +
        "");
}]);

angular.module("template/popover/popover.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("template/popover/popover.html",
        "<div class=\"popover {{placement}}\" ng-class=\"{ in: isOpen(), fade: animation() }\">\n" +
        "  <div class=\"arrow\"></div>\n" +
        "\n" +
        "  <div class=\"popover-inner\">\n" +
        "      <h3 class=\"popover-title\" ng-bind=\"title\" ng-show=\"title\"></h3>\n" +
        "      <div class=\"popover-content\" ng-bind-html=\"content\"></div>\n" +
        "  </div>\n" +
        "</div>\n" +
        "");
}]);

angular.module("template/progressbar/bar.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("template/progressbar/bar.html",
        "<div class=\"progress-bar\" ng-class=\"type && 'progress-bar-' + type\" role=\"progressbar\" aria-valuenow=\"{{value}}\" aria-valuemin=\"0\" aria-valuemax=\"{{max}}\" ng-style=\"{width: percent + '%'}\" aria-valuetext=\"{{percent | number:0}}%\" ng-transclude></div>");
}]);

angular.module("template/progressbar/progress.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("template/progressbar/progress.html",
        "<div class=\"progress\" ng-transclude></div>");
}]);

angular.module("template/progressbar/progressbar.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("template/progressbar/progressbar.html",
        "<div class=\"progress\">\n" +
        "  <div class=\"progress-bar\" ng-class=\"type && 'progress-bar-' + type\" role=\"progressbar\" aria-valuenow=\"{{value}}\" aria-valuemin=\"0\" aria-valuemax=\"{{max}}\" ng-style=\"{width: percent + '%'}\" aria-valuetext=\"{{percent | number:0}}%\" ng-transclude></div>\n" +
        "</div>");
}]);

angular.module("template/rating/rating.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("template/rating/rating.html",
        "<span ng-mouseleave=\"reset()\" ng-keydown=\"onKeydown($event)\" tabindex=\"0\" role=\"slider\" aria-valuemin=\"0\" aria-valuemax=\"{{range.length}}\" aria-valuenow=\"{{value}}\">\n" +
        "    <i ng-repeat=\"r in range track by $index\" ng-mouseenter=\"enter($index + 1)\" ng-click=\"rate($index + 1)\" class=\"glyphicon\" ng-class=\"$index < value && (r.stateOn || 'glyphicon-star') || (r.stateOff || 'glyphicon-star-empty')\">\n" +
        "        <span class=\"sr-only\">({{ $index < value ? '*' : ' ' }})</span>\n" +
        "    </i>\n" +
        "</span>");
}]);

angular.module("template/tabs/tab.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("template/tabs/tab.html",
        "<li ng-class=\"{active: active, disabled: disabled}\">\n" +
        "  <a href ng-click=\"select()\" tab-heading-transclude>{{heading}}</a>\n" +
        "</li>\n" +
        "");
}]);

angular.module("template/tabs/tabset.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("template/tabs/tabset.html",
        "<div>\n" +
        "  <ul class=\"nav nav-{{type || 'tabs'}}\" ng-class=\"{'nav-stacked': vertical, 'nav-justified': justified}\" ng-transclude></ul>\n" +
        "  <div class=\"tab-content\">\n" +
        "    <div class=\"tab-pane\" \n" +
        "         ng-repeat=\"tab in tabs\" \n" +
        "         ng-class=\"{active: tab.active}\"\n" +
        "         tab-content-transclude=\"tab\">\n" +
        "    </div>\n" +
        "  </div>\n" +
        "</div>\n" +
        "");
}]);

angular.module("template/timepicker/timepicker.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("template/timepicker/timepicker.html",
        "<table>\n" +
        "	<tbody>\n" +
        "		<tr class=\"text-center\">\n" +
        "			<td><a ng-click=\"incrementHours()\" class=\"btn btn-link\"><span class=\"glyphicon glyphicon-chevron-up\"></span></a></td>\n" +
        "			<td>&nbsp;</td>\n" +
        "			<td><a ng-click=\"incrementMinutes()\" class=\"btn btn-link\"><span class=\"glyphicon glyphicon-chevron-up\"></span></a></td>\n" +
        "			<td ng-show=\"showMeridian\"></td>\n" +
        "		</tr>\n" +
        "		<tr>\n" +
        "			<td style=\"width:50px;\" class=\"form-group\" ng-class=\"{'has-error': invalidHours}\">\n" +
        "				<input type=\"text\" ng-model=\"hours\" ng-change=\"updateHours()\" class=\"form-control text-center\" ng-mousewheel=\"incrementHours()\" ng-readonly=\"readonlyInput\" maxlength=\"2\">\n" +
        "			</td>\n" +
        "			<td>:</td>\n" +
        "			<td style=\"width:50px;\" class=\"form-group\" ng-class=\"{'has-error': invalidMinutes}\">\n" +
        "				<input type=\"text\" ng-model=\"minutes\" ng-change=\"updateMinutes()\" class=\"form-control text-center\" ng-readonly=\"readonlyInput\" maxlength=\"2\">\n" +
        "			</td>\n" +
        "			<td ng-show=\"showMeridian\"><button type=\"button\" class=\"btn btn-default text-center\" ng-click=\"toggleMeridian()\">{{meridian}}</button></td>\n" +
        "		</tr>\n" +
        "		<tr class=\"text-center\">\n" +
        "			<td><a ng-click=\"decrementHours()\" class=\"btn btn-link\"><span class=\"glyphicon glyphicon-chevron-down\"></span></a></td>\n" +
        "			<td>&nbsp;</td>\n" +
        "			<td><a ng-click=\"decrementMinutes()\" class=\"btn btn-link\"><span class=\"glyphicon glyphicon-chevron-down\"></span></a></td>\n" +
        "			<td ng-show=\"showMeridian\"></td>\n" +
        "		</tr>\n" +
        "	</tbody>\n" +
        "</table>\n" +
        "");
}]);

angular.module("template/typeahead/typeahead-match.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("template/typeahead/typeahead-match.html",
        "<a tabindex=\"-1\" bind-html-unsafe=\"match.label | typeaheadHighlight:query\"></a>");
}]);

angular.module("template/typeahead/typeahead-popup.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("template/typeahead/typeahead-popup.html",
        "<ul class=\"dropdown-menu\" ng-show=\"isOpen()\" ng-style=\"{top: position.top+'px', left: position.left+'px'}\" style=\"display: block;\" role=\"listbox\" aria-hidden=\"{{!isOpen()}}\">\n" +
        "    <li ng-show=\"isNoMatch()\" class=\"dropdown-header\">No Results Found</li>\n" +
        "    <li ng-show=\"$parent.isDropdownTitle\" class=\"dropdown-header\">{{$parent.dropdownTitle}}</li>\n" +
        "    <li ng-repeat=\"match in matches track by $index\" ng-class=\"{active: isActive($index) }\" ng-mouseenter=\"selectActive($index)\" ng-click=\"selectMatch($index)\" role=\"option\" id=\"{{match.id}}\">\n" +
        "        <div typeahead-match index=\"$index\" match=\"match\" query=\"query\" template-url=\"templateUrl\"></div>\n" +
        "    </li>\n" +
        "</ul>\n" +
        "");
}]);

/**
 * State-based routing for AngularJS
 * @version v0.4.2
 * @link http://angular-ui.github.com/
 * @license MIT License, http://www.opensource.org/licenses/MIT
 */

/* commonjs package manager support (eg componentjs) */
if (typeof module !== "undefined" && typeof exports !== "undefined" && module.exports === exports) {
    module.exports = 'ui.router';
}

(function (window, angular, undefined) {
    /*jshint globalstrict:true*/
    /*global angular:false*/
    'use strict';

    var isDefined = angular.isDefined,
        isFunction = angular.isFunction,
        isString = angular.isString,
        isObject = angular.isObject,
        isArray = angular.isArray,
        forEach = angular.forEach,
        extend = angular.extend,
        copy = angular.copy,
        toJson = angular.toJson;

    function inherit(parent, extra) {
        return extend(new (extend(function () { }, { prototype: parent }))(), extra);
    }

    function merge(dst) {
        forEach(arguments, function (obj) {
            if (obj !== dst) {
                forEach(obj, function (value, key) {
                    if (!dst.hasOwnProperty(key)) dst[key] = value;
                });
            }
        });
        return dst;
    }

    /**
     * Finds the common ancestor path between two states.
     *
     * @param {Object} first The first state.
     * @param {Object} second The second state.
     * @return {Array} Returns an array of state names in descending order, not including the root.
     */
    function ancestors(first, second) {
        var path = [];

        for (var n in first.path) {
            if (first.path[n] !== second.path[n]) break;
            path.push(first.path[n]);
        }
        return path;
    }

    /**
     * IE8-safe wrapper for `Object.keys()`.
     *
     * @param {Object} object A JavaScript object.
     * @return {Array} Returns the keys of the object as an array.
     */
    function objectKeys(object) {
        if (Object.keys) {
            return Object.keys(object);
        }
        var result = [];

        forEach(object, function (val, key) {
            result.push(key);
        });
        return result;
    }

    /**
     * IE8-safe wrapper for `Array.prototype.indexOf()`.
     *
     * @param {Array} array A JavaScript array.
     * @param {*} value A value to search the array for.
     * @return {Number} Returns the array index value of `value`, or `-1` if not present.
     */
    function indexOf(array, value) {
        if (Array.prototype.indexOf) {
            return array.indexOf(value, Number(arguments[2]) || 0);
        }
        var len = array.length >>> 0, from = Number(arguments[2]) || 0;
        from = (from < 0) ? Math.ceil(from) : Math.floor(from);

        if (from < 0) from += len;

        for (; from < len; from++) {
            if (from in array && array[from] === value) return from;
        }
        return -1;
    }

    /**
     * Merges a set of parameters with all parameters inherited between the common parents of the
     * current state and a given destination state.
     *
     * @param {Object} currentParams The value of the current state parameters ($stateParams).
     * @param {Object} newParams The set of parameters which will be composited with inherited params.
     * @param {Object} $current Internal definition of object representing the current state.
     * @param {Object} $to Internal definition of object representing state to transition to.
     */
    function inheritParams(currentParams, newParams, $current, $to) {
        var parents = ancestors($current, $to), parentParams, inherited = {}, inheritList = [];

        for (var i in parents) {
            if (!parents[i] || !parents[i].params) continue;
            parentParams = objectKeys(parents[i].params);
            if (!parentParams.length) continue;

            for (var j in parentParams) {
                if (indexOf(inheritList, parentParams[j]) >= 0) continue;
                inheritList.push(parentParams[j]);
                inherited[parentParams[j]] = currentParams[parentParams[j]];
            }
        }
        return extend({}, inherited, newParams);
    }

    /**
     * Performs a non-strict comparison of the subset of two objects, defined by a list of keys.
     *
     * @param {Object} a The first object.
     * @param {Object} b The second object.
     * @param {Array} keys The list of keys within each object to compare. If the list is empty or not specified,
     *                     it defaults to the list of keys in `a`.
     * @return {Boolean} Returns `true` if the keys match, otherwise `false`.
     */
    function equalForKeys(a, b, keys) {
        if (!keys) {
            keys = [];
            for (var n in a) keys.push(n); // Used instead of Object.keys() for IE8 compatibility
        }

        for (var i = 0; i < keys.length; i++) {
            var k = keys[i];
            if (a[k] != b[k]) return false; // Not '===', values aren't necessarily normalized
        }
        return true;
    }

    /**
     * Returns the subset of an object, based on a list of keys.
     *
     * @param {Array} keys
     * @param {Object} values
     * @return {Boolean} Returns a subset of `values`.
     */
    function filterByKeys(keys, values) {
        var filtered = {};

        forEach(keys, function (name) {
            filtered[name] = values[name];
        });
        return filtered;
    }

    // like _.indexBy
    // when you know that your index values will be unique, or you want last-one-in to win
    function indexBy(array, propName) {
        var result = {};
        forEach(array, function (item) {
            result[item[propName]] = item;
        });
        return result;
    }

    // extracted from underscore.js
    // Return a copy of the object only containing the whitelisted properties.
    function pick(obj) {
        var copy = {};
        var keys = Array.prototype.concat.apply(Array.prototype, Array.prototype.slice.call(arguments, 1));
        forEach(keys, function (key) {
            if (key in obj) copy[key] = obj[key];
        });
        return copy;
    }

    // extracted from underscore.js
    // Return a copy of the object omitting the blacklisted properties.
    function omit(obj) {
        var copy = {};
        var keys = Array.prototype.concat.apply(Array.prototype, Array.prototype.slice.call(arguments, 1));
        for (var key in obj) {
            if (indexOf(keys, key) == -1) copy[key] = obj[key];
        }
        return copy;
    }

    function pluck(collection, key) {
        var result = isArray(collection) ? [] : {};

        forEach(collection, function (val, i) {
            result[i] = isFunction(key) ? key(val) : val[key];
        });
        return result;
    }

    function filter(collection, callback) {
        var array = isArray(collection);
        var result = array ? [] : {};
        forEach(collection, function (val, i) {
            if (callback(val, i)) {
                result[array ? result.length : i] = val;
            }
        });
        return result;
    }

    function map(collection, callback) {
        var result = isArray(collection) ? [] : {};

        forEach(collection, function (val, i) {
            result[i] = callback(val, i);
        });
        return result;
    }

    // issue #2676 #2889
    function silenceUncaughtInPromise(promise) {
        return promise.then(undefined, function () { }) && promise;
    }

    /**
     * @ngdoc overview
     * @name ui.router.util
     *
     * @description
     * # ui.router.util sub-module
     *
     * This module is a dependency of other sub-modules. Do not include this module as a dependency
     * in your angular app (use {@link ui.router} module instead).
     *
     */
    angular.module('ui.router.util', ['ng']);

    /**
     * @ngdoc overview
     * @name ui.router.router
     * 
     * @requires ui.router.util
     *
     * @description
     * # ui.router.router sub-module
     *
     * This module is a dependency of other sub-modules. Do not include this module as a dependency
     * in your angular app (use {@link ui.router} module instead).
     */
    angular.module('ui.router.router', ['ui.router.util']);

    /**
     * @ngdoc overview
     * @name ui.router.state
     * 
     * @requires ui.router.router
     * @requires ui.router.util
     *
     * @description
     * # ui.router.state sub-module
     *
     * This module is a dependency of the main ui.router module. Do not include this module as a dependency
     * in your angular app (use {@link ui.router} module instead).
     * 
     */
    angular.module('ui.router.state', ['ui.router.router', 'ui.router.util']);

    /**
     * @ngdoc overview
     * @name ui.router
     *
     * @requires ui.router.state
     *
     * @description
     * # ui.router
     * 
     * ## The main module for ui.router 
     * There are several sub-modules included with the ui.router module, however only this module is needed
     * as a dependency within your angular app. The other modules are for organization purposes. 
     *
     * The modules are:
     * * ui.router - the main "umbrella" module
     * * ui.router.router - 
     * 
     * *You'll need to include **only** this module as the dependency within your angular app.*
     * 
     * <pre>
     * <!doctype html>
     * <html ng-app="myApp">
     * <head>
     *   <script src="js/angular.js"></script>
     *   <!-- Include the ui-router script -->
     *   <script src="js/angular-ui-router.min.js"></script>
     *   <script>
     *     // ...and add 'ui.router' as a dependency
     *     var myApp = angular.module('myApp', ['ui.router']);
     *   </script>
     * </head>
     * <body>
     * </body>
     * </html>
     * </pre>
     */
    angular.module('ui.router', ['ui.router.state']);

    angular.module('ui.router.compat', ['ui.router']);

    /**
     * @ngdoc object
     * @name ui.router.util.$resolve
     *
     * @requires $q
     * @requires $injector
     *
     * @description
     * Manages resolution of (acyclic) graphs of promises.
     */
    $Resolve.$inject = ['$q', '$injector'];
    function $Resolve($q, $injector) {

        var VISIT_IN_PROGRESS = 1,
            VISIT_DONE = 2,
            NOTHING = {},
            NO_DEPENDENCIES = [],
            NO_LOCALS = NOTHING,
            NO_PARENT = extend($q.when(NOTHING), { $$promises: NOTHING, $$values: NOTHING });


        /**
         * @ngdoc function
         * @name ui.router.util.$resolve#study
         * @methodOf ui.router.util.$resolve
         *
         * @description
         * Studies a set of invocables that are likely to be used multiple times.
         * <pre>
         * $resolve.study(invocables)(locals, parent, self)
         * </pre>
         * is equivalent to
         * <pre>
         * $resolve.resolve(invocables, locals, parent, self)
         * </pre>
         * but the former is more efficient (in fact `resolve` just calls `study` 
         * internally).
         *
         * @param {object} invocables Invocable objects
         * @return {function} a function to pass in locals, parent and self
         */
        this.study = function (invocables) {
            if (!isObject(invocables)) throw new Error("'invocables' must be an object");
            var invocableKeys = objectKeys(invocables || {});

            // Perform a topological sort of invocables to build an ordered plan
            var plan = [], cycle = [], visited = {};
            function visit(value, key) {
                if (visited[key] === VISIT_DONE) return;

                cycle.push(key);
                if (visited[key] === VISIT_IN_PROGRESS) {
                    cycle.splice(0, indexOf(cycle, key));
                    throw new Error("Cyclic dependency: " + cycle.join(" -> "));
                }
                visited[key] = VISIT_IN_PROGRESS;

                if (isString(value)) {
                    plan.push(key, [function () { return $injector.get(value); }], NO_DEPENDENCIES);
                } else {
                    var params = $injector.annotate(value);
                    forEach(params, function (param) {
                        if (param !== key && invocables.hasOwnProperty(param)) visit(invocables[param], param);
                    });
                    plan.push(key, value, params);
                }

                cycle.pop();
                visited[key] = VISIT_DONE;
            }
            forEach(invocables, visit);
            invocables = cycle = visited = null; // plan is all that's required

            function isResolve(value) {
                return isObject(value) && value.then && value.$$promises;
            }

            return function (locals, parent, self) {
                if (isResolve(locals) && self === undefined) {
                    self = parent; parent = locals; locals = null;
                }
                if (!locals) locals = NO_LOCALS;
                else if (!isObject(locals)) {
                    throw new Error("'locals' must be an object");
                }
                if (!parent) parent = NO_PARENT;
                else if (!isResolve(parent)) {
                    throw new Error("'parent' must be a promise returned by $resolve.resolve()");
                }

                // To complete the overall resolution, we have to wait for the parent
                // promise and for the promise for each invokable in our plan.
                var resolution = $q.defer(),
                    result = silenceUncaughtInPromise(resolution.promise),
                    promises = result.$$promises = {},
                    values = extend({}, locals),
                    wait = 1 + plan.length / 3,
                    merged = false;

                silenceUncaughtInPromise(result);

                function done() {
                    // Merge parent values we haven't got yet and publish our own $$values
                    if (!--wait) {
                        if (!merged) merge(values, parent.$$values);
                        result.$$values = values;
                        result.$$promises = result.$$promises || true; // keep for isResolve()
                        delete result.$$inheritedValues;
                        resolution.resolve(values);
                    }
                }

                function fail(reason) {
                    result.$$failure = reason;
                    resolution.reject(reason);
                }

                // Short-circuit if parent has already failed
                if (isDefined(parent.$$failure)) {
                    fail(parent.$$failure);
                    return result;
                }

                if (parent.$$inheritedValues) {
                    merge(values, omit(parent.$$inheritedValues, invocableKeys));
                }

                // Merge parent values if the parent has already resolved, or merge
                // parent promises and wait if the parent resolve is still in progress.
                extend(promises, parent.$$promises);
                if (parent.$$values) {
                    merged = merge(values, omit(parent.$$values, invocableKeys));
                    result.$$inheritedValues = omit(parent.$$values, invocableKeys);
                    done();
                } else {
                    if (parent.$$inheritedValues) {
                        result.$$inheritedValues = omit(parent.$$inheritedValues, invocableKeys);
                    }
                    parent.then(done, fail);
                }

                // Process each invocable in the plan, but ignore any where a local of the same name exists.
                for (var i = 0, ii = plan.length; i < ii; i += 3) {
                    if (locals.hasOwnProperty(plan[i])) done();
                    else invoke(plan[i], plan[i + 1], plan[i + 2]);
                }

                function invoke(key, invocable, params) {
                    // Create a deferred for this invocation. Failures will propagate to the resolution as well.
                    var invocation = $q.defer(), waitParams = 0;
                    function onfailure(reason) {
                        invocation.reject(reason);
                        fail(reason);
                    }
                    // Wait for any parameter that we have a promise for (either from parent or from this
                    // resolve; in that case study() will have made sure it's ordered before us in the plan).
                    forEach(params, function (dep) {
                        if (promises.hasOwnProperty(dep) && !locals.hasOwnProperty(dep)) {
                            waitParams++;
                            promises[dep].then(function (result) {
                                values[dep] = result;
                                if (!(--waitParams)) proceed();
                            }, onfailure);
                        }
                    });
                    if (!waitParams) proceed();
                    function proceed() {
                        if (isDefined(result.$$failure)) return;
                        try {
                            invocation.resolve($injector.invoke(invocable, self, values));
                            invocation.promise.then(function (result) {
                                values[key] = result;
                                done();
                            }, onfailure);
                        } catch (e) {
                            onfailure(e);
                        }
                    }
                    // Publish promise synchronously; invocations further down in the plan may depend on it.
                    promises[key] = silenceUncaughtInPromise(invocation.promise);
                }

                return result;
            };
        };

        /**
         * @ngdoc function
         * @name ui.router.util.$resolve#resolve
         * @methodOf ui.router.util.$resolve
         *
         * @description
         * Resolves a set of invocables. An invocable is a function to be invoked via 
         * `$injector.invoke()`, and can have an arbitrary number of dependencies. 
         * An invocable can either return a value directly,
         * or a `$q` promise. If a promise is returned it will be resolved and the 
         * resulting value will be used instead. Dependencies of invocables are resolved 
         * (in this order of precedence)
         *
         * - from the specified `locals`
         * - from another invocable that is part of this `$resolve` call
         * - from an invocable that is inherited from a `parent` call to `$resolve` 
         *   (or recursively
         * - from any ancestor `$resolve` of that parent).
         *
         * The return value of `$resolve` is a promise for an object that contains 
         * (in this order of precedence)
         *
         * - any `locals` (if specified)
         * - the resolved return values of all injectables
         * - any values inherited from a `parent` call to `$resolve` (if specified)
         *
         * The promise will resolve after the `parent` promise (if any) and all promises 
         * returned by injectables have been resolved. If any invocable 
         * (or `$injector.invoke`) throws an exception, or if a promise returned by an 
         * invocable is rejected, the `$resolve` promise is immediately rejected with the 
         * same error. A rejection of a `parent` promise (if specified) will likewise be 
         * propagated immediately. Once the `$resolve` promise has been rejected, no 
         * further invocables will be called.
         * 
         * Cyclic dependencies between invocables are not permitted and will cause `$resolve`
         * to throw an error. As a special case, an injectable can depend on a parameter 
         * with the same name as the injectable, which will be fulfilled from the `parent` 
         * injectable of the same name. This allows inherited values to be decorated. 
         * Note that in this case any other injectable in the same `$resolve` with the same
         * dependency would see the decorated value, not the inherited value.
         *
         * Note that missing dependencies -- unlike cyclic dependencies -- will cause an 
         * (asynchronous) rejection of the `$resolve` promise rather than a (synchronous) 
         * exception.
         *
         * Invocables are invoked eagerly as soon as all dependencies are available. 
         * This is true even for dependencies inherited from a `parent` call to `$resolve`.
         *
         * As a special case, an invocable can be a string, in which case it is taken to 
         * be a service name to be passed to `$injector.get()`. This is supported primarily 
         * for backwards-compatibility with the `resolve` property of `$routeProvider` 
         * routes.
         *
         * @param {object} invocables functions to invoke or 
         * `$injector` services to fetch.
         * @param {object} locals  values to make available to the injectables
         * @param {object} parent  a promise returned by another call to `$resolve`.
         * @param {object} self  the `this` for the invoked methods
         * @return {object} Promise for an object that contains the resolved return value
         * of all invocables, as well as any inherited and local values.
         */
        this.resolve = function (invocables, locals, parent, self) {
            return this.study(invocables)(locals, parent, self);
        };
    }

    angular.module('ui.router.util').service('$resolve', $Resolve);



    /**
     * @ngdoc object
     * @name ui.router.util.$templateFactoryProvider
     *
     * @description
     * Provider for $templateFactory. Manages which template-loading mechanism to
     * use, and will default to the most recent one ($templateRequest on Angular
     * versions starting from 1.3, $http otherwise).
     */
    function TemplateFactoryProvider() {
        var shouldUnsafelyUseHttp = angular.version.minor < 3;

        /**
         * @ngdoc function
         * @name ui.router.util.$templateFactoryProvider#shouldUnsafelyUseHttp
         * @methodOf ui.router.util.$templateFactoryProvider
         *
         * @description
         * Forces $templateFactory to use $http instead of $templateRequest. This
         * might cause XSS, as $http doesn't enforce the regular security checks for
         * templates that have been introduced in Angular 1.3. Note that setting this
         * to false on Angular older than 1.3.x will crash, as the $templateRequest
         * service (and the security checks) are not implemented on these versions.
         *
         * See the $sce documentation, section
         * <a href="https://docs.angularjs.org/api/ng/service/$sce#impact-on-loading-templates">
         * Impact on loading templates</a> for more details about this mechanism.
         *
         * @param {boolean} value
         */
        this.shouldUnsafelyUseHttp = function (value) {
            shouldUnsafelyUseHttp = !!value;
        };

        /**
         * @ngdoc object
         * @name ui.router.util.$templateFactory
         *
         * @requires $http
         * @requires $templateCache
         * @requires $injector
         *
         * @description
         * Service. Manages loading of templates.
         */
        this.$get = ['$http', '$templateCache', '$injector', function ($http, $templateCache, $injector) {
            return new TemplateFactory($http, $templateCache, $injector, shouldUnsafelyUseHttp);
        }];
    }


    /**
     * @ngdoc object
     * @name ui.router.util.$templateFactory
     *
     * @requires $http
     * @requires $templateCache
     * @requires $injector
     *
     * @description
     * Service. Manages loading of templates.
     */
    function TemplateFactory($http, $templateCache, $injector, shouldUnsafelyUseHttp) {

        /**
         * @ngdoc function
         * @name ui.router.util.$templateFactory#fromConfig
         * @methodOf ui.router.util.$templateFactory
         *
         * @description
         * Creates a template from a configuration object. 
         *
         * @param {object} config Configuration object for which to load a template. 
         * The following properties are search in the specified order, and the first one 
         * that is defined is used to create the template:
         *
         * @param {string|object} config.template html string template or function to 
         * load via {@link ui.router.util.$templateFactory#fromString fromString}.
         * @param {string|object} config.templateUrl url to load or a function returning 
         * the url to load via {@link ui.router.util.$templateFactory#fromUrl fromUrl}.
         * @param {Function} config.templateProvider function to invoke via 
         * {@link ui.router.util.$templateFactory#fromProvider fromProvider}.
         * @param {object} params  Parameters to pass to the template function.
         * @param {object} locals Locals to pass to `invoke` if the template is loaded 
         * via a `templateProvider`. Defaults to `{ params: params }`.
         *
         * @return {string|object}  The template html as a string, or a promise for 
         * that string,or `null` if no template is configured.
         */
        this.fromConfig = function (config, params, locals) {
            return (
              isDefined(config.template) ? this.fromString(config.template, params) :
              isDefined(config.templateUrl) ? this.fromUrl(config.templateUrl, params) :
              isDefined(config.templateProvider) ? this.fromProvider(config.templateProvider, params, locals) :
              null
            );
        };

        /**
         * @ngdoc function
         * @name ui.router.util.$templateFactory#fromString
         * @methodOf ui.router.util.$templateFactory
         *
         * @description
         * Creates a template from a string or a function returning a string.
         *
         * @param {string|object} template html template as a string or function that 
         * returns an html template as a string.
         * @param {object} params Parameters to pass to the template function.
         *
         * @return {string|object} The template html as a string, or a promise for that 
         * string.
         */
        this.fromString = function (template, params) {
            return isFunction(template) ? template(params) : template;
        };

        /**
         * @ngdoc function
         * @name ui.router.util.$templateFactory#fromUrl
         * @methodOf ui.router.util.$templateFactory
         * 
         * @description
         * Loads a template from the a URL via `$http` and `$templateCache`.
         *
         * @param {string|Function} url url of the template to load, or a function 
         * that returns a url.
         * @param {Object} params Parameters to pass to the url function.
         * @return {string|Promise.<string>} The template html as a string, or a promise 
         * for that string.
         */
        this.fromUrl = function (url, params) {
            if (isFunction(url)) url = url(params);
            if (url == null) return null;
            else {
                if (!shouldUnsafelyUseHttp) {
                    return $injector.get('$templateRequest')(url);
                } else {
                    return $http
                      .get(url, { cache: $templateCache, headers: { Accept: 'text/html' } })
                      .then(function (response) { return response.data; });
                }
            }
        };

        /**
         * @ngdoc function
         * @name ui.router.util.$templateFactory#fromProvider
         * @methodOf ui.router.util.$templateFactory
         *
         * @description
         * Creates a template by invoking an injectable provider function.
         *
         * @param {Function} provider Function to invoke via `$injector.invoke`
         * @param {Object} params Parameters for the template.
         * @param {Object} locals Locals to pass to `invoke`. Defaults to 
         * `{ params: params }`.
         * @return {string|Promise.<string>} The template html as a string, or a promise 
         * for that string.
         */
        this.fromProvider = function (provider, params, locals) {
            return $injector.invoke(provider, null, locals || { params: params });
        };
    }

    angular.module('ui.router.util').provider('$templateFactory', TemplateFactoryProvider);

    var $$UMFP; // reference to $UrlMatcherFactoryProvider

    /**
     * @ngdoc object
     * @name ui.router.util.type:UrlMatcher
     *
     * @description
     * Matches URLs against patterns and extracts named parameters from the path or the search
     * part of the URL. A URL pattern consists of a path pattern, optionally followed by '?' and a list
     * of search parameters. Multiple search parameter names are separated by '&'. Search parameters
     * do not influence whether or not a URL is matched, but their values are passed through into
     * the matched parameters returned by {@link ui.router.util.type:UrlMatcher#methods_exec exec}.
     *
     * Path parameter placeholders can be specified using simple colon/catch-all syntax or curly brace
     * syntax, which optionally allows a regular expression for the parameter to be specified:
     *
     * * `':'` name - colon placeholder
     * * `'*'` name - catch-all placeholder
     * * `'{' name '}'` - curly placeholder
     * * `'{' name ':' regexp|type '}'` - curly placeholder with regexp or type name. Should the
     *   regexp itself contain curly braces, they must be in matched pairs or escaped with a backslash.
     *
     * Parameter names may contain only word characters (latin letters, digits, and underscore) and
     * must be unique within the pattern (across both path and search parameters). For colon
     * placeholders or curly placeholders without an explicit regexp, a path parameter matches any
     * number of characters other than '/'. For catch-all placeholders the path parameter matches
     * any number of characters.
     *
     * Examples:
     *
     * * `'/hello/'` - Matches only if the path is exactly '/hello/'. There is no special treatment for
     *   trailing slashes, and patterns have to match the entire path, not just a prefix.
     * * `'/user/:id'` - Matches '/user/bob' or '/user/1234!!!' or even '/user/' but not '/user' or
     *   '/user/bob/details'. The second path segment will be captured as the parameter 'id'.
     * * `'/user/{id}'` - Same as the previous example, but using curly brace syntax.
     * * `'/user/{id:[^/]*}'` - Same as the previous example.
     * * `'/user/{id:[0-9a-fA-F]{1,8}}'` - Similar to the previous example, but only matches if the id
     *   parameter consists of 1 to 8 hex digits.
     * * `'/files/{path:.*}'` - Matches any URL starting with '/files/' and captures the rest of the
     *   path into the parameter 'path'.
     * * `'/files/*path'` - ditto.
     * * `'/calendar/{start:date}'` - Matches "/calendar/2014-11-12" (because the pattern defined
     *   in the built-in  `date` Type matches `2014-11-12`) and provides a Date object in $stateParams.start
     *
     * @param {string} pattern  The pattern to compile into a matcher.
     * @param {Object} config  A configuration object hash:
     * @param {Object=} parentMatcher Used to concatenate the pattern/config onto
     *   an existing UrlMatcher
     *
     * * `caseInsensitive` - `true` if URL matching should be case insensitive, otherwise `false`, the default value (for backward compatibility) is `false`.
     * * `strict` - `false` if matching against a URL with a trailing slash should be treated as equivalent to a URL without a trailing slash, the default value is `true`.
     *
     * @property {string} prefix  A static prefix of this pattern. The matcher guarantees that any
     *   URL matching this matcher (i.e. any string for which {@link ui.router.util.type:UrlMatcher#methods_exec exec()} returns
     *   non-null) will start with this prefix.
     *
     * @property {string} source  The pattern that was passed into the constructor
     *
     * @property {string} sourcePath  The path portion of the source property
     *
     * @property {string} sourceSearch  The search portion of the source property
     *
     * @property {string} regex  The constructed regex that will be used to match against the url when
     *   it is time to determine which url will match.
     *
     * @returns {Object}  New `UrlMatcher` object
     */
    function UrlMatcher(pattern, config, parentMatcher) {
        config = extend({ params: {} }, isObject(config) ? config : {});

        // Find all placeholders and create a compiled pattern, using either classic or curly syntax:
        //   '*' name
        //   ':' name
        //   '{' name '}'
        //   '{' name ':' regexp '}'
        // The regular expression is somewhat complicated due to the need to allow curly braces
        // inside the regular expression. The placeholder regexp breaks down as follows:
        //    ([:*])([\w\[\]]+)              - classic placeholder ($1 / $2) (search version has - for snake-case)
        //    \{([\w\[\]]+)(?:\:\s*( ... ))?\}  - curly brace placeholder ($3) with optional regexp/type ... ($4) (search version has - for snake-case
        //    (?: ... | ... | ... )+         - the regexp consists of any number of atoms, an atom being either
        //    [^{}\\]+                       - anything other than curly braces or backslash
        //    \\.                            - a backslash escape
        //    \{(?:[^{}\\]+|\\.)*\}          - a matched set of curly braces containing other atoms
        var placeholder = /([:*])([\w\[\]]+)|\{([\w\[\]]+)(?:\:\s*((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,
            searchPlaceholder = /([:]?)([\w\[\].-]+)|\{([\w\[\].-]+)(?:\:\s*((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,
            compiled = '^', last = 0, m,
            segments = this.segments = [],
            parentParams = parentMatcher ? parentMatcher.params : {},
            params = this.params = parentMatcher ? parentMatcher.params.$$new() : new $$UMFP.ParamSet(),
            paramNames = [];

        function addParameter(id, type, config, location) {
            paramNames.push(id);
            if (parentParams[id]) return parentParams[id];
            if (!/^\w+([-.]+\w+)*(?:\[\])?$/.test(id)) throw new Error("Invalid parameter name '" + id + "' in pattern '" + pattern + "'");
            if (params[id]) throw new Error("Duplicate parameter name '" + id + "' in pattern '" + pattern + "'");
            params[id] = new $$UMFP.Param(id, type, config, location);
            return params[id];
        }

        function quoteRegExp(string, pattern, squash, optional) {
            var surroundPattern = ['', ''], result = string.replace(/[\\\[\]\^$*+?.()|{}]/g, "\\$&");
            if (!pattern) return result;
            switch (squash) {
                case false: surroundPattern = ['(', ')' + (optional ? "?" : "")]; break;
                case true:
                    result = result.replace(/\/$/, '');
                    surroundPattern = ['(?:\/(', ')|\/)?'];
                    break;
                default: surroundPattern = ['(' + squash + "|", ')?']; break;
            }
            return result + surroundPattern[0] + pattern + surroundPattern[1];
        }

        this.source = pattern;

        // Split into static segments separated by path parameter placeholders.
        // The number of segments is always 1 more than the number of parameters.
        function matchDetails(m, isSearch) {
            var id, regexp, segment, type, cfg, arrayMode;
            id = m[2] || m[3]; // IE[78] returns '' for unmatched groups instead of null
            cfg = config.params[id];
            segment = pattern.substring(last, m.index);
            regexp = isSearch ? m[4] : m[4] || (m[1] == '*' ? '.*' : null);

            if (regexp) {
                type = $$UMFP.type(regexp) || inherit($$UMFP.type("string"), { pattern: new RegExp(regexp, config.caseInsensitive ? 'i' : undefined) });
            }

            return {
                id: id, regexp: regexp, segment: segment, type: type, cfg: cfg
            };
        }

        var p, param, segment;
        while ((m = placeholder.exec(pattern))) {
            p = matchDetails(m, false);
            if (p.segment.indexOf('?') >= 0) break; // we're into the search part

            param = addParameter(p.id, p.type, p.cfg, "path");
            compiled += quoteRegExp(p.segment, param.type.pattern.source, param.squash, param.isOptional);
            segments.push(p.segment);
            last = placeholder.lastIndex;
        }
        segment = pattern.substring(last);

        // Find any search parameter names and remove them from the last segment
        var i = segment.indexOf('?');

        if (i >= 0) {
            var search = this.sourceSearch = segment.substring(i);
            segment = segment.substring(0, i);
            this.sourcePath = pattern.substring(0, last + i);

            if (search.length > 0) {
                last = 0;
                while ((m = searchPlaceholder.exec(search))) {
                    p = matchDetails(m, true);
                    param = addParameter(p.id, p.type, p.cfg, "search");
                    last = placeholder.lastIndex;
                    // check if ?&
                }
            }
        } else {
            this.sourcePath = pattern;
            this.sourceSearch = '';
        }

        compiled += quoteRegExp(segment) + (config.strict === false ? '\/?' : '') + '$';
        segments.push(segment);

        this.regexp = new RegExp(compiled, config.caseInsensitive ? 'i' : undefined);
        this.prefix = segments[0];
        this.$$paramNames = paramNames;
    }

    /**
     * @ngdoc function
     * @name ui.router.util.type:UrlMatcher#concat
     * @methodOf ui.router.util.type:UrlMatcher
     *
     * @description
     * Returns a new matcher for a pattern constructed by appending the path part and adding the
     * search parameters of the specified pattern to this pattern. The current pattern is not
     * modified. This can be understood as creating a pattern for URLs that are relative to (or
     * suffixes of) the current pattern.
     *
     * @example
     * The following two matchers are equivalent:
     * <pre>
     * new UrlMatcher('/user/{id}?q').concat('/details?date');
     * new UrlMatcher('/user/{id}/details?q&date');
     * </pre>
     *
     * @param {string} pattern  The pattern to append.
     * @param {Object} config  An object hash of the configuration for the matcher.
     * @returns {UrlMatcher}  A matcher for the concatenated pattern.
     */
    UrlMatcher.prototype.concat = function (pattern, config) {
        // Because order of search parameters is irrelevant, we can add our own search
        // parameters to the end of the new pattern. Parse the new pattern by itself
        // and then join the bits together, but it's much easier to do this on a string level.
        var defaultConfig = {
            caseInsensitive: $$UMFP.caseInsensitive(),
            strict: $$UMFP.strictMode(),
            squash: $$UMFP.defaultSquashPolicy()
        };
        return new UrlMatcher(this.sourcePath + pattern + this.sourceSearch, extend(defaultConfig, config), this);
    };

    UrlMatcher.prototype.toString = function () {
        return this.source;
    };

    /**
     * @ngdoc function
     * @name ui.router.util.type:UrlMatcher#exec
     * @methodOf ui.router.util.type:UrlMatcher
     *
     * @description
     * Tests the specified path against this matcher, and returns an object containing the captured
     * parameter values, or null if the path does not match. The returned object contains the values
     * of any search parameters that are mentioned in the pattern, but their value may be null if
     * they are not present in `searchParams`. This means that search parameters are always treated
     * as optional.
     *
     * @example
     * <pre>
     * new UrlMatcher('/user/{id}?q&r').exec('/user/bob', {
     *   x: '1', q: 'hello'
     * });
     * // returns { id: 'bob', q: 'hello', r: null }
     * </pre>
     *
     * @param {string} path  The URL path to match, e.g. `$location.path()`.
     * @param {Object} searchParams  URL search parameters, e.g. `$location.search()`.
     * @returns {Object}  The captured parameter values.
     */
    UrlMatcher.prototype.exec = function (path, searchParams) {
        var m = this.regexp.exec(path);
        if (!m) return null;
        searchParams = searchParams || {};

        var paramNames = this.parameters(), nTotal = paramNames.length,
          nPath = this.segments.length - 1,
          values = {}, i, j, cfg, paramName;

        if (nPath !== m.length - 1) throw new Error("Unbalanced capture group in route '" + this.source + "'");

        function decodePathArray(string) {
            function reverseString(str) { return str.split("").reverse().join(""); }
            function unquoteDashes(str) { return str.replace(/\\-/g, "-"); }

            var split = reverseString(string).split(/-(?!\\)/);
            var allReversed = map(split, reverseString);
            return map(allReversed, unquoteDashes).reverse();
        }

        var param, paramVal;
        for (i = 0; i < nPath; i++) {
            paramName = paramNames[i];
            param = this.params[paramName];
            paramVal = m[i + 1];
            // if the param value matches a pre-replace pair, replace the value before decoding.
            for (j = 0; j < param.replace.length; j++) {
                if (param.replace[j].from === paramVal) paramVal = param.replace[j].to;
            }
            if (paramVal && param.array === true) paramVal = decodePathArray(paramVal);
            if (isDefined(paramVal)) paramVal = param.type.decode(paramVal);
            values[paramName] = param.value(paramVal);
        }
        for (/**/; i < nTotal; i++) {
            paramName = paramNames[i];
            values[paramName] = this.params[paramName].value(searchParams[paramName]);
            param = this.params[paramName];
            paramVal = searchParams[paramName];
            for (j = 0; j < param.replace.length; j++) {
                if (param.replace[j].from === paramVal) paramVal = param.replace[j].to;
            }
            if (isDefined(paramVal)) paramVal = param.type.decode(paramVal);
            values[paramName] = param.value(paramVal);
        }

        return values;
    };

    /**
     * @ngdoc function
     * @name ui.router.util.type:UrlMatcher#parameters
     * @methodOf ui.router.util.type:UrlMatcher
     *
     * @description
     * Returns the names of all path and search parameters of this pattern in an unspecified order.
     *
     * @returns {Array.<string>}  An array of parameter names. Must be treated as read-only. If the
     *    pattern has no parameters, an empty array is returned.
     */
    UrlMatcher.prototype.parameters = function (param) {
        if (!isDefined(param)) return this.$$paramNames;
        return this.params[param] || null;
    };

    /**
     * @ngdoc function
     * @name ui.router.util.type:UrlMatcher#validates
     * @methodOf ui.router.util.type:UrlMatcher
     *
     * @description
     * Checks an object hash of parameters to validate their correctness according to the parameter
     * types of this `UrlMatcher`.
     *
     * @param {Object} params The object hash of parameters to validate.
     * @returns {boolean} Returns `true` if `params` validates, otherwise `false`.
     */
    UrlMatcher.prototype.validates = function (params) {
        return this.params.$$validates(params);
    };

    /**
     * @ngdoc function
     * @name ui.router.util.type:UrlMatcher#format
     * @methodOf ui.router.util.type:UrlMatcher
     *
     * @description
     * Creates a URL that matches this pattern by substituting the specified values
     * for the path and search parameters. Null values for path parameters are
     * treated as empty strings.
     *
     * @example
     * <pre>
     * new UrlMatcher('/user/{id}?q').format({ id:'bob', q:'yes' });
     * // returns '/user/bob?q=yes'
     * </pre>
     *
     * @param {Object} values  the values to substitute for the parameters in this pattern.
     * @returns {string}  the formatted URL (path and optionally search part).
     */
    UrlMatcher.prototype.format = function (values) {
        values = values || {};
        var segments = this.segments, params = this.parameters(), paramset = this.params;
        if (!this.validates(values)) return null;

        var i, search = false, nPath = segments.length - 1, nTotal = params.length, result = segments[0];

        function encodeDashes(str) { // Replace dashes with encoded "\-"
            return encodeURIComponent(str).replace(/-/g, function (c) { return '%5C%' + c.charCodeAt(0).toString(16).toUpperCase(); });
        }

        for (i = 0; i < nTotal; i++) {
            var isPathParam = i < nPath;
            var name = params[i], param = paramset[name], value = param.value(values[name]);
            var isDefaultValue = param.isOptional && param.type.equals(param.value(), value);
            var squash = isDefaultValue ? param.squash : false;
            var encoded = param.type.encode(value);

            if (isPathParam) {
                var nextSegment = segments[i + 1];
                var isFinalPathParam = i + 1 === nPath;

                if (squash === false) {
                    if (encoded != null) {
                        if (isArray(encoded)) {
                            result += map(encoded, encodeDashes).join("-");
                        } else {
                            result += encodeURIComponent(encoded);
                        }
                    }
                    result += nextSegment;
                } else if (squash === true) {
                    var capture = result.match(/\/$/) ? /\/?(.*)/ : /(.*)/;
                    result += nextSegment.match(capture)[1];
                } else if (isString(squash)) {
                    result += squash + nextSegment;
                }

                if (isFinalPathParam && param.squash === true && result.slice(-1) === '/') result = result.slice(0, -1);
            } else {
                if (encoded == null || (isDefaultValue && squash !== false)) continue;
                if (!isArray(encoded)) encoded = [encoded];
                if (encoded.length === 0) continue;
                encoded = map(encoded, encodeURIComponent).join('&' + name + '=');
                result += (search ? '&' : '?') + (name + '=' + encoded);
                search = true;
            }
        }

        return result;
    };

    /**
     * @ngdoc object
     * @name ui.router.util.type:Type
     *
     * @description
     * Implements an interface to define custom parameter types that can be decoded from and encoded to
     * string parameters matched in a URL. Used by {@link ui.router.util.type:UrlMatcher `UrlMatcher`}
     * objects when matching or formatting URLs, or comparing or validating parameter values.
     *
     * See {@link ui.router.util.$urlMatcherFactory#methods_type `$urlMatcherFactory#type()`} for more
     * information on registering custom types.
     *
     * @param {Object} config  A configuration object which contains the custom type definition.  The object's
     *        properties will override the default methods and/or pattern in `Type`'s public interface.
     * @example
     * <pre>
     * {
     *   decode: function(val) { return parseInt(val, 10); },
     *   encode: function(val) { return val && val.toString(); },
     *   equals: function(a, b) { return this.is(a) && a === b; },
     *   is: function(val) { return angular.isNumber(val) isFinite(val) && val % 1 === 0; },
     *   pattern: /\d+/
     * }
     * </pre>
     *
     * @property {RegExp} pattern The regular expression pattern used to match values of this type when
     *           coming from a substring of a URL.
     *
     * @returns {Object}  Returns a new `Type` object.
     */
    function Type(config) {
        extend(this, config);
    }

    /**
     * @ngdoc function
     * @name ui.router.util.type:Type#is
     * @methodOf ui.router.util.type:Type
     *
     * @description
     * Detects whether a value is of a particular type. Accepts a native (decoded) value
     * and determines whether it matches the current `Type` object.
     *
     * @param {*} val  The value to check.
     * @param {string} key  Optional. If the type check is happening in the context of a specific
     *        {@link ui.router.util.type:UrlMatcher `UrlMatcher`} object, this is the name of the
     *        parameter in which `val` is stored. Can be used for meta-programming of `Type` objects.
     * @returns {Boolean}  Returns `true` if the value matches the type, otherwise `false`.
     */
    Type.prototype.is = function (val, key) {
        return true;
    };

    /**
     * @ngdoc function
     * @name ui.router.util.type:Type#encode
     * @methodOf ui.router.util.type:Type
     *
     * @description
     * Encodes a custom/native type value to a string that can be embedded in a URL. Note that the
     * return value does *not* need to be URL-safe (i.e. passed through `encodeURIComponent()`), it
     * only needs to be a representation of `val` that has been coerced to a string.
     *
     * @param {*} val  The value to encode.
     * @param {string} key  The name of the parameter in which `val` is stored. Can be used for
     *        meta-programming of `Type` objects.
     * @returns {string}  Returns a string representation of `val` that can be encoded in a URL.
     */
    Type.prototype.encode = function (val, key) {
        return val;
    };

    /**
     * @ngdoc function
     * @name ui.router.util.type:Type#decode
     * @methodOf ui.router.util.type:Type
     *
     * @description
     * Converts a parameter value (from URL string or transition param) to a custom/native value.
     *
     * @param {string} val  The URL parameter value to decode.
     * @param {string} key  The name of the parameter in which `val` is stored. Can be used for
     *        meta-programming of `Type` objects.
     * @returns {*}  Returns a custom representation of the URL parameter value.
     */
    Type.prototype.decode = function (val, key) {
        return val;
    };

    /**
     * @ngdoc function
     * @name ui.router.util.type:Type#equals
     * @methodOf ui.router.util.type:Type
     *
     * @description
     * Determines whether two decoded values are equivalent.
     *
     * @param {*} a  A value to compare against.
     * @param {*} b  A value to compare against.
     * @returns {Boolean}  Returns `true` if the values are equivalent/equal, otherwise `false`.
     */
    Type.prototype.equals = function (a, b) {
        return a == b;
    };

    Type.prototype.$subPattern = function () {
        var sub = this.pattern.toString();
        return sub.substr(1, sub.length - 2);
    };

    Type.prototype.pattern = /.*/;

    Type.prototype.toString = function () { return "{Type:" + this.name + "}"; };

    /** Given an encoded string, or a decoded object, returns a decoded object */
    Type.prototype.$normalize = function (val) {
        return this.is(val) ? val : this.decode(val);
    };

    /*
     * Wraps an existing custom Type as an array of Type, depending on 'mode'.
     * e.g.:
     * - urlmatcher pattern "/path?{queryParam[]:int}"
     * - url: "/path?queryParam=1&queryParam=2
     * - $stateParams.queryParam will be [1, 2]
     * if `mode` is "auto", then
     * - url: "/path?queryParam=1 will create $stateParams.queryParam: 1
     * - url: "/path?queryParam=1&queryParam=2 will create $stateParams.queryParam: [1, 2]
     */
    Type.prototype.$asArray = function (mode, isSearch) {
        if (!mode) return this;
        if (mode === "auto" && !isSearch) throw new Error("'auto' array mode is for query parameters only");

        function ArrayType(type, mode) {
            function bindTo(type, callbackName) {
                return function () {
                    return type[callbackName].apply(type, arguments);
                };
            }

            // Wrap non-array value as array
            function arrayWrap(val) { return isArray(val) ? val : (isDefined(val) ? [val] : []); }
            // Unwrap array value for "auto" mode. Return undefined for empty array.
            function arrayUnwrap(val) {
                switch (val.length) {
                    case 0: return undefined;
                    case 1: return mode === "auto" ? val[0] : val;
                    default: return val;
                }
            }
            function falsey(val) { return !val; }

            // Wraps type (.is/.encode/.decode) functions to operate on each value of an array
            function arrayHandler(callback, allTruthyMode) {
                return function handleArray(val) {
                    if (isArray(val) && val.length === 0) return val;
                    val = arrayWrap(val);
                    var result = map(val, callback);
                    if (allTruthyMode === true)
                        return filter(result, falsey).length === 0;
                    return arrayUnwrap(result);
                };
            }

            // Wraps type (.equals) functions to operate on each value of an array
            function arrayEqualsHandler(callback) {
                return function handleArray(val1, val2) {
                    var left = arrayWrap(val1), right = arrayWrap(val2);
                    if (left.length !== right.length) return false;
                    for (var i = 0; i < left.length; i++) {
                        if (!callback(left[i], right[i])) return false;
                    }
                    return true;
                };
            }

            this.encode = arrayHandler(bindTo(type, 'encode'));
            this.decode = arrayHandler(bindTo(type, 'decode'));
            this.is = arrayHandler(bindTo(type, 'is'), true);
            this.equals = arrayEqualsHandler(bindTo(type, 'equals'));
            this.pattern = type.pattern;
            this.$normalize = arrayHandler(bindTo(type, '$normalize'));
            this.name = type.name;
            this.$arrayMode = mode;
        }

        return new ArrayType(this, mode);
    };



    /**
     * @ngdoc object
     * @name ui.router.util.$urlMatcherFactory
     *
     * @description
     * Factory for {@link ui.router.util.type:UrlMatcher `UrlMatcher`} instances. The factory
     * is also available to providers under the name `$urlMatcherFactoryProvider`.
     */
    function $UrlMatcherFactory() {
        $$UMFP = this;

        var isCaseInsensitive = false, isStrictMode = true, defaultSquashPolicy = false;

        // Use tildes to pre-encode slashes.
        // If the slashes are simply URLEncoded, the browser can choose to pre-decode them,
        // and bidirectional encoding/decoding fails.
        // Tilde was chosen because it's not a RFC 3986 section 2.2 Reserved Character
        function valToString(val) { return val != null ? val.toString().replace(/(~|\/)/g, function (m) { return { '~': '~~', '/': '~2F' }[m]; }) : val; }
        function valFromString(val) { return val != null ? val.toString().replace(/(~~|~2F)/g, function (m) { return { '~~': '~', '~2F': '/' }[m]; }) : val; }

        var $types = {}, enqueue = true, typeQueue = [], injector, defaultTypes = {
            "string": {
                encode: valToString,
                decode: valFromString,
                // TODO: in 1.0, make string .is() return false if value is undefined/null by default.
                // In 0.2.x, string params are optional by default for backwards compat
                is: function (val) { return val == null || !isDefined(val) || typeof val === "string"; },
                pattern: /[^/]*/
            },
            "int": {
                encode: valToString,
                decode: function (val) { return parseInt(val, 10); },
                is: function (val) { return val !== undefined && val !== null && this.decode(val.toString()) === val; },
                pattern: /\d+/
            },
            "bool": {
                encode: function (val) { return val ? 1 : 0; },
                decode: function (val) { return parseInt(val, 10) !== 0; },
                is: function (val) { return val === true || val === false; },
                pattern: /0|1/
            },
            "date": {
                encode: function (val) {
                    if (!this.is(val))
                        return undefined;
                    return [val.getFullYear(),
                      ('0' + (val.getMonth() + 1)).slice(-2),
                      ('0' + val.getDate()).slice(-2)
                    ].join("-");
                },
                decode: function (val) {
                    if (this.is(val)) return val;
                    var match = this.capture.exec(val);
                    return match ? new Date(match[1], match[2] - 1, match[3]) : undefined;
                },
                is: function (val) { return val instanceof Date && !isNaN(val.valueOf()); },
                equals: function (a, b) { return this.is(a) && this.is(b) && a.toISOString() === b.toISOString(); },
                pattern: /[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1])/,
                capture: /([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/
            },
            "json": {
                encode: angular.toJson,
                decode: angular.fromJson,
                is: angular.isObject,
                equals: angular.equals,
                pattern: /[^/]*/
            },
            "any": { // does not encode/decode
                encode: angular.identity,
                decode: angular.identity,
                equals: angular.equals,
                pattern: /.*/
            }
        };

        function getDefaultConfig() {
            return {
                strict: isStrictMode,
                caseInsensitive: isCaseInsensitive
            };
        }

        function isInjectable(value) {
            return (isFunction(value) || (isArray(value) && isFunction(value[value.length - 1])));
        }

        /**
         * [Internal] Get the default value of a parameter, which may be an injectable function.
         */
        $UrlMatcherFactory.$$getDefaultValue = function (config) {
            if (!isInjectable(config.value)) return config.value;
            if (!injector) throw new Error("Injectable functions cannot be called at configuration time");
            return injector.invoke(config.value);
        };

        /**
         * @ngdoc function
         * @name ui.router.util.$urlMatcherFactory#caseInsensitive
         * @methodOf ui.router.util.$urlMatcherFactory
         *
         * @description
         * Defines whether URL matching should be case sensitive (the default behavior), or not.
         *
         * @param {boolean} value `false` to match URL in a case sensitive manner; otherwise `true`;
         * @returns {boolean} the current value of caseInsensitive
         */
        this.caseInsensitive = function (value) {
            if (isDefined(value))
                isCaseInsensitive = value;
            return isCaseInsensitive;
        };

        /**
         * @ngdoc function
         * @name ui.router.util.$urlMatcherFactory#strictMode
         * @methodOf ui.router.util.$urlMatcherFactory
         *
         * @description
         * Defines whether URLs should match trailing slashes, or not (the default behavior).
         *
         * @param {boolean=} value `false` to match trailing slashes in URLs, otherwise `true`.
         * @returns {boolean} the current value of strictMode
         */
        this.strictMode = function (value) {
            if (isDefined(value))
                isStrictMode = value;
            return isStrictMode;
        };

        /**
         * @ngdoc function
         * @name ui.router.util.$urlMatcherFactory#defaultSquashPolicy
         * @methodOf ui.router.util.$urlMatcherFactory
         *
         * @description
         * Sets the default behavior when generating or matching URLs with default parameter values.
         *
         * @param {string} value A string that defines the default parameter URL squashing behavior.
         *    `nosquash`: When generating an href with a default parameter value, do not squash the parameter value from the URL
         *    `slash`: When generating an href with a default parameter value, squash (remove) the parameter value, and, if the
         *             parameter is surrounded by slashes, squash (remove) one slash from the URL
         *    any other string, e.g. "~": When generating an href with a default parameter value, squash (remove)
         *             the parameter value from the URL and replace it with this string.
         */
        this.defaultSquashPolicy = function (value) {
            if (!isDefined(value)) return defaultSquashPolicy;
            if (value !== true && value !== false && !isString(value))
                throw new Error("Invalid squash policy: " + value + ". Valid policies: false, true, arbitrary-string");
            defaultSquashPolicy = value;
            return value;
        };

        /**
         * @ngdoc function
         * @name ui.router.util.$urlMatcherFactory#compile
         * @methodOf ui.router.util.$urlMatcherFactory
         *
         * @description
         * Creates a {@link ui.router.util.type:UrlMatcher `UrlMatcher`} for the specified pattern.
         *
         * @param {string} pattern  The URL pattern.
         * @param {Object} config  The config object hash.
         * @returns {UrlMatcher}  The UrlMatcher.
         */
        this.compile = function (pattern, config) {
            return new UrlMatcher(pattern, extend(getDefaultConfig(), config));
        };

        /**
         * @ngdoc function
         * @name ui.router.util.$urlMatcherFactory#isMatcher
         * @methodOf ui.router.util.$urlMatcherFactory
         *
         * @description
         * Returns true if the specified object is a `UrlMatcher`, or false otherwise.
         *
         * @param {Object} object  The object to perform the type check against.
         * @returns {Boolean}  Returns `true` if the object matches the `UrlMatcher` interface, by
         *          implementing all the same methods.
         */
        this.isMatcher = function (o) {
            if (!isObject(o)) return false;
            var result = true;

            forEach(UrlMatcher.prototype, function (val, name) {
                if (isFunction(val)) {
                    result = result && (isDefined(o[name]) && isFunction(o[name]));
                }
            });
            return result;
        };

        /**
         * @ngdoc function
         * @name ui.router.util.$urlMatcherFactory#type
         * @methodOf ui.router.util.$urlMatcherFactory
         *
         * @description
         * Registers a custom {@link ui.router.util.type:Type `Type`} object that can be used to
         * generate URLs with typed parameters.
         *
         * @param {string} name  The type name.
         * @param {Object|Function} definition   The type definition. See
         *        {@link ui.router.util.type:Type `Type`} for information on the values accepted.
         * @param {Object|Function} definitionFn (optional) A function that is injected before the app
         *        runtime starts.  The result of this function is merged into the existing `definition`.
         *        See {@link ui.router.util.type:Type `Type`} for information on the values accepted.
         *
         * @returns {Object}  Returns `$urlMatcherFactoryProvider`.
         *
         * @example
         * This is a simple example of a custom type that encodes and decodes items from an
         * array, using the array index as the URL-encoded value:
         *
         * <pre>
         * var list = ['John', 'Paul', 'George', 'Ringo'];
         *
         * $urlMatcherFactoryProvider.type('listItem', {
         *   encode: function(item) {
         *     // Represent the list item in the URL using its corresponding index
         *     return list.indexOf(item);
         *   },
         *   decode: function(item) {
         *     // Look up the list item by index
         *     return list[parseInt(item, 10)];
         *   },
         *   is: function(item) {
         *     // Ensure the item is valid by checking to see that it appears
         *     // in the list
         *     return list.indexOf(item) > -1;
         *   }
         * });
         *
         * $stateProvider.state('list', {
         *   url: "/list/{item:listItem}",
         *   controller: function($scope, $stateParams) {
         *     console.log($stateParams.item);
         *   }
         * });
         *
         * // ...
         *
         * // Changes URL to '/list/3', logs "Ringo" to the console
         * $state.go('list', { item: "Ringo" });
         * </pre>
         *
         * This is a more complex example of a type that relies on dependency injection to
         * interact with services, and uses the parameter name from the URL to infer how to
         * handle encoding and decoding parameter values:
         *
         * <pre>
         * // Defines a custom type that gets a value from a service,
         * // where each service gets different types of values from
         * // a backend API:
         * $urlMatcherFactoryProvider.type('dbObject', {}, function(Users, Posts) {
         *
         *   // Matches up services to URL parameter names
         *   var services = {
         *     user: Users,
         *     post: Posts
         *   };
         *
         *   return {
         *     encode: function(object) {
         *       // Represent the object in the URL using its unique ID
         *       return object.id;
         *     },
         *     decode: function(value, key) {
         *       // Look up the object by ID, using the parameter
         *       // name (key) to call the correct service
         *       return services[key].findById(value);
         *     },
         *     is: function(object, key) {
         *       // Check that object is a valid dbObject
         *       return angular.isObject(object) && object.id && services[key];
         *     }
         *     equals: function(a, b) {
         *       // Check the equality of decoded objects by comparing
         *       // their unique IDs
         *       return a.id === b.id;
         *     }
         *   };
         * });
         *
         * // In a config() block, you can then attach URLs with
         * // type-annotated parameters:
         * $stateProvider.state('users', {
         *   url: "/users",
         *   // ...
         * }).state('users.item', {
         *   url: "/{user:dbObject}",
         *   controller: function($scope, $stateParams) {
         *     // $stateParams.user will now be an object returned from
         *     // the Users service
         *   },
         *   // ...
         * });
         * </pre>
         */
        this.type = function (name, definition, definitionFn) {
            if (!isDefined(definition)) return $types[name];
            if ($types.hasOwnProperty(name)) throw new Error("A type named '" + name + "' has already been defined.");

            $types[name] = new Type(extend({ name: name }, definition));
            if (definitionFn) {
                typeQueue.push({ name: name, def: definitionFn });
                if (!enqueue) flushTypeQueue();
            }
            return this;
        };

        // `flushTypeQueue()` waits until `$urlMatcherFactory` is injected before invoking the queued `definitionFn`s
        function flushTypeQueue() {
            while (typeQueue.length) {
                var type = typeQueue.shift();
                if (type.pattern) throw new Error("You cannot override a type's .pattern at runtime.");
                angular.extend($types[type.name], injector.invoke(type.def));
            }
        }

        // Register default types. Store them in the prototype of $types.
        forEach(defaultTypes, function (type, name) { $types[name] = new Type(extend({ name: name }, type)); });
        $types = inherit($types, {});

        /* No need to document $get, since it returns this */
        this.$get = ['$injector', function ($injector) {
            injector = $injector;
            enqueue = false;
            flushTypeQueue();

            forEach(defaultTypes, function (type, name) {
                if (!$types[name]) $types[name] = new Type(type);
            });
            return this;
        }];

        this.Param = function Param(id, type, config, location) {
            var self = this;
            config = unwrapShorthand(config);
            type = getType(config, type, location);
            var arrayMode = getArrayMode();
            type = arrayMode ? type.$asArray(arrayMode, location === "search") : type;
            if (type.name === "string" && !arrayMode && location === "path" && config.value === undefined)
                config.value = ""; // for 0.2.x; in 0.3.0+ do not automatically default to ""
            var isOptional = config.value !== undefined;
            var squash = getSquashPolicy(config, isOptional);
            var replace = getReplace(config, arrayMode, isOptional, squash);

            function unwrapShorthand(config) {
                var keys = isObject(config) ? objectKeys(config) : [];
                var isShorthand = indexOf(keys, "value") === -1 && indexOf(keys, "type") === -1 &&
                                  indexOf(keys, "squash") === -1 && indexOf(keys, "array") === -1;
                if (isShorthand) config = { value: config };
                config.$$fn = isInjectable(config.value) ? config.value : function () { return config.value; };
                return config;
            }

            function getType(config, urlType, location) {
                if (config.type && urlType) throw new Error("Param '" + id + "' has two type configurations.");
                if (urlType) return urlType;
                if (!config.type) return (location === "config" ? $types.any : $types.string);

                if (angular.isString(config.type))
                    return $types[config.type];
                if (config.type instanceof Type)
                    return config.type;
                return new Type(config.type);
            }

            // array config: param name (param[]) overrides default settings.  explicit config overrides param name.
            function getArrayMode() {
                var arrayDefaults = { array: (location === "search" ? "auto" : false) };
                var arrayParamNomenclature = id.match(/\[\]$/) ? { array: true } : {};
                return extend(arrayDefaults, arrayParamNomenclature, config).array;
            }

            /**
             * returns false, true, or the squash value to indicate the "default parameter url squash policy".
             */
            function getSquashPolicy(config, isOptional) {
                var squash = config.squash;
                if (!isOptional || squash === false) return false;
                if (!isDefined(squash) || squash == null) return defaultSquashPolicy;
                if (squash === true || isString(squash)) return squash;
                throw new Error("Invalid squash policy: '" + squash + "'. Valid policies: false, true, or arbitrary string");
            }

            function getReplace(config, arrayMode, isOptional, squash) {
                var replace, configuredKeys, defaultPolicy = [
                  { from: "", to: (isOptional || arrayMode ? undefined : "") },
                  { from: null, to: (isOptional || arrayMode ? undefined : "") }
                ];
                replace = isArray(config.replace) ? config.replace : [];
                if (isString(squash))
                    replace.push({ from: squash, to: undefined });
                configuredKeys = map(replace, function (item) { return item.from; });
                return filter(defaultPolicy, function (item) { return indexOf(configuredKeys, item.from) === -1; }).concat(replace);
            }

            /**
             * [Internal] Get the default value of a parameter, which may be an injectable function.
             */
            function $$getDefaultValue() {
                if (!injector) throw new Error("Injectable functions cannot be called at configuration time");
                var defaultValue = injector.invoke(config.$$fn);
                if (defaultValue !== null && defaultValue !== undefined && !self.type.is(defaultValue))
                    throw new Error("Default value (" + defaultValue + ") for parameter '" + self.id + "' is not an instance of Type (" + self.type.name + ")");
                return defaultValue;
            }

            /**
             * [Internal] Gets the decoded representation of a value if the value is defined, otherwise, returns the
             * default value, which may be the result of an injectable function.
             */
            function $value(value) {
                function hasReplaceVal(val) { return function (obj) { return obj.from === val; }; }
                function $replace(value) {
                    var replacement = map(filter(self.replace, hasReplaceVal(value)), function (obj) { return obj.to; });
                    return replacement.length ? replacement[0] : value;
                }
                value = $replace(value);
                return !isDefined(value) ? $$getDefaultValue() : self.type.$normalize(value);
            }

            function toString() { return "{Param:" + id + " " + type + " squash: '" + squash + "' optional: " + isOptional + "}"; }

            extend(this, {
                id: id,
                type: type,
                location: location,
                array: arrayMode,
                squash: squash,
                replace: replace,
                isOptional: isOptional,
                value: $value,
                dynamic: undefined,
                config: config,
                toString: toString
            });
        };

        function ParamSet(params) {
            extend(this, params || {});
        }

        ParamSet.prototype = {
            $$new: function () {
                return inherit(this, extend(new ParamSet(), { $$parent: this }));
            },
            $$keys: function () {
                var keys = [], chain = [], parent = this,
                  ignore = objectKeys(ParamSet.prototype);
                while (parent) { chain.push(parent); parent = parent.$$parent; }
                chain.reverse();
                forEach(chain, function (paramset) {
                    forEach(objectKeys(paramset), function (key) {
                        if (indexOf(keys, key) === -1 && indexOf(ignore, key) === -1) keys.push(key);
                    });
                });
                return keys;
            },
            $$values: function (paramValues) {
                var values = {}, self = this;
                forEach(self.$$keys(), function (key) {
                    values[key] = self[key].value(paramValues && paramValues[key]);
                });
                return values;
            },
            $$equals: function (paramValues1, paramValues2) {
                var equal = true, self = this;
                forEach(self.$$keys(), function (key) {
                    var left = paramValues1 && paramValues1[key], right = paramValues2 && paramValues2[key];
                    if (!self[key].type.equals(left, right)) equal = false;
                });
                return equal;
            },
            $$validates: function $$validate(paramValues) {
                var keys = this.$$keys(), i, param, rawVal, normalized, encoded;
                for (i = 0; i < keys.length; i++) {
                    param = this[keys[i]];
                    rawVal = paramValues[keys[i]];
                    if ((rawVal === undefined || rawVal === null) && param.isOptional)
                        break; // There was no parameter value, but the param is optional
                    normalized = param.type.$normalize(rawVal);
                    if (!param.type.is(normalized))
                        return false; // The value was not of the correct Type, and could not be decoded to the correct Type
                    encoded = param.type.encode(normalized);
                    if (angular.isString(encoded) && !param.type.pattern.exec(encoded))
                        return false; // The value was of the correct type, but when encoded, did not match the Type's regexp
                }
                return true;
            },
            $$parent: undefined
        };

        this.ParamSet = ParamSet;
    }

    // Register as a provider so it's available to other providers
    angular.module('ui.router.util').provider('$urlMatcherFactory', $UrlMatcherFactory);
    angular.module('ui.router.util').run(['$urlMatcherFactory', function ($urlMatcherFactory) { }]);

    /**
     * @ngdoc object
     * @name ui.router.router.$urlRouterProvider
     *
     * @requires ui.router.util.$urlMatcherFactoryProvider
     * @requires $locationProvider
     *
     * @description
     * `$urlRouterProvider` has the responsibility of watching `$location`. 
     * When `$location` changes it runs through a list of rules one by one until a 
     * match is found. `$urlRouterProvider` is used behind the scenes anytime you specify 
     * a url in a state configuration. All urls are compiled into a UrlMatcher object.
     *
     * There are several methods on `$urlRouterProvider` that make it useful to use directly
     * in your module config.
     */
    $UrlRouterProvider.$inject = ['$locationProvider', '$urlMatcherFactoryProvider'];
    function $UrlRouterProvider($locationProvider, $urlMatcherFactory) {
        var rules = [], otherwise = null, interceptDeferred = false, listener;

        // Returns a string that is a prefix of all strings matching the RegExp
        function regExpPrefix(re) {
            var prefix = /^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(re.source);
            return (prefix != null) ? prefix[1].replace(/\\(.)/g, "$1") : '';
        }

        // Interpolates matched values into a String.replace()-style pattern
        function interpolate(pattern, match) {
            return pattern.replace(/\$(\$|\d{1,2})/, function (m, what) {
                return match[what === '$' ? 0 : Number(what)];
            });
        }

        /**
         * @ngdoc function
         * @name ui.router.router.$urlRouterProvider#rule
         * @methodOf ui.router.router.$urlRouterProvider
         *
         * @description
         * Defines rules that are used by `$urlRouterProvider` to find matches for
         * specific URLs.
         *
         * @example
         * <pre>
         * var app = angular.module('app', ['ui.router.router']);
         *
         * app.config(function ($urlRouterProvider) {
         *   // Here's an example of how you might allow case insensitive urls
         *   $urlRouterProvider.rule(function ($injector, $location) {
         *     var path = $location.path(),
         *         normalized = path.toLowerCase();
         *
         *     if (path !== normalized) {
         *       return normalized;
         *     }
         *   });
         * });
         * </pre>
         *
         * @param {function} rule Handler function that takes `$injector` and `$location`
         * services as arguments. You can use them to return a valid path as a string.
         *
         * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
         */
        this.rule = function (rule) {
            if (!isFunction(rule)) throw new Error("'rule' must be a function");
            rules.push(rule);
            return this;
        };

        /**
         * @ngdoc object
         * @name ui.router.router.$urlRouterProvider#otherwise
         * @methodOf ui.router.router.$urlRouterProvider
         *
         * @description
         * Defines a path that is used when an invalid route is requested.
         *
         * @example
         * <pre>
         * var app = angular.module('app', ['ui.router.router']);
         *
         * app.config(function ($urlRouterProvider) {
         *   // if the path doesn't match any of the urls you configured
         *   // otherwise will take care of routing the user to the
         *   // specified url
         *   $urlRouterProvider.otherwise('/index');
         *
         *   // Example of using function rule as param
         *   $urlRouterProvider.otherwise(function ($injector, $location) {
         *     return '/a/valid/url';
         *   });
         * });
         * </pre>
         *
         * @param {string|function} rule The url path you want to redirect to or a function 
         * rule that returns the url path. The function version is passed two params: 
         * `$injector` and `$location` services, and must return a url string.
         *
         * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
         */
        this.otherwise = function (rule) {
            if (isString(rule)) {
                var redirect = rule;
                rule = function () { return redirect; };
            }
            else if (!isFunction(rule)) throw new Error("'rule' must be a function");
            otherwise = rule;
            return this;
        };


        function handleIfMatch($injector, handler, match) {
            if (!match) return false;
            var result = $injector.invoke(handler, handler, { $match: match });
            return isDefined(result) ? result : true;
        }

        /**
         * @ngdoc function
         * @name ui.router.router.$urlRouterProvider#when
         * @methodOf ui.router.router.$urlRouterProvider
         *
         * @description
         * Registers a handler for a given url matching. 
         * 
         * If the handler is a string, it is
         * treated as a redirect, and is interpolated according to the syntax of match
         * (i.e. like `String.replace()` for `RegExp`, or like a `UrlMatcher` pattern otherwise).
         *
         * If the handler is a function, it is injectable. It gets invoked if `$location`
         * matches. You have the option of inject the match object as `$match`.
         *
         * The handler can return
         *
         * - **falsy** to indicate that the rule didn't match after all, then `$urlRouter`
         *   will continue trying to find another one that matches.
         * - **string** which is treated as a redirect and passed to `$location.url()`
         * - **void** or any **truthy** value tells `$urlRouter` that the url was handled.
         *
         * @example
         * <pre>
         * var app = angular.module('app', ['ui.router.router']);
         *
         * app.config(function ($urlRouterProvider) {
         *   $urlRouterProvider.when($state.url, function ($match, $stateParams) {
         *     if ($state.$current.navigable !== state ||
         *         !equalForKeys($match, $stateParams) {
         *      $state.transitionTo(state, $match, false);
         *     }
         *   });
         * });
         * </pre>
         *
         * @param {string|object} what The incoming path that you want to redirect.
         * @param {string|function} handler The path you want to redirect your user to.
         */
        this.when = function (what, handler) {
            var redirect, handlerIsString = isString(handler);
            if (isString(what)) what = $urlMatcherFactory.compile(what);

            if (!handlerIsString && !isFunction(handler) && !isArray(handler))
                throw new Error("invalid 'handler' in when()");

            var strategies = {
                matcher: function (what, handler) {
                    if (handlerIsString) {
                        redirect = $urlMatcherFactory.compile(handler);
                        handler = ['$match', function ($match) { return redirect.format($match); }];
                    }
                    return extend(function ($injector, $location) {
                        return handleIfMatch($injector, handler, what.exec($location.path(), $location.search()));
                    }, {
                        prefix: isString(what.prefix) ? what.prefix : ''
                    });
                },
                regex: function (what, handler) {
                    if (what.global || what.sticky) throw new Error("when() RegExp must not be global or sticky");

                    if (handlerIsString) {
                        redirect = handler;
                        handler = ['$match', function ($match) { return interpolate(redirect, $match); }];
                    }
                    return extend(function ($injector, $location) {
                        return handleIfMatch($injector, handler, what.exec($location.path()));
                    }, {
                        prefix: regExpPrefix(what)
                    });
                }
            };

            var check = { matcher: $urlMatcherFactory.isMatcher(what), regex: what instanceof RegExp };

            for (var n in check) {
                if (check[n]) return this.rule(strategies[n](what, handler));
            }

            throw new Error("invalid 'what' in when()");
        };

        /**
         * @ngdoc function
         * @name ui.router.router.$urlRouterProvider#deferIntercept
         * @methodOf ui.router.router.$urlRouterProvider
         *
         * @description
         * Disables (or enables) deferring location change interception.
         *
         * If you wish to customize the behavior of syncing the URL (for example, if you wish to
         * defer a transition but maintain the current URL), call this method at configuration time.
         * Then, at run time, call `$urlRouter.listen()` after you have configured your own
         * `$locationChangeSuccess` event handler.
         *
         * @example
         * <pre>
         * var app = angular.module('app', ['ui.router.router']);
         *
         * app.config(function ($urlRouterProvider) {
         *
         *   // Prevent $urlRouter from automatically intercepting URL changes;
         *   // this allows you to configure custom behavior in between
         *   // location changes and route synchronization:
         *   $urlRouterProvider.deferIntercept();
         *
         * }).run(function ($rootScope, $urlRouter, UserService) {
         *
         *   $rootScope.$on('$locationChangeSuccess', function(e) {
         *     // UserService is an example service for managing user state
         *     if (UserService.isLoggedIn()) return;
         *
         *     // Prevent $urlRouter's default handler from firing
         *     e.preventDefault();
         *
         *     UserService.handleLogin().then(function() {
         *       // Once the user has logged in, sync the current URL
         *       // to the router:
         *       $urlRouter.sync();
         *     });
         *   });
         *
         *   // Configures $urlRouter's listener *after* your custom listener
         *   $urlRouter.listen();
         * });
         * </pre>
         *
         * @param {boolean} defer Indicates whether to defer location change interception. Passing
                  no parameter is equivalent to `true`.
         */
        this.deferIntercept = function (defer) {
            if (defer === undefined) defer = true;
            interceptDeferred = defer;
        };

        /**
         * @ngdoc object
         * @name ui.router.router.$urlRouter
         *
         * @requires $location
         * @requires $rootScope
         * @requires $injector
         * @requires $browser
         *
         * @description
         *
         */
        this.$get = $get;
        $get.$inject = ['$location', '$rootScope', '$injector', '$browser', '$sniffer'];
        function $get($location, $rootScope, $injector, $browser, $sniffer) {

            var baseHref = $browser.baseHref(), location = $location.url(), lastPushedUrl;

            function appendBasePath(url, isHtml5, absolute) {
                if (baseHref === '/') return url;
                if (isHtml5) return baseHref.slice(0, -1) + url;
                if (absolute) return baseHref.slice(1) + url;
                return url;
            }

            // TODO: Optimize groups of rules with non-empty prefix into some sort of decision tree
            function update(evt) {
                if (evt && evt.defaultPrevented) return;
                var ignoreUpdate = lastPushedUrl && $location.url() === lastPushedUrl;
                lastPushedUrl = undefined;
                // TODO: Re-implement this in 1.0 for https://github.com/angular-ui/ui-router/issues/1573
                //if (ignoreUpdate) return true;

                function check(rule) {
                    var handled = rule($injector, $location);

                    if (!handled) return false;
                    if (isString(handled)) $location.replace().url(handled);
                    return true;
                }
                var n = rules.length, i;

                for (i = 0; i < n; i++) {
                    if (check(rules[i])) return;
                }
                // always check otherwise last to allow dynamic updates to the set of rules
                if (otherwise) check(otherwise);
            }

            function listen() {
                listener = listener || $rootScope.$on('$locationChangeSuccess', update);
                return listener;
            }

            if (!interceptDeferred) listen();

            return {
                /**
                 * @ngdoc function
                 * @name ui.router.router.$urlRouter#sync
                 * @methodOf ui.router.router.$urlRouter
                 *
                 * @description
                 * Triggers an update; the same update that happens when the address bar url changes, aka `$locationChangeSuccess`.
                 * This method is useful when you need to use `preventDefault()` on the `$locationChangeSuccess` event,
                 * perform some custom logic (route protection, auth, config, redirection, etc) and then finally proceed
                 * with the transition by calling `$urlRouter.sync()`.
                 *
                 * @example
                 * <pre>
                 * angular.module('app', ['ui.router'])
                 *   .run(function($rootScope, $urlRouter) {
                 *     $rootScope.$on('$locationChangeSuccess', function(evt) {
                 *       // Halt state change from even starting
                 *       evt.preventDefault();
                 *       // Perform custom logic
                 *       var meetsRequirement = ...
                 *       // Continue with the update and state transition if logic allows
                 *       if (meetsRequirement) $urlRouter.sync();
                 *     });
                 * });
                 * </pre>
                 */
                sync: function () {
                    update();
                },

                listen: function () {
                    return listen();
                },

                update: function (read) {
                    if (read) {
                        location = $location.url();
                        return;
                    }
                    if ($location.url() === location) return;

                    $location.url(location);
                    $location.replace();
                },

                push: function (urlMatcher, params, options) {
                    var url = urlMatcher.format(params || {});

                    // Handle the special hash param, if needed
                    if (url !== null && params && params['#']) {
                        url += '#' + params['#'];
                    }

                    $location.url(url);
                    lastPushedUrl = options && options.$$avoidResync ? $location.url() : undefined;
                    if (options && options.replace) $location.replace();
                },

                /**
                 * @ngdoc function
                 * @name ui.router.router.$urlRouter#href
                 * @methodOf ui.router.router.$urlRouter
                 *
                 * @description
                 * A URL generation method that returns the compiled URL for a given
                 * {@link ui.router.util.type:UrlMatcher `UrlMatcher`}, populated with the provided parameters.
                 *
                 * @example
                 * <pre>
                 * $bob = $urlRouter.href(new UrlMatcher("/about/:person"), {
                 *   person: "bob"
                 * });
                 * // $bob == "/about/bob";
                 * </pre>
                 *
                 * @param {UrlMatcher} urlMatcher The `UrlMatcher` object which is used as the template of the URL to generate.
                 * @param {object=} params An object of parameter values to fill the matcher's required parameters.
                 * @param {object=} options Options object. The options are:
                 *
                 * - **`absolute`** - {boolean=false},  If true will generate an absolute url, e.g. "http://www.example.com/fullurl".
                 *
                 * @returns {string} Returns the fully compiled URL, or `null` if `params` fail validation against `urlMatcher`
                 */
                href: function (urlMatcher, params, options) {
                    if (!urlMatcher.validates(params)) return null;

                    var isHtml5 = $locationProvider.html5Mode();
                    if (angular.isObject(isHtml5)) {
                        isHtml5 = isHtml5.enabled;
                    }

                    isHtml5 = isHtml5 && $sniffer.history;

                    var url = urlMatcher.format(params);
                    options = options || {};

                    if (!isHtml5 && url !== null) {
                        url = "#" + $locationProvider.hashPrefix() + url;
                    }

                    // Handle special hash param, if needed
                    if (url !== null && params && params['#']) {
                        url += '#' + params['#'];
                    }

                    url = appendBasePath(url, isHtml5, options.absolute);

                    if (!options.absolute || !url) {
                        return url;
                    }

                    var slash = (!isHtml5 && url ? '/' : ''), port = $location.port();
                    port = (port === 80 || port === 443 ? '' : ':' + port);

                    return [$location.protocol(), '://', $location.host(), port, slash, url].join('');
                }
            };
        }
    }

    angular.module('ui.router.router').provider('$urlRouter', $UrlRouterProvider);

    /**
     * @ngdoc object
     * @name ui.router.state.$stateProvider
     *
     * @requires ui.router.router.$urlRouterProvider
     * @requires ui.router.util.$urlMatcherFactoryProvider
     *
     * @description
     * The new `$stateProvider` works similar to Angular's v1 router, but it focuses purely
     * on state.
     *
     * A state corresponds to a "place" in the application in terms of the overall UI and
     * navigation. A state describes (via the controller / template / view properties) what
     * the UI looks like and does at that place.
     *
     * States often have things in common, and the primary way of factoring out these
     * commonalities in this model is via the state hierarchy, i.e. parent/child states aka
     * nested states.
     *
     * The `$stateProvider` provides interfaces to declare these states for your app.
     */
    $StateProvider.$inject = ['$urlRouterProvider', '$urlMatcherFactoryProvider'];
    function $StateProvider($urlRouterProvider, $urlMatcherFactory) {

        var root, states = {}, $state, queue = {}, abstractKey = 'abstract';

        // Builds state properties from definition passed to registerState()
        var stateBuilder = {

            // Derive parent state from a hierarchical name only if 'parent' is not explicitly defined.
            // state.children = [];
            // if (parent) parent.children.push(state);
            parent: function (state) {
                if (isDefined(state.parent) && state.parent) return findState(state.parent);
                // regex matches any valid composite state name
                // would match "contact.list" but not "contacts"
                var compositeName = /^(.+)\.[^.]+$/.exec(state.name);
                return compositeName ? findState(compositeName[1]) : root;
            },

            // inherit 'data' from parent and override by own values (if any)
            data: function (state) {
                if (state.parent && state.parent.data) {
                    state.data = state.self.data = inherit(state.parent.data, state.data);
                }
                return state.data;
            },

            // Build a URLMatcher if necessary, either via a relative or absolute URL
            url: function (state) {
                var url = state.url, config = { params: state.params || {} };

                if (isString(url)) {
                    if (url.charAt(0) == '^') return $urlMatcherFactory.compile(url.substring(1), config);
                    return (state.parent.navigable || root).url.concat(url, config);
                }

                if (!url || $urlMatcherFactory.isMatcher(url)) return url;
                throw new Error("Invalid url '" + url + "' in state '" + state + "'");
            },

            // Keep track of the closest ancestor state that has a URL (i.e. is navigable)
            navigable: function (state) {
                return state.url ? state : (state.parent ? state.parent.navigable : null);
            },

            // Own parameters for this state. state.url.params is already built at this point. Create and add non-url params
            ownParams: function (state) {
                var params = state.url && state.url.params || new $$UMFP.ParamSet();
                forEach(state.params || {}, function (config, id) {
                    if (!params[id]) params[id] = new $$UMFP.Param(id, null, config, "config");
                });
                return params;
            },

            // Derive parameters for this state and ensure they're a super-set of parent's parameters
            params: function (state) {
                var ownParams = pick(state.ownParams, state.ownParams.$$keys());
                return state.parent && state.parent.params ? extend(state.parent.params.$$new(), ownParams) : new $$UMFP.ParamSet();
            },

            // If there is no explicit multi-view configuration, make one up so we don't have
            // to handle both cases in the view directive later. Note that having an explicit
            // 'views' property will mean the default unnamed view properties are ignored. This
            // is also a good time to resolve view names to absolute names, so everything is a
            // straight lookup at link time.
            views: function (state) {
                var views = {};

                forEach(isDefined(state.views) ? state.views : { '': state }, function (view, name) {
                    if (name.indexOf('@') < 0) name += '@' + state.parent.name;
                    view.resolveAs = view.resolveAs || state.resolveAs || '$resolve';
                    views[name] = view;
                });
                return views;
            },

            // Keep a full path from the root down to this state as this is needed for state activation.
            path: function (state) {
                return state.parent ? state.parent.path.concat(state) : []; // exclude root from path
            },

            // Speed up $state.contains() as it's used a lot
            includes: function (state) {
                var includes = state.parent ? extend({}, state.parent.includes) : {};
                includes[state.name] = true;
                return includes;
            },

            $delegates: {}
        };

        function isRelative(stateName) {
            return stateName.indexOf(".") === 0 || stateName.indexOf("^") === 0;
        }

        function findState(stateOrName, base) {
            if (!stateOrName) return undefined;

            var isStr = isString(stateOrName),
                name = isStr ? stateOrName : stateOrName.name,
                path = isRelative(name);

            if (path) {
                if (!base) throw new Error("No reference point given for path '" + name + "'");
                base = findState(base);

                var rel = name.split("."), i = 0, pathLength = rel.length, current = base;

                for (; i < pathLength; i++) {
                    if (rel[i] === "" && i === 0) {
                        current = base;
                        continue;
                    }
                    if (rel[i] === "^") {
                        if (!current.parent) throw new Error("Path '" + name + "' not valid for state '" + base.name + "'");
                        current = current.parent;
                        continue;
                    }
                    break;
                }
                rel = rel.slice(i).join(".");
                name = current.name + (current.name && rel ? "." : "") + rel;
            }
            var state = states[name];

            if (state && (isStr || (!isStr && (state === stateOrName || state.self === stateOrName)))) {
                return state;
            }
            return undefined;
        }

        function queueState(parentName, state) {
            if (!queue[parentName]) {
                queue[parentName] = [];
            }
            queue[parentName].push(state);
        }

        function flushQueuedChildren(parentName) {
            var queued = queue[parentName] || [];
            while (queued.length) {
                registerState(queued.shift());
            }
        }

        function registerState(state) {
            // Wrap a new object around the state so we can store our private details easily.
            state = inherit(state, {
                self: state,
                resolve: state.resolve || {},
                toString: function () { return this.name; }
            });

            var name = state.name;
            if (!isString(name) || name.indexOf('@') >= 0) throw new Error("State must have a valid name");
            if (states.hasOwnProperty(name)) throw new Error("State '" + name + "' is already defined");

            // Get parent name
            var parentName = (name.indexOf('.') !== -1) ? name.substring(0, name.lastIndexOf('.'))
                : (isString(state.parent)) ? state.parent
                : (isObject(state.parent) && isString(state.parent.name)) ? state.parent.name
                : '';

            // If parent is not registered yet, add state to queue and register later
            if (parentName && !states[parentName]) {
                return queueState(parentName, state.self);
            }

            for (var key in stateBuilder) {
                if (isFunction(stateBuilder[key])) state[key] = stateBuilder[key](state, stateBuilder.$delegates[key]);
            }
            states[name] = state;

            // Register the state in the global state list and with $urlRouter if necessary.
            if (!state[abstractKey] && state.url) {
                $urlRouterProvider.when(state.url, ['$match', '$stateParams', function ($match, $stateParams) {
                    if ($state.$current.navigable != state || !equalForKeys($match, $stateParams)) {
                        $state.transitionTo(state, $match, { inherit: true, location: false });
                    }
                }]);
            }

            // Register any queued children
            flushQueuedChildren(name);

            return state;
        }

        // Checks text to see if it looks like a glob.
        function isGlob(text) {
            return text.indexOf('*') > -1;
        }

        // Returns true if glob matches current $state name.
        function doesStateMatchGlob(glob) {
            var globSegments = glob.split('.'),
                segments = $state.$current.name.split('.');

            //match single stars
            for (var i = 0, l = globSegments.length; i < l; i++) {
                if (globSegments[i] === '*') {
                    segments[i] = '*';
                }
            }

            //match greedy starts
            if (globSegments[0] === '**') {
                segments = segments.slice(indexOf(segments, globSegments[1]));
                segments.unshift('**');
            }
            //match greedy ends
            if (globSegments[globSegments.length - 1] === '**') {
                segments.splice(indexOf(segments, globSegments[globSegments.length - 2]) + 1, Number.MAX_VALUE);
                segments.push('**');
            }

            if (globSegments.length != segments.length) {
                return false;
            }

            return segments.join('') === globSegments.join('');
        }


        // Implicit root state that is always active
        root = registerState({
            name: '',
            url: '^',
            views: null,
            'abstract': true
        });
        root.navigable = null;


        /**
         * @ngdoc function
         * @name ui.router.state.$stateProvider#decorator
         * @methodOf ui.router.state.$stateProvider
         *
         * @description
         * Allows you to extend (carefully) or override (at your own peril) the 
         * `stateBuilder` object used internally by `$stateProvider`. This can be used 
         * to add custom functionality to ui-router, for example inferring templateUrl 
         * based on the state name.
         *
         * When passing only a name, it returns the current (original or decorated) builder
         * function that matches `name`.
         *
         * The builder functions that can be decorated are listed below. Though not all
         * necessarily have a good use case for decoration, that is up to you to decide.
         *
         * In addition, users can attach custom decorators, which will generate new 
         * properties within the state's internal definition. There is currently no clear 
         * use-case for this beyond accessing internal states (i.e. $state.$current), 
         * however, expect this to become increasingly relevant as we introduce additional 
         * meta-programming features.
         *
         * **Warning**: Decorators should not be interdependent because the order of 
         * execution of the builder functions in non-deterministic. Builder functions 
         * should only be dependent on the state definition object and super function.
         *
         *
         * Existing builder functions and current return values:
         *
         * - **parent** `{object}` - returns the parent state object.
         * - **data** `{object}` - returns state data, including any inherited data that is not
         *   overridden by own values (if any).
         * - **url** `{object}` - returns a {@link ui.router.util.type:UrlMatcher UrlMatcher}
         *   or `null`.
         * - **navigable** `{object}` - returns closest ancestor state that has a URL (aka is 
         *   navigable).
         * - **params** `{object}` - returns an array of state params that are ensured to 
         *   be a super-set of parent's params.
         * - **views** `{object}` - returns a views object where each key is an absolute view 
         *   name (i.e. "viewName@stateName") and each value is the config object 
         *   (template, controller) for the view. Even when you don't use the views object 
         *   explicitly on a state config, one is still created for you internally.
         *   So by decorating this builder function you have access to decorating template 
         *   and controller properties.
         * - **ownParams** `{object}` - returns an array of params that belong to the state, 
         *   not including any params defined by ancestor states.
         * - **path** `{string}` - returns the full path from the root down to this state. 
         *   Needed for state activation.
         * - **includes** `{object}` - returns an object that includes every state that 
         *   would pass a `$state.includes()` test.
         *
         * @example
         * <pre>
         * // Override the internal 'views' builder with a function that takes the state
         * // definition, and a reference to the internal function being overridden:
         * $stateProvider.decorator('views', function (state, parent) {
         *   var result = {},
         *       views = parent(state);
         *
         *   angular.forEach(views, function (config, name) {
         *     var autoName = (state.name + '.' + name).replace('.', '/');
         *     config.templateUrl = config.templateUrl || '/partials/' + autoName + '.html';
         *     result[name] = config;
         *   });
         *   return result;
         * });
         *
         * $stateProvider.state('home', {
         *   views: {
         *     'contact.list': { controller: 'ListController' },
         *     'contact.item': { controller: 'ItemController' }
         *   }
         * });
         *
         * // ...
         *
         * $state.go('home');
         * // Auto-populates list and item views with /partials/home/contact/list.html,
         * // and /partials/home/contact/item.html, respectively.
         * </pre>
         *
         * @param {string} name The name of the builder function to decorate. 
         * @param {object} func A function that is responsible for decorating the original 
         * builder function. The function receives two parameters:
         *
         *   - `{object}` - state - The state config object.
         *   - `{object}` - super - The original builder function.
         *
         * @return {object} $stateProvider - $stateProvider instance
         */
        this.decorator = decorator;
        function decorator(name, func) {
            /*jshint validthis: true */
            if (isString(name) && !isDefined(func)) {
                return stateBuilder[name];
            }
            if (!isFunction(func) || !isString(name)) {
                return this;
            }
            if (stateBuilder[name] && !stateBuilder.$delegates[name]) {
                stateBuilder.$delegates[name] = stateBuilder[name];
            }
            stateBuilder[name] = func;
            return this;
        }

        /**
         * @ngdoc function
         * @name ui.router.state.$stateProvider#state
         * @methodOf ui.router.state.$stateProvider
         *
         * @description
         * Registers a state configuration under a given state name. The stateConfig object
         * has the following acceptable properties.
         *
         * @param {string} name A unique state name, e.g. "home", "about", "contacts".
         * To create a parent/child state use a dot, e.g. "about.sales", "home.newest".
         * @param {object} stateConfig State configuration object.
         * @param {string|function=} stateConfig.template
         * <a id='template'></a>
         *   html template as a string or a function that returns
         *   an html template as a string which should be used by the uiView directives. This property 
         *   takes precedence over templateUrl.
         *   
         *   If `template` is a function, it will be called with the following parameters:
         *
         *   - {array.&lt;object&gt;} - state parameters extracted from the current $location.path() by
         *     applying the current state
         *
         * <pre>template:
         *   "<h1>inline template definition</h1>" +
         *   "<div ui-view></div>"</pre>
         * <pre>template: function(params) {
         *       return "<h1>generated template</h1>"; }</pre>
         * </div>
         *
         * @param {string|function=} stateConfig.templateUrl
         * <a id='templateUrl'></a>
         *
         *   path or function that returns a path to an html
         *   template that should be used by uiView.
         *   
         *   If `templateUrl` is a function, it will be called with the following parameters:
         *
         *   - {array.&lt;object&gt;} - state parameters extracted from the current $location.path() by 
         *     applying the current state
         *
         * <pre>templateUrl: "home.html"</pre>
         * <pre>templateUrl: function(params) {
         *     return myTemplates[params.pageId]; }</pre>
         *
         * @param {function=} stateConfig.templateProvider
         * <a id='templateProvider'></a>
         *    Provider function that returns HTML content string.
         * <pre> templateProvider:
         *       function(MyTemplateService, params) {
         *         return MyTemplateService.getTemplate(params.pageId);
         *       }</pre>
         *
         * @param {string|function=} stateConfig.controller
         * <a id='controller'></a>
         *
         *  Controller fn that should be associated with newly
         *   related scope or the name of a registered controller if passed as a string.
         *   Optionally, the ControllerAs may be declared here.
         * <pre>controller: "MyRegisteredController"</pre>
         * <pre>controller:
         *     "MyRegisteredController as fooCtrl"}</pre>
         * <pre>controller: function($scope, MyService) {
         *     $scope.data = MyService.getData(); }</pre>
         *
         * @param {function=} stateConfig.controllerProvider
         * <a id='controllerProvider'></a>
         *
         * Injectable provider function that returns the actual controller or string.
         * <pre>controllerProvider:
         *   function(MyResolveData) {
         *     if (MyResolveData.foo)
         *       return "FooCtrl"
         *     else if (MyResolveData.bar)
         *       return "BarCtrl";
         *     else return function($scope) {
         *       $scope.baz = "Qux";
         *     }
         *   }</pre>
         *
         * @param {string=} stateConfig.controllerAs
         * <a id='controllerAs'></a>
         * 
         * A controller alias name. If present the controller will be
         *   published to scope under the controllerAs name.
         * <pre>controllerAs: "myCtrl"</pre>
         *
         * @param {string|object=} stateConfig.parent
         * <a id='parent'></a>
         * Optionally specifies the parent state of this state.
         *
         * <pre>parent: 'parentState'</pre>
         * <pre>parent: parentState // JS variable</pre>
         *
         * @param {object=} stateConfig.resolve
         * <a id='resolve'></a>
         *
         * An optional map&lt;string, function&gt; of dependencies which
         *   should be injected into the controller. If any of these dependencies are promises, 
         *   the router will wait for them all to be resolved before the controller is instantiated.
         *   If all the promises are resolved successfully, the $stateChangeSuccess event is fired
         *   and the values of the resolved promises are injected into any controllers that reference them.
         *   If any  of the promises are rejected the $stateChangeError event is fired.
         *
         *   The map object is:
         *   
         *   - key - {string}: name of dependency to be injected into controller
         *   - factory - {string|function}: If string then it is alias for service. Otherwise if function, 
         *     it is injected and return value it treated as dependency. If result is a promise, it is 
         *     resolved before its value is injected into controller.
         *
         * <pre>resolve: {
         *     myResolve1:
         *       function($http, $stateParams) {
         *         return $http.get("/api/foos/"+stateParams.fooID);
         *       }
         *     }</pre>
         *
         * @param {string=} stateConfig.url
         * <a id='url'></a>
         *
         *   A url fragment with optional parameters. When a state is navigated or
         *   transitioned to, the `$stateParams` service will be populated with any 
         *   parameters that were passed.
         *
         *   (See {@link ui.router.util.type:UrlMatcher UrlMatcher} `UrlMatcher`} for
         *   more details on acceptable patterns )
         *
         * examples:
         * <pre>url: "/home"
         * url: "/users/:userid"
         * url: "/books/{bookid:[a-zA-Z_-]}"
         * url: "/books/{categoryid:int}"
         * url: "/books/{publishername:string}/{categoryid:int}"
         * url: "/messages?before&after"
         * url: "/messages?{before:date}&{after:date}"
         * url: "/messages/:mailboxid?{before:date}&{after:date}"
         * </pre>
         *
         * @param {object=} stateConfig.views
         * <a id='views'></a>
         * an optional map&lt;string, object&gt; which defined multiple views, or targets views
         * manually/explicitly.
         *
         * Examples:
         *
         * Targets three named `ui-view`s in the parent state's template
         * <pre>views: {
         *     header: {
         *       controller: "headerCtrl",
         *       templateUrl: "header.html"
         *     }, body: {
         *       controller: "bodyCtrl",
         *       templateUrl: "body.html"
         *     }, footer: {
         *       controller: "footCtrl",
         *       templateUrl: "footer.html"
         *     }
         *   }</pre>
         *
         * Targets named `ui-view="header"` from grandparent state 'top''s template, and named `ui-view="body" from parent state's template.
         * <pre>views: {
         *     'header@top': {
         *       controller: "msgHeaderCtrl",
         *       templateUrl: "msgHeader.html"
         *     }, 'body': {
         *       controller: "messagesCtrl",
         *       templateUrl: "messages.html"
         *     }
         *   }</pre>
         *
         * @param {boolean=} [stateConfig.abstract=false]
         * <a id='abstract'></a>
         * An abstract state will never be directly activated,
         *   but can provide inherited properties to its common children states.
         * <pre>abstract: true</pre>
         *
         * @param {function=} stateConfig.onEnter
         * <a id='onEnter'></a>
         *
         * Callback function for when a state is entered. Good way
         *   to trigger an action or dispatch an event, such as opening a dialog.
         * If minifying your scripts, make sure to explicitly annotate this function,
         * because it won't be automatically annotated by your build tools.
         *
         * <pre>onEnter: function(MyService, $stateParams) {
         *     MyService.foo($stateParams.myParam);
         * }</pre>
         *
         * @param {function=} stateConfig.onExit
         * <a id='onExit'></a>
         *
         * Callback function for when a state is exited. Good way to
         *   trigger an action or dispatch an event, such as opening a dialog.
         * If minifying your scripts, make sure to explicitly annotate this function,
         * because it won't be automatically annotated by your build tools.
         *
         * <pre>onExit: function(MyService, $stateParams) {
         *     MyService.cleanup($stateParams.myParam);
         * }</pre>
         *
         * @param {boolean=} [stateConfig.reloadOnSearch=true]
         * <a id='reloadOnSearch'></a>
         *
         * If `false`, will not retrigger the same state
         *   just because a search/query parameter has changed (via $location.search() or $location.hash()). 
         *   Useful for when you'd like to modify $location.search() without triggering a reload.
         * <pre>reloadOnSearch: false</pre>
         *
         * @param {object=} stateConfig.data
         * <a id='data'></a>
         *
         * Arbitrary data object, useful for custom configuration.  The parent state's `data` is
         *   prototypally inherited.  In other words, adding a data property to a state adds it to
         *   the entire subtree via prototypal inheritance.
         *
         * <pre>data: {
         *     requiredRole: 'foo'
         * } </pre>
         *
         * @param {object=} stateConfig.params
         * <a id='params'></a>
         *
         * A map which optionally configures parameters declared in the `url`, or
         *   defines additional non-url parameters.  For each parameter being
         *   configured, add a configuration object keyed to the name of the parameter.
         *
         *   Each parameter configuration object may contain the following properties:
         *
         *   - ** value ** - {object|function=}: specifies the default value for this
         *     parameter.  This implicitly sets this parameter as optional.
         *
         *     When UI-Router routes to a state and no value is
         *     specified for this parameter in the URL or transition, the
         *     default value will be used instead.  If `value` is a function,
         *     it will be injected and invoked, and the return value used.
         *
         *     *Note*: `undefined` is treated as "no default value" while `null`
         *     is treated as "the default value is `null`".
         *
         *     *Shorthand*: If you only need to configure the default value of the
         *     parameter, you may use a shorthand syntax.   In the **`params`**
         *     map, instead mapping the param name to a full parameter configuration
         *     object, simply set map it to the default parameter value, e.g.:
         *
         * <pre>// define a parameter's default value
         * params: {
         *     param1: { value: "defaultValue" }
         * }
         * // shorthand default values
         * params: {
         *     param1: "defaultValue",
         *     param2: "param2Default"
         * }</pre>
         *
         *   - ** array ** - {boolean=}: *(default: false)* If true, the param value will be
         *     treated as an array of values.  If you specified a Type, the value will be
         *     treated as an array of the specified Type.  Note: query parameter values
         *     default to a special `"auto"` mode.
         *
         *     For query parameters in `"auto"` mode, if multiple  values for a single parameter
         *     are present in the URL (e.g.: `/foo?bar=1&bar=2&bar=3`) then the values
         *     are mapped to an array (e.g.: `{ foo: [ '1', '2', '3' ] }`).  However, if
         *     only one value is present (e.g.: `/foo?bar=1`) then the value is treated as single
         *     value (e.g.: `{ foo: '1' }`).
         *
         * <pre>params: {
         *     param1: { array: true }
         * }</pre>
         *
         *   - ** squash ** - {bool|string=}: `squash` configures how a default parameter value is represented in the URL when
         *     the current parameter value is the same as the default value. If `squash` is not set, it uses the
         *     configured default squash policy.
         *     (See {@link ui.router.util.$urlMatcherFactory#methods_defaultSquashPolicy `defaultSquashPolicy()`})
         *
         *   There are three squash settings:
         *
         *     - false: The parameter's default value is not squashed.  It is encoded and included in the URL
         *     - true: The parameter's default value is omitted from the URL.  If the parameter is preceeded and followed
         *       by slashes in the state's `url` declaration, then one of those slashes are omitted.
         *       This can allow for cleaner looking URLs.
         *     - `"<arbitrary string>"`: The parameter's default value is replaced with an arbitrary placeholder of  your choice.
         *
         * <pre>params: {
         *     param1: {
         *       value: "defaultId",
         *       squash: true
         * } }
         * // squash "defaultValue" to "~"
         * params: {
         *     param1: {
         *       value: "defaultValue",
         *       squash: "~"
         * } }
         * </pre>
         *
         *
         * @example
         * <pre>
         * // Some state name examples
         *
         * // stateName can be a single top-level name (must be unique).
         * $stateProvider.state("home", {});
         *
         * // Or it can be a nested state name. This state is a child of the
         * // above "home" state.
         * $stateProvider.state("home.newest", {});
         *
         * // Nest states as deeply as needed.
         * $stateProvider.state("home.newest.abc.xyz.inception", {});
         *
         * // state() returns $stateProvider, so you can chain state declarations.
         * $stateProvider
         *   .state("home", {})
         *   .state("about", {})
         *   .state("contacts", {});
         * </pre>
         *
         */
        this.state = state;
        function state(name, definition) {
            /*jshint validthis: true */
            if (isObject(name)) definition = name;
            else definition.name = name;
            registerState(definition);
            return this;
        }

        /**
         * @ngdoc object
         * @name ui.router.state.$state
         *
         * @requires $rootScope
         * @requires $q
         * @requires ui.router.state.$view
         * @requires $injector
         * @requires ui.router.util.$resolve
         * @requires ui.router.state.$stateParams
         * @requires ui.router.router.$urlRouter
         *
         * @property {object} params A param object, e.g. {sectionId: section.id)}, that 
         * you'd like to test against the current active state.
         * @property {object} current A reference to the state's config object. However 
         * you passed it in. Useful for accessing custom data.
         * @property {object} transition Currently pending transition. A promise that'll 
         * resolve or reject.
         *
         * @description
         * `$state` service is responsible for representing states as well as transitioning
         * between them. It also provides interfaces to ask for current state or even states
         * you're coming from.
         */
        this.$get = $get;
        $get.$inject = ['$rootScope', '$q', '$view', '$injector', '$resolve', '$stateParams', '$urlRouter', '$location', '$urlMatcherFactory'];
        function $get($rootScope, $q, $view, $injector, $resolve, $stateParams, $urlRouter, $location, $urlMatcherFactory) {

            var TransitionSupersededError = new Error('transition superseded');

            var TransitionSuperseded = silenceUncaughtInPromise($q.reject(TransitionSupersededError));
            var TransitionPrevented = silenceUncaughtInPromise($q.reject(new Error('transition prevented')));
            var TransitionAborted = silenceUncaughtInPromise($q.reject(new Error('transition aborted')));
            var TransitionFailed = silenceUncaughtInPromise($q.reject(new Error('transition failed')));

            // Handles the case where a state which is the target of a transition is not found, and the user
            // can optionally retry or defer the transition
            function handleRedirect(redirect, state, params, options) {
                /**
                 * @ngdoc event
                 * @name ui.router.state.$state#$stateNotFound
                 * @eventOf ui.router.state.$state
                 * @eventType broadcast on root scope
                 * @description
                 * Fired when a requested state **cannot be found** using the provided state name during transition.
                 * The event is broadcast allowing any handlers a single chance to deal with the error (usually by
                 * lazy-loading the unfound state). A special `unfoundState` object is passed to the listener handler,
                 * you can see its three properties in the example. You can use `event.preventDefault()` to abort the
                 * transition and the promise returned from `go` will be rejected with a `'transition aborted'` value.
                 *
                 * @param {Object} event Event object.
                 * @param {Object} unfoundState Unfound State information. Contains: `to, toParams, options` properties.
                 * @param {State} fromState Current state object.
                 * @param {Object} fromParams Current state params.
                 *
                 * @example
                 *
                 * <pre>
                 * // somewhere, assume lazy.state has not been defined
                 * $state.go("lazy.state", {a:1, b:2}, {inherit:false});
                 *
                 * // somewhere else
                 * $scope.$on('$stateNotFound',
                 * function(event, unfoundState, fromState, fromParams){
                 *     console.log(unfoundState.to); // "lazy.state"
                 *     console.log(unfoundState.toParams); // {a:1, b:2}
                 *     console.log(unfoundState.options); // {inherit:false} + default options
                 * })
                 * </pre>
                 */
                var evt = $rootScope.$broadcast('$stateNotFound', redirect, state, params);

                if (evt.defaultPrevented) {
                    $urlRouter.update();
                    return TransitionAborted;
                }

                if (!evt.retry) {
                    return null;
                }

                // Allow the handler to return a promise to defer state lookup retry
                if (options.$retry) {
                    $urlRouter.update();
                    return TransitionFailed;
                }
                var retryTransition = $state.transition = $q.when(evt.retry);

                retryTransition.then(function () {
                    if (retryTransition !== $state.transition) {
                        $rootScope.$broadcast('$stateChangeCancel', redirect.to, redirect.toParams, state, params);
                        return TransitionSuperseded;
                    }
                    redirect.options.$retry = true;
                    return $state.transitionTo(redirect.to, redirect.toParams, redirect.options);
                }, function () {
                    return TransitionAborted;
                });
                $urlRouter.update();

                return retryTransition;
            }

            root.locals = { resolve: null, globals: { $stateParams: {} } };

            $state = {
                params: {},
                current: root.self,
                $current: root,
                transition: null
            };

            /**
             * @ngdoc function
             * @name ui.router.state.$state#reload
             * @methodOf ui.router.state.$state
             *
             * @description
             * A method that force reloads the current state. All resolves are re-resolved,
             * controllers reinstantiated, and events re-fired.
             *
             * @example
             * <pre>
             * var app angular.module('app', ['ui.router']);
             *
             * app.controller('ctrl', function ($scope, $state) {
             *   $scope.reload = function(){
             *     $state.reload();
             *   }
             * });
             * </pre>
             *
             * `reload()` is just an alias for:
             * <pre>
             * $state.transitionTo($state.current, $stateParams, { 
             *   reload: true, inherit: false, notify: true
             * });
             * </pre>
             *
             * @param {string=|object=} state - A state name or a state object, which is the root of the resolves to be re-resolved.
             * @example
             * <pre>
             * //assuming app application consists of 3 states: 'contacts', 'contacts.detail', 'contacts.detail.item' 
             * //and current state is 'contacts.detail.item'
             * var app angular.module('app', ['ui.router']);
             *
             * app.controller('ctrl', function ($scope, $state) {
             *   $scope.reload = function(){
             *     //will reload 'contact.detail' and 'contact.detail.item' states
             *     $state.reload('contact.detail');
             *   }
             * });
             * </pre>
             *
             * `reload()` is just an alias for:
             * <pre>
             * $state.transitionTo($state.current, $stateParams, { 
             *   reload: true, inherit: false, notify: true
             * });
             * </pre>
        
             * @returns {promise} A promise representing the state of the new transition. See
             * {@link ui.router.state.$state#methods_go $state.go}.
             */
            $state.reload = function reload(state) {
                return $state.transitionTo($state.current, $stateParams, { reload: state || true, inherit: false, notify: true });
            };

            /**
             * @ngdoc function
             * @name ui.router.state.$state#go
             * @methodOf ui.router.state.$state
             *
             * @description
             * Convenience method for transitioning to a new state. `$state.go` calls 
             * `$state.transitionTo` internally but automatically sets options to 
             * `{ location: true, inherit: true, relative: $state.$current, notify: true }`. 
             * This allows you to easily use an absolute or relative to path and specify 
             * only the parameters you'd like to update (while letting unspecified parameters 
             * inherit from the currently active ancestor states).
             *
             * @example
             * <pre>
             * var app = angular.module('app', ['ui.router']);
             *
             * app.controller('ctrl', function ($scope, $state) {
             *   $scope.changeState = function () {
             *     $state.go('contact.detail');
             *   };
             * });
             * </pre>
             * <img src='../ngdoc_assets/StateGoExamples.png'/>
             *
             * @param {string} to Absolute state name or relative state path. Some examples:
             *
             * - `$state.go('contact.detail')` - will go to the `contact.detail` state
             * - `$state.go('^')` - will go to a parent state
             * - `$state.go('^.sibling')` - will go to a sibling state
             * - `$state.go('.child.grandchild')` - will go to grandchild state
             *
             * @param {object=} params A map of the parameters that will be sent to the state, 
             * will populate $stateParams. Any parameters that are not specified will be inherited from currently 
             * defined parameters. Only parameters specified in the state definition can be overridden, new 
             * parameters will be ignored. This allows, for example, going to a sibling state that shares parameters
             * specified in a parent state. Parameter inheritance only works between common ancestor states, I.e.
             * transitioning to a sibling will get you the parameters for all parents, transitioning to a child
             * will get you all current parameters, etc.
             * @param {object=} options Options object. The options are:
             *
             * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false`
             *    will not. If string, must be `"replace"`, which will update url and also replace last history record.
             * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url.
             * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'), 
             *    defines which state to be relative from.
             * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events.
             * - **`reload`** (v0.2.5) - {boolean=false|string|object}, If `true` will force transition even if no state or params
             *    have changed.  It will reload the resolves and views of the current state and parent states.
             *    If `reload` is a string (or state object), the state object is fetched (by name, or object reference); and \
             *    the transition reloads the resolves and views for that matched state, and all its children states.
             *
             * @returns {promise} A promise representing the state of the new transition.
             *
             * Possible success values:
             *
             * - $state.current
             *
             * <br/>Possible rejection values:
             *
             * - 'transition superseded' - when a newer transition has been started after this one
             * - 'transition prevented' - when `event.preventDefault()` has been called in a `$stateChangeStart` listener
             * - 'transition aborted' - when `event.preventDefault()` has been called in a `$stateNotFound` listener or
             *   when a `$stateNotFound` `event.retry` promise errors.
             * - 'transition failed' - when a state has been unsuccessfully found after 2 tries.
             * - *resolve error* - when an error has occurred with a `resolve`
             *
             */
            $state.go = function go(to, params, options) {
                return $state.transitionTo(to, params, extend({ inherit: true, relative: $state.$current }, options));
            };

            /**
             * @ngdoc function
             * @name ui.router.state.$state#transitionTo
             * @methodOf ui.router.state.$state
             *
             * @description
             * Low-level method for transitioning to a new state. {@link ui.router.state.$state#methods_go $state.go}
             * uses `transitionTo` internally. `$state.go` is recommended in most situations.
             *
             * @example
             * <pre>
             * var app = angular.module('app', ['ui.router']);
             *
             * app.controller('ctrl', function ($scope, $state) {
             *   $scope.changeState = function () {
             *     $state.transitionTo('contact.detail');
             *   };
             * });
             * </pre>
             *
             * @param {string} to State name.
             * @param {object=} toParams A map of the parameters that will be sent to the state,
             * will populate $stateParams.
             * @param {object=} options Options object. The options are:
             *
             * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false`
             *    will not. If string, must be `"replace"`, which will update url and also replace last history record.
             * - **`inherit`** - {boolean=false}, If `true` will inherit url parameters from current url.
             * - **`relative`** - {object=}, When transitioning with relative path (e.g '^'), 
             *    defines which state to be relative from.
             * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events.
             * - **`reload`** (v0.2.5) - {boolean=false|string=|object=}, If `true` will force transition even if the state or params 
             *    have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd
             *    use this when you want to force a reload when *everything* is the same, including search params.
             *    if String, then will reload the state with the name given in reload, and any children.
             *    if Object, then a stateObj is expected, will reload the state found in stateObj, and any children.
             *
             * @returns {promise} A promise representing the state of the new transition. See
             * {@link ui.router.state.$state#methods_go $state.go}.
             */
            $state.transitionTo = function transitionTo(to, toParams, options) {
                toParams = toParams || {};
                options = extend({
                    location: true, inherit: false, relative: null, notify: true, reload: false, $retry: false
                }, options || {});

                var from = $state.$current, fromParams = $state.params, fromPath = from.path;
                var evt, toState = findState(to, options.relative);

                // Store the hash param for later (since it will be stripped out by various methods)
                var hash = toParams['#'];

                if (!isDefined(toState)) {
                    var redirect = { to: to, toParams: toParams, options: options };
                    var redirectResult = handleRedirect(redirect, from.self, fromParams, options);

                    if (redirectResult) {
                        return redirectResult;
                    }

                    // Always retry once if the $stateNotFound was not prevented
                    // (handles either redirect changed or state lazy-definition)
                    to = redirect.to;
                    toParams = redirect.toParams;
                    options = redirect.options;
                    toState = findState(to, options.relative);

                    if (!isDefined(toState)) {
                        if (!options.relative) throw new Error("No such state '" + to + "'");
                        throw new Error("Could not resolve '" + to + "' from state '" + options.relative + "'");
                    }
                }
                if (toState[abstractKey]) throw new Error("Cannot transition to abstract state '" + to + "'");
                if (options.inherit) toParams = inheritParams($stateParams, toParams || {}, $state.$current, toState);
                if (!toState.params.$$validates(toParams)) return TransitionFailed;

                toParams = toState.params.$$values(toParams);
                to = toState;

                var toPath = to.path;

                // Starting from the root of the path, keep all levels that haven't changed
                var keep = 0, state = toPath[keep], locals = root.locals, toLocals = [];

                if (!options.reload) {
                    while (state && state === fromPath[keep] && state.ownParams.$$equals(toParams, fromParams)) {
                        locals = toLocals[keep] = state.locals;
                        keep++;
                        state = toPath[keep];
                    }
                } else if (isString(options.reload) || isObject(options.reload)) {
                    if (isObject(options.reload) && !options.reload.name) {
                        throw new Error('Invalid reload state object');
                    }

                    var reloadState = options.reload === true ? fromPath[0] : findState(options.reload);
                    if (options.reload && !reloadState) {
                        throw new Error("No such reload state '" + (isString(options.reload) ? options.reload : options.reload.name) + "'");
                    }

                    while (state && state === fromPath[keep] && state !== reloadState) {
                        locals = toLocals[keep] = state.locals;
                        keep++;
                        state = toPath[keep];
                    }
                }

                // If we're going to the same state and all locals are kept, we've got nothing to do.
                // But clear 'transition', as we still want to cancel any other pending transitions.
                // TODO: We may not want to bump 'transition' if we're called from a location change
                // that we've initiated ourselves, because we might accidentally abort a legitimate
                // transition initiated from code?
                if (shouldSkipReload(to, toParams, from, fromParams, locals, options)) {
                    if (hash) toParams['#'] = hash;
                    $state.params = toParams;
                    copy($state.params, $stateParams);
                    copy(filterByKeys(to.params.$$keys(), $stateParams), to.locals.globals.$stateParams);
                    if (options.location && to.navigable && to.navigable.url) {
                        $urlRouter.push(to.navigable.url, toParams, {
                            $$avoidResync: true, replace: options.location === 'replace'
                        });
                        $urlRouter.update(true);
                    }
                    $state.transition = null;
                    return $q.when($state.current);
                }

                // Filter parameters before we pass them to event handlers etc.
                toParams = filterByKeys(to.params.$$keys(), toParams || {});

                // Re-add the saved hash before we start returning things or broadcasting $stateChangeStart
                if (hash) toParams['#'] = hash;

                // Broadcast start event and cancel the transition if requested
                if (options.notify) {
                    /**
                     * @ngdoc event
                     * @name ui.router.state.$state#$stateChangeStart
                     * @eventOf ui.router.state.$state
                     * @eventType broadcast on root scope
                     * @description
                     * Fired when the state transition **begins**. You can use `event.preventDefault()`
                     * to prevent the transition from happening and then the transition promise will be
                     * rejected with a `'transition prevented'` value.
                     *
                     * @param {Object} event Event object.
                     * @param {State} toState The state being transitioned to.
                     * @param {Object} toParams The params supplied to the `toState`.
                     * @param {State} fromState The current state, pre-transition.
                     * @param {Object} fromParams The params supplied to the `fromState`.
                     *
                     * @example
                     *
                     * <pre>
                     * $rootScope.$on('$stateChangeStart',
                     * function(event, toState, toParams, fromState, fromParams){
                     *     event.preventDefault();
                     *     // transitionTo() promise will be rejected with
                     *     // a 'transition prevented' error
                     * })
                     * </pre>
                     */
                    if ($rootScope.$broadcast('$stateChangeStart', to.self, toParams, from.self, fromParams, options).defaultPrevented) {
                        $rootScope.$broadcast('$stateChangeCancel', to.self, toParams, from.self, fromParams);
                        //Don't update and resync url if there's been a new transition started. see issue #2238, #600
                        if ($state.transition == null) $urlRouter.update();
                        return TransitionPrevented;
                    }
                }

                // Resolve locals for the remaining states, but don't update any global state just
                // yet -- if anything fails to resolve the current state needs to remain untouched.
                // We also set up an inheritance chain for the locals here. This allows the view directive
                // to quickly look up the correct definition for each view in the current state. Even
                // though we create the locals object itself outside resolveState(), it is initially
                // empty and gets filled asynchronously. We need to keep track of the promise for the
                // (fully resolved) current locals, and pass this down the chain.
                var resolved = $q.when(locals);

                for (var l = keep; l < toPath.length; l++, state = toPath[l]) {
                    locals = toLocals[l] = inherit(locals);
                    resolved = resolveState(state, toParams, state === to, resolved, locals, options);
                }

                // Once everything is resolved, we are ready to perform the actual transition
                // and return a promise for the new state. We also keep track of what the
                // current promise is, so that we can detect overlapping transitions and
                // keep only the outcome of the last transition.
                var transition = $state.transition = resolved.then(function () {
                    var l, entering, exiting;

                    if ($state.transition !== transition) {
                        $rootScope.$broadcast('$stateChangeCancel', to.self, toParams, from.self, fromParams);
                        return TransitionSuperseded;
                    }

                    // Exit 'from' states not kept
                    for (l = fromPath.length - 1; l >= keep; l--) {
                        exiting = fromPath[l];
                        if (exiting.self.onExit) {
                            $injector.invoke(exiting.self.onExit, exiting.self, exiting.locals.globals);
                        }
                        exiting.locals = null;
                    }

                    // Enter 'to' states not kept
                    for (l = keep; l < toPath.length; l++) {
                        entering = toPath[l];
                        entering.locals = toLocals[l];
                        if (entering.self.onEnter) {
                            $injector.invoke(entering.self.onEnter, entering.self, entering.locals.globals);
                        }
                    }

                    // Run it again, to catch any transitions in callbacks
                    if ($state.transition !== transition) {
                        $rootScope.$broadcast('$stateChangeCancel', to.self, toParams, from.self, fromParams);
                        return TransitionSuperseded;
                    }

                    // Update globals in $state
                    $state.$current = to;
                    $state.current = to.self;
                    $state.params = toParams;
                    copy($state.params, $stateParams);
                    $state.transition = null;

                    if (options.location && to.navigable) {
                        $urlRouter.push(to.navigable.url, to.navigable.locals.globals.$stateParams, {
                            $$avoidResync: true, replace: options.location === 'replace'
                        });
                    }

                    if (options.notify) {
                        /**
                         * @ngdoc event
                         * @name ui.router.state.$state#$stateChangeSuccess
                         * @eventOf ui.router.state.$state
                         * @eventType broadcast on root scope
                         * @description
                         * Fired once the state transition is **complete**.
                         *
                         * @param {Object} event Event object.
                         * @param {State} toState The state being transitioned to.
                         * @param {Object} toParams The params supplied to the `toState`.
                         * @param {State} fromState The current state, pre-transition.
                         * @param {Object} fromParams The params supplied to the `fromState`.
                         */
                        $rootScope.$broadcast('$stateChangeSuccess', to.self, toParams, from.self, fromParams);
                    }
                    $urlRouter.update(true);

                    return $state.current;
                }).then(null, function (error) {
                    // propagate TransitionSuperseded error without emitting $stateChangeCancel
                    // as it was already emitted in the success handler above
                    if (error === TransitionSupersededError) return TransitionSuperseded;

                    if ($state.transition !== transition) {
                        $rootScope.$broadcast('$stateChangeCancel', to.self, toParams, from.self, fromParams);
                        return TransitionSuperseded;
                    }

                    $state.transition = null;
                    /**
                     * @ngdoc event
                     * @name ui.router.state.$state#$stateChangeError
                     * @eventOf ui.router.state.$state
                     * @eventType broadcast on root scope
                     * @description
                     * Fired when an **error occurs** during transition. It's important to note that if you
                     * have any errors in your resolve functions (javascript errors, non-existent services, etc)
                     * they will not throw traditionally. You must listen for this $stateChangeError event to
                     * catch **ALL** errors.
                     *
                     * @param {Object} event Event object.
                     * @param {State} toState The state being transitioned to.
                     * @param {Object} toParams The params supplied to the `toState`.
                     * @param {State} fromState The current state, pre-transition.
                     * @param {Object} fromParams The params supplied to the `fromState`.
                     * @param {Error} error The resolve error object.
                     */
                    evt = $rootScope.$broadcast('$stateChangeError', to.self, toParams, from.self, fromParams, error);

                    if (!evt.defaultPrevented) {
                        $urlRouter.update();
                    }

                    return $q.reject(error);
                });

                silenceUncaughtInPromise(transition);
                return transition;
            };

            /**
             * @ngdoc function
             * @name ui.router.state.$state#is
             * @methodOf ui.router.state.$state
             *
             * @description
             * Similar to {@link ui.router.state.$state#methods_includes $state.includes},
             * but only checks for the full state name. If params is supplied then it will be
             * tested for strict equality against the current active params object, so all params
             * must match with none missing and no extras.
             *
             * @example
             * <pre>
             * $state.$current.name = 'contacts.details.item';
             *
             * // absolute name
             * $state.is('contact.details.item'); // returns true
             * $state.is(contactDetailItemStateObject); // returns true
             *
             * // relative name (. and ^), typically from a template
             * // E.g. from the 'contacts.details' template
             * <div ng-class="{highlighted: $state.is('.item')}">Item</div>
             * </pre>
             *
             * @param {string|object} stateOrName The state name (absolute or relative) or state object you'd like to check.
             * @param {object=} params A param object, e.g. `{sectionId: section.id}`, that you'd like
             * to test against the current active state.
             * @param {object=} options An options object.  The options are:
             *
             * - **`relative`** - {string|object} -  If `stateOrName` is a relative state name and `options.relative` is set, .is will
             * test relative to `options.relative` state (or name).
             *
             * @returns {boolean} Returns true if it is the state.
             */
            $state.is = function is(stateOrName, params, options) {
                options = extend({ relative: $state.$current }, options || {});
                var state = findState(stateOrName, options.relative);

                if (!isDefined(state)) { return undefined; }
                if ($state.$current !== state) { return false; }

                return !params || objectKeys(params).reduce(function (acc, key) {
                    var paramDef = state.params[key];
                    return acc && !paramDef || paramDef.type.equals($stateParams[key], params[key]);
                }, true);
            };

            /**
             * @ngdoc function
             * @name ui.router.state.$state#includes
             * @methodOf ui.router.state.$state
             *
             * @description
             * A method to determine if the current active state is equal to or is the child of the
             * state stateName. If any params are passed then they will be tested for a match as well.
             * Not all the parameters need to be passed, just the ones you'd like to test for equality.
             *
             * @example
             * Partial and relative names
             * <pre>
             * $state.$current.name = 'contacts.details.item';
             *
             * // Using partial names
             * $state.includes("contacts"); // returns true
             * $state.includes("contacts.details"); // returns true
             * $state.includes("contacts.details.item"); // returns true
             * $state.includes("contacts.list"); // returns false
             * $state.includes("about"); // returns false
             *
             * // Using relative names (. and ^), typically from a template
             * // E.g. from the 'contacts.details' template
             * <div ng-class="{highlighted: $state.includes('.item')}">Item</div>
             * </pre>
             *
             * Basic globbing patterns
             * <pre>
             * $state.$current.name = 'contacts.details.item.url';
             *
             * $state.includes("*.details.*.*"); // returns true
             * $state.includes("*.details.**"); // returns true
             * $state.includes("**.item.**"); // returns true
             * $state.includes("*.details.item.url"); // returns true
             * $state.includes("*.details.*.url"); // returns true
             * $state.includes("*.details.*"); // returns false
             * $state.includes("item.**"); // returns false
             * </pre>
             *
             * @param {string} stateOrName A partial name, relative name, or glob pattern
             * to be searched for within the current state name.
             * @param {object=} params A param object, e.g. `{sectionId: section.id}`,
             * that you'd like to test against the current active state.
             * @param {object=} options An options object.  The options are:
             *
             * - **`relative`** - {string|object=} -  If `stateOrName` is a relative state reference and `options.relative` is set,
             * .includes will test relative to `options.relative` state (or name).
             *
             * @returns {boolean} Returns true if it does include the state
             */
            $state.includes = function includes(stateOrName, params, options) {
                options = extend({ relative: $state.$current }, options || {});
                if (isString(stateOrName) && isGlob(stateOrName)) {
                    if (!doesStateMatchGlob(stateOrName)) {
                        return false;
                    }
                    stateOrName = $state.$current.name;
                }

                var state = findState(stateOrName, options.relative);
                if (!isDefined(state)) { return undefined; }
                if (!isDefined($state.$current.includes[state.name])) { return false; }
                if (!params) { return true; }

                var keys = objectKeys(params);
                for (var i = 0; i < keys.length; i++) {
                    var key = keys[i], paramDef = state.params[key];
                    if (paramDef && !paramDef.type.equals($stateParams[key], params[key])) {
                        return false;
                    }
                }

                return objectKeys(params).reduce(function (acc, key) {
                    var paramDef = state.params[key];
                    return acc && !paramDef || paramDef.type.equals($stateParams[key], params[key]);
                }, true);
            };


            /**
             * @ngdoc function
             * @name ui.router.state.$state#href
             * @methodOf ui.router.state.$state
             *
             * @description
             * A url generation method that returns the compiled url for the given state populated with the given params.
             *
             * @example
             * <pre>
             * expect($state.href("about.person", { person: "bob" })).toEqual("/about/bob");
             * </pre>
             *
             * @param {string|object} stateOrName The state name or state object you'd like to generate a url from.
             * @param {object=} params An object of parameter values to fill the state's required parameters.
             * @param {object=} options Options object. The options are:
             *
             * - **`lossy`** - {boolean=true} -  If true, and if there is no url associated with the state provided in the
             *    first parameter, then the constructed href url will be built from the first navigable ancestor (aka
             *    ancestor with a valid url).
             * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url.
             * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'), 
             *    defines which state to be relative from.
             * - **`absolute`** - {boolean=false},  If true will generate an absolute url, e.g. "http://www.example.com/fullurl".
             * 
             * @returns {string} compiled state url
             */
            $state.href = function href(stateOrName, params, options) {
                options = extend({
                    lossy: true,
                    inherit: true,
                    absolute: false,
                    relative: $state.$current
                }, options || {});

                var state = findState(stateOrName, options.relative);

                if (!isDefined(state)) return null;
                if (options.inherit) params = inheritParams($stateParams, params || {}, $state.$current, state);

                var nav = (state && options.lossy) ? state.navigable : state;

                if (!nav || nav.url === undefined || nav.url === null) {
                    return null;
                }
                return $urlRouter.href(nav.url, filterByKeys(state.params.$$keys().concat('#'), params || {}), {
                    absolute: options.absolute
                });
            };

            /**
             * @ngdoc function
             * @name ui.router.state.$state#get
             * @methodOf ui.router.state.$state
             *
             * @description
             * Returns the state configuration object for any specific state or all states.
             *
             * @param {string|object=} stateOrName (absolute or relative) If provided, will only get the config for
             * the requested state. If not provided, returns an array of ALL state configs.
             * @param {string|object=} context When stateOrName is a relative state reference, the state will be retrieved relative to context.
             * @returns {Object|Array} State configuration object or array of all objects.
             */
            $state.get = function (stateOrName, context) {
                if (arguments.length === 0) return map(objectKeys(states), function (name) { return states[name].self; });
                var state = findState(stateOrName, context || $state.$current);
                return (state && state.self) ? state.self : null;
            };

            function resolveState(state, params, paramsAreFiltered, inherited, dst, options) {
                // Make a restricted $stateParams with only the parameters that apply to this state if
                // necessary. In addition to being available to the controller and onEnter/onExit callbacks,
                // we also need $stateParams to be available for any $injector calls we make during the
                // dependency resolution process.
                var $stateParams = (paramsAreFiltered) ? params : filterByKeys(state.params.$$keys(), params);
                var locals = { $stateParams: $stateParams };

                // Resolve 'global' dependencies for the state, i.e. those not specific to a view.
                // We're also including $stateParams in this; that way the parameters are restricted
                // to the set that should be visible to the state, and are independent of when we update
                // the global $state and $stateParams values.
                dst.resolve = $resolve.resolve(state.resolve, locals, dst.resolve, state);
                var promises = [dst.resolve.then(function (globals) {
                    dst.globals = globals;
                })];
                if (inherited) promises.push(inherited);

                function resolveViews() {
                    var viewsPromises = [];

                    // Resolve template and dependencies for all views.
                    forEach(state.views, function (view, name) {
                        var injectables = (view.resolve && view.resolve !== state.resolve ? view.resolve : {});
                        injectables.$template = [function () {
                            return $view.load(name, { view: view, locals: dst.globals, params: $stateParams, notify: options.notify }) || '';
                        }];

                        viewsPromises.push($resolve.resolve(injectables, dst.globals, dst.resolve, state).then(function (result) {
                            // References to the controller (only instantiated at link time)
                            if (isFunction(view.controllerProvider) || isArray(view.controllerProvider)) {
                                var injectLocals = angular.extend({}, injectables, dst.globals);
                                result.$$controller = $injector.invoke(view.controllerProvider, null, injectLocals);
                            } else {
                                result.$$controller = view.controller;
                            }
                            // Provide access to the state itself for internal use
                            result.$$state = state;
                            result.$$controllerAs = view.controllerAs;
                            result.$$resolveAs = view.resolveAs;
                            dst[name] = result;
                        }));
                    });

                    return $q.all(viewsPromises).then(function () {
                        return dst.globals;
                    });
                }

                // Wait for all the promises and then return the activation object
                return $q.all(promises).then(resolveViews).then(function (values) {
                    return dst;
                });
            }

            return $state;
        }

        function shouldSkipReload(to, toParams, from, fromParams, locals, options) {
            // Return true if there are no differences in non-search (path/object) params, false if there are differences
            function nonSearchParamsEqual(fromAndToState, fromParams, toParams) {
                // Identify whether all the parameters that differ between `fromParams` and `toParams` were search params.
                function notSearchParam(key) {
                    return fromAndToState.params[key].location != "search";
                }
                var nonQueryParamKeys = fromAndToState.params.$$keys().filter(notSearchParam);
                var nonQueryParams = pick.apply({}, [fromAndToState.params].concat(nonQueryParamKeys));
                var nonQueryParamSet = new $$UMFP.ParamSet(nonQueryParams);
                return nonQueryParamSet.$$equals(fromParams, toParams);
            }

            // If reload was not explicitly requested
            // and we're transitioning to the same state we're already in
            // and    the locals didn't change
            //     or they changed in a way that doesn't merit reloading
            //        (reloadOnParams:false, or reloadOnSearch.false and only search params changed)
            // Then return true.
            if (!options.reload && to === from &&
              (locals === from.locals || (to.self.reloadOnSearch === false && nonSearchParamsEqual(from, fromParams, toParams)))) {
                return true;
            }
        }
    }

    angular.module('ui.router.state')
      .factory('$stateParams', function () { return {}; })
      .constant("$state.runtime", { autoinject: true })
      .provider('$state', $StateProvider)
      // Inject $state to initialize when entering runtime. #2574
      .run(['$injector', function ($injector) {
          // Allow tests (stateSpec.js) to turn this off by defining this constant
          if ($injector.get("$state.runtime").autoinject) {
              $injector.get('$state');
          }
      }]);


    $ViewProvider.$inject = [];
    function $ViewProvider() {

        this.$get = $get;
        /**
         * @ngdoc object
         * @name ui.router.state.$view
         *
         * @requires ui.router.util.$templateFactory
         * @requires $rootScope
         *
         * @description
         *
         */
        $get.$inject = ['$rootScope', '$templateFactory'];
        function $get($rootScope, $templateFactory) {
            return {
                // $view.load('full.viewName', { template: ..., controller: ..., resolve: ..., async: false, params: ... })
                /**
                 * @ngdoc function
                 * @name ui.router.state.$view#load
                 * @methodOf ui.router.state.$view
                 *
                 * @description
                 *
                 * @param {string} name name
                 * @param {object} options option object.
                 */
                load: function load(name, options) {
                    var result, defaults = {
                        template: null, controller: null, view: null, locals: null, notify: true, async: true, params: {}
                    };
                    options = extend(defaults, options);

                    if (options.view) {
                        result = $templateFactory.fromConfig(options.view, options.params, options.locals);
                    }
                    return result;
                }
            };
        }
    }

    angular.module('ui.router.state').provider('$view', $ViewProvider);

    /**
     * @ngdoc object
     * @name ui.router.state.$uiViewScrollProvider
     *
     * @description
     * Provider that returns the {@link ui.router.state.$uiViewScroll} service function.
     */
    function $ViewScrollProvider() {

        var useAnchorScroll = false;

        /**
         * @ngdoc function
         * @name ui.router.state.$uiViewScrollProvider#useAnchorScroll
         * @methodOf ui.router.state.$uiViewScrollProvider
         *
         * @description
         * Reverts back to using the core [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll) service for
         * scrolling based on the url anchor.
         */
        this.useAnchorScroll = function () {
            useAnchorScroll = true;
        };

        /**
         * @ngdoc object
         * @name ui.router.state.$uiViewScroll
         *
         * @requires $anchorScroll
         * @requires $timeout
         *
         * @description
         * When called with a jqLite element, it scrolls the element into view (after a
         * `$timeout` so the DOM has time to refresh).
         *
         * If you prefer to rely on `$anchorScroll` to scroll the view to the anchor,
         * this can be enabled by calling {@link ui.router.state.$uiViewScrollProvider#methods_useAnchorScroll `$uiViewScrollProvider.useAnchorScroll()`}.
         */
        this.$get = ['$anchorScroll', '$timeout', function ($anchorScroll, $timeout) {
            if (useAnchorScroll) {
                return $anchorScroll;
            }

            return function ($element) {
                return $timeout(function () {
                    $element[0].scrollIntoView();
                }, 0, false);
            };
        }];
    }

    angular.module('ui.router.state').provider('$uiViewScroll', $ViewScrollProvider);

    /**
     * @ngdoc directive
     * @name ui.router.state.directive:ui-view
     *
     * @requires ui.router.state.$state
     * @requires $compile
     * @requires $controller
     * @requires $injector
     * @requires ui.router.state.$uiViewScroll
     * @requires $document
     *
     * @restrict ECA
     *
     * @description
     * The ui-view directive tells $state where to place your templates.
     *
     * @param {string=} name A view name. The name should be unique amongst the other views in the
     * same state. You can have views of the same name that live in different states.
     *
     * @param {string=} autoscroll It allows you to set the scroll behavior of the browser window
     * when a view is populated. By default, $anchorScroll is overridden by ui-router's custom scroll
     * service, {@link ui.router.state.$uiViewScroll}. This custom service let's you
     * scroll ui-view elements into view when they are populated during a state activation.
     *
     * *Note: To revert back to old [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll)
     * functionality, call `$uiViewScrollProvider.useAnchorScroll()`.*
     *
     * @param {string=} onload Expression to evaluate whenever the view updates.
     *
     * @example
     * A view can be unnamed or named.
     * <pre>
     * <!-- Unnamed -->
     * <div ui-view></div>
     *
     * <!-- Named -->
     * <div ui-view="viewName"></div>
     * </pre>
     *
     * You can only have one unnamed view within any template (or root html). If you are only using a
     * single view and it is unnamed then you can populate it like so:
     * <pre>
     * <div ui-view></div>
     * $stateProvider.state("home", {
     *   template: "<h1>HELLO!</h1>"
     * })
     * </pre>
     *
     * The above is a convenient shortcut equivalent to specifying your view explicitly with the {@link ui.router.state.$stateProvider#methods_state `views`}
     * config property, by name, in this case an empty name:
     * <pre>
     * $stateProvider.state("home", {
     *   views: {
     *     "": {
     *       template: "<h1>HELLO!</h1>"
     *     }
     *   }    
     * })
     * </pre>
     *
     * But typically you'll only use the views property if you name your view or have more than one view
     * in the same template. There's not really a compelling reason to name a view if its the only one,
     * but you could if you wanted, like so:
     * <pre>
     * <div ui-view="main"></div>
     * </pre>
     * <pre>
     * $stateProvider.state("home", {
     *   views: {
     *     "main": {
     *       template: "<h1>HELLO!</h1>"
     *     }
     *   }    
     * })
     * </pre>
     *
     * Really though, you'll use views to set up multiple views:
     * <pre>
     * <div ui-view></div>
     * <div ui-view="chart"></div>
     * <div ui-view="data"></div>
     * </pre>
     *
     * <pre>
     * $stateProvider.state("home", {
     *   views: {
     *     "": {
     *       template: "<h1>HELLO!</h1>"
     *     },
     *     "chart": {
     *       template: "<chart_thing/>"
     *     },
     *     "data": {
     *       template: "<data_thing/>"
     *     }
     *   }    
     * })
     * </pre>
     *
     * Examples for `autoscroll`:
     *
     * <pre>
     * <!-- If autoscroll present with no expression,
     *      then scroll ui-view into view -->
     * <ui-view autoscroll/>
     *
     * <!-- If autoscroll present with valid expression,
     *      then scroll ui-view into view if expression evaluates to true -->
     * <ui-view autoscroll='true'/>
     * <ui-view autoscroll='false'/>
     * <ui-view autoscroll='scopeVariable'/>
     * </pre>
     *
     * Resolve data:
     *
     * The resolved data from the state's `resolve` block is placed on the scope as `$resolve` (this
     * can be customized using [[ViewDeclaration.resolveAs]]).  This can be then accessed from the template.
     *
     * Note that when `controllerAs` is being used, `$resolve` is set on the controller instance *after* the
     * controller is instantiated.  The `$onInit()` hook can be used to perform initialization code which
     * depends on `$resolve` data.
     *
     * Example usage of $resolve in a view template
     * <pre>
     * $stateProvider.state('home', {
     *   template: '<my-component user="$resolve.user"></my-component>',
     *   resolve: {
     *     user: function(UserService) { return UserService.fetchUser(); }
     *   }
     * });
     * </pre>
     */
    $ViewDirective.$inject = ['$state', '$injector', '$uiViewScroll', '$interpolate', '$q'];
    function $ViewDirective($state, $injector, $uiViewScroll, $interpolate, $q) {

        function getService() {
            return ($injector.has) ? function (service) {
                return $injector.has(service) ? $injector.get(service) : null;
            } : function (service) {
                try {
                    return $injector.get(service);
                } catch (e) {
                    return null;
                }
            };
        }

        var service = getService(),
            $animator = service('$animator'),
            $animate = service('$animate');

        // Returns a set of DOM manipulation functions based on which Angular version
        // it should use
        function getRenderer(attrs, scope) {
            var statics = function () {
                return {
                    enter: function (element, target, cb) { target.after(element); cb(); },
                    leave: function (element, cb) { element.remove(); cb(); }
                };
            };

            if ($animate) {
                return {
                    enter: function (element, target, cb) {
                        if (angular.version.minor > 2) {
                            $animate.enter(element, null, target).then(cb);
                        } else {
                            $animate.enter(element, null, target, cb);
                        }
                    },
                    leave: function (element, cb) {
                        if (angular.version.minor > 2) {
                            $animate.leave(element).then(cb);
                        } else {
                            $animate.leave(element, cb);
                        }
                    }
                };
            }

            if ($animator) {
                var animate = $animator && $animator(scope, attrs);

                return {
                    enter: function (element, target, cb) { animate.enter(element, null, target); cb(); },
                    leave: function (element, cb) { animate.leave(element); cb(); }
                };
            }

            return statics();
        }

        var directive = {
            restrict: 'ECA',
            terminal: true,
            priority: 400,
            transclude: 'element',
            compile: function (tElement, tAttrs, $transclude) {
                return function (scope, $element, attrs) {
                    var previousEl, currentEl, currentScope, latestLocals,
                        onloadExp = attrs.onload || '',
                        autoScrollExp = attrs.autoscroll,
                        renderer = getRenderer(attrs, scope),
                        inherited = $element.inheritedData('$uiView');

                    scope.$on('$stateChangeSuccess', function () {
                        updateView(false);
                    });

                    updateView(true);

                    function cleanupLastView() {
                        if (previousEl) {
                            previousEl.remove();
                            previousEl = null;
                        }

                        if (currentScope) {
                            currentScope.$destroy();
                            currentScope = null;
                        }

                        if (currentEl) {
                            var $uiViewData = currentEl.data('$uiViewAnim');
                            renderer.leave(currentEl, function () {
                                $uiViewData.$$animLeave.resolve();
                                previousEl = null;
                            });

                            previousEl = currentEl;
                            currentEl = null;
                        }
                    }

                    function updateView(firstTime) {
                        var newScope,
                            name = getUiViewName(scope, attrs, $element, $interpolate),
                            previousLocals = name && $state.$current && $state.$current.locals[name];

                        if (!firstTime && previousLocals === latestLocals) return; // nothing to do
                        newScope = scope.$new();
                        latestLocals = $state.$current.locals[name];

                        /**
                         * @ngdoc event
                         * @name ui.router.state.directive:ui-view#$viewContentLoading
                         * @eventOf ui.router.state.directive:ui-view
                         * @eventType emits on ui-view directive scope
                         * @description
                         *
                         * Fired once the view **begins loading**, *before* the DOM is rendered.
                         *
                         * @param {Object} event Event object.
                         * @param {string} viewName Name of the view.
                         */
                        newScope.$emit('$viewContentLoading', name);

                        var clone = $transclude(newScope, function (clone) {
                            var animEnter = $q.defer(), animLeave = $q.defer();
                            var viewAnimData = {
                                $animEnter: animEnter.promise,
                                $animLeave: animLeave.promise,
                                $$animLeave: animLeave
                            };

                            clone.data('$uiViewAnim', viewAnimData);
                            renderer.enter(clone, $element, function onUiViewEnter() {
                                animEnter.resolve();
                                if (currentScope) {
                                    currentScope.$emit('$viewContentAnimationEnded');
                                }

                                if (angular.isDefined(autoScrollExp) && !autoScrollExp || scope.$eval(autoScrollExp)) {
                                    $uiViewScroll(clone);
                                }
                            });
                            cleanupLastView();
                        });

                        currentEl = clone;
                        currentScope = newScope;
                        /**
                         * @ngdoc event
                         * @name ui.router.state.directive:ui-view#$viewContentLoaded
                         * @eventOf ui.router.state.directive:ui-view
                         * @eventType emits on ui-view directive scope
                         * @description
                         * Fired once the view is **loaded**, *after* the DOM is rendered.
                         *
                         * @param {Object} event Event object.
                         * @param {string} viewName Name of the view.
                         */
                        currentScope.$emit('$viewContentLoaded', name);
                        currentScope.$eval(onloadExp);
                    }
                };
            }
        };

        return directive;
    }

    $ViewDirectiveFill.$inject = ['$compile', '$controller', '$state', '$interpolate'];
    function $ViewDirectiveFill($compile, $controller, $state, $interpolate) {
        return {
            restrict: 'ECA',
            priority: -400,
            compile: function (tElement) {
                var initial = tElement.html();
                if (tElement.empty) {
                    tElement.empty();
                } else {
                    // ng 1.0.0 doesn't have empty(), which cleans up data and handlers
                    tElement[0].innerHTML = null;
                }

                return function (scope, $element, attrs) {
                    var current = $state.$current,
                        name = getUiViewName(scope, attrs, $element, $interpolate),
                        locals = current && current.locals[name];

                    if (!locals) {
                        $element.html(initial);
                        $compile($element.contents())(scope);
                        return;
                    }

                    $element.data('$uiView', { name: name, state: locals.$$state });
                    $element.html(locals.$template ? locals.$template : initial);

                    var resolveData = angular.extend({}, locals);
                    scope[locals.$$resolveAs] = resolveData;

                    var link = $compile($element.contents());

                    if (locals.$$controller) {
                        locals.$scope = scope;
                        locals.$element = $element;
                        var controller = $controller(locals.$$controller, locals);
                        if (locals.$$controllerAs) {
                            scope[locals.$$controllerAs] = controller;
                            scope[locals.$$controllerAs][locals.$$resolveAs] = resolveData;
                        }
                        if (isFunction(controller.$onInit)) controller.$onInit();
                        $element.data('$ngControllerController', controller);
                        $element.children().data('$ngControllerController', controller);
                    }

                    link(scope);
                };
            }
        };
    }

    /**
     * Shared ui-view code for both directives:
     * Given scope, element, and its attributes, return the view's name
     */
    function getUiViewName(scope, attrs, element, $interpolate) {
        var name = $interpolate(attrs.uiView || attrs.name || '')(scope);
        var uiViewCreatedBy = element.inheritedData('$uiView');
        return name.indexOf('@') >= 0 ? name : (name + '@' + (uiViewCreatedBy ? uiViewCreatedBy.state.name : ''));
    }

    angular.module('ui.router.state').directive('uiView', $ViewDirective);
    angular.module('ui.router.state').directive('uiView', $ViewDirectiveFill);

    function parseStateRef(ref, current) {
        var preparsed = ref.match(/^\s*({[^}]*})\s*$/), parsed;
        if (preparsed) ref = current + '(' + preparsed[1] + ')';
        parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/);
        if (!parsed || parsed.length !== 4) throw new Error("Invalid state ref '" + ref + "'");
        return { state: parsed[1], paramExpr: parsed[3] || null };
    }

    function stateContext(el) {
        var stateData = el.parent().inheritedData('$uiView');

        if (stateData && stateData.state && stateData.state.name) {
            return stateData.state;
        }
    }

    function getTypeInfo(el) {
        // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
        var isSvg = Object.prototype.toString.call(el.prop('href')) === '[object SVGAnimatedString]';
        var isForm = el[0].nodeName === "FORM";

        return {
            attr: isForm ? "action" : (isSvg ? 'xlink:href' : 'href'),
            isAnchor: el.prop("tagName").toUpperCase() === "A",
            clickable: !isForm
        };
    }

    function clickHook(el, $state, $timeout, type, current) {
        return function (e) {
            var button = e.which || e.button, target = current();

            if (!(button > 1 || e.ctrlKey || e.metaKey || e.shiftKey || el.attr('target'))) {
                // HACK: This is to allow ng-clicks to be processed before the transition is initiated:
                var transition = $timeout(function () {
                    $state.go(target.state, target.params, target.options);
                });
                e.preventDefault();

                // if the state has no URL, ignore one preventDefault from the <a> directive.
                var ignorePreventDefaultCount = type.isAnchor && !target.href ? 1 : 0;

                e.preventDefault = function () {
                    if (ignorePreventDefaultCount-- <= 0) $timeout.cancel(transition);
                };
            }
        };
    }

    function defaultOpts(el, $state) {
        return { relative: stateContext(el) || $state.$current, inherit: true };
    }

    /**
     * @ngdoc directive
     * @name ui.router.state.directive:ui-sref
     *
     * @requires ui.router.state.$state
     * @requires $timeout
     *
     * @restrict A
     *
     * @description
     * A directive that binds a link (`<a>` tag) to a state. If the state has an associated
     * URL, the directive will automatically generate & update the `href` attribute via
     * the {@link ui.router.state.$state#methods_href $state.href()} method. Clicking
     * the link will trigger a state transition with optional parameters.
     *
     * Also middle-clicking, right-clicking, and ctrl-clicking on the link will be
     * handled natively by the browser.
     *
     * You can also use relative state paths within ui-sref, just like the relative
     * paths passed to `$state.go()`. You just need to be aware that the path is relative
     * to the state that the link lives in, in other words the state that loaded the
     * template containing the link.
     *
     * You can specify options to pass to {@link ui.router.state.$state#methods_go $state.go()}
     * using the `ui-sref-opts` attribute. Options are restricted to `location`, `inherit`,
     * and `reload`.
     *
     * @example
     * Here's an example of how you'd use ui-sref and how it would compile. If you have the
     * following template:
     * <pre>
     * <a ui-sref="home">Home</a> | <a ui-sref="about">About</a> | <a ui-sref="{page: 2}">Next page</a>
     *
     * <ul>
     *     <li ng-repeat="contact in contacts">
     *         <a ui-sref="contacts.detail({ id: contact.id })">{{ contact.name }}</a>
     *     </li>
     * </ul>
     * </pre>
     *
     * Then the compiled html would be (assuming Html5Mode is off and current state is contacts):
     * <pre>
     * <a href="#/home" ui-sref="home">Home</a> | <a href="#/about" ui-sref="about">About</a> | <a href="#/contacts?page=2" ui-sref="{page: 2}">Next page</a>
     *
     * <ul>
     *     <li ng-repeat="contact in contacts">
     *         <a href="#/contacts/1" ui-sref="contacts.detail({ id: contact.id })">Joe</a>
     *     </li>
     *     <li ng-repeat="contact in contacts">
     *         <a href="#/contacts/2" ui-sref="contacts.detail({ id: contact.id })">Alice</a>
     *     </li>
     *     <li ng-repeat="contact in contacts">
     *         <a href="#/contacts/3" ui-sref="contacts.detail({ id: contact.id })">Bob</a>
     *     </li>
     * </ul>
     *
     * <a ui-sref="home" ui-sref-opts="{reload: true}">Home</a>
     * </pre>
     *
     * @param {string} ui-sref 'stateName' can be any valid absolute or relative state
     * @param {Object} ui-sref-opts options to pass to {@link ui.router.state.$state#methods_go $state.go()}
     */
    $StateRefDirective.$inject = ['$state', '$timeout'];
    function $StateRefDirective($state, $timeout) {
        return {
            restrict: 'A',
            require: ['?^uiSrefActive', '?^uiSrefActiveEq'],
            link: function (scope, element, attrs, uiSrefActive) {
                var ref = parseStateRef(attrs.uiSref, $state.current.name);
                var def = { state: ref.state, href: null, params: null };
                var type = getTypeInfo(element);
                var active = uiSrefActive[1] || uiSrefActive[0];
                var unlinkInfoFn = null;
                var hookFn;

                def.options = extend(defaultOpts(element, $state), attrs.uiSrefOpts ? scope.$eval(attrs.uiSrefOpts) : {});

                var update = function (val) {
                    if (val) def.params = angular.copy(val);
                    def.href = $state.href(ref.state, def.params, def.options);

                    if (unlinkInfoFn) unlinkInfoFn();
                    if (active) unlinkInfoFn = active.$$addStateInfo(ref.state, def.params);
                    if (def.href !== null) attrs.$set(type.attr, def.href);
                };

                if (ref.paramExpr) {
                    scope.$watch(ref.paramExpr, function (val) { if (val !== def.params) update(val); }, true);
                    def.params = angular.copy(scope.$eval(ref.paramExpr));
                }
                update();

                if (!type.clickable) return;
                hookFn = clickHook(element, $state, $timeout, type, function () { return def; });
                element[element.on ? 'on' : 'bind']("click", hookFn);
                scope.$on('$destroy', function () {
                    element[element.off ? 'off' : 'unbind']("click", hookFn);
                });
            }
        };
    }

    /**
     * @ngdoc directive
     * @name ui.router.state.directive:ui-state
     *
     * @requires ui.router.state.uiSref
     *
     * @restrict A
     *
     * @description
     * Much like ui-sref, but will accept named $scope properties to evaluate for a state definition,
     * params and override options.
     *
     * @param {string} ui-state 'stateName' can be any valid absolute or relative state
     * @param {Object} ui-state-params params to pass to {@link ui.router.state.$state#methods_href $state.href()}
     * @param {Object} ui-state-opts options to pass to {@link ui.router.state.$state#methods_go $state.go()}
     */
    $StateRefDynamicDirective.$inject = ['$state', '$timeout'];
    function $StateRefDynamicDirective($state, $timeout) {
        return {
            restrict: 'A',
            require: ['?^uiSrefActive', '?^uiSrefActiveEq'],
            link: function (scope, element, attrs, uiSrefActive) {
                var type = getTypeInfo(element);
                var active = uiSrefActive[1] || uiSrefActive[0];
                var group = [attrs.uiState, attrs.uiStateParams || null, attrs.uiStateOpts || null];
                var watch = '[' + group.map(function (val) { return val || 'null'; }).join(', ') + ']';
                var def = { state: null, params: null, options: null, href: null };
                var unlinkInfoFn = null;
                var hookFn;

                function runStateRefLink(group) {
                    def.state = group[0]; def.params = group[1]; def.options = group[2];
                    def.href = $state.href(def.state, def.params, def.options);

                    if (unlinkInfoFn) unlinkInfoFn();
                    if (active) unlinkInfoFn = active.$$addStateInfo(def.state, def.params);
                    if (def.href) attrs.$set(type.attr, def.href);
                }

                scope.$watch(watch, runStateRefLink, true);
                runStateRefLink(scope.$eval(watch));

                if (!type.clickable) return;
                hookFn = clickHook(element, $state, $timeout, type, function () { return def; });
                element[element.on ? 'on' : 'bind']("click", hookFn);
                scope.$on('$destroy', function () {
                    element[element.off ? 'off' : 'unbind']("click", hookFn);
                });
            }
        };
    }


    /**
     * @ngdoc directive
     * @name ui.router.state.directive:ui-sref-active
     *
     * @requires ui.router.state.$state
     * @requires ui.router.state.$stateParams
     * @requires $interpolate
     *
     * @restrict A
     *
     * @description
     * A directive working alongside ui-sref to add classes to an element when the
     * related ui-sref directive's state is active, and removing them when it is inactive.
     * The primary use-case is to simplify the special appearance of navigation menus
     * relying on `ui-sref`, by having the "active" state's menu button appear different,
     * distinguishing it from the inactive menu items.
     *
     * ui-sref-active can live on the same element as ui-sref or on a parent element. The first
     * ui-sref-active found at the same level or above the ui-sref will be used.
     *
     * Will activate when the ui-sref's target state or any child state is active. If you
     * need to activate only when the ui-sref target state is active and *not* any of
     * it's children, then you will use
     * {@link ui.router.state.directive:ui-sref-active-eq ui-sref-active-eq}
     *
     * @example
     * Given the following template:
     * <pre>
     * <ul>
     *   <li ui-sref-active="active" class="item">
     *     <a href ui-sref="app.user({user: 'bilbobaggins'})">@bilbobaggins</a>
     *   </li>
     * </ul>
     * </pre>
     *
     *
     * When the app state is "app.user" (or any children states), and contains the state parameter "user" with value "bilbobaggins",
     * the resulting HTML will appear as (note the 'active' class):
     * <pre>
     * <ul>
     *   <li ui-sref-active="active" class="item active">
     *     <a ui-sref="app.user({user: 'bilbobaggins'})" href="/users/bilbobaggins">@bilbobaggins</a>
     *   </li>
     * </ul>
     * </pre>
     *
     * The class name is interpolated **once** during the directives link time (any further changes to the
     * interpolated value are ignored).
     *
     * Multiple classes may be specified in a space-separated format:
     * <pre>
     * <ul>
     *   <li ui-sref-active='class1 class2 class3'>
     *     <a ui-sref="app.user">link</a>
     *   </li>
     * </ul>
     * </pre>
     *
     * It is also possible to pass ui-sref-active an expression that evaluates
     * to an object hash, whose keys represent active class names and whose
     * values represent the respective state names/globs.
     * ui-sref-active will match if the current active state **includes** any of
     * the specified state names/globs, even the abstract ones.
     *
     * @Example
     * Given the following template, with "admin" being an abstract state:
     * <pre>
     * <div ui-sref-active="{'active': 'admin.*'}">
     *   <a ui-sref-active="active" ui-sref="admin.roles">Roles</a>
     * </div>
     * </pre>
     *
     * When the current state is "admin.roles" the "active" class will be applied
     * to both the <div> and <a> elements. It is important to note that the state
     * names/globs passed to ui-sref-active shadow the state provided by ui-sref.
     */

    /**
     * @ngdoc directive
     * @name ui.router.state.directive:ui-sref-active-eq
     *
     * @requires ui.router.state.$state
     * @requires ui.router.state.$stateParams
     * @requires $interpolate
     *
     * @restrict A
     *
     * @description
     * The same as {@link ui.router.state.directive:ui-sref-active ui-sref-active} but will only activate
     * when the exact target state used in the `ui-sref` is active; no child states.
     *
     */
    $StateRefActiveDirective.$inject = ['$state', '$stateParams', '$interpolate'];
    function $StateRefActiveDirective($state, $stateParams, $interpolate) {
        return {
            restrict: "A",
            controller: ['$scope', '$element', '$attrs', '$timeout', function ($scope, $element, $attrs, $timeout) {
                var states = [], activeClasses = {}, activeEqClass, uiSrefActive;

                // There probably isn't much point in $observing this
                // uiSrefActive and uiSrefActiveEq share the same directive object with some
                // slight difference in logic routing
                activeEqClass = $interpolate($attrs.uiSrefActiveEq || '', false)($scope);

                try {
                    uiSrefActive = $scope.$eval($attrs.uiSrefActive);
                } catch (e) {
                    // Do nothing. uiSrefActive is not a valid expression.
                    // Fall back to using $interpolate below
                }
                uiSrefActive = uiSrefActive || $interpolate($attrs.uiSrefActive || '', false)($scope);
                if (isObject(uiSrefActive)) {
                    forEach(uiSrefActive, function (stateOrName, activeClass) {
                        if (isString(stateOrName)) {
                            var ref = parseStateRef(stateOrName, $state.current.name);
                            addState(ref.state, $scope.$eval(ref.paramExpr), activeClass);
                        }
                    });
                }

                // Allow uiSref to communicate with uiSrefActive[Equals]
                this.$$addStateInfo = function (newState, newParams) {
                    // we already got an explicit state provided by ui-sref-active, so we
                    // shadow the one that comes from ui-sref
                    if (isObject(uiSrefActive) && states.length > 0) {
                        return;
                    }
                    var deregister = addState(newState, newParams, uiSrefActive);
                    update();
                    return deregister;
                };

                $scope.$on('$stateChangeSuccess', update);

                function addState(stateName, stateParams, activeClass) {
                    var state = $state.get(stateName, stateContext($element));
                    var stateHash = createStateHash(stateName, stateParams);

                    var stateInfo = {
                        state: state || { name: stateName },
                        params: stateParams,
                        hash: stateHash
                    };

                    states.push(stateInfo);
                    activeClasses[stateHash] = activeClass;

                    return function removeState() {
                        var idx = states.indexOf(stateInfo);
                        if (idx !== -1) states.splice(idx, 1);
                    };
                }

                /**
                 * @param {string} state
                 * @param {Object|string} [params]
                 * @return {string}
                 */
                function createStateHash(state, params) {
                    if (!isString(state)) {
                        throw new Error('state should be a string');
                    }
                    if (isObject(params)) {
                        return state + toJson(params);
                    }
                    params = $scope.$eval(params);
                    if (isObject(params)) {
                        return state + toJson(params);
                    }
                    return state;
                }

                // Update route state
                function update() {
                    for (var i = 0; i < states.length; i++) {
                        if (anyMatch(states[i].state, states[i].params)) {
                            addClass($element, activeClasses[states[i].hash]);
                        } else {
                            removeClass($element, activeClasses[states[i].hash]);
                        }

                        if (exactMatch(states[i].state, states[i].params)) {
                            addClass($element, activeEqClass);
                        } else {
                            removeClass($element, activeEqClass);
                        }
                    }
                }

                function addClass(el, className) { $timeout(function () { el.addClass(className); }); }
                function removeClass(el, className) { el.removeClass(className); }
                function anyMatch(state, params) { return $state.includes(state.name, params); }
                function exactMatch(state, params) { return $state.is(state.name, params); }

                update();
            }]
        };
    }

    angular.module('ui.router.state')
      .directive('uiSref', $StateRefDirective)
      .directive('uiSrefActive', $StateRefActiveDirective)
      .directive('uiSrefActiveEq', $StateRefActiveDirective)
      .directive('uiState', $StateRefDynamicDirective);

    /**
     * @ngdoc filter
     * @name ui.router.state.filter:isState
     *
     * @requires ui.router.state.$state
     *
     * @description
     * Translates to {@link ui.router.state.$state#methods_is $state.is("stateName")}.
     */
    $IsStateFilter.$inject = ['$state'];
    function $IsStateFilter($state) {
        var isFilter = function (state, params) {
            return $state.is(state, params);
        };
        isFilter.$stateful = true;
        return isFilter;
    }

    /**
     * @ngdoc filter
     * @name ui.router.state.filter:includedByState
     *
     * @requires ui.router.state.$state
     *
     * @description
     * Translates to {@link ui.router.state.$state#methods_includes $state.includes('fullOrPartialStateName')}.
     */
    $IncludedByStateFilter.$inject = ['$state'];
    function $IncludedByStateFilter($state) {
        var includesFilter = function (state, params, options) {
            return $state.includes(state, params, options);
        };
        includesFilter.$stateful = true;
        return includesFilter;
    }

    angular.module('ui.router.state')
      .filter('isState', $IsStateFilter)
      .filter('includedByState', $IncludedByStateFilter);
})(window, window.angular);
/**!
 * AngularJS file upload shim for angular XHR HTML5 browsers
 * @author  Danial  <danial.farid@gmail.com>
 * @version 1.4.0
 */
if (window.XMLHttpRequest) {
	if (window.FormData) {
	    // allow access to Angular XHR private field: https://github.com/angular/angular.js/issues/1934
		XMLHttpRequest = (function(origXHR) {
			return function() {
				var xhr = new origXHR();
				xhr.setRequestHeader = (function(orig) {
					return function(header, value) {
						if (header === '__setXHR_') {
							var val = value(xhr);
							// fix for angular < 1.2.0
							if (val instanceof Function) {
								val(xhr);
							}
						} else {
							orig.apply(xhr, arguments);
						}
					}
				})(xhr.setRequestHeader);
				return xhr;
			}
		})(XMLHttpRequest);
		window.XMLHttpRequest.__isShim = true;
	}
}

/**!
 * AngularJS file upload shim for HTML5 FormData
 * @author  Danial  <danial.farid@gmail.com>
 * @version 1.4.0
 */
(function() {

var hasFlash = function() {
	try {
	  var fo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash');
	  if (fo) return true;
	} catch(e) {
	  if (navigator.mimeTypes["application/x-shockwave-flash"] != undefined) return true;
	}
	return false;
}

var patchXHR = function(fnName, newFn) {
	window.XMLHttpRequest.prototype[fnName] = newFn(window.XMLHttpRequest.prototype[fnName]);
};

if (window.XMLHttpRequest) {
	if (window.FormData) {
		// allow access to Angular XHR private field: https://github.com/angular/angular.js/issues/1934
		patchXHR("setRequestHeader", function(orig) {
			return function(header, value) {
				if (header === '__setXHR_') {
					var val = value(this);
					// fix for angular < 1.2.0
					if (val instanceof Function) {
						val(this);
					}
				} else {
					orig.apply(this, arguments);
				}
			}
		});
	} else {
		function initializeUploadListener(xhr) {
			if (!xhr.__listeners) {
				if (!xhr.upload) xhr.upload = {};
				xhr.__listeners = [];
				var origAddEventListener = xhr.upload.addEventListener;
				xhr.upload.addEventListener = function(t, fn, b) {
					xhr.__listeners[t] = fn;
					origAddEventListener && origAddEventListener.apply(this, arguments);
				};
			}
		}
		
		patchXHR("open", function(orig) {
			return function(m, url, b) {
				initializeUploadListener(this);
				this.__url = url;
				orig.apply(this, [m, url, b]);
			}
		});

		patchXHR("getResponseHeader", function(orig) {
			return function(h) {
				return this.__fileApiXHR ? this.__fileApiXHR.getResponseHeader(h) : orig.apply(this, [h]);
			};
		});

		patchXHR("getAllResponseHeaders", function(orig) {
			return function() {
				return this.__fileApiXHR ? this.__fileApiXHR.abort() : (orig == null ? null : orig.apply(this));
			}
		});

		patchXHR("abort", function(orig) {
			return function() {
				return this.__fileApiXHR ? this.__fileApiXHR.abort() : (orig == null ? null : orig.apply(this));
			}
		});

		patchXHR("setRequestHeader", function(orig) {
			return function(header, value) {
				if (header === '__setXHR_') {
					initializeUploadListener(this);
					var val = value(this);
					// fix for angular < 1.2.0
					if (val instanceof Function) {
						val(this);
					}
				} else {
					this.__requestHeaders = this.__requestHeaders || {};
					this.__requestHeaders[header] = value;
					orig.apply(this, arguments);
				}
			}
		});

		patchXHR("send", function(orig) {
			return function() {
				var xhr = this;
				if (arguments[0] && arguments[0].__isShim) {
					var formData = arguments[0];
					var config = {
						url: xhr.__url,
						complete: function(err, fileApiXHR) {
							if (!err && xhr.__listeners['load']) 
								xhr.__listeners['load']({type: 'load', loaded: xhr.__loaded, total: xhr.__total, target: xhr, lengthComputable: true});
							if (!err && xhr.__listeners['loadend']) 
								xhr.__listeners['loadend']({type: 'loadend', loaded: xhr.__loaded, total: xhr.__total, target: xhr, lengthComputable: true});
							if (err === 'abort' && xhr.__listeners['abort']) 
								xhr.__listeners['abort']({type: 'abort', loaded: xhr.__loaded, total: xhr.__total, target: xhr, lengthComputable: true});
							if (fileApiXHR.status !== undefined) Object.defineProperty(xhr, 'status', {get: function() {return fileApiXHR.status}});
							if (fileApiXHR.statusText !== undefined) Object.defineProperty(xhr, 'statusText', {get: function() {return fileApiXHR.statusText}});
							Object.defineProperty(xhr, 'readyState', {get: function() {return 4}});
							if (fileApiXHR.response !== undefined) Object.defineProperty(xhr, 'response', {get: function() {return fileApiXHR.response}});
							Object.defineProperty(xhr, 'responseText', {get: function() {return fileApiXHR.responseText}});
							xhr.__fileApiXHR = fileApiXHR;
							xhr.onreadystatechange();
						},
						fileprogress: function(e) {
							e.target = xhr;
							xhr.__listeners['progress'] && xhr.__listeners['progress'](e);
							xhr.__total = e.total;
							xhr.__loaded = e.loaded;
						},
						headers: xhr.__requestHeaders
					}
					config.data = {};
					config.files = {}
					for (var i = 0; i < formData.data.length; i++) {
						var item = formData.data[i];
						if (item.val != null && item.val.name != null && item.val.size != null && item.val.type != null) {
							config.files[item.key] = item.val;
						} else {
							config.data[item.key] = item.val;
						}
					}

					setTimeout(function() {
						if (!hasFlash()) {
							throw 'Adode Flash Player need to be installed. To check ahead use "FileAPI.hasFlash"';
						}
						xhr.__fileApiXHR = FileAPI.upload(config);
					}, 1);
				} else {
					orig.apply(xhr, arguments);
				}
			}
		});
	}
	window.XMLHttpRequest.__isShim = true;
}

if (!window.FormData) {
	var wrapFileApi = function(elem) {
		if (!hasFlash()) {
			throw 'Adode Flash Player need to be installed. To check ahead use "FileAPI.hasFlash"';
		}
		if (!elem.__isWrapped && (elem.getAttribute('ng-file-select') != null || elem.getAttribute('data-ng-file-select') != null)) {
			var wrap = document.createElement('div');
			wrap.innerHTML = '<div class="js-fileapi-wrapper" style="position:relative; overflow:hidden"></div>';
			wrap = wrap.firstChild;
			var parent = elem.parentNode;
			parent.insertBefore(wrap, elem);
			parent.removeChild(elem);
			wrap.appendChild(elem);
			elem.__isWrapped = true;
		}
	};
	var changeFnWrapper = function(fn) {
		return function(evt) {
			var files = FileAPI.getFiles(evt);
			if (!evt.target) {
				evt.target = {};
			}
			evt.target.files = files;
			evt.target.files.item = function(i) {
				return evt.target.files[i] || null;
			}
			fn(evt);
		};
	};
	var isFileChange = function(elem, e) {
		return (e.toLowerCase() === 'change' || e.toLowerCase() === 'onchange') && elem.getAttribute('type') == 'file';
	}
	if (HTMLInputElement.prototype.addEventListener) {
		HTMLInputElement.prototype.addEventListener = (function(origAddEventListener) {
			return function(e, fn, b, d) {
				if (isFileChange(this, e)) {
					wrapFileApi(this);
					origAddEventListener.apply(this, [e, changeFnWrapper(fn), b, d]);
				} else {
					origAddEventListener.apply(this, [e, fn, b, d]);
				}
			}
		})(HTMLInputElement.prototype.addEventListener);
	}
	if (HTMLInputElement.prototype.attachEvent) {
		HTMLInputElement.prototype.attachEvent = (function(origAttachEvent) {
			return function(e, fn) {
				if (isFileChange(this, e)) {
					wrapFileApi(this);
					origAttachEvent.apply(this, [e, changeFnWrapper(fn)]);
				} else {
					origAttachEvent.apply(this, [e, fn]);
				}
			}
		})(HTMLInputElement.prototype.attachEvent);
	}

	window.FormData = FormData = function() {
		return {
			append: function(key, val, name) {
				this.data.push({
					key: key,
					val: val,
					name: name
				});
			},
			data: [],
			__isShim: true
		};
	};

	(function () {
		//load FileAPI
		if (!window.FileAPI) {
			window.FileAPI = {};
		}
		if (!FileAPI.upload) {
			var jsUrl, basePath, script = document.createElement('script'), allScripts = document.getElementsByTagName('script'), i, index, src;
			if (window.FileAPI.jsUrl) {
				jsUrl = window.FileAPI.jsUrl;
			} else if (window.FileAPI.jsPath) {
				basePath = window.FileAPI.jsPath;
			} else {
				for (i = 0; i < allScripts.length; i++) {
					src = allScripts[i].src;
					index = src.indexOf('angular-file-upload-shim.js')
					if (index == -1) {
						index = src.indexOf('angular-file-upload-shim.min.js');
					}
					if (index > -1) {
						basePath = src.substring(0, index);
						break;
					}
				}
			}

			if (FileAPI.staticPath == null) FileAPI.staticPath = basePath;
			script.setAttribute('src', jsUrl || basePath + "FileAPI.min.js");
			document.getElementsByTagName('head')[0].appendChild(script);
			FileAPI.hasFlash = hasFlash();
		}
	})();
}


if (!window.FileReader) {
	window.FileReader = function() {
		var _this = this, loadStarted = false;
		this.listeners = {};
		this.addEventListener = function(type, fn) {
			_this.listeners[type] = _this.listeners[type] || [];
			_this.listeners[type].push(fn);
		};
		this.removeEventListener = function(type, fn) {
			_this.listeners[type] && _this.listeners[type].splice(_this.listeners[type].indexOf(fn), 1);
		};
		this.dispatchEvent = function(evt) {
			var list = _this.listeners[evt.type];
			if (list) {
				for (var i = 0; i < list.length; i++) {
					list[i].call(_this, evt);
				}
			}
		};
		this.onabort = this.onerror = this.onload = this.onloadstart = this.onloadend = this.onprogress = null;

		function constructEvent(type, evt) {
			var e = {type: type, target: _this, loaded: evt.loaded, total: evt.total, error: evt.error};
			if (evt.result != null) e.target.result = evt.result;
			return e;
		};
		var listener = function(evt) {
			if (!loadStarted) {
				loadStarted = true;
				_this.onloadstart && this.onloadstart(constructEvent('loadstart', evt));
			}
			if (evt.type === 'load') {
				_this.onloadend && _this.onloadend(constructEvent('loadend', evt));
				var e = constructEvent('load', evt);
				_this.onload && _this.onload(e);
				_this.dispatchEvent(e);
			} else if (evt.type === 'progress') {
				var e = constructEvent('progress', evt);
				_this.onprogress && _this.onprogress(e);
				_this.dispatchEvent(e);
			} else {
				var e = constructEvent('error', evt);
				_this.onerror && _this.onerror(e);
				_this.dispatchEvent(e);
			}
		};
		this.readAsArrayBuffer = function(file) {
			FileAPI.readAsBinaryString(file, listener);
		}
		this.readAsBinaryString = function(file) {
			FileAPI.readAsBinaryString(file, listener);
		}
		this.readAsDataURL = function(file) {
			FileAPI.readAsDataURL(file, listener);
		}
		this.readAsText = function(file) {
			FileAPI.readAsText(file, listener);
		}
	}
}

})();

/**!
 * AngularJS file upload/drop directive with http post and progress
 * @author  Danial  <danial.farid@gmail.com>
 * @version 1.4.0
 */
(function() {
	
var angularFileUpload = angular.module('angularFileUpload', []);

angularFileUpload.service('$upload', ['$http', '$timeout', function($http, $timeout) {
	function sendHttp(config) {
		config.method = config.method || 'POST';
		config.headers = config.headers || {};
		config.transformRequest = config.transformRequest || function(data, headersGetter) {
			if (window.ArrayBuffer && data instanceof window.ArrayBuffer) {
				return data;
			}
			return $http.defaults.transformRequest[0](data, headersGetter);
		};

		if (window.XMLHttpRequest.__isShim) {
			config.headers['__setXHR_'] = function() {
				return function(xhr) {
					if (!xhr) return;
					config.__XHR = xhr;
					config.xhrFn && config.xhrFn(xhr);
					xhr.upload.addEventListener('progress', function(e) {
						if (config.progress) {
							$timeout(function() {
								if(config.progress) config.progress(e);
							});
						}
					}, false);
					//fix for firefox not firing upload progress end, also IE8-9
					xhr.upload.addEventListener('load', function(e) {
						if (e.lengthComputable) {
							if(config.progress) config.progress(e);
						}
					}, false);
				};
			};
		}

		var promise = $http(config);

		promise.progress = function(fn) {
			config.progress = fn;
			return promise;
		};
		promise.abort = function() {
			if (config.__XHR) {
				$timeout(function() {
					config.__XHR.abort();
				});
			}
			return promise;
		};
		promise.xhr = function(fn) {
			config.xhrFn = fn;
			return promise;
		};
		promise.then = (function(promise, origThen) {
			return function(s, e, p) {
				config.progress = p || config.progress;
				var result = origThen.apply(promise, [s, e, p]);
				result.abort = promise.abort;
				result.progress = promise.progress;
				result.xhr = promise.xhr;
				result.then = promise.then;
				return result;
			};
		})(promise, promise.then);

        promise.success = function (fn) {
            promise.then(function (response) {
                fn(response.data, response.status, response.headers, config);
            });
            return promise;
        };

        promise.error = function (fn) {
            promise.then(null, function (response) {
                fn(response.data, response.status, response.headers, config);
            });
            return promise;
        };
		//promise.success = (function (origThen) {
		//    return function (s) {
  //              return origThen(function (res) {
  //                  return s(res.data);
  //              });
		//    };
		//})(promise.then);
		
		return promise;
	}

	this.upload = function(config) {
		config.headers = config.headers || {};
		config.headers['Content-Type'] = undefined;
		config.transformRequest = config.transformRequest || $http.defaults.transformRequest;
		var formData = new FormData();
		var origTransformRequest = config.transformRequest;
		var origData = config.data;
		config.transformRequest = function(formData, headerGetter) {
			if (origData) {
				if (config.formDataAppender) {
					for (var key in origData) {
						var val = origData[key];
						config.formDataAppender(formData, key, val);
					}
				} else {
					for (var key in origData) {
						var val = origData[key];
						if (typeof origTransformRequest == 'function') {
							val = origTransformRequest(val, headerGetter);
						} else {
							for (var i = 0; i < origTransformRequest.length; i++) {
								var transformFn = origTransformRequest[i];
								if (typeof transformFn == 'function') {
									val = transformFn(val, headerGetter);
								}
							}
						}
						formData.append(key, val);
					}
				}
			}

			if (config.file != null) {
				var fileFormName = config.fileFormDataName || 'file';

				if (Object.prototype.toString.call(config.file) === '[object Array]') {
					var isFileFormNameString = Object.prototype.toString.call(fileFormName) === '[object String]'; 
					for (var i = 0; i < config.file.length; i++) {
						formData.append(isFileFormNameString ? fileFormName + i : fileFormName[i], config.file[i], config.file[i].name);
					}
				} else {
					formData.append(fileFormName, config.file, config.file.name);
				}
			}
			return formData;
		};

		config.data = formData;

		return sendHttp(config);
	};

	this.http = function(config) {
		return sendHttp(config);
	}
}]);

angularFileUpload.directive('ngFileSelect', [ '$parse', '$timeout', function($parse, $timeout) {
	return function(scope, elem, attr) {
		var fn = $parse(attr['ngFileSelect']);
		elem.bind('change', function(evt) {
			var files = [], fileList, i;
			fileList = evt.target.files;
			if (fileList != null) {
				for (i = 0; i < fileList.length; i++) {
					files.push(fileList.item(i));
				}
			}
			$timeout(function() {
				fn(scope, {
					$files : files,
					$event : evt
				});
			});
		});
		// removed this since it was confusing if the user click on browse and then cancel #181
//		elem.bind('click', function(){
//			this.value = null;
//		});
		
		// touch screens
		if (('ontouchstart' in window) ||
				(navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)) {
			elem.bind('touchend', function(e) {
				e.preventDefault();
				e.target.click();
			});
		}
	};
} ]);

angularFileUpload.directive('ngFileDropAvailable', [ '$parse', '$timeout', function($parse, $timeout) {
	return function(scope, elem, attr) {
		if ('draggable' in document.createElement('span')) {
			var fn = $parse(attr['ngFileDropAvailable']);
			$timeout(function() {
				fn(scope);
			});
		}
	};
} ]);

angularFileUpload.directive('ngFileDrop', [ '$parse', '$timeout', function($parse, $timeout) {
	return function(scope, elem, attr) {		
		if ('draggable' in document.createElement('span')) {
			var cancel = null;
			var fn = $parse(attr['ngFileDrop']);
			elem[0].addEventListener("dragover", function(evt) {
				$timeout.cancel(cancel);
				evt.stopPropagation();
				evt.preventDefault();
				elem.addClass(attr['ngFileDragOverClass'] || "dragover");
			}, false);
			elem[0].addEventListener("dragleave", function(evt) {
				cancel = $timeout(function() {
					elem.removeClass(attr['ngFileDragOverClass'] || "dragover");
				});
			}, false);
			
			var processing = 0;
			function traverseFileTree(files, item) {
				if (item.isDirectory) {
					var dirReader = item.createReader();
					processing++;
					dirReader.readEntries(function(entries) {
						for (var i = 0; i < entries.length; i++) {
							traverseFileTree(files, entries[i]);
						}
						processing--;
					});
				} else {
					processing++;
		    	    item.file(function(file) {
		    	    	processing--;
		    	    	files.push(file);
		    	    });
	    	  }
			}
			
			elem[0].addEventListener("drop", function(evt) {
				evt.stopPropagation();
				evt.preventDefault();
				elem.removeClass(attr['ngFileDragOverClass'] || "dragover");
				var files = [], items = evt.dataTransfer.items;
				if (items && items.length > 0 && items[0].webkitGetAsEntry) {
					for (var i = 0; i < items.length; i++) {
						traverseFileTree(files, items[i].webkitGetAsEntry());
					}
				} else {
					var fileList = evt.dataTransfer.files;
					if (fileList != null) {
						for (var i = 0; i < fileList.length; i++) {
							files.push(fileList.item(i));
						}
					}
				}
				(function callback(delay) {
					$timeout(function() {
						if (!processing) {
							fn(scope, {
								$files : files,
								$event : evt
							});
						} else {
							callback(10);
						}
					}, delay || 0)
				})();
			}, false);
		}
	};
} ]);

})();

/**!
 * FileAPI a set of tools for working with files
 *
 * @author  RubaXa  <trash@rubaxa.org>
 * @build	lib/canvas-to-blob lib/FileAPI.core lib/FileAPI.Image lib/FileAPI.Form lib/FileAPI.XHR lib/FileAPI.Flash lib/load-image-ios
 */
(function(a){var q=a.HTMLCanvasElement&&a.HTMLCanvasElement.prototype,g;if(g=a.Blob)try{g=Boolean(new Blob)}catch(l){g=!1}var m=g;if(g=m)if(g=a.Uint8Array)try{g=100===(new Blob([new Uint8Array(100)])).size}catch(f){g=!1}var c=g,e=a.BlobBuilder||a.WebKitBlobBuilder||a.MozBlobBuilder||a.MSBlobBuilder,n=(m||e)&&a.atob&&a.ArrayBuffer&&a.Uint8Array&&function(a){var s,f,g,n;s=0<=a.split(",")[0].indexOf("base64")?atob(a.split(",")[1]):decodeURIComponent(a.split(",")[1]);f=new ArrayBuffer(s.length);g=new Uint8Array(f);
for(n=0;n<s.length;n+=1)g[n]=s.charCodeAt(n);a=a.split(",")[0].split(":")[1].split(";")[0];if(m)return new Blob([c?g:f],{type:a});g=new e;g.append(f);return g.getBlob(a)};a.HTMLCanvasElement&&!q.toBlob&&(q.mozGetAsFile?q.toBlob=function(a,c){a(this.mozGetAsFile("blob",c))}:q.toDataURL&&n&&(q.toBlob=function(a,c){a(n(this.toDataURL(c)))}));a.dataURLtoBlob=n})(this);
(function(a,q){function g(b,d,a){if(b)if(u(b))for(var c=0,h=b.length;c<h;c++)c in b&&d.call(a,b[c],c,b);else for(c in b)b.hasOwnProperty(c)&&d.call(a,b[c],c,b)}function l(b,d,a){if(b){var c=k.uid(b);D[c]||(D[c]={});g(d.split(/\s+/),function(d){w?w.event.add(b,d,a):(D[c][d]||(D[c][d]=[]),D[c][d].push(a),b.addEventListener?b.addEventListener(d,a,!1):b.attachEvent?b.attachEvent("on"+d,a):b["on"+d]=a)})}}function m(b,d,a){if(b){var c=k.uid(b),h=D[c]||{};g(d.split(/\s+/),function(d){if(w)w.event.remove(b,
d,a);else{for(var K=h[d]||[],c=K.length;c--;)if(K[c]===a){K.splice(c,1);break}b.addEventListener?b.removeEventListener(d,a,!1):b.detachEvent?b.detachEvent("on"+d,a):b["on"+d]=null}})}}function f(b,d,a){l(b,d,function R(c){m(b,d,R);a(c)})}function c(b,d,a,c,h){b={type:a.type||a,target:b,result:c};k.extend(b,h);d(b)}function e(b,d,a,h){if(k.isFile(b)&&v&&v.prototype["readAs"+a]){var e=new v;l(e,L,function S(a){var h=a.type;"progress"==h?c(b,d,a,a.target.result,{loaded:a.loaded,total:a.total}):"loadend"==
h?(m(e,L,S),e=null):c(b,d,a,a.target.result)});try{if(h)e["readAs"+a](h,b);else e["readAs"+a](b)}catch(f){c(b,d,"error",q,{error:f.toString()})}}else c(b,d,"error",q,{error:"filreader_not_support_"+a})}function n(b,d){if(!b.type&&0==b.size%4096&&102400>=b.size)if(v)try{var a=new v;f(a,L,function(b){b="error"!=b.type;d(b);b&&a.abort()});a.readAsDataURL(b)}catch(c){d(!1)}else d(null);else d(!0)}function r(b){var d;b.getAsEntry?d=b.getAsEntry():b.webkitGetAsEntry&&(d=b.webkitGetAsEntry());return d}function s(b,
d){if(b)if(b.isFile)b.file(function(a){a.fullPath=b.fullPath;d(!1,[a])},function(){d("entry_file")});else if(b.isDirectory){var a=[];b.createReader().readEntries(function(b){k.afor(b,function(b,c){s(c,function(c,h){c||(a=a.concat(h));b?b():d(!1,a)})})},function(){d("directory_reader")})}else s(r(b),d);else d("empty_entry")}function u(b){return b&&"length"in b}function x(b){var d={};g(b,function(b,a){b&&"object"===typeof b&&(b=k.extend({},b));d[a]=b});return d}function y(b){b.target||(b.target=a.event&&
a.event.srcElement||C);3===b.target.nodeType&&(b.target=event.target.parentNode);return b}var p=1,d=function(){},h=navigator.userAgent,t=a.createObjectURL&&a||a.URL&&URL.revokeObjectURL&&URL||a.webkitURL&&webkitURL,E=a.Blob,F=a.File,v=a.FileReader,z=a.FormData,I=a.XMLHttpRequest,w=a.jQuery,A=!!(F&&v&&(a.Uint8Array||z||I.prototype.sendAsBinary))&&!(/safari\//i.test(h)&&!/chrome\//i.test(h)&&/windows/i.test(h)),h=A&&"withCredentials"in new I,E=A&&!!E&&!!(E.prototype.webkitSlice||E.prototype.mozSlice||
E.prototype.slice),C=a.document,N=a.dataURLtoBlob,V=/img/i,W=/canvas/i,X=/img|canvas/,M=/input/i,Y=/^data:[^,]+,/,G=Math.pow,Z=Math.round,J=Number,z=function(b){return Z(b*this)},H=new J(1024),O=new J(G(H,2)),P=new J(G(H,3)),G=new J(G(H,4)),D={},Q=[],L="abort progress error load loadend",$="status statusText readyState response responseXML responseText responseBody".split(" "),k={version:"1.2.7",cors:!1,html5:!0,debug:!1,pingUrl:!1,flashAbortTimeout:0,withCredentials:!0,staticPath:"./",flashUrl:0,
flashImageUrl:0,postNameConcat:function(b,d){return b+(null!=d?"["+d+"]":"")},ext2mime:{jpg:"image/jpeg",tif:"image/tiff"},accept:{"image/*":"art bm bmp dwg dxf cbr cbz fif fpx gif ico iefs jfif jpe jpeg jpg jps jut mcf nap nif pbm pcx pgm pict pm png pnm qif qtif ras rast rf rp svf tga tif tiff xbm xbm xpm xwd","audio/*":"m4a flac aac rm mpa wav wma ogg mp3 mp2 m3u mod amf dmf dsm far gdm imf it m15 med okt s3m stm sfx ult uni xm sid ac3 dts cue aif aiff wpl ape mac mpc mpp shn wv nsf spc gym adplug adx dsp adp ymf ast afc hps xs",
"video/*":"m4v 3gp nsv ts ty strm rm rmvb m3u ifo mov qt divx xvid bivx vob nrg img iso pva wmv asf asx ogm m2v avi bin dat dvr-ms mpg mpeg mp4 mkv avc vp3 svq3 nuv viv dv fli flv wpl"},chunkSize:0,chunkUploadRetry:0,chunkNetworkDownRetryTimeout:2E3,KB:(H.from=z,H),MB:(O.from=z,O),GB:(P.from=z,P),TB:(G.from=z,G),expando:"fileapi"+(new Date).getTime(),uid:function(b){return b?b[k.expando]=b[k.expando]||k.uid():(++p,k.expando+p)},log:function(){k.debug&&a.console&&console.log&&(console.log.apply?console.log.apply(console,
arguments):console.log([].join.call(arguments," ")))},newImage:function(b,d){var a=C.createElement("img");if(d)k.event.one(a,"error load",function(b){d("error"==b.type,a);a=null});a.src=b;return a},getXHR:function(){var b;if(I)b=new I;else if(a.ActiveXObject)try{b=new ActiveXObject("MSXML2.XMLHttp.3.0")}catch(d){b=new ActiveXObject("Microsoft.XMLHTTP")}return b},isArray:u,support:{dnd:h&&"ondrop"in C.createElement("div"),cors:h,html5:A,chunked:E,dataURI:!0},event:{on:l,off:m,one:f,fix:y},throttle:function(b,
d){var c,h;return function(){h=arguments;c||(b.apply(a,h),c=setTimeout(function(){c=0;b.apply(a,h)},d))}},F:function(){},parseJSON:function(b){return a.JSON&&JSON.parse?JSON.parse(b):(new Function("return ("+b.replace(/([\r\n])/g,"\\$1")+");"))()},trim:function(b){b=String(b);return b.trim?b.trim():b.replace(/^\s+|\s+$/g,"")},defer:function(){var b=[],a,c,h={resolve:function(e,f){h.resolve=d;c=e||!1;for(a=f;f=b.shift();)f(c,a)},then:function(d){c!==q?d(c,a):b.push(d)}};return h},queue:function(b){var d=
0,a=0,c=!1,h=!1,e={inc:function(){a++},next:function(){d++;setTimeout(e.check,0)},check:function(){d>=a&&!c&&e.end()},isFail:function(){return c},fail:function(){!c&&b(c=!0)},end:function(){h||(h=!0,b())}};return e},each:g,afor:function(b,d){var a=0,c=b.length;u(b)&&c--?function B(){d(c!=a&&B,b[a],a++)}():d(!1)},extend:function(b){g(arguments,function(d){g(d,function(d,a){b[a]=d})});return b},isFile:function(b){return A&&b&&b instanceof F},isCanvas:function(b){return b&&W.test(b.nodeName)},getFilesFilter:function(b){return(b=
"string"==typeof b?b:b.getAttribute&&b.getAttribute("accept")||"")?RegExp("("+b.replace(/\./g,"\\.").replace(/,/g,"|")+")$","i"):/./},readAsDataURL:function(b,d){k.isCanvas(b)?c(b,d,"load",k.toDataURL(b)):e(b,d,"DataURL")},readAsBinaryString:function(b,d){v&&v.prototype.readAsBinaryString?e(b,d,"BinaryString"):e(b,function(b){if("load"==b.type)try{b.result=k.toBinaryString(b.result)}catch(a){b.type="error",b.message=a.toString()}d(b)},"DataURL")},readAsArrayBuffer:function(b,d){e(b,d,"ArrayBuffer")},
readAsText:function(b,d,a){a||(a=d,d="utf-8");e(b,a,"Text",d)},toDataURL:function(b){if("string"==typeof b)return b;if(b.toDataURL)return b.toDataURL("image/png")},toBinaryString:function(b){return a.atob(k.toDataURL(b).replace(Y,""))},readAsImage:function(b,d,a){if(k.isFile(b))if(t){var h=t.createObjectURL(b);h===q?c(b,d,"error"):k.readAsImage(h,d,a)}else k.readAsDataURL(b,function(h){"load"==h.type?k.readAsImage(h.result,d,a):(a||"error"==h.type)&&c(b,d,h,null,{loaded:h.loaded,total:h.total})});
else k.isCanvas(b)?c(b,d,"load",b):V.test(b.nodeName)?b.complete?c(b,d,"load",b):f(b,"error abort load",function B(a){"load"==a.type&&t&&t.revokeObjectURL(b.src);m(b,"error abort load",B);c(b,d,a,b)}):b.iframe?c(b,d,{type:"error"}):(h=k.newImage(b.dataURL||b),k.readAsImage(h,d,a))},checkFileObj:function(b){var d={},a=k.accept;"object"==typeof b?d=b:d.name=(b+"").split(/\\|\//g).pop();null==d.type&&(d.type=d.name.split(".").pop());g(a,function(b,a){b=RegExp(b.replace(/\s/g,"|"),"i");b.test(d.type)&&
(d.type=k.ext2mime[d.type]||a.split("/")[0]+"/"+d.type)});return d},getDropFiles:function(b,d){var a=[],c=(b.originalEvent||b||"").dataTransfer||{},h=u(c.items)&&c.items[0]&&r(c.items[0]),e=k.queue(function(){d(a)});g((h?c.items:c.files)||[],function(b){e.inc();try{h?s(b,function(b,d){!b&&a.push.apply(a,d);e.next()}):n(b,function(d){d&&a.push(b);e.next()})}catch(d){e.next(),k.log("getDropFiles.error:",d.toString())}});e.check()},getFiles:function(b,d,a){var c=[];if(a)return k.filterFiles(k.getFiles(b),
d,a),null;b.jquery&&(b.each(function(){c=c.concat(k.getFiles(this))}),b=c,c=[]);"string"==typeof d&&(d=k.getFilesFilter(d));b.originalEvent?b=y(b.originalEvent):b.srcElement&&(b=y(b));b.dataTransfer?b=b.dataTransfer:b.target&&(b=b.target);b.files?(c=b.files,A||(c[0].blob=b,c[0].iframe=!0)):!A&&M.test(b&&b.tagName)?k.trim(b.value)&&(c=[k.checkFileObj(b.value)],c[0].blob=b,c[0].iframe=!0):u(b)&&(c=b);return k.filter(c,function(b){return!d||d.test(b.name)})},getInfo:function(b,d){var a={},c=Q.concat();
k.isFile(b)?function B(){var h=c.shift();h?h.test(b.type)?h(b,function(b,c){b?d(b):(k.extend(a,c),B())}):B():d(!1,a)}():d("not_support",a)},addInfoReader:function(b,d){d.test=function(d){return b.test(d)};Q.push(d)},filter:function(b,d){for(var a=[],c=0,h=b.length,e;c<h;c++)c in b&&(e=b[c],d.call(e,e,c,b)&&a.push(e));return a},filterFiles:function(b,d,a){if(b.length){var c=b.concat(),h,e=[],f=[];(function T(){c.length?(h=c.shift(),k.getInfo(h,function(b,a){(d(h,b?!1:a)?e:f).push(h);T()})):a(e,f)})()}else a([],
b)},upload:function(b){b=k.extend({prepare:k.F,beforeupload:k.F,upload:k.F,fileupload:k.F,fileprogress:k.F,filecomplete:k.F,progress:k.F,complete:k.F,pause:k.F,chunkSize:k.chunkSize,chunkUploadRetry:k.chunkUploadRetry},b);b.imageAutoOrientation&&!b.imageTransform&&(b.imageTransform={rotate:"auto"});var a=new k.XHR(b),c=this._getFilesDataArray(b.files),h=0,e=0,f=this,r,s=!1;g(c,function(b){h+=b.size});a.files=[];g(c,function(b){a.files.push(b.file)});a.total=h;a.loaded=0;a.filesLeft=c.length;b.beforeupload(a,
b);(r=function U(){var r=c.shift(),t=r&&r.file,p=!1,u=x(b);a.filesLeft=c.length;t&&t.name===k.expando&&(t=null,k.log("[warn] FileAPI.upload() \u2014 called without files"));("abort"!=a.statusText||a.current)&&r?(s=!1,(a.currentFile=t)&&b.prepare(t,u),this._getFormData(u,r,function(s){e||b.upload(a,b);var m=new k.XHR(k.extend({},u,{upload:t?function(){b.fileupload(t,m,u)}:d,progress:t?function(d){p||(b.fileprogress({type:"progress",total:r.total=d.total,loaded:r.loaded=d.loaded},t,m,u),b.progress({type:"progress",
total:h,loaded:a.loaded=e+d.loaded/d.total*r.size|0},t,m,u))}:d,complete:function(d){p=!0;g($,function(b){a[b]=m[b]});t&&(r.loaded=r.total,this.progress(r),e+=r.size,a.loaded=e,b.filecomplete(d,m,t,u));U.call(f)}}));a.abort=function(b){b||(c.length=0);this.current=b;m.abort()};m.send(s)})):(b.complete(200==a.status||201==a.status?!1:a.statusText||"error",a,b),s=!0)}).call(this);a.append=function(b,d){b=k._getFilesDataArray([].concat(b));g(b,function(b){h+=b.size;a.files.push(b.file);d?c.unshift(b):
c.push(b)});a.statusText="";s&&r.call(f)};a.remove=function(b){var d=-1;g(c,function(a){d++;if(a.file==b)return c.splice(d,1)})};return a},_getFilesDataArray:function(b){var d=[],a={};if(M.test(b&&b.tagName)){var c=k.getFiles(b);a[b.name||"file"]=null!==b.getAttribute("multiple")?c:c[0]}else u(b)&&M.test(b[0]&&b[0].tagName)?g(b,function(b){a[b.name||"file"]=k.getFiles(b)}):a=b;g(a,function B(b,a){u(b)?g(b,function(b,d){B(b,a)}):b&&b.name&&d.push({name:a,file:b,size:b.size,total:b.size,loaded:0})});
d.length||d.push({file:{name:k.expando}});return d},_getFormData:function(b,d,a){var c=d.file,h=d.name,e=c.name,f=c.type;d=k.support.transform&&b.imageTransform;var r=new k.Form,t=k.queue(function(){a(r)}),s=d&&(0<parseInt(d.maxWidth||d.minWidth||d.width,10)||d.rotate);g(b.data,function aa(b,d){"object"==typeof b?g(b,function(b,a){aa(b,k.postNameConcat(d,a))}):r.append(d,b)});k.Image&&d&&(/image/.test(c.type)||X.test(c.nodeType))?(t.inc(),s&&(d=[d]),k.Image.transform(c,d,b.imageAutoOrientation,function(d,
a){s&&!d?(N||k.flashEngine||(a[0]=k.toBinaryString(a[0]),r.multipart=!0),r.append(h,a[0],e,f)):(d||(g(a,function(b,d){N||k.flashEngine||(b=k.toBinaryString(b),r.multipart=!0);r.append(h+"["+d+"]",b,e,f)}),h+="[original]"),(d||b.imageOriginal)&&r.append(h,c,e,f));t.next()})):e!==k.expando&&r.append(h,c,e);t.check()},reset:function(b,d){var a,c;w?(c=w(b).clone(!0).insertBefore(b).val("")[0],d||w(b).remove()):(a=b.parentNode,c=a.insertBefore(b.cloneNode(!0),b),c.value="",d||a.removeChild(b),g(D[k.uid(b)],
function(d,a){g(d,function(d){m(b,a,d);l(c,a,d)})}));return c},load:function(b,d){var a=k.getXHR();a?(a.open("GET",b,!0),a.overrideMimeType&&a.overrideMimeType("text/plain; charset=x-user-defined"),l(a,"progress",function(b){b.lengthComputable&&d({type:b.type,loaded:b.loaded,total:b.total},a)}),a.onreadystatechange=function(){if(4==a.readyState)if(a.onreadystatechange=null,200==a.status){b=b.split("/");var c={name:b[b.length-1],size:a.getResponseHeader("Content-Length"),type:a.getResponseHeader("Content-Type")};
c.dataURL="data:"+c.type+";base64,"+k.encode64(a.responseBody||a.responseText);d({type:"load",result:c})}else d({type:"error"})},a.send(null)):d({type:"error"});return a},encode64:function(b){var d="",a=0;for("string"!==typeof b&&(b=String(b));a<b.length;){var c=b.charCodeAt(a++)&255,h=b.charCodeAt(a++)&255,e=b.charCodeAt(a++)&255,f=c>>2,c=(c&3)<<4|h>>4;isNaN(h)?h=e=64:(h=(h&15)<<2|e>>6,e=isNaN(e)?64:e&63);d+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(f)+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(c)+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(h)+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(e)}return d}};k.addInfoReader(/^image/,function(b,d){if(!b.__dimensions){var a=b.__dimensions=k.defer();k.readAsImage(b,function(b){var d=b.target;a.resolve("load"==b.type?!1:"error",{width:d.width,height:d.height})})}b.__dimensions.then(d)});k.event.dnd=function(b,d,a){var c,h;a||(a=d,d=k.F);v?(l(b,"dragenter dragleave dragover",function(b){for(var a=
((b.originalEvent||b||"").dataTransfer||{}).types,e=a&&a.length;e--;)~a[e].indexOf("File")&&(b.preventDefault(),h!==b.type&&(h=b.type,"dragleave"!=h&&d.call(b.currentTarget,!0,b),clearTimeout(c),c=setTimeout(function(){d.call(b.currentTarget,"dragleave"!=h,b)},50)))}),l(b,"drop",function(b){b.preventDefault();h=0;d.call(b.currentTarget,!1,b);k.getDropFiles(b,function(d){a.call(b.currentTarget,d,b)})})):k.log("Drag'n'Drop -- not supported")};w&&!w.fn.dnd&&(w.fn.dnd=function(b,d){return this.each(function(){k.event.dnd(this,
b,d)})});a.FileAPI=k.extend(k,a.FileAPI);k.log("version: "+k.version);k.log("protocol: "+a.location.protocol);C.doctype?k.log("doctype: ["+C.doctype.name+"] "+C.doctype.publicId+" "+C.doctype.systemId):k.log("doctype: unknown");g(C.getElementsByTagName("meta"),function(b){/x-ua-compatible/i.test(b.getAttribute("http-equiv"))&&k.log("meta.http-equiv: "+b.getAttribute("content"))});k.flashUrl||(k.flashUrl=k.staticPath+"FileAPI.flash.swf");k.flashImageUrl||(k.flashImageUrl=k.staticPath+"FileAPI.flash.image.swf")})(window);
(function(a,q,g){function l(a,c){if(!(this instanceof l))return new l(a);this.file=a;this.better=!c;this.matrix={sx:0,sy:0,sw:0,sh:0,dx:0,dy:0,dw:0,dh:0,resize:0,deg:0}}var m=Math.min,f=Math.round,c=!1,e={8:270,3:180,6:90};try{c=-1<q.createElement("canvas").toDataURL("image/png").indexOf("data:image/png")}catch(n){}l.prototype={constructor:l,set:function(c){a.extend(this.matrix,c);return this},crop:function(a,c,e,f){e===g&&(e=a,f=c,a=c=0);return this.set({sx:a,sy:c,sw:e,sh:f||e})},resize:function(a,
c,e){"string"==typeof c&&(e=c,c=a);return this.set({dw:a,dh:c,resize:e})},preview:function(a,c){return this.set({dw:a,dh:c||a,resize:"preview"})},rotate:function(a){return this.set({deg:a})},_load:function(c,e){var f=this;a.readAsImage(c,function(a){e.call(f,"load"!=a.type,a.result)})},_apply:function(c,e){var f=q.createElement("canvas"),g=this.getMatrix(c),m=f.getContext("2d"),p=g.deg,d=g.dw,h=g.dh,t=c.width,n=c.height,l,v=c,z=a.renderImageToCanvas;c._type=this.file.type;if(this.better)for(;2<Math.min(t/
d,n/h);)t=~~(t/2+0.5),n=~~(n/2+0.5),l=q.createElement("canvas"),l.width=t,l.height=n,v!==c?(z(l,v,0,0,v.width,v.height,0,0,t,n),v=l):(v=l,z(v,c,g.sx,g.sy,g.sw,g.sh,0,0,t,n),g.sx=g.sy=g.sw=g.sh=0);f.width=p%180?h:d;f.height=p%180?d:h;m.rotate(p*Math.PI/180);z(f,v,g.sx,g.sy,g.sw||v.width,g.sh||v.height,180==p||270==p?-d:0,90==p||180==p?-h:0,d,h);e.call(this,!1,f)},getMatrix:function(c){var e=a.extend({},this.matrix),g=e.sw=e.sw||c.width;c=e.sh=e.sh||c.height;var n=e.dw=e.dw||e.sw,l=e.dh=e.dh||e.sh,
p=g/c,d=n/l,h=e.resize;if("preview"==h){if(n!=g||l!=c)if(d>=p?(p=g,h=p/d):(h=c,p=h*d),p!=g||h!=c)e.sx=~~((g-p)/2),e.sy=~~((c-h)/2),g=p,c=h}else h&&(g>n||c>l?"min"==h?(n=f(p<d?m(g,n):l*p),l=f(p<d?n/p:m(c,l))):(n=f(p>=d?m(g,n):l*p),l=f(p>=d?n/p:m(c,l))):(n=g,l=c));e.sw=g;e.sh=c;e.dw=n;e.dh=l;return e},_trans:function(a){this._load(this.file,function(c,e){c?a(c):this._apply(e,a)})},get:function(c){if(a.support.transform){var f=this;"auto"==f.matrix.deg?a.getInfo(this.file,function(a,g){f.matrix.deg=
e[g&&g.exif&&g.exif.Orientation]||0;f._trans(c)}):f._trans(c)}else c("not_support")},toData:function(a){this.get(a)}};l.exifOrientation=e;l.transform=function(c,e,f,n){a.getInfo(c,function(m,p){var d={},h=a.queue(function(a){n(a,d)});m?h.fail():a.each(e,function(a,e){if(!h.isFail()){var n=l(p.nodeType?p:c);if("function"==typeof a)a(p,n);else if(a.width)n[a.preview?"preview":"resize"](a.width,a.height,a.type);else a.maxWidth&&(p.width>a.maxWidth||p.height>a.maxHeight)&&n.resize(a.maxWidth,a.maxHeight,
"max");a.rotate===g&&f&&(a.rotate="auto");n.rotate(a.rotate);h.inc();n.toData(function(a,c){a?h.fail():(d[e]=c,h.next())})}})})};a.renderImageToCanvas=function(a,c,e,f,g,n,d,h,t,m){a.getContext("2d").drawImage(c,e,f,g,n,d,h,t,m);return a};a.support.canvas=a.support.transform=c;a.Image=l})(FileAPI,document);
(function(a,q,g){var l=q.encodeURIComponent,m=q.FormData;q=function(){this.items=[]};q.prototype={append:function(a,c,e,g){this.items.push({name:a,blob:c&&c.blob||(void 0==c?"":c),file:c&&(e||c.name),type:c&&(g||c.type)})},each:function(a){for(var c=0,e=this.items.length;c<e;c++)a.call(this,this.items[c])},toData:function(f,c){c._chunked=a.support.chunked&&0<c.chunkSize&&1==a.filter(this.items,function(a){return a.file}).length;a.support.html5?this.multipart||!m?(a.log("FileAPI.Form.toMultipartData"),
this.toMultipartData(f)):c._chunked?(a.log("FileAPI.Form.toPlainData"),this.toPlainData(f)):(a.log("FileAPI.Form.toFormData"),this.toFormData(f)):(a.log("FileAPI.Form.toHtmlData"),this.toHtmlData(f))},_to:function(f,c,e,g){var m=a.queue(function(){c(f)});this.each(function(a){e(a,f,m,g)});m.check()},toHtmlData:function(f){this._to(g.createDocumentFragment(),f,function(c,e){var f=c.blob,m;c.file?(a.reset(f,!0),f.name=c.name,e.appendChild(f)):(m=g.createElement("input"),m.name=c.name,m.type="hidden",
m.value=f,e.appendChild(m))})},toPlainData:function(a){this._to({},a,function(a,e,f){a.file&&(e.type=a.file);a.blob.toBlob?(f.inc(),a.blob.toBlob(function(g){e.name=a.name;e.file=g;e.size=g.length;e.type=a.type;f.next()},"image/png")):a.file?(e.name=a.blob.name,e.file=a.blob,e.size=a.blob.size,e.type=a.type):(e.params||(e.params=[]),e.params.push(encodeURIComponent(a.name)+"="+encodeURIComponent(a.blob)));e.start=-1;e.end=e.file&&e.file.FileAPIReadPosition||-1;e.retry=0})},toFormData:function(a){this._to(new m,
a,function(a,e,f){a.file&&e.append("_"+a.name,a.file);a.blob&&a.blob.toBlob?(f.inc(),a.blob.toBlob(function(g){e.append(a.name,g,a.file);f.next()},"image/png")):a.file?e.append(a.name,a.blob,a.file):e.append(a.name,a.blob)})},toMultipartData:function(f){this._to([],f,function(c,e,f,g){var m=!!c.file,q=c.blob,x=function(a){e.push("--_"+g+('\r\nContent-Disposition: form-data; name="'+c.name+'"'+(m?'; filename="'+l(c.file)+'"':"")+(m?"\r\nContent-Type: "+(c.type||"application/octet-stream"):"")+"\r\n\r\n"+
(m?a:l(a))+"\r\n"));f.next()};f.inc();a.isFile(q)?a.readAsBinaryString(q,function(a){"load"==a.type&&x(a.result)}):x(q)},a.expando)}};a.Form=q})(FileAPI,window,document);
(function(a,q){var g=function(){},l=function(a){this.uid=q.uid();this.xhr={abort:g,getResponseHeader:g,getAllResponseHeaders:g};this.options=a};l.prototype={status:0,statusText:"",getResponseHeader:function(a){return this.xhr.getResponseHeader(a)},getAllResponseHeaders:function(){return this.xhr.getAllResponseHeaders()||{}},end:function(m,f){var c=this,e=c.options;c.end=c.abort=g;c.status=m;f&&(c.statusText=f);q.log("xhr.end:",m,f);e.complete(200==m||201==m?!1:c.statusText||"unknown",c);c.xhr&&c.xhr.node&&
setTimeout(function(){var e=c.xhr.node;try{e.parentNode.removeChild(e)}catch(f){}try{delete a[c.uid]}catch(g){}a[c.uid]=c.xhr.node=null},9)},abort:function(){this.end(0,"abort");this.xhr&&(this.xhr.aborted=!0,this.xhr.abort())},send:function(a){var f=this,c=this.options;a.toData(function(a){c.upload(c,f);f._send.call(f,c,a)},c)},_send:function(g,f){var c=this,e,n=c.uid,l=g.url;q.log("XHR._send:",f);g.cache||(l+=(~l.indexOf("?")?"&":"?")+q.uid());f.nodeName?(g.upload(g,c),e=document.createElement("div"),
e.innerHTML='<form target="'+n+'" action="'+l+'" method="POST" enctype="multipart/form-data" style="position: absolute; top: -1000px; overflow: hidden; width: 1px; height: 1px;"><iframe name="'+n+'" src="javascript:false;"></iframe><input value="'+n+'" name="callback" type="hidden"/></form>',c.xhr.abort=function(){var a=e.getElementsByName("iframe")[0];if(a)try{a.stop?a.stop():a.contentWindow.stop?a.contentWindow.stop():a.contentWindow.document.execCommand("Stop")}catch(c){}e=null},l=e.getElementsByTagName("form")[0],
l.appendChild(f),q.log(l.parentNode.innerHTML),document.body.appendChild(e),c.xhr.node=e,a[n]=function(a,f,g){c.readyState=4;c.responseText=g;c.end(a,f);e=null},c.readyState=2,l.submit(),l=null):this.xhr&&this.xhr.aborted?q.log("Error: already aborted"):(e=c.xhr=q.getXHR(),f.params&&(l+=(0>l.indexOf("?")?"?":"&")+f.params.join("&")),e.open("POST",l,!0),q.withCredentials&&(e.withCredentials="true"),g.headers&&g.headers["X-Requested-With"]||e.setRequestHeader("X-Requested-With","XMLHttpRequest"),q.each(g.headers,
function(a,c){e.setRequestHeader(c,a)}),g._chunked?(e.upload&&e.upload.addEventListener("progress",function(a){f.retry||g.progress({type:a.type,total:f.size,loaded:f.start+a.loaded,totalSize:f.size},c,g)},!1),e.onreadystatechange=function(){c.status=e.status;c.statusText=e.statusText;c.readyState=e.readyState;if(4==e.readyState){for(var a in{"":1,XML:1,Text:1,Body:1})c["response"+a]=e["response"+a];e.onreadystatechange=null;if(!e.status||0<e.status-201)if(q.log("Error: "+e.status),(!e.status&&!e.aborted||
500==e.status||416==e.status)&&++f.retry<=g.chunkUploadRetry){a=e.status?0:q.chunkNetworkDownRetryTimeout;g.pause(f.file,g);var l=parseInt(e.getResponseHeader("X-Last-Known-Byte"),10);q.log("X-Last-Known-Byte: "+l);f.end=l?l:f.start-1;setTimeout(function(){c._send(g,f)},a)}else c.end(e.status);else f.retry=0,f.end==f.size-1?c.end(e.status):(l=parseInt(e.getResponseHeader("X-Last-Known-Byte"),10),q.log("X-Last-Known-Byte: "+l),l&&(f.end=l),f.file.FileAPIReadPosition=f.end,setTimeout(function(){c._send(g,
f)},0));e=null}},f.start=f.end+1,f.end=Math.max(Math.min(f.start+g.chunkSize,f.size)-1,f.start),(n="slice")in f.file||(n="mozSlice")in f.file||(n="webkitSlice"),e.setRequestHeader("Content-Range","bytes "+f.start+"-"+f.end+"/"+f.size),e.setRequestHeader("Content-Disposition","attachment; filename="+encodeURIComponent(f.name)),e.setRequestHeader("Content-Type",f.type||"application/octet-stream"),n=f.file[n](f.start,f.end+1),e.send(n),n=null):(e.upload&&e.upload.addEventListener("progress",q.throttle(function(a){g.progress(a,
c,g)},100),!1),e.onreadystatechange=function(){c.status=e.status;c.statusText=e.statusText;c.readyState=e.readyState;if(4==e.readyState){for(var a in{"":1,XML:1,Text:1,Body:1})c["response"+a]=e["response"+a];e.onreadystatechange=null;c.end(e.status);e=null}},q.isArray(f)?(e.setRequestHeader("Content-Type","multipart/form-data; boundary=_"+q.expando),f=f.join("")+"--_"+q.expando+"--",e.sendAsBinary?e.sendAsBinary(f):(n=Array.prototype.map.call(f,function(a){return a.charCodeAt(0)&255}),e.send((new Uint8Array(n)).buffer))):
e.send(f)))}};q.XHR=l})(window,FileAPI);
(function(a,q,g){a.support.flash=function(){var g=q.navigator,m=g.mimeTypes,f=!1;if(g.plugins&&"object"==typeof g.plugins["Shockwave Flash"])f=g.plugins["Shockwave Flash"].description&&!(m&&m["application/x-shockwave-flash"]&&!m["application/x-shockwave-flash"].enabledPlugin);else try{f=!(!q.ActiveXObject||!new ActiveXObject("ShockwaveFlash.ShockwaveFlash"))}catch(c){}f||a.log("Flash -- does not supported.");return f}();/^file:/i.test(q.location)?a.log("[warn] Flash does not work on `file:` protocol."):!a.support.flash||
a.html5&&a.support.html5&&(!a.cors||a.support.cors)||function(){function l(a){return('<object id="#id#" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="'+(a.width||"100%")+'" height="'+(a.height||"100%")+'"><param name="movie" value="#src#" /><param name="flashvars" value="#flashvars#" /><param name="swliveconnect" value="true" /><param name="allowscriptaccess" value="always" /><param name="allownetworking" value="all" /><param name="menu" value="false" /><param name="wmode" value="#wmode#" /><embed flashvars="#flashvars#" swliveconnect="true" allownetworking="all" allowscriptaccess="always" name="#id#" src="#src#" width="'+
(a.width||"100%")+'" height="'+(a.height||"100%")+'" menu="false" wmode="transparent" type="application/x-shockwave-flash"></embed></object>').replace(/#(\w+)#/ig,function(c,e){return a[e]})}function m(a,c){if(a&&a.style){var e,f;for(e in c){f=c[e];"number"==typeof f&&(f+="px");try{a.style[e]=f}catch(g){}}}}function f(d,c){a.each(c,function(a,c){var e=d[c];d[c]=function(){this.parent=e;return a.apply(this,arguments)}})}function c(d){var c=d.wid=a.uid();p._fn[c]=d;return"FileAPI.Flash._fn."+c}function e(a){try{p._fn[a.wid]=
null,delete p._fn[a.wid]}catch(c){}}function n(a,c){if(!y.test(a)){if(/^\.\//.test(a)||"/"!=a.charAt(0)){var e=location.pathname,e=e.substr(0,e.lastIndexOf("/"));a=(e+"/"+a).replace("/./","/")}"//"!=a.substr(0,2)&&(a="//"+location.host+a);y.test(a)||(a=location.protocol+a)}c&&(a+=(/\?/.test(a)?"&":"?")+c);return a}function r(d,h,f){function q(){try{p.get(v).setImage(h)}catch(d){a.log('flash.setImage -- can not set "base64":',d)}}var r,v=a.uid(),s=g.createElement("div");for(r in d)s.setAttribute("data-img-"+
r,d[r]);m(s,d);s.innerHTML=l(a.extend({id:v,src:n(a.flashImageUrl,"r="+a.uid()),wmode:"opaque",flashvars:"scale="+d.scale+"&callback="+c(function w(){e(w);setTimeout(q,99);return!0})},d));f(!1,s);s=null}var s=a.uid(),u=0,x={},y=/^https?:/i,p={_fn:{},init:function(){var d=g.body&&g.body.firstChild;if(d){do if(1==d.nodeType){a.log("FlashAPI.Flash.init...");var c=g.createElement("div");m(c,{top:1,right:1,width:5,height:5,position:"absolute"});d.parentNode.insertBefore(c,d);p.publish(c,s);return}while(d=
d.nextSibling)}10>u&&setTimeout(p.init,50*++u)},publish:function(d,c){d.innerHTML=l({id:c,src:n(a.flashUrl,"r="+a.version),wmode:"transparent",flashvars:"callback=FileAPI.Flash.event&flashId="+c+"&storeKey="+navigator.userAgent.match(/\d/ig).join("")+"_"+a.version+(p.isReady||(a.pingUrl?"&ping="+a.pingUrl:""))+"&timeout="+a.flashAbortTimeout})},ready:function(){p.ready=a.F;p.isReady=!0;p.patch();a.event.on(g,"mouseover",p.mouseover);a.event.on(g,"click",function(a){p.mouseover(a)&&(a.preventDefault?
a.preventDefault():a.returnValue=!0)})},getWrapper:function(a){do if(/js-fileapi-wrapper/.test(a.className))return a;while((a=a.parentNode)&&a!==g.body)},mouseover:function(d){d=a.event.fix(d).target;if(/input/i.test(d.nodeName)&&"file"==d.type){var c=d.getAttribute(s);if("i"==c||"r"==c)return!1;if("p"!=c){d.setAttribute(s,"i");var c=g.createElement("div"),e=p.getWrapper(d);if(!e){a.log("flash.mouseover.error: js-fileapi-wrapper not found");return}m(c,{top:0,left:0,width:d.offsetWidth+100,height:d.offsetHeight+
100,zIndex:"1000000",position:"absolute"});e.appendChild(c);p.publish(c,a.uid());d.setAttribute(s,"p")}return!0}},event:function(d){var c=d.type;if("ready"==c){try{p.getInput(d.flashId).setAttribute(s,"r")}catch(e){}p.ready();setTimeout(function(){p.mouseenter(d)},50);return!0}"ping"===c?a.log("(flash -> js).ping:",[d.status,d.savedStatus],d.error):"log"===c?a.log("(flash -> js).log:",d.target):c in p&&setTimeout(function(){a.log("Flash.event."+d.type+":",d);p[c](d)},1)},mouseenter:function(d){var c=
p.getInput(d.flashId);if(c){p.cmd(d,"multiple",null!=c.getAttribute("multiple"));var e=[],f={};a.each((c.getAttribute("accept")||"").split(/,\s*/),function(d){a.accept[d]&&a.each(a.accept[d].split(" "),function(a){f[a]=1})});a.each(f,function(a,d){e.push(d)});p.cmd(d,"accept",e.length?e.join(",")+","+e.join(",").toUpperCase():"*")}},get:function(a){return g[a]||q[a]||g.embeds[a]},getInput:function(d){try{var c=p.getWrapper(p.get(d));if(c)return c.getElementsByTagName("input")[0]}catch(e){a.log('Can not find "input" by flashId:',
d,e)}},select:function(d){var c=p.getInput(d.flashId),e=a.uid(c);d=d.target.files;a.each(d,function(d){a.checkFileObj(d)});x[e]=d;g.createEvent?(e=g.createEvent("Event"),e.initEvent("change",!0,!1),c.dispatchEvent(e)):g.createEventObject&&(e=g.createEventObject(),c.fireEvent("onchange",e))},cmd:function(d,c,e,f){try{return a.log("(js -> flash)."+c+":",e),p.get(d.flashId||d).cmd(c,e)}catch(g){a.log("(js -> flash).onError:",g),f||setTimeout(function(){p.cmd(d,c,e,!0)},50)}},patch:function(){a.flashEngine=
a.support.transform=!0;f(a,{getFiles:function(d,c,e){if(e)return a.filterFiles(a.getFiles(d),c,e),null;var f=a.isArray(d)?d:x[a.uid(d.target||d.srcElement||d)];if(!f)return this.parent.apply(this,arguments);c&&(c=a.getFilesFilter(c),f=a.filter(f,function(a){return c.test(a.name)}));return f},getInfo:function(d,f){if(d&&!d.flashId)this.parent.apply(this,arguments);else{if(!d.__info){var g=d.__info=a.defer();p.cmd(d,"getFileInfo",{id:d.id,callback:c(function F(a,c){e(F);g.resolve(a,d.info=c)})})}d.__info.then(f)}}});
a.support.transform=!0;a.Image&&f(a.Image.prototype,{get:function(a,c){this.set({scaleMode:c||"noScale"});this.parent(a)},_load:function(c,e){a.log("FileAPI.Image._load:",c);if(c&&!c.flashId)this.parent.apply(this,arguments);else{var f=this;a.getInfo(c,function(a,g){e.call(f,a,c)})}},_apply:function(d,f){a.log("FileAPI.Image._apply:",d);if(d&&!d.flashId)this.parent.apply(this,arguments);else{var g=this.getMatrix(d.info);p.cmd(d,"imageTransform",{id:d.id,matrix:g,callback:c(function F(c,l){a.log("FileAPI.Image._apply.callback:",
c);e(F);c?f(c):!a.support.dataURI||3E4<l.length?r({width:g.deg%180?g.dh:g.dw,height:g.deg%180?g.dw:g.dh,scale:g.scaleMode},l,f):a.newImage("data:"+d.type+";base64,"+l,f)})})}},toData:function(c){var e=this.file,f=e.info,g=this.getMatrix(f);e&&!e.flashId?this.parent.apply(this,arguments):("auto"==g.deg&&(g.deg=a.Image.exifOrientation[f&&f.exif&&f.exif.Orientation]||0),c.call(this,!e.info,{id:e.id,flashId:e.flashId,name:e.name,type:e.type,matrix:g}))}});f(a.Form.prototype,{toData:function(c){for(var e=
this.items,f=e.length;f--;)if(e[f].file&&e[f].blob&&!e[f].blob.flashId)return this.parent.apply(this,arguments);a.log("flash.Form.toData");c(e)}});f(a.XHR.prototype,{_send:function(d,f){if(f.nodeName||f.append&&a.support.html5||a.isArray(f)&&"string"===typeof f[0])return this.parent.apply(this,arguments);var g={},l={},m=this,q,r;a.each(f,function(a){a.file?(l[a.name]=a={id:a.blob.id,name:a.blob.name,matrix:a.blob.matrix,flashId:a.blob.flashId},r=a.id,q=a.flashId):g[a.name]=a.blob});if(r||q)a.log("flash.XHR._send:",
q,r,l);else return this.parent.apply(this,arguments);m.xhr={headers:{},abort:function(){p.cmd(q,"abort",{id:r})},getResponseHeader:function(a){return this.headers[a]},getAllResponseHeaders:function(){return this.headers}};var s=a.queue(function(){p.cmd(q,"upload",{url:n(d.url),data:g,files:l,headers:d.headers,callback:c(function A(c){var f=c.type,g=c.result;a.log("flash.upload."+f+":",c);if("progress"==f)c.loaded=Math.min(c.loaded,c.total),c.lengthComputable=!0,d.progress(c);else if("complete"==f)e(A),
"string"==typeof g&&(m.responseText=g.replace(/%22/g,'"').replace(/%5c/g,"\\").replace(/%26/g,"&").replace(/%25/g,"%")),m.end(c.status||200);else if("abort"==f||"error"==f)m.end(c.status||0,c.message),e(A)})})});a.each(l,function(c){s.inc();a.getInfo(c,s.next)});s.check()}})}};a.Flash=p;a.newImage("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==",function(c,e){a.support.dataURI=!(1!=e.width||1!=e.height);p.init()})}()})(FileAPI,window,document);
(function(a){a(FileAPI)})(function(a){if(window.navigator&&window.navigator.platform&&/iP(hone|od|ad)/.test(window.navigator.platform)){var q=a.renderImageToCanvas;a.detectSubsampling=function(a){var l;return 1048576<a.width*a.height?(l=document.createElement("canvas"),l.width=l.height=1,l=l.getContext("2d"),l.drawImage(a,-a.width+1,0),0===l.getImageData(0,0,1,1).data[3]):!1};a.detectVerticalSquash=function(a,l){var m=a.naturalHeight||a.height,f=document.createElement("canvas"),c=f.getContext("2d"),
e,n,q;l&&(m/=2);f.width=1;f.height=m;c.drawImage(a,0,0);f=c.getImageData(0,0,1,m).data;c=0;for(n=e=m;n>c;)q=f[4*(n-1)+3],0===q?e=n:c=n,n=e+c>>1;return n/m||1};a.renderImageToCanvas=function(g,l,m,f,c,e,n,r,s,u){if("image/jpeg"===l._type){var x=g.getContext("2d"),y=document.createElement("canvas"),p=y.getContext("2d"),d,h;y.width=1024;y.height=1024;x.save();if(d=a.detectSubsampling(l))m/=2,f/=2,c/=2,e/=2;h=a.detectVerticalSquash(l,d);if(d||1!==h){f*=h;s=Math.ceil(1024*s/c);u=Math.ceil(1024*u/e/h);
for(h=r=0;h<e;){for(d=n=0;d<c;)p.clearRect(0,0,1024,1024),p.drawImage(l,m,f,c,e,-d,-h,c,e),x.drawImage(y,0,0,1024,1024,n,r,s,u),d+=1024,n+=s;h+=1024;r+=u}x.restore();return g}}return q(g,l,m,f,c,e,n,r,s,u)}}});"undefined"!==typeof ajs&&ajs.loaded&&ajs.loaded("{fileapi}FileAPI.min");"function"===typeof define&&define.amd&&define("FileAPI",[],function(){return window.FileAPI||{}});
/*
*  AngularJs Fullcalendar Wrapper for the JQuery FullCalendar
*  API @ http://arshaw.com/fullcalendar/
*
*  Angular Calendar Directive that takes in the [eventSources] nested array object as the ng-model and watches it deeply changes.
*       Can also take in multiple event urls as a source object(s) and feed the events per view.
*       The calendar will watch any eventSource array and update itself when a change is made.
*
*/

angular.module('ui.calendar', [])
  .constant('uiCalendarConfig', {})
  .controller('uiCalendarCtrl', ['$scope', '$timeout', function ($scope, $timeout) {

      var sourceSerialId = 1,
          eventSerialId = 1,
          sources = $scope.eventSources,
          extraEventSignature = $scope.calendarWatchEvent ? $scope.calendarWatchEvent : angular.noop,

          wrapFunctionWithScopeApply = function (functionToWrap) {
              var wrapper;

              if (functionToWrap) {
                  wrapper = function () {
                      // This happens outside of angular context so we need to wrap it in a timeout which has an implied apply.
                      // In this way the function will be safely executed on the next digest.

                      var args = arguments;
                      $timeout(function () {
                          functionToWrap.apply(this, args);
                      });
                  };
              }

              return wrapper;
          };

      this.eventsFingerprint = function (e) {
          if (!e.__uiCalId) {
              e.__uiCalId = eventSerialId++;
          }
          // This extracts all the information we need from the event. http://jsperf.com/angular-calendar-events-fingerprint/3
          return "" + e.__uiCalId + (e.id || '') + (e.title || '') + (e.url || '') + (+e.start || '') + (+e.end || '') +
            (e.allDay || '') + (e.className || '') + extraEventSignature(e) || '';
      };

      this.sourcesFingerprint = function (source) {
          return source.__id || (source.__id = sourceSerialId++);
      };

      this.allEvents = function () {
          // return sources.flatten(); but we don't have flatten
          var arraySources = [];
          for (var i = 0, srcLen = sources.length; i < srcLen; i++) {
              var source = sources[i];
              if (angular.isArray(source)) {
                  // event source as array
                  arraySources.push(source);
              } else if (angular.isObject(source) && angular.isArray(source.events)) {
                  // event source as object, ie extended form
                  var extEvent = {};
                  for (var key in source) {
                      if (key !== '_uiCalId' && key !== 'events') {
                          extEvent[key] = source[key];
                      }
                  }
                  for (var eI = 0; eI < source.events.length; eI++) {
                      angular.extend(source.events[eI], extEvent);
                  }
                  arraySources.push(source.events);
              }
          }

          return Array.prototype.concat.apply([], arraySources);
      };

      // Track changes in array by assigning id tokens to each element and watching the scope for changes in those tokens
      // arguments:
      //  arraySource array of function that returns array of objects to watch
      //  tokenFn function(object) that returns the token for a given object
      this.changeWatcher = function (arraySource, tokenFn) {
          var self;
          var getTokens = function () {
              var array = angular.isFunction(arraySource) ? arraySource() : arraySource;
              var result = [], token, el;
              for (var i = 0, n = array.length; i < n; i++) {
                  el = array[i];
                  token = tokenFn(el);
                  map[token] = el;
                  result.push(token);
              }
              return result;
          };
          // returns elements in that are in a but not in b
          // subtractAsSets([4, 5, 6], [4, 5, 7]) => [6]
          var subtractAsSets = function (a, b) {
              var result = [], inB = {}, i, n;
              for (i = 0, n = b.length; i < n; i++) {
                  inB[b[i]] = true;
              }
              for (i = 0, n = a.length; i < n; i++) {
                  if (!inB[a[i]]) {
                      result.push(a[i]);
                  }
              }
              return result;
          };

          // Map objects to tokens and vice-versa
          var map = {};

          var applyChanges = function (newTokens, oldTokens) {
              var i, n, el, token;
              var replacedTokens = {};
              var removedTokens = subtractAsSets(oldTokens, newTokens);
              for (i = 0, n = removedTokens.length; i < n; i++) {
                  var removedToken = removedTokens[i];
                  el = map[removedToken];
                  delete map[removedToken];
                  var newToken = tokenFn(el);
                  // if the element wasn't removed but simply got a new token, its old token will be different from the current one
                  if (newToken === removedToken) {
                      self.onRemoved(el);
                  } else {
                      replacedTokens[newToken] = removedToken;
                      self.onChanged(el);
                  }
              }

              var addedTokens = subtractAsSets(newTokens, oldTokens);
              for (i = 0, n = addedTokens.length; i < n; i++) {
                  token = addedTokens[i];
                  el = map[token];
                  if (!replacedTokens[token]) {
                      self.onAdded(el);
                  }
              }
          };
          return self = {
              subscribe: function (scope, onChanged) {
                  scope.$watch(getTokens, function (newTokens, oldTokens) {
                      if (!onChanged || onChanged(newTokens, oldTokens) !== false) {
                          applyChanges(newTokens, oldTokens);
                      }
                  }, true);
              },
              onAdded: angular.noop,
              onChanged: angular.noop,
              onRemoved: angular.noop
          };
      };

      this.getFullCalendarConfig = function (calendarSettings, uiCalendarConfig) {
          var config = {};

          angular.extend(config, uiCalendarConfig);
          angular.extend(config, calendarSettings);

          angular.forEach(config, function (value, key) {
              if (typeof value === 'function') {
                  config[key] = wrapFunctionWithScopeApply(config[key]);
              }
          });

          return config;
      };
  }])
  .directive('uiCalendar', ['uiCalendarConfig', '$locale', function (uiCalendarConfig, $locale) {
      // Configure to use locale names by default
      var tValues = function (data) {
          // convert {0: "Jan", 1: "Feb", ...} to ["Jan", "Feb", ...]
          var r, k;
          r = [];
          for (k in data) {
              r[k] = data[k];
          }
          return r;
      };
      var dtf = $locale.DATETIME_FORMATS;
      uiCalendarConfig = angular.extend({
          monthNames: tValues(dtf.MONTH),
          monthNamesShort: tValues(dtf.SHORTMONTH),
          dayNames: tValues(dtf.DAY),
          dayNamesShort: tValues(dtf.SHORTDAY)
      }, uiCalendarConfig || {});

      return {
          restrict: 'A',
          scope: { eventSources: '=ngModel', calendarWatchEvent: '&' },
          controller: 'uiCalendarCtrl',
          link: function (scope, elm, attrs, controller) {

              var sources = scope.eventSources,
                  sourcesChanged = false,
                  eventSourcesWatcher = controller.changeWatcher(sources, controller.sourcesFingerprint),
                  eventsWatcher = controller.changeWatcher(controller.allEvents, controller.eventsFingerprint),
                  options = null;

              function getOptions() {
                  var calendarSettings = attrs.uiCalendar ? scope.$parent.$eval(attrs.uiCalendar) : {},
                      fullCalendarConfig;

                  fullCalendarConfig = controller.getFullCalendarConfig(calendarSettings, uiCalendarConfig);

                  options = { eventSources: sources };
                  angular.extend(options, fullCalendarConfig);

                  var options2 = {};
                  for (var o in options) {
                      if (o !== 'eventSources') {
                          options2[o] = options[o];
                      }
                  }
                  return JSON.stringify(options2);
              }

              scope.destroy = function () {
                  if (attrs.calendar) {
                      scope.calendar = scope.$parent[attrs.calendar] = elm.html('');
                  } else {
                      scope.calendar = elm.html('');
                  }
              };

              scope.init = function () {
                  scope.calendar.fullCalendar(options);
              };

              eventSourcesWatcher.onAdded = function (source) {
                  scope.calendar.fullCalendar('addEventSource', source);
                  sourcesChanged = true;
              };

              eventSourcesWatcher.onRemoved = function (source) {
                  scope.calendar.fullCalendar('removeEventSource', source);
                  sourcesChanged = true;
              };

              eventsWatcher.onAdded = function (event) {
                  scope.calendar.fullCalendar('renderEvent', event);
              };

              eventsWatcher.onRemoved = function (event) {
                  scope.calendar.fullCalendar('removeEvents', function (e) { return e === event; });
              };

              eventsWatcher.onChanged = function (event) {
                  scope.calendar.fullCalendar('updateEvent', event);
              };

              eventSourcesWatcher.subscribe(scope);
              eventsWatcher.subscribe(scope, function (newTokens, oldTokens) {
                  if (sourcesChanged === true) {
                      sourcesChanged = false;
                      // prevent incremental updates in this case
                      return false;
                  }
              });

              scope.$watch(getOptions, function (newO, oldO) {
                  scope.destroy();
                  scope.init();
              });
          }
      };
  }]);
function ngGridFlexibleHeightPlugin(opts) {
    var self = this;
    self.grid = null;
    self.scope = null;
    self.init = function (scope, grid, services) {
        self.domUtilityService = services.DomUtilityService;
        self.grid = grid;
        self.scope = scope;
        var recalcHeightForData = function () { setTimeout(innerRecalcForData, 1); };
        var innerRecalcForData = function () {
            var gridId = self.grid.gridId;
            var footerPanelSel = '.' + gridId + ' .ngFooterPanel';
            var extraHeight = self.grid.$topPanel.height() + $(footerPanelSel).height();
            var naturalHeight = self.grid.$canvas.height() + 1;
            if (opts != null) {
                if (opts.minHeight != null && (naturalHeight + extraHeight) < opts.minHeight) {
                    naturalHeight = opts.minHeight - extraHeight - 2;
                }
            }

            var newViewportHeight = naturalHeight + 2;
            if (!self.scope.baseViewportHeight || self.scope.baseViewportHeight !== newViewportHeight) {
                self.grid.$viewport.css('height', newViewportHeight + 'px');
                self.grid.$root.css('height', (newViewportHeight + extraHeight) + 'px');
                self.scope.baseViewportHeight = newViewportHeight;
                self.domUtilityService.UpdateGridLayout(self.scope, self.grid);
            }
        };
        self.scope.catHashKeys = function () {
            var hash = '',
                idx;
            for (idx in self.scope.renderedRows) {
                hash += self.scope.renderedRows[idx].$$hashKey;
            }
            return hash;
        };
        self.scope.$watch('catHashKeys()', innerRecalcForData);
        self.scope.$watch(self.grid.config.data, recalcHeightForData);
    };
}

function ngGridLayoutPlugin() {
    var self = this;
    this.grid = null;
    this.scope = null;
    
    this.init = function (scope, grid, services) {
        self.domUtilityService = services.DomUtilityService;
        self.grid = grid;
        self.scope = scope;
        this.scope.$onRootScope('rebuild-grid', function () {
            self.domUtilityService.RebuildGrid(self.scope, self.grid);
        });
    };



    this.updateGridLayout = function () {
        if (!self.scope.$$phase) {
            self.scope.$apply(function () {
                self.domUtilityService.RebuildGrid(self.scope, self.grid);
            });
        }
        else {
            // $digest or $apply already in progress
            self.domUtilityService.RebuildGrid(self.scope, self.grid);
        }
    };
}
/***********************************************
* ng-grid JavaScript Library
* Authors: https://github.com/angular-ui/ng-grid/blob/master/README.md 
* License: MIT (http://www.opensource.org/licenses/mit-license.php)
* Compiled At: 04/29/2014 10:21
***********************************************/
(function(window, $) {
'use strict';

var EXCESS_ROWS = 6;
var SCROLL_THRESHOLD = 4;
var ASC = "asc";

var DESC = "desc";

var NG_FIELD = '_ng_field_';
var NG_DEPTH = '_ng_depth_';
var NG_HIDDEN = '_ng_hidden_';
var NG_COLUMN = '_ng_column_';
var CUSTOM_FILTERS = /CUSTOM_FILTERS/g;
var COL_FIELD = /COL_FIELD/g;
var DISPLAY_CELL_TEMPLATE = /DISPLAY_CELL_TEMPLATE/g;
var EDITABLE_CELL_TEMPLATE = /EDITABLE_CELL_TEMPLATE/g;
var CELL_EDITABLE_CONDITION = /CELL_EDITABLE_CONDITION/g;
var TEMPLATE_REGEXP = /<.+>/;
window.ngGrid = {};
window.ngGrid.i18n = {};
var ngGridServices = angular.module('ngGrid.services', []);
var ngGridDirectives = angular.module('ngGrid.directives', []);
var ngGridFilters = angular.module('ngGrid.filters', []);

angular.module('ngGrid', ['ngGrid.services', 'ngGrid.directives', 'ngGrid.filters']);

var ngMoveSelectionHandler = function($scope, elm, evt, grid) {
    if ($scope.selectionProvider.selectedItems === undefined) {
        return true;
    }

    var charCode = evt.which || evt.keyCode,
        newColumnIndex,
        lastInRow = false,
        firstInRow = false,
        rowIndex = $scope.selectionProvider.lastClickedRow === undefined ? 1 : $scope.selectionProvider.lastClickedRow.rowIndex,
        visibleCols = $scope.columns.filter(function(c) { return c.visible; }),
        pinnedCols = $scope.columns.filter(function(c) { return c.pinned; });

    if ($scope.col) {
        newColumnIndex = visibleCols.indexOf($scope.col);
    }

    if (charCode !== 37 && charCode !== 38 && charCode !== 39 && charCode !== 40 && (grid.config.noTabInterference || charCode !== 9) && charCode !== 13) {
        return true;
    }
    if ($scope.enableCellSelection) {
        if (charCode === 9) { 
            evt.preventDefault();
        }

        var focusedOnFirstColumn = $scope.showSelectionCheckbox ? $scope.col.index === 1 : $scope.col.index === 0;
        var focusedOnFirstVisibleColumns = $scope.$index === 1 || $scope.$index === 0;
        var focusedOnLastVisibleColumns = $scope.$index === ($scope.renderedColumns.length - 1) || $scope.$index === ($scope.renderedColumns.length - 2);
        var focusedOnLastColumn = visibleCols.indexOf($scope.col) === (visibleCols.length - 1);
        var focusedOnLastPinnedColumn = pinnedCols.indexOf($scope.col) === (pinnedCols.length - 1);
        if (charCode === 37 || charCode === 9 && evt.shiftKey) {
            var scrollTo = 0;

            if (!focusedOnFirstColumn) {
                newColumnIndex -= 1;
            }

            if (focusedOnFirstVisibleColumns) {
                if (focusedOnFirstColumn && charCode === 9 && evt.shiftKey){
                    scrollTo = grid.$canvas.width();
                    newColumnIndex = visibleCols.length - 1;
                    firstInRow = true;
                }
                else {
                    scrollTo = grid.$viewport.scrollLeft() - $scope.col.width;
                }
            }
            else if (pinnedCols.length > 0) {
                scrollTo = grid.$viewport.scrollLeft() - visibleCols[newColumnIndex].width;
            }

            grid.$viewport.scrollLeft(scrollTo);
        }
        else if (charCode === 39 || charCode ===  9 && !evt.shiftKey) {
            if (focusedOnLastVisibleColumns) {
                if (focusedOnLastColumn && charCode ===  9 && !evt.shiftKey) {
                    grid.$viewport.scrollLeft(0);
                    newColumnIndex = $scope.showSelectionCheckbox ? 1 : 0;  
                    lastInRow = true;
                }
                else {
                    grid.$viewport.scrollLeft(grid.$viewport.scrollLeft() + $scope.col.width);
                }
            }
            else if (focusedOnLastPinnedColumn) {
                grid.$viewport.scrollLeft(0);
            }

            if (!focusedOnLastColumn) {
                newColumnIndex += 1;
            }
        }
    }
    var items;
    if ($scope.configGroups.length > 0) {
        items = grid.rowFactory.parsedData.filter(function (row) {
            return !row.isAggRow;
        });
    }
    else {
        items = grid.filteredRows;
    }
    var offset = 0;
    if (rowIndex !== 0 && (charCode === 38 || charCode === 13 && evt.shiftKey || charCode === 9 && evt.shiftKey && firstInRow)) { 
        offset = -1;
    }
    else if (rowIndex !== items.length - 1 && (charCode === 40 || charCode === 13 && !evt.shiftKey || charCode === 9 && lastInRow)) {
        offset = 1;
    }
    if (offset) {
        var r = items[rowIndex + offset];
        if (r.beforeSelectionChange(r, evt)) {
            r.continueSelection(evt);
            $scope.$emit('ngGridEventDigestGridParent');

            if ($scope.selectionProvider.lastClickedRow.renderedRowIndex >= $scope.renderedRows.length - EXCESS_ROWS - 2) {
                grid.$viewport.scrollTop(grid.$viewport.scrollTop() + $scope.rowHeight);
            }
            else if ($scope.selectionProvider.lastClickedRow.renderedRowIndex <= EXCESS_ROWS + 2) {
                grid.$viewport.scrollTop(grid.$viewport.scrollTop() - $scope.rowHeight);
            }
      }
    }
    if ($scope.enableCellSelection) {
        setTimeout(function(){
            $scope.domAccessProvider.focusCellElement($scope, $scope.renderedColumns.indexOf(visibleCols[newColumnIndex]));
        }, 3);
    }

    return false;
};

if (!String.prototype.trim) {
    String.prototype.trim = function() {
        return this.replace(/^\s+|\s+$/g, '');
    };
}
if (!Array.prototype.indexOf) {
    Array.prototype.indexOf = function(elt ) {
        var len = this.length >>> 0;
        var from = Number(arguments[1]) || 0;
        from = (from < 0) ? Math.ceil(from) : Math.floor(from);
        if (from < 0) {
            from += len;
        }
        for (; from < len; from++) {
            if (from in this && this[from] === elt) {
                return from;
            }
        }
        return -1;
    };
}
if (!Array.prototype.filter) {
    Array.prototype.filter = function(fun ) {
        "use strict";
        var t = Object(this);
        var len = t.length >>> 0;
        if (typeof fun !== "function") {
            throw new TypeError();
        }
        var res = [];
        var thisp = arguments[1];
        for (var i = 0; i < len; i++) {
            if (i in t) {
                var val = t[i]; 
                if (fun.call(thisp, val, i, t)) {
                    res.push(val);
                }
            }
        }
        return res;
    };
}
ngGridFilters.filter('checkmark', function() {
    return function(input) {
        return input ? '\u2714' : '\u2718';
    };
});
ngGridFilters.filter('ngColumns', function() {
    return function(input) {
        return input.filter(function(col) {
            return !col.isAggCol;
        });
    };
});
angular.module('ngGrid.services').factory('$domUtilityService',['$utilityService', '$window', function($utils, $window) {
    var domUtilityService = {};
    var regexCache = {};
    var getWidths = function() {
        var $testContainer = $('<div></div>');
        $testContainer.appendTo('body');
        $testContainer.height(100).width(100).css("position", "absolute").css("overflow", "scroll");
        $testContainer.append('<div style="height: 400px; width: 400px;"></div>');
        domUtilityService.ScrollH = ($testContainer.height() - $testContainer[0].clientHeight);
        domUtilityService.ScrollW = ($testContainer.width() - $testContainer[0].clientWidth);
        $testContainer.empty();
        $testContainer.attr('style', '');
        $testContainer.append('<span style="font-family: Verdana, Helvetica, Sans-Serif; font-size: 14px;"><strong>M</strong></span>');
        domUtilityService.LetterW = $testContainer.children().first().width();
        $testContainer.remove();
    };
    domUtilityService.eventStorage = {};
    domUtilityService.AssignGridContainers = function($scope, rootEl, grid) {
        grid.$root = $(rootEl);
        grid.$topPanel = grid.$root.find(".ngTopPanel");
        grid.$groupPanel = grid.$root.find(".ngGroupPanel");
        grid.$headerContainer = grid.$topPanel.find(".ngHeaderContainer");
        $scope.$headerContainer = grid.$headerContainer;

        grid.$headerScroller = grid.$topPanel.find(".ngHeaderScroller");
        grid.$headers = grid.$headerScroller.children();
        grid.$viewport = grid.$root.find(".ngViewport");
        grid.$canvas = grid.$viewport.find(".ngCanvas");
        grid.$footerPanel = grid.$root.find(".ngFooterPanel");

        var scopeDereg = $scope.$watch(function () {
            return grid.$viewport.scrollLeft();
        }, function (newLeft) {
            return grid.$headerContainer.scrollLeft(newLeft);
        });

        $scope.$on('$destroy', function() {
            $(grid.$root.parent()).off('resize.nggrid');

            grid.$root = null;
            grid.$topPanel = null;
            grid.$headerContainer = null;
            grid.$headers = null;
            grid.$canvas = null;
            grid.$footerPanel = null;

            scopeDereg();
        });

        domUtilityService.UpdateGridLayout($scope, grid);
    };
    domUtilityService.getRealWidth = function (obj) {
        var width = 0;
        var props = { visibility: "hidden", display: "block" };
        var hiddenParents = obj.parents().andSelf().not(':visible');
        $.swap(hiddenParents[0], props, function () {
            width = obj.outerWidth();
        });
        return width;
    };
    domUtilityService.UpdateGridLayout = function($scope, grid) {
        if (!grid.$root){
            return;
        }
        var scrollTop = grid.$viewport.scrollTop();
        grid.elementDims.rootMaxW = grid.$root.width();
        if (grid.$root.is(':hidden')) {
            grid.elementDims.rootMaxW = domUtilityService.getRealWidth(grid.$root);
        }
        grid.elementDims.rootMaxH = grid.$root.height();
        grid.refreshDomSizes();
        $scope.adjustScrollTop(scrollTop, true); 
    };
    domUtilityService.numberOfGrids = 0;
    domUtilityService.setStyleText = function(grid, css) {
        var style = grid.styleSheet,
            gridId = grid.gridId,
            doc = $window.document;

        if (!style) {
            style = doc.getElementById(gridId);
        }
        if (!style) {
            style = doc.createElement('style');
            style.type = 'text/css';
            style.id = gridId;
            (doc.head || doc.getElementsByTagName('head')[0]).appendChild(style);
        }

        if (style.styleSheet && !style.sheet) {
            style.styleSheet.cssText = css;
        } else {
            style.innerHTML = css;
        }
        grid.styleSheet = style;
        grid.styleText = css;
    };
    domUtilityService.BuildStyles = function($scope, grid, digest) {
        var rowHeight = grid.config.rowHeight,
            gridId = grid.gridId,
            css,
            cols = $scope.columns,
            sumWidth = 0;

        var trw = $scope.totalRowWidth();
        css = "." + gridId + " .ngCanvas { width: " + trw + "px; }" +
            "." + gridId + " .ngRow { width: " + trw + "px; }" +
            "." + gridId + " .ngCanvas { width: " + trw + "px; }" +
            "." + gridId + " .ngHeaderScroller { width: " + (trw + domUtilityService.ScrollH) + "px}";

        for (var i = 0; i < cols.length; i++) {
            var col = cols[i];
            if (col.visible !== false) {
                css += "." + gridId + " .col" + i + " { width: " + (col.width > 0 ? col.width : 0) + "px; left: " + sumWidth + "px; height: " + rowHeight + "px }" +
                    "." + gridId + " .colt" + i + " { width: " + (col.width > 0 ? col.width : 0) + "px; }";
                sumWidth += col.width;
            }
        }
        domUtilityService.setStyleText(grid, css);

        $scope.adjustScrollLeft(grid.$viewport.scrollLeft());
        if (digest) {
            domUtilityService.digest($scope);
        }
    };
    domUtilityService.setColLeft = function(col, colLeft, grid) {
        if (grid.styleText) {
            var regex = regexCache[col.index];
            if (!regex) {
                regex = regexCache[col.index] = new RegExp(".col" + col.index + " { width: [0-9]+px; left: [0-9]+px");
            }
            var css = grid.styleText.replace(regex, ".col" + col.index + " { width: " + (col.width > 0 ? col.width : 0) + "px; left: " + colLeft + "px");
            domUtilityService.setStyleText(grid, css);
        }
    };
    domUtilityService.setColLeft.immediate = 1;
    domUtilityService.RebuildGrid = function($scope, grid){
        domUtilityService.UpdateGridLayout($scope, grid);
        if (grid.config.maintainColumnRatios == null || grid.config.maintainColumnRatios) {
            grid.configureColumnWidths();
        }
        $scope.adjustScrollLeft(grid.$viewport.scrollLeft());
        domUtilityService.BuildStyles($scope, grid, true);
    };

    domUtilityService.digest = function($scope) {
        if (!$scope.$root.$$phase) {
            $scope.$digest();
        }
    };
    domUtilityService.ScrollH = 0; 
    domUtilityService.ScrollW = 0; 
    domUtilityService.LetterW = 10;
    getWidths();
    return domUtilityService;
}]);
angular.module('ngGrid.services').factory('$sortService', ['$parse', function($parse) {
    var sortService = {};
    sortService.colSortFnCache = {}; 
    sortService.isCustomSort = false;
    sortService.guessSortFn = function(item) {
        var itemType = typeof(item);
        switch (itemType) {
            case "number":
                return sortService.sortNumber;
            case "boolean":
                return sortService.sortBool;
            case "string":
                return item.match(/^[-+]?[£$¤]?[\d,.]+%?$/) ? sortService.sortNumberStr : sortService.sortAlpha;
            default:
                if (Object.prototype.toString.call(item) === '[object Date]') {
                    return sortService.sortDate;
                }
                else {
                    return sortService.basicSort;
                }
        }
    };
    sortService.basicSort = function(a, b) {
        if (a === b) {
            return 0;
        }
        if (a < b) {
            return -1;
        }
        return 1;
    };
    sortService.sortNumber = function(a, b) {
        return a - b;
    };
    sortService.sortNumberStr = function(a, b) {
        var numA, numB, badA = false, badB = false;
        numA = parseFloat(a.replace(/[^0-9.-]/g, ''));
        if (isNaN(numA)) {
            badA = true;
        }
        numB = parseFloat(b.replace(/[^0-9.-]/g, ''));
        if (isNaN(numB)) {
            badB = true;
        }
        if (badA && badB) {
            return 0;
        }
        if (badA) {
            return 1;
        }
        if (badB) {
            return -1;
        }
        return numA - numB;
    };
    sortService.sortAlpha = function(a, b) {
        var strA = a.toLowerCase(),
            strB = b.toLowerCase();
        return strA === strB ? 0 : (strA < strB ? -1 : 1);
    };
    sortService.sortDate = function(a, b) {
        var timeA = a.getTime(),
            timeB = b.getTime();
        return timeA === timeB ? 0 : (timeA < timeB ? -1 : 1);
    };
    sortService.sortBool = function(a, b) {
        if (a && b) {
            return 0;
        }
        if (!a && !b) {
            return 0;
        } else {
            return a ? 1 : -1;
        }
    };
    sortService.sortData = function(sortInfo, data ) {
        if (!data || !sortInfo) {
            return;
        }
        var l = sortInfo.fields.length,
            order = sortInfo.fields,
            col,
            direction,
            d = data.slice(0);
        data.sort(function (itemA, itemB) {
            var tem = 0,
                indx = 0,
                res,
                sortFn;
            while (tem === 0 && indx < l) {
                col = sortInfo.columns[indx];
                direction = sortInfo.directions[indx];
                sortFn = sortService.getSortFn(col, d);
                var propA = $parse(order[indx])(itemA);
                var propB = $parse(order[indx])(itemB);
                if (sortService.isCustomSort) {
                    res = sortFn(propA, propB);
                    tem = direction === ASC ? res : 0 - res;
                } else {
                    if ((!propA && propA !== 0) || (!propB && propB !== 0)) {
                        if (!propB && !propA) {
                            tem = 0;
                        }
                        else if (!propA) {
                            tem = 1;
                        }
                        else if (!propB) {
                            tem = -1;
                        }
                    }
                    else {
                        res = sortFn(propA, propB);
                        tem = direction === ASC ? res : 0 - res;
                    }
                }
                indx++;
            }
            return tem;
        });
    };
    sortService.Sort = function(sortInfo, data) {
        if (sortService.isSorting) {
            return;
        }
        sortService.isSorting = true;
        sortService.sortData(sortInfo, data);
        sortService.isSorting = false;
    };
    sortService.getSortFn = function(col, data) {
        var sortFn, item;
        if (sortService.colSortFnCache[col.field]) {
            sortFn = sortService.colSortFnCache[col.field];
        }
        else if (col.sortingAlgorithm !== undefined) {
            sortFn = col.sortingAlgorithm;
            sortService.colSortFnCache[col.field] = col.sortingAlgorithm;
            sortService.isCustomSort = true;
        }
        else { 
            item = data[0];
            if (!item) {
                return sortFn;
            }
            sortFn = sortService.guessSortFn($parse(col.field)(item));
            if (sortFn) {
                sortService.colSortFnCache[col.field] = sortFn;
            } else {
                sortFn = sortService.sortAlpha;
            }
        }
        return sortFn;
    };
    return sortService;
}]);

angular.module('ngGrid.services').factory('$utilityService', ['$parse', function ($parse) {
    var funcNameRegex = /function (.{1,})\(/;
    var utils = {
        visualLength: function(node) {
            var elem = document.getElementById('testDataLength');
            if (!elem) {
                elem = document.createElement('SPAN');
                elem.id = "testDataLength";
                elem.style.visibility = "hidden";
                document.body.appendChild(elem);
            }
            var $node = $(node);
            $(elem).css({'font': $node.css('font'),
                        'font-size': $node.css('font-size'),
                        'font-family': $node.css('font-family')});
            elem.innerHTML = $node.text();
            var width = elem.offsetWidth;
            document.body.removeChild(elem);
            return width;
        },
        forIn: function(obj, action) {
            for (var prop in obj) {
                if (obj.hasOwnProperty(prop)) {
                    action(obj[prop], prop);
                }
            }
        },
        evalProperty: function (entity, path) {
            return $parse("entity." + path)({ entity: entity });
        },
        endsWith: function(str, suffix) {
            if (!str || !suffix || typeof str !== "string") {
                return false;
            }
            return str.indexOf(suffix, str.length - suffix.length) !== -1;
        },
        isNullOrUndefined: function(obj) {
            if (obj === undefined || obj === null) {
                return true;
            }
            return false;
        },
        getElementsByClassName: function(cl) {
            if (document.getElementsByClassName) {
                return document.getElementsByClassName(cl);
            }
            else {
                var retnode = [];
                var myclass = new RegExp('\\b' + cl + '\\b');
                var elem = document.getElementsByTagName('*');
                for (var i = 0; i < elem.length; i++) {
                    var classes = elem[i].className;
                    if (myclass.test(classes)) {
                        retnode.push(elem[i]);
                    }
                }
                return retnode;
            }
        },
        newId: (function() {
            var seedId = new Date().getTime();
            return function() {
                return seedId += 1;
            };
        })(),
        seti18n: function($scope, language) {
            var $langPack = window.ngGrid.i18n[language];
            for (var label in $langPack) {
                $scope.i18n[label] = $langPack[label];
            }
        },
        getInstanceType: function (o) {
            var results = (funcNameRegex).exec(o.constructor.toString());
            if (results && results.length > 1) {
                var instanceType = results[1].replace(/^\s+|\s+$/g, ""); 
                return instanceType;
            }
            else {
                return "";
            }
        }
    };

    return utils;
}]);

var ngAggregate = function (aggEntity, rowFactory, rowHeight, groupInitState) {
    this.rowIndex = 0;
    this.offsetTop = this.rowIndex * rowHeight;
    this.entity = aggEntity;
    this.label = aggEntity.gLabel;
    this.field = aggEntity.gField;
    this.depth = aggEntity.gDepth;
    this.parent = aggEntity.parent;
    this.children = aggEntity.children;
    this.aggChildren = aggEntity.aggChildren;
    this.aggIndex = aggEntity.aggIndex;
    this.collapsed = groupInitState;
    this.groupInitState = groupInitState;
    this.rowFactory = rowFactory;
    this.rowHeight = rowHeight;
    this.isAggRow = true;
    this.offsetLeft = aggEntity.gDepth * 25;
    this.aggLabelFilter = aggEntity.aggLabelFilter;
};

ngAggregate.prototype.toggleExpand = function () {
    this.collapsed = this.collapsed ? false : true;
    if (this.orig) {
        this.orig.collapsed = this.collapsed;
    }
    this.notifyChildren();
};
ngAggregate.prototype.setExpand = function (state) {
    this.collapsed = state;
    this.notifyChildren();
};
ngAggregate.prototype.notifyChildren = function () {
    var longest = Math.max(this.rowFactory.aggCache.length, this.children.length);
    for (var i = 0; i < longest; i++) {
        if (this.aggChildren[i]) {
            this.aggChildren[i].entity[NG_HIDDEN] = this.collapsed;
            if (this.collapsed) {
                this.aggChildren[i].setExpand(this.collapsed);
            }
        }
        if (this.children[i]) {
            this.children[i][NG_HIDDEN] = this.collapsed;
        }
        if (i > this.aggIndex && this.rowFactory.aggCache[i]) {
            var agg = this.rowFactory.aggCache[i];
            var offset = (30 * this.children.length);
            agg.offsetTop = this.collapsed ? agg.offsetTop - offset : agg.offsetTop + offset;
        }
    }
    this.rowFactory.renderedChange();
};
ngAggregate.prototype.aggClass = function () {
    return this.collapsed ? "ngAggArrowCollapsed" : "ngAggArrowExpanded";
};
ngAggregate.prototype.totalChildren = function () {
    if (this.aggChildren.length > 0) {
        var i = 0;
        var recurse = function (cur) {
            if (cur.aggChildren.length > 0) {
                angular.forEach(cur.aggChildren, function (a) {
                    recurse(a);
                });
            } else {
                i += cur.children.length;
            }
        };
        recurse(this);
        return i;
    } else {
        return this.children.length;
    }
};
ngAggregate.prototype.copy = function () {
    var ret = new ngAggregate(this.entity, this.rowFactory, this.rowHeight, this.groupInitState);
    ret.orig = this;
    return ret;
};
var ngColumn = function (config, $scope, grid, domUtilityService, $templateCache, $utils) {
    var self = this,
        colDef = config.colDef,
        delay = 500,
        clicks = 0,
        timer = null;
    self.colDef = config.colDef;
    self.width = colDef.width;
    self.groupIndex = 0;
    self.isGroupedBy = false;
    self.minWidth = !colDef.minWidth ? 50 : colDef.minWidth;
    self.maxWidth = !colDef.maxWidth ? 9000 : colDef.maxWidth;
    self.enableCellEdit = colDef.enableCellEdit !== undefined ? colDef.enableCellEdit : (config.enableCellEdit || config.enableCellEditOnFocus);
    self.cellEditableCondition = colDef.cellEditableCondition || config.cellEditableCondition || 'true';

    self.headerRowHeight = config.headerRowHeight;
    self.displayName = (colDef.displayName === undefined) ? colDef.field : colDef.displayName;

    self.index = config.index;
    self.isAggCol = config.isAggCol;
    self.cellClass = colDef.cellClass;
    self.sortPriority = undefined;
    self.cellFilter = colDef.cellFilter ? colDef.cellFilter : "";
    self.field = colDef.field;
    self.aggLabelFilter = colDef.aggLabelFilter || colDef.cellFilter;
    self.visible = $utils.isNullOrUndefined(colDef.visible) || colDef.visible;
    self.sortable = false;
    self.resizable = false;
    self.pinnable = false;
    self.pinned = (config.enablePinning && colDef.pinned);
    self.originalIndex = config.originalIndex == null ? self.index : config.originalIndex;
    self.groupable = $utils.isNullOrUndefined(colDef.groupable) || colDef.groupable;
    if (config.enableSort) {
        self.sortable = $utils.isNullOrUndefined(colDef.sortable) || colDef.sortable;
    }
    if (config.enableResize) {
        self.resizable = $utils.isNullOrUndefined(colDef.resizable) || colDef.resizable;
    }
    if (config.enablePinning) {
        self.pinnable = $utils.isNullOrUndefined(colDef.pinnable) || colDef.pinnable;
    }
    self.sortDirection = undefined;
    self.sortingAlgorithm = colDef.sortFn;
    self.headerClass = colDef.headerClass;
    self.cursor = self.sortable ? 'pointer' : 'default';
    self.headerCellTemplate = colDef.headerCellTemplate || $templateCache.get('headerCellTemplate.html');
    self.cellTemplate = colDef.cellTemplate || $templateCache.get('cellTemplate.html').replace(CUSTOM_FILTERS, self.cellFilter ? "|" + self.cellFilter : "");
    if(self.enableCellEdit) {
        self.cellEditTemplate = colDef.cellEditTemplate || $templateCache.get('cellEditTemplate.html');
        self.editableCellTemplate = colDef.editableCellTemplate || $templateCache.get('editableCellTemplate.html');
    }
    if (colDef.cellTemplate && !TEMPLATE_REGEXP.test(colDef.cellTemplate)) {
        self.cellTemplate = $templateCache.get(colDef.cellTemplate) || $.ajax({
            type: "GET",
            url: colDef.cellTemplate,
            async: false
        }).responseText;
    }
    if (self.enableCellEdit && colDef.editableCellTemplate && !TEMPLATE_REGEXP.test(colDef.editableCellTemplate)) {
        self.editableCellTemplate = $templateCache.get(colDef.editableCellTemplate) || $.ajax({
            type: "GET",
            url: colDef.editableCellTemplate,
            async: false
        }).responseText;
    }
    if (colDef.headerCellTemplate && !TEMPLATE_REGEXP.test(colDef.headerCellTemplate)) {
        self.headerCellTemplate = $templateCache.get(colDef.headerCellTemplate) || $.ajax({
            type: "GET",
            url: colDef.headerCellTemplate,
            async: false
        }).responseText;
    }
    self.colIndex = function () {
        var classes = self.pinned ? "pinned " : "";
        classes += "col" + self.index + " colt" + self.index;
        if (self.cellClass) {
            classes += " " + self.cellClass;
        }
        return classes;
    };
    self.groupedByClass = function() {
        return self.isGroupedBy ? "ngGroupedByIcon" : "ngGroupIcon";
    };
    self.toggleVisible = function() {
        self.visible = !self.visible;
    };
    self.showSortButtonUp = function() {
        return self.sortable ? self.sortDirection === DESC : self.sortable;
    };
    self.showSortButtonDown = function() {
        return self.sortable ? self.sortDirection === ASC : self.sortable;
    };
    self.noSortVisible = function() {
        return !self.sortDirection;
    };
    self.sort = function(evt) {
        if (!self.sortable) {
            return true; 
        }
        var dir = self.sortDirection === ASC ? DESC : ASC;
        self.sortDirection = dir;
        config.sortCallback(self, evt);
        return false;
    };
    self.gripClick = function() {
        clicks++; 
        if (clicks === 1) {
            timer = setTimeout(function() {
                clicks = 0; 
            }, delay);
        } else {
            clearTimeout(timer); 
            config.resizeOnDataCallback(self); 
            clicks = 0; 
        }
    };
    self.gripOnMouseDown = function(event) {
        $scope.isColumnResizing = true;
        if (event.ctrlKey && !self.pinned) {
            self.toggleVisible();
            domUtilityService.BuildStyles($scope, grid);
            return true;
        }
        event.target.parentElement.style.cursor = 'col-resize';
        self.startMousePosition = event.clientX;
        self.origWidth = self.width;
        $(document).mousemove(self.onMouseMove);
        $(document).mouseup(self.gripOnMouseUp);
        return false;
    };
    self.onMouseMove = function(event) {
        var diff = event.clientX - self.startMousePosition;
        var newWidth = diff + self.origWidth;
        self.width = (newWidth < self.minWidth ? self.minWidth : (newWidth > self.maxWidth ? self.maxWidth : newWidth));
        $scope.hasUserChangedGridColumnWidths = true;
        domUtilityService.BuildStyles($scope, grid);
        return false;
    };
    self.gripOnMouseUp = function (event) {
        $(document).off('mousemove', self.onMouseMove);
        $(document).off('mouseup', self.gripOnMouseUp);
        event.target.parentElement.style.cursor = 'default';
        domUtilityService.digest($scope);
        $scope.isColumnResizing = false;
        return false;
    };
    self.copy = function() {
        var ret = new ngColumn(config, $scope, grid, domUtilityService, $templateCache, $utils);
        ret.isClone = true;
        ret.orig = self;
        return ret;
    };
    self.setVars = function (fromCol) {
        self.orig = fromCol;
        self.width = fromCol.width;
        self.groupIndex = fromCol.groupIndex;
        self.isGroupedBy = fromCol.isGroupedBy;
        self.displayName = fromCol.displayName;
        self.index = fromCol.index;
        self.isAggCol = fromCol.isAggCol;
        self.cellClass = fromCol.cellClass;
        self.cellFilter = fromCol.cellFilter;
        self.field = fromCol.field;
        self.aggLabelFilter = fromCol.aggLabelFilter;
        self.visible = fromCol.visible;
        self.sortable = fromCol.sortable;
        self.resizable = fromCol.resizable;
        self.pinnable = fromCol.pinnable;
        self.pinned = fromCol.pinned;
        self.originalIndex = fromCol.originalIndex;
        self.sortDirection = fromCol.sortDirection;
        self.sortingAlgorithm = fromCol.sortingAlgorithm;
        self.headerClass = fromCol.headerClass;
        self.headerCellTemplate = fromCol.headerCellTemplate;
        self.cellTemplate = fromCol.cellTemplate;
        self.cellEditTemplate = fromCol.cellEditTemplate;
    };
};

var ngDimension = function (options) {
    this.outerHeight = null;
    this.outerWidth = null;
    $.extend(this, options);
};
var ngDomAccessProvider = function (grid) {
    this.previousColumn = null;
    this.grid = grid;

};

ngDomAccessProvider.prototype.changeUserSelect = function (elm, value) {
    elm.css({
        '-webkit-touch-callout': value,
        '-webkit-user-select': value,
        '-khtml-user-select': value,
        '-moz-user-select': value === 'none' ? '-moz-none' : value,
        '-ms-user-select': value,
        'user-select': value
    });
};
ngDomAccessProvider.prototype.focusCellElement = function ($scope, index) { 
    if ($scope.selectionProvider.lastClickedRow) {
        var columnIndex = index !== undefined ? index : this.previousColumn;
        var elm = $scope.selectionProvider.lastClickedRow.clone ? $scope.selectionProvider.lastClickedRow.clone.elm : $scope.selectionProvider.lastClickedRow.elm;
        if (columnIndex !== undefined && elm) {
            var columns = angular.element(elm[0].children).filter(function () { return this.nodeType !== 8; }); 
            var i = Math.max(Math.min($scope.renderedColumns.length - 1, columnIndex), 0);
            if (this.grid.config.showSelectionCheckbox && angular.element(columns[i]).scope() && angular.element(columns[i]).scope().col.index === 0) {
                i = 1; 
            }
            if (columns[i]) {
                columns[i].children[1].children[0].focus();
            }
            this.previousColumn = columnIndex;
        }
    }
};
ngDomAccessProvider.prototype.selectionHandlers = function ($scope, elm) {
    var doingKeyDown = false;
    var self = this;

    function keydown (evt) {
        if (evt.keyCode === 16) { 
            self.changeUserSelect(elm, 'none', evt);
            return true;
        } else if (!doingKeyDown) {
            doingKeyDown = true;
            var ret = ngMoveSelectionHandler($scope, elm, evt, self.grid);
            doingKeyDown = false;
            return ret;
        }
        return true;
    }

    elm.bind('keydown', keydown);

    function keyup (evt) {
        if (evt.keyCode === 16) { 
            self.changeUserSelect(elm, 'text', evt);
        }
        return true;
    }

    elm.bind('keyup', keyup);

    elm.on('$destroy', function() {
        elm.off('keydown', keydown);
        elm.off('keyup', keyup);
    });
};
var ngEventProvider = function (grid, $scope, domUtilityService, $timeout) {
    var self = this;
    self.colToMove = undefined;
    self.groupToMove = undefined;
    self.assignEvents = function() {
        if (grid.config.jqueryUIDraggable && !grid.config.enablePinning) {
            grid.$groupPanel.droppable({
                addClasses: false,
                drop: function(event) {
                    self.onGroupDrop(event);
                }
            });

            grid.$groupPanel.on('$destroy', function() {
                grid.$groupPanel = null;
            });
        } else {
            grid.$groupPanel.on('mousedown', self.onGroupMouseDown).on('dragover', self.dragOver).on('drop', self.onGroupDrop);
            grid.$topPanel.on('mousedown', '.ngHeaderScroller', self.onHeaderMouseDown).on('dragover', '.ngHeaderScroller', self.dragOver);

            grid.$groupPanel.on('$destroy', function() {
                if (grid.$groupPanel){
                    grid.$groupPanel.off('mousedown');
                }

                grid.$groupPanel = null;
            });

            if (grid.config.enableColumnReordering) {
                grid.$topPanel.on('drop', '.ngHeaderScroller', self.onHeaderDrop);
            }

            grid.$topPanel.on('$destroy', function() {
                if (grid.$topPanel){
                    grid.$topPanel.off('mousedown');
                }

                if (grid.config.enableColumnReordering && grid.$topPanel) {
                    grid.$topPanel.off('drop');
                }

                grid.$topPanel = null;
            });
        }

        $scope.$on('$destroy', $scope.$watch('renderedColumns', function() {
            $timeout(self.setDraggables);
        }));
    };
    self.dragStart = function(evt){
      evt.dataTransfer.setData('text', ''); 
    };
    self.dragOver = function(evt) {
        evt.preventDefault();
    };
    self.setDraggables = function() {
        if (!grid.config.jqueryUIDraggable) {
            var columns = grid.$root.find('.ngHeaderSortColumn'); 
            angular.forEach(columns, function(col){
                if(col.className && col.className.indexOf("ngHeaderSortColumn") !== -1){
                    col.setAttribute('draggable', 'true');
                    if (col.addEventListener) { 
                        col.addEventListener('dragstart', self.dragStart);

                        angular.element(col).on('$destroy', function() {
                            angular.element(col).off('dragstart', self.dragStart);
                            col.removeEventListener('dragstart', self.dragStart);
                        });
                    }
                }
            });
            if (navigator.userAgent.indexOf("MSIE") !== -1){
                var sortColumn = grid.$root.find('.ngHeaderSortColumn');
                sortColumn.bind('selectstart', function () { 
                    this.dragDrop(); 
                    return false; 
                });
                angular.element(sortColumn).on('$destroy', function() {
                    sortColumn.off('selectstart');
                });
            }
        } else {
            if (grid.$root) {
                grid.$root.find('.ngHeaderSortColumn').draggable({
                    helper: 'clone',
                    appendTo: 'body',
                    stack: 'div',
                    addClasses: false,
                    start: function(event) {
                        self.onHeaderMouseDown(event);
                    }
                }).droppable({
                    drop: function(event) {
                        self.onHeaderDrop(event);
                    }
                });
            }
        }
    };
    self.onGroupMouseDown = function(event) {
        var groupItem = $(event.target);
        if (groupItem[0].className !== 'ngRemoveGroup') {
            var groupItemScope = angular.element(groupItem).scope();
            if (groupItemScope) {
                if (!grid.config.jqueryUIDraggable) {
                    groupItem.attr('draggable', 'true');
                    if(this.addEventListener){
                        this.addEventListener('dragstart', self.dragStart);
                        angular.element(this).on('$destroy', function() {
                            this.removeEventListener('dragstart', self.dragStart); 
                        });
                    }
                    if (navigator.userAgent.indexOf("MSIE") !== -1){
                        groupItem.bind('selectstart', function () { 
                            this.dragDrop(); 
                            return false; 
                        });

                        groupItem.on('$destroy', function() {
                            groupItem.off('selectstart');
                        });
                    }
                }
                self.groupToMove = { header: groupItem, groupName: groupItemScope.group, index: groupItemScope.$index };
            }
        } else {
            self.groupToMove = undefined;
        }
    };
    self.onGroupDrop = function(event) {
        event.stopPropagation();
        var groupContainer;
        var groupScope;
        if (self.groupToMove) {
            groupContainer = $(event.target).closest('.ngGroupElement'); 
            if (groupContainer.context.className === 'ngGroupPanel') {
                $scope.configGroups.splice(self.groupToMove.index, 1);
                $scope.configGroups.push(self.groupToMove.groupName);
            } else {
                groupScope = angular.element(groupContainer).scope();
                if (groupScope) {
                    if (self.groupToMove.index !== groupScope.$index) {
                        $scope.configGroups.splice(self.groupToMove.index, 1);
                        $scope.configGroups.splice(groupScope.$index, 0, self.groupToMove.groupName);
                    }
                }
            }
            self.groupToMove = undefined;
            grid.fixGroupIndexes();
        } else if (self.colToMove) {
            if ($scope.configGroups.indexOf(self.colToMove.col) === -1) {
                groupContainer = $(event.target).closest('.ngGroupElement'); 
                if (groupContainer.context.className === 'ngGroupPanel' || groupContainer.context.className === 'ngGroupPanelDescription ng-binding') {
                    $scope.groupBy(self.colToMove.col);
                } else {
                    groupScope = angular.element(groupContainer).scope();
                    if (groupScope) {
                        $scope.removeGroup(groupScope.$index);
                    }
                }
            }
            self.colToMove = undefined;
        }
        if (!$scope.$$phase) {
            $scope.$apply();
        }
    };
    self.onHeaderMouseDown = function(event) {
        var headerContainer = $(event.target).closest('.ngHeaderSortColumn');
        var headerScope = angular.element(headerContainer).scope();
        if (headerScope) {
            self.colToMove = { header: headerContainer, col: headerScope.col };
        }
    };
    self.onHeaderDrop = function(event) {
        if (!self.colToMove || self.colToMove.col.pinned) {
            return;
        }
        var headerContainer = $(event.target).closest('.ngHeaderSortColumn');
        var headerScope = angular.element(headerContainer).scope();
        if (headerScope) {
            if (self.colToMove.col === headerScope.col || headerScope.col.pinned) {
                return;
            }
            $scope.columns.splice(self.colToMove.col.index, 1);
            $scope.columns.splice(headerScope.col.index, 0, self.colToMove.col);
            grid.fixColumnIndexes();
            self.colToMove = undefined;
            domUtilityService.digest($scope);
        }
    };

    self.assignGridEventHandlers = function() {
        if (grid.config.tabIndex === -1) {
            grid.$viewport.attr('tabIndex', domUtilityService.numberOfGrids);
            domUtilityService.numberOfGrids++;
        } else {
            grid.$viewport.attr('tabIndex', grid.config.tabIndex);
        }
        var windowThrottle;
        var windowResize = function(){
            clearTimeout(windowThrottle);
            windowThrottle = setTimeout(function() {
                domUtilityService.RebuildGrid($scope,grid);
            }, 100);
        };
        $(window).on('resize.nggrid', windowResize);
        var parentThrottle;
        var parentResize = function() {
            clearTimeout(parentThrottle);
            parentThrottle = setTimeout(function() {
                domUtilityService.RebuildGrid($scope,grid);
            }, 100);
        };
        $(grid.$root.parent()).on('resize.nggrid', parentResize);

        $scope.$on('$destroy', function(){
            $(window).off('resize.nggrid', windowResize);
        });
    };
    self.assignGridEventHandlers();
    self.assignEvents();
};

var ngFooter = function ($scope, grid) {
    $scope.maxRows = function () {
        var ret = Math.max($scope.totalServerItems, grid.data.length);
        return ret;
    };
     $scope.$on('$destroy', $scope.$watch('totalServerItems',function(n,o){
        $scope.currentMaxPages = $scope.maxPages();
    }));

    $scope.multiSelect = (grid.config.enableRowSelection && grid.config.multiSelect);
    $scope.selectedItemCount = grid.selectedItemCount;
    $scope.maxPages = function () {
        if($scope.maxRows() === 0) {
            return 1;
        }
        return Math.ceil($scope.maxRows() / $scope.pagingOptions.pageSize);
    };

    $scope.pageForward = function() {
        var page = $scope.pagingOptions.currentPage;
        if ($scope.totalServerItems > 0) {
            $scope.pagingOptions.currentPage = Math.min(page + 1, $scope.maxPages());
        } else {
            $scope.pagingOptions.currentPage++;
        }
    };

    $scope.pageBackward = function() {
        var page = $scope.pagingOptions.currentPage;
        $scope.pagingOptions.currentPage = Math.max(page - 1, 1);
    };

    $scope.pageToFirst = function() {
        $scope.pagingOptions.currentPage = 1;
    };

    $scope.pageToLast = function() {
        var maxPages = $scope.maxPages();
        $scope.pagingOptions.currentPage = maxPages;
    };

    $scope.cantPageForward = function() {
        var curPage = $scope.pagingOptions.currentPage;
        var maxPages = $scope.maxPages();
        if ($scope.totalServerItems > 0) {
            return curPage >= maxPages;
        } else {
            return grid.data.length < 1;
        }

    };
    $scope.cantPageToLast = function() {
        if ($scope.totalServerItems > 0) {
            return $scope.cantPageForward();
        } else {
            return true;
        }
    };
    $scope.cantPageBackward = function() {
        var curPage = $scope.pagingOptions.currentPage;
        return curPage <= 1;
    };
};

var ngGrid = function ($scope, options, sortService, domUtilityService, $filter, $templateCache, $utils, $timeout, $parse, $http, $q) {
    var defaults = {
        aggregateTemplate: undefined,
        afterSelectionChange: function() {
        },
        beforeSelectionChange: function() {
            return true;
        },
        checkboxCellTemplate: undefined,
        checkboxHeaderTemplate: undefined,
        columnDefs: undefined,
        data: [],
        dataUpdated: function() {
        },
        enableCellEdit: false,
        enableCellEditOnFocus: false,
        enableCellSelection: false,
        enableColumnResize: false,
        enableColumnReordering: false,
        enableColumnHeavyVirt: false,
        enablePaging: false,
        enablePinning: false,
        enableRowSelection: true,
        enableSorting: true,
        enableHighlighting: false,
        excludeProperties: [],
        filterOptions: {
            filterText: "",
            useExternalFilter: false
        },
        footerRowHeight: 55,
        footerTemplate: undefined,
        forceSyncScrolling: true,
        groups: [],
        groupsCollapsedByDefault: true,
        headerRowHeight: 30,
        headerRowTemplate: undefined,
        jqueryUIDraggable: false,
        jqueryUITheme: false,
        keepLastSelected: true,
        maintainColumnRatios: undefined,
        menuTemplate: undefined,
        multiSelect: true,
        pagingOptions: {
            pageSizes: [250, 500, 1000],
            pageSize: 250,
            currentPage: 1
        },
        pinSelectionCheckbox: false,
        plugins: [],
        primaryKey: undefined,
        rowHeight: 30,
        rowTemplate: undefined,
        selectedItems: [],
        selectWithCheckboxOnly: false,
        showColumnMenu: false,
        showFilter: false,
        showFooter: false,
        showGroupPanel: false,
        showSelectionCheckbox: false,
        sortInfo: {fields: [], columns: [], directions: [] },
        tabIndex: -1,
        totalServerItems: 0,
        useExternalSorting: false,
        i18n: 'en',
        virtualizationThreshold: 50,
	noTabInterference: false
    },
        self = this;
    self.maxCanvasHt = 0;
    self.config = $.extend(defaults, window.ngGrid.config, options);
    self.config.showSelectionCheckbox = (self.config.showSelectionCheckbox && self.config.enableColumnHeavyVirt === false);
    self.config.enablePinning = (self.config.enablePinning && self.config.enableColumnHeavyVirt === false);
    self.config.selectWithCheckboxOnly = (self.config.selectWithCheckboxOnly && self.config.showSelectionCheckbox !== false);
    self.config.pinSelectionCheckbox = self.config.enablePinning;

    if (typeof options.columnDefs === "string") {
        self.config.columnDefs = $scope.$eval(options.columnDefs);
    }
    self.rowCache = [];
    self.rowMap = [];
    self.gridId = "ng" + $utils.newId();
    self.$root = null; 
    self.$groupPanel = null;
    self.$topPanel = null;
    self.$headerContainer = null;
    self.$headerScroller = null;
    self.$headers = null;
    self.$viewport = null;
    self.$canvas = null;
    self.rootDim = self.config.gridDim;
    self.data = [];
    self.lateBindColumns = false;
    self.filteredRows = [];

    self.initTemplates = function() {
        var templates = ['rowTemplate', 'aggregateTemplate', 'headerRowTemplate', 'checkboxCellTemplate', 'checkboxHeaderTemplate', 'menuTemplate', 'footerTemplate'];

        var promises = [];
        angular.forEach(templates, function(template) {
            promises.push( self.getTemplate(template) );
        });

        return $q.all(promises);
    };
    self.getTemplate = function (key) {
        var t = self.config[key];
        var uKey = self.gridId + key + ".html";
        var p = $q.defer();
        if (t && !TEMPLATE_REGEXP.test(t)) {
            $http.get(t, {
                cache: $templateCache
            })
            .success(function(data){
                $templateCache.put(uKey, data);
                p.resolve();
            })
            .error(function(err){
                p.reject("Could not load template: " + t);
            });
        } else if (t) {
            $templateCache.put(uKey, t);
            p.resolve();
        } else {
            var dKey = key + ".html";
            $templateCache.put(uKey, $templateCache.get(dKey));
            p.resolve();
        }

        return p.promise;
    };

    if (typeof self.config.data === "object") {
        self.data = self.config.data; 
    }
    self.calcMaxCanvasHeight = function() {
        var calculatedHeight;
        if(self.config.groups.length > 0){
            calculatedHeight = self.rowFactory.parsedData.filter(function(e) {
                return !e[NG_HIDDEN];
            }).length * self.config.rowHeight;
        } else {
            calculatedHeight = self.filteredRows.length * self.config.rowHeight;
        }
        return calculatedHeight;
    };
    self.elementDims = {
        scrollW: 0,
        scrollH: 0,
        rowIndexCellW: 25,
        rowSelectedCellW: 25,
        rootMaxW: 0,
        rootMaxH: 0
    };
    self.setRenderedRows = function (newRows) {
        $scope.renderedRows.length = newRows.length;
        for (var i = 0; i < newRows.length; i++) {
            if (!$scope.renderedRows[i] || (newRows[i].isAggRow || $scope.renderedRows[i].isAggRow)) {
                $scope.renderedRows[i] = newRows[i].copy();
                $scope.renderedRows[i].collapsed = newRows[i].collapsed;
                if (!newRows[i].isAggRow) {
                    $scope.renderedRows[i].setVars(newRows[i]);
                }
            } else {
                $scope.renderedRows[i].setVars(newRows[i]);
            }
            $scope.renderedRows[i].rowIndex = newRows[i].rowIndex;
            $scope.renderedRows[i].offsetTop = newRows[i].offsetTop;
            $scope.renderedRows[i].selected = newRows[i].selected;
            newRows[i].renderedRowIndex = i;
        }
        self.refreshDomSizes();
        $scope.$emit('ngGridEventRows', newRows);
    };
    self.minRowsToRender = function() {
        var viewportH = $scope.viewportDimHeight() || 1;
        return Math.floor(viewportH / self.config.rowHeight);
    };
    self.refreshDomSizes = function() {
        var dim = new ngDimension();
        dim.outerWidth = self.elementDims.rootMaxW;
        dim.outerHeight = self.elementDims.rootMaxH;
        self.rootDim = dim;
        self.maxCanvasHt = self.calcMaxCanvasHeight();
    };
    self.buildColumnDefsFromData = function () {
        self.config.columnDefs = [];
        var item = self.data[0];
        if (!item) {
            self.lateBoundColumns = true;
            return;
        }
        $utils.forIn(item, function (prop, propName) {
            if (self.config.excludeProperties.indexOf(propName) === -1) {
                self.config.columnDefs.push({
                    field: propName
                });
            }
        });
    };
    self.buildColumns = function() {
        var columnDefs = self.config.columnDefs,
            cols = [];
        if (!columnDefs) {
            self.buildColumnDefsFromData();
            columnDefs = self.config.columnDefs;
        }
        if (self.config.showSelectionCheckbox) {
            cols.push(new ngColumn({
                colDef: {
                    field: '\u2714',
                    width: self.elementDims.rowSelectedCellW,
                    sortable: false,
                    resizable: false,
                    groupable: false,
                    headerCellTemplate: $templateCache.get($scope.gridId + 'checkboxHeaderTemplate.html'),
                    cellTemplate: $templateCache.get($scope.gridId + 'checkboxCellTemplate.html'),
                    pinned: self.config.pinSelectionCheckbox
                },
                index: 0,
                headerRowHeight: self.config.headerRowHeight,
                sortCallback: self.sortData,
                resizeOnDataCallback: self.resizeOnData,
                enableResize: self.config.enableColumnResize,
                enableSort: self.config.enableSorting,
                enablePinning: self.config.enablePinning
            }, $scope, self, domUtilityService, $templateCache, $utils));
        }
        if (columnDefs.length > 0) {
            var checkboxOffset = self.config.showSelectionCheckbox ? 1 : 0;
            var groupOffset = $scope.configGroups.length;
            $scope.configGroups.length = 0;
            angular.forEach(columnDefs, function(colDef, i) {
                i += checkboxOffset;
                var column = new ngColumn({
                    colDef: colDef,
                    index: i + groupOffset,
                    originalIndex: i,
                    headerRowHeight: self.config.headerRowHeight,
                    sortCallback: self.sortData,
                    resizeOnDataCallback: self.resizeOnData,
                    enableResize: self.config.enableColumnResize,
                    enableSort: self.config.enableSorting,
                    enablePinning: self.config.enablePinning,
                    enableCellEdit: self.config.enableCellEdit || self.config.enableCellEditOnFocus,
                    cellEditableCondition: self.config.cellEditableCondition
                }, $scope, self, domUtilityService, $templateCache, $utils);
                var indx = self.config.groups.indexOf(colDef.field);
                if (indx !== -1) {
                    column.isGroupedBy = true;
                    $scope.configGroups.splice(indx, 0, column);
                    column.groupIndex = $scope.configGroups.length;
                }
                cols.push(column);
            });
            $scope.columns = cols;
            if (self.config.groups.length > 0) {
                self.rowFactory.getGrouping(self.config.groups);
            }
        }
    };
    self.configureColumnWidths = function() {
        var asterisksArray = [],
            percentArray = [],
            asteriskNum = 0,
            totalWidth = 0;
        var indexMap = {};
        angular.forEach($scope.columns, function(ngCol, i) {
            if (!$utils.isNullOrUndefined(ngCol.originalIndex)) {
                var origIndex = ngCol.originalIndex;
                if (self.config.showSelectionCheckbox) {
                    if(ngCol.originalIndex === 0 && ngCol.visible){
                        totalWidth += 25;
                    }
                    origIndex--;
                }
                indexMap[origIndex] = i;
            }
        });

        angular.forEach(self.config.columnDefs, function(colDef, i) {
            var ngColumn = $scope.columns[indexMap[i]];

            colDef.index = i;

            var isPercent = false, t;
            if ($utils.isNullOrUndefined(colDef.width)) {
                colDef.width = "*";
            } else { 
                isPercent = isNaN(colDef.width) ? $utils.endsWith(colDef.width, "%") : false;
                t = isPercent ? colDef.width : parseInt(colDef.width, 10);
            }
            if (isNaN(t) && !$scope.hasUserChangedGridColumnWidths) {
                t = colDef.width;
                if (t === 'auto') { 
                    ngColumn.width = ngColumn.minWidth;
                    totalWidth += ngColumn.width;
                    var temp = ngColumn;

                    $scope.$on('$destroy', $scope.$on("ngGridEventData", function () {
                        self.resizeOnData(temp);
                    }));

                    return;
                } else if (t.indexOf("*") !== -1) { 
                    if (ngColumn.visible !== false) {
                        asteriskNum += t.length;
                    }
                    asterisksArray.push(colDef);
                    return;
                } else if (isPercent) { 
                    percentArray.push(colDef);
                    return;
                } else { 
                    throw "unable to parse column width, use percentage (\"10%\",\"20%\", etc...) or \"*\" to use remaining width of grid";
                }
            } else if (ngColumn.visible !== false) {
                totalWidth += ngColumn.width = parseInt(ngColumn.width, 10);
            }
        });
        if (percentArray.length > 0) {
            self.config.maintainColumnRatios = self.config.maintainColumnRatios !== false;
            var percentWidth = 0; 
            var hiddenPercent = 0; 
            angular.forEach(percentArray, function(colDef) {
                var ngColumn = $scope.columns[indexMap[colDef.index]];
                var percent = parseFloat(colDef.width) / 100;
                percentWidth += percent;

                if (!ngColumn.visible) {
                    hiddenPercent += percent;
                }
            });
            var percentWidthUsed = percentWidth - hiddenPercent;
            angular.forEach(percentArray, function(colDef) {
                var ngColumn = $scope.columns[indexMap[colDef.index]];
                var percent = parseFloat(colDef.width) / 100;
                if (hiddenPercent > 0) {
                    percent = percent / percentWidthUsed;
                }
                else {
                    percent = percent / percentWidth;
                }

                var pixelsForPercentBasedWidth = self.rootDim.outerWidth * percentWidth;
                ngColumn.width = pixelsForPercentBasedWidth * percent;
                totalWidth += ngColumn.width;
            });
        }
        if (asterisksArray.length > 0) {
            self.config.maintainColumnRatios = self.config.maintainColumnRatios !== false;
            var remainingWidth = self.rootDim.outerWidth - totalWidth;
            if (self.maxCanvasHt > $scope.viewportDimHeight()) {
                remainingWidth -= domUtilityService.ScrollW;
            }
            var asteriskVal = Math.floor(remainingWidth / asteriskNum);
            angular.forEach(asterisksArray, function(colDef, i) {
                var ngColumn = $scope.columns[indexMap[colDef.index]];
                ngColumn.width = asteriskVal * colDef.width.length;
                if (ngColumn.visible !== false) {
                    totalWidth += ngColumn.width;
                }

                var isLast = (i === (asterisksArray.length - 1));
                if(isLast && totalWidth < self.rootDim.outerWidth){
                    var gridWidthDifference = self.rootDim.outerWidth - totalWidth;
                    if(self.maxCanvasHt > $scope.viewportDimHeight()){
                        gridWidthDifference -= domUtilityService.ScrollW;
                    }
                    ngColumn.width += gridWidthDifference;
                }
            });
        }
    };
    self.init = function() {
        return self.initTemplates().then(function(){
            $scope.selectionProvider = new ngSelectionProvider(self, $scope, $parse);
            $scope.domAccessProvider = new ngDomAccessProvider(self);
            self.rowFactory = new ngRowFactory(self, $scope, domUtilityService, $templateCache, $utils);
            self.searchProvider = new ngSearchProvider($scope, self, $filter);
            self.styleProvider = new ngStyleProvider($scope, self);
            $scope.$on('$destroy', $scope.$watch('configGroups', function(a) {
              var tempArr = [];
              angular.forEach(a, function(item) {
                tempArr.push(item.field || item);
              });
              self.config.groups = tempArr;
              self.rowFactory.filteredRowsChanged();
              $scope.$emit('ngGridEventGroups', a);
            }, true));
             $scope.$on('$destroy', $scope.$watch('columns', function (a) {
                if(!$scope.isColumnResizing){
                    domUtilityService.RebuildGrid($scope, self);
                }
                $scope.$emit('ngGridEventColumns', a);
            }, true));
             $scope.$on('$destroy', $scope.$watch(function() {
                return options.i18n;
            }, function(newLang) {
                $utils.seti18n($scope, newLang);
            }));
            self.maxCanvasHt = self.calcMaxCanvasHeight();

            if (self.config.sortInfo.fields && self.config.sortInfo.fields.length > 0) {
                $scope.$on('$destroy', $scope.$watch(function() {
                    return self.config.sortInfo;
                }, function(sortInfo){
                    if (!sortService.isSorting) {
                        self.sortColumnsInit();
                        $scope.$emit('ngGridEventSorted', self.config.sortInfo);
                    }
                }, true));
            }
        });
    };
    self.resizeOnData = function(col) {
        var longest = col.minWidth;
        var arr = $utils.getElementsByClassName('col' + col.index);
        angular.forEach(arr, function(elem, index) {
            var i;
            if (index === 0) {
                var kgHeaderText = $(elem).find('.ngHeaderText');
                i = $utils.visualLength(kgHeaderText) + 10; 
            } else {
                var ngCellText = $(elem).find('.ngCellText');
                i = $utils.visualLength(ngCellText) + 10; 
            }
            if (i > longest) {
                longest = i;
            }
        });
        col.width = col.longest = Math.min(col.maxWidth, longest + 7); 
        domUtilityService.BuildStyles($scope, self, true);
    };
    self.lastSortedColumns = [];
    self.sortData = function(col, evt) {
        if (evt && evt.shiftKey && self.config.sortInfo) {
            var indx = self.config.sortInfo.columns.indexOf(col);
            if (indx === -1) {
                if (self.config.sortInfo.columns.length === 1) {
                    self.config.sortInfo.columns[0].sortPriority = 1;
                }
                self.config.sortInfo.columns.push(col);
                col.sortPriority = self.config.sortInfo.columns.length;
                self.config.sortInfo.fields.push(col.field);
                self.config.sortInfo.directions.push(col.sortDirection);
                self.lastSortedColumns.push(col);
            } else {
                self.config.sortInfo.directions[indx] = col.sortDirection;
            }
        } else if (!self.config.useExternalSorting || (self.config.useExternalSorting && self.config.sortInfo )) {
            var isArr = $.isArray(col);
            self.config.sortInfo.columns.length = 0;
            self.config.sortInfo.fields.length = 0;
            self.config.sortInfo.directions.length = 0;
            var push = function (c) {
                self.config.sortInfo.columns.push(c);
                self.config.sortInfo.fields.push(c.field);
                self.config.sortInfo.directions.push(c.sortDirection);
                self.lastSortedColumns.push(c);
            };
            if (isArr) {
                angular.forEach(col, function (c, i) {
                    c.sortPriority = i + 1;
                    push(c);
                });
            } else {
                self.clearSortingData(col);
                col.sortPriority = undefined;
                push(col);
            }

            self.sortActual();
            self.searchProvider.evalFilter();
            $scope.$emit('ngGridEventSorted', self.config.sortInfo);
        }
    };
    self.sortColumnsInit = function() {
        if (self.config.sortInfo.columns) {
            self.config.sortInfo.columns.length = 0;
        } else {
            self.config.sortInfo.columns = [];
        }

        var cols = [];
        angular.forEach($scope.columns, function(c) {
            var i = self.config.sortInfo.fields.indexOf(c.field);
            if (i !== -1) {
                c.sortDirection = self.config.sortInfo.directions[i] || 'asc';
                cols[i] = c;
            }
        });

        if(cols.length === 1){
            self.sortData(cols[0]);
        }else{
            self.sortData(cols);
        }
    };
    self.sortActual = function() {
        if (!self.config.useExternalSorting) {
            var tempData = self.data.slice(0);
            angular.forEach(tempData, function(item, i) {
                var e = self.rowMap[i];
                if (e !== undefined) {
                    var v = self.rowCache[e];
                    if (v !== undefined) {
                        item.preSortSelected = v.selected;
                        item.preSortIndex = i;
                    }
                }
            });
            sortService.Sort(self.config.sortInfo, tempData);
            angular.forEach(tempData, function(item, i) {
                self.rowCache[i].entity = item;
                self.rowCache[i].selected = item.preSortSelected;
                self.rowMap[item.preSortIndex] = i;
                delete item.preSortSelected;
                delete item.preSortIndex;
            });
        }
    };

    self.clearSortingData = function (col) {
        if (!col) {
            angular.forEach(self.lastSortedColumns, function (c) {
                c.sortDirection = "";
                c.sortPriority = null;
            });
            self.lastSortedColumns = [];
        } else {
            angular.forEach(self.lastSortedColumns, function (c) {
                if (col.index !== c.index) {
                    c.sortDirection = "";
                    c.sortPriority = null;
                }
            });
            self.lastSortedColumns[0] = col;
            self.lastSortedColumns.length = 1;
        }
    };
    self.fixColumnIndexes = function() {
        for (var i = 0; i < $scope.columns.length; i++) {
            $scope.columns[i].index = i;
        }
    };
    self.fixGroupIndexes = function() {
        angular.forEach($scope.configGroups, function(item, i) {
            item.groupIndex = i + 1;
        });
    };
    $scope.elementsNeedMeasuring = true;
    $scope.columns = [];
    $scope.renderedRows = [];
    $scope.renderedColumns = [];
    $scope.headerRow = null;
    $scope.rowHeight = self.config.rowHeight;
    $scope.jqueryUITheme = self.config.jqueryUITheme;
    $scope.showSelectionCheckbox = self.config.showSelectionCheckbox;
    $scope.enableCellSelection = self.config.enableCellSelection;
    $scope.enableCellEditOnFocus = self.config.enableCellEditOnFocus;
    $scope.footer = null;
    $scope.selectedItems = self.config.selectedItems;
    $scope.multiSelect = self.config.multiSelect;
    $scope.showFooter = self.config.showFooter;
    $scope.footerRowHeight = $scope.showFooter ? self.config.footerRowHeight : 0;
    $scope.showColumnMenu = self.config.showColumnMenu;
    $scope.forceSyncScrolling = self.config.forceSyncScrolling;
    $scope.showMenu = false;
    $scope.configGroups = [];
    $scope.gridId = self.gridId;
    $scope.enablePaging = self.config.enablePaging;
    $scope.pagingOptions = self.config.pagingOptions;
    $scope.i18n = {};
    $utils.seti18n($scope, self.config.i18n);
    $scope.adjustScrollLeft = function (scrollLeft) {
        var colwidths = 0,
            totalLeft = 0,
            x = $scope.columns.length,
            newCols = [],
            dcv = !self.config.enableColumnHeavyVirt;
        var r = 0;
        var addCol = function (c) {
            if (dcv) {
                newCols.push(c);
            } else {
                if (!$scope.renderedColumns[r]) {
                    $scope.renderedColumns[r] = c.copy();
                } else {
                    $scope.renderedColumns[r].setVars(c);
                }
            }
            r++;
        };
        for (var i = 0; i < x; i++) {
            var col = $scope.columns[i];
            if (col.visible !== false) {
                var w = col.width + colwidths;
                if (col.pinned) {
                    addCol(col);
                    var newLeft = i > 0 ? (scrollLeft + totalLeft) : scrollLeft;
                    domUtilityService.setColLeft(col, newLeft, self);
                    totalLeft += col.width;
                } else {
                    if (w >= scrollLeft) {
                        if (colwidths <= scrollLeft + self.rootDim.outerWidth) {
                            addCol(col);
                        }
                    }
                }
                colwidths += col.width;
            }
        }
        if (dcv) {
            $scope.renderedColumns = newCols;
        }
    };
    self.prevScrollTop = 0;
    self.prevScrollIndex = 0;
    $scope.adjustScrollTop = function(scrollTop, force) {
        if (self.prevScrollTop === scrollTop && !force) {
            return;
        }
        if (scrollTop > 0 && self.$viewport[0].scrollHeight - scrollTop <= self.$viewport.outerHeight()) {
            $scope.$emit('ngGridEventScroll');
        }
        var rowIndex = Math.floor(scrollTop / self.config.rowHeight);
        var newRange;
        if (self.filteredRows.length > self.config.virtualizationThreshold) {
            if (self.prevScrollTop < scrollTop && rowIndex < self.prevScrollIndex + SCROLL_THRESHOLD) {
                return;
            }
            if (self.prevScrollTop > scrollTop && rowIndex > self.prevScrollIndex - SCROLL_THRESHOLD) {
                return;
            }
            newRange = new ngRange(Math.max(0, rowIndex - EXCESS_ROWS), rowIndex + self.minRowsToRender() + EXCESS_ROWS);
        } else {
            var maxLen = $scope.configGroups.length > 0 ? self.rowFactory.parsedData.length : self.filteredRows.length;
            newRange = new ngRange(0, Math.max(maxLen, self.minRowsToRender() + EXCESS_ROWS));
        }
        self.prevScrollTop = scrollTop;
        self.rowFactory.UpdateViewableRange(newRange);
        self.prevScrollIndex = rowIndex;
    };
    $scope.toggleShowMenu = function() {
        $scope.showMenu = !$scope.showMenu;
    };
    $scope.toggleSelectAll = function(state, selectOnlyVisible) {
        $scope.selectionProvider.toggleSelectAll(state, false, selectOnlyVisible);
    };
    $scope.totalFilteredItemsLength = function() {
        return self.filteredRows.length;
    };
    $scope.showGroupPanel = function() {
        return self.config.showGroupPanel;
    };
    $scope.topPanelHeight = function() {
        return self.config.showGroupPanel === true ? self.config.headerRowHeight + 32 : self.config.headerRowHeight;
    };

    $scope.viewportDimHeight = function() {
        return Math.max(0, self.rootDim.outerHeight - $scope.topPanelHeight() - $scope.footerRowHeight - 2);
    };
    $scope.groupBy = function (col) {
        if (self.data.length < 1 || !col.groupable  || !col.field) {
            return;
        }
        if (!col.sortDirection) {
            col.sort({ shiftKey: $scope.configGroups.length > 0 ? true : false });
        }

        var indx = $scope.configGroups.indexOf(col);
        if (indx === -1) {
            col.isGroupedBy = true;
            $scope.configGroups.push(col);
            col.groupIndex = $scope.configGroups.length;
        } else {
            $scope.removeGroup(indx);
        }
        self.$viewport.scrollTop(0);
        domUtilityService.digest($scope);
    };
    $scope.removeGroup = function(index) {
        var col = $scope.columns.filter(function(item) {
            return item.groupIndex === (index + 1);
        })[0];
        col.isGroupedBy = false;
        col.groupIndex = 0;
        if ($scope.columns[index].isAggCol) {
            $scope.columns.splice(index, 1);
            $scope.configGroups.splice(index, 1);
            self.fixGroupIndexes();
        }
        if ($scope.configGroups.length === 0) {
            self.fixColumnIndexes();
            domUtilityService.digest($scope);
        }
        $scope.adjustScrollLeft(0);
    };
    $scope.togglePin = function (col) {
        var indexFrom = col.index;
        var indexTo = 0;
        for (var i = 0; i < $scope.columns.length; i++) {
            if (!$scope.columns[i].pinned) {
                break;
            }
            indexTo++;
        }
        if (col.pinned) {
            indexTo = Math.max(col.originalIndex, indexTo - 1);
        }
        col.pinned = !col.pinned;
        $scope.columns.splice(indexFrom, 1);
        $scope.columns.splice(indexTo, 0, col);
        self.fixColumnIndexes();
        domUtilityService.BuildStyles($scope, self, true);
        self.$viewport.scrollLeft(self.$viewport.scrollLeft() - col.width);
    };
    $scope.totalRowWidth = function() {
        var totalWidth = 0,
            cols = $scope.columns;
        for (var i = 0; i < cols.length; i++) {
            if (cols[i].visible !== false) {
                totalWidth += cols[i].width;
            }
        }
        return totalWidth;
    };
    $scope.headerScrollerDim = function() {
        var viewportH = $scope.viewportDimHeight(),
            maxHeight = self.maxCanvasHt,
            vScrollBarIsOpen = (maxHeight > viewportH),
            newDim = new ngDimension();

        newDim.autoFitHeight = true;
        newDim.outerWidth = $scope.totalRowWidth();
        if (vScrollBarIsOpen) {
            newDim.outerWidth += self.elementDims.scrollW;
        } else if ((maxHeight - viewportH) <= self.elementDims.scrollH) { 
            newDim.outerWidth += self.elementDims.scrollW;
        }
        return newDim;
    };
};

var ngRange = function (top, bottom) {
    this.topRow = top;
    this.bottomRow = bottom;
};
var ngRow = function (entity, config, selectionProvider, rowIndex, $utils) {
	this.entity = entity;
	this.config = config;
	this.selectionProvider = selectionProvider;
	this.rowIndex = rowIndex;
	this.utils = $utils;
	this.selected = selectionProvider.getSelection(entity);
	this.cursor = this.config.enableRowSelection && !this.config.selectWithCheckboxOnly ? 'pointer' : 'default';
	this.beforeSelectionChange = config.beforeSelectionChangeCallback;
	this.afterSelectionChange = config.afterSelectionChangeCallback;
	this.offsetTop = this.rowIndex * config.rowHeight;
	this.rowDisplayIndex = 0;
};

ngRow.prototype.setSelection = function (isSelected) {
	this.selectionProvider.setSelection(this, isSelected);
	this.selectionProvider.lastClickedRow = this;
};
ngRow.prototype.continueSelection = function (event) {
	this.selectionProvider.ChangeSelection(this, event);
};
ngRow.prototype.ensureEntity = function (expected) {
	if (this.entity !== expected) {
		this.entity = expected;
		this.selected = this.selectionProvider.getSelection(this.entity);
	}
};
ngRow.prototype.toggleSelected = function (event) {
	if (!this.config.enableRowSelection && !this.config.enableCellSelection) {
		return true;
	}
	var element = event.target || event;
	if (element.type === "checkbox" && element.parentElement.className !== "ngSelectionCell ng-scope") {
		return true;
	}
	if (this.config.selectWithCheckboxOnly && element.type !== "checkbox") {
		this.selectionProvider.lastClickedRow = this;
		return true;
	} 
	if (this.beforeSelectionChange(this, event)) {
		this.continueSelection(event);
	}
	return false;
};
ngRow.prototype.alternatingRowClass = function () {
	var isEven = (this.rowIndex % 2) === 0;
	var classes = {
		'ngRow' : true,
		'selected': this.selected,
		'even': isEven,
		'odd': !isEven,
		'ui-state-default': this.config.jqueryUITheme && isEven,
		'ui-state-active': this.config.jqueryUITheme && !isEven
	};
	return classes;
};
ngRow.prototype.getProperty = function (path) {
	return this.utils.evalProperty(this.entity, path);
};
ngRow.prototype.copy = function () {
	this.clone = new ngRow(this.entity, this.config, this.selectionProvider, this.rowIndex, this.utils);
	this.clone.isClone = true;
	this.clone.elm = this.elm;
	this.clone.orig = this;
	return this.clone;
};
ngRow.prototype.setVars = function (fromRow) {
	fromRow.clone = this;
	this.entity = fromRow.entity;
	this.selected = fromRow.selected;
    this.orig = fromRow;
};
var ngRowFactory = function (grid, $scope, domUtilityService, $templateCache, $utils) {
    var self = this;
    self.aggCache = {};
    self.parentCache = []; 
    self.dataChanged = true;
    self.parsedData = [];
    self.rowConfig = {};
    self.selectionProvider = $scope.selectionProvider;
    self.rowHeight = 30;
    self.numberOfAggregates = 0;
    self.groupedData = undefined;
    self.rowHeight = grid.config.rowHeight;
    self.rowConfig = {
        enableRowSelection: grid.config.enableRowSelection,
        rowClasses: grid.config.rowClasses,
        selectedItems: $scope.selectedItems,
        selectWithCheckboxOnly: grid.config.selectWithCheckboxOnly,
        beforeSelectionChangeCallback: grid.config.beforeSelectionChange,
        afterSelectionChangeCallback: grid.config.afterSelectionChange,
        jqueryUITheme: grid.config.jqueryUITheme,
        enableCellSelection: grid.config.enableCellSelection,
        rowHeight: grid.config.rowHeight
    };

    self.renderedRange = new ngRange(0, grid.minRowsToRender() + EXCESS_ROWS);
    self.buildEntityRow = function(entity, rowIndex) {
        return new ngRow(entity, self.rowConfig, self.selectionProvider, rowIndex, $utils);
    };

    self.buildAggregateRow = function(aggEntity, rowIndex) {
        var agg = self.aggCache[aggEntity.aggIndex]; 
        if (!agg) {
            agg = new ngAggregate(aggEntity, self, self.rowConfig.rowHeight, grid.config.groupsCollapsedByDefault);
            self.aggCache[aggEntity.aggIndex] = agg;
        }
        agg.rowIndex = rowIndex;
        agg.offsetTop = rowIndex * self.rowConfig.rowHeight;
        return agg;
    };
    self.UpdateViewableRange = function(newRange) {
        self.renderedRange = newRange;
        self.renderedChange();
    };
    self.filteredRowsChanged = function() {
        if (grid.lateBoundColumns && grid.filteredRows.length > 0) {
            grid.config.columnDefs = undefined;
            grid.buildColumns();
            grid.lateBoundColumns = false;
            $scope.$evalAsync(function() {
                $scope.adjustScrollLeft(0);
            });
        }
        self.dataChanged = true;
        if (grid.config.groups.length > 0) {
            self.getGrouping(grid.config.groups);
        }
        self.UpdateViewableRange(self.renderedRange);
    };

    self.renderedChange = function() {
        if (!self.groupedData || grid.config.groups.length < 1) {
            self.renderedChangeNoGroups();
            grid.refreshDomSizes();
            return;
        }
        self.wasGrouped = true;
        self.parentCache = [];
        var x = 0;
        var temp = self.parsedData.filter(function (e) {
            if (e.isAggRow) {
                if (e.parent && e.parent.collapsed) {
                    return false;
                }
                return true;
            }
            if (!e[NG_HIDDEN]) {
                e.rowIndex = x++;
            }
            return !e[NG_HIDDEN];
        });
        self.totalRows = temp.length;
        var rowArr = [];
        for (var i = self.renderedRange.topRow; i < self.renderedRange.bottomRow; i++) {
            if (temp[i]) {
                temp[i].offsetTop = i * grid.config.rowHeight;
                rowArr.push(temp[i]);
            }
        }
        grid.setRenderedRows(rowArr);
    };

    self.renderedChangeNoGroups = function () {
        var rowArr = [];
        for (var i = self.renderedRange.topRow; i < self.renderedRange.bottomRow; i++) {
            if (grid.filteredRows[i]) {
                grid.filteredRows[i].rowIndex = i;
                grid.filteredRows[i].offsetTop = i * grid.config.rowHeight;
                rowArr.push(grid.filteredRows[i]);
            }
        }
        grid.setRenderedRows(rowArr);
    };

    self.fixRowCache = function () {
        var newLen = grid.data.length;
        var diff = newLen - grid.rowCache.length;
        if (diff < 0) {
            grid.rowCache.length = grid.rowMap.length = newLen;
        } else {
            for (var i = grid.rowCache.length; i < newLen; i++) {
                grid.rowCache[i] = grid.rowFactory.buildEntityRow(grid.data[i], i);
            }
        }
    };
    self.parseGroupData = function(g) {
        if (g.values) {
            for (var x = 0; x < g.values.length; x++){
                self.parentCache[self.parentCache.length - 1].children.push(g.values[x]);
                self.parsedData.push(g.values[x]);
            }
        } else {
            for (var prop in g) {
                if (prop === NG_FIELD || prop === NG_DEPTH || prop === NG_COLUMN) {
                    continue;
                } else if (g.hasOwnProperty(prop)) {
                    var agg = self.buildAggregateRow({
                        gField: g[NG_FIELD],
                        gLabel: prop,
                        gDepth: g[NG_DEPTH],
                        isAggRow: true,
                        '_ng_hidden_': false,
                        children: [],
                        aggChildren: [],
                        aggIndex: self.numberOfAggregates,
                        aggLabelFilter: g[NG_COLUMN].aggLabelFilter
                    }, 0);
                    self.numberOfAggregates++;
                    agg.parent = self.parentCache[agg.depth - 1];
                    if (agg.parent) {
                        agg.parent.collapsed = false;
                        agg.parent.aggChildren.push(agg);
                    }
                    self.parsedData.push(agg);
                    self.parentCache[agg.depth] = agg;
                    self.parseGroupData(g[prop]);
                }
            }
        }
    };
    self.getGrouping = function(groups) {
        self.aggCache = [];
        self.numberOfAggregates = 0;
        self.groupedData = {};
        var rows = grid.filteredRows,
            maxDepth = groups.length,
            cols = $scope.columns;

        function filterCols(cols, group) {
            return cols.filter(function(c) {
                return c.field === group;
            });
        }

        for (var x = 0; x < rows.length; x++) {
            var model = rows[x].entity;
            if (!model) {
                return;
            }
            rows[x][NG_HIDDEN] = grid.config.groupsCollapsedByDefault;
            var ptr = self.groupedData;

            for (var y = 0; y < groups.length; y++) {
                var group = groups[y];

                var col = filterCols(cols, group)[0];

                var val = $utils.evalProperty(model, group);
                val = val ? val.toString() : 'null';
                if (!ptr[val]) {
                    ptr[val] = {};
                }
                if (!ptr[NG_FIELD]) {
                    ptr[NG_FIELD] = group;
                }
                if (!ptr[NG_DEPTH]) {
                    ptr[NG_DEPTH] = y;
                }
                if (!ptr[NG_COLUMN]) {
                    ptr[NG_COLUMN] = col;
                }
                ptr = ptr[val];
            }
            if (!ptr.values) {
                ptr.values = [];
            }
            ptr.values.push(rows[x]);
        }
        if(cols.length > 0) {
            for (var z = 0; z < groups.length; z++) {
                if (!cols[z].isAggCol && z <= maxDepth) {
                    cols.splice(0, 0, new ngColumn({
                        colDef: {
                            field: '',
                            width: 25,
                            sortable: false,
                            resizable: false,
                            headerCellTemplate: '<div class="ngAggHeader"></div>',
                            pinned: grid.config.pinSelectionCheckbox
                        },
                        enablePinning: grid.config.enablePinning,
                        isAggCol: true,
                        headerRowHeight: grid.config.headerRowHeight
                    }, $scope, grid, domUtilityService, $templateCache, $utils));
                }
            }
        }

        grid.fixColumnIndexes();
        $scope.adjustScrollLeft(0);
        self.parsedData.length = 0;
        self.parseGroupData(self.groupedData);
        self.fixRowCache();
    };

    if (grid.config.groups.length > 0 && grid.filteredRows.length > 0) {
        self.getGrouping(grid.config.groups);
    }
};
var ngSearchProvider = function ($scope, grid, $filter) {
    var self = this,
        searchConditions = [];

    self.extFilter = grid.config.filterOptions.useExternalFilter;
    $scope.showFilter = grid.config.showFilter;
    $scope.filterText = '';

    self.fieldMap = {};

    var convertToFieldMap = function(obj) {
        var fieldMap = {};
        for (var prop in obj) {
            if (obj.hasOwnProperty(prop)) {
                fieldMap[prop.toLowerCase()] = obj[prop];
            }
        }
        return fieldMap;
    };

    var searchEntireRow = function(condition, item, fieldMap){
        var result;
        for (var prop in item) {
            if (item.hasOwnProperty(prop)) {
                var c = fieldMap[prop.toLowerCase()];
                if (!c) {
                    continue;
                }
                var pVal = item[prop];
                if(typeof pVal === 'object' && !(pVal instanceof Date)) {
                    var objectFieldMap = convertToFieldMap(c);
                    result = searchEntireRow(condition, pVal, objectFieldMap);
                    if (result) {
                        return true;
                    }
                } else {
                    var f = null,
                        s = null;
                    if (c && c.cellFilter) {
                        s = c.cellFilter.split(':');
                        f = $filter(s[0]);
                    }
                    if (pVal !== null && pVal !== undefined) {
                        if (typeof f === "function") {
                            var filterRes = f(pVal, s[1].slice(1,-1)).toString();
                            result = condition.regex.test(filterRes);
                        } else {
                            result = condition.regex.test(pVal.toString());
                        }
                        if (result) {
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    };

    var searchColumn = function(condition, item){
        var result;
        var col = self.fieldMap[condition.columnDisplay];
        if (!col) {
            return false;
        }
        var sp = col.cellFilter.split(':');
        var filter = col.cellFilter ? $filter(sp[0]) : null;
        var value = item[condition.column] || item[col.field.split('.')[0]];
        if (value === null || value === undefined) {
            return false;
        }
        if (typeof filter === "function") {
            var filterResults = filter(typeof value === "object" ? evalObject(value, col.field) : value, sp[1]).toString();
            result = condition.regex.test(filterResults);
        }
        else {
            result = condition.regex.test(typeof value === "object" ? evalObject(value, col.field).toString() : value.toString());
        }
        if (result) {
            return true;
        }
        return false;
    };

    var filterFunc = function(item) {
        for (var x = 0, len = searchConditions.length; x < len; x++) {
            var condition = searchConditions[x];
            var result;
            if (!condition.column) {
                result = searchEntireRow(condition, item, self.fieldMap);
            } else {
                result = searchColumn(condition, item);
            }
            if(!result) {
                return false;
            }
        }
        return true;
    };

    self.evalFilter = function () {
        if (searchConditions.length === 0) {
            grid.filteredRows = grid.rowCache;
        } else {
            grid.filteredRows = grid.rowCache.filter(function(row) {
                return filterFunc(row.entity);
            });
        }
        for (var i = 0; i < grid.filteredRows.length; i++)
        {
            grid.filteredRows[i].rowIndex = i;

        }
        grid.rowFactory.filteredRowsChanged();
    };
    var evalObject = function (obj, columnName) {
        if (typeof obj !== "object" || typeof columnName !== "string") {
            return obj;
        }
        var args = columnName.split('.');
        var cObj = obj;
        if (args.length > 1) {
            for (var i = 1, len = args.length; i < len; i++) {
                cObj = cObj[args[i]];
                if (!cObj) {
                    return obj;
                }
            }
            return cObj;
        }
        return obj;
    };
    var getRegExp = function (str, modifiers) {
        try {
            return new RegExp(str, modifiers);
        } catch (err) {
            return new RegExp(str.replace(/(\^|\$|\(|\)|<|>|\[|\]|\{|\}|\\|\||\.|\*|\+|\?)/g, '\\$1'));
        }
    };
    var buildSearchConditions = function (a) {
        searchConditions = [];
        var qStr;
        if (!(qStr = $.trim(a))) {
            return;
        }
        var columnFilters = qStr.split(";");
        for (var i = 0; i < columnFilters.length; i++) {
            var args = columnFilters[i].split(':');
            if (args.length > 1) {
                var columnName = $.trim(args[0]);
                var columnValue = $.trim(args[1]);
                if (columnName && columnValue) {
                    searchConditions.push({
                        column: columnName,
                        columnDisplay: columnName.replace(/\s+/g, '').toLowerCase(),
                        regex: getRegExp(columnValue, 'i')
                    });
                }
            } else {
                var val = $.trim(args[0]);
                if (val) {
                    searchConditions.push({
                        column: '',
                        regex: getRegExp(val, 'i')
                    });
                }
            }
        }
    };

    if (!self.extFilter) {
         $scope.$on('$destroy', $scope.$watch('columns', function (cs) {
            for (var i = 0; i < cs.length; i++) {
                var col = cs[i];
                if (col.field) {
                    if(col.field.match(/\./g)){
                        var properties = col.field.split('.');
                        var currentProperty = self.fieldMap;
                        for(var j = 0; j < properties.length - 1; j++) {
                            currentProperty[ properties[j] ] =  currentProperty[ properties[j] ] || {};
                            currentProperty = currentProperty[properties[j]];
                        }
                        currentProperty[ properties[properties.length - 1] ] = col;
                    } else {
                        self.fieldMap[col.field.toLowerCase()] = col;
                    }
                }
                if (col.displayName) {
                    self.fieldMap[col.displayName.toLowerCase().replace(/\s+/g, '')] = col;
                }
            }
        }));
    }

     $scope.$on('$destroy', $scope.$watch(
        function () {
            return grid.config.filterOptions.filterText;
        },
        function (a) {
            $scope.filterText = a;
        }
    ));

    $scope.$on('$destroy', $scope.$watch('filterText', function(a){
        if (!self.extFilter) {
            $scope.$emit('ngGridEventFilter', a);
            buildSearchConditions(a);
            self.evalFilter();
        }
    }));
};

var ngSelectionProvider = function (grid, $scope, $parse) {
    var self = this;
    self.multi = grid.config.multiSelect;
    self.selectedItems = grid.config.selectedItems;
    self.selectedIndex = grid.config.selectedIndex;
    self.lastClickedRow = undefined;
    self.ignoreSelectedItemChanges = false; 
    self.pKeyParser = $parse(grid.config.primaryKey);
    self.ChangeSelection = function (rowItem, evt) {
        var charCode = evt.which || evt.keyCode;
        var isUpDownKeyPress = (charCode === 40 || charCode === 38);

        if (evt && evt.shiftKey && !evt.keyCode && self.multi && grid.config.enableRowSelection) {
            if (self.lastClickedRow) {
                var rowsArr;
                if ($scope.configGroups.length > 0) {
                    rowsArr = grid.rowFactory.parsedData.filter(function(row) {
                        return !row.isAggRow;
                    });
                }
                else {
                    rowsArr = grid.filteredRows;
                }

                var thisIndx = rowItem.rowIndex;
                var prevIndx = self.lastClickedRowIndex;
                if (thisIndx === prevIndx) {
                    return false;
                }

                if (thisIndx < prevIndx) {
                    thisIndx = thisIndx ^ prevIndx;
                    prevIndx = thisIndx ^ prevIndx;
                    thisIndx = thisIndx ^ prevIndx;
                    thisIndx--;
                }
                else {
                    prevIndx++;
                }

                var rows = [];
                for (; prevIndx <= thisIndx; prevIndx++) {
                    rows.push(rowsArr[prevIndx]);
                }

                if (rows[rows.length - 1].beforeSelectionChange(rows, evt)) {
                    for (var i = 0; i < rows.length; i++) {
                        var ri = rows[i];
                        var selectionState = ri.selected;
                        ri.selected = !selectionState;
                        if (ri.clone) {
                            ri.clone.selected = ri.selected;
                        }
                        var index = self.selectedItems.indexOf(ri.entity);
                        if (index === -1) {
                            self.selectedItems.push(ri.entity);
                        }
                        else {
                            self.selectedItems.splice(index, 1);
                        }
                    }
                    rows[rows.length - 1].afterSelectionChange(rows, evt);
                }
                self.lastClickedRow = rowItem;
                self.lastClickedRowIndex = rowItem.rowIndex;

                return true;
            }
        }
        else if (!self.multi) {
            if (self.lastClickedRow === rowItem) {
                self.setSelection(self.lastClickedRow, grid.config.keepLastSelected ? true : !rowItem.selected);
            } else {
                if (self.lastClickedRow) {
                    self.setSelection(self.lastClickedRow, false);
                }
                self.setSelection(rowItem, !rowItem.selected);
            }
        }
        else if (!evt.keyCode || isUpDownKeyPress && !grid.config.selectWithCheckboxOnly) {
            self.setSelection(rowItem, !rowItem.selected);
        }
        self.lastClickedRow = rowItem;
        self.lastClickedRowIndex = rowItem.rowIndex;
        return true;
    };

    self.getSelection = function (entity) {
        return self.getSelectionIndex(entity) !== -1;
    };

    self.getSelectionIndex = function (entity) {
        var index = -1;
        if (grid.config.primaryKey) {
            var val = self.pKeyParser(entity);
            angular.forEach(self.selectedItems, function (c, k) {
                if (val === self.pKeyParser(c)) {
                    index = k;
                }
            });
        }
        else {
            index = self.selectedItems.indexOf(entity);
        }
        return index;
    };
    self.setSelection = function (rowItem, isSelected) {
        if(grid.config.enableRowSelection){
            if (!isSelected) {
                var indx = self.getSelectionIndex(rowItem.entity);
                if (indx !== -1) {
                    self.selectedItems.splice(indx, 1);
                }
            }
            else {
                if (self.getSelectionIndex(rowItem.entity) === -1) {
                    if (!self.multi && self.selectedItems.length > 0) {
                        self.toggleSelectAll(false, true);
                    }
                    self.selectedItems.push(rowItem.entity);
                }
            }
            rowItem.selected = isSelected;
            if (rowItem.orig) {
                rowItem.orig.selected = isSelected;
            }
            if (rowItem.clone) {
                rowItem.clone.selected = isSelected;
            }
            rowItem.afterSelectionChange(rowItem);
        }
    };
    self.toggleSelectAll = function (checkAll, bypass, selectFiltered) {
        var rows = selectFiltered ? grid.filteredRows : grid.rowCache;
        if (bypass || grid.config.beforeSelectionChange(rows, checkAll)) {
            var selectedlength = self.selectedItems.length;
            if (selectedlength > 0) {
                self.selectedItems.length = 0;
            }
            for (var i = 0; i < rows.length; i++) {
                rows[i].selected = checkAll;
                if (rows[i].clone) {
                    rows[i].clone.selected = checkAll;
                }
                if (checkAll) {
                    self.selectedItems.push(rows[i].entity);
                }
            }
            if (!bypass) {
                grid.config.afterSelectionChange(rows, checkAll);
            }
        }
    };
};

var ngStyleProvider = function($scope, grid) {
    $scope.headerCellStyle = function(col) {
        return { "height": col.headerRowHeight + "px" };
    };
    $scope.rowStyle = function (row) {
        var ret = { "top": row.offsetTop + "px", "height": $scope.rowHeight + "px" };
        if (row.isAggRow) {
            ret.left = row.offsetLeft;
        }
        return ret;
    };
    $scope.canvasStyle = function() {
        return { "height": grid.maxCanvasHt + "px" };
    };
    $scope.headerScrollerStyle = function() {
        return { "height": grid.config.headerRowHeight + "px" };
    };
    $scope.topPanelStyle = function() {
        return { "width": grid.rootDim.outerWidth + "px", "height": $scope.topPanelHeight() + "px" };
    };
    $scope.headerStyle = function() {
        return { "width": grid.rootDim.outerWidth + "px", "height": grid.config.headerRowHeight + "px" };
    };
    $scope.groupPanelStyle = function () {
        return { "width": grid.rootDim.outerWidth + "px", "height": "32px" };
    };
    $scope.viewportStyle = function() {
        return { "width": grid.rootDim.outerWidth + "px", "height": $scope.viewportDimHeight() + "px" };
    };
    $scope.footerStyle = function() {
        return { "width": grid.rootDim.outerWidth + "px", "height": $scope.footerRowHeight + "px" };
    };
};
ngGridDirectives.directive('ngCellHasFocus', ['$domUtilityService',
    function (domUtilityService) {
        var focusOnInputElement = function($scope, elm) {
            $scope.isFocused = true;
            domUtilityService.digest($scope);

            $scope.$broadcast('ngGridEventStartCellEdit');
            $scope.$emit('ngGridEventStartCellEdit');

            $scope.$on('$destroy', $scope.$on('ngGridEventEndCellEdit', function() {
                $scope.isFocused = false;
                domUtilityService.digest($scope);
            }));
        };

        return function($scope, elm) {
            var isFocused = false;
            var isCellEditableOnMouseDown = false;

            $scope.editCell = function() {
                if(!$scope.enableCellEditOnFocus) {
                    setTimeout(function() {
                        focusOnInputElement($scope,elm);
                    }, 0);
                }
            };

            function mousedown (evt) {
                if($scope.enableCellEditOnFocus) {
                    isCellEditableOnMouseDown = true;
                } else {
                    elm.focus();
                }
                return true;
            }

            function click (evt) {
                if($scope.enableCellEditOnFocus) {
                    evt.preventDefault();
                    isCellEditableOnMouseDown = false;
                    focusOnInputElement($scope,elm);
                }
            }

            elm.bind('mousedown', mousedown);

            elm.bind('click', click);
            function focus (evt) {
                isFocused = true;
                if($scope.enableCellEditOnFocus && !isCellEditableOnMouseDown) {
                    focusOnInputElement($scope,elm);
                }
                return true;
            }

            elm.bind('focus', focus);

            function blur() {
                isFocused = false;
                return true;
            }

            elm.bind('blur', blur);

            function keydown (evt) {
                if(!$scope.enableCellEditOnFocus) {
                    if (isFocused && evt.keyCode !== 37 && evt.keyCode !== 38 && evt.keyCode !== 39 && evt.keyCode !== 40 && evt.keyCode !== 9 && !evt.shiftKey && evt.keyCode !== 13) {
                        focusOnInputElement($scope,elm);
                    }
                    if (isFocused && evt.shiftKey && (evt.keyCode >= 65 && evt.keyCode <= 90)) {
                        focusOnInputElement($scope, elm);
                    }
                    if (evt.keyCode === 27) {
                        elm.focus();
                    }
                }
                return true;
            }

            elm.bind('keydown', keydown);

            elm.on('$destroy', function() {
                elm.off('mousedown', mousedown);
                elm.off('click', click);
                elm.off('focus', focus);
                elm.off('blur', blur);
                elm.off('keydown', keydown);
            });
        };
    }]);

ngGridDirectives.directive('ngCellText',
  function () {
      return function(scope, elm) {
          function mouseover (evt) {
              evt.preventDefault();
          }
          elm.bind('mouseover', mouseover);

          function mouseleave(evt) {
              evt.preventDefault();
          }
          elm.bind('mouseleave', mouseleave);

          elm.on('$destroy', function() {
            elm.off('mouseover', mouseover);
            elm.off('mouseleave', mouseleave);
          });
      };
  });
ngGridDirectives.directive('ngCell', ['$compile', '$domUtilityService', function ($compile, domUtilityService) {
    var ngCell = {
        scope: false,
        compile: function() {
            return {
                pre: function($scope, iElement) {
                    var html;
                    var cellTemplate = $scope.col.cellTemplate.replace(COL_FIELD, 'row.entity.' + $scope.col.field);

                    if ($scope.col.enableCellEdit) {
                        html =  $scope.col.cellEditTemplate;
                        html = html.replace(CELL_EDITABLE_CONDITION, $scope.col.cellEditableCondition);
                        html = html.replace(DISPLAY_CELL_TEMPLATE, cellTemplate);
                        html = html.replace(EDITABLE_CELL_TEMPLATE, $scope.col.editableCellTemplate.replace(COL_FIELD, 'row.entity.' + $scope.col.field));
                    } else {
                        html = cellTemplate;
                    }

                    var cellElement = $(html);
                    iElement.append(cellElement);
                    $compile(cellElement)($scope);
                    if ($scope.enableCellSelection && cellElement[0].className.indexOf('ngSelectionCell') === -1) {
                        cellElement[0].setAttribute('tabindex', 0);
                        cellElement.addClass('ngCellElement');
                    }
                },
                post: function($scope, iElement) {
                    if ($scope.enableCellSelection) {
                        $scope.domAccessProvider.selectionHandlers($scope, iElement);
                    }
                    $scope.$on('$destroy', $scope.$on('ngGridEventDigestCell', function() {
                        domUtilityService.digest($scope);
                    }));
                }
            };
        }
    };
    return ngCell;
}]);

ngGridDirectives.directive('ngEditCellIf', [function () {
  return {
    transclude: 'element',
    priority: 1000,
    terminal: true,
    restrict: 'A',
    compile: function (e, a, transclude) {
      return function (scope, element, attr) {

        var childElement;
        var childScope;
         scope.$on('$destroy', scope.$watch(attr['ngEditCellIf'], function (newValue) {
          if (childElement) {
            childElement.remove();
            childElement = undefined;
          }
          if (childScope) {
            childScope.$destroy();
            childScope = undefined;
          }

          if (newValue) {
            childScope = scope.$new();
            transclude(childScope, function (clone) {
              childElement = clone;
              element.after(clone);
            });
          }
        }));
      };
    }
  };
}]);
ngGridDirectives.directive('ngGridFooter', ['$compile', '$templateCache', function ($compile, $templateCache) {
    var ngGridFooter = {
        scope: false,
        compile: function () {
            return {
                pre: function ($scope, iElement) {
                    if (iElement.children().length === 0) {
                        iElement.append($compile($templateCache.get($scope.gridId + 'footerTemplate.html'))($scope));
                    }
                }
            };
        }
    };
    return ngGridFooter;
}]);
ngGridDirectives.directive('ngGridMenu', ['$compile', '$templateCache', function ($compile, $templateCache) {
    var ngGridMenu = {
        scope: false,
        compile: function () {
            return {
                pre: function ($scope, iElement) {
                    if (iElement.children().length === 0) {
                        iElement.append($compile($templateCache.get($scope.gridId + 'menuTemplate.html'))($scope));
                    }
                }
            };
        }
    };
    return ngGridMenu;
}]);
ngGridDirectives.directive('ngGrid', ['$compile', '$filter', '$templateCache', '$sortService', '$domUtilityService', '$utilityService', '$timeout', '$parse', '$http', '$q', function ($compile, $filter, $templateCache, sortService, domUtilityService, $utils, $timeout, $parse, $http, $q) {
    var ngGridDirective = {
        scope: true,
        compile: function() {
            return {
                pre: function($scope, iElement, iAttrs) {
                    var $element = $(iElement);
                    var options = $scope.$eval(iAttrs.ngGrid);
                    options.gridDim = new ngDimension({ outerHeight: $($element).height(), outerWidth: $($element).width() });

                    var grid = new ngGrid($scope, options, sortService, domUtilityService, $filter, $templateCache, $utils, $timeout, $parse, $http, $q);
                    $scope.$on('$destroy', function cleanOptions() {
                        options.gridDim = null;
                        options.selectRow = null;
                        options.selectItem = null;
                        options.selectAll = null;
                        options.selectVisible = null;
                        options.groupBy = null;
                        options.sortBy = null;
                        options.gridId = null;
                        options.ngGrid = null;
                        options.$gridScope = null;
                        options.$gridServices = null;

                        $scope.domAccessProvider.grid = null;
                        angular.element(grid.styleSheet).remove();
                        grid.styleSheet = null;
                    });

                    return grid.init().then(function() {
                        if (typeof options.columnDefs === "string") {
                            $scope.$on('$destroy', $scope.$parent.$watch(options.columnDefs, function (a) {
                                if (!a) {
                                    grid.refreshDomSizes();
                                    grid.buildColumns();
                                    return;
                                }
                                grid.lateBoundColumns = false;
                                $scope.columns = [];
                                grid.config.columnDefs = a;
                                grid.buildColumns();
                                grid.eventProvider.assignEvents();
                                domUtilityService.RebuildGrid($scope, grid);
                            }, true));
                        }
                        else {
                            grid.buildColumns();
                        }
                        if (typeof options.totalServerItems === "string") {
                            $scope.$on('$destroy', $scope.$parent.$watch(options.totalServerItems, function (newTotal, oldTotal) {
                                if (!angular.isDefined(newTotal)) {
                                    $scope.totalServerItems = 0;
                                }
                                else {
                                    $scope.totalServerItems = newTotal;
                                }
                            }));
                        }
                        else {
                            $scope.totalServerItems = 0;
                        }
                        if (typeof options.data === "string") {
                            var dataWatcher = function (a) {
                                grid.data = $.extend([], a);
                                grid.rowFactory.fixRowCache();
                                angular.forEach(grid.data, function (item, j) {
                                    var indx = grid.rowMap[j] || j;
                                    if (grid.rowCache[indx]) {
                                        grid.rowCache[indx].ensureEntity(item);
                                    }
                                    grid.rowMap[indx] = j;
                                });
                                grid.searchProvider.evalFilter();
                                grid.configureColumnWidths();
                                grid.refreshDomSizes();
                                if (grid.config.sortInfo.fields.length > 0) {
                                    grid.sortColumnsInit();
                                    $scope.$emit('ngGridEventSorted', grid.config.sortInfo);
                                }
                                $scope.$emit("ngGridEventData", grid.gridId);
                            };
                            $scope.$on('$destroy', $scope.$parent.$watch(options.data, dataWatcher));
                            $scope.$on('$destroy', $scope.$parent.$watch(options.data + '.length', function() {
                                dataWatcher($scope.$eval(options.data));
								$scope.adjustScrollTop(grid.$viewport.scrollTop(), true);
                            }));
                        }
                        grid.footerController = new ngFooter($scope, grid);
                        iElement.addClass("ngGrid").addClass(grid.gridId.toString());
                        if (!options.enableHighlighting) {
                            iElement.addClass("unselectable");
                        }
                        if (options.jqueryUITheme) {
                            iElement.addClass('ui-widget');
                        }
                        iElement.append($compile($templateCache.get('gridTemplate.html'))($scope));
                        domUtilityService.AssignGridContainers($scope, iElement, grid);
                        grid.eventProvider = new ngEventProvider(grid, $scope, domUtilityService, $timeout);
                        options.selectRow = function (rowIndex, state) {
                            if (grid.rowCache[rowIndex]) {
                                if (grid.rowCache[rowIndex].clone) {
                                    grid.rowCache[rowIndex].clone.setSelection(state ? true : false);
                                } 
                                grid.rowCache[rowIndex].setSelection(state ? true : false);
                            }
                        };
                        options.selectItem = function (itemIndex, state) {
                            options.selectRow(grid.rowMap[itemIndex], state);
                        };
                        options.selectAll = function (state) {
                            $scope.toggleSelectAll(state);
                        };
                        options.selectVisible = function (state) {
                            $scope.toggleSelectAll(state, true);
                        };
                        options.groupBy = function (field) {
                            if (field) {
                                $scope.groupBy($scope.columns.filter(function(c) {
                                    return c.field === field;
                                })[0]);
                            } else {
                                var arr = $.extend(true, [], $scope.configGroups);
                                angular.forEach(arr, $scope.groupBy);
                            }
                        };
                        options.sortBy = function (field) {
                            var col = $scope.columns.filter(function (c) {
                                return c.field === field;
                            })[0];
                            if (col) {
                                col.sort();
                            }
                        };
                        options.gridId = grid.gridId;
                        options.ngGrid = grid;
                        options.$gridScope = $scope;
                        options.$gridServices = { SortService: sortService, DomUtilityService: domUtilityService, UtilityService: $utils };

                        $scope.$on('$destroy', $scope.$on('ngGridEventDigestGrid', function(){
                            domUtilityService.digest($scope.$parent);
                        }));
                        $scope.$on('$destroy', $scope.$on('ngGridEventDigestGridParent', function(){
                            domUtilityService.digest($scope.$parent);
                        }));
                        $scope.$evalAsync(function() {
                            $scope.adjustScrollLeft(0);
                        });
                        angular.forEach(options.plugins, function (p) {
                            if (typeof p === "function") {
                                p = new p(); 
                            }
                            var newScope = $scope.$new();
                            p.init(newScope, grid, options.$gridServices);
                            options.plugins[$utils.getInstanceType(p)] = p;

                            $scope.$on('$destroy', function() {
                                newScope.$destroy();
                            });
                        });
                        if (typeof options.init === "function") {
                            options.init(grid, $scope);
                        }
                        return null;
                    });
                }
            };
        }
    };
    return ngGridDirective;
}]);

ngGridDirectives.directive('ngHeaderCell', ['$compile', function($compile) {
    var ngHeaderCell = {
        scope: false,
        compile: function() {
            return {
                pre: function($scope, iElement) {
                    iElement.append($compile($scope.col.headerCellTemplate)($scope));
                }
            };
        }
    };
    return ngHeaderCell;
}]);
ngGridDirectives.directive('ngHeaderRow', ['$compile', '$templateCache', function ($compile, $templateCache) {
    var ngHeaderRow = {
        scope: false,
        compile: function () {
            return {
                pre: function ($scope, iElement) {
                    if (iElement.children().length === 0) {
                        iElement.append($compile($templateCache.get($scope.gridId + 'headerRowTemplate.html'))($scope));
                    }
                }
            };
        }
    };
    return ngHeaderRow;
}]);
ngGridDirectives.directive('ngInput', [function() {
    return {
        require: 'ngModel',
        link: function (scope, elm, attrs, ngModel) {
            var oldCellValue;
            var dereg = scope.$watch('ngModel', function() {
                oldCellValue = ngModel.$modelValue;
                dereg(); 
            });

            function keydown (evt) {
                switch (evt.keyCode) {
                    case 37: 
                    case 38: 
                    case 39: 
                    case 40: 
                        evt.stopPropagation();
                        break;
                    case 27: 
                        if (!scope.$$phase) {
                            scope.$apply(function() {
                                ngModel.$setViewValue(oldCellValue);
                                elm.blur();
                            });
                        }
                        break;
                    case 13: 
                        if(scope.enableCellEditOnFocus && scope.totalFilteredItemsLength() - 1 > scope.row.rowIndex && scope.row.rowIndex > 0  || scope.col.enableCellEdit) {
                            elm.blur();
                        }
                        break;
                }

                return true;
            }
            elm.bind('keydown', keydown);

            function click (evt) {
                evt.stopPropagation();
            }

            elm.bind('click', click);
            function mousedown (evt) {
                evt.stopPropagation();
            }

            elm.bind('mousedown', mousedown);

            elm.on('$destroy', function() {
                elm.off('keydown', keydown);
                elm.off('click', click);
                elm.off('mousedown', mousedown);
            });

            scope.$on('$destroy', scope.$on('ngGridEventStartCellEdit', function () {
                elm.focus();
                elm.select();
            }));

            angular.element(elm).bind('blur', function () {
                scope.$emit('ngGridEventEndCellEdit');
            });
        }
    };
}]);

ngGridDirectives.directive('ngRow', ['$compile', '$domUtilityService', '$templateCache', function ($compile, domUtilityService, $templateCache) {
    var ngRow = {
        scope: false,
        compile: function() {
            return {
                pre: function($scope, iElement) {
                    $scope.row.elm = iElement;
                    if ($scope.row.clone) {
                        $scope.row.clone.elm = iElement;
                    }
                    if ($scope.row.isAggRow) {
                        var html = $templateCache.get($scope.gridId + 'aggregateTemplate.html');
                        if ($scope.row.aggLabelFilter) {
                            html = html.replace(CUSTOM_FILTERS, '| ' + $scope.row.aggLabelFilter);
                        } else {
                            html = html.replace(CUSTOM_FILTERS, "");
                        }
                        iElement.append($compile(html)($scope));
                    } else {
                        iElement.append($compile($templateCache.get($scope.gridId + 'rowTemplate.html'))($scope));
                    }
					$scope.$on('$destroy', $scope.$on('ngGridEventDigestRow', function(){
						domUtilityService.digest($scope);
					}));
                }
            };
        }
    };
    return ngRow;
}]);
ngGridDirectives.directive('ngViewport', [function() {
    return function($scope, elm) {
        var isMouseWheelActive;
        var prevScollLeft;
        var prevScollTop = 0;
        var ensureDigest = function() {
            if (!$scope.$root.$$phase) {
                $scope.$digest();
            }
        };
        var scrollTimer;

        function scroll (evt) {
            var scrollLeft = evt.target.scrollLeft,
                scrollTop = evt.target.scrollTop;
            if ($scope.$headerContainer) {
                $scope.$headerContainer.scrollLeft(scrollLeft);
            }
            $scope.adjustScrollLeft(scrollLeft);
            $scope.adjustScrollTop(scrollTop);
            if ($scope.forceSyncScrolling) {
                ensureDigest();
            } else {
                clearTimeout(scrollTimer);
                scrollTimer = setTimeout(ensureDigest, 150);
            }
            prevScollLeft = scrollLeft;
            prevScollTop = scrollTop;
            isMouseWheelActive = false;
            return true;
        }

        elm.bind('scroll', scroll);

        function mousewheel() {
            isMouseWheelActive = true;
            if (elm.focus) { elm.focus(); }
            return true;
        }

        elm.bind("mousewheel DOMMouseScroll", mousewheel);

        elm.on('$destroy', function() {
            elm.off('scroll', scroll);
            elm.off('mousewheel DOMMouseScroll', mousewheel);
        });

        if (!$scope.enableCellSelection) {
            $scope.domAccessProvider.selectionHandlers($scope, elm);
        }
    };
}]);
window.ngGrid.i18n['da'] = {
    ngAggregateLabel: 'artikler',
    ngGroupPanelDescription: 'Grupér rækker udfra en kolonne ved at trække dens overskift hertil.',
    ngSearchPlaceHolder: 'Søg...',
    ngMenuText: 'Vælg kolonner:',
    ngShowingItemsLabel: 'Viste rækker:',
    ngTotalItemsLabel: 'Rækker totalt:',
    ngSelectedItemsLabel: 'Valgte rækker:',
    ngPageSizeLabel: 'Side størrelse:',
    ngPagerFirstTitle: 'Første side',
    ngPagerNextTitle: 'Næste side',
    ngPagerPrevTitle: 'Forrige side',
    ngPagerLastTitle: 'Sidste side'
};
window.ngGrid.i18n['de'] = {
    ngAggregateLabel: 'eintrag',
    ngGroupPanelDescription: 'Ziehen Sie eine Spaltenüberschrift hierhin um nach dieser Spalte zu gruppieren.',
    ngSearchPlaceHolder: 'Suche...',
    ngMenuText: 'Spalten auswählen:',
    ngShowingItemsLabel: 'Zeige Einträge:',
    ngTotalItemsLabel: 'Einträge gesamt:',
    ngSelectedItemsLabel: 'Ausgewählte Einträge:',
    ngPageSizeLabel: 'Einträge pro Seite:',
    ngPagerFirstTitle: 'Erste Seite',
    ngPagerNextTitle: 'Nächste Seite',
    ngPagerPrevTitle: 'Vorherige Seite',
    ngPagerLastTitle: 'Letzte Seite'
};
window.ngGrid.i18n['en'] = {
    ngAggregateLabel: 'items',
    ngGroupPanelDescription: 'Drag a column header here and drop it to group by that column.',
    ngSearchPlaceHolder: 'Search...',
    ngMenuText: 'Choose Columns:',
    ngShowingItemsLabel: 'Showing Items:',
    ngTotalItemsLabel: 'Total Items:',
    ngSelectedItemsLabel: 'Selected Items:',
    ngPageSizeLabel: 'Page Size:',
    ngPagerFirstTitle: 'First Page',
    ngPagerNextTitle: 'Next Page',
    ngPagerPrevTitle: 'Previous Page',
    ngPagerLastTitle: 'Last Page'
};
window.ngGrid.i18n['es'] = {
    ngAggregateLabel: 'Artículos',
    ngGroupPanelDescription: 'Arrastre un encabezado de columna aquí y soltarlo para agrupar por esa columna.',
    ngSearchPlaceHolder: 'Buscar...',
    ngMenuText: 'Elegir columnas:',
    ngShowingItemsLabel: 'Artículos Mostrando:',
    ngTotalItemsLabel: 'Artículos Totales:',
    ngSelectedItemsLabel: 'Artículos Seleccionados:',
    ngPageSizeLabel: 'Tamaño de Página:',
    ngPagerFirstTitle: 'Primera Página',
    ngPagerNextTitle: 'Página Siguiente',
    ngPagerPrevTitle: 'Página Anterior',
    ngPagerLastTitle: 'Última Página'
};
window.ngGrid.i18n['fa'] = {
    ngAggregateLabel: 'موردها',
    ngGroupPanelDescription: 'یک عنوان ستون اینجا را بردار و به گروهی از آن ستون بیانداز.',
    ngSearchPlaceHolder: 'جستجو...',
    ngMenuText: 'انتخاب ستون\u200cها:',
    ngShowingItemsLabel: 'نمایش موردها:',
    ngTotalItemsLabel: 'همهٔ موردها:',
    ngSelectedItemsLabel: 'موردهای انتخاب\u200cشده:',
    ngPageSizeLabel: 'اندازهٔ صفحه:',
    ngPagerFirstTitle: 'صفحهٔ اول',
    ngPagerNextTitle: 'صفحهٔ بعد',
    ngPagerPrevTitle: 'صفحهٔ قبل',
    ngPagerLastTitle: 'آخرین صفحه'
};

window.ngGrid.i18n['fr'] = {
    ngAggregateLabel: 'articles',
    ngGroupPanelDescription: 'Faites glisser un en-tête de colonne ici et déposez-le vers un groupe par cette colonne.',
    ngSearchPlaceHolder: 'Recherche...',
    ngMenuText: 'Choisir des colonnes:',
    ngShowingItemsLabel: 'Articles Affichage des:',
    ngTotalItemsLabel: 'Nombre total d\'articles:',
    ngSelectedItemsLabel: 'Éléments Articles:',
    ngPageSizeLabel: 'Taille de page:',
    ngPagerFirstTitle: 'Première page',
    ngPagerNextTitle: 'Page Suivante',
    ngPagerPrevTitle: 'Page précédente',
    ngPagerLastTitle: 'Dernière page'
};
window.ngGrid.i18n['nl'] = {
    ngAggregateLabel: 'items',
    ngGroupPanelDescription: 'Sleep hier een kolomkop om op te groeperen.',
    ngSearchPlaceHolder: 'Zoeken...',
    ngMenuText: 'Kies kolommen:',
    ngShowingItemsLabel: 'Toon items:',
    ngTotalItemsLabel: 'Totaal items:',
    ngSelectedItemsLabel: 'Geselecteerde items:',
    ngPageSizeLabel: 'Pagina grootte:, ',
    ngPagerFirstTitle: 'Eerste pagina',
    ngPagerNextTitle: 'Volgende pagina',
    ngPagerPrevTitle: 'Vorige pagina',
    ngPagerLastTitle: 'Laatste pagina'
};

window.ngGrid.i18n['pt-br'] = {
    ngAggregateLabel: 'itens',
    ngGroupPanelDescription: 'Arraste e solte uma coluna aqui para agrupar por essa coluna',
    ngSearchPlaceHolder: 'Procurar...',
    ngMenuText: 'Selecione as colunas:',
    ngShowingItemsLabel: 'Mostrando os Itens:',
    ngTotalItemsLabel: 'Total de Itens:',
    ngSelectedItemsLabel: 'Items Selecionados:',
    ngPageSizeLabel: 'Tamanho da Página:',
    ngPagerFirstTitle: 'Primeira Página',
    ngPagerNextTitle: 'Próxima Página',
    ngPagerPrevTitle: 'Página Anterior',
    ngPagerLastTitle: 'Última Página'
};

window.ngGrid.i18n['zh-cn'] = {
    ngAggregateLabel: '条目',
    ngGroupPanelDescription: '拖曳表头到此处以进行分组',
    ngSearchPlaceHolder: '搜索...',
    ngMenuText: '数据分组与选择列：',
    ngShowingItemsLabel: '当前显示条目：',
    ngTotalItemsLabel: '条目总数：',
    ngSelectedItemsLabel: '选中条目：',
    ngPageSizeLabel: '每页显示数：',
    ngPagerFirstTitle: '回到首页',
    ngPagerNextTitle: '下一页',
    ngPagerPrevTitle: '上一页',
    ngPagerLastTitle: '前往尾页' 
};

window.ngGrid.i18n['zh-tw'] = {
    ngAggregateLabel: '筆',
    ngGroupPanelDescription: '拖拉表頭到此處以進行分組',
    ngSearchPlaceHolder: '搜尋...',
    ngMenuText: '選擇欄位：',
    ngShowingItemsLabel: '目前顯示筆數：',
    ngTotalItemsLabel: '總筆數：',
    ngSelectedItemsLabel: '選取筆數：',
    ngPageSizeLabel: '每頁顯示：',
    ngPagerFirstTitle: '第一頁',
    ngPagerNextTitle: '下一頁',
    ngPagerPrevTitle: '上一頁',
    ngPagerLastTitle: '最後頁'
};

angular.module('ngGrid').run(['$templateCache', function($templateCache) {
  'use strict';

  $templateCache.put('aggregateTemplate.html',
    "<div ng-click=\"row.toggleExpand()\" ng-style=\"rowStyle(row)\" class=\"ngAggregate\">\r" +
    "\n" +
    "    <span class=\"ngAggregateText\">{{row.label CUSTOM_FILTERS}} ({{row.totalChildren()}} {{AggItemsLabel}})</span>\r" +
    "\n" +
    "    <div class=\"{{row.aggClass()}}\"></div>\r" +
    "\n" +
    "</div>\r" +
    "\n"
  );
  $templateCache.put('cellEditTemplate.html',
    "<div ng-cell-has-focus ng-dblclick=\"CELL_EDITABLE_CONDITION && editCell()\">\r" +
    "\n" +
    "\t<div ng-edit-cell-if=\"!(isFocused && CELL_EDITABLE_CONDITION)\">\t\r" +
    "\n" +
    "\t\tDISPLAY_CELL_TEMPLATE\r" +
    "\n" +
    "\t</div>\r" +
    "\n" +
    "\t<div ng-edit-cell-if=\"isFocused && CELL_EDITABLE_CONDITION\">\r" +
    "\n" +
    "\t\tEDITABLE_CELL_TEMPLATE\r" +
    "\n" +
    "\t</div>\r" +
    "\n" +
    "</div>\r" +
    "\n"
  );
  $templateCache.put('cellTemplate.html',
    "<div class=\"ngCellText\" ng-class=\"col.colIndex()\"><span ng-cell-text ng-if=\"'COL_FIELD' != 'row.entity.'\">{{COL_FIELD CUSTOM_FILTERS}}</span></div>"
  );
  $templateCache.put('checkboxCellTemplate.html',
    "<div class=\"ngSelectionCell\"><input tabindex=\"-1\" class=\"ngSelectionCheckbox\" type=\"checkbox\" ng-checked=\"row.selected\" /></div>"
  );
  $templateCache.put('checkboxHeaderTemplate.html',
    "<input class=\"ngSelectionHeader\" type=\"checkbox\" ng-show=\"multiSelect\" ng-model=\"allSelected\" ng-change=\"toggleSelectAll(allSelected, true)\"/>"
  );
  $templateCache.put('editableCellTemplate.html',
    "<input ng-class=\"'colt' + col.index\" ng-input=\"COL_FIELD\" ng-model=\"COL_FIELD\" />"
  );
  $templateCache.put('footerTemplate.html',
    "<div ng-show=\"showFooter\" class=\"ngFooterPanel\" ng-class=\"{'ui-widget-content': jqueryUITheme, 'ui-corner-bottom': jqueryUITheme}\" ng-style=\"footerStyle()\">\r" +
    "\n" +
    "    <div class=\"ngTotalSelectContainer\" >\r" +
    "\n" +
    "        <div class=\"ngFooterTotalItems\" ng-class=\"{'ngNoMultiSelect': !multiSelect}\" >\r" +
    "\n" +
    "            <span class=\"ngLabel\">{{i18n.ngTotalItemsLabel}} {{maxRows()}}</span><span ng-show=\"filterText.length > 0\" class=\"ngLabel\">({{i18n.ngShowingItemsLabel}} {{totalFilteredItemsLength()}})</span>\r" +
    "\n" +
    "        </div>\r" +
    "\n" +
    "        <div class=\"ngFooterSelectedItems\" ng-show=\"multiSelect\">\r" +
    "\n" +
    "            <span class=\"ngLabel\">{{i18n.ngSelectedItemsLabel}} {{selectedItems.length}}</span>\r" +
    "\n" +
    "        </div>\r" +
    "\n" +
    "    </div>\r" +
    "\n" +
    "    <div class=\"ngPagerContainer\" style=\"float: right; margin-top: 10px;\" ng-show=\"enablePaging\" ng-class=\"{'ngNoMultiSelect': !multiSelect}\">\r" +
    "\n" +
    "        <div style=\"float:left; margin-right: 10px;\" class=\"ngRowCountPicker\">\r" +
    "\n" +
    "            <span style=\"float: left; margin-top: 3px;\" class=\"ngLabel\">{{i18n.ngPageSizeLabel}}</span>\r" +
    "\n" +
    "            <select style=\"float: left;height: 27px; width: 100px\" ng-model=\"pagingOptions.pageSize\" >\r" +
    "\n" +
    "                <option ng-repeat=\"size in pagingOptions.pageSizes\">{{size}}</option>\r" +
    "\n" +
    "            </select>\r" +
    "\n" +
    "        </div>\r" +
    "\n" +
    "        <div style=\"float:left; margin-right: 10px; line-height:25px;\" class=\"ngPagerControl\" style=\"float: left; min-width: 135px;\">\r" +
    "\n" +
    "            <button type=\"button\" class=\"ngPagerButton\" ng-click=\"pageToFirst()\" ng-disabled=\"cantPageBackward()\" title=\"{{i18n.ngPagerFirstTitle}}\"><div class=\"ngPagerFirstTriangle\"><div class=\"ngPagerFirstBar\"></div></div></button>\r" +
    "\n" +
    "            <button type=\"button\" class=\"ngPagerButton\" ng-click=\"pageBackward()\" ng-disabled=\"cantPageBackward()\" title=\"{{i18n.ngPagerPrevTitle}}\"><div class=\"ngPagerFirstTriangle ngPagerPrevTriangle\"></div></button>\r" +
    "\n" +
    "            <input class=\"ngPagerCurrent\" min=\"1\" max=\"{{currentMaxPages}}\" type=\"number\" style=\"width:50px; height: 24px; margin-top: 1px; padding: 0 4px;\" ng-model=\"pagingOptions.currentPage\"/>\r" +
    "\n" +
    "            <span class=\"ngGridMaxPagesNumber\" ng-show=\"maxPages() > 0\">/ {{maxPages()}}</span>\r" +
    "\n" +
    "            <button type=\"button\" class=\"ngPagerButton\" ng-click=\"pageForward()\" ng-disabled=\"cantPageForward()\" title=\"{{i18n.ngPagerNextTitle}}\"><div class=\"ngPagerLastTriangle ngPagerNextTriangle\"></div></button>\r" +
    "\n" +
    "            <button type=\"button\" class=\"ngPagerButton\" ng-click=\"pageToLast()\" ng-disabled=\"cantPageToLast()\" title=\"{{i18n.ngPagerLastTitle}}\"><div class=\"ngPagerLastTriangle\"><div class=\"ngPagerLastBar\"></div></div></button>\r" +
    "\n" +
    "        </div>\r" +
    "\n" +
    "    </div>\r" +
    "\n" +
    "</div>\r" +
    "\n"
  );
  $templateCache.put('gridTemplate.html',
    "<div class=\"ngTopPanel\" ng-class=\"{'ui-widget-header':jqueryUITheme, 'ui-corner-top': jqueryUITheme}\" ng-style=\"topPanelStyle()\">\r" +
    "\n" +
    "    <div class=\"ngGroupPanel\" ng-show=\"showGroupPanel()\" ng-style=\"groupPanelStyle()\">\r" +
    "\n" +
    "        <div class=\"ngGroupPanelDescription\" ng-show=\"configGroups.length == 0\">{{i18n.ngGroupPanelDescription}}</div>\r" +
    "\n" +
    "        <ul ng-show=\"configGroups.length > 0\" class=\"ngGroupList\">\r" +
    "\n" +
    "            <li class=\"ngGroupItem\" ng-repeat=\"group in configGroups\">\r" +
    "\n" +
    "                <span class=\"ngGroupElement\">\r" +
    "\n" +
    "                    <span class=\"ngGroupName\">{{group.displayName}}\r" +
    "\n" +
    "                        <span ng-click=\"removeGroup($index)\" class=\"ngRemoveGroup\">x</span>\r" +
    "\n" +
    "                    </span>\r" +
    "\n" +
    "                    <span ng-hide=\"$last\" class=\"ngGroupArrow\"></span>\r" +
    "\n" +
    "                </span>\r" +
    "\n" +
    "            </li>\r" +
    "\n" +
    "        </ul>\r" +
    "\n" +
    "    </div>\r" +
    "\n" +
    "    <div class=\"ngHeaderContainer\" ng-style=\"headerStyle()\">\r" +
    "\n" +
    "        <div ng-header-row class=\"ngHeaderScroller\" ng-style=\"headerScrollerStyle()\"></div>\r" +
    "\n" +
    "    </div>\r" +
    "\n" +
    "    <div ng-grid-menu></div>\r" +
    "\n" +
    "</div>\r" +
    "\n" +
    "<div class=\"ngViewport\" unselectable=\"on\" ng-viewport ng-class=\"{'ui-widget-content': jqueryUITheme}\" ng-style=\"viewportStyle()\">\r" +
    "\n" +
    "    <div class=\"ngCanvas\" ng-style=\"canvasStyle()\">\r" +
    "\n" +
    "        <div ng-style=\"rowStyle(row)\" ng-repeat=\"row in renderedRows\" ng-click=\"row.toggleSelected($event)\" ng-class=\"row.alternatingRowClass()\" ng-row></div>\r" +
    "\n" +
    "    </div>\r" +
    "\n" +
    "</div>\r" +
    "\n" +
    "<div ng-grid-footer></div>\r" +
    "\n"
  );
  $templateCache.put('headerCellTemplate.html',
    "<div class=\"ngHeaderSortColumn {{col.headerClass}}\" ng-style=\"{'cursor': col.cursor}\" ng-class=\"{ 'ngSorted': !col.noSortVisible() }\">\r" +
    "\n" +
    "    <div ng-click=\"col.sort($event)\" ng-class=\"'colt' + col.index\" class=\"ngHeaderText\">{{col.displayName}}</div>\r" +
    "\n" +
    "    <div class=\"ngSortButtonDown\" ng-click=\"col.sort($event)\" ng-show=\"col.showSortButtonDown()\"></div>\r" +
    "\n" +
    "    <div class=\"ngSortButtonUp\" ng-click=\"col.sort($event)\" ng-show=\"col.showSortButtonUp()\"></div>\r" +
    "\n" +
    "    <div class=\"ngSortPriority\">{{col.sortPriority}}</div>\r" +
    "\n" +
    "    <div ng-class=\"{ ngPinnedIcon: col.pinned, ngUnPinnedIcon: !col.pinned }\" ng-click=\"togglePin(col)\" ng-show=\"col.pinnable\"></div>\r" +
    "\n" +
    "</div>\r" +
    "\n" +
    "<div ng-show=\"col.resizable\" class=\"ngHeaderGrip\" ng-click=\"col.gripClick($event)\" ng-mousedown=\"col.gripOnMouseDown($event)\"></div>\r" +
    "\n"
  );
  $templateCache.put('headerRowTemplate.html',
    "<div ng-style=\"{ height: col.headerRowHeight }\" ng-repeat=\"col in renderedColumns\" ng-class=\"col.colIndex()\" class=\"ngHeaderCell\">\r" +
    "\n" +
    "\t<div class=\"ngVerticalBar\" ng-style=\"{height: col.headerRowHeight}\" ng-class=\"{ ngVerticalBarVisible: !$last }\">&nbsp;</div>\r" +
    "\n" +
    "\t<div ng-header-cell></div>\r" +
    "\n" +
    "</div>"
  );
  $templateCache.put('menuTemplate.html',
    "<div ng-show=\"showColumnMenu || showFilter\"  class=\"ngHeaderButton\" ng-click=\"toggleShowMenu()\">\r" +
    "\n" +
    "    <div class=\"ngHeaderButtonArrow\"></div>\r" +
    "\n" +
    "</div>\r" +
    "\n" +
    "<div ng-show=\"showMenu\" class=\"ngColMenu\">\r" +
    "\n" +
    "    <div ng-show=\"showFilter\">\r" +
    "\n" +
    "        <input placeholder=\"{{i18n.ngSearchPlaceHolder}}\" type=\"text\" ng-model=\"filterText\"/>\r" +
    "\n" +
    "    </div>\r" +
    "\n" +
    "    <div ng-show=\"showColumnMenu\">\r" +
    "\n" +
    "        <span class=\"ngMenuText\">{{i18n.ngMenuText}}</span>\r" +
    "\n" +
    "        <ul class=\"ngColList\">\r" +
    "\n" +
    "            <li class=\"ngColListItem\" ng-repeat=\"col in columns | ngColumns\">\r" +
    "\n" +
    "                <label><input ng-disabled=\"col.pinned\" type=\"checkbox\" class=\"ngColListCheckbox\" ng-model=\"col.visible\"/>{{col.displayName}}</label>\r" +
    "\n" +
    "\t\t\t\t<a title=\"Group By\" ng-class=\"col.groupedByClass()\" ng-show=\"col.groupable && col.visible\" ng-click=\"groupBy(col)\"></a>\r" +
    "\n" +
    "\t\t\t\t<span class=\"ngGroupingNumber\" ng-show=\"col.groupIndex > 0\">{{col.groupIndex}}</span>          \r" +
    "\n" +
    "            </li>\r" +
    "\n" +
    "        </ul>\r" +
    "\n" +
    "    </div>\r" +
    "\n" +
    "</div>"
  );
  $templateCache.put('rowTemplate.html',
    "<div ng-style=\"{ 'cursor': row.cursor }\" ng-repeat=\"col in renderedColumns\" ng-class=\"col.colIndex()\" class=\"ngCell {{col.cellClass}}\">\r" +
    "\n" +
    "\t<div class=\"ngVerticalBar\" ng-style=\"{height: rowHeight}\" ng-class=\"{ ngVerticalBarVisible: !$last }\">&nbsp;</div>\r" +
    "\n" +
    "\t<div ng-cell></div>\r" +
    "\n" +
    "</div>"
  );

}]);

}(window, jQuery));
/**
 * @namespace bootstrapLightbox
 */
angular.module('bootstrapLightbox', [
    'ngTouch',
    'ui.bootstrap',
    'chieffancypants.loadingBar',
]);
angular.module('bootstrapLightbox').run(['$templateCache', function ($templateCache) {
    'use strict';

    $templateCache.put('lightbox.tpl',
        "<div class=modal-body ng-swipe-left=Lightbox.nextImage() ng-swipe-right=Lightbox.prevImage()>" +
        //"<div class=lightbox-nav>" +
        //"<button class=close aria-hidden=true ng-click=$dismiss()>×</button>" +
        //"<div class=btn-group>" +
        //"<a class=\"btn btn-xs btn-default\" ng-click=Lightbox.prevImage()>‹ Previous</a> " +
        //"<a ng-href={{Lightbox.imageUrl}} target=_blank class=\"btn btn-xs btn-default\" title=\"Open in new tab\">Open image in new tab</a> " +
        //"<a class=\"btn btn-xs btn-default\" ng-click=Lightbox.nextImage()>Next ›</a>" +
        //"</div>" +
        //"</div>" +
        // Image container
        "<div class=lightbox-image-container>" +
        "<a class=\"lightbox-close-popup\" ng-click=\"$dismiss()\"><i class=\"fa fa-close\"></i></a>" +
        "<div class=lightbox-image-caption><span>{{Lightbox.imageCaption}}</span></div>" +
        "<a class=\"lightbox-nav-left\" ng-click=\"Lightbox.prevImage()\"><i class=\"glyphicon glyphicon-chevron-left\"></i></a>" +
        "<img lightbox-src={{Lightbox.imageUrl}} class=\"effect\" alt=\"\">" +
        "<a class=\"lightbox-nav-right\" ng-click=\"Lightbox.nextImage()\"><i class=\"glyphicon glyphicon-chevron-right\"></i></a>" +
        "</div></div>"
    );
}]);
/**
 * @class     ImageLoader
 * @classdesc Service for loading an image.
 * @memberOf  bootstrapLightbox
 */
angular.module('bootstrapLightbox').service('ImageLoader', ['$q',
    function ($q) {
        /**
         * Load the image at the given URL.
         * @param    {String} url
         * @return   {Promise} A $q promise that resolves when the image has loaded
         *   successfully.
         * @type     {Function}
         * @name     load
         * @memberOf bootstrapLightbox.ImageLoader
         */
        this.load = function (url) {
            var deferred = $q.defer();

            var image = new Image();

            // when the image has loaded
            image.onload = function () {
                // check image properties for possible errors
                if ((typeof this.complete === 'boolean' && this.complete === false) ||
                    (typeof this.naturalWidth === 'number' && this.naturalWidth === 0)) {
                    deferred.reject();
                }

                deferred.resolve(image);
            };

            // when the image fails to load
            image.onerror = function () {
                deferred.reject();
            };

            // start loading the image
            image.src = url;

            return deferred.promise;
        };
    }]);
/**
 * @class     Lightbox
 * @classdesc Lightbox service.
 * @memberOf  bootstrapLightbox
 */
angular.module('bootstrapLightbox').provider('Lightbox', function () {
    /**
     * Template URL passed into `$modal.open()`.
     * @type     {String}
     * @name     templateUrl
     * @memberOf bootstrapLightbox.Lightbox
     */
    this.templateUrl = 'lightbox.tpl';

    /**
     * @param    {*} image An element in the array of images.
     * @return   {String} The URL of the given image.
     * @type     {Function}
     * @name     getImageUrl
     * @memberOf bootstrapLightbox.Lightbox
     */
    this.getImageUrl = function (image) {
        return image.url;
    };

    /**
     * @param    {*} image An element in the array of images.
     * @return   {String} The caption of the given image.
     * @type     {Function}
     * @name     getImageCaption
     * @memberOf bootstrapLightbox.Lightbox
     */
    this.getImageCaption = function (image) {
        return image.caption;
    };

    /**
     * Calculate the max and min limits to the width and height of the displayed
     *   image (all are optional). The max dimensions override the min
     *   dimensions if they conflict.
     * @param    {Object} dimensions Contains the properties `windowWidth`,
     *   `windowHeight`, `imageWidth`, and `imageHeight`.
     * @return   {Object} May optionally contain the properties `minWidth`,
     *   `minHeight`, `maxWidth`, and `maxHeight`.
     * @type     {Function}
     * @name     calculateImageDimensionLimits
     * @memberOf bootstrapLightbox.Lightbox
     */
    this.calculateImageDimensionLimits = function (dimensions) {
        if (dimensions.windowWidth >= 768) {
            return {
                // 92px = 2 * (30px margin of .modal-dialog
                //             + 1px border of .modal-content
                //             + 15px padding of .modal-body)
                // with the goal of 30px side margins; however, the actual side margins
                // will be slightly less (at 22.5px) due to the vertical scrollbar
                'maxWidth': dimensions.windowWidth - 92,
                // 126px = 92px as above
                //         + 34px outer height of .lightbox-nav
                'maxHeight': dimensions.windowHeight - 126
            };
        } else {
            return {
                // 52px = 2 * (10px margin of .modal-dialog
                //             + 1px border of .modal-content
                //             + 15px padding of .modal-body)
                'maxWidth': dimensions.windowWidth - 52,
                // 86px = 52px as above
                //        + 34px outer height of .lightbox-nav
                'maxHeight': dimensions.windowHeight - 86
            };
        }
    };

    /**
     * Calculate the width and height of the modal. This method gets called
     *   after the width and height of the image, as displayed inside the modal,
     *   are calculated.
     * @param    {Object} dimensions Contains the properties `windowWidth`,
     *   `windowHeight`, `imageDisplayWidth`, and `imageDisplayHeight`.
     * @return   {Object} Must contain the properties `width` and `height`.
     * @type     {Function}
     * @name     calculateModalDimensions
     * @memberOf bootstrapLightbox.Lightbox
     */
    this.calculateModalDimensions = function (dimensions) {
        // 400px = arbitrary min width
        // 32px = 2 * (1px border of .modal-content
        //             + 15px padding of .modal-body)
        var width = Math.max(400, dimensions.imageDisplayWidth + 32);

        // 200px = arbitrary min height
        // 66px = 32px as above
        //        + 34px outer height of .lightbox-nav
        var height = Math.max(200, dimensions.imageDisplayHeight + 66);

        // first case:  the modal width cannot be larger than the window width
        //              20px = arbitrary value larger than the vertical scrollbar
        //                     width in order to avoid having a horizontal scrollbar
        // second case: Bootstrap modals are not centered below 768px
        if (width >= dimensions.windowWidth - 20 || dimensions.windowWidth < 768) {
            width = 'auto';
        }

        // the modal height cannot be larger than the window height
        if (height >= dimensions.windowHeight) {
            height = 'auto';
        }

        return {
            'width': width,
            'height': height
        };
    };

    this.$get = ['$document', '$modal', '$timeout', 'cfpLoadingBar',
        'ImageLoader', function ($document, $modal, $timeout, cfpLoadingBar,
            ImageLoader) {
            var Lightbox = {};

            /**
             * Array of all images to be shown in the lightbox (not `Image` objects).
             * @type     {Array}
             * @name     images
             * @memberOf bootstrapLightbox.Lightbox
             */
            Lightbox.images = [];

            /**
             * The index in the `Lightbox.images` aray of the image that is currently
             *   shown in the lightbox.
             * @type     {Number}
             * @name     index
             * @memberOf bootstrapLightbox.Lightbox
             */
            Lightbox.index = -1;

            // set the configurable properties and methods, the defaults of which are
            // defined above
            Lightbox.templateUrl = this.templateUrl;
            Lightbox.getImageUrl = this.getImageUrl;
            Lightbox.getImageCaption = this.getImageCaption;
            Lightbox.calculateImageDimensionLimits = this.calculateImageDimensionLimits;
            Lightbox.calculateModalDimensions = this.calculateModalDimensions;

            /**
             * Whether keyboard navigation is currently enabled for navigating through
             *   images in the lightbox.
             * @type     {Boolean}
             * @name     keyboardNavEnabled
             * @memberOf bootstrapLightbox.Lightbox
             */
            Lightbox.keyboardNavEnabled = false;

            /**
             * The image currently shown in the lightbox.
             * @type     {*}
             * @name     image
             * @memberOf bootstrapLightbox.Lightbox
             */
            Lightbox.image = {};

            /**
             * The UI Bootstrap modal instance. See {@link
             *   http://angular-ui.github.io/bootstrap/#/modal}.
             * @type     {Object}
             * @name     modalInstance
             * @memberOf bootstrapLightbox.Lightbox
             */
            Lightbox.modalInstance = null;

            /**
             * The URL of the current image. This is a property of the service rather
             *   than of `Lightbox.image` because `Lightbox.image` need not be an
             *   object, and besides it would be poor practice to alter the given
             *   objects.
             * @type     {String}
             * @name     imageUrl
             * @memberOf bootstrapLightbox.Lightbox
             */

            /**
             * The optional caption of the current image.
             * @type     {String}
             * @name     imageCaption
             * @memberOf bootstrapLightbox.Lightbox
             */

            /**
             * Open the lightbox modal.
             * @param    {Array}  newImages An array of images. Each image may be of
             *   any type.
             * @param    {Number} newIndex  The index in `newImages` to set as the
             *   current image.
             * @return   {Object} The created UI Bootstrap modal instance.
             * @type     {Function}
             * @name     openModal
             * @memberOf bootstrapLightbox.Lightbox
             */
            Lightbox.openModal = function (newImages, newIndex) {
                Lightbox.images = newImages;
                Lightbox.setImage(newIndex);

                // store the modal instance so we can close it manually if we need to
                Lightbox.modalInstance = $modal.open({
                    'animation': false,
                    'templateUrl': Lightbox.templateUrl,
                    'controller': ['$scope', function ($scope) {
                        // $scope is the modal scope, a child of $rootScope
                        $scope.Lightbox = Lightbox;

                        Lightbox.keyboardNavEnabled = true;
                    }],
                    'windowClass': 'lightbox-modal'
                });

                // modal close handler
                Lightbox.modalInstance.result.then(function () {
                    // prevent the lightbox from flickering from the old image when it gets
                    // opened again
                    Lightbox.images = [];
                    Lightbox.index = 1;
                    Lightbox.image = {};
                    Lightbox.imageUrl = null;
                    Lightbox.imageCaption = null;

                    Lightbox.keyboardNavEnabled = false;

                    // complete any lingering loading bar progress
                    cfpLoadingBar.complete();
                }, function () { });

                return Lightbox.modalInstance;
            };

            /**
             * Close the lightbox modal.
             * @param    {*} result This argument can be useful if the modal promise
             *   gets handler(s) attached to it.
             * @type     {Function}
             * @name     closeModal
             * @memberOf bootstrapLightbox.Lightbox
             */
            Lightbox.closeModal = function (result) {
                return Lightbox.modalInstance.close(result);
            };

            /**
             * This method can be used in all methods which navigate/change the
             *   current image.
             * @param    {Number} newIndex The index in the array of images to set as
             *   the new current image.
             * @type     {Function}
             * @name     setImage
             * @memberOf bootstrapLightbox.Lightbox
             */
            Lightbox.setImage = function (newIndex) {
                if (!(newIndex in Lightbox.images)) {
                    throw 'Invalid image.';
                }

                cfpLoadingBar.start();

                var success = function () {
                    Lightbox.index = newIndex;
                    Lightbox.image = Lightbox.images[Lightbox.index];

                    cfpLoadingBar.complete();
                };

                var imageUrl = Lightbox.getImageUrl(Lightbox.images[newIndex]);

                // load the image before setting it, so everything in the view is updated
                // at the same time; otherwise, the previous image remains while the
                // current image is loading
                ImageLoader.load(imageUrl).then(function () {
                    success();

                    // set the url and caption
                    Lightbox.imageUrl = imageUrl;
                    Lightbox.imageCaption = Lightbox.getImageCaption(Lightbox.image);
                }, function () {
                    success();

                    // blank image
                    Lightbox.imageUrl = '//:0';
                    // use the caption to show the user an error
                    Lightbox.imageCaption = 'Failed to load image';
                });
            };

            /**
             * Navigate to the first image.
             * @type     {Function}
             * @name     firstImage
             * @memberOf bootstrapLightbox.Lightbox
             */
            Lightbox.firstImage = function () {
                Lightbox.setImage(0);
            };

            /**
             * Navigate to the previous image.
             * @type     {Function}
             * @name     prevImage
             * @memberOf bootstrapLightbox.Lightbox
             */
            Lightbox.prevImage = function () {
                Lightbox.setImage((Lightbox.index - 1 + Lightbox.images.length) %
                    Lightbox.images.length);
            };

            /**
             * Navigate to the next image.
             * @type     {Function}
             * @name     nextImage
             * @memberOf bootstrapLightbox.Lightbox
             */
            Lightbox.nextImage = function () {
                Lightbox.setImage((Lightbox.index + 1) % Lightbox.images.length);
            };

            /**
             * Navigate to the last image.
             * @type     {Function}
             * @name     lastImage
             * @memberOf bootstrapLightbox.Lightbox
             */
            Lightbox.lastImage = function () {
                Lightbox.setImage(Lightbox.images.length - 1);
            };

            /**
             * Call this method to set both the array of images and the current image
             *   (based on the current index). A use case is when the image collection
             *   gets changed dynamically in some way while the lightbox is still
             *   open.
             * @param {Array} newImages The new array of images.
             * @type     {Function}
             * @name     setImages
             * @memberOf bootstrapLightbox.Lightbox
             */
            Lightbox.setImages = function (newImages) {
                Lightbox.images = newImages;
                Lightbox.setImage(Lightbox.index);
            };

            // Bind the left and right arrow keys for image navigation. This event
            // handler never gets unbinded. Disable this using the `keyboardNavEnabled`
            // flag. It is automatically disabled when the target is an input and or a
            // textarea. TODO: Move this to a directive.
            $document.bind('keydown', function (event) {
                if (!Lightbox.keyboardNavEnabled) {
                    return;
                }

                // method of Lightbox to call
                var method = null;

                switch (event.which) {
                    case 39: // right arrow key
                        method = 'nextImage';
                        break;
                    case 37: // left arrow key
                        method = 'prevImage';
                        break;
                }

                if (method !== null && ['input', 'textarea'].indexOf(
                    event.target.tagName.toLowerCase()) === -1) {
                    // the view doesn't update without a manual digest
                    $timeout(function () {
                        Lightbox[method]();
                    });

                    event.preventDefault();
                }
            });

            return Lightbox;
        }];
});
/**
 * @class     lightboxSrc
 * @classdesc This attribute directive is used in an `<img>` element in the
 *   modal template in place of `src`. It handles resizing both the `<img>`
 *   element and its relevant parent elements within the modal.
 * @memberOf  bootstrapLightbox
 */
angular.module('bootstrapLightbox').directive('lightboxSrc', ['$window',
    'ImageLoader', 'Lightbox', function ($window, ImageLoader, Lightbox) {
        // Calculate the dimensions to display the image. The max dimensions override
        // the min dimensions if they conflict.
        var calculateImageDisplayDimensions = function (dimensions) {
            var w = dimensions.width;
            var h = dimensions.height;
            var minW = dimensions.minWidth;
            var minH = dimensions.minHeight;
            var maxW = dimensions.maxWidth;
            var maxH = dimensions.maxHeight;

            var displayW = w;
            var displayH = h;

            // resize the image if it is too small
            if (w < minW && h < minH) {
                // the image is both too thin and short, so compare the aspect ratios to
                // determine whether to min the width or height
                if (w / h > maxW / maxH) {
                    displayH = minH;
                    displayW = Math.round(w * minH / h);
                } else {
                    displayW = minW;
                    displayH = Math.round(h * minW / w);
                }
            } else if (w < minW) {
                // the image is too thin
                displayW = minW;
                displayH = Math.round(h * minW / w);
            } else if (h < minH) {
                // the image is too short
                displayH = minH;
                displayW = Math.round(w * minH / h);
            }

            // resize the image if it is too large
            if (w > maxW && h > maxH) {
                // the image is both too tall and wide, so compare the aspect ratios
                // to determine whether to max the width or height
                if (w / h > maxW / maxH) {
                    displayW = maxW;
                    displayH = Math.round(h * maxW / w);
                } else {
                    displayH = maxH;
                    displayW = Math.round(w * maxH / h);
                }
            } else if (w > maxW) {
                // the image is too wide
                displayW = maxW;
                displayH = Math.round(h * maxW / w);
            } else if (h > maxH) {
                // the image is too tall
                displayH = maxH;
                displayW = Math.round(w * maxH / h);
            }

            return {
                'width': displayW || 0,
                'height': displayH || 0 // NaN is possible when dimensions.width is 0
            };
        };

        // the dimensions of the image
        var imageWidth = 0;
        var imageHeight = 0;

        return {
            'link': function (scope, element, attrs) {
                // resize the img element and the containing modal
                var resize = function () {
                    // get the window dimensions
                    var windowWidth = $window.innerWidth;
                    var windowHeight = $window.innerHeight;

                    // calculate the max/min dimensions for the image
                    var imageDimensionLimits = Lightbox.calculateImageDimensionLimits({
                        'windowWidth': windowWidth,
                        'windowHeight': windowHeight,
                        'imageWidth': imageWidth,
                        'imageHeight': imageHeight
                    });

                    // calculate the dimensions to display the image
                    var imageDisplayDimensions = calculateImageDisplayDimensions(
                        angular.extend({
                            'width': imageWidth,
                            'height': imageHeight,
                            'minWidth': 1,
                            'minHeight': 1,
                            'maxWidth': 3000,
                            'maxHeight': 3000,
                        }, imageDimensionLimits)
                    );

                    // calculate the dimensions of the modal container
                    var modalDimensions = Lightbox.calculateModalDimensions({
                        'windowWidth': windowWidth,
                        'windowHeight': windowHeight,
                        'imageDisplayWidth': imageDisplayDimensions.width,
                        'imageDisplayHeight': imageDisplayDimensions.height
                    });

                    // resize the image
                    element.css({
                        'width': imageDisplayDimensions.width + 'px',
                        'height': imageDisplayDimensions.height + 'px'
                    });

                    // setting the height on .modal-dialog does not expand the div with the
                    // background, which is .modal-content
                    angular.element(
                        document.querySelector('.lightbox-modal .modal-dialog')
                    ).css({
                        'width': modalDimensions.width + 'px'
                    });

                    // .modal-content has no width specified; if we set the width on
                    // .modal-content and not on .modal-dialog, .modal-dialog retains its
                    // default width of 600px and that places .modal-content off center
                    angular.element(
                        document.querySelector('.lightbox-modal .modal-content')
                    ).css({
                        'height': modalDimensions.height + 'px'
                    });
                };

                // load the new image whenever the attr changes
                scope.$watch(function () {
                    return attrs.lightboxSrc;
                }, function (src) {
                    // blank the image before resizing the element; see
                    // http://stackoverflow.com/questions/5775469
                    element[0].src = '//:0';

                    ImageLoader.load(src).then(function (image) {
                        // these variables must be set before resize(), as they are used in it
                        imageWidth = image.naturalWidth;
                        imageHeight = image.naturalHeight;

                        // resize the img element and the containing modal
                        resize();

                        // show the image
                        element[0].src = src;
                    });
                });

                // resize the image and modal whenever the window gets resized
                angular.element($window).on('resize', resize);
            }
        };
    }]);
//fgnass.github.com/spin.js#v1.3

/**
 * Copyright (c) 2011-2013 Felix Gnass
 * Licensed under the MIT license
 */
(function(root, factory) {

  /* CommonJS */
  if (typeof exports == 'object')  module.exports = factory()

  /* AMD module */
  else if (typeof define == 'function' && define.amd) define(factory)

  /* Browser global */
  else root.Spinner = factory()
}
(this, function() {
  "use strict";

  var prefixes = ['webkit', 'Moz', 'ms', 'O'] /* Vendor prefixes */
    , animations = {} /* Animation rules keyed by their name */
    , useCssAnimations /* Whether to use CSS animations or setTimeout */

  /**
   * Utility function to create elements. If no tag name is given,
   * a DIV is created. Optionally properties can be passed.
   */
  function createEl(tag, prop) {
    var el = document.createElement(tag || 'div')
      , n

    for(n in prop) el[n] = prop[n]
    return el
  }

  /**
   * Appends children and returns the parent.
   */
  function ins(parent /* child1, child2, ...*/) {
    for (var i=1, n=arguments.length; i<n; i++)
      parent.appendChild(arguments[i])

    return parent
  }

  /**
   * Insert a new stylesheet to hold the @keyframe or VML rules.
   */
  var sheet = (function() {
    var el = createEl('style', {type : 'text/css'})
    ins(document.getElementsByTagName('head')[0], el)
    return el.sheet || el.styleSheet
  }())

  /**
   * Creates an opacity keyframe animation rule and returns its name.
   * Since most mobile Webkits have timing issues with animation-delay,
   * we create separate rules for each line/segment.
   */
  function addAnimation(alpha, trail, i, lines) {
    var name = ['opacity', trail, ~~(alpha*100), i, lines].join('-')
      , start = 0.01 + i/lines * 100
      , z = Math.max(1 - (1-alpha) / trail * (100-start), alpha)
      , prefix = useCssAnimations.substring(0, useCssAnimations.indexOf('Animation')).toLowerCase()
      , pre = prefix && '-' + prefix + '-' || ''

    if (!animations[name]) {
      sheet.insertRule(
        '@' + pre + 'keyframes ' + name + '{' +
        '0%{opacity:' + z + '}' +
        start + '%{opacity:' + alpha + '}' +
        (start+0.01) + '%{opacity:1}' +
        (start+trail) % 100 + '%{opacity:' + alpha + '}' +
        '100%{opacity:' + z + '}' +
        '}', sheet.cssRules.length)

      animations[name] = 1
    }

    return name
  }

  /**
   * Tries various vendor prefixes and returns the first supported property.
   */
  function vendor(el, prop) {
    var s = el.style
      , pp
      , i

    if(s[prop] !== undefined) return prop
    prop = prop.charAt(0).toUpperCase() + prop.slice(1)
    for(i=0; i<prefixes.length; i++) {
      pp = prefixes[i]+prop
      if(s[pp] !== undefined) return pp
    }
  }

  /**
   * Sets multiple style properties at once.
   */
  function css(el, prop) {
    for (var n in prop)
      el.style[vendor(el, n)||n] = prop[n]

    return el
  }

  /**
   * Fills in default values.
   */
  function merge(obj) {
    for (var i=1; i < arguments.length; i++) {
      var def = arguments[i]
      for (var n in def)
        if (obj[n] === undefined) obj[n] = def[n]
    }
    return obj
  }

  /**
   * Returns the absolute page-offset of the given element.
   */
  function pos(el) {
    var o = { x:el.offsetLeft, y:el.offsetTop }
    while((el = el.offsetParent))
      o.x+=el.offsetLeft, o.y+=el.offsetTop

    return o
  }

  // Built-in defaults

  var defaults = {
    lines: 12,            // The number of lines to draw
    length: 7,            // The length of each line
    width: 5,             // The line thickness
    radius: 10,           // The radius of the inner circle
    rotate: 0,            // Rotation offset
    corners: 1,           // Roundness (0..1)
    color: '#000',        // #rgb or #rrggbb
    direction: 1,         // 1: clockwise, -1: counterclockwise
    speed: 1,             // Rounds per second
    trail: 100,           // Afterglow percentage
    opacity: 1/4,         // Opacity of the lines
    fps: 20,              // Frames per second when using setTimeout()
    zIndex: 2e9,          // Use a high z-index by default
    className: 'spinner', // CSS class to assign to the element
    top: 'auto',          // center vertically
    left: 'auto',         // center horizontally
    position: 'relative'  // element position
  }

  /** The constructor */
  function Spinner(o) {
    if (typeof this == 'undefined') return new Spinner(o)
    this.opts = merge(o || {}, Spinner.defaults, defaults)
  }

  // Global defaults that override the built-ins:
  Spinner.defaults = {}

  merge(Spinner.prototype, {

    /**
     * Adds the spinner to the given target element. If this instance is already
     * spinning, it is automatically removed from its previous target b calling
     * stop() internally.
     */
    spin: function(target) {
      this.stop()

      var self = this
        , o = self.opts
        , el = self.el = css(createEl(0, {className: o.className}), {position: o.position, width: 0, zIndex: o.zIndex})
        , mid = o.radius+o.length+o.width
        , ep // element position
        , tp // target position

      if (target) {
        target.insertBefore(el, target.firstChild||null)
        tp = pos(target)
        ep = pos(el)
        css(el, {
          left: (o.left == 'auto' ? tp.x-ep.x + (target.offsetWidth >> 1) : parseInt(o.left, 10) + mid) + 'px',
          top: (o.top == 'auto' ? tp.y-ep.y + (target.offsetHeight >> 1) : parseInt(o.top, 10) + mid)  + 'px'
        })
      }

      el.setAttribute('role', 'progressbar')
      self.lines(el, self.opts)

      if (!useCssAnimations) {
        // No CSS animation support, use setTimeout() instead
        var i = 0
          , start = (o.lines - 1) * (1 - o.direction) / 2
          , alpha
          , fps = o.fps
          , f = fps/o.speed
          , ostep = (1-o.opacity) / (f*o.trail / 100)
          , astep = f/o.lines

        ;(function anim() {
          i++;
          for (var j = 0; j < o.lines; j++) {
            alpha = Math.max(1 - (i + (o.lines - j) * astep) % f * ostep, o.opacity)

            self.opacity(el, j * o.direction + start, alpha, o)
          }
          self.timeout = self.el && setTimeout(anim, ~~(1000/fps))
        })()
      }
      return self
    },

    /**
     * Stops and removes the Spinner.
     */
    stop: function() {
      var el = this.el
      if (el) {
        clearTimeout(this.timeout)
        if (el.parentNode) el.parentNode.removeChild(el)
        this.el = undefined
      }
      return this
    },

    /**
     * Internal method that draws the individual lines. Will be overwritten
     * in VML fallback mode below.
     */
    lines: function(el, o) {
      var i = 0
        , start = (o.lines - 1) * (1 - o.direction) / 2
        , seg

      function fill(color, shadow) {
        return css(createEl(), {
          position: 'absolute',
          width: (o.length+o.width) + 'px',
          height: o.width + 'px',
          background: color,
          boxShadow: shadow,
          transformOrigin: 'left',
          transform: 'rotate(' + ~~(360/o.lines*i+o.rotate) + 'deg) translate(' + o.radius+'px' +',0)',
          borderRadius: (o.corners * o.width>>1) + 'px'
        })
      }

      for (; i < o.lines; i++) {
        seg = css(createEl(), {
          position: 'absolute',
          top: 1+~(o.width/2) + 'px',
          transform: o.hwaccel ? 'translate3d(0,0,0)' : '',
          opacity: o.opacity,
          animation: useCssAnimations && addAnimation(o.opacity, o.trail, start + i * o.direction, o.lines) + ' ' + 1/o.speed + 's linear infinite'
        })

        if (o.shadow) ins(seg, css(fill('#000', '0 0 4px ' + '#000'), {top: 2+'px'}))

        ins(el, ins(seg, fill(o.color, '0 0 1px rgba(0,0,0,.1)')))
      }
      return el
    },

    /**
     * Internal method that adjusts the opacity of a single line.
     * Will be overwritten in VML fallback mode below.
     */
    opacity: function(el, i, val) {
      if (i < el.childNodes.length) el.childNodes[i].style.opacity = val
    }

  })


  function initVML() {

    /* Utility function to create a VML tag */
    function vml(tag, attr) {
      return createEl('<' + tag + ' xmlns="urn:schemas-microsoft.com:vml" class="spin-vml">', attr)
    }

    // No CSS transforms but VML support, add a CSS rule for VML elements:
    sheet.addRule('.spin-vml', 'behavior:url(#default#VML)')

    Spinner.prototype.lines = function(el, o) {
      var r = o.length+o.width
        , s = 2*r

      function grp() {
        return css(
          vml('group', {
            coordsize: s + ' ' + s,
            coordorigin: -r + ' ' + -r
          }),
          { width: s, height: s }
        )
      }

      var margin = -(o.width+o.length)*2 + 'px'
        , g = css(grp(), {position: 'absolute', top: margin, left: margin})
        , i

      function seg(i, dx, filter) {
        ins(g,
          ins(css(grp(), {rotation: 360 / o.lines * i + 'deg', left: ~~dx}),
            ins(css(vml('roundrect', {arcsize: o.corners}), {
                width: r,
                height: o.width,
                left: o.radius,
                top: -o.width>>1,
                filter: filter
              }),
              vml('fill', {color: o.color, opacity: o.opacity}),
              vml('stroke', {opacity: 0}) // transparent stroke to fix color bleeding upon opacity change
            )
          )
        )
      }

      if (o.shadow)
        for (i = 1; i <= o.lines; i++)
          seg(i, -2, 'progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)')

      for (i = 1; i <= o.lines; i++) seg(i)
      return ins(el, g)
    }

    Spinner.prototype.opacity = function(el, i, val, o) {
      var c = el.firstChild
      o = o.shadow && o.lines || 0
      if (c && i+o < c.childNodes.length) {
        c = c.childNodes[i+o]; c = c && c.firstChild; c = c && c.firstChild
        if (c) c.opacity = val
      }
    }
  }

  var probe = css(createEl('group'), {behavior: 'url(#default#VML)'})

  if (!vendor(probe, 'transform') && probe.adj) initVML()
  else useCssAnimations = vendor(probe, 'animation')

  return Spinner

}));
(function(root, factory) {
    if (typeof exports === "object") {
        module.exports = factory();
    } else if (typeof define === "function" && define.amd) {
        define([ "spin" ], factory);
    } else {
        root.Ladda = factory(root.Spinner);
    }
})(this, function(Spinner) {
    "use strict";
    var ALL_INSTANCES = [];
    function create(button) {
        if (typeof button === "undefined") {
            console.warn("Ladda button target must be defined.");
            return;
        }
        if (!button.querySelector(".ladda-label")) {
            button.innerHTML = '<span class="ladda-label">' + button.innerHTML + "</span>";
        }
        var spinner = createSpinner(button);
        var spinnerWrapper = document.createElement("span");
        spinnerWrapper.className = "ladda-spinner";
        button.appendChild(spinnerWrapper);
        var timer;
        var instance = {
            start: function() {
                button.setAttribute("disabled", "");
                button.setAttribute("data-loading", "");
                clearTimeout(timer);
                spinner.spin(spinnerWrapper);
                this.setProgress(0);
                return this;
            },
            startAfter: function(delay) {
                clearTimeout(timer);
                timer = setTimeout(function() {
                    instance.start();
                }, delay);
                return this;
            },
            stop: function() {
                button.removeAttribute("disabled");
                button.removeAttribute("data-loading");
                clearTimeout(timer);
                timer = setTimeout(function() {
                    spinner.stop();
                }, 1e3);
                return this;
            },
            toggle: function() {
                if (this.isLoading()) {
                    this.stop();
                } else {
                    this.start();
                }
                return this;
            },
            setProgress: function(progress) {
                progress = Math.max(Math.min(progress, 1), 0);
                var progressElement = button.querySelector(".ladda-progress");
                if (progress === 0 && progressElement && progressElement.parentNode) {
                    progressElement.parentNode.removeChild(progressElement);
                } else {
                    if (!progressElement) {
                        progressElement = document.createElement("div");
                        progressElement.className = "ladda-progress";
                        button.appendChild(progressElement);
                    }
                    progressElement.style.width = (progress || 0) * button.offsetWidth + "px";
                }
            },
            enable: function() {
                this.stop();
                return this;
            },
            disable: function() {
                this.stop();
                button.setAttribute("disabled", "");
                return this;
            },
            isLoading: function() {
                return button.hasAttribute("data-loading");
            }
        };
        ALL_INSTANCES.push(instance);
        return instance;
    }
    function bind(target, options) {
        options = options || {};
        var targets = [];
        if (typeof target === "string") {
            targets = toArray(document.querySelectorAll(target));
        } else if (typeof target === "object" && typeof target.nodeName === "string") {
            targets = [ target ];
        }
        for (var i = 0, len = targets.length; i < len; i++) {
            (function() {
                var element = targets[i];
                if (typeof element.addEventListener === "function") {
                    var instance = create(element);
                    var timeout = -1;
                    element.addEventListener("click", function() {
                        instance.startAfter(1);
                        if (typeof options.timeout === "number") {
                            clearTimeout(timeout);
                            timeout = setTimeout(instance.stop, options.timeout);
                        }
                        if (typeof options.callback === "function") {
                            options.callback.apply(null, [ instance ]);
                        }
                    }, false);
                }
            })();
        }
    }
    function stopAll() {
        for (var i = 0, len = ALL_INSTANCES.length; i < len; i++) {
            ALL_INSTANCES[i].stop();
        }
    }
    function createSpinner(button) {
        var height = button.offsetHeight, spinnerColor;
        if (height > 32) {
            height *= .8;
        }
        if (button.hasAttribute("data-spinner-size")) {
            height = parseInt(button.getAttribute("data-spinner-size"), 10);
        }
        if (button.hasAttribute("data-spinner-color")) {
            spinnerColor = button.getAttribute("data-spinner-color");
        }
        var lines = 12, radius = height * .2, length = radius * .6, width = radius < 7 ? 2 : 3;
        return new Spinner({
            color: spinnerColor || "#fff",
            lines: lines,
            radius: radius,
            length: length,
            width: width,
            zIndex: "auto",
            top: "auto",
            left: "auto",
            className: ""
        });
    }
    function toArray(nodes) {
        var a = [];
        for (var i = 0; i < nodes.length; i++) {
            a.push(nodes[i]);
        }
        return a;
    }
    return {
        bind: bind,
        create: create,
        stopAll: stopAll
    };
});
/**!
 * AngularJS Ladda directive
 * @author Chungsub Kim <subicura@subicura.com>
 */

/* global Ladda */
(function () {
    'use strict';

    angular.module('angular-ladda', [])
      .provider('ladda', function () {
          var opts = {
              'style': 'expand-left'
          };
          return {
              setOption: function (newOpts) {
                  angular.extend(opts, newOpts);
              },
              $get: function () {
                  return opts;
              }
          };
      })
      .directive('ladda', ['ladda', function (laddaOption) {
          return {
              restrict: 'A',
              priority: -1,
              link: function (scope, element, attrs) {
                  element.addClass('ladda-button');
                  if (angular.isUndefined(element.attr('data-style'))) {
                      element.attr('data-style', laddaOption.style || 'zoom-in');
                  }

                  // ladda breaks childNode's event property.
                  // because ladda use innerHTML instead of append node
                  if (!element[0].querySelector('.ladda-label')) {
                      var labelWrapper = document.createElement('span');
                      labelWrapper.className = 'ladda-label';
                      angular.element(labelWrapper).append(element.contents());
                      element.append(labelWrapper);
                  }

                  // create ladda button
                  var ladda = Ladda.create(element[0]);

                  // add watch!
                  scope.$watch(attrs.ladda, function (loading) {
                      if (loading || angular.isNumber(loading)) {
                          if (!ladda.isLoading()) {
                              ladda.start();
                          }
                          if (angular.isNumber(loading)) {
                              ladda.setProgress(loading);
                          }
                      } else {
                          ladda.stop();
                          // When the button also have the ng-disabled directive it needs to be 
                          // re-evaluated since the disabled attribute is removed by the 'stop' method.
                          if (attrs.ngDisabled) {
                              element.attr('disabled', scope.$eval(attrs.ngDisabled));
                          }
                      }
                  });
              }
          };
      }]);
})();
/**
 * @license Angular UI Tree v2.1.5
 * (c) 2010-2014. https://github.com/JimLiu/angular-ui-tree
 * License: MIT
 */
(function () {
  'use strict';

  angular.module('ui.tree', [])
    .constant('treeConfig', {
      treeClass: 'angular-ui-tree',
      emptyTreeClass: 'angular-ui-tree-empty',
      hiddenClass: 'angular-ui-tree-hidden',
      nodesClass: 'angular-ui-tree-nodes',
      nodeClass: 'angular-ui-tree-node',
      handleClass: 'angular-ui-tree-handle',
      placeHolderClass: 'angular-ui-tree-placeholder',
      dragClass: 'angular-ui-tree-drag',
      dragThreshold: 3,
      levelThreshold: 30
    });

})();

(function () {
  'use strict';

  angular.module('ui.tree')

   /**
    * @ngdoc service
    * @name ui.tree.service:$helper
    * @requires ng.$document
    * @requires ng.$window
    *
    * @description
    * angular-ui-tree.
    */
    .factory('$uiTreeHelper', ['$document', '$window',
      function ($document, $window) {
        return {

          /**
           * A hashtable used to storage data of nodes
           * @type {Object}
           */
          nodesData: {
          },

          setNodeAttribute: function(scope, attrName, val) {
            if (!scope.$modelValue) {
              return null;
            }
            var data = this.nodesData[scope.$modelValue.$$hashKey];
            if (!data) {
              data = {};
              this.nodesData[scope.$modelValue.$$hashKey] = data;
            }
            data[attrName] = val;
          },

          getNodeAttribute: function(scope, attrName) {
            if (!scope.$modelValue) {
              return null;
            }
            var data = this.nodesData[scope.$modelValue.$$hashKey];
            if (data) {
              return data[attrName];
            }
            return null;
          },

          /**
           * @ngdoc method
           * @methodOf ui.tree.service:$nodrag
           * @param  {Object} targetElm angular element
           * @return {Bool} check if the node can be dragged.
           */
          nodrag: function (targetElm) {
            return (typeof targetElm.attr('data-nodrag')) != "undefined";
          },

          /**
           * get the event object for touchs
           * @param  {[type]} e [description]
           * @return {[type]}   [description]
           */
          eventObj: function(e) {
            var obj = e;
            if (e.targetTouches !== undefined) {
              obj = e.targetTouches.item(0);
            } else if (e.originalEvent !== undefined && e.originalEvent.targetTouches !== undefined) {
              obj = e.originalEvent.targetTouches.item(0);
            }
            return obj;
          },

          dragInfo: function(node) {
            return {
              source: node,
              sourceInfo: {
                nodeScope: node,
                index: node.index(),
                nodesScope: node.$parentNodesScope
              },
              index: node.index(),
              siblings: node.siblings().slice(0),
              parent: node.$parentNodesScope,

              moveTo: function(parent, siblings, index) { // Move the node to a new position
                this.parent = parent;
                this.siblings = siblings.slice(0);
                var i = this.siblings.indexOf(this.source); // If source node is in the target nodes
                if (i > -1) {
                  this.siblings.splice(i, 1);
                  if (this.source.index() < index) {
                    index--;
                  }
                }
                this.siblings.splice(index, 0, this.source);
                this.index = index;
              },

              parentNode: function() {
                return this.parent.$nodeScope;
              },

              prev: function() {
                if (this.index > 0) {
                  return this.siblings[this.index - 1];
                }
                return null;
              },

              next: function() {
                if (this.index < this.siblings.length - 1) {
                  return this.siblings[this.index + 1];
                }
                return null;
              },

              isDirty: function() {
                return this.source.$parentNodesScope != this.parent ||
                        this.source.index() != this.index;
              },

              eventArgs: function(elements, pos) {
                return {
                  source: this.sourceInfo,
                  dest: {
                    index: this.index,
                    nodesScope: this.parent
                  },
                  elements: elements,
                  pos: pos
                };
              },

              apply: function() {
                var nodeData = this.source.$modelValue;
                this.source.remove();
                this.parent.insertNode(this.index, nodeData);
              }
            };
          },

          /**
          * @ngdoc method
          * @name hippo.theme#height
          * @methodOf ui.tree.service:$helper
          *
          * @description
          * Get the height of an element.
          *
          * @param {Object} element Angular element.
          * @returns {String} Height
          */
          height: function (element) {
            return element.prop('scrollHeight');
          },

          /**
          * @ngdoc method
          * @name hippo.theme#width
          * @methodOf ui.tree.service:$helper
          *
          * @description
          * Get the width of an element.
          *
          * @param {Object} element Angular element.
          * @returns {String} Width
          */
          width: function (element) {
            return element.prop('scrollWidth');
          },

          /**
          * @ngdoc method
          * @name hippo.theme#offset
          * @methodOf ui.nestedSortable.service:$helper
          *
          * @description
          * Get the offset values of an element.
          *
          * @param {Object} element Angular element.
          * @returns {Object} Object with properties width, height, top and left
          */
          offset: function (element) {
            var boundingClientRect = element[0].getBoundingClientRect();

            return {
                width: element.prop('offsetWidth'),
                height: element.prop('offsetHeight'),
                top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop || $document[0].documentElement.scrollTop),
                left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft  || $document[0].documentElement.scrollLeft)
              };
          },

          /**
          * @ngdoc method
          * @name hippo.theme#positionStarted
          * @methodOf ui.tree.service:$helper
          *
          * @description
          * Get the start position of the target element according to the provided event properties.
          *
          * @param {Object} e Event
          * @param {Object} target Target element
          * @returns {Object} Object with properties offsetX, offsetY, startX, startY, nowX and dirX.
          */
          positionStarted: function (e, target) {
            var pos = {};
            pos.offsetX = e.pageX - this.offset(target).left;
            pos.offsetY = e.pageY - this.offset(target).top;
            pos.startX = pos.lastX = e.pageX;
            pos.startY = pos.lastY = e.pageY;
            pos.nowX = pos.nowY = pos.distX = pos.distY = pos.dirAx = 0;
            pos.dirX = pos.dirY = pos.lastDirX = pos.lastDirY = pos.distAxX = pos.distAxY = 0;
            return pos;
          },

          positionMoved: function (e, pos, firstMoving) {
            // mouse position last events
            pos.lastX = pos.nowX;
            pos.lastY = pos.nowY;

            // mouse position this events
            pos.nowX  = e.pageX;
            pos.nowY  = e.pageY;

            // distance mouse moved between events
            pos.distX = pos.nowX - pos.lastX;
            pos.distY = pos.nowY - pos.lastY;

            // direction mouse was moving
            pos.lastDirX = pos.dirX;
            pos.lastDirY = pos.dirY;

            // direction mouse is now moving (on both axis)
            pos.dirX = pos.distX === 0 ? 0 : pos.distX > 0 ? 1 : -1;
            pos.dirY = pos.distY === 0 ? 0 : pos.distY > 0 ? 1 : -1;

            // axis mouse is now moving on
            var newAx   = Math.abs(pos.distX) > Math.abs(pos.distY) ? 1 : 0;

            // do nothing on first move
            if (firstMoving) {
              pos.dirAx  = newAx;
              pos.moving = true;
              return;
            }

            // calc distance moved on this axis (and direction)
            if (pos.dirAx !== newAx) {
              pos.distAxX = 0;
              pos.distAxY = 0;
            } else {
              pos.distAxX += Math.abs(pos.distX);
              if (pos.dirX !== 0 && pos.dirX !== pos.lastDirX) {
                pos.distAxX = 0;
              }

              pos.distAxY += Math.abs(pos.distY);
              if (pos.dirY !== 0 && pos.dirY !== pos.lastDirY) {
                pos.distAxY = 0;
              }
            }

            pos.dirAx = newAx;
          }
        };
      }
    ]);

})();
(function () {
  'use strict';

  angular.module('ui.tree')

    .controller('TreeController', ['$scope', '$element', '$attrs', 'treeConfig',
      function ($scope, $element, $attrs, treeConfig) {
        this.scope = $scope;

        $scope.$element = $element;
        $scope.$nodesScope = null; // root nodes
        $scope.$type = 'uiTree';
        $scope.$emptyElm = null;
        $scope.$callbacks = null;

        $scope.dragEnabled = true;
        $scope.emptyPlaceHolderEnabled = true;
        $scope.maxDepth = 0;
        $scope.dragDelay = 0;

        // Check if it's a empty tree
        $scope.isEmpty = function() {
          return ($scope.$nodesScope && $scope.$nodesScope.$modelValue
            && $scope.$nodesScope.$modelValue.length === 0);
        };

        // add placeholder to empty tree
        $scope.place = function(placeElm) {
          $scope.$nodesScope.$element.append(placeElm);
          $scope.$emptyElm.remove();
        };

        $scope.resetEmptyElement = function() {
          if ($scope.$nodesScope.$modelValue.length === 0 &&
            $scope.emptyPlaceHolderEnabled) {
            $element.append($scope.$emptyElm);
          } else {
            $scope.$emptyElm.remove();
          }
        };

        var collapseOrExpand = function(scope, collapsed) {
          var nodes = scope.childNodes();
          for (var i = 0; i < nodes.length; i++) {
            collapsed ? nodes[i].collapse() : nodes[i].expand();
            var subScope = nodes[i].$childNodesScope;
            if (subScope) {
              collapseOrExpand(subScope, collapsed);
            }
          }
        };

        $scope.collapseAll = function() {
          collapseOrExpand($scope.$nodesScope, true);
        };

        $scope.expandAll = function() {
          collapseOrExpand($scope.$nodesScope, false);
        };

      }
    ]);
})();

(function () {
  'use strict';

  angular.module('ui.tree')

    .controller('TreeNodesController', ['$scope', '$element', 'treeConfig',
      function ($scope, $element, treeConfig) {
        this.scope = $scope;

        $scope.$element = $element;
        $scope.$modelValue = null;
        $scope.$nodeScope = null; // the scope of node which the nodes belongs to
        $scope.$treeScope = null;
        $scope.$type = 'uiTreeNodes';
        $scope.$nodesMap = {};

        $scope.nodrop = false;
        $scope.maxDepth = 0;

        $scope.initSubNode = function(subNode) {
          if(!subNode.$modelValue) {
            return null;
          }
          $scope.$nodesMap[subNode.$modelValue.$$hashKey] = subNode;
        };

        $scope.destroySubNode = function(subNode) {
          if(!subNode.$modelValue) {
            return null;
          }
          $scope.$nodesMap[subNode.$modelValue.$$hashKey] = null;
        };

        $scope.accept = function(sourceNode, destIndex) {
          return $scope.$treeScope.$callbacks.accept(sourceNode, $scope, destIndex);
        };

        $scope.beforeDrag = function(sourceNode) {
          return $scope.$treeScope.$callbacks.beforeDrag(sourceNode);
        };

        $scope.isParent = function(node) {
          return node.$parentNodesScope == $scope;
        };

        $scope.hasChild = function() {
          return $scope.$modelValue.length > 0;
        };

        $scope.safeApply = function(fn) {
          var phase = this.$root.$$phase;
          if(phase == '$apply' || phase == '$digest') {
            if(fn && (typeof(fn) === 'function')) {
              fn();
            }
          } else {
            this.$apply(fn);
          }
        };

        $scope.removeNode = function(node) {
          var index = $scope.$modelValue.indexOf(node.$modelValue);
          if (index > -1) {
            $scope.safeApply(function() {
              $scope.$modelValue.splice(index, 1)[0];
            });
            return node;
          }
          return null;
        };

        $scope.insertNode = function(index, nodeData) {
          $scope.safeApply(function() {
            $scope.$modelValue.splice(index, 0, nodeData);
          });
        };

        $scope.childNodes = function() {
          var nodes = [];
          if ($scope.$modelValue) {
            for (var i = 0; i < $scope.$modelValue.length; i++) {
              nodes.push($scope.$nodesMap[$scope.$modelValue[i].$$hashKey]);
            }
          }
          return nodes;
        };

        $scope.depth = function() {
          if ($scope.$nodeScope) {
            return $scope.$nodeScope.depth();
          }
          return 0; // if it has no $nodeScope, it's root
        };

        // check if depth limit has reached
        $scope.outOfDepth = function(sourceNode) {
          var maxDepth = $scope.maxDepth || $scope.$treeScope.maxDepth;
          if (maxDepth > 0) {
            return $scope.depth() + sourceNode.maxSubDepth() + 1 > maxDepth;
          }
          return false;
        };

      }
    ]);
})();
(function () {
  'use strict';

  angular.module('ui.tree')

    .controller('TreeNodeController', ['$scope', '$element', '$attrs', 'treeConfig',
      function ($scope, $element, $attrs, treeConfig) {
        this.scope = $scope;

        $scope.$element = $element;
        $scope.$modelValue = null; // Model value for node;
        $scope.$parentNodeScope = null; // uiTreeNode Scope of parent node;
        $scope.$childNodesScope = null; // uiTreeNodes Scope of child nodes.
        $scope.$parentNodesScope = null; // uiTreeNodes Scope of parent nodes.
        $scope.$treeScope = null; // uiTree scope
        $scope.$handleScope = null; // it's handle scope
        $scope.$type = 'uiTreeNode';
        $scope.$$apply = false; //

        $scope.collapsed = false;

        $scope.init = function(controllersArr) {
          var treeNodesCtrl = controllersArr[0];
          $scope.$treeScope = controllersArr[1] ? controllersArr[1].scope : null;

          // find the scope of it's parent node
          $scope.$parentNodeScope = treeNodesCtrl.scope.$nodeScope;
          // modelValue for current node
          $scope.$modelValue = treeNodesCtrl.scope.$modelValue[$scope.$index];
          $scope.$parentNodesScope = treeNodesCtrl.scope;
          treeNodesCtrl.scope.initSubNode($scope); // init sub nodes

          $element.on('$destroy', function() {
            treeNodesCtrl.scope.destroySubNode($scope); // destroy sub nodes
          });
        };

        $scope.index = function() {
          return $scope.$parentNodesScope.$modelValue.indexOf($scope.$modelValue);
        };

        $scope.dragEnabled = function() {
          return !($scope.$treeScope && !$scope.$treeScope.dragEnabled);
        };

        $scope.isSibling = function(targetNode) {
          return $scope.$parentNodesScope == targetNode.$parentNodesScope;
        };

        $scope.isChild = function(targetNode) {
          var nodes = $scope.childNodes();
          return nodes && nodes.indexOf(targetNode) > -1;
        };

        $scope.prev = function() {
          var index = $scope.index();
          if (index > 0) {
            return $scope.siblings()[index - 1];
          }
          return null;
        };

        $scope.siblings = function() {
          return $scope.$parentNodesScope.childNodes();
        };

        $scope.childNodesCount = function() {
          return $scope.childNodes() ? $scope.childNodes().length : 0;
        };

        $scope.hasChild = function() {
          return $scope.childNodesCount() > 0;
        };

        $scope.childNodes = function() {
          return $scope.$childNodesScope && $scope.$childNodesScope.$modelValue ?
              $scope.$childNodesScope.childNodes() :
              null;
        };

        $scope.accept = function(sourceNode, destIndex) {
          return $scope.$childNodesScope &&
                  $scope.$childNodesScope.$modelValue &&
                  $scope.$childNodesScope.accept(sourceNode, destIndex);
        };

        $scope.removeNode = function(){
          var node = $scope.remove();
          $scope.$callbacks.removed(node);
          return node;
        };

        $scope.remove = function() {
          return $scope.$parentNodesScope.removeNode($scope);
        };

        $scope.toggle = function() {
          $scope.collapsed = !$scope.collapsed;
        };

        $scope.collapse = function() {
          $scope.collapsed = true;
        };

        $scope.expand = function() {
          $scope.collapsed = false;
        };

        $scope.depth = function() {
          var parentNode = $scope.$parentNodeScope;
          if (parentNode) {
            return parentNode.depth() + 1;
          }
          return 1;
        };

        var subDepth = 0;
        var countSubDepth = function(scope) {
          var count = 0;
          var nodes = scope.childNodes();
          for (var i = 0; i < nodes.length; i++) {
            var childNodes = nodes[i].$childNodesScope;
            if (childNodes) {
              count = 1;
              countSubDepth(childNodes);
            }
          }
          subDepth += count;
        };

        $scope.maxSubDepth = function() {
          subDepth = 0;
          if ($scope.$childNodesScope) {
            countSubDepth($scope.$childNodesScope);
          }
          return subDepth;
        };

      }
    ]);
})();

(function () {
  'use strict';

  angular.module('ui.tree')

    .controller('TreeHandleController', ['$scope', '$element', '$attrs', 'treeConfig',
      function ($scope, $element, $attrs, treeConfig) {
        this.scope = $scope;

        $scope.$element = $element;
        $scope.$nodeScope = null;
        $scope.$type = 'uiTreeHandle';

      }
    ]);
})();

(function () {
  'use strict';

  angular.module('ui.tree')
  .directive('uiTree', [ 'treeConfig', '$window',
    function(treeConfig, $window) {
      return {
        restrict: 'A',
        scope: true,
        controller: 'TreeController',
        link: function(scope, element, attrs) {
          var callbacks = {
            accept: null,
            beforeDrag: null
          };

          var config = {};
          angular.extend(config, treeConfig);
          if (config.treeClass) {
            element.addClass(config.treeClass);
          }

          scope.$emptyElm = angular.element($window.document.createElement('div'));
          if (config.emptyTreeClass) {
            scope.$emptyElm.addClass(config.emptyTreeClass);
          }

          scope.$watch('$nodesScope.$modelValue.length', function() {
            if (scope.$nodesScope.$modelValue) {
              scope.resetEmptyElement();
            }
          }, true);

          scope.$watch(attrs.dragEnabled, function(val) {
            if((typeof val) == "boolean") {
              scope.dragEnabled = val;
            }
          });

          scope.$watch(attrs.emptyPlaceHolderEnabled, function(val) {
            if((typeof val) == "boolean") {
              scope.emptyPlaceHolderEnabled = val;
            }
          });

          scope.$watch(attrs.maxDepth, function(val) {
            if((typeof val) == "number") {
              scope.maxDepth = val;
            }
          });

          scope.$watch(attrs.dragDelay, function(val) {
            if((typeof val) == "number") {
              scope.dragDelay = val;
            }
          });

          // check if the dest node can accept the dragging node
          // by default, we check the 'data-nodrop' attribute in `ui-tree-nodes`
          // and the 'max-depth' attribute in `ui-tree` or `ui-tree-nodes`.
          // the method can be overrided
          callbacks.accept = function(sourceNodeScope, destNodesScope, destIndex) {
            if (destNodesScope.nodrop || destNodesScope.outOfDepth(sourceNodeScope)) {
              return false;
            }
            return true;
          };

          callbacks.beforeDrag = function(sourceNodeScope) {
            return true;
          };

          callbacks.removed = function(node){
          
          };

          callbacks.dropped = function(event) {

          };

          //
          callbacks.dragStart = function(event) {

          };

          callbacks.dragMove = function(event) {

          };

          callbacks.dragStop = function(event) {

          };

          callbacks.beforeDrop = function(event) {

          };

          scope.$watch(attrs.uiTree, function(newVal, oldVal){
            angular.forEach(newVal, function(value, key){
              if (callbacks[key]) {
                if (typeof value === "function") {
                  callbacks[key] = value;
                }
              }
            });

            scope.$callbacks = callbacks;
          }, true);


        }
      };
    }
  ]);
})();

(function () {
  'use strict';

  angular.module('ui.tree')
  .directive('uiTreeNodes', [ 'treeConfig', '$window',
    function(treeConfig) {
      return {
        require: ['ngModel', '?^uiTreeNode', '^uiTree'],
        restrict: 'A',
        scope: true,
        controller: 'TreeNodesController',
        link: function(scope, element, attrs, controllersArr) {

          var config = {};
          angular.extend(config, treeConfig);
          if (config.nodesClass) {
            element.addClass(config.nodesClass);
          }

          var ngModel = controllersArr[0];
          var treeNodeCtrl = controllersArr[1];
          var treeCtrl = controllersArr[2];
          if (treeNodeCtrl) {
            treeNodeCtrl.scope.$childNodesScope = scope;
            scope.$nodeScope = treeNodeCtrl.scope;
          }
          else { // find the root nodes if there is no parent node and have a parent ui-tree
            treeCtrl.scope.$nodesScope = scope;
          }
          scope.$treeScope = treeCtrl.scope;

          if (ngModel) {
            ngModel.$render = function() {
              if (!ngModel.$modelValue || !angular.isArray(ngModel.$modelValue)) {
                scope.$modelValue = [];
              }
              scope.$modelValue = ngModel.$modelValue;
            };
          }

          scope.$watch(attrs.maxDepth, function(val) {
            if((typeof val) == "number") {
              scope.maxDepth = val;
            }
          });

          scope.$watch(function () {
            return attrs.nodrop;
          }, function (newVal) {
            if((typeof newVal) != "undefined") {
              scope.nodrop = true;
            }
          }, true);

          attrs.$observe('horizontal', function(val) {
            scope.horizontal = ((typeof val) != "undefined");
          });

        }
      };
    }
  ]);
})();

(function () {
  'use strict';

  angular.module('ui.tree')

    .directive('uiTreeNode', ['treeConfig', '$uiTreeHelper', '$window', '$document','$timeout',
      function (treeConfig, $uiTreeHelper, $window, $document, $timeout) {
        return {
          require: ['^uiTreeNodes', '^uiTree'],
          restrict: 'A',
          controller: 'TreeNodeController',
          link: function(scope, element, attrs, controllersArr) {
            var config = {};
            angular.extend(config, treeConfig);
            if (config.nodeClass) {
              element.addClass(config.nodeClass);
            }
            scope.init(controllersArr);

            scope.collapsed = !!$uiTreeHelper.getNodeAttribute(scope, 'collapsed');

            scope.$watch(attrs.collapsed, function(val) {
              if((typeof val) == "boolean") {
                scope.collapsed = val;
              }
            });

            scope.$watch('collapsed', function(val) {
              $uiTreeHelper.setNodeAttribute(scope, 'collapsed', val);
              attrs.$set('collapsed', val);
            });

            var hasTouch = 'ontouchstart' in window;
            // todo startPos is unused
            var startPos, firstMoving, dragInfo, pos;
            var placeElm, hiddenPlaceElm, dragElm;
            var treeScope = null;
            var elements; // As a parameter for callbacks
            var dragDelaying = true;
            var dragStarted = false;
            var dragTimer = null;
            var body = document.body,
                html = document.documentElement,
                document_height,
                document_width;

            var dragStart = function(e) {
              if (!hasTouch && (e.button == 2 || e.which == 3)) {
                // disable right click
                return;
              }
              if (e.uiTreeDragging || (e.originalEvent && e.originalEvent.uiTreeDragging)) { // event has already fired in other scope.
                return;
              }

              // the element which is clicked.
              var eventElm = angular.element(e.target);
              var eventScope = eventElm.scope();
              if (!eventScope || !eventScope.$type) {
                return;
              }
              if (eventScope.$type != 'uiTreeNode'
                && eventScope.$type != 'uiTreeHandle') { // Check if it is a node or a handle
                return;
              }
              if (eventScope.$type == 'uiTreeNode'
                && eventScope.$handleScope) { // If the node has a handle, then it should be clicked by the handle
                return;
              }

              var eventElmTagName = eventElm.prop('tagName').toLowerCase();
              if (eventElmTagName == 'input' ||
                eventElmTagName == 'textarea' ||
                eventElmTagName == 'button' ||
                eventElmTagName == 'select') { // if it's a input or button, ignore it
                return;
              }

              // check if it or it's parents has a 'data-nodrag' attribute
              while (eventElm && eventElm[0] && eventElm[0] != element) {
                if ($uiTreeHelper.nodrag(eventElm)) { // if the node mark as `nodrag`, DONOT drag it.
                  return;
                }
                eventElm = eventElm.parent();
              }

              if (!scope.beforeDrag(scope)){
                return;
              }

              e.uiTreeDragging = true; // stop event bubbling
              if (e.originalEvent) {
                e.originalEvent.uiTreeDragging = true;
              }
              e.preventDefault();
              var eventObj = $uiTreeHelper.eventObj(e);

              firstMoving = true;
              dragInfo = $uiTreeHelper.dragInfo(scope);

              var tagName = scope.$element.prop('tagName');
              if (tagName.toLowerCase() === 'tr') {
                placeElm = angular.element($window.document.createElement(tagName));
                var tdElm = angular.element($window.document.createElement('td'))
                              .addClass(config.placeHolderClass);
                placeElm.append(tdElm);
              } else {
                placeElm = angular.element($window.document.createElement(tagName))
                              .addClass(config.placeHolderClass);
              }
              hiddenPlaceElm = angular.element($window.document.createElement(tagName));
              if (config.hiddenClass) {
                hiddenPlaceElm.addClass(config.hiddenClass);
              }
              pos = $uiTreeHelper.positionStarted(eventObj, scope.$element);
              placeElm.css('height', $uiTreeHelper.height(scope.$element) + 'px');
              placeElm.css('width', $uiTreeHelper.width(scope.$element) + 'px');
              dragElm = angular.element($window.document.createElement(scope.$parentNodesScope.$element.prop('tagName')))
                        .addClass(scope.$parentNodesScope.$element.attr('class')).addClass(config.dragClass);
              dragElm.css('width', $uiTreeHelper.width(scope.$element) + 'px');
              dragElm.css('z-index', 9999);

              // Prevents cursor to change rapidly in Opera 12.16 and IE when dragging an element
              var hStyle = (scope.$element[0].querySelector('.angular-ui-tree-handle') || scope.$element[0]).currentStyle;
              if (hStyle) {
                document.body.setAttribute('ui-tree-cursor', $document.find('body').css('cursor') || '');
                $document.find('body').css({'cursor': hStyle.cursor + '!important'});
              }

              scope.$element.after(placeElm);
              scope.$element.after(hiddenPlaceElm);
              dragElm.append(scope.$element);
              $document.find('body').append(dragElm);
              dragElm.css({
                'left' : eventObj.pageX - pos.offsetX + 'px',
                'top'  : eventObj.pageY - pos.offsetY + 'px'
              });
              elements = {
                placeholder: placeElm,
                dragging: dragElm
              };

              angular.element($document).bind('touchend', dragEndEvent);
              angular.element($document).bind('touchcancel', dragEndEvent);
              angular.element($document).bind('touchmove', dragMoveEvent);
              angular.element($document).bind('mouseup', dragEndEvent);
              angular.element($document).bind('mousemove', dragMoveEvent);
              angular.element($document).bind('mouseleave', dragCancelEvent);

              document_height = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight);
              document_width = Math.max(body.scrollWidth, body.offsetWidth, html.clientWidth, html.scrollWidth, html.offsetWidth);
            };

            var dragMove = function(e) {
              if (!dragStarted) {
                if (!dragDelaying) {
                  dragStarted = true;
                  scope.$apply(function() {
                    scope.$callbacks.dragStart(dragInfo.eventArgs(elements, pos));
                  });
                }
                return;
              }

              var eventObj = $uiTreeHelper.eventObj(e);
              var prev, leftElmPos, topElmPos;

              if (dragElm) {
                e.preventDefault();

                if ($window.getSelection) {
                  $window.getSelection().removeAllRanges();
                } else if ($window.document.selection) {
                  $window.document.selection.empty();
                }

                leftElmPos = eventObj.pageX - pos.offsetX;
                topElmPos = eventObj.pageY - pos.offsetY;

                //dragElm can't leave the screen on the left
                if(leftElmPos < 0){
                  leftElmPos = 0;
                }

                //dragElm can't leave the screen on the top
                if(topElmPos < 0){
                  topElmPos = 0;
                }

                //dragElm can't leave the screen on the bottom
                if ((topElmPos + 10) > document_height){
                  topElmPos = document_height - 10;
                }

                //dragElm can't leave the screen on the right
                if((leftElmPos + 10) > document_width) {
                  leftElmPos = document_width - 10;
                }

                dragElm.css({
                  'left': leftElmPos + 'px',
                  'top': topElmPos + 'px'
                });

                var top_scroll = window.pageYOffset || $window.document.documentElement.scrollTop;
                var bottom_scroll = top_scroll + (window.innerHeight || $window.document.clientHeight || $window.document.clientHeight);

                // to scroll down if cursor y-position is greater than the bottom position the vertical scroll
                if (bottom_scroll < eventObj.pageY && bottom_scroll <= document_height) {
                  window.scrollBy(0, 10);
                }

                // to scroll top if cursor y-position is less than the top position the vertical scroll
                if (top_scroll > eventObj.pageY) {
                  window.scrollBy(0, -10);
                }

                $uiTreeHelper.positionMoved(e, pos, firstMoving);
                if (firstMoving) {
                  firstMoving = false;
                  return;
                }

                // move horizontal
                if (pos.dirAx && pos.distAxX >= config.levelThreshold) {
                  pos.distAxX = 0;

                  // increase horizontal level if previous sibling exists and is not collapsed
                  if (pos.distX > 0) {
                    prev = dragInfo.prev();
                    if (prev && !prev.collapsed
                      && prev.accept(scope, prev.childNodesCount())) {
                      prev.$childNodesScope.$element.append(placeElm);
                      dragInfo.moveTo(prev.$childNodesScope, prev.childNodes(), prev.childNodesCount());
                    }
                  }

                  // decrease horizontal level
                  if (pos.distX < 0) {
                    // we can't decrease a level if an item preceeds the current one
                    var next = dragInfo.next();
                    if (!next) {
                      var target = dragInfo.parentNode(); // As a sibling of it's parent node
                      if (target
                        && target.$parentNodesScope.accept(scope, target.index() + 1)) {
                        target.$element.after(placeElm);
                        dragInfo.moveTo(target.$parentNodesScope, target.siblings(), target.index() + 1);
                      }
                    }
                  }
                }

                // check if add it as a child node first
                // todo decrease is unused
                var decrease = ($uiTreeHelper.offset(dragElm).left - $uiTreeHelper.offset(placeElm).left) >= config.threshold;
                var targetX = eventObj.pageX - $window.document.body.scrollLeft;
                var targetY = eventObj.pageY - (window.pageYOffset || $window.document.documentElement.scrollTop);

                // Select the drag target. Because IE does not support CSS 'pointer-events: none', it will always
                // pick the drag element itself as the target. To prevent this, we hide the drag element while
                // selecting the target.
                var displayElm;
                if (angular.isFunction(dragElm.hide)) {
                  dragElm.hide();
                }else{
                  displayElm = dragElm[0].style.display;
                  dragElm[0].style.display = "none";
                }

                // when using elementFromPoint() inside an iframe, you have to call
                // elementFromPoint() twice to make sure IE8 returns the correct value
                $window.document.elementFromPoint(targetX, targetY);

                var targetElm = angular.element($window.document.elementFromPoint(targetX, targetY));
                if (angular.isFunction(dragElm.show)) {
                  dragElm.show();
                }else{
                  dragElm[0].style.display = displayElm;
                }

                // move vertical
                if (!pos.dirAx) {
                  var targetBefore, targetNode;
                  // check it's new position
                  targetNode = targetElm.scope();
                  var isEmpty = false;
                  if (!targetNode) {
                    return;
                  }
                  if (targetNode.$type == 'uiTree' && targetNode.dragEnabled) {
                    isEmpty = targetNode.isEmpty(); // Check if it's empty tree
                  }
                  if (targetNode.$type == 'uiTreeHandle') {
                    targetNode = targetNode.$nodeScope;
                  }
                  if (targetNode.$type != 'uiTreeNode'
                    && !isEmpty) { // Check if it is a uiTreeNode or it's an empty tree
                    return;
                  }

                  // if placeholder move from empty tree, reset it.
                  if (treeScope && placeElm.parent()[0] != treeScope.$element[0]) {
                    treeScope.resetEmptyElement();
                    treeScope = null;
                  }

                  if (isEmpty) { // it's an empty tree
                    treeScope = targetNode;
                    if (targetNode.$nodesScope.accept(scope, 0)) {
                      targetNode.place(placeElm);
                      dragInfo.moveTo(targetNode.$nodesScope, targetNode.$nodesScope.childNodes(), 0);
                    }
                  } else if (targetNode.dragEnabled()){ // drag enabled
                    targetElm = targetNode.$element; // Get the element of ui-tree-node
                    var targetOffset = $uiTreeHelper.offset(targetElm);
                    targetBefore = targetNode.horizontal ? eventObj.pageX < (targetOffset.left + $uiTreeHelper.width(targetElm) / 2)
                                                         : eventObj.pageY < (targetOffset.top + $uiTreeHelper.height(targetElm) / 2);

                    if (targetNode.$parentNodesScope.accept(scope, targetNode.index())) {
                      if (targetBefore) {
                        targetElm[0].parentNode.insertBefore(placeElm[0], targetElm[0]);
                        dragInfo.moveTo(targetNode.$parentNodesScope, targetNode.siblings(), targetNode.index());
                      } else {
                        targetElm.after(placeElm);
                        dragInfo.moveTo(targetNode.$parentNodesScope, targetNode.siblings(), targetNode.index() + 1);
                      }
                    }
                    else if (!targetBefore && targetNode.accept(scope, targetNode.childNodesCount())) { // we have to check if it can add the dragging node as a child
                      targetNode.$childNodesScope.$element.append(placeElm);
                      dragInfo.moveTo(targetNode.$childNodesScope, targetNode.childNodes(), targetNode.childNodesCount());
                    }
                  }

                }

                scope.$apply(function() {
                  scope.$callbacks.dragMove(dragInfo.eventArgs(elements, pos));
                });
              }
            };

            var dragEnd = function(e) {
              e.preventDefault();

              if (dragElm) {
                scope.$treeScope.$apply(function() {
                  scope.$callbacks.beforeDrop(dragInfo.eventArgs(elements, pos));
                });
                // roll back elements changed
                hiddenPlaceElm.replaceWith(scope.$element);
                placeElm.remove();

                dragElm.remove();
                dragElm = null;
                if (scope.$$apply) {
                  dragInfo.apply();
                  scope.$treeScope.$apply(function() {
                    scope.$callbacks.dropped(dragInfo.eventArgs(elements, pos));
                  });
                } else {
                  bindDrag();
                }
                scope.$treeScope.$apply(function() {
                  scope.$callbacks.dragStop(dragInfo.eventArgs(elements, pos));
                });
                scope.$$apply = false;
                dragInfo = null;

              }

              // Restore cursor in Opera 12.16 and IE
              var oldCur = document.body.getAttribute('ui-tree-cursor');
              if (oldCur !== null) {
                $document.find('body').css({'cursor': oldCur});
                document.body.removeAttribute('ui-tree-cursor');
              }

              angular.element($document).unbind('touchend', dragEndEvent); // Mobile
              angular.element($document).unbind('touchcancel', dragEndEvent); // Mobile
              angular.element($document).unbind('touchmove', dragMoveEvent); // Mobile
              angular.element($document).unbind('mouseup', dragEndEvent);
              angular.element($document).unbind('mousemove', dragMoveEvent);
              angular.element($window.document.body).unbind('mouseleave', dragCancelEvent);
            };

            var dragStartEvent = function(e) {
              if (scope.dragEnabled()) {
                dragStart(e);
              }
            };

            var dragMoveEvent = function(e) {
              dragMove(e);
            };

            var dragEndEvent = function(e) {
              scope.$$apply = true;
              dragEnd(e);
            };

            var dragCancelEvent = function(e) {
              dragEnd(e);
            };

            var bindDrag = function() {
              element.bind('touchstart mousedown', function (e) {
                dragDelaying = true;
                dragStarted = false;
                dragStartEvent(e);
                dragTimer = $timeout(function(){dragDelaying = false;}, scope.dragDelay);
              });
              element.bind('touchend touchcancel mouseup',function(){$timeout.cancel(dragTimer);});
            };
            bindDrag();

            angular.element($window.document.body).bind("keydown", function(e) {
              if (e.keyCode == 27) {
                scope.$$apply = false;
                dragEnd(e);
              }
            });
          }
        };
      }
    ]);

})();

(function () {
  'use strict';

  angular.module('ui.tree')
  .directive('uiTreeHandle', [ 'treeConfig', '$window',
    function(treeConfig) {
      return {
        require: '^uiTreeNode',
        restrict: 'A',
        scope: true,
        controller: 'TreeHandleController',
        link: function(scope, element, attrs, treeNodeCtrl) {
          var config = {};
          angular.extend(config, treeConfig);
          if (config.handleClass) {
            element.addClass(config.handleClass);
          }
          // connect with the tree node.
          if (scope != treeNodeCtrl.scope) {
            scope.$nodeScope = treeNodeCtrl.scope;
            treeNodeCtrl.scope.$handleScope = scope;
          }
        }
      };
    }
  ]);
})();

/*global document: false, angular: false */
(function () {
    'use strict';

    angular.module('simpleGrid', [])//['sly'])
        .directive('simpleGrid', ['$timeout', '$log', function ($timeout, $log) {
            var gridNum = 0;
            return {
                scope: {
                    simpleGrid: '='
                },

                link: function (scope, elem, attrs) {

                    /**
                     * @param {boolean} editable
                     * @returns {boolean}
                     */
                    function isEditable(editable) {
                        //$log.debug('isEditable');
                        if (angular.isUndefined(editable)) {
                            return true; // editable by default
                        }
                        return editable || false;
                    }

                    /**
                     * @param {jQuery.event} event
                     * @param {number} targetRowIndex
                     * @param {number} colIndex
                     */
                    function setFocusedCell(event, targetRowIndex, colIndex) {
                        var elem = null, elems;
                        if (null !== targetRowIndex) {
                            elem = document.getElementById(scope.formName(targetRowIndex));
                        }
                        if (!elem) {
                            return;
                        }
                        if (event) {
                            event.preventDefault();
                        }
                        elems = elem.getElementsByClassName('sg-column-' + colIndex, angular.element(elem));
                        if (elems.length) {
                            $timeout(function () {
                                elems[0].focus();
                            });
                        }
                    }

                    function getOptionsForSelectColumn(column) {
                        // TODO: Allow column.options to be a promise
                        var options = column.options,
                            select = applyIfTruthy(column.select || identity);
                        if (!options) {
                            return [];
                        }
                        //$log.debug('getOptions');//, options);
                        if (options.length) {
                            // TODO: Not compatible with old browsers
                            return options.map(function (val) {
                                return {
                                    value: select(val),
                                    title: getColumnFormatter(column)(val)
                                };
                            });
                        }
                        return options;
                    }

                    function identity(x) {
                        return x;
                    }

                    function applyIfTruthy(func) {
                        return function (x) {
                            return x && func(x);
                        };
                    }

                    // TODO: Don't actually change the column object. Instead, store all internal data in some dictionary of sorts.
                    function setColumnFormatter(column, formatter) {
                        column.$formatter = formatter;
                    }

                    function getColumnFormatter(column) {
                        return column.$formatter;
                    }

                    function setRowSelected(row, isSelected) {
                        if (isSelected) {
                            row.$selected = true;
                        }
                        else {
                            delete row.$selected;
                        }
                    }

                    function recalculateColumns(columns) {
                        angular.forEach(columns, function (column) {
                            setColumnFormatter(column, applyIfTruthy(column.formatter || identity));
                            if (column.inputType === 'select') {
                                column.$options = getOptionsForSelectColumn(column);
                            }
                            column.$title = column.title || scope.capitalize(column.field);
                        });
                    }

                    function initialize() {
                        var columnsWatcherDeregister;
                        scope.gridNum = gridNum;
                        gridNum += 1;

                        scope.page = [];
                        scope.selectedRow = null;
                        scope.focusedRow = null;

                        scope.$watchCollection('simpleGrid.getData()', function (newVal) {
                            scope.data = newVal;
                            scope.updatePage();
                        });

                        scope.$watch('simpleGrid.options.pageSize', scope.updatePage);
                        scope.$watch('simpleGrid.options.pageNum', scope.updatePage);

                        scope.$watch('simpleGrid.options.editable', function (editable) {
                            scope.gridIsEditable = isEditable(editable);
                        });

                        scope.$watch('simpleGrid.options.dynamicColumns', function (newVal) {
                            if (columnsWatcherDeregister) { columnsWatcherDeregister(); }
                            if (newVal) {
                                columnsWatcherDeregister = scope.$watch('simpleGrid.options.columns', recalculateColumns, true);
                            } else {
                                columnsWatcherDeregister = scope.$watchCollection('simpleGrid.options.columns', recalculateColumns);
                            }
                        });

                        recalculateColumns();
                    }

                    scope.updatePage = function () {
                        var i,
                            pageSize,
                            pageStart;
                        scope.page.length = 0;
                        if (!scope.data) {
                            return;
                        }
                        pageSize = scope.simpleGrid.options.pageSize || scope.data.length;
                        pageStart = (scope.simpleGrid.options.pageNum || 0) * pageSize;
                        for (i = pageStart;
                             i < Math.min(pageStart + pageSize,
                                 scope.data.length) ;
                             i += 1) {
                            scope.page.push(scope.data[i]);
                        }
                    };

                    /**
                     * @param {string} str
                     * @returns {string}
                     */
                    scope.capitalize = function (str) {
                        //$log.debug('capitalize');
                        if (!str) {
                            return str;
                        }
                        return str[0].toUpperCase() + str.slice(1);
                    };

                    scope.toggleDeleted = function (row, rows) {
                        console.log(rows);
                        row.$deleted = !(row.$deleted || false);
                        if (row.$deleted && scope.simpleGrid.options && scope.simpleGrid.options.rowDeleted) {
                            scope.simpleGrid.options.rowDeleted(row);
                        }

                        var filtered = rows.filter(function (row) { return !row.$deleted; });
                        rows.splice(0, rows.length);
                        angular.forEach(filtered, function (item) {
                            rows.push(item);
                        });

                        angular.forEach(row, function(value, index) {
                            
                        });

                    };


                    scope.editRequested = function (row) {
                        if (scope.simpleGrid.options.perRowEditModeEnabled) {
                            row.$editable = !(row.$editable || false);
                        }
                        if (scope.simpleGrid.options.editRequested) {
                            scope.simpleGrid.options.editRequested(row);
                        }
                    };

                    /**
                     * @param {number} rowIndex
                     * @returns {boolean}
                     */
                    scope.isInvalid = function (rowIndex) {
                        //$log.debug('isInvalid', rowIndex);
                        var formCtrl = scope.$eval(scope.formName(rowIndex));
                        return formCtrl.$error;
                    };


                    /**
                     * @param {jQuery.event} event
                     * @param {number} rowIndex
                     * @param {number} colIndex
                     */
                    scope.keydown = function (event, rowIndex, colIndex) {
                        var elem = null, elems,
                            targetRowIndex = null;
                        switch (event.keyCode) {
                            case 40: //down
                                targetRowIndex = rowIndex + 1;
                                break;
                            case 38: //up
                                targetRowIndex = rowIndex - 1;
                                break;
                            case 13:
                                event.currentTarget.blur();
                                event.preventDefault();
                                return;
                        }
                        setFocusedCell(event, targetRowIndex, colIndex);
                    };

                    /**
                     * @param row
                     * @param column
                     * @returns {string}
                     */
                    scope.getCellText = function (row, column) {
                        //$log.debug('getCellText');//, row, column);
                        var cellValue = row[column.field];
                        if (column.inputType === 'select') {
                            return getColumnFormatter(column)(scope.getSelectOptionByValue(column.$options, cellValue));
                        }
                        return getColumnFormatter(column)(cellValue);
                    };

                    /**
                     * @param options
                     * @param value
                     * @returns {string}
                     */
                    scope.getSelectOptionByValue = function (options, value) {
                        //$log.debug('getOptionTitleByValue');//, options, value);
                        // TODO: Highly ineffecient.
                        // TODO: Array.prototype.filter is not compatible with older browsers
                        var filteredOptions = options.filter(function (option) {
                            return option.value === value;
                        });
                        if (!filteredOptions.length) {
                            // TODO: Write a log indicating that the value was not found.
                            return value;
                        }
                        return filteredOptions[0];
                    };

                    scope.toggleRowSelected = function (row) {
                        if (row && row.$selected) {
                            setRowSelected(row, false);
                            scope.selectRow(null);
                        } else {
                            scope.selectRow(row);
                        }
                    };

                    scope.selectRow = function (row) {
                        //$log.debug('selectRow', row);
                        if (!scope.simpleGrid.options.allowMultiSelect) {
                            if (scope.selectedRow && scope.selectedRow.$selected) {
                                delete scope.selectedRow.$selected;
                            }
                        }
                        if (!row || row.$deleted) {
                            scope.selectedRow = null;
                            return;
                        }
                        setRowSelected(row, true);
                        scope.selectedRow = row;

                        if (scope.simpleGrid.options.rowSelected) {
                            scope.simpleGrid.options.rowSelected(row);
                        }
                    };

                    scope.setFocusedRow = function (row) {
                        //$log.debug('setFocusedRow', row);
                        if (scope.focusedRow === row) {
                            return;
                        }
                        if (scope.focusedRow && scope.focusedRow.$focused) {
                            delete scope.focusedRow.$focused;
                        }

                        if (!scope.gridIsEditable) {
                            if (scope.simpleGrid.options.allowMultiSelect) {
                                scope.toggleRowSelected(row);
                            } else {
                                scope.selectRow(row);
                            }
                            return;
                        }

                        if (row) {
                            row.$focused = true;
                        }
                        scope.focusedRow = row;
                    };

                    scope.cellBlurred = function (event, row, column) {
                        scope.setFocusedRow(null);
                    };

                    scope.cellFocused = function (event, row, column) {
                        //$log.debug('cellFocused', row, column);
                        if (event.currentTarget.type === 'checkbox') {
                            return;
                        }
                        scope.setFocusedRow(row);
                        if (!scope.simpleGrid.options.allowMultiSelect) {
                            scope.selectRow(row);
                        }
                        if (column && scope.simpleGrid.options.cellFocused) {
                            scope.simpleGrid.options.cellFocused(row, column);
                        }
                    };

                    /**
                     * @param {jQuery.event} event
                     * @param {number} rowIndex
                     * @param {number} columnIndex
                     * @param row
                     * @param column
                     */
                    scope.startEditingCell = function (event, rowIndex, columnIndex, row, column) {
                        row.$editable = true;
                        $timeout(function () {
                            setFocusedCell(null, rowIndex, columnIndex);
                        });
                    };

                    scope.formName = (function () {
                        // memoize
                        var formNames = {};
                        /**
                         * @returns {string}
                         */
                        return function (rowIndex) {
                            var existing = formNames[rowIndex];
                            if (!existing) {
                                //$log.debug('formName', rowIndex);
                                existing = formNames[rowIndex] = 'simpleGrid' + scope.gridNum.toString() + 'Row' + rowIndex.toString();
                            }
                            return existing;
                        };
                    }());

                    /**
                     * @returns {boolean}
                     */
                    scope.isOrderByReverse = function () {
                        if (scope.simpleGrid && !angular.isUndefined(scope.simpleGrid.options.reverseOrder)) {
                            return scope.simpleGrid.options.reverseOrder;
                        }
                        return false;
                    };

                    initialize();
                },

                templateUrl: function (tElement, tAttrs) {
                    return document.getElementById('simple-grid.html').getAttribute('src');
                }
            };
        }]);
}());

/**
 * The iVantage Treeview module
 *
 * @package ivh.treeview
 */

angular.module('ivh.treeview', []);


/**
 * Selection management logic for treeviews with checkboxes
 *
 * @private
 * @package ivh.treeview
 * @copyright 2014 iVantage Health Analytics, Inc.
 */

angular.module('ivh.treeview').directive('ivhTreeviewCheckbox', [function() {
  'use strict';
  return {
    restrict: 'A',
    scope: {
      node: '=ivhTreeviewCheckbox'
    },
    require: '^ivhTreeview',
    link: function(scope, element, attrs, ctrl) {
      var node = scope.node
        , opts = ctrl.opts()
        , indeterminateAttr = opts.indeterminateAttribute
        , selectedAttr = opts.selectedAttribute;

      // Set initial selected state of this checkbox
      scope.isSelected = node[selectedAttr];

      // Local access to the parent controller
      scope.ctrl = ctrl;

      // Update the checkbox when the node's selected status changes
      scope.$watch(function() {
        return node[selectedAttr];
      }, function(newVal, oldVal) {
        scope.isSelected = newVal;
      });

      // Update the checkbox when the node's indeterminate status changes
      scope.$watch(function() {
        return node[indeterminateAttr];
      }, function(newVal, oldVal) {
        element.find('input').prop('indeterminate', newVal);
      });
    },
    template: [
      '<input type="checkbox"',
        'ng-model="isSelected"',
        'ng-change="ctrl.select(node, isSelected)" />'
    ].join('\n')
  };
}]);


/**
 * Treeview tree node directive
 *
 * @private
 * @package ivh.treeview
 * @copyright 2014 iVantage Health Analytics, Inc.
 */

angular.module('ivh.treeview').directive('ivhTreeviewNode', ['ivhTreeviewCompiler', 'ivhTreeviewOptions', function(ivhTreeviewCompiler, ivhTreeviewOptions) {
  'use strict';
  return {
    restrict: 'A',
    scope: {
      node: '=ivhTreeviewNode',
      depth: '=ivhTreeviewDepth'
    },
    require: '^ivhTreeview',
    compile: function(tElement) {
      return ivhTreeviewCompiler
        .compile(tElement, function(scope, element, attrs, ctrl) {
          var node = scope.node;

          var getChildren = scope.getChildren = function() {
            return ctrl.children(node);
          };

          scope.ctrl = ctrl;
          scope.childDepth = scope.depth + 1;

          // Expand/collapse the node as dictated by the expandToDepth property
          ctrl.expand(node, ctrl.isInitiallyExpanded(scope.depth));

          // Set the title to the full label
          element.attr('title', ctrl.label(node));

          /**
           * @todo Provide a way to opt out of this
           */
          var watcher = scope.$watch(function() {
            return getChildren().length > 0;
          }, function(newVal) {
            if(newVal) {
              element.removeClass('ivh-treeview-node-leaf');
            } else {
              element.addClass('ivh-treeview-node-leaf');
            }
          });
        });
    },
    template: [
      '<div>',
        '<div>',
          '<span ivh-treeview-toggle="node">',
            '<span ivh-treeview-twistie></span>',
          '</span>',
          '<span ng-if="ctrl.useCheckboxes()"',
              'ivh-treeview-checkbox="node">',
          '</span>',
          '<span class="ivh-treeview-node-label" ivh-treeview-toggle>',
            '{{ctrl.label(node)}}',
          '</span>',
        '</div>',
        '<ul ng-if="getChildren().length" class="ivh-treeview">',
          '<li ng-repeat="child in getChildren()"',
              'ng-hide="ctrl.hasFilter() && !ctrl.isVisible(child)"',
              'ng-class="{\'ivh-treeview-node-collapsed\': !ctrl.isExpanded(child) && !ctrl.isLeaf(child)}"',
              'ivh-treeview-node="child"',
              'ivh-treeview-depth="childDepth">',
          '</li>',
        '</ul>',
      '</div>'
    ].join('\n')
  };
}]);



/**
 * Toggle logic for treeview nodes
 *
 * Handles expand/collapse on click. Does nothing for leaf nodes.
 *
 * @private
 * @package ivh.treeview
 * @copyright 2014 iVantage Health Analytics, Inc.
 */

angular.module('ivh.treeview').directive('ivhTreeviewToggle', [function() {
  'use strict';
  return {
    restrict: 'A',
    require: '^ivhTreeview',
    link: function(scope, element, attrs, ctrl) {
      var node = scope.node
        , children = ctrl.children(node);

      element.addClass('ivh-treeview-toggle');

      element.bind('click', function() {
        scope.$apply(function() {
          ctrl.onNodeClick(node);
          ctrl.toggleExpanded(node);
        });
      });
    }
  };
}]);


/**
 * Treeview twistie directive
 *
 * @private
 * @package ivh.treeview
 * @copyright 2014 iVantage Health Analytics, Inc.
 */

angular.module('ivh.treeview').directive('ivhTreeviewTwistie', ['$compile', 'ivhTreeviewOptions', function($compile, ivhTreeviewOptions) {
  'use strict';

  var globalOpts = ivhTreeviewOptions();

  return {
    restrict: 'A',
    require: '^ivhTreeview',
    template: [
      '<span class="ivh-treeview-twistie">',
        '<span class="ivh-treeview-twistie-collapsed">',
          globalOpts.twistieCollapsedTpl,
        '</span>',
        '<span class="ivh-treeview-twistie-expanded">',
          globalOpts.twistieExpandedTpl,
        '</span>',
        '<span class="ivh-treeview-twistie-leaf">',
        '</span>',
      '</span>'
    ].join('\n'),
    link: function(scope, element, attrs, ctrl) {

      if(!ctrl.hasLocalTwistieTpls) {
        return;
      }

      var opts = ctrl.opts()
        , $twistieContainers = element
          .children().eq(0) // Template root
          .children(); // The twistie spans
      
      angular.forEach([
        // Should be in the same order as elements in template
        'twistieCollapsedTpl', 
        'twistieExpandedTpl',
        'twistieLeafTpl'
      ], function(tplKey, ix) {
        var tpl = opts[tplKey]
          , tplGlobal = globalOpts[tplKey];

        // Do nothing if we don't have a new template
        if(!tpl || tpl === tplGlobal) {
          return;
        }

        // Super gross, the template must actually be an html string, we won't
        // try too hard to enforce this, just don't shoot yourself in the foot
        // too badly and everything will be alright.
        if(tpl.substr(0,1) !== '<' || tpl.substr(-1,1) !== '>') {
          tpl = '<span>' + tpl + '</span>';
        }

        var $el = $compile(tpl)(scope)
          , $container = $twistieContainers.eq(ix);

        // Clean out global template and append the new one
        $container.html('').append($el);
      });

    }
  };
}]);


/**
 * The `ivh-treeview` directive
 *
 * A filterable tree view with checkbox support.
 *
 * Example:
 *
 * ```
 * <div
 *   ivh-treeview="myHierarchicalData">
 *   ivh-treeview-filter="myFilterText">
 * </div>
 * ```
 *
 * @package ivh.treeview
 * @copyright 2014 iVantage Health Analytics, Inc.
 */

angular.module('ivh.treeview').directive('ivhTreeview', ['ivhTreeviewMgr', function(ivhTreeviewMgr) {
  'use strict';
  return {
    restrict: 'A',
    scope: {
      // The tree data store
      root: '=ivhTreeview',

      // Specific config options
      childrenAttribute: '=ivhTreeviewChildrenAttribute',
      clickHandler: '=ivhTreeviewClickHandler',
      changeHandler: '=ivhTreeviewChangeHandler',
      defaultSelectedState: '=ivhTreeviewDefaultSelectedState',
      expandToDepth: '=ivhTreeviewExpandToDepth',
      idAttribute: '=ivhTreeviewIdAttribute',
      indeterminateAttribute: '=ivhTreeviewIndeterminateAttribute',
      expandedAttribute: '=ivhTreeviewExpandedAttribute',
      labelAttribute: '=ivhTreeviewLabelAttribute',
      selectedAttribute: '=ivhTreeviewSelectedAttribute',
      twistieCollapsedTpl: '=ivhTreeviewTwistieCollapsedTpl',
      twistieExpandedTpl: '=ivhTreeviewTwistieExpandedTpl',
      twistieLeafTpl: '=ivhTreeviewTwistieLeafTpl',
      useCheckboxes: '=ivhTreeviewUseCheckboxes',
      validate: '=ivhTreeviewValidate',
      visibleAttribute: '=ivhTreeviewVisibleAttribute',

      // Generic options object
      userOptions: '=ivhTreeviewOptions',

      // The filter
      filter: '=ivhTreeviewFilter'
    },
    controllerAs: 'ctrl',
    controller: ['$scope', '$element', '$attrs', '$transclude', 'ivhTreeviewOptions', 'filterFilter', function($scope, $element, $attrs, $transclude, ivhTreeviewOptions, filterFilter) {
      var ng = angular
        , ctrl = this;

      // Merge any locally set options with those registered with hte
      // ivhTreeviewOptions provider
      var localOpts = ng.extend({}, ivhTreeviewOptions(), $scope.userOptions);

      ng.forEach([
        'childrenAttribute',
        'defaultSelectedState',
        'expandToDepth',
        'idAttribute',
        'indeterminateAttribute',
        'expandedAttribute',
        'labelAttribute',
        'selectedAttribute',
        'twistieCollapsedTpl',
        'twistieExpandedTpl',
        'twistieLeafTpl',
        'useCheckboxes',
        'validate',
        'visibleAttribute'
      ], function(attr) {
        if(ng.isDefined($scope[attr])) {
          localOpts[attr] = $scope[attr];
        }
      });

      // Give child directives an easy way to get at merged options
      ctrl.opts = function() {
        return localOpts;
      };

      // If we didn't provide twistie templates we'll be doing a fair bit of
      // extra checks for no reason. Let's just inform down stream directives
      // whether or not they need to worry about twistie non-global templates.
      var userOpts = $scope.userOptions || {};
      ctrl.hasLocalTwistieTpls = !!(
        userOpts.twistieCollapsedTpl ||
        userOpts.twistieExpandedTpl ||
        userOpts.twistieLeafTpl ||
        $scope.twistieCollapsedTpl ||
        $scope.twistieExpandedTpl ||
        $scope.twistieLeafTpl);

      ctrl.children = function(node) {
        var children = node[localOpts.childrenAttribute];
        return ng.isArray(children) ? children : [];
      };

      ctrl.label = function(node) {
        return node[localOpts.labelAttribute];
      };

      ctrl.hasFilter = function() {
        return ng.isDefined($scope.filter);
      };

      ctrl.getFilter = function() {
        return $scope.filter || '';
      };

      ctrl.isVisible = function(node) {
        var filter = ctrl.getFilter();
        if(!filter) {
          return true;
        }
        return !!filterFilter([node], filter).length;
      };

      ctrl.useCheckboxes = function() {
        return localOpts.useCheckboxes;
      };

      ctrl.select = function(node, isSelected) {
        ivhTreeviewMgr.select($scope.root, node, localOpts, isSelected);
        ctrl.onNodeChange(node, isSelected);
      };

      ctrl.expand = function(node, isExpanded) {
        ivhTreeviewMgr.expand($scope.root, node, localOpts, isExpanded);
      };

      ctrl.isExpanded = function(node) {
        return node[localOpts.expandedAttribute];
      };

      ctrl.toggleExpanded = function(node) {
        ctrl.expand(node, !ctrl.isExpanded(node));
      };

      ctrl.isInitiallyExpanded = function(depth) {
        var expandTo = localOpts.expandToDepth === -1 ?
          Infinity : localOpts.expandToDepth;
        return depth < expandTo;
      };

      ctrl.isLeaf = function(node) {
        return ctrl.children(node).length === 0;
      };

      ctrl.onNodeClick = function(node) {
        ($scope.clickHandler || angular.noop)(node, $scope.root);
      };

      ctrl.onNodeChange = function(node, isSelected) {
        ($scope.changeHandler || angular.noop)(node, isSelected, $scope.root);
      };
    }],
    link: function(scope, element, attrs) {
      var opts = scope.ctrl.opts();

      // Allow opt-in validate on startup
      if(opts.validate) {
        ivhTreeviewMgr.validate(scope.root, opts);
      }
    },
    template: [
      '<ul class="ivh-treeview">',
        '<li ng-repeat="child in root | ivhTreeviewAsArray"',
            'ng-hide="ctrl.hasFilter() && !ctrl.isVisible(child)"',
            'ng-class="{\'ivh-treeview-node-collapsed\': !ctrl.isExpanded(child) && !ctrl.isLeaf(child)}"',
            'ivh-treeview-node="child"',
            'ivh-treeview-depth="0">',
        '</li>',
      '</ul>'
    ].join('\n')
  };
}]);



angular.module('ivh.treeview').filter('ivhTreeviewAsArray', function() {
  'use strict';
  return function(arr) {
    if(!angular.isArray(arr) && angular.isObject(arr)) {
      return [arr];
    }
    return arr;
  };
});

/**
 * Breadth first searching for treeview data stores
 *
 * @package ivh.treeview
 * @copyright 2014 iVantage Health Analytics, Inc.
 */

angular.module('ivh.treeview').factory('ivhTreeviewBfs', ['ivhTreeviewOptions', function(ivhTreeviewOptions) {
  'use strict';

  var ng = angular;

  /**
   * Breadth first search of `tree`
   *
   * `opts` is optional and may override settings from `ivhTreeviewOptions.options`.
   * The callback `cb` will be invoked on each node in the tree as we traverse,
   * if it returns `false` traversal of that branch will not continue. The
   * callback is given the current node as the first parameter and the node
   * ancestors, from closest to farthest, as an array in the second parameter.
   *
   * @param {Array|Object} tree The tree data
   * @param {Object} opts [optional] Settings overrides
   * @param {Function} cb [optional] Callback to run against each node
   */
  return function(tree, opts, cb) {
    if(arguments.length === 2 && ng.isFunction(opts)) {
      cb = opts;
      opts = {};
    }
    opts = angular.extend({}, ivhTreeviewOptions(), opts);
    cb = cb || ng.noop;

    var queue = []
      , childAttr = opts.childrenAttribute
      , next, node, parents, ix, numChildren;

    if(ng.isArray(tree)) {
      ng.forEach(tree, function(n) {
        // node and parents
        queue.push([n, []]);
      });
      next = queue.shift();
    } else {
      // node and parents
      next = [tree, []];
    }

    while(next) {
      node = next[0];
      parents = next[1];
      // cb might return `undefined` so we have to actually check for equality
      // against `false`
      if(cb(node, parents) !== false) {
        if(node[childAttr] && ng.isArray(node[childAttr])) {
          numChildren = node[childAttr].length;
          for(ix = 0; ix < numChildren; ix++) {
            queue.push([node[childAttr][ix], [node].concat(parents)]);
          }
        }
      }
      next = queue.shift();
    }
  };
}]);


/**
 * Treeview tree node directive
 *
 * Thanks to http://stackoverflow.com/questions/14430655/recursion-in-angular-directives
 *
 * @private
 * @package ivh.treeview
 * @copyright 2014 iVantage Health Analytics, Inc.
 */

angular.module('ivh.treeview').factory('ivhTreeviewCompiler', ['$compile', function($compile){
  'use strict';
  return {
    /**
     * Manually compiles the element, fixing the recursion loop.
     * @param {Object} element The angular element or template
     * @param {Function} link [optional] A post-link function, or an object with function(s) registered via pre and post properties.
     * @returns An object containing the linking functions.
     */
    compile: function(element, link){
      // Normalize the link parameter
      if(angular.isFunction(link)){
        link = { post: link };
      }

      // Break the recursion loop by removing the contents
      var contents = element.contents().remove();
      var compiledContents;
      return {
        pre: (link && link.pre) ? link.pre : null,
        /**
         * Compiles and re-adds the contents
         */
        post: function(scope, element){
          // Compile the contents
          if(!compiledContents){
            compiledContents = $compile(contents);
          }
          // Re-add the compiled contents to the element
          compiledContents(scope, function(clone){
            element.append(clone);
          });

          // Call the post-linking function, if any
          if(link && link.post){
            link.post.apply(null, arguments);
          }
        }
      };
    }
  };
}]);


/**
 * Manager for treeview data stores
 *
 * Used to assist treeview operations, e.g. selecting or validating a tree-like
 * collection.
 *
 * @package ivh.treeview
 * @copyright 2014 iVantage Health Analytics, Inc.
 */

angular.module('ivh.treeview')
  .factory('ivhTreeviewMgr', ['ivhTreeviewOptions', 'ivhTreeviewBfs', function(ivhTreeviewOptions, ivhTreeviewBfs) {
    'use strict';

    var ng = angular
      , options = ivhTreeviewOptions()
      , exports = {};

    // The make* methods and validateParent need to be bound to an options
    // object
    var makeDeselected = function(node) {
      node[this.selectedAttribute] = false;
      node[this.indeterminateAttribute] = false;
    };

    var makeSelected = function(node) {
      node[this.selectedAttribute] = true;
      node[this.indeterminateAttribute] = false;
    };

    var validateParent = function(node) {
      var children = node[this.childrenAttribute]
        , selectedAttr = this.selectedAttribute
        , indeterminateAttr = this.indeterminateAttribute
        , numSelected = 0
        , numIndeterminate = 0;
      ng.forEach(children, function(n, ix) {
        if(n[selectedAttr]) {
          numSelected++;
        } else {
          if(n[indeterminateAttr]) {
            numIndeterminate++;
          }
        }
      });

      if(0 === numSelected && 0 === numIndeterminate) {
        node[selectedAttr] = false;
        node[indeterminateAttr] = false;
      } else if(numSelected === children.length) {
        node[selectedAttr] = true;
        node[indeterminateAttr] = false;
      } else {
        node[selectedAttr] = false;
        node[indeterminateAttr] = true;
      }
    };

    var findNode = function(tree, node, opts, cb) {
      var useId = ng.isString(node)
        , proceed = true
        , idAttr = opts.idAttribute;

      // Our return values
      var foundNode = null
        , foundParents = [];

      ivhTreeviewBfs(tree, opts, function(n, p) {
        var isNode = proceed && (useId ?
          node === n[idAttr] :
          node === n);

        if(isNode) {
          // I've been looking for you all my life
          proceed = false;
          foundNode = n;
          foundParents = p;
        }

        return proceed;
      });

      return cb(foundNode, foundParents);
    };

    /**
     * Select (or deselect) a tree node
     *
     * This method will update the rest of the tree to account for your change.
     *
     * You may alternatively pass an id as `node`, in which case the tree will
     * be searched for your item.
     *
     * @param {Object|Array} tree The tree data
     * @param {Object|String} node The node (or id) to (de)select
     * @param {Object} opts [optional] Options to override default options with
     * @param {Boolean} isSelected [optional] Whether or not to select `node`, defaults to `true`
     * @return {Object} Returns the ivhTreeviewMgr instance for chaining
     */
    exports.select = function(tree, node, opts, isSelected) {
      if(arguments.length > 2) {
        if(typeof opts === 'boolean') {
          isSelected = opts;
          opts = {};
        }
      }
      opts = ng.extend({}, options, opts);
      isSelected = ng.isDefined(isSelected) ? isSelected : true;

      var useId = ng.isString(node)
        , proceed = true
        , idAttr = opts.idAttribute;

      ivhTreeviewBfs(tree, opts, function(n, p) {
        var isNode = proceed && (useId ?
          node === n[idAttr] :
          node === n);

        if(isNode) {
          // I've been looking for you all my life
          proceed = false;

          var cb = isSelected ?
            makeSelected.bind(opts) :
            makeDeselected.bind(opts);

          ivhTreeviewBfs(n, opts, cb);
          ng.forEach(p, validateParent.bind(opts));
        }

        return proceed;
      });

      return exports;
    };

    /**
     * Select all nodes in a tree
     *
     * `opts` will default to an empty object, `isSelected` defaults to `true`.
     *
     * @param {Object|Array} tree The tree data
     * @param {Object} opts [optional] Default options overrides
     * @param {Boolean} isSelected [optional] Whether or not to select items
     * @return {Object} Returns the ivhTreeviewMgr instance for chaining
     */
    exports.selectAll = function(tree, opts, isSelected) {
      if(arguments.length > 1) {
        if(typeof opts === 'boolean') {
          isSelected = opts;
          opts = {};
        }
      }

      opts = ng.extend({}, options, opts);
      isSelected = ng.isDefined(isSelected) ? isSelected : true;

      var selectedAttr = opts.selectedAttribute
        , indeterminateAttr = opts.indeterminateAttribute;

      ivhTreeviewBfs(tree, opts, function(node) {
        node[selectedAttr] = isSelected;
        node[indeterminateAttr] = false;
      });

      return exports;
    };

    /**
     * Select or deselect each of the passed items
     *
     * Eventually it would be nice if this did something more intelligent than
     * just calling `select` on each item in the array...
     *
     * @param {Object|Array} tree The tree data
     * @param {Array} nodes The array of nodes or node ids
     * @param {Object} opts [optional] Default options overrides
     * @param {Boolean} isSelected [optional] Whether or not to select items
     * @return {Object} Returns the ivhTreeviewMgr instance for chaining
     */
    exports.selectEach = function(tree, nodes, opts, isSelected) {
      /**
       * @todo Surely we can do something better than this...
       */
      ng.forEach(nodes, function(node) {
        exports.select(tree, node, opts, isSelected);
      });
      return exports;
    };

    /**
     * Deselect a tree node
     *
     * Delegates to `ivhTreeviewMgr.select` with `isSelected` set to `false`.
     *
     * @param {Object|Array} tree The tree data
     * @param {Object|String} node The node (or id) to (de)select
     * @param {Object} opts [optional] Options to override default options with
     * @return {Object} Returns the ivhTreeviewMgr instance for chaining
     */
    exports.deselect = function(tree, node, opts) {
      return exports.select(tree, node, opts, false);
    };

    /**
     * Deselect all nodes in a tree
     *
     * Delegates to `ivhTreeviewMgr.selectAll` with `isSelected` set to `false`.
     *
     * @param {Object|Array} tree The tree data
     * @param {Object} opts [optional] Default options overrides
     * @return {Object} Returns the ivhTreeviewMgr instance for chaining
     */
    exports.deselectAll = function(tree, opts) {
      return exports.selectAll(tree, opts, false);
    };

    /**
     * Deselect each of the passed items
     *
     * Delegates to `ivhTreeviewMgr.selectEach` with `isSelected` set to
     * `false`.
     *
     * @param {Object|Array} tree The tree data
     * @param {Array} nodes The array of nodes or node ids
     * @param {Object} opts [optional] Default options overrides
     * @return {Object} Returns the ivhTreeviewMgr instance for chaining
     */
    exports.deselectEach = function(tree, nodes, opts) {
      return exports.selectEach(tree, nodes, opts, false);
    };

    /**
     * Validate tree for parent/child selection consistency
     *
     * Assumes `bias` as default selected state. The first element with
     * `node.select !== bias` will be assumed correct. For example, if `bias` is
     * `true` (the default) we'll traverse the tree until we come to an
     * unselected node at which point we stop and deselect each of that node's
     * children (and their children, etc.).
     *
     * Indeterminate states will also be resolved.
     *
     * @param {Object|Array} tree The tree data
     * @param {Object} opts [optional] Options to override default options with
     * @param {Boolean} bias [optional] Default selected state
     * @return {Object} Returns the ivhTreeviewMgr instance for chaining
     */
    exports.validate = function(tree, opts, bias) {
      if(arguments.length > 1) {
        if(typeof opts === 'boolean') {
          bias = opts;
          opts = {};
        }
      }
      opts = ng.extend({}, options, opts);
      bias = ng.isDefined(bias) ? bias : opts.defaultSelectedState;

      var selectedAttr = opts.selectedAttribute
        , indeterminateAttr = opts.indeterminateAttribute;

      ivhTreeviewBfs(tree, opts, function(node, parents) {
        if(ng.isDefined(node[selectedAttr]) && node[selectedAttr] !== bias) {
          exports.select(tree, node, opts, !bias);
          return false;
        } else {
          node[selectedAttr] = bias;
          node[indeterminateAttr] = false;
        }
      });

      return exports;
    };

    /**
     * Expand/collapse a given tree node
     *
     * `node` may be either an actual tree node object or a node id. 
     *
     * `opts` may override any of the defaults set by `ivhTreeviewOptions`.
     *
     * @param {Object|Array} tree The tree data
     * @param {Object|String} node The node (or id) to expand/collapse
     * @param {Object} opts [optional] Options to override default options with
     * @param {Boolean} isExpanded [optional] Whether or not to expand `node`, defaults to `true`
     * @return {Object} Returns the ivhTreeviewMgr instance for chaining
     */
    exports.expand = function(tree, node, opts, isExpanded) {
      if(arguments.length > 2) {
        if(typeof opts === 'boolean') {
          isExpanded = opts;
          opts = {};
        }
      }
      opts = ng.extend({}, options, opts);
      isExpanded = ng.isDefined(isExpanded) ? isExpanded : true;

      var useId = ng.isString(node)
        , expandedAttr = opts.expandedAttribute;

      if(!useId) {
        // No need to do any searching if we already have the node in hand
        node[expandedAttr] = isExpanded;
        return exports;
      }

      return findNode(tree, node, opts, function(n, p) {
        n[expandedAttr] = isExpanded;
        return exports;
      });
    };

    /**
     * Expand/collapse a given tree node and its children
     *
     * `node` may be either an actual tree node object or a node id. You may
     * leave off `node` entirely to expand/collapse the entire tree, however, if
     * you specify a value for `opts` or `isExpanded` you must provide a value
     * for `node`.
     *
     * `opts` may override any of the defaults set by `ivhTreeviewOptions`.
     *
     * @param {Object|Array} tree The tree data
     * @param {Object|String} node [optional*] The node (or id) to expand/collapse recursively
     * @param {Object} opts [optional] Options to override default options with
     * @param {Boolean} isExpanded [optional] Whether or not to expand `node`, defaults to `true`
     * @return {Object} Returns the ivhTreeviewMgr instance for chaining
     */
    exports.expandRecursive = function(tree, node, opts, isExpanded) {
      if(arguments.length > 2) {
        if(typeof opts === 'boolean') {
          isExpanded = opts;
          opts = {};
        }
      }
      node = ng.isDefined(node) ? node : tree;
      opts = ng.extend({}, options, opts);
      isExpanded = ng.isDefined(isExpanded) ? isExpanded : true;

      var useId = ng.isString(node)
        , expandedAttr = opts.expandedAttribute
        , branch;

      // If we have an ID first resolve it to an actual node in the tree
      if(useId) {
        findNode(tree, node, opts, function(n, p) {
          branch = n;
        });
      } else {
        branch = node;
      }

      if(branch) {
        ivhTreeviewBfs(branch, opts, function(n, p) {
          n[expandedAttr] = isExpanded;
        });
      }

      return exports;
    };

    /**
     * Collapse a given tree node
     *
     * Delegates to `exports.expand` with `isExpanded` set to `false`.
     *
     * @param {Object|Array} tree The tree data
     * @param {Object|String} node The node (or id) to collapse
     * @param {Object} opts [optional] Options to override default options with
     * @return {Object} Returns the ivhTreeviewMgr instance for chaining
     */
    exports.collapse = function(tree, node, opts) {
      return exports.expand(tree, node, opts, false);
    };

    /**
     * Collapse a given tree node and its children
     *
     * Delegates to `exports.expandRecursive` with `isExpanded` set to `false`.
     *
     * @param {Object|Array} tree The tree data
     * @param {Object|String} node The node (or id) to expand/collapse recursively
     * @param {Object} opts [optional] Options to override default options with
     * @return {Object} Returns the ivhTreeviewMgr instance for chaining
     */
    exports.collapseRecursive = function(tree, node, opts, isExpanded) {
      return exports.expandRecursive(tree, node, opts, false);
    };

    /**
     * Expand[/collapse] all parents of a given node, i.e. "reveal" the node
     *
     * @param {Object|Array} tree The tree data
     * @param {Object|String} node The node (or id) to expand to
     * @param {Object} opts [optional] Options to override default options with
     * @param {Boolean} isExpanded [optional] Whether or not to expand parent nodes
     * @return {Object} Returns the ivhTreeviewMgr instance for chaining
     */
    exports.expandTo = function(tree, node, opts, isExpanded) {
      if(arguments.length > 2) {
        if(typeof opts === 'boolean') {
          isExpanded = opts;
          opts = {};
        }
      }
      opts = ng.extend({}, options, opts);
      isExpanded = ng.isDefined(isExpanded) ? isExpanded : true;

      var expandedAttr = opts.expandedAttribute;

      var expandCollapseNode = function(n) {
        n[expandedAttr] = isExpanded;
      };

      // Even if wer were given the actual node and not its ID we must still
      // traverse the tree to find that node's parents.
      return findNode(tree, node, opts, function(n, p) {
        ng.forEach(p, expandCollapseNode);
        return exports;
      });
    };

    /**
     * Collapse all parents of a give node
     *
     * Delegates to `exports.expandTo` with `isExpanded` set to `false`.
     *
     * @param {Object|Array} tree The tree data
     * @param {Object|String} node The node (or id) to expand to
     * @param {Object} opts [optional] Options to override default options with
     * @return {Object} Returns the ivhTreeviewMgr instance for chaining
     */
    exports.collapseParents = function(tree, node, opts) {
      return exports.expandTo(tree, node, opts, false);
    };

    return exports;
  }
]);


/**
 * Global options for ivhTreeview
 *
 * @package ivh.treeview
 * @copyright 2014 iVantage Health Analytics, Inc.
 */

angular.module('ivh.treeview').provider('ivhTreeviewOptions', function() {
  'use strict';

  var options = {
    /**
     * ID attribute
     *
     * For selecting nodes by identifier rather than reference
     */
    idAttribute: 'id',

    /**
     * Collection item attribute to use for labels
     */
    labelAttribute: 'label',

    /**
     * Collection item attribute to use for child nodes
     */
    childrenAttribute: 'children',

    /**
     * Collection item attribute to use for selected state
     */
    selectedAttribute: 'selected',

    /**
     * Controls whether branches are initially expanded or collapsed
     *
     * A value of `0` means the tree will be entirely collapsd (the default
     * state) otherwise branches will be expanded up to the specified depth. Use
     * `-1` to have the tree entirely expanded.
     */
    expandToDepth: 0,

    /**
     * Whether or not to use checkboxes
     *
     * If `false` the markup to support checkboxes is not included in the
     * directive.
     */
    useCheckboxes: true,

    /**
     * Whether or not directive should validate treestore on startup
     *
     * Must opt-in.
     */
    validate: false,

    /**
     * (internal) Collection item attribute to track intermediate states
     */
    indeterminateAttribute: '__ivhTreeviewIndeterminate',

    /**
     * (internal) Collection item attribute to track expanded status
     */
    expandedAttribute: '__ivhTreeviewExpanded',

    /**
     * Default selected state when validating
     */
    defaultSelectedState: true,

    /**
     * Template for expanded twisties
     */
    twistieExpandedTpl: '<i class="glyphicon glyphicon-chevron-down" style="top:0px !important"></i>',

    /**
     * Template for collapsed twisties
     */
    twistieCollapsedTpl: '<i class="glyphicon glyphicon-chevron-right" style="top:0px !important"></i>',

    /**
     * Template for leaf twisties (i.e. no children)
     */
    twistieLeafTpl: 'o'

  };

  /**
   * Update global options
   *
   * @param {Object} opts options object to override defaults with
   */
  this.set = function(opts) {
    angular.extend(options, opts);
  };

  this.$get = function() {
    /**
     * Get a copy of the global options
     *
     * @return {Object} The options object
     */
    return function() {
      return angular.copy(options);
    };
  };
});

'use strict';

angular.module('minicolors', []);

angular.module('minicolors').provider('minicolors', function () {
  this.defaults = {
    theme: 'bootstrap',
    position: 'top left',
    defaultValue: '',
    animationSpeed: 50,
    animationEasing: 'swing',
    change: null,
    changeDelay: 0,
    control: 'hue',
    hide: null,
    hideSpeed: 100,
    inline: false,
    letterCase: 'lowercase',
    opacity: false,
    show: null,
    showSpeed: 100
  };

  this.$get = function() {
    return this;
  };

});

angular.module('minicolors').directive('minicolors', ['minicolors', '$timeout', function (minicolors, $timeout) {
  return {
    require: '?ngModel',
    restrict: 'A',
    priority: 1, //since we bind on an input element, we have to set a higher priority than angular-default input
    link: function(scope, element, attrs, ngModel) {

      var inititalized = false;

      //gets the settings object
      var getSettings = function () {
        var config = angular.extend({}, minicolors.defaults, scope.$eval(attrs.minicolors));
        return config;
      };

      //what to do if the value changed
      ngModel.$render = function () {

        //we are in digest or apply, and therefore call a timeout function
        $timeout(function() {
          var color = ngModel.$viewValue;
          element.minicolors('value', color);
        }, 0, false);
      };

      //init method
      var initMinicolors = function () {

        if(!ngModel) {
          return;
        }
        var settings = getSettings();
        settings.change = function (hex) {
          scope.$apply(function () {
            ngModel.$setViewValue(hex);
          });
        };

        //destroy the old colorpicker if one already exists
        if(element.hasClass('minicolors')) {
          element.minicolors('destroy');
        }

        // Create the new minicolors widget
        element.minicolors(settings);

        // are we inititalized yet ?
        //needs to be wrapped in $timeout, to prevent $apply / $digest errors
        //$scope.$apply will be called by $timeout, so we don't have to handle that case
        if (!inititalized) {
          $timeout(function() {
            var color = ngModel.$viewValue;
            element.minicolors('value', color);
          }, 0);
          inititalized = true;
          return;
        }
      };

      initMinicolors();
      //initital call

      // Watch for changes to the directives options and then call init method again
      scope.$watch(getSettings, initMinicolors, true);
    }
  };
}]);

angular.module('colorpicker.module', [])
    .factory('Helper', function () {
        'use strict';
        return {
            closestSlider: function (elem) {
                var matchesSelector = elem.matches || elem.webkitMatchesSelector || elem.mozMatchesSelector || elem.msMatchesSelector;
                if (matchesSelector.bind(elem)('I')) {
                    return elem.parentNode;
                }
                return elem;
            },
            getOffset: function (elem, fixedPosition) {
                var
                  scrollX = 0,
                  scrollY = 0,
                  rect = elem.getBoundingClientRect();
                while (elem && !isNaN(elem.offsetLeft) && !isNaN(elem.offsetTop)) {
                    if (!fixedPosition && elem.tagName === 'BODY') {
                        scrollX += document.documentElement.scrollLeft || elem.scrollLeft;
                        scrollY += document.documentElement.scrollTop || elem.scrollTop;
                    } else {
                        scrollX += elem.scrollLeft;
                        scrollY += elem.scrollTop;
                    }
                    elem = elem.offsetParent;
                }
                return {
                    top: rect.top + window.pageYOffset,
                    left: rect.left + window.pageXOffset,
                    scrollX: scrollX,
                    scrollY: scrollY
                };
            },
            // a set of RE's that can match strings and generate color tuples. https://github.com/jquery/jquery-color/
            stringParsers: [
              {
                  re: /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,
                  parse: function (execResult) {
                      return [
                        execResult[1],
                        execResult[2],
                        execResult[3],
                        execResult[4]
                      ];
                  }
              },
              {
                  re: /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,
                  parse: function (execResult) {
                      return [
                        2.55 * execResult[1],
                        2.55 * execResult[2],
                        2.55 * execResult[3],
                        execResult[4]
                      ];
                  }
              },
              {
                  re: /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/,
                  parse: function (execResult) {
                      return [
                        parseInt(execResult[1], 16),
                        parseInt(execResult[2], 16),
                        parseInt(execResult[3], 16)
                      ];
                  }
              },
              {
                  re: /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/,
                  parse: function (execResult) {
                      return [
                        parseInt(execResult[1] + execResult[1], 16),
                        parseInt(execResult[2] + execResult[2], 16),
                        parseInt(execResult[3] + execResult[3], 16)
                      ];
                  }
              }
            ]
        };
    })
    .factory('Color', ['Helper', function (Helper) {
        'use strict';
        return {
            value: {
                h: 1,
                s: 1,
                b: 1,
                a: 1
            },
            // translate a format from Color object to a string
            'rgb': function () {
                var rgb = this.toRGB();
                return 'rgb(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ')';
            },
            'rgba': function () {
                var rgb = this.toRGB();
                return 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + rgb.a + ')';
            },
            'hex': function () {
                return this.toHex();
            },

            // HSBtoRGB from RaphaelJS
            RGBtoHSB: function (r, g, b, a) {
                r /= 255;
                g /= 255;
                b /= 255;

                var H, S, V, C;
                V = Math.max(r, g, b);
                C = V - Math.min(r, g, b);
                H = (C === 0 ? null :
                    V === r ? (g - b) / C :
                        V === g ? (b - r) / C + 2 :
                            (r - g) / C + 4
                    );
                H = ((H + 360) % 6) * 60 / 360;
                S = C === 0 ? 0 : C / V;
                return { h: H || 1, s: S, b: V, a: a || 1 };
            },

            //parse a string to HSB
            setColor: function (val) {
                val = (val) ? val.toLowerCase() : val;
                for (var key in Helper.stringParsers) {
                    if (Helper.stringParsers.hasOwnProperty(key)) {
                        var parser = Helper.stringParsers[key];
                        var match = parser.re.exec(val),
                            values = match && parser.parse(match);
                        if (values) {
                            this.value = this.RGBtoHSB.apply(null, values);
                            return false;
                        }
                    }
                }
            },

            setHue: function (h) {
                this.value.h = 1 - h;
            },

            setSaturation: function (s) {
                this.value.s = s;
            },

            setLightness: function (b) {
                this.value.b = 1 - b;
            },

            setAlpha: function (a) {
                this.value.a = parseInt((1 - a) * 100, 10) / 100;
            },

            // HSBtoRGB from RaphaelJS
            // https://github.com/DmitryBaranovskiy/raphael/
            toRGB: function (h, s, b, a) {
                if (!h) {
                    h = this.value.h;
                    s = this.value.s;
                    b = this.value.b;
                }
                h *= 360;
                var R, G, B, X, C;
                h = (h % 360) / 60;
                C = b * s;
                X = C * (1 - Math.abs(h % 2 - 1));
                R = G = B = b - C;

                h = ~~h;
                R += [C, X, 0, 0, X, C][h];
                G += [X, C, C, X, 0, 0][h];
                B += [0, 0, X, C, C, X][h];
                return {
                    r: Math.round(R * 255),
                    g: Math.round(G * 255),
                    b: Math.round(B * 255),
                    a: a || this.value.a
                };
            },

            toHex: function (h, s, b, a) {
                var rgb = this.toRGB(h, s, b, a);
                return '#' + ((1 << 24) | (parseInt(rgb.r, 10) << 16) | (parseInt(rgb.g, 10) << 8) | parseInt(rgb.b, 10)).toString(16).substr(1);
            }
        };
    }])
    .factory('Slider', ['Helper', function (Helper) {
        'use strict';
        var
            slider = {
                maxLeft: 0,
                maxTop: 0,
                callLeft: null,
                callTop: null,
                knob: {
                    top: 0,
                    left: 0
                }
            },
            pointer = {};

        return {
            getSlider: function () {
                return slider;
            },
            getLeftPosition: function (event) {
                return Math.max(0, Math.min(slider.maxLeft, slider.left + ((event.pageX || pointer.left) - pointer.left)));
            },
            getTopPosition: function (event) {
                return Math.max(0, Math.min(slider.maxTop, slider.top + ((event.pageY || pointer.top) - pointer.top)));
            },
            setSlider: function (event, fixedPosition) {
                var
                  target = Helper.closestSlider(event.target),
                  targetOffset = Helper.getOffset(target, fixedPosition),
                  rect = target.getBoundingClientRect(),
                  offsetX = event.clientX - rect.left,
                  offsetY = event.clientY - rect.top;

                slider.knob = target.children[0].style;
                slider.left = event.pageX - targetOffset.left - window.pageXOffset + targetOffset.scrollX;
                slider.top = event.pageY - targetOffset.top - window.pageYOffset + targetOffset.scrollY;

                pointer = {
                    left: event.pageX - (offsetX - slider.left),
                    top: event.pageY - (offsetY - slider.top)
                };
            },
            setSaturation: function (event, fixedPosition, componentSize) {
                slider = {
                    maxLeft: componentSize,
                    maxTop: componentSize,
                    callLeft: 'setSaturation',
                    callTop: 'setLightness'
                };
                this.setSlider(event, fixedPosition);
            },
            setHue: function (event, fixedPosition, componentSize) {
                slider = {
                    maxLeft: 0,
                    maxTop: componentSize,
                    callLeft: false,
                    callTop: 'setHue'
                };
                this.setSlider(event, fixedPosition);
            },
            setAlpha: function (event, fixedPosition, componentSize) {
                slider = {
                    maxLeft: 0,
                    maxTop: componentSize,
                    callLeft: false,
                    callTop: 'setAlpha'
                };
                this.setSlider(event, fixedPosition);
            },
            setKnob: function (top, left) {
                slider.knob.top = top + 'px';
                slider.knob.left = left + 'px';
            }
        };
    }])
    .directive('colorpicker', ['$document', '$compile', 'Color', 'Slider', 'Helper', function ($document, $compile, Color, Slider, Helper) {
        'use strict';
        return {
            require: '?ngModel',
            restrict: 'A',
            link: function ($scope, elem, attrs, ngModel) {
                var
                    thisFormat = attrs.colorpicker ? attrs.colorpicker : 'hex',
                    position = angular.isDefined(attrs.colorpickerPosition) ? attrs.colorpickerPosition : 'bottom',
                    inline = angular.isDefined(attrs.colorpickerInline) ? attrs.colorpickerInline : false,
                    fixedPosition = angular.isDefined(attrs.colorpickerFixedPosition) ? attrs.colorpickerFixedPosition : false,
                    target = angular.isDefined(attrs.colorpickerParent) ? elem.parent() : angular.element(document.body),
                    withInput = angular.isDefined(attrs.colorpickerWithInput) ? attrs.colorpickerWithInput : false,
                    componentSize = angular.isDefined(attrs.colorpickerSize) ? attrs.colorpickerSize : 100,
                    componentSizePx = componentSize + 'px',
                    inputTemplate = withInput ? '<input type="text" name="colorpicker-input" spellcheck="false">' : '',
                    closeButton = !inline ? '<button type="button" class="close close-colorpicker">&times;</button>' : '',
                    template =
                        '<div class="colorpicker dropdown">' +
                            '<div class="dropdown-menu">' +
                            '<colorpicker-saturation><i></i></colorpicker-saturation>' +
                            '<colorpicker-hue><i></i></colorpicker-hue>' +
                            '<colorpicker-alpha><i></i></colorpicker-alpha>' +
                            '<colorpicker-preview></colorpicker-preview>' +
                            inputTemplate +
                            closeButton +
                            '</div>' +
                            '</div>',
                    colorpickerTemplate = angular.element(template),
                    pickerColor = Color,
                    componentSizePx,
                    sliderAlpha,
                    sliderHue = colorpickerTemplate.find('colorpicker-hue'),
                    sliderSaturation = colorpickerTemplate.find('colorpicker-saturation'),
                    colorpickerPreview = colorpickerTemplate.find('colorpicker-preview'),
                    pickerColorPointers = colorpickerTemplate.find('i');

                $compile(colorpickerTemplate)($scope);
                colorpickerTemplate.css('min-width', parseInt(componentSize) + 29 + 'px');
                sliderSaturation.css({
                    'width': componentSizePx,
                    'height': componentSizePx
                });
                sliderHue.css('height', componentSizePx);

                if (withInput) {
                    var pickerColorInput = colorpickerTemplate.find('input');
                    pickerColorInput.css('width', componentSizePx);
                    pickerColorInput
                        .on('mousedown', function (event) {
                            event.stopPropagation();
                        })
                      .on('keyup', function () {
                          var newColor = this.value;
                          elem.val(newColor);
                          if (ngModel && ngModel.$modelValue !== newColor) {
                              $scope.$apply(ngModel.$setViewValue(newColor));
                              update(true);
                          }
                      });
                }

                function bindMouseEvents() {
                    $document.on('mousemove', mousemove);
                    $document.on('mouseup', mouseup);
                }

                if (thisFormat === 'rgba') {
                    colorpickerTemplate.addClass('alpha');
                    sliderAlpha = colorpickerTemplate.find('colorpicker-alpha');
                    sliderAlpha.css('height', componentSizePx);
                    sliderAlpha
                        .on('click', function (event) {
                            Slider.setAlpha(event, fixedPosition, componentSize);
                            mousemove(event);
                        })
                        .on('mousedown', function (event) {
                            Slider.setAlpha(event, fixedPosition, componentSize);
                            bindMouseEvents();
                        })
                        .on('mouseup', function (event) {
                            emitEvent('colorpicker-selected-alpha');
                        });
                }

                sliderHue
                    .on('click', function (event) {
                        Slider.setHue(event, fixedPosition, componentSize);
                        mousemove(event);
                    })
                    .on('mousedown', function (event) {
                        Slider.setHue(event, fixedPosition, componentSize);
                        bindMouseEvents();
                    })
                    .on('mouseup', function (event) {
                        emitEvent('colorpicker-selected-hue');
                    });

                sliderSaturation
                    .on('click', function (event) {
                        Slider.setSaturation(event, fixedPosition, componentSize);
                        mousemove(event);
                        if (angular.isDefined(attrs.colorpickerCloseOnSelect)) {
                            hideColorpickerTemplate();
                        }
                    })
                    .on('mousedown', function (event) {
                        Slider.setSaturation(event, fixedPosition, componentSize);
                        bindMouseEvents();
                    })
                    .on('mouseup', function (event) {
                        emitEvent('colorpicker-selected-saturation');
                    });

                if (fixedPosition) {
                    colorpickerTemplate.addClass('colorpicker-fixed-position');
                }

                colorpickerTemplate.addClass('colorpicker-position-' + position);
                if (inline === 'true') {
                    colorpickerTemplate.addClass('colorpicker-inline');
                }

                target.append(colorpickerTemplate);

                if (ngModel) {
                    ngModel.$render = function () {
                        elem.val(ngModel.$viewValue);

                        update();
                    };
                }

                elem.on('blur keyup change', function () {
                    update();
                });

                elem.on('$destroy', function () {
                    colorpickerTemplate.remove();
                });

                function previewColor() {
                    try {
                        colorpickerPreview.css('backgroundColor', pickerColor[thisFormat]());
                    } catch (e) {
                        colorpickerPreview.css('backgroundColor', pickerColor.toHex());
                    }
                    sliderSaturation.css('backgroundColor', pickerColor.toHex(pickerColor.value.h, 1, 1, 1));
                    if (thisFormat === 'rgba') {
                        sliderAlpha.css.backgroundColor = pickerColor.toHex();
                    }
                }

                function mousemove(event) {
                    var
                        left = Slider.getLeftPosition(event),
                        top = Slider.getTopPosition(event),
                        slider = Slider.getSlider();

                    Slider.setKnob(top, left);

                    if (slider.callLeft) {
                        pickerColor[slider.callLeft].call(pickerColor, left / componentSize);
                    }
                    if (slider.callTop) {
                        pickerColor[slider.callTop].call(pickerColor, top / componentSize);
                    }
                    previewColor();
                    var newColor = pickerColor[thisFormat]();
                    elem.val(newColor);
                    if (ngModel) {
                        $scope.$apply(ngModel.$setViewValue(newColor));
                    }
                    if (withInput) {
                        pickerColorInput.val(newColor);
                    }
                    return false;
                }

                function mouseup() {
                    emitEvent('colorpicker-selected');
                    $document.off('mousemove', mousemove);
                    $document.off('mouseup', mouseup);
                }

                function update(omitInnerInput) {
                    pickerColor.setColor(elem.val());
                    if (withInput && !omitInnerInput) {
                        pickerColorInput.val(elem.val());
                    }
                    pickerColorPointers.eq(0).css({
                        left: pickerColor.value.s * componentSize + 'px',
                        top: componentSize - pickerColor.value.b * componentSize + 'px'
                    });
                    pickerColorPointers.eq(1).css('top', componentSize * (1 - pickerColor.value.h) + 'px');
                    pickerColorPointers.eq(2).css('top', componentSize * (1 - pickerColor.value.a) + 'px');
                    previewColor();
                }

                function getColorpickerTemplatePosition() {
                    var
                        positionValue,
                        positionOffset = Helper.getOffset(elem[0]);

                    if (angular.isDefined(attrs.colorpickerParent)) {
                        positionOffset.left = 0;
                        positionOffset.top = 0;
                    }

                    if (position === 'top') {
                        positionValue = {
                            'top': positionOffset.top - 147,
                            'left': positionOffset.left
                        };
                    } else if (position === 'right') {
                        positionValue = {
                            'top': positionOffset.top,
                            'left': positionOffset.left + 126
                        };
                    } else if (position === 'bottom') {
                        positionValue = {
                            'top': positionOffset.top + elem[0].offsetHeight + 2,
                            'left': positionOffset.left
                        };
                    } else if (position === 'left') {
                        positionValue = {
                            'top': positionOffset.top,
                            'left': positionOffset.left - 150
                        };
                    }
                    return {
                        'top': positionValue.top + 'px',
                        'left': positionValue.left + 'px'
                    };
                }

                function documentMousedownHandler() {
                    hideColorpickerTemplate();
                }

                function showColorpickerTemplate() {

                    if (!colorpickerTemplate.hasClass('colorpicker-visible')) {
                        update();
                        colorpickerTemplate
                          .addClass('colorpicker-visible')
                          .css(getColorpickerTemplatePosition());
                        emitEvent('colorpicker-shown');

                        if (inline === false) {
                            // register global mousedown event to hide the colorpicker
                            $document.on('mousedown', documentMousedownHandler);
                        }

                        if (attrs.colorpickerIsOpen) {
                            $scope[attrs.colorpickerIsOpen] = true;
                            if (!$scope.$$phase) {
                                $scope.$digest(); //trigger the watcher to fire
                            }
                        }
                    }
                }

                if (inline === false) {
                    elem.on('click', showColorpickerTemplate);
                } else {
                    showColorpickerTemplate();
                }

                colorpickerTemplate.on('mousedown', function (event) {
                    event.stopPropagation();
                    event.preventDefault();
                });

                function emitEvent(name) {
                    if (ngModel) {
                        $scope.$emit(name, {
                            name: attrs.ngModel,
                            value: ngModel.$modelValue
                        });
                    }
                }

                function hideColorpickerTemplate() {
                    if (colorpickerTemplate.hasClass('colorpicker-visible')) {
                        colorpickerTemplate.removeClass('colorpicker-visible');
                        emitEvent('colorpicker-closed');
                        // unregister the global mousedown event
                        $document.off('mousedown', documentMousedownHandler);

                        if (attrs.colorpickerIsOpen) {
                            $scope[attrs.colorpickerIsOpen] = false;
                            if (!$scope.$$phase) {
                                $scope.$digest(); //trigger the watcher to fire
                            }
                        }
                    }
                }

                colorpickerTemplate.find('button').on('click', function () {
                    hideColorpickerTemplate();
                });

                if (attrs.colorpickerIsOpen) {
                    $scope.$watch(attrs.colorpickerIsOpen, function (shouldBeOpen) {

                        if (shouldBeOpen === true) {
                            showColorpickerTemplate();
                        } else if (shouldBeOpen === false) {
                            hideColorpickerTemplate();
                        }

                    });
                }
            }
        };
    }]);
if (typeof module !== 'undefined' && typeof exports !== 'undefined' && module.exports === exports) {
    module.exports = 'highcharts-ng';
}
(function () {
    'use strict';
    /*global angular: false, Highcharts: false */

    angular.module('highcharts-ng', [])
      .factory('highchartsNGUtils', highchartsNGUtils)
      .directive('highchart', ['highchartsNGUtils', highchart]);

    function highchartsNGUtils() {

        return {

            //IE8 support
            indexOf: function (arr, find, i /*opt*/) {
                if (i === undefined) i = 0;
                if (i < 0) i += arr.length;
                if (i < 0) i = 0;
                for (var n = arr.length; i < n; i++)
                    if (i in arr && arr[i] === find)
                        return i;
                return -1;
            },

            prependMethod: function (obj, method, func) {
                var original = obj[method];
                obj[method] = function () {
                    var args = Array.prototype.slice.call(arguments);
                    func.apply(this, args);
                    if (original) {
                        return original.apply(this, args);
                    } else {
                        return;
                    }

                };
            },

            deepExtend: function deepExtend(destination, source) {
                //Slightly strange behaviour in edge cases (e.g. passing in non objects)
                //But does the job for current use cases.
                if (angular.isArray(source)) {
                    destination = angular.isArray(destination) ? destination : [];
                    for (var i = 0; i < source.length; i++) {
                        destination[i] = deepExtend(destination[i] || {}, source[i]);
                    }
                } else if (angular.isObject(source)) {
                    for (var property in source) {
                        destination[property] = deepExtend(destination[property] || {}, source[property]);
                    }
                } else {
                    destination = source;
                }
                return destination;
            }
        };
    }

    function highchart(highchartsNGUtils) {

        // acceptable shared state
        var seriesId = 0;
        var ensureIds = function (series) {
            var changed = false;
            angular.forEach(series, function (s) {
                if (!angular.isDefined(s.id)) {
                    s.id = 'series-' + seriesId++;
                    changed = true;
                }
            });
            return changed;
        };

        // immutable
        var axisNames = ['xAxis', 'yAxis'];

        var getMergedOptions = function (scope, element, config) {
            var mergedOptions = {};

            var defaultOptions = {
                chart: {
                    events: {}
                },
                title: {},
                subtitle: {},
                series: [],
                credits: {},
                plotOptions: {},
                navigator: { enabled: false }
            };

            if (config.options) {
                mergedOptions = highchartsNGUtils.deepExtend(defaultOptions, config.options);
            } else {
                mergedOptions = defaultOptions;
            }
            mergedOptions.chart.renderTo = element[0];

            angular.forEach(axisNames, function (axisName) {
                if (angular.isDefined(config[axisName])) {
                    mergedOptions[axisName] = angular.copy(config[axisName]);

                    if (angular.isDefined(config[axisName].currentMin) ||
                        angular.isDefined(config[axisName].currentMax)) {

                        highchartsNGUtils.prependMethod(mergedOptions.chart.events, 'selection', function (e) {
                            var thisChart = this;
                            if (e[axisName]) {
                                scope.$apply(function () {
                                    scope.config[axisName].currentMin = e[axisName][0].min;
                                    scope.config[axisName].currentMax = e[axisName][0].max;
                                });
                            } else {
                                //handle reset button - zoom out to all
                                scope.$apply(function () {
                                    scope.config[axisName].currentMin = thisChart[axisName][0].dataMin;
                                    scope.config[axisName].currentMax = thisChart[axisName][0].dataMax;
                                });
                            }
                        });

                        highchartsNGUtils.prependMethod(mergedOptions.chart.events, 'addSeries', function (e) {
                            scope.config[axisName].currentMin = this[axisName][0].min || scope.config[axisName].currentMin;
                            scope.config[axisName].currentMax = this[axisName][0].max || scope.config[axisName].currentMax;
                        });
                    }
                }
            });

            if (config.title) {
                mergedOptions.title = config.title;
            }
            if (config.subtitle) {
                mergedOptions.subtitle = config.subtitle;
            }
            if (config.credits) {
                mergedOptions.credits = config.credits;
            }
            if (config.size) {
                if (config.size.width) {
                    mergedOptions.chart.width = config.size.width;
                }
                if (config.size.height) {
                    mergedOptions.chart.height = config.size.height;
                }
            }
            return mergedOptions;
        };

        var updateZoom = function (axis, modelAxis) {
            var extremes = axis.getExtremes();
            if (modelAxis.currentMin !== extremes.dataMin || modelAxis.currentMax !== extremes.dataMax) {
                axis.setExtremes(modelAxis.currentMin, modelAxis.currentMax, false);
            }
        };

        var processExtremes = function (chart, axis, axisName) {
            if (axis.currentMin || axis.currentMax) {
                chart[axisName][0].setExtremes(axis.currentMin, axis.currentMax, true);
            }
        };

        var chartOptionsWithoutEasyOptions = function (options) {
            return angular.extend({}, options, { data: null, visible: null });
        };

        return {
            restrict: 'EAC',
            replace: true,
            template: '<div></div>',
            scope: {
                config: '=',
                disableDataWatch: '='
            },
            link: function (scope, element, attrs) {
                // We keep some chart-specific variables here as a closure
                // instead of storing them on 'scope'.

                // prevSeriesOptions is maintained by processSeries
                var prevSeriesOptions = {};

                var processSeries = function (series) {
                    var i;
                    var ids = [];

                    if (series) {
                        var setIds = ensureIds(series);
                        if (setIds) {
                            //If we have set some ids this will trigger another digest cycle.
                            //In this scenario just return early and let the next cycle take care of changes
                            return false;
                        }

                        //Find series to add or update
                        angular.forEach(series, function (s) {
                            ids.push(s.id);
                            var chartSeries = chart.get(s.id);
                            if (chartSeries) {
                                if (!angular.equals(prevSeriesOptions[s.id], chartOptionsWithoutEasyOptions(s))) {
                                    chartSeries.update(angular.copy(s), false);
                                } else {
                                    if (s.visible !== undefined && chartSeries.visible !== s.visible) {
                                        chartSeries.setVisible(s.visible, false);
                                    }
                                    chartSeries.setData(angular.copy(s.data), false);
                                }
                            } else {
                                chart.addSeries(angular.copy(s), false);
                            }
                            prevSeriesOptions[s.id] = chartOptionsWithoutEasyOptions(s);
                        });

                        //  Shows no data text if all series are empty
                        if (scope.config.noData) {
                            var chartContainsData = false;

                            for (i = 0; i < series.length; i++) {
                                if (series[i].data && series[i].data.length > 0) {
                                    chartContainsData = true;

                                    break;
                                }
                            }

                            if (!chartContainsData) {
                                chart.showLoading(scope.config.noData);
                            } else {
                                chart.hideLoading();
                            }
                        }
                    }

                    //Now remove any missing series
                    for (i = chart.series.length - 1; i >= 0; i--) {
                        var s = chart.series[i];
                        if (s.options.id !== 'highcharts-navigator-series' && highchartsNGUtils.indexOf(ids, s.options.id) < 0) {
                            s.remove(false);
                        }
                    }

                    return true;
                };

                // chart is maintained by initChart
                var chart = false;
                var initChart = function () {
                    if (chart) chart.destroy();
                    prevSeriesOptions = {};
                    var config = scope.config || {};
                    var mergedOptions = getMergedOptions(scope, element, config);
                    var func = config.func || undefined;
                    chart = config.useHighStocks ?
                      new Highcharts.StockChart(mergedOptions, func) :
                      new Highcharts.Chart(mergedOptions, func);
                    for (var i = 0; i < axisNames.length; i++) {
                        if (config[axisNames[i]]) {
                            processExtremes(chart, config[axisNames[i]], axisNames[i]);
                        }
                    }
                    if (config.loading) {
                        chart.showLoading();
                    }

                };
                initChart();


                if (scope.disableDataWatch) {
                    scope.$watchCollection('config.series', function (newSeries, oldSeries) {
                        processSeries(newSeries);
                        chart.redraw();
                    });
                } else {
                    scope.$watch('config.series', function (newSeries, oldSeries) {
                        var needsRedraw = processSeries(newSeries);
                        if (needsRedraw) {
                            chart.redraw();
                        }
                    }, true);
                }

                scope.$watch('config.title', function (newTitle) {
                    chart.setTitle(newTitle, true);
                }, true);

                scope.$watch('config.subtitle', function (newSubtitle) {
                    chart.setTitle(true, newSubtitle);
                }, true);

                scope.$watch('config.loading', function (loading) {
                    if (loading) {
                        chart.showLoading();
                    } else {
                        chart.hideLoading();
                    }
                });

                scope.$watch('config.credits.enabled', function (enabled) {
                    if (enabled) {
                        chart.credits.show();
                    } else if (chart.credits) {
                        chart.credits.hide();
                    }
                });

                scope.$watch('config.useHighStocks', function (useHighStocks, oldUseHighStocks) {
                    if (useHighStocks === oldUseHighStocks) return;
                    initChart();
                });

                angular.forEach(axisNames, function (axisName) {
                    scope.$watch('config.' + axisName, function (newAxes, oldAxes) {
                        if (newAxes === oldAxes) return;
                        if (newAxes) {
                            chart[axisName][0].update(newAxes, false);
                            updateZoom(chart[axisName][0], angular.copy(newAxes));
                            chart.redraw();
                        }
                    }, true);
                });
                scope.$watch('config.options', function (newOptions, oldOptions, scope) {
                    //do nothing when called on registration
                    if (newOptions === oldOptions) return;
                    initChart();
                    processSeries(scope.config.series);
                    chart.redraw();
                }, true);

                scope.$watch('config.size', function (newSize, oldSize) {
                    if (newSize === oldSize) return;
                    if (newSize) {
                        chart.setSize(newSize.width || undefined, newSize.height || undefined);
                    }
                }, true);

                scope.$on('highchartsng.reflow', function () {
                    chart.reflow();
                });

                scope.$on('$destroy', function () {
                    if (chart) {
                        chart.destroy();
                        setTimeout(function () {
                            element.remove();
                        }, 0);
                    }
                });

            }
        };
    }

}());
/*
 Highcharts JS v4.0.4 (2014-09-02)

 (c) 2009-2014 Torstein Honsi

 License: www.highcharts.com/license
*/
(function(){function r(a,b){var c;a||(a={});for(c in b)a[c]=b[c];return a}function w(){var a,b=arguments,c,d={},e=function(a,b){var c,d;typeof a!=="object"&&(a={});for(d in b)b.hasOwnProperty(d)&&(c=b[d],a[d]=c&&typeof c==="object"&&Object.prototype.toString.call(c)!=="[object Array]"&&d!=="renderTo"&&typeof c.nodeType!=="number"?e(a[d]||{},c):b[d]);return a};b[0]===!0&&(d=b[1],b=Array.prototype.slice.call(b,2));c=b.length;for(a=0;a<c;a++)d=e(d,b[a]);return d}function y(a,b){return parseInt(a,b||
10)}function Ga(a){return typeof a==="string"}function da(a){return a&&typeof a==="object"}function Ha(a){return Object.prototype.toString.call(a)==="[object Array]"}function ja(a){return typeof a==="number"}function za(a){return V.log(a)/V.LN10}function ka(a){return V.pow(10,a)}function la(a,b){for(var c=a.length;c--;)if(a[c]===b){a.splice(c,1);break}}function s(a){return a!==u&&a!==null}function F(a,b,c){var d,e;if(Ga(b))s(c)?a.setAttribute(b,c):a&&a.getAttribute&&(e=a.getAttribute(b));else if(s(b)&&
da(b))for(d in b)a.setAttribute(d,b[d]);return e}function ra(a){return Ha(a)?a:[a]}function p(){var a=arguments,b,c,d=a.length;for(b=0;b<d;b++)if(c=a[b],c!==u&&c!==null)return c}function B(a,b){if(Aa&&!ba&&b&&b.opacity!==u)b.filter="alpha(opacity="+b.opacity*100+")";r(a.style,b)}function $(a,b,c,d,e){a=x.createElement(a);b&&r(a,b);e&&B(a,{padding:0,border:P,margin:0});c&&B(a,c);d&&d.appendChild(a);return a}function ma(a,b){var c=function(){return u};c.prototype=new a;r(c.prototype,b);return c}function Ba(a,
b,c,d){var e=K.numberFormat,f=E.lang,g=+a||0,h=b===-1?(g.toString().split(".")[1]||"").length:isNaN(b=Q(b))?2:b,i=c===void 0?f.decimalPoint:c,f=d===void 0?f.thousandsSep:d,j=g<0?"-":"",k=String(y(g=Q(g).toFixed(h))),l=k.length>3?k.length%3:0;return e!==Ba?e(a,b,c,d):j+(l?k.substr(0,l)+f:"")+k.substr(l).replace(/(\d{3})(?=\d)/g,"$1"+f)+(h?i+Q(g-k).toFixed(h).slice(2):"")}function Ia(a,b){return Array((b||2)+1-String(a).length).join(0)+a}function Na(a,b,c){var d=a[b];a[b]=function(){var a=Array.prototype.slice.call(arguments);
a.unshift(d);return c.apply(this,a)}}function Ja(a,b){for(var c="{",d=!1,e,f,g,h,i,j=[];(c=a.indexOf(c))!==-1;){e=a.slice(0,c);if(d){f=e.split(":");g=f.shift().split(".");i=g.length;e=b;for(h=0;h<i;h++)e=e[g[h]];if(f.length)f=f.join(":"),g=/\.([0-9])/,h=E.lang,i=void 0,/f$/.test(f)?(i=(i=f.match(g))?i[1]:-1,e!==null&&(e=Ba(e,i,h.decimalPoint,f.indexOf(",")>-1?h.thousandsSep:""))):e=cb(f,e)}j.push(e);a=a.slice(c+1);c=(d=!d)?"}":"{"}j.push(a);return j.join("")}function mb(a){return V.pow(10,U(V.log(a)/
V.LN10))}function nb(a,b,c,d){var e,c=p(c,1);e=a/c;b||(b=[1,2,2.5,5,10],d===!1&&(c===1?b=[1,2,5,10]:c<=0.1&&(b=[1/c])));for(d=0;d<b.length;d++)if(a=b[d],e<=(b[d]+(b[d+1]||b[d]))/2)break;a*=c;return a}function ob(a,b){var c=a.length,d,e;for(e=0;e<c;e++)a[e].ss_i=e;a.sort(function(a,c){d=b(a,c);return d===0?a.ss_i-c.ss_i:d});for(e=0;e<c;e++)delete a[e].ss_i}function Oa(a){for(var b=a.length,c=a[0];b--;)a[b]<c&&(c=a[b]);return c}function Ca(a){for(var b=a.length,c=a[0];b--;)a[b]>c&&(c=a[b]);return c}
function Pa(a,b){for(var c in a)a[c]&&a[c]!==b&&a[c].destroy&&a[c].destroy(),delete a[c]}function Qa(a){db||(db=$(Ka));a&&db.appendChild(a);db.innerHTML=""}function ea(a){return parseFloat(a.toPrecision(14))}function Ra(a,b){va=p(a,b.animation)}function Bb(){var a=E.global.useUTC,b=a?"getUTC":"get",c=a?"setUTC":"set";Da=E.global.Date||window.Date;Sa=(a&&E.global.timezoneOffset||0)*6E4;eb=a?Da.UTC:function(a,b,c,g,h,i){return(new Da(a,b,p(c,1),p(g,0),p(h,0),p(i,0))).getTime()};pb=b+"Minutes";qb=b+
"Hours";rb=b+"Day";Xa=b+"Date";fb=b+"Month";gb=b+"FullYear";Cb=c+"Minutes";Db=c+"Hours";sb=c+"Date";Eb=c+"Month";Fb=c+"FullYear"}function S(){}function Ta(a,b,c,d){this.axis=a;this.pos=b;this.type=c||"";this.isNew=!0;!c&&!d&&this.addLabel()}function na(){this.init.apply(this,arguments)}function Ya(){this.init.apply(this,arguments)}function Gb(a,b,c,d,e){var f=a.chart.inverted;this.axis=a;this.isNegative=c;this.options=b;this.x=d;this.total=null;this.points={};this.stack=e;this.alignOptions={align:b.align||
(f?c?"left":"right":"center"),verticalAlign:b.verticalAlign||(f?"middle":c?"bottom":"top"),y:p(b.y,f?4:c?14:-6),x:p(b.x,f?c?-6:6:0)};this.textAlign=b.textAlign||(f?c?"right":"left":"center")}var u,x=document,G=window,V=Math,v=V.round,U=V.floor,La=V.ceil,t=V.max,L=V.min,Q=V.abs,aa=V.cos,fa=V.sin,oa=V.PI,Ea=oa*2/360,wa=navigator.userAgent,Hb=G.opera,Aa=/msie/i.test(wa)&&!Hb,hb=x.documentMode===8,tb=/AppleWebKit/.test(wa),Ua=/Firefox/.test(wa),Ib=/(Mobile|Android|Windows Phone)/.test(wa),xa="http://www.w3.org/2000/svg",
ba=!!x.createElementNS&&!!x.createElementNS(xa,"svg").createSVGRect,Ob=Ua&&parseInt(wa.split("Firefox/")[1],10)<4,ga=!ba&&!Aa&&!!x.createElement("canvas").getContext,Za,$a,Jb={},ub=0,db,E,cb,va,vb,A,ha,sa=function(){return u},W=[],ab=0,Ka="div",P="none",Pb=/^[0-9]+$/,Qb="stroke-width",Da,eb,Sa,pb,qb,rb,Xa,fb,gb,Cb,Db,sb,Eb,Fb,H={},K;G.Highcharts?ha(16,!0):K=G.Highcharts={};cb=function(a,b,c){if(!s(b)||isNaN(b))return"Invalid date";var a=p(a,"%Y-%m-%d %H:%M:%S"),d=new Da(b-Sa),e,f=d[qb](),g=d[rb](),
h=d[Xa](),i=d[fb](),j=d[gb](),k=E.lang,l=k.weekdays,d=r({a:l[g].substr(0,3),A:l[g],d:Ia(h),e:h,b:k.shortMonths[i],B:k.months[i],m:Ia(i+1),y:j.toString().substr(2,2),Y:j,H:Ia(f),I:Ia(f%12||12),l:f%12||12,M:Ia(d[pb]()),p:f<12?"AM":"PM",P:f<12?"am":"pm",S:Ia(d.getSeconds()),L:Ia(v(b%1E3),3)},K.dateFormats);for(e in d)for(;a.indexOf("%"+e)!==-1;)a=a.replace("%"+e,typeof d[e]==="function"?d[e](b):d[e]);return c?a.substr(0,1).toUpperCase()+a.substr(1):a};ha=function(a,b){var c="Highcharts error #"+a+": www.highcharts.com/errors/"+
a;if(b)throw c;G.console&&console.log(c)};A={millisecond:1,second:1E3,minute:6E4,hour:36E5,day:864E5,week:6048E5,month:26784E5,year:31556952E3};vb={init:function(a,b,c){var b=b||"",d=a.shift,e=b.indexOf("C")>-1,f=e?7:3,g,b=b.split(" "),c=[].concat(c),h,i,j=function(a){for(g=a.length;g--;)a[g]==="M"&&a.splice(g+1,0,a[g+1],a[g+2],a[g+1],a[g+2])};e&&(j(b),j(c));a.isArea&&(h=b.splice(b.length-6,6),i=c.splice(c.length-6,6));if(d<=c.length/f&&b.length===c.length)for(;d--;)c=[].concat(c).splice(0,f).concat(c);
a.shift=0;if(b.length)for(a=c.length;b.length<a;)d=[].concat(b).splice(b.length-f,f),e&&(d[f-6]=d[f-2],d[f-5]=d[f-1]),b=b.concat(d);h&&(b=b.concat(h),c=c.concat(i));return[b,c]},step:function(a,b,c,d){var e=[],f=a.length;if(c===1)e=d;else if(f===b.length&&c<1)for(;f--;)d=parseFloat(a[f]),e[f]=isNaN(d)?a[f]:c*parseFloat(b[f]-d)+d;else e=b;return e}};(function(a){G.HighchartsAdapter=G.HighchartsAdapter||a&&{init:function(b){var c=a.fx;a.extend(a.easing,{easeOutQuad:function(a,b,c,g,h){return-g*(b/=
h)*(b-2)+c}});a.each(["cur","_default","width","height","opacity"],function(b,e){var f=c.step,g;e==="cur"?f=c.prototype:e==="_default"&&a.Tween&&(f=a.Tween.propHooks[e],e="set");(g=f[e])&&(f[e]=function(a){var c,a=b?a:this;if(a.prop!=="align")return c=a.elem,c.attr?c.attr(a.prop,e==="cur"?u:a.now):g.apply(this,arguments)})});Na(a.cssHooks.opacity,"get",function(a,b,c){return b.attr?b.opacity||0:a.call(this,b,c)});this.addAnimSetter("d",function(a){var c=a.elem,f;if(!a.started)f=b.init(c,c.d,c.toD),
a.start=f[0],a.end=f[1],a.started=!0;c.attr("d",b.step(a.start,a.end,a.pos,c.toD))});this.each=Array.prototype.forEach?function(a,b){return Array.prototype.forEach.call(a,b)}:function(a,b){var c,g=a.length;for(c=0;c<g;c++)if(b.call(a[c],a[c],c,a)===!1)return c};a.fn.highcharts=function(){var a="Chart",b=arguments,c,g;if(this[0]){Ga(b[0])&&(a=b[0],b=Array.prototype.slice.call(b,1));c=b[0];if(c!==u)c.chart=c.chart||{},c.chart.renderTo=this[0],new K[a](c,b[1]),g=this;c===u&&(g=W[F(this[0],"data-highcharts-chart")])}return g}},
addAnimSetter:function(b,c){a.Tween?a.Tween.propHooks[b]={set:c}:a.fx.step[b]=c},getScript:a.getScript,inArray:a.inArray,adapterRun:function(b,c){return a(b)[c]()},grep:a.grep,map:function(a,c){for(var d=[],e=0,f=a.length;e<f;e++)d[e]=c.call(a[e],a[e],e,a);return d},offset:function(b){return a(b).offset()},addEvent:function(b,c,d){a(b).bind(c,d)},removeEvent:function(b,c,d){var e=x.removeEventListener?"removeEventListener":"detachEvent";x[e]&&b&&!b[e]&&(b[e]=function(){});a(b).unbind(c,d)},fireEvent:function(b,
c,d,e){var f=a.Event(c),g="detached"+c,h;!Aa&&d&&(delete d.layerX,delete d.layerY,delete d.returnValue);r(f,d);b[c]&&(b[g]=b[c],b[c]=null);a.each(["preventDefault","stopPropagation"],function(a,b){var c=f[b];f[b]=function(){try{c.call(f)}catch(a){b==="preventDefault"&&(h=!0)}}});a(b).trigger(f);b[g]&&(b[c]=b[g],b[g]=null);e&&!f.isDefaultPrevented()&&!h&&e(f)},washMouseEvent:function(a){var c=a.originalEvent||a;if(c.pageX===u)c.pageX=a.pageX,c.pageY=a.pageY;return c},animate:function(b,c,d){var e=
a(b);if(!b.style)b.style={};if(c.d)b.toD=c.d,c.d=1;e.stop();c.opacity!==u&&b.attr&&(c.opacity+="px");b.hasAnim=1;e.animate(c,d)},stop:function(b){b.hasAnim&&a(b).stop()}}})(G.jQuery);var T=G.HighchartsAdapter,M=T||{};T&&T.init.call(T,vb);var ib=M.adapterRun,Rb=M.getScript,Ma=M.inArray,q=M.each,wb=M.grep,Sb=M.offset,Va=M.map,N=M.addEvent,X=M.removeEvent,I=M.fireEvent,Tb=M.washMouseEvent,jb=M.animate,bb=M.stop,M={enabled:!0,x:0,y:15,style:{color:"#606060",cursor:"default",fontSize:"11px"}};E={colors:"#7cb5ec,#434348,#90ed7d,#f7a35c,#8085e9,#f15c80,#e4d354,#8085e8,#8d4653,#91e8e1".split(","),
symbols:["circle","diamond","square","triangle","triangle-down"],lang:{loading:"Loading...",months:"January,February,March,April,May,June,July,August,September,October,November,December".split(","),shortMonths:"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec".split(","),weekdays:"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday".split(","),decimalPoint:".",numericSymbols:"k,M,G,T,P,E".split(","),resetZoom:"Reset zoom",resetZoomTitle:"Reset zoom level 1:1",thousandsSep:","},global:{useUTC:!0,
canvasToolsURL:"http://code.highcharts.com/4.0.4/modules/canvas-tools.js",VMLRadialGradientURL:"http://code.highcharts.com/4.0.4/gfx/vml-radial-gradient.png"},chart:{borderColor:"#4572A7",borderRadius:0,defaultSeriesType:"line",ignoreHiddenSeries:!0,spacing:[10,10,15,10],backgroundColor:"#FFFFFF",plotBorderColor:"#C0C0C0",resetZoomButton:{theme:{zIndex:20},position:{align:"right",x:-10,y:10}}},title:{text:"Chart title",align:"center",margin:15,style:{color:"#333333",fontSize:"18px"}},subtitle:{text:"",
align:"center",style:{color:"#555555"}},plotOptions:{line:{allowPointSelect:!1,showCheckbox:!1,animation:{duration:1E3},events:{},lineWidth:2,marker:{lineWidth:0,radius:4,lineColor:"#FFFFFF",states:{hover:{enabled:!0,lineWidthPlus:1,radiusPlus:2},select:{fillColor:"#FFFFFF",lineColor:"#000000",lineWidth:2}}},point:{events:{}},dataLabels:w(M,{align:"center",enabled:!1,formatter:function(){return this.y===null?"":Ba(this.y,-1)},verticalAlign:"bottom",y:0}),cropThreshold:300,pointRange:0,states:{hover:{lineWidthPlus:1,
marker:{},halo:{size:10,opacity:0.25}},select:{marker:{}}},stickyTracking:!0,turboThreshold:1E3}},labels:{style:{position:"absolute",color:"#3E576F"}},legend:{enabled:!0,align:"center",layout:"horizontal",labelFormatter:function(){return this.name},borderColor:"#909090",borderRadius:0,navigation:{activeColor:"#274b6d",inactiveColor:"#CCC"},shadow:!1,itemStyle:{color:"#333333",fontSize:"12px",fontWeight:"bold"},itemHoverStyle:{color:"#000"},itemHiddenStyle:{color:"#CCC"},itemCheckboxStyle:{position:"absolute",
width:"13px",height:"13px"},symbolPadding:5,verticalAlign:"bottom",x:0,y:0,title:{style:{fontWeight:"bold"}}},loading:{labelStyle:{fontWeight:"bold",position:"relative",top:"45%"},style:{position:"absolute",backgroundColor:"white",opacity:0.5,textAlign:"center"}},tooltip:{enabled:!0,animation:ba,backgroundColor:"rgba(249, 249, 249, .85)",borderWidth:1,borderRadius:3,dateTimeLabelFormats:{millisecond:"%A, %b %e, %H:%M:%S.%L",second:"%A, %b %e, %H:%M:%S",minute:"%A, %b %e, %H:%M",hour:"%A, %b %e, %H:%M",
day:"%A, %b %e, %Y",week:"Week from %A, %b %e, %Y",month:"%B %Y",year:"%Y"},headerFormat:'<span style="font-size: 10px">{point.key}</span><br/>',pointFormat:'<span style="color:{series.color}">â—</span> {series.name}: <b>{point.y}</b><br/>',shadow:!0,snap:Ib?25:10,style:{color:"#333333",cursor:"default",fontSize:"12px",padding:"8px",whiteSpace:"nowrap"}},credits:{enabled:!0,text:"Highcharts.com",href:"http://www.highcharts.com",position:{align:"right",x:-10,verticalAlign:"bottom",y:-5},style:{cursor:"pointer",
color:"#909090",fontSize:"9px"}}};var ca=E.plotOptions,T=ca.line;Bb();var Ub=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/,Vb=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/,Wb=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/,ya=function(a){var b=[],c,d;(function(a){a&&a.stops?d=Va(a.stops,function(a){return ya(a[1])}):(c=Ub.exec(a))?b=[y(c[1]),y(c[2]),y(c[3]),parseFloat(c[4],10)]:(c=Vb.exec(a))?b=[y(c[1],16),y(c[2],16),y(c[3],
16),1]:(c=Wb.exec(a))&&(b=[y(c[1]),y(c[2]),y(c[3]),1])})(a);return{get:function(c){var f;d?(f=w(a),f.stops=[].concat(f.stops),q(d,function(a,b){f.stops[b]=[f.stops[b][0],a.get(c)]})):f=b&&!isNaN(b[0])?c==="rgb"?"rgb("+b[0]+","+b[1]+","+b[2]+")":c==="a"?b[3]:"rgba("+b.join(",")+")":a;return f},brighten:function(a){if(d)q(d,function(b){b.brighten(a)});else if(ja(a)&&a!==0){var c;for(c=0;c<3;c++)b[c]+=y(a*255),b[c]<0&&(b[c]=0),b[c]>255&&(b[c]=255)}return this},rgba:b,setOpacity:function(a){b[3]=a;return this}}};
S.prototype={opacity:1,textProps:"fontSize,fontWeight,fontFamily,color,lineHeight,width,textDecoration,textShadow,HcTextStroke".split(","),init:function(a,b){this.element=b==="span"?$(b):x.createElementNS(xa,b);this.renderer=a},animate:function(a,b,c){b=p(b,va,!0);bb(this);if(b){b=w(b,{});if(c)b.complete=c;jb(this,a,b)}else this.attr(a),c&&c();return this},colorGradient:function(a,b,c){var d=this.renderer,e,f,g,h,i,j,k,l,n,m,o=[];a.linearGradient?f="linearGradient":a.radialGradient&&(f="radialGradient");
if(f){g=a[f];h=d.gradients;j=a.stops;n=c.radialReference;Ha(g)&&(a[f]=g={x1:g[0],y1:g[1],x2:g[2],y2:g[3],gradientUnits:"userSpaceOnUse"});f==="radialGradient"&&n&&!s(g.gradientUnits)&&(g=w(g,{cx:n[0]-n[2]/2+g.cx*n[2],cy:n[1]-n[2]/2+g.cy*n[2],r:g.r*n[2],gradientUnits:"userSpaceOnUse"}));for(m in g)m!=="id"&&o.push(m,g[m]);for(m in j)o.push(j[m]);o=o.join(",");h[o]?a=h[o].attr("id"):(g.id=a="highcharts-"+ub++,h[o]=i=d.createElement(f).attr(g).add(d.defs),i.stops=[],q(j,function(a){a[1].indexOf("rgba")===
0?(e=ya(a[1]),k=e.get("rgb"),l=e.get("a")):(k=a[1],l=1);a=d.createElement("stop").attr({offset:a[0],"stop-color":k,"stop-opacity":l}).add(i);i.stops.push(a)}));c.setAttribute(b,"url("+d.url+"#"+a+")")}},attr:function(a,b){var c,d,e=this.element,f,g=this,h;typeof a==="string"&&b!==u&&(c=a,a={},a[c]=b);if(typeof a==="string")g=(this[a+"Getter"]||this._defaultGetter).call(this,a,e);else{for(c in a){d=a[c];h=!1;this.symbolName&&/^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)/.test(c)&&(f||(this.symbolAttr(a),
f=!0),h=!0);if(this.rotation&&(c==="x"||c==="y"))this.doTransform=!0;h||(this[c+"Setter"]||this._defaultSetter).call(this,d,c,e);this.shadows&&/^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test(c)&&this.updateShadows(c,d)}if(this.doTransform)this.updateTransform(),this.doTransform=!1}return g},updateShadows:function(a,b){for(var c=this.shadows,d=c.length;d--;)c[d].setAttribute(a,a==="height"?t(b-(c[d].cutHeight||0),0):a==="d"?this.d:b)},addClass:function(a){var b=this.element,c=F(b,"class")||
"";c.indexOf(a)===-1&&F(b,"class",c+" "+a);return this},symbolAttr:function(a){var b=this;q("x,y,r,start,end,width,height,innerR,anchorX,anchorY".split(","),function(c){b[c]=p(a[c],b[c])});b.attr({d:b.renderer.symbols[b.symbolName](b.x,b.y,b.width,b.height,b)})},clip:function(a){return this.attr("clip-path",a?"url("+this.renderer.url+"#"+a.id+")":P)},crisp:function(a){var b,c={},d,e=a.strokeWidth||this.strokeWidth||0;d=v(e)%2/2;a.x=U(a.x||this.x||0)+d;a.y=U(a.y||this.y||0)+d;a.width=U((a.width||this.width||
0)-2*d);a.height=U((a.height||this.height||0)-2*d);a.strokeWidth=e;for(b in a)this[b]!==a[b]&&(this[b]=c[b]=a[b]);return c},css:function(a){var b=this.styles,c={},d=this.element,e,f,g="";e=!b;if(a&&a.color)a.fill=a.color;if(b)for(f in a)a[f]!==b[f]&&(c[f]=a[f],e=!0);if(e){e=this.textWidth=a&&a.width&&d.nodeName.toLowerCase()==="text"&&y(a.width);b&&(a=r(b,c));this.styles=a;e&&(ga||!ba&&this.renderer.forExport)&&delete a.width;if(Aa&&!ba)B(this.element,a);else{b=function(a,b){return"-"+b.toLowerCase()};
for(f in a)g+=f.replace(/([A-Z])/g,b)+":"+a[f]+";";F(d,"style",g)}e&&this.added&&this.renderer.buildText(this)}return this},on:function(a,b){var c=this,d=c.element;$a&&a==="click"?(d.ontouchstart=function(a){c.touchEventFired=Da.now();a.preventDefault();b.call(d,a)},d.onclick=function(a){(wa.indexOf("Android")===-1||Da.now()-(c.touchEventFired||0)>1100)&&b.call(d,a)}):d["on"+a]=b;return this},setRadialReference:function(a){this.element.radialReference=a;return this},translate:function(a,b){return this.attr({translateX:a,
translateY:b})},invert:function(){this.inverted=!0;this.updateTransform();return this},updateTransform:function(){var a=this.translateX||0,b=this.translateY||0,c=this.scaleX,d=this.scaleY,e=this.inverted,f=this.rotation,g=this.element;e&&(a+=this.attr("width"),b+=this.attr("height"));a=["translate("+a+","+b+")"];e?a.push("rotate(90) scale(-1,1)"):f&&a.push("rotate("+f+" "+(g.getAttribute("x")||0)+" "+(g.getAttribute("y")||0)+")");(s(c)||s(d))&&a.push("scale("+p(c,1)+" "+p(d,1)+")");a.length&&g.setAttribute("transform",
a.join(" "))},toFront:function(){var a=this.element;a.parentNode.appendChild(a);return this},align:function(a,b,c){var d,e,f,g,h={};e=this.renderer;f=e.alignedObjects;if(a){if(this.alignOptions=a,this.alignByTranslate=b,!c||Ga(c))this.alignTo=d=c||"renderer",la(f,this),f.push(this),c=null}else a=this.alignOptions,b=this.alignByTranslate,d=this.alignTo;c=p(c,e[d],e);d=a.align;e=a.verticalAlign;f=(c.x||0)+(a.x||0);g=(c.y||0)+(a.y||0);if(d==="right"||d==="center")f+=(c.width-(a.width||0))/{right:1,center:2}[d];
h[b?"translateX":"x"]=v(f);if(e==="bottom"||e==="middle")g+=(c.height-(a.height||0))/({bottom:1,middle:2}[e]||1);h[b?"translateY":"y"]=v(g);this[this.placed?"animate":"attr"](h);this.placed=!0;this.alignAttr=h;return this},getBBox:function(){var a=this.bBox,b=this.renderer,c,d,e=this.rotation;c=this.element;var f=this.styles,g=e*Ea;d=this.textStr;var h;if(d===""||Pb.test(d))h="num."+d.toString().length+(f?"|"+f.fontSize+"|"+f.fontFamily:"");h&&(a=b.cache[h]);if(!a){if(c.namespaceURI===xa||b.forExport){try{a=
c.getBBox?r({},c.getBBox()):{width:c.offsetWidth,height:c.offsetHeight}}catch(i){}if(!a||a.width<0)a={width:0,height:0}}else a=this.htmlGetBBox();if(b.isSVG){c=a.width;d=a.height;if(Aa&&f&&f.fontSize==="11px"&&d.toPrecision(3)==="16.9")a.height=d=14;if(e)a.width=Q(d*fa(g))+Q(c*aa(g)),a.height=Q(d*aa(g))+Q(c*fa(g))}this.bBox=a;h&&(b.cache[h]=a)}return a},show:function(a){a&&this.element.namespaceURI===xa?this.element.removeAttribute("visibility"):this.attr({visibility:a?"inherit":"visible"});return this},
hide:function(){return this.attr({visibility:"hidden"})},fadeOut:function(a){var b=this;b.animate({opacity:0},{duration:a||150,complete:function(){b.attr({y:-9999})}})},add:function(a){var b=this.renderer,c=a||b,d=c.element||b.box,e=this.element,f=this.zIndex,g,h;if(a)this.parentGroup=a;this.parentInverted=a&&a.inverted;this.textStr!==void 0&&b.buildText(this);if(f)c.handleZ=!0,f=y(f);if(c.handleZ){a=d.childNodes;for(g=0;g<a.length;g++)if(b=a[g],c=F(b,"zIndex"),b!==e&&(y(c)>f||!s(f)&&s(c))){d.insertBefore(e,
b);h=!0;break}}h||d.appendChild(e);this.added=!0;if(this.onAdd)this.onAdd();return this},safeRemoveChild:function(a){var b=a.parentNode;b&&b.removeChild(a)},destroy:function(){var a=this,b=a.element||{},c=a.shadows,d=a.renderer.isSVG&&b.nodeName==="SPAN"&&a.parentGroup,e,f;b.onclick=b.onmouseout=b.onmouseover=b.onmousemove=b.point=null;bb(a);if(a.clipPath)a.clipPath=a.clipPath.destroy();if(a.stops){for(f=0;f<a.stops.length;f++)a.stops[f]=a.stops[f].destroy();a.stops=null}a.safeRemoveChild(b);for(c&&
q(c,function(b){a.safeRemoveChild(b)});d&&d.div&&d.div.childNodes.length===0;)b=d.parentGroup,a.safeRemoveChild(d.div),delete d.div,d=b;a.alignTo&&la(a.renderer.alignedObjects,a);for(e in a)delete a[e];return null},shadow:function(a,b,c){var d=[],e,f,g=this.element,h,i,j,k;if(a){i=p(a.width,3);j=(a.opacity||0.15)/i;k=this.parentInverted?"(-1,-1)":"("+p(a.offsetX,1)+", "+p(a.offsetY,1)+")";for(e=1;e<=i;e++){f=g.cloneNode(0);h=i*2+1-2*e;F(f,{isShadow:"true",stroke:a.color||"black","stroke-opacity":j*
e,"stroke-width":h,transform:"translate"+k,fill:P});if(c)F(f,"height",t(F(f,"height")-h,0)),f.cutHeight=h;b?b.element.appendChild(f):g.parentNode.insertBefore(f,g);d.push(f)}this.shadows=d}return this},xGetter:function(a){this.element.nodeName==="circle"&&(a={x:"cx",y:"cy"}[a]||a);return this._defaultGetter(a)},_defaultGetter:function(a){a=p(this[a],this.element?this.element.getAttribute(a):null,0);/^[\-0-9\.]+$/.test(a)&&(a=parseFloat(a));return a},dSetter:function(a,b,c){a&&a.join&&(a=a.join(" "));
/(NaN| {2}|^$)/.test(a)&&(a="M 0 0");c.setAttribute(b,a);this[b]=a},dashstyleSetter:function(a){var b;if(a=a&&a.toLowerCase()){a=a.replace("shortdashdotdot","3,1,1,1,1,1,").replace("shortdashdot","3,1,1,1").replace("shortdot","1,1,").replace("shortdash","3,1,").replace("longdash","8,3,").replace(/dot/g,"1,3,").replace("dash","4,3,").replace(/,$/,"").split(",");for(b=a.length;b--;)a[b]=y(a[b])*this["stroke-width"];a=a.join(",").replace("NaN","none");this.element.setAttribute("stroke-dasharray",a)}},
alignSetter:function(a){this.element.setAttribute("text-anchor",{left:"start",center:"middle",right:"end"}[a])},opacitySetter:function(a,b,c){this[b]=a;c.setAttribute(b,a)},titleSetter:function(a){var b=this.element.getElementsByTagName("title")[0];b||(b=x.createElementNS(xa,"title"),this.element.appendChild(b));b.textContent=p(a,"").replace(/<[^>]*>/g,"")},textSetter:function(a){if(a!==this.textStr)delete this.bBox,this.textStr=a,this.added&&this.renderer.buildText(this)},fillSetter:function(a,b,
c){typeof a==="string"?c.setAttribute(b,a):a&&this.colorGradient(a,b,c)},zIndexSetter:function(a,b,c){c.setAttribute(b,a);this[b]=a},_defaultSetter:function(a,b,c){c.setAttribute(b,a)}};S.prototype.yGetter=S.prototype.xGetter;S.prototype.translateXSetter=S.prototype.translateYSetter=S.prototype.rotationSetter=S.prototype.verticalAlignSetter=S.prototype.scaleXSetter=S.prototype.scaleYSetter=function(a,b){this[b]=a;this.doTransform=!0};S.prototype["stroke-widthSetter"]=S.prototype.strokeSetter=function(a,
b,c){this[b]=a;if(this.stroke&&this["stroke-width"])this.strokeWidth=this["stroke-width"],S.prototype.fillSetter.call(this,this.stroke,"stroke",c),c.setAttribute("stroke-width",this["stroke-width"]),this.hasStroke=!0;else if(b==="stroke-width"&&a===0&&this.hasStroke)c.removeAttribute("stroke"),this.hasStroke=!1};var ta=function(){this.init.apply(this,arguments)};ta.prototype={Element:S,init:function(a,b,c,d,e){var f=location,g,d=this.createElement("svg").attr({version:"1.1"}).css(this.getStyle(d));
g=d.element;a.appendChild(g);a.innerHTML.indexOf("xmlns")===-1&&F(g,"xmlns",xa);this.isSVG=!0;this.box=g;this.boxWrapper=d;this.alignedObjects=[];this.url=(Ua||tb)&&x.getElementsByTagName("base").length?f.href.replace(/#.*?$/,"").replace(/([\('\)])/g,"\\$1").replace(/ /g,"%20"):"";this.createElement("desc").add().element.appendChild(x.createTextNode("Created with Highcharts 4.0.4"));this.defs=this.createElement("defs").add();this.forExport=e;this.gradients={};this.cache={};this.setSize(b,c,!1);var h;
if(Ua&&a.getBoundingClientRect)this.subPixelFix=b=function(){B(a,{left:0,top:0});h=a.getBoundingClientRect();B(a,{left:La(h.left)-h.left+"px",top:La(h.top)-h.top+"px"})},b(),N(G,"resize",b)},getStyle:function(a){return this.style=r({fontFamily:'"Lucida Grande", "Lucida Sans Unicode", Arial, Helvetica, sans-serif',fontSize:"12px"},a)},isHidden:function(){return!this.boxWrapper.getBBox().width},destroy:function(){var a=this.defs;this.box=null;this.boxWrapper=this.boxWrapper.destroy();Pa(this.gradients||
{});this.gradients=null;if(a)this.defs=a.destroy();this.subPixelFix&&X(G,"resize",this.subPixelFix);return this.alignedObjects=null},createElement:function(a){var b=new this.Element;b.init(this,a);return b},draw:function(){},buildText:function(a){for(var b=a.element,c=this,d=c.forExport,e=p(a.textStr,"").toString(),f=e.indexOf("<")!==-1,g=b.childNodes,h,i,j=F(b,"x"),k=a.styles,l=a.textWidth,n=k&&k.lineHeight,m=k&&k.HcTextStroke,o=g.length,Y=function(a){return n?y(n):c.fontMetrics(/(px|em)$/.test(a&&
a.style.fontSize)?a.style.fontSize:k&&k.fontSize||c.style.fontSize||12,a).h};o--;)b.removeChild(g[o]);!f&&!m&&e.indexOf(" ")===-1?b.appendChild(x.createTextNode(e)):(h=/<.*style="([^"]+)".*>/,i=/<.*href="(http[^"]+)".*>/,l&&!a.added&&this.box.appendChild(b),e=f?e.replace(/<(b|strong)>/g,'<span style="font-weight:bold">').replace(/<(i|em)>/g,'<span style="font-style:italic">').replace(/<a/g,"<span").replace(/<\/(b|strong|i|em|a)>/g,"</span>").split(/<br.*?>/g):[e],e[e.length-1]===""&&e.pop(),q(e,function(e,
f){var g,n=0,e=e.replace(/<span/g,"|||<span").replace(/<\/span>/g,"</span>|||");g=e.split("|||");q(g,function(e){if(e!==""||g.length===1){var m={},o=x.createElementNS(xa,"tspan"),p;h.test(e)&&(p=e.match(h)[1].replace(/(;| |^)color([ :])/,"$1fill$2"),F(o,"style",p));i.test(e)&&!d&&(F(o,"onclick",'location.href="'+e.match(i)[1]+'"'),B(o,{cursor:"pointer"}));e=(e.replace(/<(.|\n)*?>/g,"")||" ").replace(/&lt;/g,"<").replace(/&gt;/g,">");if(e!==" "){o.appendChild(x.createTextNode(e));if(n)m.dx=0;else if(f&&
j!==null)m.x=j;F(o,m);b.appendChild(o);!n&&f&&(!ba&&d&&B(o,{display:"block"}),F(o,"dy",Y(o)));if(l)for(var e=e.replace(/([^\^])-/g,"$1- ").split(" "),m=g.length>1||e.length>1&&k.whiteSpace!=="nowrap",q,D,s=k.HcHeight,t=[],u=Y(o),Lb=1;m&&(e.length||t.length);)delete a.bBox,q=a.getBBox(),D=q.width,!ba&&c.forExport&&(D=c.measureSpanWidth(o.firstChild.data,a.styles)),q=D>l,!q||e.length===1?(e=t,t=[],e.length&&(Lb++,s&&Lb*u>s?(e=["..."],a.attr("title",a.textStr)):(o=x.createElementNS(xa,"tspan"),F(o,{dy:u,
x:j}),p&&F(o,"style",p),b.appendChild(o))),D>l&&(l=D)):(o.removeChild(o.firstChild),t.unshift(e.pop())),e.length&&o.appendChild(x.createTextNode(e.join(" ").replace(/- /g,"-")));n++}}})}))},button:function(a,b,c,d,e,f,g,h,i){var j=this.label(a,b,c,i,null,null,null,null,"button"),k=0,l,n,m,o,p,q,a={x1:0,y1:0,x2:0,y2:1},e=w({"stroke-width":1,stroke:"#CCCCCC",fill:{linearGradient:a,stops:[[0,"#FEFEFE"],[1,"#F6F6F6"]]},r:2,padding:5,style:{color:"black"}},e);m=e.style;delete e.style;f=w(e,{stroke:"#68A",
fill:{linearGradient:a,stops:[[0,"#FFF"],[1,"#ACF"]]}},f);o=f.style;delete f.style;g=w(e,{stroke:"#68A",fill:{linearGradient:a,stops:[[0,"#9BD"],[1,"#CDF"]]}},g);p=g.style;delete g.style;h=w(e,{style:{color:"#CCC"}},h);q=h.style;delete h.style;N(j.element,Aa?"mouseover":"mouseenter",function(){k!==3&&j.attr(f).css(o)});N(j.element,Aa?"mouseout":"mouseleave",function(){k!==3&&(l=[e,f,g][k],n=[m,o,p][k],j.attr(l).css(n))});j.setState=function(a){(j.state=k=a)?a===2?j.attr(g).css(p):a===3&&j.attr(h).css(q):
j.attr(e).css(m)};return j.on("click",function(){k!==3&&d.call(j)}).attr(e).css(r({cursor:"default"},m))},crispLine:function(a,b){a[1]===a[4]&&(a[1]=a[4]=v(a[1])-b%2/2);a[2]===a[5]&&(a[2]=a[5]=v(a[2])+b%2/2);return a},path:function(a){var b={fill:P};Ha(a)?b.d=a:da(a)&&r(b,a);return this.createElement("path").attr(b)},circle:function(a,b,c){a=da(a)?a:{x:a,y:b,r:c};b=this.createElement("circle");b.xSetter=function(a){this.element.setAttribute("cx",a)};b.ySetter=function(a){this.element.setAttribute("cy",
a)};return b.attr(a)},arc:function(a,b,c,d,e,f){if(da(a))b=a.y,c=a.r,d=a.innerR,e=a.start,f=a.end,a=a.x;a=this.symbol("arc",a||0,b||0,c||0,c||0,{innerR:d||0,start:e||0,end:f||0});a.r=c;return a},rect:function(a,b,c,d,e,f){var e=da(a)?a.r:e,g=this.createElement("rect"),a=da(a)?a:a===u?{}:{x:a,y:b,width:t(c,0),height:t(d,0)};if(f!==u)a.strokeWidth=f,a=g.crisp(a);if(e)a.r=e;g.rSetter=function(a){F(this.element,{rx:a,ry:a})};return g.attr(a)},setSize:function(a,b,c){var d=this.alignedObjects,e=d.length;
this.width=a;this.height=b;for(this.boxWrapper[p(c,!0)?"animate":"attr"]({width:a,height:b});e--;)d[e].align()},g:function(a){var b=this.createElement("g");return s(a)?b.attr({"class":"highcharts-"+a}):b},image:function(a,b,c,d,e){var f={preserveAspectRatio:P};arguments.length>1&&r(f,{x:b,y:c,width:d,height:e});f=this.createElement("image").attr(f);f.element.setAttributeNS?f.element.setAttributeNS("http://www.w3.org/1999/xlink","href",a):f.element.setAttribute("hc-svg-href",a);return f},symbol:function(a,
b,c,d,e,f){var g,h=this.symbols[a],h=h&&h(v(b),v(c),d,e,f),i=/^url\((.*?)\)$/,j,k;if(h)g=this.path(h),r(g,{symbolName:a,x:b,y:c,width:d,height:e}),f&&r(g,f);else if(i.test(a))k=function(a,b){a.element&&(a.attr({width:b[0],height:b[1]}),a.alignByTranslate||a.translate(v((d-b[0])/2),v((e-b[1])/2)))},j=a.match(i)[1],a=Jb[j]||f&&f.width&&f.height&&[f.width,f.height],g=this.image(j).attr({x:b,y:c}),g.isImg=!0,a?k(g,a):(g.attr({width:0,height:0}),$("img",{onload:function(){k(g,Jb[j]=[this.width,this.height])},
src:j}));return g},symbols:{circle:function(a,b,c,d){var e=0.166*c;return["M",a+c/2,b,"C",a+c+e,b,a+c+e,b+d,a+c/2,b+d,"C",a-e,b+d,a-e,b,a+c/2,b,"Z"]},square:function(a,b,c,d){return["M",a,b,"L",a+c,b,a+c,b+d,a,b+d,"Z"]},triangle:function(a,b,c,d){return["M",a+c/2,b,"L",a+c,b+d,a,b+d,"Z"]},"triangle-down":function(a,b,c,d){return["M",a,b,"L",a+c,b,a+c/2,b+d,"Z"]},diamond:function(a,b,c,d){return["M",a+c/2,b,"L",a+c,b+d/2,a+c/2,b+d,a,b+d/2,"Z"]},arc:function(a,b,c,d,e){var f=e.start,c=e.r||c||d,g=e.end-
0.001,d=e.innerR,h=e.open,i=aa(f),j=fa(f),k=aa(g),g=fa(g),e=e.end-f<oa?0:1;return["M",a+c*i,b+c*j,"A",c,c,0,e,1,a+c*k,b+c*g,h?"M":"L",a+d*k,b+d*g,"A",d,d,0,e,0,a+d*i,b+d*j,h?"":"Z"]},callout:function(a,b,c,d,e){var f=L(e&&e.r||0,c,d),g=f+6,h=e&&e.anchorX,i=e&&e.anchorY,e=v(e.strokeWidth||0)%2/2;a+=e;b+=e;e=["M",a+f,b,"L",a+c-f,b,"C",a+c,b,a+c,b,a+c,b+f,"L",a+c,b+d-f,"C",a+c,b+d,a+c,b+d,a+c-f,b+d,"L",a+f,b+d,"C",a,b+d,a,b+d,a,b+d-f,"L",a,b+f,"C",a,b,a,b,a+f,b];h&&h>c&&i>b+g&&i<b+d-g?e.splice(13,3,
"L",a+c,i-6,a+c+6,i,a+c,i+6,a+c,b+d-f):h&&h<0&&i>b+g&&i<b+d-g?e.splice(33,3,"L",a,i+6,a-6,i,a,i-6,a,b+f):i&&i>d&&h>a+g&&h<a+c-g?e.splice(23,3,"L",h+6,b+d,h,b+d+6,h-6,b+d,a+f,b+d):i&&i<0&&h>a+g&&h<a+c-g&&e.splice(3,3,"L",h-6,b,h,b-6,h+6,b,c-f,b);return e}},clipRect:function(a,b,c,d){var e="highcharts-"+ub++,f=this.createElement("clipPath").attr({id:e}).add(this.defs),a=this.rect(a,b,c,d,0).add(f);a.id=e;a.clipPath=f;return a},text:function(a,b,c,d){var e=ga||!ba&&this.forExport,f={};if(d&&!this.forExport)return this.html(a,
b,c);f.x=Math.round(b||0);if(c)f.y=Math.round(c);if(a||a===0)f.text=a;a=this.createElement("text").attr(f);e&&a.css({position:"absolute"});if(!d)a.xSetter=function(a,b,c){var d=c.getElementsByTagName("tspan"),e,f=c.getAttribute(b),n;for(n=0;n<d.length;n++)e=d[n],e.getAttribute(b)===f&&e.setAttribute(b,a);c.setAttribute(b,a)};return a},fontMetrics:function(a,b){a=a||this.style.fontSize;if(b&&G.getComputedStyle)b=b.element||b,a=G.getComputedStyle(b,"").fontSize;var a=/px/.test(a)?y(a):/em/.test(a)?
parseFloat(a)*12:12,c=a<24?a+4:v(a*1.2),d=v(c*0.8);return{h:c,b:d,f:a}},label:function(a,b,c,d,e,f,g,h,i){function j(){var a,b;a=o.element.style;D=(t===void 0||xb===void 0||m.styles.textAlign)&&o.textStr&&o.getBBox();m.width=(t||D.width||0)+2*C+kb;m.height=(xb||D.height||0)+2*C;R=C+n.fontMetrics(a&&a.fontSize,o).b;if(y){if(!p)a=v(-J*C),b=h?-R:0,m.box=p=d?n.symbol(d,a,b,m.width,m.height,z):n.rect(a,b,m.width,m.height,0,z[Qb]),p.attr("fill",P).add(m);p.isImg||p.attr(r({width:v(m.width),height:v(m.height)},
z));z=null}}function k(){var a=m.styles,a=a&&a.textAlign,b=kb+C*(1-J),c;c=h?0:R;if(s(t)&&D&&(a==="center"||a==="right"))b+={center:0.5,right:1}[a]*(t-D.width);if(b!==o.x||c!==o.y)o.attr("x",b),c!==u&&o.attr("y",c);o.x=b;o.y=c}function l(a,b){p?p.attr(a,b):z[a]=b}var n=this,m=n.g(i),o=n.text("",0,0,g).attr({zIndex:1}),p,D,J=0,C=3,kb=0,t,xb,yb,x,Kb=0,z={},R,y;m.onAdd=function(){o.add(m);m.attr({text:a||a===0?a:"",x:b,y:c});p&&s(e)&&m.attr({anchorX:e,anchorY:f})};m.widthSetter=function(a){t=a};m.heightSetter=
function(a){xb=a};m.paddingSetter=function(a){s(a)&&a!==C&&(C=a,k())};m.paddingLeftSetter=function(a){s(a)&&a!==kb&&(kb=a,k())};m.alignSetter=function(a){J={left:0,center:0.5,right:1}[a]};m.textSetter=function(a){a!==u&&o.textSetter(a);j();k()};m["stroke-widthSetter"]=function(a,b){a&&(y=!0);Kb=a%2/2;l(b,a)};m.strokeSetter=m.fillSetter=m.rSetter=function(a,b){b==="fill"&&a&&(y=!0);l(b,a)};m.anchorXSetter=function(a,b){e=a;l(b,a+Kb-yb)};m.anchorYSetter=function(a,b){f=a;l(b,a-x)};m.xSetter=function(a){m.x=
a;J&&(a-=J*((t||D.width)+C));yb=v(a);m.attr("translateX",yb)};m.ySetter=function(a){x=m.y=v(a);m.attr("translateY",x)};var A=m.css;return r(m,{css:function(a){if(a){var b={},a=w(a);q(m.textProps,function(c){a[c]!==u&&(b[c]=a[c],delete a[c])});o.css(b)}return A.call(m,a)},getBBox:function(){return{width:D.width+2*C,height:D.height+2*C,x:D.x-C,y:D.y-C}},shadow:function(a){p&&p.shadow(a);return m},destroy:function(){X(m.element,"mouseenter");X(m.element,"mouseleave");o&&(o=o.destroy());p&&(p=p.destroy());
S.prototype.destroy.call(m);m=n=j=k=l=null}})}};Za=ta;r(S.prototype,{htmlCss:function(a){var b=this.element;if(b=a&&b.tagName==="SPAN"&&a.width)delete a.width,this.textWidth=b,this.updateTransform();this.styles=r(this.styles,a);B(this.element,a);return this},htmlGetBBox:function(){var a=this.element,b=this.bBox;if(!b){if(a.nodeName==="text")a.style.position="absolute";b=this.bBox={x:a.offsetLeft,y:a.offsetTop,width:a.offsetWidth,height:a.offsetHeight}}return b},htmlUpdateTransform:function(){if(this.added){var a=
this.renderer,b=this.element,c=this.translateX||0,d=this.translateY||0,e=this.x||0,f=this.y||0,g=this.textAlign||"left",h={left:0,center:0.5,right:1}[g],i=this.shadows;B(b,{marginLeft:c,marginTop:d});i&&q(i,function(a){B(a,{marginLeft:c+1,marginTop:d+1})});this.inverted&&q(b.childNodes,function(c){a.invertChild(c,b)});if(b.tagName==="SPAN"){var j=this.rotation,k,l=y(this.textWidth),n=[j,g,b.innerHTML,this.textWidth].join(",");if(n!==this.cTT){k=a.fontMetrics(b.style.fontSize).b;s(j)&&this.setSpanRotation(j,
h,k);i=p(this.elemWidth,b.offsetWidth);if(i>l&&/[ \-]/.test(b.textContent||b.innerText))B(b,{width:l+"px",display:"block",whiteSpace:"normal"}),i=l;this.getSpanCorrection(i,k,h,j,g)}B(b,{left:e+(this.xCorr||0)+"px",top:f+(this.yCorr||0)+"px"});if(tb)k=b.offsetHeight;this.cTT=n}}else this.alignOnAdd=!0},setSpanRotation:function(a,b,c){var d={},e=Aa?"-ms-transform":tb?"-webkit-transform":Ua?"MozTransform":Hb?"-o-transform":"";d[e]=d.transform="rotate("+a+"deg)";d[e+(Ua?"Origin":"-origin")]=d.transformOrigin=
b*100+"% "+c+"px";B(this.element,d)},getSpanCorrection:function(a,b,c){this.xCorr=-a*c;this.yCorr=-b}});r(ta.prototype,{html:function(a,b,c){var d=this.createElement("span"),e=d.element,f=d.renderer;d.textSetter=function(a){a!==e.innerHTML&&delete this.bBox;e.innerHTML=this.textStr=a};d.xSetter=d.ySetter=d.alignSetter=d.rotationSetter=function(a,b){b==="align"&&(b="textAlign");d[b]=a;d.htmlUpdateTransform()};d.attr({text:a,x:v(b),y:v(c)}).css({position:"absolute",whiteSpace:"nowrap",fontFamily:this.style.fontFamily,
fontSize:this.style.fontSize});d.css=d.htmlCss;if(f.isSVG)d.add=function(a){var b,c=f.box.parentNode,j=[];if(this.parentGroup=a){if(b=a.div,!b){for(;a;)j.push(a),a=a.parentGroup;q(j.reverse(),function(a){var d;b=a.div=a.div||$(Ka,{className:F(a.element,"class")},{position:"absolute",left:(a.translateX||0)+"px",top:(a.translateY||0)+"px"},b||c);d=b.style;r(a,{translateXSetter:function(b,c){d.left=b+"px";a[c]=b;a.doTransform=!0},translateYSetter:function(b,c){d.top=b+"px";a[c]=b;a.doTransform=!0},visibilitySetter:function(a,
b){d[b]=a}})})}}else b=c;b.appendChild(e);d.added=!0;d.alignOnAdd&&d.htmlUpdateTransform();return d};return d}});var Z;if(!ba&&!ga){Z={init:function(a,b){var c=["<",b,' filled="f" stroked="f"'],d=["position: ","absolute",";"],e=b===Ka;(b==="shape"||e)&&d.push("left:0;top:0;width:1px;height:1px;");d.push("visibility: ",e?"hidden":"visible");c.push(' style="',d.join(""),'"/>');if(b)c=e||b==="span"||b==="img"?c.join(""):a.prepVML(c),this.element=$(c);this.renderer=a},add:function(a){var b=this.renderer,
c=this.element,d=b.box,d=a?a.element||a:d;a&&a.inverted&&b.invertChild(c,d);d.appendChild(c);this.added=!0;this.alignOnAdd&&!this.deferUpdateTransform&&this.updateTransform();if(this.onAdd)this.onAdd();return this},updateTransform:S.prototype.htmlUpdateTransform,setSpanRotation:function(){var a=this.rotation,b=aa(a*Ea),c=fa(a*Ea);B(this.element,{filter:a?["progid:DXImageTransform.Microsoft.Matrix(M11=",b,", M12=",-c,", M21=",c,", M22=",b,", sizingMethod='auto expand')"].join(""):P})},getSpanCorrection:function(a,
b,c,d,e){var f=d?aa(d*Ea):1,g=d?fa(d*Ea):0,h=p(this.elemHeight,this.element.offsetHeight),i;this.xCorr=f<0&&-a;this.yCorr=g<0&&-h;i=f*g<0;this.xCorr+=g*b*(i?1-c:c);this.yCorr-=f*b*(d?i?c:1-c:1);e&&e!=="left"&&(this.xCorr-=a*c*(f<0?-1:1),d&&(this.yCorr-=h*c*(g<0?-1:1)),B(this.element,{textAlign:e}))},pathToVML:function(a){for(var b=a.length,c=[];b--;)if(ja(a[b]))c[b]=v(a[b]*10)-5;else if(a[b]==="Z")c[b]="x";else if(c[b]=a[b],a.isArc&&(a[b]==="wa"||a[b]==="at"))c[b+5]===c[b+7]&&(c[b+7]+=a[b+7]>a[b+
5]?1:-1),c[b+6]===c[b+8]&&(c[b+8]+=a[b+8]>a[b+6]?1:-1);return c.join(" ")||"x"},clip:function(a){var b=this,c;a?(c=a.members,la(c,b),c.push(b),b.destroyClip=function(){la(c,b)},a=a.getCSS(b)):(b.destroyClip&&b.destroyClip(),a={clip:hb?"inherit":"rect(auto)"});return b.css(a)},css:S.prototype.htmlCss,safeRemoveChild:function(a){a.parentNode&&Qa(a)},destroy:function(){this.destroyClip&&this.destroyClip();return S.prototype.destroy.apply(this)},on:function(a,b){this.element["on"+a]=function(){var a=
G.event;a.target=a.srcElement;b(a)};return this},cutOffPath:function(a,b){var c,a=a.split(/[ ,]/);c=a.length;if(c===9||c===11)a[c-4]=a[c-2]=y(a[c-2])-10*b;return a.join(" ")},shadow:function(a,b,c){var d=[],e,f=this.element,g=this.renderer,h,i=f.style,j,k=f.path,l,n,m,o;k&&typeof k.value!=="string"&&(k="x");n=k;if(a){m=p(a.width,3);o=(a.opacity||0.15)/m;for(e=1;e<=3;e++){l=m*2+1-2*e;c&&(n=this.cutOffPath(k.value,l+0.5));j=['<shape isShadow="true" strokeweight="',l,'" filled="false" path="',n,'" coordsize="10 10" style="',
f.style.cssText,'" />'];h=$(g.prepVML(j),null,{left:y(i.left)+p(a.offsetX,1),top:y(i.top)+p(a.offsetY,1)});if(c)h.cutOff=l+1;j=['<stroke color="',a.color||"black",'" opacity="',o*e,'"/>'];$(g.prepVML(j),null,null,h);b?b.element.appendChild(h):f.parentNode.insertBefore(h,f);d.push(h)}this.shadows=d}return this},updateShadows:sa,setAttr:function(a,b){hb?this.element[a]=b:this.element.setAttribute(a,b)},classSetter:function(a){this.element.className=a},dashstyleSetter:function(a,b,c){(c.getElementsByTagName("stroke")[0]||
$(this.renderer.prepVML(["<stroke/>"]),null,null,c))[b]=a||"solid";this[b]=a},dSetter:function(a,b,c){var d=this.shadows,a=a||[];this.d=a.join&&a.join(" ");c.path=a=this.pathToVML(a);if(d)for(c=d.length;c--;)d[c].path=d[c].cutOff?this.cutOffPath(a,d[c].cutOff):a;this.setAttr(b,a)},fillSetter:function(a,b,c){var d=c.nodeName;if(d==="SPAN")c.style.color=a;else if(d!=="IMG")c.filled=a!==P,this.setAttr("fillcolor",this.renderer.color(a,c,b,this))},opacitySetter:sa,rotationSetter:function(a,b,c){c=c.style;
this[b]=c[b]=a;c.left=-v(fa(a*Ea)+1)+"px";c.top=v(aa(a*Ea))+"px"},strokeSetter:function(a,b,c){this.setAttr("strokecolor",this.renderer.color(a,c,b))},"stroke-widthSetter":function(a,b,c){c.stroked=!!a;this[b]=a;ja(a)&&(a+="px");this.setAttr("strokeweight",a)},titleSetter:function(a,b){this.setAttr(b,a)},visibilitySetter:function(a,b,c){a==="inherit"&&(a="visible");this.shadows&&q(this.shadows,function(c){c.style[b]=a});c.nodeName==="DIV"&&(a=a==="hidden"?"-999em":0,hb||(c.style[b]=a?"visible":"hidden"),
b="top");c.style[b]=a},xSetter:function(a,b,c){this[b]=a;b==="x"?b="left":b==="y"&&(b="top");this.updateClipping?(this[b]=a,this.updateClipping()):c.style[b]=a},zIndexSetter:function(a,b,c){c.style[b]=a}};K.VMLElement=Z=ma(S,Z);Z.prototype.ySetter=Z.prototype.widthSetter=Z.prototype.heightSetter=Z.prototype.xSetter;var ia={Element:Z,isIE8:wa.indexOf("MSIE 8.0")>-1,init:function(a,b,c,d){var e;this.alignedObjects=[];d=this.createElement(Ka).css(r(this.getStyle(d),{position:"relative"}));e=d.element;
a.appendChild(d.element);this.isVML=!0;this.box=e;this.boxWrapper=d;this.cache={};this.setSize(b,c,!1);if(!x.namespaces.hcv){x.namespaces.add("hcv","urn:schemas-microsoft-com:vml");try{x.createStyleSheet().cssText="hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke{ behavior:url(#default#VML); display: inline-block; } "}catch(f){x.styleSheets[0].cssText+="hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke{ behavior:url(#default#VML); display: inline-block; } "}}},isHidden:function(){return!this.box.offsetWidth},
clipRect:function(a,b,c,d){var e=this.createElement(),f=da(a);return r(e,{members:[],left:(f?a.x:a)+1,top:(f?a.y:b)+1,width:(f?a.width:c)-1,height:(f?a.height:d)-1,getCSS:function(a){var b=a.element,c=b.nodeName,a=a.inverted,d=this.top-(c==="shape"?b.offsetTop:0),e=this.left,b=e+this.width,f=d+this.height,d={clip:"rect("+v(a?e:d)+"px,"+v(a?f:b)+"px,"+v(a?b:f)+"px,"+v(a?d:e)+"px)"};!a&&hb&&c==="DIV"&&r(d,{width:b+"px",height:f+"px"});return d},updateClipping:function(){q(e.members,function(a){a.element&&
a.css(e.getCSS(a))})}})},color:function(a,b,c,d){var e=this,f,g=/^rgba/,h,i,j=P;a&&a.linearGradient?i="gradient":a&&a.radialGradient&&(i="pattern");if(i){var k,l,n=a.linearGradient||a.radialGradient,m,o,p,D,J,C="",a=a.stops,t,s=[],u=function(){h=['<fill colors="'+s.join(",")+'" opacity="',p,'" o:opacity2="',o,'" type="',i,'" ',C,'focus="100%" method="any" />'];$(e.prepVML(h),null,null,b)};m=a[0];t=a[a.length-1];m[0]>0&&a.unshift([0,m[1]]);t[0]<1&&a.push([1,t[1]]);q(a,function(a,b){g.test(a[1])?(f=
ya(a[1]),k=f.get("rgb"),l=f.get("a")):(k=a[1],l=1);s.push(a[0]*100+"% "+k);b?(p=l,D=k):(o=l,J=k)});if(c==="fill")if(i==="gradient")c=n.x1||n[0]||0,a=n.y1||n[1]||0,m=n.x2||n[2]||0,n=n.y2||n[3]||0,C='angle="'+(90-V.atan((n-a)/(m-c))*180/oa)+'"',u();else{var j=n.r,r=j*2,v=j*2,x=n.cx,z=n.cy,R=b.radialReference,w,j=function(){R&&(w=d.getBBox(),x+=(R[0]-w.x)/w.width-0.5,z+=(R[1]-w.y)/w.height-0.5,r*=R[2]/w.width,v*=R[2]/w.height);C='src="'+E.global.VMLRadialGradientURL+'" size="'+r+","+v+'" origin="0.5,0.5" position="'+
x+","+z+'" color2="'+J+'" ';u()};d.added?j():d.onAdd=j;j=D}else j=k}else if(g.test(a)&&b.tagName!=="IMG")f=ya(a),h=["<",c,' opacity="',f.get("a"),'"/>'],$(this.prepVML(h),null,null,b),j=f.get("rgb");else{j=b.getElementsByTagName(c);if(j.length)j[0].opacity=1,j[0].type="solid";j=a}return j},prepVML:function(a){var b=this.isIE8,a=a.join("");b?(a=a.replace("/>",' xmlns="urn:schemas-microsoft-com:vml" />'),a=a.indexOf('style="')===-1?a.replace("/>",' style="display:inline-block;behavior:url(#default#VML);" />'):
a.replace('style="','style="display:inline-block;behavior:url(#default#VML);')):a=a.replace("<","<hcv:");return a},text:ta.prototype.html,path:function(a){var b={coordsize:"10 10"};Ha(a)?b.d=a:da(a)&&r(b,a);return this.createElement("shape").attr(b)},circle:function(a,b,c){var d=this.symbol("circle");if(da(a))c=a.r,b=a.y,a=a.x;d.isCircle=!0;d.r=c;return d.attr({x:a,y:b})},g:function(a){var b;a&&(b={className:"highcharts-"+a,"class":"highcharts-"+a});return this.createElement(Ka).attr(b)},image:function(a,
b,c,d,e){var f=this.createElement("img").attr({src:a});arguments.length>1&&f.attr({x:b,y:c,width:d,height:e});return f},createElement:function(a){return a==="rect"?this.symbol(a):ta.prototype.createElement.call(this,a)},invertChild:function(a,b){var c=this,d=b.style,e=a.tagName==="IMG"&&a.style;B(a,{flip:"x",left:y(d.width)-(e?y(e.top):1),top:y(d.height)-(e?y(e.left):1),rotation:-90});q(a.childNodes,function(b){c.invertChild(b,a)})},symbols:{arc:function(a,b,c,d,e){var f=e.start,g=e.end,h=e.r||c||
d,c=e.innerR,d=aa(f),i=fa(f),j=aa(g),k=fa(g);if(g-f===0)return["x"];f=["wa",a-h,b-h,a+h,b+h,a+h*d,b+h*i,a+h*j,b+h*k];e.open&&!c&&f.push("e","M",a,b);f.push("at",a-c,b-c,a+c,b+c,a+c*j,b+c*k,a+c*d,b+c*i,"x","e");f.isArc=!0;return f},circle:function(a,b,c,d,e){e&&(c=d=2*e.r);e&&e.isCircle&&(a-=c/2,b-=d/2);return["wa",a,b,a+c,b+d,a+c,b+d/2,a+c,b+d/2,"e"]},rect:function(a,b,c,d,e){return ta.prototype.symbols[!s(e)||!e.r?"square":"callout"].call(0,a,b,c,d,e)}}};K.VMLRenderer=Z=function(){this.init.apply(this,
arguments)};Z.prototype=w(ta.prototype,ia);Za=Z}ta.prototype.measureSpanWidth=function(a,b){var c=x.createElement("span"),d;d=x.createTextNode(a);c.appendChild(d);B(c,b);this.box.appendChild(c);d=c.offsetWidth;Qa(c);return d};var Mb;if(ga)K.CanVGRenderer=Z=function(){xa="http://www.w3.org/1999/xhtml"},Z.prototype.symbols={},Mb=function(){function a(){var a=b.length,d;for(d=0;d<a;d++)b[d]();b=[]}var b=[];return{push:function(c,d){b.length===0&&Rb(d,a);b.push(c)}}}(),Za=Z;Ta.prototype={addLabel:function(){var a=
this.axis,b=a.options,c=a.chart,d=a.horiz,e=a.categories,f=a.names,g=this.pos,h=b.labels,i=h.rotation,j=a.tickPositions,d=d&&e&&!h.step&&!h.staggerLines&&!h.rotation&&c.plotWidth/j.length||!d&&(c.margin[3]||c.chartWidth*0.33),k=g===j[0],l=g===j[j.length-1],n,f=e?p(e[g],f[g],g):g,e=this.label,m=j.info;a.isDatetimeAxis&&m&&(n=b.dateTimeLabelFormats[m.higherRanks[g]||m.unitName]);this.isFirst=k;this.isLast=l;b=a.labelFormatter.call({axis:a,chart:c,isFirst:k,isLast:l,dateTimeLabelFormat:n,value:a.isLog?
ea(ka(f)):f});g=d&&{width:t(1,v(d-2*(h.padding||10)))+"px"};if(s(e))e&&e.attr({text:b}).css(g);else{n={align:a.labelAlign};if(ja(i))n.rotation=i;if(d&&h.ellipsis)g.HcHeight=a.len/j.length;this.label=e=s(b)&&h.enabled?c.renderer.text(b,0,0,h.useHTML).attr(n).css(r(g,h.style)).add(a.labelGroup):null;a.tickBaseline=c.renderer.fontMetrics(h.style.fontSize,e).b;i&&a.side===2&&(a.tickBaseline*=aa(i*Ea))}this.yOffset=e?p(h.y,a.tickBaseline+(a.side===2?8:-(e.getBBox().height/2))):0},getLabelSize:function(){var a=
this.label,b=this.axis;return a?a.getBBox()[b.horiz?"height":"width"]:0},getLabelSides:function(){var a=this.label.getBBox(),b=this.axis,c=b.horiz,d=b.options.labels,a=c?a.width:a.height,b=c?d.x-a*{left:0,center:0.5,right:1}[b.labelAlign]:0;return[b,c?a+b:a]},handleOverflow:function(a,b){var c=!0,d=this.axis,e=this.isFirst,f=this.isLast,g=d.horiz?b.x:b.y,h=d.reversed,i=d.tickPositions,j=this.getLabelSides(),k=j[0],j=j[1],l,n,m,o=this.label.line;l=o||0;n=d.labelEdge;m=d.justifyLabels&&(e||f);n[l]===
u||g+k>n[l]?n[l]=g+j:m||(c=!1);if(m){l=(n=d.justifyToPlot)?d.pos:0;n=n?l+d.len:d.chart.chartWidth;do a+=e?1:-1,m=d.ticks[i[a]];while(i[a]&&(!m||!m.label||m.label.line!==o));d=m&&m.label.xy&&m.label.xy.x+m.getLabelSides()[e?0:1];e&&!h||f&&h?g+k<l&&(g=l-k,m&&g+j>d&&(c=!1)):g+j>n&&(g=n-j,m&&g+k<d&&(c=!1));b.x=g}return c},getPosition:function(a,b,c,d){var e=this.axis,f=e.chart,g=d&&f.oldChartHeight||f.chartHeight;return{x:a?e.translate(b+c,null,null,d)+e.transB:e.left+e.offset+(e.opposite?(d&&f.oldChartWidth||
f.chartWidth)-e.right-e.left:0),y:a?g-e.bottom+e.offset-(e.opposite?e.height:0):g-e.translate(b+c,null,null,d)-e.transB}},getLabelPosition:function(a,b,c,d,e,f,g,h){var i=this.axis,j=i.transA,k=i.reversed,l=i.staggerLines,a=a+e.x-(f&&d?f*j*(k?-1:1):0),b=b+this.yOffset-(f&&!d?f*j*(k?1:-1):0);if(l)c.line=g/(h||1)%l,b+=c.line*(i.labelOffset/l);return{x:a,y:b}},getMarkPath:function(a,b,c,d,e,f){return f.crispLine(["M",a,b,"L",a+(e?0:-c),b+(e?c:0)],d)},render:function(a,b,c){var d=this.axis,e=d.options,
f=d.chart.renderer,g=d.horiz,h=this.type,i=this.label,j=this.pos,k=e.labels,l=this.gridLine,n=h?h+"Grid":"grid",m=h?h+"Tick":"tick",o=e[n+"LineWidth"],q=e[n+"LineColor"],D=e[n+"LineDashStyle"],J=e[m+"Length"],n=e[m+"Width"]||0,C=e[m+"Color"],t=e[m+"Position"],m=this.mark,s=k.step,r=!0,v=d.tickmarkOffset,w=this.getPosition(g,j,v,b),x=w.x,w=w.y,z=g&&x===d.pos+d.len||!g&&w===d.pos?-1:1,c=p(c,1);this.isActive=!0;if(o){j=d.getPlotLinePath(j+v,o*z,b,!0);if(l===u){l={stroke:q,"stroke-width":o};if(D)l.dashstyle=
D;if(!h)l.zIndex=1;if(b)l.opacity=0;this.gridLine=l=o?f.path(j).attr(l).add(d.gridGroup):null}if(!b&&l&&j)l[this.isNew?"attr":"animate"]({d:j,opacity:c})}if(n&&J)t==="inside"&&(J=-J),d.opposite&&(J=-J),h=this.getMarkPath(x,w,J,n*z,g,f),m?m.animate({d:h,opacity:c}):this.mark=f.path(h).attr({stroke:C,"stroke-width":n,opacity:c}).add(d.axisGroup);if(i&&!isNaN(x))i.xy=w=this.getLabelPosition(x,w,i,g,k,v,a,s),this.isFirst&&!this.isLast&&!p(e.showFirstLabel,1)||this.isLast&&!this.isFirst&&!p(e.showLastLabel,
1)?r=!1:!d.isRadial&&!k.step&&!k.rotation&&!b&&c!==0&&(r=this.handleOverflow(a,w)),s&&a%s&&(r=!1),r&&!isNaN(w.y)?(w.opacity=c,i[this.isNew?"attr":"animate"](w),this.isNew=!1):i.attr("y",-9999)},destroy:function(){Pa(this,this.axis)}};K.PlotLineOrBand=function(a,b){this.axis=a;if(b)this.options=b,this.id=b.id};K.PlotLineOrBand.prototype={render:function(){var a=this,b=a.axis,c=b.horiz,d=(b.pointRange||0)/2,e=a.options,f=e.label,g=a.label,h=e.width,i=e.to,j=e.from,k=s(j)&&s(i),l=e.value,n=e.dashStyle,
m=a.svgElem,o=[],p,q=e.color,J=e.zIndex,C=e.events,r={},u=b.chart.renderer;b.isLog&&(j=za(j),i=za(i),l=za(l));if(h){if(o=b.getPlotLinePath(l,h),r={stroke:q,"stroke-width":h},n)r.dashstyle=n}else if(k){j=t(j,b.min-d);i=L(i,b.max+d);o=b.getPlotBandPath(j,i,e);if(q)r.fill=q;if(e.borderWidth)r.stroke=e.borderColor,r["stroke-width"]=e.borderWidth}else return;if(s(J))r.zIndex=J;if(m)if(o)m.animate({d:o},null,m.onGetPath);else{if(m.hide(),m.onGetPath=function(){m.show()},g)a.label=g=g.destroy()}else if(o&&
o.length&&(a.svgElem=m=u.path(o).attr(r).add(),C))for(p in d=function(b){m.on(b,function(c){C[b].apply(a,[c])})},C)d(p);if(f&&s(f.text)&&o&&o.length&&b.width>0&&b.height>0){f=w({align:c&&k&&"center",x:c?!k&&4:10,verticalAlign:!c&&k&&"middle",y:c?k?16:10:k?6:-4,rotation:c&&!k&&90},f);if(!g){r={align:f.textAlign||f.align,rotation:f.rotation};if(s(J))r.zIndex=J;a.label=g=u.text(f.text,0,0,f.useHTML).attr(r).css(f.style).add()}b=[o[1],o[4],k?o[6]:o[1]];k=[o[2],o[5],k?o[7]:o[2]];o=Oa(b);c=Oa(k);g.align(f,
!1,{x:o,y:c,width:Ca(b)-o,height:Ca(k)-c});g.show()}else g&&g.hide();return a},destroy:function(){la(this.axis.plotLinesAndBands,this);delete this.axis;Pa(this)}};na.prototype={defaultOptions:{dateTimeLabelFormats:{millisecond:"%H:%M:%S.%L",second:"%H:%M:%S",minute:"%H:%M",hour:"%H:%M",day:"%e. %b",week:"%e. %b",month:"%b '%y",year:"%Y"},endOnTick:!1,gridLineColor:"#C0C0C0",labels:M,lineColor:"#C0D0E0",lineWidth:1,minPadding:0.01,maxPadding:0.01,minorGridLineColor:"#E0E0E0",minorGridLineWidth:1,minorTickColor:"#A0A0A0",
minorTickLength:2,minorTickPosition:"outside",startOfWeek:1,startOnTick:!1,tickColor:"#C0D0E0",tickLength:10,tickmarkPlacement:"between",tickPixelInterval:100,tickPosition:"outside",tickWidth:1,title:{align:"middle",style:{color:"#707070"}},type:"linear"},defaultYAxisOptions:{endOnTick:!0,gridLineWidth:1,tickPixelInterval:72,showLastLabel:!0,labels:{x:-8,y:3},lineWidth:0,maxPadding:0.05,minPadding:0.05,startOnTick:!0,tickWidth:0,title:{rotation:270,text:"Values"},stackLabels:{enabled:!1,formatter:function(){return Ba(this.total,
-1)},style:M.style}},defaultLeftAxisOptions:{labels:{x:-15,y:null},title:{rotation:270}},defaultRightAxisOptions:{labels:{x:15,y:null},title:{rotation:90}},defaultBottomAxisOptions:{labels:{x:0,y:null},title:{rotation:0}},defaultTopAxisOptions:{labels:{x:0,y:-15},title:{rotation:0}},init:function(a,b){var c=b.isX;this.horiz=a.inverted?!c:c;this.coll=(this.isXAxis=c)?"xAxis":"yAxis";this.opposite=b.opposite;this.side=b.side||(this.horiz?this.opposite?0:2:this.opposite?1:3);this.setOptions(b);var d=
this.options,e=d.type;this.labelFormatter=d.labels.formatter||this.defaultLabelFormatter;this.userOptions=b;this.minPixelPadding=0;this.chart=a;this.reversed=d.reversed;this.zoomEnabled=d.zoomEnabled!==!1;this.categories=d.categories||e==="category";this.names=[];this.isLog=e==="logarithmic";this.isDatetimeAxis=e==="datetime";this.isLinked=s(d.linkedTo);this.tickmarkOffset=this.categories&&d.tickmarkPlacement==="between"&&p(d.tickInterval,1)===1?0.5:0;this.ticks={};this.labelEdge=[];this.minorTicks=
{};this.plotLinesAndBands=[];this.alternateBands={};this.len=0;this.minRange=this.userMinRange=d.minRange||d.maxZoom;this.range=d.range;this.offset=d.offset||0;this.stacks={};this.oldStacks={};this.min=this.max=null;this.crosshair=p(d.crosshair,ra(a.options.tooltip.crosshairs)[c?0:1],!1);var f,d=this.options.events;Ma(this,a.axes)===-1&&(c&&!this.isColorAxis?a.axes.splice(a.xAxis.length,0,this):a.axes.push(this),a[this.coll].push(this));this.series=this.series||[];if(a.inverted&&c&&this.reversed===
u)this.reversed=!0;this.removePlotLine=this.removePlotBand=this.removePlotBandOrLine;for(f in d)N(this,f,d[f]);if(this.isLog)this.val2lin=za,this.lin2val=ka},setOptions:function(a){this.options=w(this.defaultOptions,this.isXAxis?{}:this.defaultYAxisOptions,[this.defaultTopAxisOptions,this.defaultRightAxisOptions,this.defaultBottomAxisOptions,this.defaultLeftAxisOptions][this.side],w(E[this.coll],a))},defaultLabelFormatter:function(){var a=this.axis,b=this.value,c=a.categories,d=this.dateTimeLabelFormat,
e=E.lang.numericSymbols,f=e&&e.length,g,h=a.options.labels.format,a=a.isLog?b:a.tickInterval;if(h)g=Ja(h,this);else if(c)g=b;else if(d)g=cb(d,b);else if(f&&a>=1E3)for(;f--&&g===u;)c=Math.pow(1E3,f+1),a>=c&&e[f]!==null&&(g=Ba(b/c,-1)+e[f]);g===u&&(g=Q(b)>=1E4?Ba(b,0):Ba(b,-1,u,""));return g},getSeriesExtremes:function(){var a=this,b=a.chart;a.hasVisibleSeries=!1;a.dataMin=a.dataMax=a.ignoreMinPadding=a.ignoreMaxPadding=null;a.buildStacks&&a.buildStacks();q(a.series,function(c){if(c.visible||!b.options.chart.ignoreHiddenSeries){var d;
d=c.options.threshold;var e;a.hasVisibleSeries=!0;a.isLog&&d<=0&&(d=null);if(a.isXAxis){if(d=c.xData,d.length)a.dataMin=L(p(a.dataMin,d[0]),Oa(d)),a.dataMax=t(p(a.dataMax,d[0]),Ca(d))}else{c.getExtremes();e=c.dataMax;c=c.dataMin;if(s(c)&&s(e))a.dataMin=L(p(a.dataMin,c),c),a.dataMax=t(p(a.dataMax,e),e);if(s(d))if(a.dataMin>=d)a.dataMin=d,a.ignoreMinPadding=!0;else if(a.dataMax<d)a.dataMax=d,a.ignoreMaxPadding=!0}}})},translate:function(a,b,c,d,e,f){var g=1,h=0,i=d?this.oldTransA:this.transA,d=d?this.oldMin:
this.min,j=this.minPixelPadding,e=(this.options.ordinal||this.isLog&&e)&&this.lin2val;if(!i)i=this.transA;if(c)g*=-1,h=this.len;this.reversed&&(g*=-1,h-=g*(this.sector||this.len));b?(a=a*g+h,a-=j,a=a/i+d,e&&(a=this.lin2val(a))):(e&&(a=this.val2lin(a)),f==="between"&&(f=0.5),a=g*(a-d)*i+h+g*j+(ja(f)?i*f*this.pointRange:0));return a},toPixels:function(a,b){return this.translate(a,!1,!this.horiz,null,!0)+(b?0:this.pos)},toValue:function(a,b){return this.translate(a-(b?0:this.pos),!0,!this.horiz,null,
!0)},getPlotLinePath:function(a,b,c,d,e){var f=this.chart,g=this.left,h=this.top,i,j,k=c&&f.oldChartHeight||f.chartHeight,l=c&&f.oldChartWidth||f.chartWidth,n;i=this.transB;e=p(e,this.translate(a,null,null,c));a=c=v(e+i);i=j=v(k-e-i);if(isNaN(e))n=!0;else if(this.horiz){if(i=h,j=k-this.bottom,a<g||a>g+this.width)n=!0}else if(a=g,c=l-this.right,i<h||i>h+this.height)n=!0;return n&&!d?null:f.renderer.crispLine(["M",a,i,"L",c,j],b||1)},getLinearTickPositions:function(a,b,c){var d,e=ea(U(b/a)*a),f=ea(La(c/
a)*a),g=[];if(b===c&&ja(b))return[b];for(b=e;b<=f;){g.push(b);b=ea(b+a);if(b===d)break;d=b}return g},getMinorTickPositions:function(){var a=this.options,b=this.tickPositions,c=this.minorTickInterval,d=[],e;if(this.isLog){e=b.length;for(a=1;a<e;a++)d=d.concat(this.getLogTickPositions(c,b[a-1],b[a],!0))}else if(this.isDatetimeAxis&&a.minorTickInterval==="auto")d=d.concat(this.getTimeTicks(this.normalizeTimeTickInterval(c),this.min,this.max,a.startOfWeek)),d[0]<this.min&&d.shift();else for(b=this.min+
(b[0]-this.min)%c;b<=this.max;b+=c)d.push(b);return d},adjustForMinRange:function(){var a=this.options,b=this.min,c=this.max,d,e=this.dataMax-this.dataMin>=this.minRange,f,g,h,i,j;if(this.isXAxis&&this.minRange===u&&!this.isLog)s(a.min)||s(a.max)?this.minRange=null:(q(this.series,function(a){i=a.xData;for(g=j=a.xIncrement?1:i.length-1;g>0;g--)if(h=i[g]-i[g-1],f===u||h<f)f=h}),this.minRange=L(f*5,this.dataMax-this.dataMin));if(c-b<this.minRange){var k=this.minRange;d=(k-c+b)/2;d=[b-d,p(a.min,b-d)];
if(e)d[2]=this.dataMin;b=Ca(d);c=[b+k,p(a.max,b+k)];if(e)c[2]=this.dataMax;c=Oa(c);c-b<k&&(d[0]=c-k,d[1]=p(a.min,c-k),b=Ca(d))}this.min=b;this.max=c},setAxisTranslation:function(a){var b=this,c=b.max-b.min,d=b.axisPointRange||0,e,f=0,g=0,h=b.linkedParent,i=!!b.categories,j=b.transA;if(b.isXAxis||i||d)h?(f=h.minPointOffset,g=h.pointRangePadding):q(b.series,function(a){var h=i?1:b.isXAxis?a.pointRange:b.axisPointRange||0,j=a.options.pointPlacement,m=a.closestPointRange;h>c&&(h=0);d=t(d,h);f=t(f,Ga(j)?
0:h/2);g=t(g,j==="on"?0:h);!a.noSharedTooltip&&s(m)&&(e=s(e)?L(e,m):m)}),h=b.ordinalSlope&&e?b.ordinalSlope/e:1,b.minPointOffset=f*=h,b.pointRangePadding=g*=h,b.pointRange=L(d,c),b.closestPointRange=e;if(a)b.oldTransA=j;b.translationSlope=b.transA=j=b.len/(c+g||1);b.transB=b.horiz?b.left:b.bottom;b.minPixelPadding=j*f},setTickPositions:function(a){var b=this,c=b.chart,d=b.options,e=d.startOnTick,f=d.endOnTick,g=b.isLog,h=b.isDatetimeAxis,i=b.isXAxis,j=b.isLinked,k=b.options.tickPositioner,l=d.maxPadding,
n=d.minPadding,m=d.tickInterval,o=d.minTickInterval,Y=d.tickPixelInterval,D,J=b.categories;j?(b.linkedParent=c[b.coll][d.linkedTo],c=b.linkedParent.getExtremes(),b.min=p(c.min,c.dataMin),b.max=p(c.max,c.dataMax),d.type!==b.linkedParent.options.type&&ha(11,1)):(b.min=p(b.userMin,d.min,b.dataMin),b.max=p(b.userMax,d.max,b.dataMax));if(g)!a&&L(b.min,p(b.dataMin,b.min))<=0&&ha(10,1),b.min=ea(za(b.min)),b.max=ea(za(b.max));if(b.range&&s(b.max))b.userMin=b.min=t(b.min,b.max-b.range),b.userMax=b.max,b.range=
null;b.beforePadding&&b.beforePadding();b.adjustForMinRange();if(!J&&!b.axisPointRange&&!b.usePercentage&&!j&&s(b.min)&&s(b.max)&&(c=b.max-b.min)){if(!s(d.min)&&!s(b.userMin)&&n&&(b.dataMin<0||!b.ignoreMinPadding))b.min-=c*n;if(!s(d.max)&&!s(b.userMax)&&l&&(b.dataMax>0||!b.ignoreMaxPadding))b.max+=c*l}if(ja(d.floor))b.min=t(b.min,d.floor);if(ja(d.ceiling))b.max=L(b.max,d.ceiling);b.min===b.max||b.min===void 0||b.max===void 0?b.tickInterval=1:j&&!m&&Y===b.linkedParent.options.tickPixelInterval?b.tickInterval=
b.linkedParent.tickInterval:(b.tickInterval=p(m,J?1:(b.max-b.min)*Y/t(b.len,Y)),!s(m)&&b.len<Y&&!this.isRadial&&!this.isLog&&!J&&e&&f&&(D=!0,b.tickInterval/=4));i&&!a&&q(b.series,function(a){a.processData(b.min!==b.oldMin||b.max!==b.oldMax)});b.setAxisTranslation(!0);b.beforeSetTickPositions&&b.beforeSetTickPositions();if(b.postProcessTickInterval)b.tickInterval=b.postProcessTickInterval(b.tickInterval);if(b.pointRange)b.tickInterval=t(b.pointRange,b.tickInterval);if(!m&&b.tickInterval<o)b.tickInterval=
o;if(!h&&!g&&!m)b.tickInterval=nb(b.tickInterval,null,mb(b.tickInterval),p(d.allowDecimals,!(b.tickInterval>1&&b.tickInterval<5&&b.max>1E3&&b.max<9999)));b.minorTickInterval=d.minorTickInterval==="auto"&&b.tickInterval?b.tickInterval/5:d.minorTickInterval;b.tickPositions=a=d.tickPositions?[].concat(d.tickPositions):k&&k.apply(b,[b.min,b.max]);if(!a)!b.ordinalPositions&&(b.max-b.min)/b.tickInterval>t(2*b.len,200)&&ha(19,!0),a=h?b.getTimeTicks(b.normalizeTimeTickInterval(b.tickInterval,d.units),b.min,
b.max,d.startOfWeek,b.ordinalPositions,b.closestPointRange,!0):g?b.getLogTickPositions(b.tickInterval,b.min,b.max):b.getLinearTickPositions(b.tickInterval,b.min,b.max),D&&a.splice(1,a.length-2),b.tickPositions=a;if(!j)d=a[0],g=a[a.length-1],h=b.minPointOffset||0,e?b.min=d:b.min-h>d&&a.shift(),f?b.max=g:b.max+h<g&&a.pop(),a.length===0&&s(d)&&a.push((g+d)/2),a.length===1&&(e=Q(b.max)>1E13?1:0.001,b.min-=e,b.max+=e)},setMaxTicks:function(){var a=this.chart,b=a.maxTicks||{},c=this.tickPositions,d=this._maxTicksKey=
[this.coll,this.pos,this.len].join("-");if(!this.isLinked&&!this.isDatetimeAxis&&c&&c.length>(b[d]||0)&&this.options.alignTicks!==!1)b[d]=c.length;a.maxTicks=b},adjustTickAmount:function(){var a=this._maxTicksKey,b=this.tickPositions,c=this.chart.maxTicks;if(c&&c[a]&&!this.isDatetimeAxis&&!this.categories&&!this.isLinked&&this.options.alignTicks!==!1&&this.min!==u){var d=this.tickAmount,e=b.length;this.tickAmount=a=c[a];if(e<a){for(;b.length<a;)b.push(ea(b[b.length-1]+this.tickInterval));this.transA*=
(e-1)/(a-1);this.max=b[b.length-1]}if(s(d)&&a!==d)this.isDirty=!0}},setScale:function(){var a=this.stacks,b,c,d,e;this.oldMin=this.min;this.oldMax=this.max;this.oldAxisLength=this.len;this.setAxisSize();e=this.len!==this.oldAxisLength;q(this.series,function(a){if(a.isDirtyData||a.isDirty||a.xAxis.isDirty)d=!0});if(e||d||this.isLinked||this.forceRedraw||this.userMin!==this.oldUserMin||this.userMax!==this.oldUserMax){if(!this.isXAxis)for(b in a)for(c in a[b])a[b][c].total=null,a[b][c].cum=0;this.forceRedraw=
!1;this.getSeriesExtremes();this.setTickPositions();this.oldUserMin=this.userMin;this.oldUserMax=this.userMax;if(!this.isDirty)this.isDirty=e||this.min!==this.oldMin||this.max!==this.oldMax}else if(!this.isXAxis){if(this.oldStacks)a=this.stacks=this.oldStacks;for(b in a)for(c in a[b])a[b][c].cum=a[b][c].total}this.setMaxTicks()},setExtremes:function(a,b,c,d,e){var f=this,g=f.chart,c=p(c,!0),e=r(e,{min:a,max:b});I(f,"setExtremes",e,function(){f.userMin=a;f.userMax=b;f.eventArgs=e;f.isDirtyExtremes=
!0;c&&g.redraw(d)})},zoom:function(a,b){var c=this.dataMin,d=this.dataMax,e=this.options;this.allowZoomOutside||(s(c)&&a<=L(c,p(e.min,c))&&(a=u),s(d)&&b>=t(d,p(e.max,d))&&(b=u));this.displayBtn=a!==u||b!==u;this.setExtremes(a,b,!1,u,{trigger:"zoom"});return!0},setAxisSize:function(){var a=this.chart,b=this.options,c=b.offsetLeft||0,d=this.horiz,e=p(b.width,a.plotWidth-c+(b.offsetRight||0)),f=p(b.height,a.plotHeight),g=p(b.top,a.plotTop),b=p(b.left,a.plotLeft+c),c=/%$/;c.test(f)&&(f=parseInt(f,10)/
100*a.plotHeight);c.test(g)&&(g=parseInt(g,10)/100*a.plotHeight+a.plotTop);this.left=b;this.top=g;this.width=e;this.height=f;this.bottom=a.chartHeight-f-g;this.right=a.chartWidth-e-b;this.len=t(d?e:f,0);this.pos=d?b:g},getExtremes:function(){var a=this.isLog;return{min:a?ea(ka(this.min)):this.min,max:a?ea(ka(this.max)):this.max,dataMin:this.dataMin,dataMax:this.dataMax,userMin:this.userMin,userMax:this.userMax}},getThreshold:function(a){var b=this.isLog,c=b?ka(this.min):this.min,b=b?ka(this.max):
this.max;c>a||a===null?a=c:b<a&&(a=b);return this.translate(a,0,1,0,1)},autoLabelAlign:function(a){a=(p(a,0)-this.side*90+720)%360;return a>15&&a<165?"right":a>195&&a<345?"left":"center"},getOffset:function(){var a=this,b=a.chart,c=b.renderer,d=a.options,e=a.tickPositions,f=a.ticks,g=a.horiz,h=a.side,i=b.inverted?[1,0,3,2][h]:h,j,k,l=0,n,m=0,o=d.title,Y=d.labels,D=0,J=b.axisOffset,b=b.clipOffset,C=[-1,1,1,-1][h],r,v=1,w=p(Y.maxStaggerLines,5),x,y,A,z,R;a.hasData=j=a.hasVisibleSeries||s(a.min)&&s(a.max)&&
!!e;a.showAxis=k=j||p(d.showEmpty,!0);a.staggerLines=a.horiz&&Y.staggerLines;if(!a.axisGroup)a.gridGroup=c.g("grid").attr({zIndex:d.gridZIndex||1}).add(),a.axisGroup=c.g("axis").attr({zIndex:d.zIndex||2}).add(),a.labelGroup=c.g("axis-labels").attr({zIndex:Y.zIndex||7}).addClass("highcharts-"+a.coll.toLowerCase()+"-labels").add();if(j||a.isLinked){a.labelAlign=p(Y.align||a.autoLabelAlign(Y.rotation));q(e,function(b){f[b]?f[b].addLabel():f[b]=new Ta(a,b)});if(a.horiz&&!a.staggerLines&&w&&!Y.rotation){for(j=
a.reversed?[].concat(e).reverse():e;v<w;){x=[];y=!1;for(r=0;r<j.length;r++)A=j[r],z=(z=f[A].label&&f[A].label.getBBox())?z.width:0,R=r%v,z&&(A=a.translate(A),x[R]!==u&&A<x[R]&&(y=!0),x[R]=A+z);if(y)v++;else break}if(v>1)a.staggerLines=v}q(e,function(b){if(h===0||h===2||{1:"left",3:"right"}[h]===a.labelAlign)D=t(f[b].getLabelSize(),D)});if(a.staggerLines)D*=a.staggerLines,a.labelOffset=D}else for(r in f)f[r].destroy(),delete f[r];if(o&&o.text&&o.enabled!==!1){if(!a.axisTitle)a.axisTitle=c.text(o.text,
0,0,o.useHTML).attr({zIndex:7,rotation:o.rotation||0,align:o.textAlign||{low:"left",middle:"center",high:"right"}[o.align]}).addClass("highcharts-"+this.coll.toLowerCase()+"-title").css(o.style).add(a.axisGroup),a.axisTitle.isNew=!0;if(k)l=a.axisTitle.getBBox()[g?"height":"width"],n=o.offset,m=s(n)?0:p(o.margin,g?5:10);a.axisTitle[k?"show":"hide"]()}a.offset=C*p(d.offset,J[h]);c=h===2?a.tickBaseline:0;g=D+m+(D&&C*(g?p(Y.y,a.tickBaseline+8):Y.x)-c);a.axisTitleMargin=p(n,g);J[h]=t(J[h],a.axisTitleMargin+
l+C*a.offset,g);b[i]=t(b[i],U(d.lineWidth/2)*2)},getLinePath:function(a){var b=this.chart,c=this.opposite,d=this.offset,e=this.horiz,f=this.left+(c?this.width:0)+d,d=b.chartHeight-this.bottom-(c?this.height:0)+d;c&&(a*=-1);return b.renderer.crispLine(["M",e?this.left:f,e?d:this.top,"L",e?b.chartWidth-this.right:f,e?d:b.chartHeight-this.bottom],a)},getTitlePosition:function(){var a=this.horiz,b=this.left,c=this.top,d=this.len,e=this.options.title,f=a?b:c,g=this.opposite,h=this.offset,i=y(e.style.fontSize||
12),d={low:f+(a?0:d),middle:f+d/2,high:f+(a?d:0)}[e.align],b=(a?c+this.height:b)+(a?1:-1)*(g?-1:1)*this.axisTitleMargin+(this.side===2?i:0);return{x:a?d:b+(g?this.width:0)+h+(e.x||0),y:a?b-(g?this.height:0)+h:d+(e.y||0)}},render:function(){var a=this,b=a.horiz,c=a.reversed,d=a.chart,e=d.renderer,f=a.options,g=a.isLog,h=a.isLinked,i=a.tickPositions,j,k=a.axisTitle,l=a.ticks,n=a.minorTicks,m=a.alternateBands,o=f.stackLabels,p=f.alternateGridColor,D=a.tickmarkOffset,J=f.lineWidth,C=d.hasRendered&&s(a.oldMin)&&
!isNaN(a.oldMin),r=a.hasData,t=a.showAxis,v,w=f.labels.overflow,x=a.justifyLabels=b&&w!==!1,A;a.labelEdge.length=0;a.justifyToPlot=w==="justify";q([l,n,m],function(a){for(var b in a)a[b].isActive=!1});if(r||h)if(a.minorTickInterval&&!a.categories&&q(a.getMinorTickPositions(),function(b){n[b]||(n[b]=new Ta(a,b,"minor"));C&&n[b].isNew&&n[b].render(null,!0);n[b].render(null,!1,1)}),i.length&&(j=i.slice(),(b&&c||!b&&!c)&&j.reverse(),x&&(j=j.slice(1).concat([j[0]])),q(j,function(b,c){x&&(c=c===j.length-
1?0:c+1);if(!h||b>=a.min&&b<=a.max)l[b]||(l[b]=new Ta(a,b)),C&&l[b].isNew&&l[b].render(c,!0,0.1),l[b].render(c)}),D&&a.min===0&&(l[-1]||(l[-1]=new Ta(a,-1,null,!0)),l[-1].render(-1))),p&&q(i,function(b,c){if(c%2===0&&b<a.max)m[b]||(m[b]=new K.PlotLineOrBand(a)),v=b+D,A=i[c+1]!==u?i[c+1]+D:a.max,m[b].options={from:g?ka(v):v,to:g?ka(A):A,color:p},m[b].render(),m[b].isActive=!0}),!a._addedPlotLB)q((f.plotLines||[]).concat(f.plotBands||[]),function(b){a.addPlotBandOrLine(b)}),a._addedPlotLB=!0;q([l,n,
m],function(a){var b,c,e=[],f=va?va.duration||500:0,g=function(){for(c=e.length;c--;)a[e[c]]&&!a[e[c]].isActive&&(a[e[c]].destroy(),delete a[e[c]])};for(b in a)if(!a[b].isActive)a[b].render(b,!1,0),a[b].isActive=!1,e.push(b);a===m||!d.hasRendered||!f?g():f&&setTimeout(g,f)});if(J)b=a.getLinePath(J),a.axisLine?a.axisLine.animate({d:b}):a.axisLine=e.path(b).attr({stroke:f.lineColor,"stroke-width":J,zIndex:7}).add(a.axisGroup),a.axisLine[t?"show":"hide"]();if(k&&t)k[k.isNew?"attr":"animate"](a.getTitlePosition()),
k.isNew=!1;o&&o.enabled&&a.renderStackTotals();a.isDirty=!1},redraw:function(){this.render();q(this.plotLinesAndBands,function(a){a.render()});q(this.series,function(a){a.isDirty=!0})},destroy:function(a){var b=this,c=b.stacks,d,e=b.plotLinesAndBands;a||X(b);for(d in c)Pa(c[d]),c[d]=null;q([b.ticks,b.minorTicks,b.alternateBands],function(a){Pa(a)});for(a=e.length;a--;)e[a].destroy();q("stackTotalGroup,axisLine,axisTitle,axisGroup,cross,gridGroup,labelGroup".split(","),function(a){b[a]&&(b[a]=b[a].destroy())});
this.cross&&this.cross.destroy()},drawCrosshair:function(a,b){if(this.crosshair)if((s(b)||!p(this.crosshair.snap,!0))===!1)this.hideCrosshair();else{var c,d=this.crosshair,e=d.animation;p(d.snap,!0)?s(b)&&(c=this.chart.inverted!=this.horiz?b.plotX:this.len-b.plotY):c=this.horiz?a.chartX-this.pos:this.len-a.chartY+this.pos;c=this.isRadial?this.getPlotLinePath(this.isXAxis?b.x:p(b.stackY,b.y)):this.getPlotLinePath(null,null,null,null,c);if(c===null)this.hideCrosshair();else if(this.cross)this.cross.attr({visibility:"visible"})[e?
"animate":"attr"]({d:c},e);else{e={"stroke-width":d.width||1,stroke:d.color||"#C0C0C0",zIndex:d.zIndex||2};if(d.dashStyle)e.dashstyle=d.dashStyle;this.cross=this.chart.renderer.path(c).attr(e).add()}}},hideCrosshair:function(){this.cross&&this.cross.hide()}};r(na.prototype,{getPlotBandPath:function(a,b){var c=this.getPlotLinePath(b),d=this.getPlotLinePath(a);d&&c?d.push(c[4],c[5],c[1],c[2]):d=null;return d},addPlotBand:function(a){return this.addPlotBandOrLine(a,"plotBands")},addPlotLine:function(a){return this.addPlotBandOrLine(a,
"plotLines")},addPlotBandOrLine:function(a,b){var c=(new K.PlotLineOrBand(this,a)).render(),d=this.userOptions;c&&(b&&(d[b]=d[b]||[],d[b].push(a)),this.plotLinesAndBands.push(c));return c},removePlotBandOrLine:function(a){for(var b=this.plotLinesAndBands,c=this.options,d=this.userOptions,e=b.length;e--;)b[e].id===a&&b[e].destroy();q([c.plotLines||[],d.plotLines||[],c.plotBands||[],d.plotBands||[]],function(b){for(e=b.length;e--;)b[e].id===a&&la(b,b[e])})}});na.prototype.getTimeTicks=function(a,b,
c,d){var e=[],f={},g=E.global.useUTC,h,i=new Da(b-Sa),j=a.unitRange,k=a.count;if(s(b)){j>=A.second&&(i.setMilliseconds(0),i.setSeconds(j>=A.minute?0:k*U(i.getSeconds()/k)));if(j>=A.minute)i[Cb](j>=A.hour?0:k*U(i[pb]()/k));if(j>=A.hour)i[Db](j>=A.day?0:k*U(i[qb]()/k));if(j>=A.day)i[sb](j>=A.month?1:k*U(i[Xa]()/k));j>=A.month&&(i[Eb](j>=A.year?0:k*U(i[fb]()/k)),h=i[gb]());j>=A.year&&(h-=h%k,i[Fb](h));if(j===A.week)i[sb](i[Xa]()-i[rb]()+p(d,1));b=1;Sa&&(i=new Da(i.getTime()+Sa));h=i[gb]();for(var d=
i.getTime(),l=i[fb](),n=i[Xa](),m=(A.day+(g?Sa:i.getTimezoneOffset()*6E4))%A.day;d<c;)e.push(d),j===A.year?d=eb(h+b*k,0):j===A.month?d=eb(h,l+b*k):!g&&(j===A.day||j===A.week)?d=eb(h,l,n+b*k*(j===A.day?1:7)):d+=j*k,b++;e.push(d);q(wb(e,function(a){return j<=A.hour&&a%A.day===m}),function(a){f[a]="day"})}e.info=r(a,{higherRanks:f,totalRange:j*k});return e};na.prototype.normalizeTimeTickInterval=function(a,b){var c=b||[["millisecond",[1,2,5,10,20,25,50,100,200,500]],["second",[1,2,5,10,15,30]],["minute",
[1,2,5,10,15,30]],["hour",[1,2,3,4,6,8,12]],["day",[1,2]],["week",[1,2]],["month",[1,2,3,4,6]],["year",null]],d=c[c.length-1],e=A[d[0]],f=d[1],g;for(g=0;g<c.length;g++)if(d=c[g],e=A[d[0]],f=d[1],c[g+1]&&a<=(e*f[f.length-1]+A[c[g+1][0]])/2)break;e===A.year&&a<5*e&&(f=[1,2,5]);c=nb(a/e,f,d[0]==="year"?t(mb(a/e),1):1);return{unitRange:e,count:c,unitName:d[0]}};na.prototype.getLogTickPositions=function(a,b,c,d){var e=this.options,f=this.len,g=[];if(!d)this._minorAutoInterval=null;if(a>=0.5)a=v(a),g=this.getLinearTickPositions(a,
b,c);else if(a>=0.08)for(var f=U(b),h,i,j,k,l,e=a>0.3?[1,2,4]:a>0.15?[1,2,4,6,8]:[1,2,3,4,5,6,7,8,9];f<c+1&&!l;f++){i=e.length;for(h=0;h<i&&!l;h++)j=za(ka(f)*e[h]),j>b&&(!d||k<=c)&&k!==u&&g.push(k),k>c&&(l=!0),k=j}else if(b=ka(b),c=ka(c),a=e[d?"minorTickInterval":"tickInterval"],a=p(a==="auto"?null:a,this._minorAutoInterval,(c-b)*(e.tickPixelInterval/(d?5:1))/((d?f/this.tickPositions.length:f)||1)),a=nb(a,null,mb(a)),g=Va(this.getLinearTickPositions(a,b,c),za),!d)this._minorAutoInterval=a/5;if(!d)this.tickInterval=
a;return g};var Nb=K.Tooltip=function(){this.init.apply(this,arguments)};Nb.prototype={init:function(a,b){var c=b.borderWidth,d=b.style,e=y(d.padding);this.chart=a;this.options=b;this.crosshairs=[];this.now={x:0,y:0};this.isHidden=!0;this.label=a.renderer.label("",0,0,b.shape||"callout",null,null,b.useHTML,null,"tooltip").attr({padding:e,fill:b.backgroundColor,"stroke-width":c,r:b.borderRadius,zIndex:8}).css(d).css({padding:0}).add().attr({y:-9999});ga||this.label.shadow(b.shadow);this.shared=b.shared},
destroy:function(){if(this.label)this.label=this.label.destroy();clearTimeout(this.hideTimer);clearTimeout(this.tooltipTimeout)},move:function(a,b,c,d){var e=this,f=e.now,g=e.options.animation!==!1&&!e.isHidden&&(Q(a-f.x)>1||Q(b-f.y)>1),h=e.followPointer||e.len>1;r(f,{x:g?(2*f.x+a)/3:a,y:g?(f.y+b)/2:b,anchorX:h?u:g?(2*f.anchorX+c)/3:c,anchorY:h?u:g?(f.anchorY+d)/2:d});e.label.attr(f);if(g)clearTimeout(this.tooltipTimeout),this.tooltipTimeout=setTimeout(function(){e&&e.move(a,b,c,d)},32)},hide:function(a){var b=
this,c;clearTimeout(this.hideTimer);if(!this.isHidden)c=this.chart.hoverPoints,this.hideTimer=setTimeout(function(){b.label.fadeOut();b.isHidden=!0},p(a,this.options.hideDelay,500)),c&&q(c,function(a){a.setState()}),this.chart.hoverPoints=null},getAnchor:function(a,b){var c,d=this.chart,e=d.inverted,f=d.plotTop,g=0,h=0,i,a=ra(a);c=a[0].tooltipPos;this.followPointer&&b&&(b.chartX===u&&(b=d.pointer.normalize(b)),c=[b.chartX-d.plotLeft,b.chartY-f]);c||(q(a,function(a){i=a.series.yAxis;g+=a.plotX;h+=
(a.plotLow?(a.plotLow+a.plotHigh)/2:a.plotY)+(!e&&i?i.top-f:0)}),g/=a.length,h/=a.length,c=[e?d.plotWidth-h:g,this.shared&&!e&&a.length>1&&b?b.chartY-f:e?d.plotHeight-g:h]);return Va(c,v)},getPosition:function(a,b,c){var d=this.chart,e=this.distance,f={},g,h=["y",d.chartHeight,b,c.plotY+d.plotTop],i=["x",d.chartWidth,a,c.plotX+d.plotLeft],j=c.ttBelow||d.inverted&&!c.negative||!d.inverted&&c.negative,k=function(a,b,c,d){var g=c<d-e,b=d+e+c<b,c=d-e-c;d+=e;if(j&&b)f[a]=d;else if(!j&&g)f[a]=c;else if(g)f[a]=
c;else if(b)f[a]=d;else return!1},l=function(a,b,c,d){if(d<e||d>b-e)return!1;else f[a]=d<c/2?1:d>b-c/2?b-c-2:d-c/2},n=function(a){var b=h;h=i;i=b;g=a},m=function(){k.apply(0,h)!==!1?l.apply(0,i)===!1&&!g&&(n(!0),m()):g?f.x=f.y=0:(n(!0),m())};(d.inverted||this.len>1)&&n();m();return f},defaultFormatter:function(a){var b=this.points||ra(this),c=b[0].series,d;d=[a.tooltipHeaderFormatter(b[0])];q(b,function(a){c=a.series;d.push(c.tooltipFormatter&&c.tooltipFormatter(a)||a.point.tooltipFormatter(c.tooltipOptions.pointFormat))});
d.push(a.options.footerFormat||"");return d.join("")},refresh:function(a,b){var c=this.chart,d=this.label,e=this.options,f,g,h={},i,j=[];i=e.formatter||this.defaultFormatter;var h=c.hoverPoints,k,l=this.shared;clearTimeout(this.hideTimer);this.followPointer=ra(a)[0].series.tooltipOptions.followPointer;g=this.getAnchor(a,b);f=g[0];g=g[1];l&&(!a.series||!a.series.noSharedTooltip)?(c.hoverPoints=a,h&&q(h,function(a){a.setState()}),q(a,function(a){a.setState("hover");j.push(a.getLabelConfig())}),h={x:a[0].category,
y:a[0].y},h.points=j,this.len=j.length,a=a[0]):h=a.getLabelConfig();i=i.call(h,this);h=a.series;this.distance=p(h.tooltipOptions.distance,16);i===!1?this.hide():(this.isHidden&&(bb(d),d.attr("opacity",1).show()),d.attr({text:i}),k=e.borderColor||a.color||h.color||"#606060",d.attr({stroke:k}),this.updatePosition({plotX:f,plotY:g,negative:a.negative,ttBelow:a.ttBelow}),this.isHidden=!1);I(c,"tooltipRefresh",{text:i,x:f+c.plotLeft,y:g+c.plotTop,borderColor:k})},updatePosition:function(a){var b=this.chart,
c=this.label,c=(this.options.positioner||this.getPosition).call(this,c.width,c.height,a);this.move(v(c.x),v(c.y),a.plotX+b.plotLeft,a.plotY+b.plotTop)},tooltipHeaderFormatter:function(a){var b=a.series,c=b.tooltipOptions,d=c.dateTimeLabelFormats,e=c.xDateFormat,f=b.xAxis,g=f&&f.options.type==="datetime"&&ja(a.key),c=c.headerFormat,f=f&&f.closestPointRange,h;if(g&&!e){if(f)for(h in A){if(A[h]>=f||A[h]<=A.day&&a.key%A[h]>0){e=d[h];break}}else e=d.day;e=e||d.year}g&&e&&(c=c.replace("{point.key}","{point.key:"+
e+"}"));return Ja(c,{point:a,series:b})}};var pa;$a=x.documentElement.ontouchstart!==u;var Wa=K.Pointer=function(a,b){this.init(a,b)};Wa.prototype={init:function(a,b){var c=b.chart,d=c.events,e=ga?"":c.zoomType,c=a.inverted,f;this.options=b;this.chart=a;this.zoomX=f=/x/.test(e);this.zoomY=e=/y/.test(e);this.zoomHor=f&&!c||e&&c;this.zoomVert=e&&!c||f&&c;this.hasZoom=f||e;this.runChartClick=d&&!!d.click;this.pinchDown=[];this.lastValidTouch={};if(K.Tooltip&&b.tooltip.enabled)a.tooltip=new Nb(a,b.tooltip),
this.followTouchMove=b.tooltip.followTouchMove;this.setDOMEvents()},normalize:function(a,b){var c,d,a=a||window.event,a=Tb(a);if(!a.target)a.target=a.srcElement;d=a.touches?a.touches.length?a.touches.item(0):a.changedTouches[0]:a;if(!b)this.chartPosition=b=Sb(this.chart.container);d.pageX===u?(c=t(a.x,a.clientX-b.left),d=a.y):(c=d.pageX-b.left,d=d.pageY-b.top);return r(a,{chartX:v(c),chartY:v(d)})},getCoordinates:function(a){var b={xAxis:[],yAxis:[]};q(this.chart.axes,function(c){b[c.isXAxis?"xAxis":
"yAxis"].push({axis:c,value:c.toValue(a[c.horiz?"chartX":"chartY"])})});return b},getIndex:function(a){var b=this.chart;return b.inverted?b.plotHeight+b.plotTop-a.chartY:a.chartX-b.plotLeft},runPointActions:function(a){var b=this.chart,c=b.series,d=b.tooltip,e,f,g=b.hoverPoint,h=b.hoverSeries,i,j,k=b.chartWidth,l=this.getIndex(a);if(d&&this.options.tooltip.shared&&(!h||!h.noSharedTooltip)){f=[];i=c.length;for(j=0;j<i;j++)if(c[j].visible&&c[j].options.enableMouseTracking!==!1&&!c[j].noSharedTooltip&&
c[j].singularTooltips!==!0&&c[j].tooltipPoints.length&&(e=c[j].tooltipPoints[l])&&e.series)e._dist=Q(l-e.clientX),k=L(k,e._dist),f.push(e);for(i=f.length;i--;)f[i]._dist>k&&f.splice(i,1);if(f.length&&f[0].clientX!==this.hoverX)d.refresh(f,a),this.hoverX=f[0].clientX}c=h&&h.tooltipOptions.followPointer;if(h&&h.tracker&&!c){if((e=h.tooltipPoints[l])&&e!==g)e.onMouseOver(a)}else d&&c&&!d.isHidden&&(h=d.getAnchor([{}],a),d.updatePosition({plotX:h[0],plotY:h[1]}));if(d&&!this._onDocumentMouseMove)this._onDocumentMouseMove=
function(a){if(W[pa])W[pa].pointer.onDocumentMouseMove(a)},N(x,"mousemove",this._onDocumentMouseMove);q(b.axes,function(b){b.drawCrosshair(a,p(e,g))})},reset:function(a,b){var c=this.chart,d=c.hoverSeries,e=c.hoverPoint,f=c.tooltip,g=f&&f.shared?c.hoverPoints:e;(a=a&&f&&g)&&ra(g)[0].plotX===u&&(a=!1);if(a)f.refresh(g),e&&e.setState(e.state,!0);else{if(e)e.onMouseOut();if(d)d.onMouseOut();f&&f.hide(b);if(this._onDocumentMouseMove)X(x,"mousemove",this._onDocumentMouseMove),this._onDocumentMouseMove=
null;q(c.axes,function(a){a.hideCrosshair()});this.hoverX=null}},scaleGroups:function(a,b){var c=this.chart,d;q(c.series,function(e){d=a||e.getPlotBox();e.xAxis&&e.xAxis.zoomEnabled&&(e.group.attr(d),e.markerGroup&&(e.markerGroup.attr(d),e.markerGroup.clip(b?c.clipRect:null)),e.dataLabelsGroup&&e.dataLabelsGroup.attr(d))});c.clipRect.attr(b||c.clipBox)},dragStart:function(a){var b=this.chart;b.mouseIsDown=a.type;b.cancelClick=!1;b.mouseDownX=this.mouseDownX=a.chartX;b.mouseDownY=this.mouseDownY=a.chartY},
drag:function(a){var b=this.chart,c=b.options.chart,d=a.chartX,e=a.chartY,f=this.zoomHor,g=this.zoomVert,h=b.plotLeft,i=b.plotTop,j=b.plotWidth,k=b.plotHeight,l,n=this.mouseDownX,m=this.mouseDownY,o=c.panKey&&a[c.panKey+"Key"];d<h?d=h:d>h+j&&(d=h+j);e<i?e=i:e>i+k&&(e=i+k);this.hasDragged=Math.sqrt(Math.pow(n-d,2)+Math.pow(m-e,2));if(this.hasDragged>10){l=b.isInsidePlot(n-h,m-i);if(b.hasCartesianSeries&&(this.zoomX||this.zoomY)&&l&&!o&&!this.selectionMarker)this.selectionMarker=b.renderer.rect(h,i,
f?1:j,g?1:k,0).attr({fill:c.selectionMarkerFill||"rgba(69,114,167,0.25)",zIndex:7}).add();this.selectionMarker&&f&&(d-=n,this.selectionMarker.attr({width:Q(d),x:(d>0?0:d)+n}));this.selectionMarker&&g&&(d=e-m,this.selectionMarker.attr({height:Q(d),y:(d>0?0:d)+m}));l&&!this.selectionMarker&&c.panning&&b.pan(a,c.panning)}},drop:function(a){var b=this.chart,c=this.hasPinched;if(this.selectionMarker){var d={xAxis:[],yAxis:[],originalEvent:a.originalEvent||a},e=this.selectionMarker,f=e.attr?e.attr("x"):
e.x,g=e.attr?e.attr("y"):e.y,h=e.attr?e.attr("width"):e.width,i=e.attr?e.attr("height"):e.height,j;if(this.hasDragged||c)q(b.axes,function(b){if(b.zoomEnabled){var c=b.horiz,e=a.type==="touchend"?b.minPixelPadding:0,m=b.toValue((c?f:g)+e),c=b.toValue((c?f+h:g+i)-e);!isNaN(m)&&!isNaN(c)&&(d[b.coll].push({axis:b,min:L(m,c),max:t(m,c)}),j=!0)}}),j&&I(b,"selection",d,function(a){b.zoom(r(a,c?{animation:!1}:null))});this.selectionMarker=this.selectionMarker.destroy();c&&this.scaleGroups()}if(b)B(b.container,
{cursor:b._cursor}),b.cancelClick=this.hasDragged>10,b.mouseIsDown=this.hasDragged=this.hasPinched=!1,this.pinchDown=[]},onContainerMouseDown:function(a){a=this.normalize(a);a.preventDefault&&a.preventDefault();this.dragStart(a)},onDocumentMouseUp:function(a){W[pa]&&W[pa].pointer.drop(a)},onDocumentMouseMove:function(a){var b=this.chart,c=this.chartPosition,d=b.hoverSeries,a=this.normalize(a,c);c&&d&&!this.inClass(a.target,"highcharts-tracker")&&!b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop)&&
this.reset()},onContainerMouseLeave:function(){var a=W[pa];if(a)a.pointer.reset(),a.pointer.chartPosition=null},onContainerMouseMove:function(a){var b=this.chart;pa=b.index;a=this.normalize(a);a.returnValue=!1;b.mouseIsDown==="mousedown"&&this.drag(a);(this.inClass(a.target,"highcharts-tracker")||b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop))&&!b.openMenu&&this.runPointActions(a)},inClass:function(a,b){for(var c;a;){if(c=F(a,"class"))if(c.indexOf(b)!==-1)return!0;else if(c.indexOf("highcharts-container")!==
-1)return!1;a=a.parentNode}},onTrackerMouseOut:function(a){var b=this.chart.hoverSeries,c=(a=a.relatedTarget||a.toElement)&&a.point&&a.point.series;if(b&&!b.options.stickyTracking&&!this.inClass(a,"highcharts-tooltip")&&c!==b)b.onMouseOut()},onContainerClick:function(a){var b=this.chart,c=b.hoverPoint,d=b.plotLeft,e=b.plotTop,a=this.normalize(a);a.cancelBubble=!0;b.cancelClick||(c&&this.inClass(a.target,"highcharts-tracker")?(I(c.series,"click",r(a,{point:c})),b.hoverPoint&&c.firePointEvent("click",
a)):(r(a,this.getCoordinates(a)),b.isInsidePlot(a.chartX-d,a.chartY-e)&&I(b,"click",a)))},setDOMEvents:function(){var a=this,b=a.chart.container;b.onmousedown=function(b){a.onContainerMouseDown(b)};b.onmousemove=function(b){a.onContainerMouseMove(b)};b.onclick=function(b){a.onContainerClick(b)};N(b,"mouseleave",a.onContainerMouseLeave);ab===1&&N(x,"mouseup",a.onDocumentMouseUp);if($a)b.ontouchstart=function(b){a.onContainerTouchStart(b)},b.ontouchmove=function(b){a.onContainerTouchMove(b)},ab===1&&
N(x,"touchend",a.onDocumentTouchEnd)},destroy:function(){var a;X(this.chart.container,"mouseleave",this.onContainerMouseLeave);ab||(X(x,"mouseup",this.onDocumentMouseUp),X(x,"touchend",this.onDocumentTouchEnd));clearInterval(this.tooltipTimeout);for(a in this)this[a]=null}};r(K.Pointer.prototype,{pinchTranslate:function(a,b,c,d,e,f){(this.zoomHor||this.pinchHor)&&this.pinchTranslateDirection(!0,a,b,c,d,e,f);(this.zoomVert||this.pinchVert)&&this.pinchTranslateDirection(!1,a,b,c,d,e,f)},pinchTranslateDirection:function(a,
b,c,d,e,f,g,h){var i=this.chart,j=a?"x":"y",k=a?"X":"Y",l="chart"+k,n=a?"width":"height",m=i["plot"+(a?"Left":"Top")],o,p,q=h||1,r=i.inverted,C=i.bounds[a?"h":"v"],t=b.length===1,s=b[0][l],v=c[0][l],u=!t&&b[1][l],w=!t&&c[1][l],x,c=function(){!t&&Q(s-u)>20&&(q=h||Q(v-w)/Q(s-u));p=(m-v)/q+s;o=i["plot"+(a?"Width":"Height")]/q};c();b=p;b<C.min?(b=C.min,x=!0):b+o>C.max&&(b=C.max-o,x=!0);x?(v-=0.8*(v-g[j][0]),t||(w-=0.8*(w-g[j][1])),c()):g[j]=[v,w];r||(f[j]=p-m,f[n]=o);f=r?1/q:q;e[n]=o;e[j]=b;d[r?a?"scaleY":
"scaleX":"scale"+k]=q;d["translate"+k]=f*m+(v-f*s)},pinch:function(a){var b=this,c=b.chart,d=b.pinchDown,e=b.followTouchMove,f=a.touches,g=f.length,h=b.lastValidTouch,i=b.hasZoom,j=b.selectionMarker,k={},l=g===1&&(b.inClass(a.target,"highcharts-tracker")&&c.runTrackerClick||b.runChartClick),n={};(i||e)&&!l&&a.preventDefault();Va(f,function(a){return b.normalize(a)});if(a.type==="touchstart")q(f,function(a,b){d[b]={chartX:a.chartX,chartY:a.chartY}}),h.x=[d[0].chartX,d[1]&&d[1].chartX],h.y=[d[0].chartY,
d[1]&&d[1].chartY],q(c.axes,function(a){if(a.zoomEnabled){var b=c.bounds[a.horiz?"h":"v"],d=a.minPixelPadding,e=a.toPixels(p(a.options.min,a.dataMin)),f=a.toPixels(p(a.options.max,a.dataMax)),g=L(e,f),e=t(e,f);b.min=L(a.pos,g-d);b.max=t(a.pos+a.len,e+d)}}),b.res=!0;else if(d.length){if(!j)b.selectionMarker=j=r({destroy:sa},c.plotBox);b.pinchTranslate(d,f,k,j,n,h);b.hasPinched=i;b.scaleGroups(k,n);if(!i&&e&&g===1)this.runPointActions(b.normalize(a));else if(b.res)b.res=!1,this.reset(!1,0)}},onContainerTouchStart:function(a){var b=
this.chart;pa=b.index;a.touches.length===1?(a=this.normalize(a),b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop)?(this.runPointActions(a),this.pinch(a)):this.reset()):a.touches.length===2&&this.pinch(a)},onContainerTouchMove:function(a){(a.touches.length===1||a.touches.length===2)&&this.pinch(a)},onDocumentTouchEnd:function(a){W[pa]&&W[pa].pointer.drop(a)}});if(G.PointerEvent||G.MSPointerEvent){var ua={},zb=!!G.PointerEvent,Xb=function(){var a,b=[];b.item=function(a){return this[a]};for(a in ua)ua.hasOwnProperty(a)&&
b.push({pageX:ua[a].pageX,pageY:ua[a].pageY,target:ua[a].target});return b},Ab=function(a,b,c,d){a=a.originalEvent||a;if((a.pointerType==="touch"||a.pointerType===a.MSPOINTER_TYPE_TOUCH)&&W[pa])d(a),d=W[pa].pointer,d[b]({type:c,target:a.currentTarget,preventDefault:sa,touches:Xb()})};r(Wa.prototype,{onContainerPointerDown:function(a){Ab(a,"onContainerTouchStart","touchstart",function(a){ua[a.pointerId]={pageX:a.pageX,pageY:a.pageY,target:a.currentTarget}})},onContainerPointerMove:function(a){Ab(a,
"onContainerTouchMove","touchmove",function(a){ua[a.pointerId]={pageX:a.pageX,pageY:a.pageY};if(!ua[a.pointerId].target)ua[a.pointerId].target=a.currentTarget})},onDocumentPointerUp:function(a){Ab(a,"onContainerTouchEnd","touchend",function(a){delete ua[a.pointerId]})},batchMSEvents:function(a){a(this.chart.container,zb?"pointerdown":"MSPointerDown",this.onContainerPointerDown);a(this.chart.container,zb?"pointermove":"MSPointerMove",this.onContainerPointerMove);a(x,zb?"pointerup":"MSPointerUp",this.onDocumentPointerUp)}});
Na(Wa.prototype,"init",function(a,b,c){a.call(this,b,c);(this.hasZoom||this.followTouchMove)&&B(b.container,{"-ms-touch-action":P,"touch-action":P})});Na(Wa.prototype,"setDOMEvents",function(a){a.apply(this);(this.hasZoom||this.followTouchMove)&&this.batchMSEvents(N)});Na(Wa.prototype,"destroy",function(a){this.batchMSEvents(X);a.call(this)})}var lb=K.Legend=function(a,b){this.init(a,b)};lb.prototype={init:function(a,b){var c=this,d=b.itemStyle,e=p(b.padding,8),f=b.itemMarginTop||0;this.options=b;
if(b.enabled)c.itemStyle=d,c.itemHiddenStyle=w(d,b.itemHiddenStyle),c.itemMarginTop=f,c.padding=e,c.initialItemX=e,c.initialItemY=e-5,c.maxItemWidth=0,c.chart=a,c.itemHeight=0,c.lastLineHeight=0,c.symbolWidth=p(b.symbolWidth,16),c.pages=[],c.render(),N(c.chart,"endResize",function(){c.positionCheckboxes()})},colorizeItem:function(a,b){var c=this.options,d=a.legendItem,e=a.legendLine,f=a.legendSymbol,g=this.itemHiddenStyle.color,c=b?c.itemStyle.color:g,h=b?a.legendColor||a.color||"#CCC":g,g=a.options&&
a.options.marker,i={fill:h},j;d&&d.css({fill:c,color:c});e&&e.attr({stroke:h});if(f){if(g&&f.isMarker)for(j in i.stroke=h,g=a.convertAttribs(g),g)d=g[j],d!==u&&(i[j]=d);f.attr(i)}},positionItem:function(a){var b=this.options,c=b.symbolPadding,b=!b.rtl,d=a._legendItemPos,e=d[0],d=d[1],f=a.checkbox;a.legendGroup&&a.legendGroup.translate(b?e:this.legendWidth-e-2*c-4,d);if(f)f.x=e,f.y=d},destroyItem:function(a){var b=a.checkbox;q(["legendItem","legendLine","legendSymbol","legendGroup"],function(b){a[b]&&
(a[b]=a[b].destroy())});b&&Qa(a.checkbox)},destroy:function(){var a=this.group,b=this.box;if(b)this.box=b.destroy();if(a)this.group=a.destroy()},positionCheckboxes:function(a){var b=this.group.alignAttr,c,d=this.clipHeight||this.legendHeight;if(b)c=b.translateY,q(this.allItems,function(e){var f=e.checkbox,g;f&&(g=c+f.y+(a||0)+3,B(f,{left:b.translateX+e.checkboxOffset+f.x-20+"px",top:g+"px",display:g>c-6&&g<c+d-6?"":P}))})},renderTitle:function(){var a=this.padding,b=this.options.title,c=0;if(b.text){if(!this.title)this.title=
this.chart.renderer.label(b.text,a-3,a-4,null,null,null,null,null,"legend-title").attr({zIndex:1}).css(b.style).add(this.group);a=this.title.getBBox();c=a.height;this.offsetWidth=a.width;this.contentGroup.attr({translateY:c})}this.titleHeight=c},renderItem:function(a){var b=this.chart,c=b.renderer,d=this.options,e=d.layout==="horizontal",f=this.symbolWidth,g=d.symbolPadding,h=this.itemStyle,i=this.itemHiddenStyle,j=this.padding,k=e?p(d.itemDistance,20):0,l=!d.rtl,n=d.width,m=d.itemMarginBottom||0,
o=this.itemMarginTop,q=this.initialItemX,r=a.legendItem,s=a.series&&a.series.drawLegendSymbol?a.series:a,C=s.options,C=this.createCheckboxForItem&&C&&C.showCheckbox,u=d.useHTML;if(!r){a.legendGroup=c.g("legend-item").attr({zIndex:1}).add(this.scrollGroup);a.legendItem=r=c.text(d.labelFormat?Ja(d.labelFormat,a):d.labelFormatter.call(a),l?f+g:-g,this.baseline||0,u).css(w(a.visible?h:i)).attr({align:l?"left":"right",zIndex:2}).add(a.legendGroup);if(!this.baseline)this.baseline=c.fontMetrics(h.fontSize,
r).f+3+o,r.attr("y",this.baseline);s.drawLegendSymbol(this,a);this.setItemEvents&&this.setItemEvents(a,r,u,h,i);this.colorizeItem(a,a.visible);C&&this.createCheckboxForItem(a)}c=r.getBBox();f=a.checkboxOffset=d.itemWidth||a.legendItemWidth||f+g+c.width+k+(C?20:0);this.itemHeight=g=v(a.legendItemHeight||c.height);if(e&&this.itemX-q+f>(n||b.chartWidth-2*j-q-d.x))this.itemX=q,this.itemY+=o+this.lastLineHeight+m,this.lastLineHeight=0;this.maxItemWidth=t(this.maxItemWidth,f);this.lastItemY=o+this.itemY+
m;this.lastLineHeight=t(g,this.lastLineHeight);a._legendItemPos=[this.itemX,this.itemY];e?this.itemX+=f:(this.itemY+=o+g+m,this.lastLineHeight=g);this.offsetWidth=n||t((e?this.itemX-q-k:f)+j,this.offsetWidth)},getAllItems:function(){var a=[];q(this.chart.series,function(b){var c=b.options;if(p(c.showInLegend,!s(c.linkedTo)?u:!1,!0))a=a.concat(b.legendItems||(c.legendType==="point"?b.data:b))});return a},render:function(){var a=this,b=a.chart,c=b.renderer,d=a.group,e,f,g,h,i=a.box,j=a.options,k=a.padding,
l=j.borderWidth,n=j.backgroundColor;a.itemX=a.initialItemX;a.itemY=a.initialItemY;a.offsetWidth=0;a.lastItemY=0;if(!d)a.group=d=c.g("legend").attr({zIndex:7}).add(),a.contentGroup=c.g().attr({zIndex:1}).add(d),a.scrollGroup=c.g().add(a.contentGroup);a.renderTitle();e=a.getAllItems();ob(e,function(a,b){return(a.options&&a.options.legendIndex||0)-(b.options&&b.options.legendIndex||0)});j.reversed&&e.reverse();a.allItems=e;a.display=f=!!e.length;q(e,function(b){a.renderItem(b)});g=j.width||a.offsetWidth;
h=a.lastItemY+a.lastLineHeight+a.titleHeight;h=a.handleOverflow(h);if(l||n){g+=k;h+=k;if(i){if(g>0&&h>0)i[i.isNew?"attr":"animate"](i.crisp({width:g,height:h})),i.isNew=!1}else a.box=i=c.rect(0,0,g,h,j.borderRadius,l||0).attr({stroke:j.borderColor,"stroke-width":l||0,fill:n||P}).add(d).shadow(j.shadow),i.isNew=!0;i[f?"show":"hide"]()}a.legendWidth=g;a.legendHeight=h;q(e,function(b){a.positionItem(b)});f&&d.align(r({width:g,height:h},j),!0,"spacingBox");b.isResizing||this.positionCheckboxes()},handleOverflow:function(a){var b=
this,c=this.chart,d=c.renderer,e=this.options,f=e.y,f=c.spacingBox.height+(e.verticalAlign==="top"?-f:f)-this.padding,g=e.maxHeight,h,i=this.clipRect,j=e.navigation,k=p(j.animation,!0),l=j.arrowSize||12,n=this.nav,m=this.pages,o,r=this.allItems;e.layout==="horizontal"&&(f/=2);g&&(f=L(f,g));m.length=0;if(a>f&&!e.useHTML){this.clipHeight=h=t(f-20-this.titleHeight-this.padding,0);this.currentPage=p(this.currentPage,1);this.fullHeight=a;q(r,function(a,b){var c=a._legendItemPos[1],d=v(a.legendItem.getBBox().height),
e=m.length;if(!e||c-m[e-1]>h&&(o||c)!==m[e-1])m.push(o||c),e++;b===r.length-1&&c+d-m[e-1]>h&&m.push(c);c!==o&&(o=c)});if(!i)i=b.clipRect=d.clipRect(0,this.padding,9999,0),b.contentGroup.clip(i);i.attr({height:h});if(!n)this.nav=n=d.g().attr({zIndex:1}).add(this.group),this.up=d.symbol("triangle",0,0,l,l).on("click",function(){b.scroll(-1,k)}).add(n),this.pager=d.text("",15,10).css(j.style).add(n),this.down=d.symbol("triangle-down",0,0,l,l).on("click",function(){b.scroll(1,k)}).add(n);b.scroll(0);
a=f}else if(n)i.attr({height:c.chartHeight}),n.hide(),this.scrollGroup.attr({translateY:1}),this.clipHeight=0;return a},scroll:function(a,b){var c=this.pages,d=c.length,e=this.currentPage+a,f=this.clipHeight,g=this.options.navigation,h=g.activeColor,g=g.inactiveColor,i=this.pager,j=this.padding;e>d&&(e=d);if(e>0)b!==u&&Ra(b,this.chart),this.nav.attr({translateX:j,translateY:f+this.padding+7+this.titleHeight,visibility:"visible"}),this.up.attr({fill:e===1?g:h}).css({cursor:e===1?"default":"pointer"}),
i.attr({text:e+"/"+d}),this.down.attr({x:18+this.pager.getBBox().width,fill:e===d?g:h}).css({cursor:e===d?"default":"pointer"}),c=-c[e-1]+this.initialItemY,this.scrollGroup.animate({translateY:c}),this.currentPage=e,this.positionCheckboxes(c)}};M=K.LegendSymbolMixin={drawRectangle:function(a,b){var c=a.options.symbolHeight||12;b.legendSymbol=this.chart.renderer.rect(0,a.baseline-5-c/2,a.symbolWidth,c,a.options.symbolRadius||0).attr({zIndex:3}).add(b.legendGroup)},drawLineMarker:function(a){var b=
this.options,c=b.marker,d;d=a.symbolWidth;var e=this.chart.renderer,f=this.legendGroup,a=a.baseline-v(e.fontMetrics(a.options.itemStyle.fontSize,this.legendItem).b*0.3),g;if(b.lineWidth){g={"stroke-width":b.lineWidth};if(b.dashStyle)g.dashstyle=b.dashStyle;this.legendLine=e.path(["M",0,a,"L",d,a]).attr(g).add(f)}if(c&&c.enabled!==!1)b=c.radius,this.legendSymbol=d=e.symbol(this.symbol,d/2-b,a-b,2*b,2*b).add(f),d.isMarker=!0}};(/Trident\/7\.0/.test(wa)||Ua)&&Na(lb.prototype,"positionItem",function(a,
b){var c=this,d=function(){b._legendItemPos&&a.call(c,b)};d();setTimeout(d)});Ya.prototype={init:function(a,b){var c,d=a.series;a.series=null;c=w(E,a);c.series=a.series=d;this.userOptions=a;d=c.chart;this.margin=this.splashArray("margin",d);this.spacing=this.splashArray("spacing",d);var e=d.events;this.bounds={h:{},v:{}};this.callback=b;this.isResizing=0;this.options=c;this.axes=[];this.series=[];this.hasCartesianSeries=d.showAxes;var f=this,g;f.index=W.length;W.push(f);ab++;d.reflow!==!1&&N(f,"load",
function(){f.initReflow()});if(e)for(g in e)N(f,g,e[g]);f.xAxis=[];f.yAxis=[];f.animation=ga?!1:p(d.animation,!0);f.pointCount=f.colorCounter=f.symbolCounter=0;f.firstRender()},initSeries:function(a){var b=this.options.chart;(b=H[a.type||b.type||b.defaultSeriesType])||ha(17,!0);b=new b;b.init(this,a);return b},isInsidePlot:function(a,b,c){var d=c?b:a,a=c?a:b;return d>=0&&d<=this.plotWidth&&a>=0&&a<=this.plotHeight},adjustTickAmounts:function(){this.options.chart.alignTicks!==!1&&q(this.axes,function(a){a.adjustTickAmount()});
this.maxTicks=null},redraw:function(a){var b=this.axes,c=this.series,d=this.pointer,e=this.legend,f=this.isDirtyLegend,g,h,i=this.hasCartesianSeries,j=this.isDirtyBox,k=c.length,l=k,n=this.renderer,m=n.isHidden(),o=[];Ra(a,this);m&&this.cloneRenderTo();for(this.layOutTitles();l--;)if(a=c[l],a.options.stacking&&(g=!0,a.isDirty)){h=!0;break}if(h)for(l=k;l--;)if(a=c[l],a.options.stacking)a.isDirty=!0;q(c,function(a){a.isDirty&&a.options.legendType==="point"&&(f=!0)});if(f&&e.options.enabled)e.render(),
this.isDirtyLegend=!1;g&&this.getStacks();if(i){if(!this.isResizing)this.maxTicks=null,q(b,function(a){a.setScale()});this.adjustTickAmounts()}this.getMargins();i&&(q(b,function(a){a.isDirty&&(j=!0)}),q(b,function(a){if(a.isDirtyExtremes)a.isDirtyExtremes=!1,o.push(function(){I(a,"afterSetExtremes",r(a.eventArgs,a.getExtremes()));delete a.eventArgs});(j||g)&&a.redraw()}));j&&this.drawChartBox();q(c,function(a){a.isDirty&&a.visible&&(!a.isCartesian||a.xAxis)&&a.redraw()});d&&d.reset(!0);n.draw();I(this,
"redraw");m&&this.cloneRenderTo(!0);q(o,function(a){a.call()})},get:function(a){var b=this.axes,c=this.series,d,e;for(d=0;d<b.length;d++)if(b[d].options.id===a)return b[d];for(d=0;d<c.length;d++)if(c[d].options.id===a)return c[d];for(d=0;d<c.length;d++){e=c[d].points||[];for(b=0;b<e.length;b++)if(e[b].id===a)return e[b]}return null},getAxes:function(){var a=this,b=this.options,c=b.xAxis=ra(b.xAxis||{}),b=b.yAxis=ra(b.yAxis||{});q(c,function(a,b){a.index=b;a.isX=!0});q(b,function(a,b){a.index=b});
c=c.concat(b);q(c,function(b){new na(a,b)});a.adjustTickAmounts()},getSelectedPoints:function(){var a=[];q(this.series,function(b){a=a.concat(wb(b.points||[],function(a){return a.selected}))});return a},getSelectedSeries:function(){return wb(this.series,function(a){return a.selected})},getStacks:function(){var a=this;q(a.yAxis,function(a){if(a.stacks&&a.hasVisibleSeries)a.oldStacks=a.stacks});q(a.series,function(b){if(b.options.stacking&&(b.visible===!0||a.options.chart.ignoreHiddenSeries===!1))b.stackKey=
b.type+p(b.options.stack,"")})},setTitle:function(a,b,c){var g;var d=this,e=d.options,f;f=e.title=w(e.title,a);g=e.subtitle=w(e.subtitle,b),e=g;q([["title",a,f],["subtitle",b,e]],function(a){var b=a[0],c=d[b],e=a[1],a=a[2];c&&e&&(d[b]=c=c.destroy());a&&a.text&&!c&&(d[b]=d.renderer.text(a.text,0,0,a.useHTML).attr({align:a.align,"class":"highcharts-"+b,zIndex:a.zIndex||4}).css(a.style).add())});d.layOutTitles(c)},layOutTitles:function(a){var b=0,c=this.title,d=this.subtitle,e=this.options,f=e.title,
e=e.subtitle,g=this.renderer,h=this.spacingBox.width-44;if(c&&(c.css({width:(f.width||h)+"px"}).align(r({y:g.fontMetrics(f.style.fontSize,c).b-3},f),!1,"spacingBox"),!f.floating&&!f.verticalAlign))b=c.getBBox().height;d&&(d.css({width:(e.width||h)+"px"}).align(r({y:b+(f.margin-13)+g.fontMetrics(f.style.fontSize,d).b},e),!1,"spacingBox"),!e.floating&&!e.verticalAlign&&(b=La(b+d.getBBox().height)));c=this.titleOffset!==b;this.titleOffset=b;if(!this.isDirtyBox&&c)this.isDirtyBox=c,this.hasRendered&&
p(a,!0)&&this.isDirtyBox&&this.redraw()},getChartSize:function(){var a=this.options.chart,b=a.width,a=a.height,c=this.renderToClone||this.renderTo;if(!s(b))this.containerWidth=ib(c,"width");if(!s(a))this.containerHeight=ib(c,"height");this.chartWidth=t(0,b||this.containerWidth||600);this.chartHeight=t(0,p(a,this.containerHeight>19?this.containerHeight:400))},cloneRenderTo:function(a){var b=this.renderToClone,c=this.container;a?b&&(this.renderTo.appendChild(c),Qa(b),delete this.renderToClone):(c&&
c.parentNode===this.renderTo&&this.renderTo.removeChild(c),this.renderToClone=b=this.renderTo.cloneNode(0),B(b,{position:"absolute",top:"-9999px",display:"block"}),b.style.setProperty&&b.style.setProperty("display","block","important"),x.body.appendChild(b),c&&b.appendChild(c))},getContainer:function(){var a,b=this.options.chart,c,d,e;this.renderTo=a=b.renderTo;e="highcharts-"+ub++;if(Ga(a))this.renderTo=a=x.getElementById(a);a||ha(13,!0);c=y(F(a,"data-highcharts-chart"));!isNaN(c)&&W[c]&&W[c].hasRendered&&
W[c].destroy();F(a,"data-highcharts-chart",this.index);a.innerHTML="";!b.skipClone&&!a.offsetWidth&&this.cloneRenderTo();this.getChartSize();c=this.chartWidth;d=this.chartHeight;this.container=a=$(Ka,{className:"highcharts-container"+(b.className?" "+b.className:""),id:e},r({position:"relative",overflow:"hidden",width:c+"px",height:d+"px",textAlign:"left",lineHeight:"normal",zIndex:0,"-webkit-tap-highlight-color":"rgba(0,0,0,0)"},b.style),this.renderToClone||a);this._cursor=a.style.cursor;this.renderer=
b.forExport?new ta(a,c,d,b.style,!0):new Za(a,c,d,b.style);ga&&this.renderer.create(this,a,c,d)},getMargins:function(){var a=this.spacing,b,c=this.legend,d=this.margin,e=this.options.legend,f=p(e.margin,20),g=e.x,h=e.y,i=e.align,j=e.verticalAlign,k=this.titleOffset;this.resetMargins();b=this.axisOffset;if(k&&!s(d[0]))this.plotTop=t(this.plotTop,k+this.options.title.margin+a[0]);if(c.display&&!e.floating)if(i==="right"){if(!s(d[1]))this.marginRight=t(this.marginRight,c.legendWidth-g+f+a[1])}else if(i===
"left"){if(!s(d[3]))this.plotLeft=t(this.plotLeft,c.legendWidth+g+f+a[3])}else if(j==="top"){if(!s(d[0]))this.plotTop=t(this.plotTop,c.legendHeight+h+f+a[0])}else if(j==="bottom"&&!s(d[2]))this.marginBottom=t(this.marginBottom,c.legendHeight-h+f+a[2]);this.extraBottomMargin&&(this.marginBottom+=this.extraBottomMargin);this.extraTopMargin&&(this.plotTop+=this.extraTopMargin);this.hasCartesianSeries&&q(this.axes,function(a){a.getOffset()});s(d[3])||(this.plotLeft+=b[3]);s(d[0])||(this.plotTop+=b[0]);
s(d[2])||(this.marginBottom+=b[2]);s(d[1])||(this.marginRight+=b[1]);this.setChartSize()},reflow:function(a){var b=this,c=b.options.chart,d=b.renderTo,e=c.width||ib(d,"width"),f=c.height||ib(d,"height"),c=a?a.target:G,d=function(){if(b.container)b.setSize(e,f,!1),b.hasUserSize=null};if(!b.hasUserSize&&e&&f&&(c===G||c===x)){if(e!==b.containerWidth||f!==b.containerHeight)clearTimeout(b.reflowTimeout),a?b.reflowTimeout=setTimeout(d,100):d();b.containerWidth=e;b.containerHeight=f}},initReflow:function(){var a=
this,b=function(b){a.reflow(b)};N(G,"resize",b);N(a,"destroy",function(){X(G,"resize",b)})},setSize:function(a,b,c){var d=this,e,f,g;d.isResizing+=1;g=function(){d&&I(d,"endResize",null,function(){d.isResizing-=1})};Ra(c,d);d.oldChartHeight=d.chartHeight;d.oldChartWidth=d.chartWidth;if(s(a))d.chartWidth=e=t(0,v(a)),d.hasUserSize=!!e;if(s(b))d.chartHeight=f=t(0,v(b));(va?jb:B)(d.container,{width:e+"px",height:f+"px"},va);d.setChartSize(!0);d.renderer.setSize(e,f,c);d.maxTicks=null;q(d.axes,function(a){a.isDirty=
!0;a.setScale()});q(d.series,function(a){a.isDirty=!0});d.isDirtyLegend=!0;d.isDirtyBox=!0;d.layOutTitles();d.getMargins();d.redraw(c);d.oldChartHeight=null;I(d,"resize");va===!1?g():setTimeout(g,va&&va.duration||500)},setChartSize:function(a){var b=this.inverted,c=this.renderer,d=this.chartWidth,e=this.chartHeight,f=this.options.chart,g=this.spacing,h=this.clipOffset,i,j,k,l;this.plotLeft=i=v(this.plotLeft);this.plotTop=j=v(this.plotTop);this.plotWidth=k=t(0,v(d-i-this.marginRight));this.plotHeight=
l=t(0,v(e-j-this.marginBottom));this.plotSizeX=b?l:k;this.plotSizeY=b?k:l;this.plotBorderWidth=f.plotBorderWidth||0;this.spacingBox=c.spacingBox={x:g[3],y:g[0],width:d-g[3]-g[1],height:e-g[0]-g[2]};this.plotBox=c.plotBox={x:i,y:j,width:k,height:l};d=2*U(this.plotBorderWidth/2);b=La(t(d,h[3])/2);c=La(t(d,h[0])/2);this.clipBox={x:b,y:c,width:U(this.plotSizeX-t(d,h[1])/2-b),height:t(0,U(this.plotSizeY-t(d,h[2])/2-c))};a||q(this.axes,function(a){a.setAxisSize();a.setAxisTranslation()})},resetMargins:function(){var a=
this.spacing,b=this.margin;this.plotTop=p(b[0],a[0]);this.marginRight=p(b[1],a[1]);this.marginBottom=p(b[2],a[2]);this.plotLeft=p(b[3],a[3]);this.axisOffset=[0,0,0,0];this.clipOffset=[0,0,0,0]},drawChartBox:function(){var a=this.options.chart,b=this.renderer,c=this.chartWidth,d=this.chartHeight,e=this.chartBackground,f=this.plotBackground,g=this.plotBorder,h=this.plotBGImage,i=a.borderWidth||0,j=a.backgroundColor,k=a.plotBackgroundColor,l=a.plotBackgroundImage,n=a.plotBorderWidth||0,m,o=this.plotLeft,
p=this.plotTop,q=this.plotWidth,r=this.plotHeight,t=this.plotBox,s=this.clipRect,v=this.clipBox;m=i+(a.shadow?8:0);if(i||j)if(e)e.animate(e.crisp({width:c-m,height:d-m}));else{e={fill:j||P};if(i)e.stroke=a.borderColor,e["stroke-width"]=i;this.chartBackground=b.rect(m/2,m/2,c-m,d-m,a.borderRadius,i).attr(e).addClass("highcharts-background").add().shadow(a.shadow)}if(k)f?f.animate(t):this.plotBackground=b.rect(o,p,q,r,0).attr({fill:k}).add().shadow(a.plotShadow);if(l)h?h.animate(t):this.plotBGImage=
b.image(l,o,p,q,r).add();s?s.animate({width:v.width,height:v.height}):this.clipRect=b.clipRect(v);if(n)g?g.animate(g.crisp({x:o,y:p,width:q,height:r,strokeWidth:-n})):this.plotBorder=b.rect(o,p,q,r,0,-n).attr({stroke:a.plotBorderColor,"stroke-width":n,fill:P,zIndex:1}).add();this.isDirtyBox=!1},propFromSeries:function(){var a=this,b=a.options.chart,c,d=a.options.series,e,f;q(["inverted","angular","polar"],function(g){c=H[b.type||b.defaultSeriesType];f=a[g]||b[g]||c&&c.prototype[g];for(e=d&&d.length;!f&&
e--;)(c=H[d[e].type])&&c.prototype[g]&&(f=!0);a[g]=f})},linkSeries:function(){var a=this,b=a.series;q(b,function(a){a.linkedSeries.length=0});q(b,function(b){var d=b.options.linkedTo;if(Ga(d)&&(d=d===":previous"?a.series[b.index-1]:a.get(d)))d.linkedSeries.push(b),b.linkedParent=d})},renderSeries:function(){q(this.series,function(a){a.translate();a.setTooltipPoints&&a.setTooltipPoints();a.render()})},renderLabels:function(){var a=this,b=a.options.labels;b.items&&q(b.items,function(c){var d=r(b.style,
c.style),e=y(d.left)+a.plotLeft,f=y(d.top)+a.plotTop+12;delete d.left;delete d.top;a.renderer.text(c.html,e,f).attr({zIndex:2}).css(d).add()})},render:function(){var a=this.axes,b=this.renderer,c=this.options;this.setTitle();this.legend=new lb(this,c.legend);this.getStacks();q(a,function(a){a.setScale()});this.getMargins();this.maxTicks=null;q(a,function(a){a.setTickPositions(!0);a.setMaxTicks()});this.adjustTickAmounts();this.getMargins();this.drawChartBox();this.hasCartesianSeries&&q(a,function(a){a.render()});
if(!this.seriesGroup)this.seriesGroup=b.g("series-group").attr({zIndex:3}).add();this.renderSeries();this.renderLabels();this.showCredits(c.credits);this.hasRendered=!0},showCredits:function(a){if(a.enabled&&!this.credits)this.credits=this.renderer.text(a.text,0,0).on("click",function(){if(a.href)location.href=a.href}).attr({align:a.position.align,zIndex:8}).css(a.style).add().align(a.position)},destroy:function(){var a=this,b=a.axes,c=a.series,d=a.container,e,f=d&&d.parentNode;I(a,"destroy");W[a.index]=
u;ab--;a.renderTo.removeAttribute("data-highcharts-chart");X(a);for(e=b.length;e--;)b[e]=b[e].destroy();for(e=c.length;e--;)c[e]=c[e].destroy();q("title,subtitle,chartBackground,plotBackground,plotBGImage,plotBorder,seriesGroup,clipRect,credits,pointer,scroller,rangeSelector,legend,resetZoomButton,tooltip,renderer".split(","),function(b){var c=a[b];c&&c.destroy&&(a[b]=c.destroy())});if(d)d.innerHTML="",X(d),f&&Qa(d);for(e in a)delete a[e]},isReadyToRender:function(){var a=this;return!ba&&G==G.top&&
x.readyState!=="complete"||ga&&!G.canvg?(ga?Mb.push(function(){a.firstRender()},a.options.global.canvasToolsURL):x.attachEvent("onreadystatechange",function(){x.detachEvent("onreadystatechange",a.firstRender);x.readyState==="complete"&&a.firstRender()}),!1):!0},firstRender:function(){var a=this,b=a.options,c=a.callback;if(a.isReadyToRender()){a.getContainer();I(a,"init");a.resetMargins();a.setChartSize();a.propFromSeries();a.getAxes();q(b.series||[],function(b){a.initSeries(b)});a.linkSeries();I(a,
"beforeRender");if(K.Pointer)a.pointer=new Wa(a,b);a.render();a.renderer.draw();c&&c.apply(a,[a]);q(a.callbacks,function(b){b.apply(a,[a])});a.cloneRenderTo(!0);I(a,"load")}},splashArray:function(a,b){var c=b[a],c=da(c)?c:[c,c,c,c];return[p(b[a+"Top"],c[0]),p(b[a+"Right"],c[1]),p(b[a+"Bottom"],c[2]),p(b[a+"Left"],c[3])]}};Ya.prototype.callbacks=[];Z=K.CenteredSeriesMixin={getCenter:function(){var a=this.options,b=this.chart,c=2*(a.slicedOffset||0),d,e=b.plotWidth-2*c,f=b.plotHeight-2*c,b=a.center,
a=[p(b[0],"50%"),p(b[1],"50%"),a.size||"100%",a.innerSize||0],g=L(e,f),h;return Va(a,function(a,b){h=/%$/.test(a);d=b<2||b===2&&h;return(h?[e,f,g,g][b]*y(a)/100:a)+(d?c:0)})}};var Fa=function(){};Fa.prototype={init:function(a,b,c){this.series=a;this.applyOptions(b,c);this.pointAttr={};if(a.options.colorByPoint&&(b=a.options.colors||a.chart.options.colors,this.color=this.color||b[a.colorCounter++],a.colorCounter===b.length))a.colorCounter=0;a.chart.pointCount++;return this},applyOptions:function(a,
b){var c=this.series,d=c.options.pointValKey||c.pointValKey,a=Fa.prototype.optionsToObject.call(this,a);r(this,a);this.options=this.options?r(this.options,a):a;if(d)this.y=this[d];if(this.x===u&&c)this.x=b===u?c.autoIncrement():b;return this},optionsToObject:function(a){var b={},c=this.series,d=c.pointArrayMap||["y"],e=d.length,f=0,g=0;if(typeof a==="number"||a===null)b[d[0]]=a;else if(Ha(a)){if(a.length>e){c=typeof a[0];if(c==="string")b.name=a[0];else if(c==="number")b.x=a[0];f++}for(;g<e;)b[d[g++]]=
a[f++]}else if(typeof a==="object"){b=a;if(a.dataLabels)c._hasPointLabels=!0;if(a.marker)c._hasPointMarkers=!0}return b},destroy:function(){var a=this.series.chart,b=a.hoverPoints,c;a.pointCount--;if(b&&(this.setState(),la(b,this),!b.length))a.hoverPoints=null;if(this===a.hoverPoint)this.onMouseOut();if(this.graphic||this.dataLabel)X(this),this.destroyElements();this.legendItem&&a.legend.destroyItem(this);for(c in this)this[c]=null},destroyElements:function(){for(var a="graphic,dataLabel,dataLabelUpper,group,connector,shadowGroup".split(","),
b,c=6;c--;)b=a[c],this[b]&&(this[b]=this[b].destroy())},getLabelConfig:function(){return{x:this.category,y:this.y,key:this.name||this.category,series:this.series,point:this,percentage:this.percentage,total:this.total||this.stackTotal}},tooltipFormatter:function(a){var b=this.series,c=b.tooltipOptions,d=p(c.valueDecimals,""),e=c.valuePrefix||"",f=c.valueSuffix||"";q(b.pointArrayMap||["y"],function(b){b="{point."+b;if(e||f)a=a.replace(b+"}",e+b+"}"+f);a=a.replace(b+"}",b+":,."+d+"f}")});return Ja(a,
{point:this,series:this.series})},firePointEvent:function(a,b,c){var d=this,e=this.series.options;(e.point.events[a]||d.options&&d.options.events&&d.options.events[a])&&this.importEvents();a==="click"&&e.allowPointSelect&&(c=function(a){d.select(null,a.ctrlKey||a.metaKey||a.shiftKey)});I(this,a,b,c)}};var O=function(){};O.prototype={isCartesian:!0,type:"line",pointClass:Fa,sorted:!0,requireSorting:!0,pointAttrToOptions:{stroke:"lineColor","stroke-width":"lineWidth",fill:"fillColor",r:"radius"},axisTypes:["xAxis",
"yAxis"],colorCounter:0,parallelArrays:["x","y"],init:function(a,b){var c=this,d,e,f=a.series,g=function(a,b){return p(a.options.index,a._i)-p(b.options.index,b._i)};c.chart=a;c.options=b=c.setOptions(b);c.linkedSeries=[];c.bindAxes();r(c,{name:b.name,state:"",pointAttr:{},visible:b.visible!==!1,selected:b.selected===!0});if(ga)b.animation=!1;e=b.events;for(d in e)N(c,d,e[d]);if(e&&e.click||b.point&&b.point.events&&b.point.events.click||b.allowPointSelect)a.runTrackerClick=!0;c.getColor();c.getSymbol();
q(c.parallelArrays,function(a){c[a+"Data"]=[]});c.setData(b.data,!1);if(c.isCartesian)a.hasCartesianSeries=!0;f.push(c);c._i=f.length-1;ob(f,g);this.yAxis&&ob(this.yAxis.series,g);q(f,function(a,b){a.index=b;a.name=a.name||"Series "+(b+1)})},bindAxes:function(){var a=this,b=a.options,c=a.chart,d;q(a.axisTypes||[],function(e){q(c[e],function(c){d=c.options;if(b[e]===d.index||b[e]!==u&&b[e]===d.id||b[e]===u&&d.index===0)c.series.push(a),a[e]=c,c.isDirty=!0});!a[e]&&a.optionalAxis!==e&&ha(18,!0)})},
updateParallelArrays:function(a,b){var c=a.series,d=arguments;q(c.parallelArrays,typeof b==="number"?function(d){var f=d==="y"&&c.toYData?c.toYData(a):a[d];c[d+"Data"][b]=f}:function(a){Array.prototype[b].apply(c[a+"Data"],Array.prototype.slice.call(d,2))})},autoIncrement:function(){var a=this.options,b=this.xIncrement,b=p(b,a.pointStart,0);this.pointInterval=p(this.pointInterval,a.pointInterval,1);this.xIncrement=b+this.pointInterval;return b},getSegments:function(){var a=-1,b=[],c,d=this.points,
e=d.length;if(e)if(this.options.connectNulls){for(c=e;c--;)d[c].y===null&&d.splice(c,1);d.length&&(b=[d])}else q(d,function(c,g){c.y===null?(g>a+1&&b.push(d.slice(a+1,g)),a=g):g===e-1&&b.push(d.slice(a+1,g+1))});this.segments=b},setOptions:function(a){var b=this.chart,c=b.options.plotOptions,b=b.userOptions||{},d=b.plotOptions||{},e=c[this.type];this.userOptions=a;c=w(e,c.series,a);this.tooltipOptions=w(E.tooltip,E.plotOptions[this.type].tooltip,b.tooltip,d.series&&d.series.tooltip,d[this.type]&&
d[this.type].tooltip,a.tooltip);e.marker===null&&delete c.marker;return c},getCyclic:function(a,b,c){var d=this.userOptions,e="_"+a+"Index",f=a+"Counter";b||(s(d[e])?b=d[e]:(d[e]=b=this.chart[f]%c.length,this.chart[f]+=1),b=c[b]);this[a]=b},getColor:function(){this.options.colorByPoint||this.getCyclic("color",this.options.color||ca[this.type].color,this.chart.options.colors)},getSymbol:function(){var a=this.options.marker;this.getCyclic("symbol",a.symbol,this.chart.options.symbols);if(/^url/.test(this.symbol))a.radius=
0},drawLegendSymbol:M.drawLineMarker,setData:function(a,b,c,d){var e=this,f=e.points,g=f&&f.length||0,h,i=e.options,j=e.chart,k=null,l=e.xAxis,n=l&&!!l.categories,m=e.tooltipPoints,o=i.turboThreshold,r=this.xData,t=this.yData,s=(h=e.pointArrayMap)&&h.length,a=a||[];h=a.length;b=p(b,!0);if(d!==!1&&h&&g===h&&!e.cropped&&!e.hasGroupedData)q(a,function(a,b){f[b].update(a,!1,null,!1)});else{e.xIncrement=null;e.pointRange=n?1:i.pointRange;e.colorCounter=0;q(this.parallelArrays,function(a){e[a+"Data"].length=
0});if(o&&h>o){for(c=0;k===null&&c<h;)k=a[c],c++;if(ja(k)){n=p(i.pointStart,0);i=p(i.pointInterval,1);for(c=0;c<h;c++)r[c]=n,t[c]=a[c],n+=i;e.xIncrement=n}else if(Ha(k))if(s)for(c=0;c<h;c++)i=a[c],r[c]=i[0],t[c]=i.slice(1,s+1);else for(c=0;c<h;c++)i=a[c],r[c]=i[0],t[c]=i[1];else ha(12)}else for(c=0;c<h;c++)if(a[c]!==u&&(i={series:e},e.pointClass.prototype.applyOptions.apply(i,[a[c]]),e.updateParallelArrays(i,c),n&&i.name))l.names[i.x]=i.name;Ga(t[0])&&ha(14,!0);e.data=[];e.options.data=a;for(c=g;c--;)f[c]&&
f[c].destroy&&f[c].destroy();if(m)m.length=0;if(l)l.minRange=l.userMinRange;e.isDirty=e.isDirtyData=j.isDirtyBox=!0;c=!1}b&&j.redraw(c)},processData:function(a){var b=this.xData,c=this.yData,d=b.length,e;e=0;var f,g,h=this.xAxis,i,j=this.options;i=j.cropThreshold;var k=0,l=this.isCartesian,n,m;if(l&&!this.isDirty&&!h.isDirty&&!this.yAxis.isDirty&&!a)return!1;if(h)n=h.getExtremes(),m=n.min,n=n.max;if(l&&this.sorted&&(!i||d>i||this.forceCrop))if(b[d-1]<m||b[0]>n)b=[],c=[];else if(b[0]<m||b[d-1]>n)e=
this.cropData(this.xData,this.yData,m,n),b=e.xData,c=e.yData,e=e.start,f=!0,k=b.length;for(i=b.length-1;i>=0;i--)d=b[i]-b[i-1],!f&&b[i]>m&&b[i]<n&&k++,d>0&&(g===u||d<g)?g=d:d<0&&this.requireSorting&&ha(15);this.cropped=f;this.cropStart=e;this.processedXData=b;this.processedYData=c;this.activePointCount=k;if(j.pointRange===null)this.pointRange=g||1;this.closestPointRange=g},cropData:function(a,b,c,d){var e=a.length,f=0,g=e,h=p(this.cropShoulder,1),i;for(i=0;i<e;i++)if(a[i]>=c){f=t(0,i-h);break}for(;i<
e;i++)if(a[i]>d){g=i+h;break}return{xData:a.slice(f,g),yData:b.slice(f,g),start:f,end:g}},generatePoints:function(){var a=this.options.data,b=this.data,c,d=this.processedXData,e=this.processedYData,f=this.pointClass,g=d.length,h=this.cropStart||0,i,j=this.hasGroupedData,k,l=[],n;if(!b&&!j)b=[],b.length=a.length,b=this.data=b;for(n=0;n<g;n++)i=h+n,j?l[n]=(new f).init(this,[d[n]].concat(ra(e[n]))):(b[i]?k=b[i]:a[i]!==u&&(b[i]=k=(new f).init(this,a[i],d[n])),l[n]=k),l[n].index=i;if(b&&(g!==(c=b.length)||
j))for(n=0;n<c;n++)if(n===h&&!j&&(n+=g),b[n])b[n].destroyElements(),b[n].plotX=u;this.data=b;this.points=l},getExtremes:function(a){var b=this.yAxis,c=this.processedXData,d,e=[],f=0;d=this.xAxis.getExtremes();var g=d.min,h=d.max,i,j,k,l,a=a||this.stackedYData||this.processedYData;d=a.length;for(l=0;l<d;l++)if(j=c[l],k=a[l],i=k!==null&&k!==u&&(!b.isLog||k.length||k>0),j=this.getExtremesFromAll||this.cropped||(c[l+1]||j)>=g&&(c[l-1]||j)<=h,i&&j)if(i=k.length)for(;i--;)k[i]!==null&&(e[f++]=k[i]);else e[f++]=
k;this.dataMin=p(void 0,Oa(e));this.dataMax=p(void 0,Ca(e))},translate:function(){this.processedXData||this.processData();this.generatePoints();for(var a=this.options,b=a.stacking,c=this.xAxis,d=c.categories,e=this.yAxis,f=this.points,g=f.length,h=!!this.modifyValue,i=a.pointPlacement,j=i==="between"||ja(i),k=a.threshold,a=0;a<g;a++){var l=f[a],n=l.x,m=l.y,o=l.low,q=b&&e.stacks[(this.negStacks&&m<k?"-":"")+this.stackKey];if(e.isLog&&m<=0)l.y=m=null,ha(10);l.plotX=c.translate(n,0,0,0,1,i,this.type===
"flags");if(b&&this.visible&&q&&q[n])q=q[n],m=q.points[this.index+","+a],o=m[0],m=m[1],o===0&&(o=p(k,e.min)),e.isLog&&o<=0&&(o=null),l.total=l.stackTotal=q.total,l.percentage=q.total&&l.y/q.total*100,l.stackY=m,q.setOffset(this.pointXOffset||0,this.barW||0);l.yBottom=s(o)?e.translate(o,0,1,0,1):null;h&&(m=this.modifyValue(m,l));l.plotY=typeof m==="number"&&m!==Infinity?e.translate(m,0,1,0,1):u;l.clientX=j?c.translate(n,0,0,0,1):l.plotX;l.negative=l.y<(k||0);l.category=d&&d[l.x]!==u?d[l.x]:l.x}this.getSegments()},
animate:function(a){var b=this.chart,c=b.renderer,d;d=this.options.animation;var e=this.clipBox||b.clipBox,f=b.inverted,g;if(d&&!da(d))d=ca[this.type].animation;g=["_sharedClip",d.duration,d.easing,e.height].join(",");a?(a=b[g],d=b[g+"m"],a||(b[g]=a=c.clipRect(r(e,{width:0})),b[g+"m"]=d=c.clipRect(-99,f?-b.plotLeft:-b.plotTop,99,f?b.chartWidth:b.chartHeight)),this.group.clip(a),this.markerGroup.clip(d),this.sharedClipKey=g):((a=b[g])&&a.animate({width:b.plotSizeX},d),b[g+"m"]&&b[g+"m"].animate({width:b.plotSizeX+
99},d),this.animate=null)},afterAnimate:function(){var a=this.chart,b=this.sharedClipKey,c=this.group,d=this.clipBox;if(c&&this.options.clip!==!1){if(!b||!d)c.clip(d?a.renderer.clipRect(d):a.clipRect);this.markerGroup.clip()}I(this,"afterAnimate");setTimeout(function(){b&&a[b]&&(d||(a[b]=a[b].destroy()),a[b+"m"]&&(a[b+"m"]=a[b+"m"].destroy()))},100)},drawPoints:function(){var a,b=this.points,c=this.chart,d,e,f,g,h,i,j,k,l=this.options.marker,n=this.pointAttr[""],m,o,q,t=this.markerGroup,s=p(l.enabled,
!this.requireSorting||this.activePointCount<0.5*this.xAxis.len/l.radius);if(l.enabled!==!1||this._hasPointMarkers)for(f=b.length;f--;)if(g=b[f],d=U(g.plotX),e=g.plotY,k=g.graphic,m=g.marker||{},o=!!g.marker,a=s&&m.enabled===u||m.enabled,q=c.isInsidePlot(v(d),e,c.inverted),a&&e!==u&&!isNaN(e)&&g.y!==null)if(a=g.pointAttr[g.selected?"select":""]||n,h=a.r,i=p(m.symbol,this.symbol),j=i.indexOf("url")===0,k)k[q?"show":"hide"](!0).animate(r({x:d-h,y:e-h},k.symbolName?{width:2*h,height:2*h}:{}));else{if(q&&
(h>0||j))g.graphic=c.renderer.symbol(i,d-h,e-h,2*h,2*h,o?m:l).attr(a).add(t)}else if(k)g.graphic=k.destroy()},convertAttribs:function(a,b,c,d){var e=this.pointAttrToOptions,f,g,h={},a=a||{},b=b||{},c=c||{},d=d||{};for(f in e)g=e[f],h[f]=p(a[g],b[f],c[f],d[f]);return h},getAttribs:function(){var a=this,b=a.options,c=ca[a.type].marker?b.marker:b,d=c.states,e=d.hover,f,g=a.color;f={stroke:g,fill:g};var h=a.points||[],i,j=[],k,l=a.pointAttrToOptions;k=a.hasPointSpecificOptions;var n=b.negativeColor,m=
c.lineColor,o=c.fillColor;i=b.turboThreshold;var p;b.marker?(e.radius=e.radius||c.radius+e.radiusPlus,e.lineWidth=e.lineWidth||c.lineWidth+e.lineWidthPlus):e.color=e.color||ya(e.color||g).brighten(e.brightness).get();j[""]=a.convertAttribs(c,f);q(["hover","select"],function(b){j[b]=a.convertAttribs(d[b],j[""])});a.pointAttr=j;g=h.length;if(!i||g<i||k)for(;g--;){i=h[g];if((c=i.options&&i.options.marker||i.options)&&c.enabled===!1)c.radius=0;if(i.negative&&n)i.color=i.fillColor=n;k=b.colorByPoint||
i.color;if(i.options)for(p in l)s(c[l[p]])&&(k=!0);if(k){c=c||{};k=[];d=c.states||{};f=d.hover=d.hover||{};if(!b.marker)f.color=f.color||!i.options.color&&e.color||ya(i.color).brighten(f.brightness||e.brightness).get();f={color:i.color};if(!o)f.fillColor=i.color;if(!m)f.lineColor=i.color;k[""]=a.convertAttribs(r(f,c),j[""]);k.hover=a.convertAttribs(d.hover,j.hover,k[""]);k.select=a.convertAttribs(d.select,j.select,k[""])}else k=j;i.pointAttr=k}},destroy:function(){var a=this,b=a.chart,c=/AppleWebKit\/533/.test(wa),
d,e,f=a.data||[],g,h,i;I(a,"destroy");X(a);q(a.axisTypes||[],function(b){if(i=a[b])la(i.series,a),i.isDirty=i.forceRedraw=!0});a.legendItem&&a.chart.legend.destroyItem(a);for(e=f.length;e--;)(g=f[e])&&g.destroy&&g.destroy();a.points=null;clearTimeout(a.animationTimeout);q("area,graph,dataLabelsGroup,group,markerGroup,tracker,graphNeg,areaNeg,posClip,negClip".split(","),function(b){a[b]&&(d=c&&b==="group"?"hide":"destroy",a[b][d]())});if(b.hoverSeries===a)b.hoverSeries=null;la(b.series,a);for(h in a)delete a[h]},
getSegmentPath:function(a){var b=this,c=[],d=b.options.step;q(a,function(e,f){var g=e.plotX,h=e.plotY,i;b.getPointSpline?c.push.apply(c,b.getPointSpline(a,e,f)):(c.push(f?"L":"M"),d&&f&&(i=a[f-1],d==="right"?c.push(i.plotX,h):d==="center"?c.push((i.plotX+g)/2,i.plotY,(i.plotX+g)/2,h):c.push(g,i.plotY)),c.push(e.plotX,e.plotY))});return c},getGraphPath:function(){var a=this,b=[],c,d=[];q(a.segments,function(e){c=a.getSegmentPath(e);e.length>1?b=b.concat(c):d.push(e[0])});a.singlePoints=d;return a.graphPath=
b},drawGraph:function(){var a=this,b=this.options,c=[["graph",b.lineColor||this.color]],d=b.lineWidth,e=b.dashStyle,f=b.linecap!=="square",g=this.getGraphPath(),h=b.negativeColor;h&&c.push(["graphNeg",h]);q(c,function(c,h){var k=c[0],l=a[k];if(l)bb(l),l.animate({d:g});else if(d&&g.length)l={stroke:c[1],"stroke-width":d,fill:P,zIndex:1},e?l.dashstyle=e:f&&(l["stroke-linecap"]=l["stroke-linejoin"]="round"),a[k]=a.chart.renderer.path(g).attr(l).add(a.group).shadow(!h&&b.shadow)})},clipNeg:function(){var a=
this.options,b=this.chart,c=b.renderer,d=a.negativeColor||a.negativeFillColor,e,f=this.graph,g=this.area,h=this.posClip,i=this.negClip;e=b.chartWidth;var j=b.chartHeight,k=t(e,j),l=this.yAxis;if(d&&(f||g)){d=v(l.toPixels(a.threshold||0,!0));d<0&&(k-=d);a={x:0,y:0,width:k,height:d};k={x:0,y:d,width:k,height:k};if(b.inverted)a.height=k.y=b.plotWidth-d,c.isVML&&(a={x:b.plotWidth-d-b.plotLeft,y:0,width:e,height:j},k={x:d+b.plotLeft-e,y:0,width:b.plotLeft+d,height:e});l.reversed?(b=k,e=a):(b=a,e=k);h?
(h.animate(b),i.animate(e)):(this.posClip=h=c.clipRect(b),this.negClip=i=c.clipRect(e),f&&this.graphNeg&&(f.clip(h),this.graphNeg.clip(i)),g&&(g.clip(h),this.areaNeg.clip(i)))}},invertGroups:function(){function a(){var a={width:b.yAxis.len,height:b.xAxis.len};q(["group","markerGroup"],function(c){b[c]&&b[c].attr(a).invert()})}var b=this,c=b.chart;if(b.xAxis)N(c,"resize",a),N(b,"destroy",function(){X(c,"resize",a)}),a(),b.invertGroups=a},plotGroup:function(a,b,c,d,e){var f=this[a],g=!f;g&&(this[a]=
f=this.chart.renderer.g(b).attr({visibility:c,zIndex:d||0.1}).add(e));f[g?"attr":"animate"](this.getPlotBox());return f},getPlotBox:function(){var a=this.chart,b=this.xAxis,c=this.yAxis;if(a.inverted)b=c,c=this.xAxis;return{translateX:b?b.left:a.plotLeft,translateY:c?c.top:a.plotTop,scaleX:1,scaleY:1}},render:function(){var a=this,b=a.chart,c,d=a.options,e=(c=d.animation)&&!!a.animate&&b.renderer.isSVG&&p(c.duration,500)||0,f=a.visible?"visible":"hidden",g=d.zIndex,h=a.hasRendered,i=b.seriesGroup;
c=a.plotGroup("group","series",f,g,i);a.markerGroup=a.plotGroup("markerGroup","markers",f,g,i);e&&a.animate(!0);a.getAttribs();c.inverted=a.isCartesian?b.inverted:!1;a.drawGraph&&(a.drawGraph(),a.clipNeg());q(a.points,function(a){a.redraw&&a.redraw()});a.drawDataLabels&&a.drawDataLabels();a.visible&&a.drawPoints();a.drawTracker&&a.options.enableMouseTracking!==!1&&a.drawTracker();b.inverted&&a.invertGroups();d.clip!==!1&&!a.sharedClipKey&&!h&&c.clip(b.clipRect);e&&a.animate();if(!h)e?a.animationTimeout=
setTimeout(function(){a.afterAnimate()},e):a.afterAnimate();a.isDirty=a.isDirtyData=!1;a.hasRendered=!0},redraw:function(){var a=this.chart,b=this.isDirtyData,c=this.group,d=this.xAxis,e=this.yAxis;c&&(a.inverted&&c.attr({width:a.plotWidth,height:a.plotHeight}),c.animate({translateX:p(d&&d.left,a.plotLeft),translateY:p(e&&e.top,a.plotTop)}));this.translate();this.setTooltipPoints&&this.setTooltipPoints(!0);this.render();b&&I(this,"updatedData")}};Gb.prototype={destroy:function(){Pa(this,this.axis)},
render:function(a){var b=this.options,c=b.format,c=c?Ja(c,this):b.formatter.call(this);this.label?this.label.attr({text:c,visibility:"hidden"}):this.label=this.axis.chart.renderer.text(c,null,null,b.useHTML).css(b.style).attr({align:this.textAlign,rotation:b.rotation,visibility:"hidden"}).add(a)},setOffset:function(a,b){var c=this.axis,d=c.chart,e=d.inverted,f=this.isNegative,g=c.translate(c.usePercentage?100:this.total,0,0,0,1),c=c.translate(0),c=Q(g-c),h=d.xAxis[0].translate(this.x)+a,i=d.plotHeight,
f={x:e?f?g:g-c:h,y:e?i-h-b:f?i-g-c:i-g,width:e?c:b,height:e?b:c};if(e=this.label)e.align(this.alignOptions,null,f),f=e.alignAttr,e[this.options.crop===!1||d.isInsidePlot(f.x,f.y)?"show":"hide"](!0)}};na.prototype.buildStacks=function(){var a=this.series,b=p(this.options.reversedStacks,!0),c=a.length;if(!this.isXAxis){for(this.usePercentage=!1;c--;)a[b?c:a.length-c-1].setStackedPoints();if(this.usePercentage)for(c=0;c<a.length;c++)a[c].setPercentStacks()}};na.prototype.renderStackTotals=function(){var a=
this.chart,b=a.renderer,c=this.stacks,d,e,f=this.stackTotalGroup;if(!f)this.stackTotalGroup=f=b.g("stack-labels").attr({visibility:"visible",zIndex:6}).add();f.translate(a.plotLeft,a.plotTop);for(d in c)for(e in a=c[d],a)a[e].render(f)};O.prototype.setStackedPoints=function(){if(this.options.stacking&&!(this.visible!==!0&&this.chart.options.chart.ignoreHiddenSeries!==!1)){var a=this.processedXData,b=this.processedYData,c=[],d=b.length,e=this.options,f=e.threshold,g=e.stack,e=e.stacking,h=this.stackKey,
i="-"+h,j=this.negStacks,k=this.yAxis,l=k.stacks,n=k.oldStacks,m,o,p,q,r,s;for(q=0;q<d;q++){r=a[q];s=b[q];p=this.index+","+q;o=(m=j&&s<f)?i:h;l[o]||(l[o]={});if(!l[o][r])n[o]&&n[o][r]?(l[o][r]=n[o][r],l[o][r].total=null):l[o][r]=new Gb(k,k.options.stackLabels,m,r,g);o=l[o][r];o.points[p]=[o.cum||0];e==="percent"?(m=m?h:i,j&&l[m]&&l[m][r]?(m=l[m][r],o.total=m.total=t(m.total,o.total)+Q(s)||0):o.total=ea(o.total+(Q(s)||0))):o.total=ea(o.total+(s||0));o.cum=(o.cum||0)+(s||0);o.points[p].push(o.cum);
c[q]=o.cum}if(e==="percent")k.usePercentage=!0;this.stackedYData=c;k.oldStacks={}}};O.prototype.setPercentStacks=function(){var a=this,b=a.stackKey,c=a.yAxis.stacks,d=a.processedXData;q([b,"-"+b],function(b){var e;for(var f=d.length,g,h;f--;)if(g=d[f],e=(h=c[b]&&c[b][g])&&h.points[a.index+","+f],g=e)h=h.total?100/h.total:0,g[0]=ea(g[0]*h),g[1]=ea(g[1]*h),a.stackedYData[f]=g[1]})};r(Ya.prototype,{addSeries:function(a,b,c){var d,e=this;a&&(b=p(b,!0),I(e,"addSeries",{options:a},function(){d=e.initSeries(a);
e.isDirtyLegend=!0;e.linkSeries();b&&e.redraw(c)}));return d},addAxis:function(a,b,c,d){var e=b?"xAxis":"yAxis",f=this.options;new na(this,w(a,{index:this[e].length,isX:b}));f[e]=ra(f[e]||{});f[e].push(a);p(c,!0)&&this.redraw(d)},showLoading:function(a){var b=this,c=b.options,d=b.loadingDiv,e=c.loading,f=function(){d&&B(d,{left:b.plotLeft+"px",top:b.plotTop+"px",width:b.plotWidth+"px",height:b.plotHeight+"px"})};if(!d)b.loadingDiv=d=$(Ka,{className:"highcharts-loading"},r(e.style,{zIndex:10,display:P}),
b.container),b.loadingSpan=$("span",null,e.labelStyle,d),N(b,"redraw",f);b.loadingSpan.innerHTML=a||c.lang.loading;if(!b.loadingShown)B(d,{opacity:0,display:""}),jb(d,{opacity:e.style.opacity},{duration:e.showDuration||0}),b.loadingShown=!0;f()},hideLoading:function(){var a=this.options,b=this.loadingDiv;b&&jb(b,{opacity:0},{duration:a.loading.hideDuration||100,complete:function(){B(b,{display:P})}});this.loadingShown=!1}});r(Fa.prototype,{update:function(a,b,c,d){function e(){f.applyOptions(a);if(da(a)&&
!Ha(a))f.redraw=function(){if(h)a&&a.marker&&a.marker.symbol?f.graphic=h.destroy():h.attr(f.pointAttr[f.state||""]);if(a&&a.dataLabels&&f.dataLabel)f.dataLabel=f.dataLabel.destroy();f.redraw=null};i=f.index;g.updateParallelArrays(f,i);k.data[i]=f.options;g.isDirty=g.isDirtyData=!0;if(!g.fixedBox&&g.hasCartesianSeries)j.isDirtyBox=!0;k.legendType==="point"&&j.legend.destroyItem(f);b&&j.redraw(c)}var f=this,g=f.series,h=f.graphic,i,j=g.chart,k=g.options,b=p(b,!0);d===!1?e():f.firePointEvent("update",
{options:a},e)},remove:function(a,b){var c=this,d=c.series,e=d.points,f=d.chart,g,h=d.data;Ra(b,f);a=p(a,!0);c.firePointEvent("remove",null,function(){g=Ma(c,h);h.length===e.length&&e.splice(g,1);h.splice(g,1);d.options.data.splice(g,1);d.updateParallelArrays(c,"splice",g,1);c.destroy();d.isDirty=!0;d.isDirtyData=!0;a&&f.redraw()})}});r(O.prototype,{addPoint:function(a,b,c,d){var e=this.options,f=this.data,g=this.graph,h=this.area,i=this.chart,j=this.xAxis&&this.xAxis.names,k=g&&g.shift||0,l=e.data,
n,m=this.xData;Ra(d,i);c&&q([g,h,this.graphNeg,this.areaNeg],function(a){if(a)a.shift=k+1});if(h)h.isArea=!0;b=p(b,!0);d={series:this};this.pointClass.prototype.applyOptions.apply(d,[a]);g=d.x;h=m.length;if(this.requireSorting&&g<m[h-1])for(n=!0;h&&m[h-1]>g;)h--;this.updateParallelArrays(d,"splice",h,0,0);this.updateParallelArrays(d,h);if(j&&d.name)j[g]=d.name;l.splice(h,0,a);n&&(this.data.splice(h,0,null),this.processData());e.legendType==="point"&&this.generatePoints();c&&(f[0]&&f[0].remove?f[0].remove(!1):
(f.shift(),this.updateParallelArrays(d,"shift"),l.shift()));this.isDirtyData=this.isDirty=!0;b&&(this.getAttribs(),i.redraw())},remove:function(a,b){var c=this,d=c.chart,a=p(a,!0);if(!c.isRemoving)c.isRemoving=!0,I(c,"remove",null,function(){c.destroy();d.isDirtyLegend=d.isDirtyBox=!0;d.linkSeries();a&&d.redraw(b)});c.isRemoving=!1},update:function(a,b){var c=this,d=this.chart,e=this.userOptions,f=this.type,g=H[f].prototype,h=["group","markerGroup","dataLabelsGroup"],i;q(h,function(a){h[a]=c[a];delete c[a]});
a=w(e,{animation:!1,index:this.index,pointStart:this.xData[0]},{data:this.options.data},a);this.remove(!1);for(i in g)g.hasOwnProperty(i)&&(this[i]=u);r(this,H[a.type||f].prototype);q(h,function(a){c[a]=h[a]});this.init(d,a);d.linkSeries();p(b,!0)&&d.redraw(!1)}});r(na.prototype,{update:function(a,b){var c=this.chart,a=c.options[this.coll][this.options.index]=w(this.userOptions,a);this.destroy(!0);this._addedPlotLB=u;this.init(c,r(a,{events:u}));c.isDirtyBox=!0;p(b,!0)&&c.redraw()},remove:function(a){for(var b=
this.chart,c=this.coll,d=this.series,e=d.length;e--;)d[e]&&d[e].remove(!1);la(b.axes,this);la(b[c],this);b.options[c].splice(this.options.index,1);q(b[c],function(a,b){a.options.index=b});this.destroy();b.isDirtyBox=!0;p(a,!0)&&b.redraw()},setTitle:function(a,b){this.update({title:a},b)},setCategories:function(a,b){this.update({categories:a},b)}});ia=ma(O);H.line=ia;ca.area=w(T,{threshold:0});var qa=ma(O,{type:"area",getSegments:function(){var a=this,b=[],c=[],d=[],e=this.xAxis,f=this.yAxis,g=f.stacks[this.stackKey],
h={},i,j,k=this.points,l=this.options.connectNulls,n,m;if(this.options.stacking&&!this.cropped){for(n=0;n<k.length;n++)h[k[n].x]=k[n];for(m in g)g[m].total!==null&&d.push(+m);d.sort(function(a,b){return a-b});q(d,function(b){var d=0,k;if(!l||h[b]&&h[b].y!==null)if(h[b])c.push(h[b]);else{for(n=a.index;n<=f.series.length;n++)if(k=g[b].points[n+","+b]){d=k[1];break}i=e.translate(b);j=f.toPixels(d,!0);c.push({y:null,plotX:i,clientX:i,plotY:j,yBottom:j,onMouseOver:sa})}});c.length&&b.push(c)}else O.prototype.getSegments.call(this),
b=this.segments;this.segments=b},getSegmentPath:function(a){var b=O.prototype.getSegmentPath.call(this,a),c=[].concat(b),d,e=this.options;d=b.length;var f=this.yAxis.getThreshold(e.threshold),g;d===3&&c.push("L",b[1],b[2]);if(e.stacking&&!this.closedStacks)for(d=a.length-1;d>=0;d--)g=p(a[d].yBottom,f),d<a.length-1&&e.step&&c.push(a[d+1].plotX,g),c.push(a[d].plotX,g);else this.closeSegment(c,a,f);this.areaPath=this.areaPath.concat(c);return b},closeSegment:function(a,b,c){a.push("L",b[b.length-1].plotX,
c,"L",b[0].plotX,c)},drawGraph:function(){this.areaPath=[];O.prototype.drawGraph.apply(this);var a=this,b=this.areaPath,c=this.options,d=c.negativeColor,e=c.negativeFillColor,f=[["area",this.color,c.fillColor]];(d||e)&&f.push(["areaNeg",d,e]);q(f,function(d){var e=d[0],f=a[e];f?f.animate({d:b}):a[e]=a.chart.renderer.path(b).attr({fill:p(d[2],ya(d[1]).setOpacity(p(c.fillOpacity,0.75)).get()),zIndex:0}).add(a.group)})},drawLegendSymbol:M.drawRectangle});H.area=qa;ca.spline=w(T);ia=ma(O,{type:"spline",
getPointSpline:function(a,b,c){var d=b.plotX,e=b.plotY,f=a[c-1],g=a[c+1],h,i,j,k;if(f&&g){a=f.plotY;j=g.plotX;var g=g.plotY,l;h=(1.5*d+f.plotX)/2.5;i=(1.5*e+a)/2.5;j=(1.5*d+j)/2.5;k=(1.5*e+g)/2.5;l=(k-i)*(j-d)/(j-h)+e-k;i+=l;k+=l;i>a&&i>e?(i=t(a,e),k=2*e-i):i<a&&i<e&&(i=L(a,e),k=2*e-i);k>g&&k>e?(k=t(g,e),i=2*e-k):k<g&&k<e&&(k=L(g,e),i=2*e-k);b.rightContX=j;b.rightContY=k}c?(b=["C",f.rightContX||f.plotX,f.rightContY||f.plotY,h||d,i||e,d,e],f.rightContX=f.rightContY=null):b=["M",d,e];return b}});H.spline=
ia;ca.areaspline=w(ca.area);qa=qa.prototype;ia=ma(ia,{type:"areaspline",closedStacks:!0,getSegmentPath:qa.getSegmentPath,closeSegment:qa.closeSegment,drawGraph:qa.drawGraph,drawLegendSymbol:M.drawRectangle});H.areaspline=ia;ca.column=w(T,{borderColor:"#FFFFFF",borderRadius:0,groupPadding:0.2,marker:null,pointPadding:0.1,minPointLength:0,cropThreshold:50,pointRange:null,states:{hover:{brightness:0.1,shadow:!1,halo:!1},select:{color:"#C0C0C0",borderColor:"#000000",shadow:!1}},dataLabels:{align:null,
verticalAlign:null,y:null},stickyTracking:!1,tooltip:{distance:6},threshold:0});ia=ma(O,{type:"column",pointAttrToOptions:{stroke:"borderColor",fill:"color",r:"borderRadius"},cropShoulder:0,trackerGroups:["group","dataLabelsGroup"],negStacks:!0,init:function(){O.prototype.init.apply(this,arguments);var a=this,b=a.chart;b.hasRendered&&q(b.series,function(b){if(b.type===a.type)b.isDirty=!0})},getColumnMetrics:function(){var a=this,b=a.options,c=a.xAxis,d=a.yAxis,e=c.reversed,f,g={},h,i=0;b.grouping===
!1?i=1:q(a.chart.series,function(b){var c=b.options,e=b.yAxis;if(b.type===a.type&&b.visible&&d.len===e.len&&d.pos===e.pos)c.stacking?(f=b.stackKey,g[f]===u&&(g[f]=i++),h=g[f]):c.grouping!==!1&&(h=i++),b.columnIndex=h});var c=L(Q(c.transA)*(c.ordinalSlope||b.pointRange||c.closestPointRange||c.tickInterval||1),c.len),j=c*b.groupPadding,k=(c-2*j)/i,l=b.pointWidth,b=s(l)?(k-l)/2:k*b.pointPadding,l=p(l,k-2*b);return a.columnMetrics={width:l,offset:b+(j+((e?i-(a.columnIndex||0):a.columnIndex)||0)*k-c/2)*
(e?-1:1)}},translate:function(){var a=this,b=a.chart,c=a.options,d=a.borderWidth=p(c.borderWidth,a.activePointCount>0.5*a.xAxis.len?0:1),e=a.yAxis,f=a.translatedThreshold=e.getThreshold(c.threshold),g=p(c.minPointLength,5),h=a.getColumnMetrics(),i=h.width,j=a.barW=t(i,1+2*d),k=a.pointXOffset=h.offset,l=-(d%2?0.5:0),n=d%2?0.5:1;b.renderer.isVML&&b.inverted&&(n+=1);c.pointPadding&&(j=La(j));O.prototype.translate.apply(a);q(a.points,function(c){var d=p(c.yBottom,f),h=L(t(-999-d,c.plotY),e.len+999+d),
q=c.plotX+k,r=j,s=L(h,d),u;u=t(h,d)-s;Q(u)<g&&g&&(u=g,s=v(Q(s-f)>g?d-g:f-(e.translate(c.y,0,1,0,1)<=f?g:0)));c.barX=q;c.pointWidth=i;c.tooltipPos=b.inverted?[e.len-h,a.xAxis.len-q-r/2]:[q+r/2,h+e.pos-b.plotTop];r=v(q+r)+l;q=v(q)+l;r-=q;d=Q(s)<0.5;u=v(s+u)+n;s=v(s)+n;u-=s;d&&(s-=1,u+=1);c.shapeType="rect";c.shapeArgs={x:q,y:s,width:r,height:u}})},getSymbol:sa,drawLegendSymbol:M.drawRectangle,drawGraph:sa,drawPoints:function(){var a=this,b=this.chart,c=a.options,d=b.renderer,e=c.animationLimit||250,
f,g;q(a.points,function(h){var i=h.plotY,j=h.graphic;if(i!==u&&!isNaN(i)&&h.y!==null)f=h.shapeArgs,i=s(a.borderWidth)?{"stroke-width":a.borderWidth}:{},g=h.pointAttr[h.selected?"select":""]||a.pointAttr[""],j?(bb(j),j.attr(i)[b.pointCount<e?"animate":"attr"](w(f))):h.graphic=d[h.shapeType](f).attr(g).attr(i).add(a.group).shadow(c.shadow,null,c.stacking&&!c.borderRadius);else if(j)h.graphic=j.destroy()})},animate:function(a){var b=this.yAxis,c=this.options,d=this.chart.inverted,e={};if(ba)a?(e.scaleY=
0.001,a=L(b.pos+b.len,t(b.pos,b.toPixels(c.threshold))),d?e.translateX=a-b.len:e.translateY=a,this.group.attr(e)):(e.scaleY=1,e[d?"translateX":"translateY"]=b.pos,this.group.animate(e,this.options.animation),this.animate=null)},remove:function(){var a=this,b=a.chart;b.hasRendered&&q(b.series,function(b){if(b.type===a.type)b.isDirty=!0});O.prototype.remove.apply(a,arguments)}});H.column=ia;ca.bar=w(ca.column);qa=ma(ia,{type:"bar",inverted:!0});H.bar=qa;ca.scatter=w(T,{lineWidth:0,tooltip:{headerFormat:'<span style="color:{series.color}">â—</span> <span style="font-size: 10px;"> {series.name}</span><br/>',
pointFormat:"x: <b>{point.x}</b><br/>y: <b>{point.y}</b><br/>"},stickyTracking:!1});qa=ma(O,{type:"scatter",sorted:!1,requireSorting:!1,noSharedTooltip:!0,trackerGroups:["markerGroup","dataLabelsGroup"],takeOrdinalPosition:!1,singularTooltips:!0,drawGraph:function(){this.options.lineWidth&&O.prototype.drawGraph.call(this)}});H.scatter=qa;ca.pie=w(T,{borderColor:"#FFFFFF",borderWidth:1,center:[null,null],clip:!1,colorByPoint:!0,dataLabels:{distance:30,enabled:!0,formatter:function(){return this.point.name}},
ignoreHiddenPoint:!0,legendType:"point",marker:null,size:null,showInLegend:!1,slicedOffset:10,states:{hover:{brightness:0.1,shadow:!1}},stickyTracking:!1,tooltip:{followPointer:!0}});T={type:"pie",isCartesian:!1,pointClass:ma(Fa,{init:function(){Fa.prototype.init.apply(this,arguments);var a=this,b;if(a.y<0)a.y=null;r(a,{visible:a.visible!==!1,name:p(a.name,"Slice")});b=function(b){a.slice(b.type==="select")};N(a,"select",b);N(a,"unselect",b);return a},setVisible:function(a){var b=this,c=b.series,
d=c.chart;b.visible=b.options.visible=a=a===u?!b.visible:a;c.options.data[Ma(b,c.data)]=b.options;q(["graphic","dataLabel","connector","shadowGroup"],function(c){if(b[c])b[c][a?"show":"hide"](!0)});b.legendItem&&d.legend.colorizeItem(b,a);if(!c.isDirty&&c.options.ignoreHiddenPoint)c.isDirty=!0,d.redraw()},slice:function(a,b,c){var d=this.series;Ra(c,d.chart);p(b,!0);this.sliced=this.options.sliced=a=s(a)?a:!this.sliced;d.options.data[Ma(this,d.data)]=this.options;a=a?this.slicedTranslation:{translateX:0,
translateY:0};this.graphic.animate(a);this.shadowGroup&&this.shadowGroup.animate(a)},haloPath:function(a){var b=this.shapeArgs,c=this.series.chart;return this.sliced||!this.visible?[]:this.series.chart.renderer.symbols.arc(c.plotLeft+b.x,c.plotTop+b.y,b.r+a,b.r+a,{innerR:this.shapeArgs.r,start:b.start,end:b.end})}}),requireSorting:!1,noSharedTooltip:!0,trackerGroups:["group","dataLabelsGroup"],axisTypes:[],pointAttrToOptions:{stroke:"borderColor","stroke-width":"borderWidth",fill:"color"},singularTooltips:!0,
getColor:sa,animate:function(a){var b=this,c=b.points,d=b.startAngleRad;if(!a)q(c,function(a){var c=a.graphic,a=a.shapeArgs;c&&(c.attr({r:b.center[3]/2,start:d,end:d}),c.animate({r:a.r,start:a.start,end:a.end},b.options.animation))}),b.animate=null},setData:function(a,b,c,d){O.prototype.setData.call(this,a,!1,c,d);this.processData();this.generatePoints();p(b,!0)&&this.chart.redraw(c)},generatePoints:function(){var a,b=0,c,d,e,f=this.options.ignoreHiddenPoint;O.prototype.generatePoints.call(this);
c=this.points;d=c.length;for(a=0;a<d;a++)e=c[a],b+=f&&!e.visible?0:e.y;this.total=b;for(a=0;a<d;a++)e=c[a],e.percentage=b>0?e.y/b*100:0,e.total=b},translate:function(a){this.generatePoints();var b=0,c=this.options,d=c.slicedOffset,e=d+c.borderWidth,f,g,h,i=c.startAngle||0,j=this.startAngleRad=oa/180*(i-90),i=(this.endAngleRad=oa/180*(p(c.endAngle,i+360)-90))-j,k=this.points,l=c.dataLabels.distance,c=c.ignoreHiddenPoint,n,m=k.length,o;if(!a)this.center=a=this.getCenter();this.getX=function(b,c){h=
V.asin(L((b-a[1])/(a[2]/2+l),1));return a[0]+(c?-1:1)*aa(h)*(a[2]/2+l)};for(n=0;n<m;n++){o=k[n];f=j+b*i;if(!c||o.visible)b+=o.percentage/100;g=j+b*i;o.shapeType="arc";o.shapeArgs={x:a[0],y:a[1],r:a[2]/2,innerR:a[3]/2,start:v(f*1E3)/1E3,end:v(g*1E3)/1E3};h=(g+f)/2;h>1.5*oa?h-=2*oa:h<-oa/2&&(h+=2*oa);o.slicedTranslation={translateX:v(aa(h)*d),translateY:v(fa(h)*d)};f=aa(h)*a[2]/2;g=fa(h)*a[2]/2;o.tooltipPos=[a[0]+f*0.7,a[1]+g*0.7];o.half=h<-oa/2||h>oa/2?1:0;o.angle=h;e=L(e,l/2);o.labelPos=[a[0]+f+aa(h)*
l,a[1]+g+fa(h)*l,a[0]+f+aa(h)*e,a[1]+g+fa(h)*e,a[0]+f,a[1]+g,l<0?"center":o.half?"right":"left",h]}},drawGraph:null,drawPoints:function(){var a=this,b=a.chart.renderer,c,d,e=a.options.shadow,f,g;if(e&&!a.shadowGroup)a.shadowGroup=b.g("shadow").add(a.group);q(a.points,function(h){d=h.graphic;g=h.shapeArgs;f=h.shadowGroup;if(e&&!f)f=h.shadowGroup=b.g("shadow").add(a.shadowGroup);c=h.sliced?h.slicedTranslation:{translateX:0,translateY:0};f&&f.attr(c);d?d.animate(r(g,c)):h.graphic=d=b[h.shapeType](g).setRadialReference(a.center).attr(h.pointAttr[h.selected?
"select":""]).attr({"stroke-linejoin":"round"}).attr(c).add(a.group).shadow(e,f);h.visible!==void 0&&h.setVisible(h.visible)})},sortByAngle:function(a,b){a.sort(function(a,d){return a.angle!==void 0&&(d.angle-a.angle)*b})},drawLegendSymbol:M.drawRectangle,getCenter:Z.getCenter,getSymbol:sa};T=ma(O,T);H.pie=T;O.prototype.drawDataLabels=function(){var a=this,b=a.options,c=b.cursor,d=b.dataLabels,e=a.points,f,g,h=a.hasRendered||0,i,j;if(d.enabled||a._hasPointLabels)a.dlProcessOptions&&a.dlProcessOptions(d),
j=a.plotGroup("dataLabelsGroup","data-labels",d.defer?"hidden":"visible",d.zIndex||6),p(d.defer,!0)&&(j.attr({opacity:+h}),h||N(a,"afterAnimate",function(){a.visible&&j.show();j[b.animation?"animate":"attr"]({opacity:1},{duration:200})})),g=d,q(e,function(b){var e,h=b.dataLabel,m,o,q=b.connector,t=!0;f=b.options&&b.options.dataLabels;e=p(f&&f.enabled,g.enabled);if(h&&!e)b.dataLabel=h.destroy();else if(e){d=w(g,f);e=d.rotation;m=b.getLabelConfig();i=d.format?Ja(d.format,m):d.formatter.call(m,d);d.style.color=
p(d.color,d.style.color,a.color,"black");if(h)if(s(i))h.attr({text:i}),t=!1;else{if(b.dataLabel=h=h.destroy(),q)b.connector=q.destroy()}else if(s(i)){h={fill:d.backgroundColor,stroke:d.borderColor,"stroke-width":d.borderWidth,r:d.borderRadius||0,rotation:e,padding:d.padding,zIndex:1};for(o in h)h[o]===u&&delete h[o];h=b.dataLabel=a.chart.renderer[e?"text":"label"](i,0,-999,null,null,null,d.useHTML).attr(h).css(r(d.style,c&&{cursor:c})).add(j).shadow(d.shadow)}h&&a.alignDataLabel(b,h,d,null,t)}})};
O.prototype.alignDataLabel=function(a,b,c,d,e){var f=this.chart,g=f.inverted,h=p(a.plotX,-999),i=p(a.plotY,-999),j=b.getBBox();if(a=this.visible&&(a.series.forceDL||f.isInsidePlot(h,v(i),g)||d&&f.isInsidePlot(h,g?d.x+1:d.y+d.height-1,g)))d=r({x:g?f.plotWidth-i:h,y:v(g?f.plotHeight-h:i),width:0,height:0},d),r(c,{width:j.width,height:j.height}),c.rotation?b[e?"attr":"animate"]({x:d.x+c.x+d.width/2,y:d.y+c.y+d.height/2}).attr({align:c.align}):(b.align(c,null,d),g=b.alignAttr,p(c.overflow,"justify")===
"justify"?this.justifyDataLabel(b,c,g,j,d,e):p(c.crop,!0)&&(a=f.isInsidePlot(g.x,g.y)&&f.isInsidePlot(g.x+j.width,g.y+j.height)));if(!a)b.attr({y:-999}),b.placed=!1};O.prototype.justifyDataLabel=function(a,b,c,d,e,f){var g=this.chart,h=b.align,i=b.verticalAlign,j,k;j=c.x;if(j<0)h==="right"?b.align="left":b.x=-j,k=!0;j=c.x+d.width;if(j>g.plotWidth)h==="left"?b.align="right":b.x=g.plotWidth-j,k=!0;j=c.y;if(j<0)i==="bottom"?b.verticalAlign="top":b.y=-j,k=!0;j=c.y+d.height;if(j>g.plotHeight)i==="top"?
b.verticalAlign="bottom":b.y=g.plotHeight-j,k=!0;if(k)a.placed=!f,a.align(b,null,e)};if(H.pie)H.pie.prototype.drawDataLabels=function(){var a=this,b=a.data,c,d=a.chart,e=a.options.dataLabels,f=p(e.connectorPadding,10),g=p(e.connectorWidth,1),h=d.plotWidth,i=d.plotHeight,j,k,l=p(e.softConnector,!0),n=e.distance,m=a.center,o=m[2]/2,r=m[1],s=n>0,u,w,x,A=[[],[]],y,B,I,H,z,R=[0,0,0,0],N=function(a,b){return b.y-a.y};if(a.visible&&(e.enabled||a._hasPointLabels)){O.prototype.drawDataLabels.apply(a);q(b,
function(a){a.dataLabel&&a.visible&&A[a.half].push(a)});for(H=2;H--;){var G=[],M=[],F=A[H],K=F.length,E;if(K){a.sortByAngle(F,H-0.5);for(z=b=0;!b&&F[z];)b=F[z]&&F[z].dataLabel&&(F[z].dataLabel.getBBox().height||21),z++;if(n>0){w=L(r+o+n,d.plotHeight);for(z=t(0,r-o-n);z<=w;z+=b)G.push(z);w=G.length;if(K>w){c=[].concat(F);c.sort(N);for(z=K;z--;)c[z].rank=z;for(z=K;z--;)F[z].rank>=w&&F.splice(z,1);K=F.length}for(z=0;z<K;z++){c=F[z];x=c.labelPos;c=9999;var S,P;for(P=0;P<w;P++)S=Q(G[P]-x[1]),S<c&&(c=S,
E=P);if(E<z&&G[z]!==null)E=z;else for(w<K-z+E&&G[z]!==null&&(E=w-K+z);G[E]===null;)E++;M.push({i:E,y:G[E]});G[E]=null}M.sort(N)}for(z=0;z<K;z++){c=F[z];x=c.labelPos;u=c.dataLabel;I=c.visible===!1?"hidden":"visible";c=x[1];if(n>0){if(w=M.pop(),E=w.i,B=w.y,c>B&&G[E+1]!==null||c<B&&G[E-1]!==null)B=L(t(0,c),d.plotHeight)}else B=c;y=e.justify?m[0]+(H?-1:1)*(o+n):a.getX(B===r-o-n||B===r+o+n?c:B,H);u._attr={visibility:I,align:x[6]};u._pos={x:y+e.x+({left:f,right:-f}[x[6]]||0),y:B+e.y-10};u.connX=y;u.connY=
B;if(this.options.size===null)w=u.width,y-w<f?R[3]=t(v(w-y+f),R[3]):y+w>h-f&&(R[1]=t(v(y+w-h+f),R[1])),B-b/2<0?R[0]=t(v(-B+b/2),R[0]):B+b/2>i&&(R[2]=t(v(B+b/2-i),R[2]))}}}if(Ca(R)===0||this.verifyDataLabelOverflow(R))this.placeDataLabels(),s&&g&&q(this.points,function(b){j=b.connector;x=b.labelPos;if((u=b.dataLabel)&&u._pos)I=u._attr.visibility,y=u.connX,B=u.connY,k=l?["M",y+(x[6]==="left"?5:-5),B,"C",y,B,2*x[2]-x[4],2*x[3]-x[5],x[2],x[3],"L",x[4],x[5]]:["M",y+(x[6]==="left"?5:-5),B,"L",x[2],x[3],
"L",x[4],x[5]],j?(j.animate({d:k}),j.attr("visibility",I)):b.connector=j=a.chart.renderer.path(k).attr({"stroke-width":g,stroke:e.connectorColor||b.color||"#606060",visibility:I}).add(a.dataLabelsGroup);else if(j)b.connector=j.destroy()})}},H.pie.prototype.placeDataLabels=function(){q(this.points,function(a){var a=a.dataLabel,b;if(a)(b=a._pos)?(a.attr(a._attr),a[a.moved?"animate":"attr"](b),a.moved=!0):a&&a.attr({y:-999})})},H.pie.prototype.alignDataLabel=sa,H.pie.prototype.verifyDataLabelOverflow=
function(a){var b=this.center,c=this.options,d=c.center,e=c=c.minSize||80,f;d[0]!==null?e=t(b[2]-t(a[1],a[3]),c):(e=t(b[2]-a[1]-a[3],c),b[0]+=(a[3]-a[1])/2);d[1]!==null?e=t(L(e,b[2]-t(a[0],a[2])),c):(e=t(L(e,b[2]-a[0]-a[2]),c),b[1]+=(a[0]-a[2])/2);e<b[2]?(b[2]=e,this.translate(b),q(this.points,function(a){if(a.dataLabel)a.dataLabel._pos=null}),this.drawDataLabels&&this.drawDataLabels()):f=!0;return f};if(H.column)H.column.prototype.alignDataLabel=function(a,b,c,d,e){var f=this.chart,g=f.inverted,
h=a.dlBox||a.shapeArgs,i=a.below||a.plotY>p(this.translatedThreshold,f.plotSizeY),j=p(c.inside,!!this.options.stacking);if(h&&(d=w(h),g&&(d={x:f.plotWidth-d.y-d.height,y:f.plotHeight-d.x-d.width,width:d.height,height:d.width}),!j))g?(d.x+=i?0:d.width,d.width=0):(d.y+=i?d.height:0,d.height=0);c.align=p(c.align,!g||j?"center":i?"right":"left");c.verticalAlign=p(c.verticalAlign,g||j?"middle":i?"top":"bottom");O.prototype.alignDataLabel.call(this,a,b,c,d,e)};T=K.TrackerMixin={drawTrackerPoint:function(){var a=
this,b=a.chart,c=b.pointer,d=a.options.cursor,e=d&&{cursor:d},f=function(c){var d=c.target,e;if(b.hoverSeries!==a)a.onMouseOver();for(;d&&!e;)e=d.point,d=d.parentNode;if(e!==u&&e!==b.hoverPoint)e.onMouseOver(c)};q(a.points,function(a){if(a.graphic)a.graphic.element.point=a;if(a.dataLabel)a.dataLabel.element.point=a});if(!a._hasTracking)q(a.trackerGroups,function(b){if(a[b]&&(a[b].addClass("highcharts-tracker").on("mouseover",f).on("mouseout",function(a){c.onTrackerMouseOut(a)}).css(e),$a))a[b].on("touchstart",
f)}),a._hasTracking=!0},drawTrackerGraph:function(){var a=this,b=a.options,c=b.trackByArea,d=[].concat(c?a.areaPath:a.graphPath),e=d.length,f=a.chart,g=f.pointer,h=f.renderer,i=f.options.tooltip.snap,j=a.tracker,k=b.cursor,l=k&&{cursor:k},k=a.singlePoints,n,m=function(){if(f.hoverSeries!==a)a.onMouseOver()},o="rgba(192,192,192,"+(ba?1.0E-4:0.002)+")";if(e&&!c)for(n=e+1;n--;)d[n]==="M"&&d.splice(n+1,0,d[n+1]-i,d[n+2],"L"),(n&&d[n]==="M"||n===e)&&d.splice(n,0,"L",d[n-2]+i,d[n-1]);for(n=0;n<k.length;n++)e=
k[n],d.push("M",e.plotX-i,e.plotY,"L",e.plotX+i,e.plotY);j?j.attr({d:d}):(a.tracker=h.path(d).attr({"stroke-linejoin":"round",visibility:a.visible?"visible":"hidden",stroke:o,fill:c?o:P,"stroke-width":b.lineWidth+(c?0:2*i),zIndex:2}).add(a.group),q([a.tracker,a.markerGroup],function(a){a.addClass("highcharts-tracker").on("mouseover",m).on("mouseout",function(a){g.onTrackerMouseOut(a)}).css(l);if($a)a.on("touchstart",m)}))}};if(H.column)ia.prototype.drawTracker=T.drawTrackerPoint;if(H.pie)H.pie.prototype.drawTracker=
T.drawTrackerPoint;if(H.scatter)qa.prototype.drawTracker=T.drawTrackerPoint;r(lb.prototype,{setItemEvents:function(a,b,c,d,e){var f=this;(c?b:a.legendGroup).on("mouseover",function(){a.setState("hover");b.css(f.options.itemHoverStyle)}).on("mouseout",function(){b.css(a.visible?d:e);a.setState()}).on("click",function(b){var c=function(){a.setVisible()},b={browserEvent:b};a.firePointEvent?a.firePointEvent("legendItemClick",b,c):I(a,"legendItemClick",b,c)})},createCheckboxForItem:function(a){a.checkbox=
$("input",{type:"checkbox",checked:a.selected,defaultChecked:a.selected},this.options.itemCheckboxStyle,this.chart.container);N(a.checkbox,"click",function(b){I(a,"checkboxClick",{checked:b.target.checked},function(){a.select()})})}});E.legend.itemStyle.cursor="pointer";r(Ya.prototype,{showResetZoom:function(){var a=this,b=E.lang,c=a.options.chart.resetZoomButton,d=c.theme,e=d.states,f=c.relativeTo==="chart"?null:"plotBox";this.resetZoomButton=a.renderer.button(b.resetZoom,null,null,function(){a.zoomOut()},
d,e&&e.hover).attr({align:c.position.align,title:b.resetZoomTitle}).add().align(c.position,!1,f)},zoomOut:function(){var a=this;I(a,"selection",{resetSelection:!0},function(){a.zoom()})},zoom:function(a){var b,c=this.pointer,d=!1,e;!a||a.resetSelection?q(this.axes,function(a){b=a.zoom()}):q(a.xAxis.concat(a.yAxis),function(a){var e=a.axis,h=e.isXAxis;if(c[h?"zoomX":"zoomY"]||c[h?"pinchX":"pinchY"])b=e.zoom(a.min,a.max),e.displayBtn&&(d=!0)});e=this.resetZoomButton;if(d&&!e)this.showResetZoom();else if(!d&&
da(e))this.resetZoomButton=e.destroy();b&&this.redraw(p(this.options.chart.animation,a&&a.animation,this.pointCount<100))},pan:function(a,b){var c=this,d=c.hoverPoints,e;d&&q(d,function(a){a.setState()});q(b==="xy"?[1,0]:[1],function(b){var d=a[b?"chartX":"chartY"],h=c[b?"xAxis":"yAxis"][0],i=c[b?"mouseDownX":"mouseDownY"],j=(h.pointRange||0)/2,k=h.getExtremes(),l=h.toValue(i-d,!0)+j,i=h.toValue(i+c[b?"plotWidth":"plotHeight"]-d,!0)-j;h.series.length&&l>L(k.dataMin,k.min)&&i<t(k.dataMax,k.max)&&(h.setExtremes(l,
i,!1,!1,{trigger:"pan"}),e=!0);c[b?"mouseDownX":"mouseDownY"]=d});e&&c.redraw(!1);B(c.container,{cursor:"move"})}});r(Fa.prototype,{select:function(a,b){var c=this,d=c.series,e=d.chart,a=p(a,!c.selected);c.firePointEvent(a?"select":"unselect",{accumulate:b},function(){c.selected=c.options.selected=a;d.options.data[Ma(c,d.data)]=c.options;c.setState(a&&"select");b||q(e.getSelectedPoints(),function(a){if(a.selected&&a!==c)a.selected=a.options.selected=!1,d.options.data[Ma(a,d.data)]=a.options,a.setState(""),
a.firePointEvent("unselect")})})},onMouseOver:function(a){var b=this.series,c=b.chart,d=c.tooltip,e=c.hoverPoint;if(e&&e!==this)e.onMouseOut();this.firePointEvent("mouseOver");d&&(!d.shared||b.noSharedTooltip)&&d.refresh(this,a);this.setState("hover");c.hoverPoint=this},onMouseOut:function(){var a=this.series.chart,b=a.hoverPoints;this.firePointEvent("mouseOut");if(!b||Ma(this,b)===-1)this.setState(),a.hoverPoint=null},importEvents:function(){if(!this.hasImportedEvents){var a=w(this.series.options.point,
this.options).events,b;this.events=a;for(b in a)N(this,b,a[b]);this.hasImportedEvents=!0}},setState:function(a,b){var c=this.plotX,d=this.plotY,e=this.series,f=e.options.states,g=ca[e.type].marker&&e.options.marker,h=g&&!g.enabled,i=g&&g.states[a],j=i&&i.enabled===!1,k=e.stateMarkerGraphic,l=this.marker||{},n=e.chart,m=e.halo,o,a=a||"";o=this.pointAttr[a]||e.pointAttr[a];if(!(a===this.state&&!b||this.selected&&a!=="select"||f[a]&&f[a].enabled===!1||a&&(j||h&&i.enabled===!1)||a&&l.states&&l.states[a]&&
l.states[a].enabled===!1)){if(this.graphic)g=g&&this.graphic.symbolName&&o.r,this.graphic.attr(w(o,g?{x:c-g,y:d-g,width:2*g,height:2*g}:{})),k&&k.hide();else{if(a&&i)if(g=i.radius,l=l.symbol||e.symbol,k&&k.currentSymbol!==l&&(k=k.destroy()),k)k[b?"animate":"attr"]({x:c-g,y:d-g});else if(l)e.stateMarkerGraphic=k=n.renderer.symbol(l,c-g,d-g,2*g,2*g).attr(o).add(e.markerGroup),k.currentSymbol=l;if(k)k[a&&n.isInsidePlot(c,d,n.inverted)?"show":"hide"]()}if((c=f[a]&&f[a].halo)&&c.size){if(!m)e.halo=m=n.renderer.path().add(e.seriesGroup);
m.attr(r({fill:ya(this.color||e.color).setOpacity(c.opacity).get()},c.attributes))[b?"animate":"attr"]({d:this.haloPath(c.size)})}else m&&m.attr({d:[]});this.state=a}},haloPath:function(a){var b=this.series,c=b.chart,d=b.getPlotBox(),e=c.inverted;return c.renderer.symbols.circle(d.translateX+(e?b.yAxis.len-this.plotY:this.plotX)-a,d.translateY+(e?b.xAxis.len-this.plotX:this.plotY)-a,a*2,a*2)}});r(O.prototype,{onMouseOver:function(){var a=this.chart,b=a.hoverSeries;if(b&&b!==this)b.onMouseOut();this.options.events.mouseOver&&
I(this,"mouseOver");this.setState("hover");a.hoverSeries=this},onMouseOut:function(){var a=this.options,b=this.chart,c=b.tooltip,d=b.hoverPoint;if(d)d.onMouseOut();this&&a.events.mouseOut&&I(this,"mouseOut");c&&!a.stickyTracking&&(!c.shared||this.noSharedTooltip)&&c.hide();this.setState();b.hoverSeries=null},setState:function(a){var b=this.options,c=this.graph,d=this.graphNeg,e=b.states,b=b.lineWidth,a=a||"";if(this.state!==a)this.state=a,e[a]&&e[a].enabled===!1||(a&&(b=e[a].lineWidth||b+(e[a].lineWidthPlus||
0)),c&&!c.dashstyle&&(a={"stroke-width":b},c.attr(a),d&&d.attr(a)))},setVisible:function(a,b){var c=this,d=c.chart,e=c.legendItem,f,g=d.options.chart.ignoreHiddenSeries,h=c.visible;f=(c.visible=a=c.userOptions.visible=a===u?!h:a)?"show":"hide";q(["group","dataLabelsGroup","markerGroup","tracker"],function(a){if(c[a])c[a][f]()});if(d.hoverSeries===c)c.onMouseOut();e&&d.legend.colorizeItem(c,a);c.isDirty=!0;c.options.stacking&&q(d.series,function(a){if(a.options.stacking&&a.visible)a.isDirty=!0});q(c.linkedSeries,
function(b){b.setVisible(a,!1)});if(g)d.isDirtyBox=!0;b!==!1&&d.redraw();I(c,f)},setTooltipPoints:function(a){var b=[],c,d,e=this.xAxis,f=e&&e.getExtremes(),g=e?e.tooltipLen||e.len:this.chart.plotSizeX,h,i,j=[];if(!(this.options.enableMouseTracking===!1||this.singularTooltips)){if(a)this.tooltipPoints=null;q(this.segments||this.points,function(a){b=b.concat(a)});e&&e.reversed&&(b=b.reverse());this.orderTooltipPoints&&this.orderTooltipPoints(b);a=b.length;for(i=0;i<a;i++)if(e=b[i],c=e.x,c>=f.min&&
c<=f.max){h=b[i+1];c=d===u?0:d+1;for(d=b[i+1]?L(t(0,U((e.clientX+(h?h.wrappedClientX||h.clientX:g))/2)),g):g;c>=0&&c<=d;)j[c++]=e}this.tooltipPoints=j}},show:function(){this.setVisible(!0)},hide:function(){this.setVisible(!1)},select:function(a){this.selected=a=a===u?!this.selected:a;if(this.checkbox)this.checkbox.checked=a;I(this,a?"select":"unselect")},drawTracker:T.drawTrackerGraph});r(K,{Axis:na,Chart:Ya,Color:ya,Point:Fa,Tick:Ta,Renderer:Za,Series:O,SVGElement:S,SVGRenderer:ta,arrayMin:Oa,arrayMax:Ca,
charts:W,dateFormat:cb,format:Ja,pathAnim:vb,getOptions:function(){return E},hasBidiBug:Ob,isTouchDevice:Ib,numberFormat:Ba,seriesTypes:H,setOptions:function(a){E=w(!0,E,a);Bb();return E},addEvent:N,removeEvent:X,createElement:$,discardElement:Qa,css:B,each:q,extend:r,map:Va,merge:w,pick:p,splat:ra,extendClass:ma,pInt:y,wrap:Na,svg:ba,canvas:ga,vml:!ba&&!ga,product:"Highcharts",version:"4.0.4"})})();
(function () {
    'use strict';

    var truncate = function (s, l) {
        if (s.length <= l) {
            return s;
        } else {
            var ts = "";
            var subs = s.split('&');
            if (subs[0].length > l) {
                return subs[0].substring(0, l) + "&hellip;";
            } else {
                ts = ts + subs[0];
            }
            for (var i = 1; i < subs.length; i++) {
                var end = subs[i].indexOf(';');
                l += end + 1;
                ts = ts + '&' + subs[i];
                if (ts.length >= l) {
                    return ts.substring(0, l) + "&hellip;";
                }
            }
            return ts;
        }
    };

    angular.module('ngTextTruncate', [])
        .directive("ngTextTruncate", function ($compile, ValidationServices, CharBasedTruncation, WordBasedTruncation, HtmlBasedTruncation) {
            return {
                restrict: "A",
                scope: {
                    text: "=ngTextTruncate",
                    charsThreshould: "@ngTtCharsThreshold",
                    wordsThreshould: "@ngTtWordsThreshold",
                    htmlThreshould: "@ngTtHtmlThreshold",
                    customMoreLabel: "@ngTtMoreLabel",
                    customLessLabel: "@ngTtLessLabel"
                },
                controller: function ($scope, $element, $attrs) {
                    $scope.toggleShow = function () {
                        $scope.open = !$scope.open;
                    };

                    $scope.useToggling = $attrs.ngTtNoToggling === undefined;
                },
                link: function ($scope, $element, $attrs) {
                    $scope.open = false;

                    ValidationServices.failIfWrongThreshouldConfig($scope.charsThreshould, $scope.wordsThreshould, $scope.htmlThreshould);

                    var CHARS_THRESHOLD = parseInt($scope.charsThreshould);
                    var WORDS_THRESHOLD = parseInt($scope.wordsThreshould);
                    var HTML_THRESHOLD = parseInt($scope.htmlThreshould);

                    $scope.$watch("text", function () {
                        $element.empty();

                        if (CHARS_THRESHOLD) {
                            if ($scope.text && CharBasedTruncation.truncationApplies($scope, CHARS_THRESHOLD)) {
                                CharBasedTruncation.applyTruncation(CHARS_THRESHOLD, $scope, $element);

                            } else {
                                $element.append($scope.text);
                            }

                        } else if (HTML_THRESHOLD) {
                            if ($scope.text && HtmlBasedTruncation.truncationApplies($scope, HTML_THRESHOLD)) {
                                HtmlBasedTruncation.applyTruncation(HTML_THRESHOLD, $scope, $element);

                            } else {
                                $element.append($scope.text);
                            }

                        } else {

                            if ($scope.text && WordBasedTruncation.truncationApplies($scope, WORDS_THRESHOLD)) {
                                WordBasedTruncation.applyTruncation(WORDS_THRESHOLD, $scope, $element);

                            } else {
                                $element.append($scope.text);
                            }

                        }
                    });
                }
            };
        })
        .factory("ValidationServices", function () {
            return {
                failIfWrongThreshouldConfig: function (firstThreshould, secondThreshould, thirdThreshould) {
                    if ((!firstThreshould && !secondThreshould && !thirdThreshould) || (firstThreshould && secondThreshould && thirdThreshould)) {
                        throw "You must specify one, and only one, type of threshould (chars or words or html)";
                    }
                }
            };
        })
        .factory("CharBasedTruncation", function ($compile) {
            return {
                truncationApplies: function ($scope, threshould) {
                    return $scope.text.length > threshould;
                },

                applyTruncation: function (threshould, $scope, $element) {
                    if ($scope.useToggling) {
                        var el = angular.element("<span>" +
                            $scope.text.substr(0, threshould) +
                            "<span ng-show='!open'>...</span>" +
                            "<span class='btn-link ngTruncateToggleText' " +
                            "ng-click='toggleShow()'" +
                            "ng-show='!open'>" +
                            " " + ($scope.customMoreLabel ? $scope.customMoreLabel : "More") +
                            "</span>" +
                            "<span ng-show='open'>" +
                            $scope.text.substring(threshould) +
                            "<span class='btn-link ngTruncateToggleText'" +
                            "ng-click='toggleShow()'>" +
                            " " + ($scope.customLessLabel ? $scope.customLessLabel : "Less") +
                            "</span>" +
                            "</span>" +
                            "</span>");
                        $compile(el)($scope);
                        $element.append(el);

                    } else {
                        $element.append($scope.text.substr(0, threshould) + "...");

                    }
                }
            };
        })
        .factory("WordBasedTruncation", function ($compile) {
            return {
                truncationApplies: function ($scope, threshould) {
                    return $scope.text.split(" ").length > threshould;
                },

                applyTruncation: function (threshould, $scope, $element) {
                    var splitText = $scope.text.split(" ");
                    if ($scope.useToggling) {
                        var el = angular.element("<span>" +
                            splitText.slice(0, threshould).join(" ") + " " +
                            "<span ng-show='!open'>...</span>" +
                            "<span class='btn-link ngTruncateToggleText' " +
                            "ng-click='toggleShow()'" +
                            "ng-show='!open'>" +
                            " " + ($scope.customMoreLabel ? $scope.customMoreLabel : "More") +
                            "</span>" +
                            "<span ng-show='open'>" +
                            splitText.slice(threshould, splitText.length).join(" ") +
                            "<span class='btn-link ngTruncateToggleText'" +
                            "ng-click='toggleShow()'>" +
                            " " + ($scope.customLessLabel ? $scope.customLessLabel : "Less") +
                            "</span>" +
                            "</span>" +
                            "</span>");
                        $compile(el)($scope);
                        $element.append(el);

                    } else {
                        $element.append(splitText.slice(0, threshould).join(" ") + "...");
                    }
                }
            };
        })
        .factory("HtmlBasedTruncation", function ($compile) {
            return {
                truncationApplies: function ($scope, threshould) {
                    // Exclude HTML
                    var txt = angular.copy($scope.text);
                    txt = txt.replace(/<[^>]+>/gm, '');

                    return txt.length > threshould;
                },

                applyTruncation: function (threshould, $scope, $element) {
                    var txt = angular.copy($scope.text);
                    var firstTag = '';
                    try{
                        firstTag = $(angular.copy(txt));
                    } catch (er) {
                        firstTag = $('<div>' + angular.copy(txt) + '</div>');
                    }
                    var divContent = document.createElement('div');
                    var get_html = firstTag.length > 0 ? truncate(angular.element(firstTag[0]).html(), threshould) : angular.element(firstTag[0]).html();
                    divContent.innerHTML = get_html;

                    if ($scope.useToggling) {
                        var tHtml = "<span ng-show='!open'><span>" + divContent.innerHTML + "</span>" + "<small class='btn-link ngTruncateToggleText' " + "ng-click='toggleShow()'" + "ng-show='!open'>" + " " + ($scope.customMoreLabel ? $scope.customMoreLabel : "More") + "</small></span>" +
                            "<span ng-show='open'><span>" + txt + "</span>" + "<small class='btn-link ngTruncateToggleText'" + "ng-click='toggleShow()'>" + " " + ($scope.customLessLabel ? $scope.customLessLabel : "Less") + "</small></span>";

                        var el = angular.element(tHtml);
                        $compile(el)($scope);
                        $element.append(el);

                    } else {
                        $element.append(divContent.innerHTML);
                    }
                }
            };
        });

}());

angular.module('ngCacheBuster', [])
  .config(['$httpProvider', function ($httpProvider) {
      return $httpProvider.interceptors.push('httpRequestInterceptorCacheBuster');
  }])
    .provider('httpRequestInterceptorCacheBuster', function () {

        // this.matchlist = [/.*partials.*/, /.*views.*/];

        this.matchlist = [/.*template.*/, /.*tpl.*/, /.*hbs.*/];
        this.logRequests = false;

        //Default to whitelist (i.e. block all except matches)
        this.black = false;

        //Select blacklist or whitelist, default to whitelist
        this.setMatchlist = function (list, black) {
            this.black = typeof black != 'undefined' ? black : false
            this.matchlist = list;
        };


        this.setLogRequests = function (logRequests) {
            this.logRequests = logRequests;
        };

        this.$get = ['$q', '$log', '$rootScope', function ($q, $log, $rootScope) {
            var matchlist = this.matchlist;
            var logRequests = this.logRequests;
            var black = this.black;
            if (logRequests) {
                $log.log("Blacklist? ", black);
            }
            return {
                'request': function (config) {
                    //Blacklist by default, match with whitelist
                    var busted = !black;

                    for (var i = 0; i < matchlist.length; i++) {
                        if (config.url.match(matchlist[i])) {
                            busted = black; break;
                        }
                    }

                    ////Bust if the URL was on blacklist or not on whitelist
                    //if (busted && config.url.indexOf(".tpl") == -1) {
                    //var d = new Date();
                    //config.url = config.url.replace(/[?|&]cacheBuster=\d+/,'');
                    ////Some url's allready have '?' attached
                    //config.url+=config.url.indexOf('?') === -1 ? '?' : '&'
                    //config.url += 'cacheBuster=' + d.getTime();
                    //}

                    //Bust if the URL was on blacklist or not on whitelist
                    if (busted && $rootScope.buildNumber && config.url.indexOf('ng-table') != 0 && config.url.indexOf(".tpl") == -1) {
                        //var d = new Date();
                        //Some url's allready have '?' attached
                        config.url += config.url.indexOf('?') === -1 ? '?' : '&';
                        //config.url += 'cacheBuster=' + d.getTime();
                        config.url += 'cb=' + $rootScope.buildNumber;
                    }

                    if (logRequests) {
                        var log = 'request.url =' + config.url
                        busted ? $log.warn(log) : $log.info(log)
                    }

                    return config || $q.when(config);
                }
            }
        }];
    });



/**
 * @license AngularJS v1.2.0
 * (c) 2015 Lifely
 * License: MIT
 */

angular.module('angular-carousel', [])

//
//  This service controls existing carousel instances
//
.factory('Carousel', function() {

    var Carousel = {};

    Carousel.instances = {};

    //
    // Add a new carousel instance
    //
    Carousel.add = function(slidesCount, name, scope, options) {

        // Check if name is specified
        name = name || false;
        if(!name) {
            return 'Error: no carousel name specified';
        }

        // Check slidesCount
        slidesCount = slidesCount || 0;

        // Check if carousel already exists
        var carouselExists = Carousel.instances[name] || false;
        if(carouselExists) {
            return 'Error: carousel instance already exists';
        }

        // Create carousel instance
        var instance = new constructor(slidesCount, scope, options);

        // Save new carousel instance
        Carousel.instances[name] = instance;

        return instance;
    };

    //
    // Get an existing carousel instance by name
    //
    Carousel.get = function(name) {
        var instance = Carousel.instances[name] || false;
        return instance ? instance : 'Error: carousel with name \'' + name + '\' does not exist';
    };

    //
    // Remove a carousel
    //
    Carousel.remove = function(name) {
        delete Carousel.instances[name];
    };

    //
    // Carousel prototype definition
    //
    var constructor = function(slidesCount, scope, options) {
        options = options || {};
        if (typeof options.looping === 'undefined') options.looping = true;

        var instance = this;

        this.slidesCount = slidesCount;
        this.currentSlide = 0;
        this.onSlideChangeCallbacks = [];

        // Operation: to specified index
        this.toIndex = function(index, wrapping) {
            wrapping = wrapping || false;
            this.currentSlide = index % this.slidesCount;

            // Own on slide change callbacks
            angular.forEach(this.onSlideChangeCallbacks, function(callback) {
                if(typeof(callback) === 'function') {
                    callback(instance.currentSlide, wrapping);
                }
            });

            // If the scope is not currently updating, trigger one update using a timeout of zero
            setTimeout(function() {
                scope.$apply();
            }, 0);
        };

        // Operation: to next slide
        this.next = function() {
            var nextSlide = this.currentSlide + 1,
                wrapping = false;

            if(nextSlide > this.slidesCount - 1) {
                if(options.looping){
                    nextSlide = 0;
                    wrapping = 'right';
                }
                else {
                    nextSlide = this.slidesCount - 1;
                }

            }

            this.toIndex(nextSlide, wrapping);
            return nextSlide;
        };

        // Operation: to previous slide
        this.previous = function() {
            var previousSlide = this.currentSlide - 1,
                wrapping = false;

            if(previousSlide < 0) {
                if(options.looping){
                    previousSlide = this.slidesCount - 1;
                    wrapping = 'left';
                }
                else {
                    previousSlide = 0;
                }

            }

            this.toIndex(previousSlide, wrapping);
            return previousSlide;
        };

        // Operation: on slide change
        this.onSlideChange = function(callback) {
            this.onSlideChangeCallbacks.push(callback);
            return this.onSlideChangeCallbacks.indexOf(callback);
        };

        // Operation: unbind on slide change callback
        this.unbindOnSlideChangeCallback = function(index) {
            if(typeof(this.onSlideChangeCallbacks[index]) === 'undefined') return;
            this.onSlideChangeCallbacks.splice(index, 1);
        };
    }

    return Carousel;

})

//
//  This directive makes an element with class 'ng-carousel' interactive
//  using own UI logic and HammerJS
//
.directive('ngCarousel', ['Carousel', '$compile', '$document', '$timeout', function(Carousel, $compile, $document, $timeout) {
    function isTouchDevice() {
        return 'ontouchstart' in document.documentElement;
    }

    var MOVE_TRESHOLD_PERCENTAGE = 25;

    function createEmptySlide(){
        return angular.element('<slide class="empty"></slide>');
    }

    return {
        restrict: 'AE',
        replace: true,
        scope: {
            ngCarouselWatch: '='
        },
        link: function(scope, element, attrs) {
            // Options
            var interval = false, timeoutPromise = false, random = false, name = '', looping = false;
            interval = typeof(attrs.ngCarouselTimer) !== 'undefined' && parseInt(attrs.ngCarouselTimer, 10) > 0 ? parseInt(attrs.ngCarouselTimer, 10) : false;
            random = typeof(attrs.ngCarouselRandom) !== 'undefined';
            looping = !(attrs.ngCarouselLoop === 'false');

            // Function to initialize interaction with dom (should be loaded after the dom has changed)
            var slides, currentCarousel, firstSlideCopy, lastSlideCopy, slideContainer, hammer, name;

            function copyFirstAndLastSlide() {
                firstSlideCopy = angular.element(slides[0].outerHTML);
                lastSlideCopy = angular.element(slides[slides.length - 1].outerHTML);
            }

            function makeFirstAndLastSlideEmpty() {
                firstSlideCopy = angular.element('<slide class="empty"></slide>');
                lastSlideCopy = angular.element('<slide class="empty"></slide>');
            }

            var refreshInteractionWithDom = function() {

                // Add initial classes
                element.addClass('ng-carousel');
                element.addClass(isTouchDevice() ? 'carousel-touch' : 'carousel-no-touch');

                // Find slide wrapper
                slideContainer = element.find('[slidecontainer]');

                // Remove old carousel
                var savedSlideIndex = false;
                var savedCallbacks = false;
                if(name) {
                    savedSlideIndex = Carousel.get(name).currentSlide;
                    savedCallbacks = Carousel.get(name).onSlideChangeCallbacks;
                    Carousel.remove(name);
                }

                // Remove old duplicated slides
                var removeOldVirtualSlides = function() {
                    var oldSlides = angular.element(element[0].querySelectorAll('.carousel-slide-copy'));
                    if(oldSlides.length > 0) oldSlides.remove();
                };

                // Find slides
                removeOldVirtualSlides();
                slides = element.find('[slide]');

                // Add slides before and after the current slides
                if(slides.length > 0) {

                    // Create new carousel and duplicate slides
                    name = attrs.ngCarouselName;
                    currentCarousel = Carousel.add(slides.length, attrs.ngCarouselName, scope, {
                        looping: looping
                    });
                    angular.forEach(savedCallbacks, function(savedCallback) {
                        currentCarousel.onSlideChange(savedCallback);
                        currentCarousel.unbindOnSlideChangeCallback(0);
                    });

                    // Duplicate first and last slide (for infinite effect)
                    var refreshVirtualSlides = function() {
                        removeOldVirtualSlides();
                        slides = element.find('[slide]');

                        if(looping) {
                            copyFirstAndLastSlide();
                        }
                        else {
                            makeFirstAndLastSlideEmpty();
                        }

                        firstSlideCopy.addClass('carousel-slide-copy');
                        lastSlideCopy.addClass('carousel-slide-copy');
                        slideContainer.append(firstSlideCopy);
                        slideContainer.prepend(lastSlideCopy);
                        slideContainer.addClass('carousel-ignore-first-slide');

                    };

                    refreshVirtualSlides();

                    // On slide change, move the slideContainer
                    var onSlideChangeCallback = function(slideIndex, wrapping) {
                        var newSlideIndex = slideIndex + 1; // because the first slide doesn't count

                        if(wrapping === 'left') {
                            newSlideIndex = 0; // first slide
                        } else if(wrapping === 'right') {
                            newSlideIndex = slides.length + 1; // last slide
                        }

                        move(newSlideIndex, true, function() {
                            if(wrapping === 'left') {
                                move(slides.length, false);
                            } else if(wrapping === 'right') {
                                move(1, false);
                            }
                        });

                        setNextSlideTimeout();
                        refreshVirtualSlides();
                    };
                    currentCarousel.onSlideChange(onSlideChangeCallback);

                    // If new slide was out of range, move to the new assigned one
                    if(savedSlideIndex !== false && currentCarousel.currentSlide !== savedSlideIndex) {
                        onSlideChangeCallback(currentCarousel.currentSlide, false);
                        currentCarousel.toIndex(savedSlideIndex);
                    }

                    // Option: random
                    if(random) {
                        var randomSlide = Math.floor(Math.random() * currentCarousel.slidesCount);
                        currentCarousel.toIndex(randomSlide);
                    }

                    // Option: interval
                    if (interval && currentCarousel.slidesCount >= 2) {
                        setNextSlideTimeout();
                    }
                } else {
                    console.log('ng-carousel error: No slides found')
                }

                // Initialize Hammer
                if(slideContainer[0]) {
                    hammer = new Hammer.Manager(slideContainer[0]);
                    hammer.add(new Hammer.Pan({ direction: Hammer.DIRECTION_HORIZONTAL, threshold: 0 }));

                    // On pan left/right
                    hammer.on("panleft panright", function(ev) {
                        if(!ev.isFinal) carouselDrag(ev.deltaX);
                    });
                } else {
                    console.log('ng-carousel error: No slidecontainer found')
                }

            };

            // Reset interval function
            var setNextSlideTimeout = function() {
                if(!interval || currentCarousel.slidesCount < 2) return;
                if(timeoutPromise) $timeout.cancel(timeoutPromise);
                timeoutPromise = $timeout(function() {
                    currentCarousel.next();
                }, interval);
            };

            // UI move function
            var move = function(slideIndex, animate, transitionEndCallback) {
                if(animate) {
                    slideContainer.addClass('carousel-animate');
                } else {
                    slideContainer.removeClass('carousel-animate');
                }

                var rule = 'translate(-' + (100 * slideIndex) + '%, 0)';
                slideContainer.css({
                    '-webkit-transform': rule,
                       '-moz-transform': rule,
                        '-ms-transform': rule,
                         '-o-transform': rule,
                            'transform': rule
                });

                if(animate) {
                    slideContainer.on('transitionend oTransitionEnd webkitTransitionEnd', function() {
                        if(typeof transitionEndCallback === 'function') transitionEndCallback();
                        slideContainer.off('transitionend oTransitionEnd webkitTransitionEnd');
                        move(currentCarousel.currentSlide + 1, false);
                    });
                }
            };

            // Make the carousel draggable (either with touch or with mouse)
            var deltaXFactor = 0,
                width = 0;
            var carouselDrag = function(newDeltaX) {
                deltaXFactor = newDeltaX / width;
                deltaXFactor = deltaXFactor > 1 ? 1 : deltaXFactor < -1 ? -1 : deltaXFactor;
                move(currentCarousel.currentSlide + 1 - deltaXFactor, false);
            };
            var carouselPress = function() {
                width = slideContainer[0].offsetWidth;
            };
            var carouselRelease = function() {
                if(Math.abs(deltaXFactor) > MOVE_TRESHOLD_PERCENTAGE / 100) {
                    if(deltaXFactor > 0) {
                        currentCarousel.previous(); // user dragged right, go to previous slide
                    } else {
                        currentCarousel.next(); // user dragged left, go to next slide
                    }
                    deltaXFactor = 0;
                } else if(deltaXFactor > 0 || deltaXFactor < 0) {
                    move(currentCarousel.currentSlide + 1, true, function() {
                        deltaXFactor = 0;
                    });
                }
            };

            // On release
            var pressEvent = isTouchDevice() ? 'touchstart' : 'mousedown';
            var releaseEvent = isTouchDevice() ? 'touchend' : 'mouseup';
            $document.on(pressEvent, carouselPress);
            $document.on(releaseEvent, carouselRelease);

            //
            element.on('mouseover', function() {
                if(timeoutPromise) $timeout.cancel(timeoutPromise);
            });
            element.on('mouseout', setNextSlideTimeout);

            // Events to refresh the dom selectors
            var refreshInteractionWithDomTimer = $timeout(refreshInteractionWithDom, 0);
            if(typeof(attrs.ngCarouselWatch) !== 'undefined') {
                scope.$watch('ngCarouselWatch', function() {

                    // Wait for angular compile to complete
                    $timeout(refreshInteractionWithDom);

                }, true);
            }

            // Destroy all binded events on scope destroy
            scope.$on('$destroy', function() {
                $timeout.cancel(refreshInteractionWithDomTimer);
                element.off('mouseover mouseout');
                $document.off(pressEvent);
                $document.off(releaseEvent);
                slideContainer.off('transitionend oTransitionEnd webkitTransitionEnd');
                currentCarousel.onSlideChangeCallbacks = [];
                Carousel.remove(name);
            });

        }
    };
}]);

/*global angular, navigator*/

(function () {
    'use strict';

    angular
        .module('angular-click-outside', [])
        .directive('clickOutside', [
            '$document', '$parse', '$timeout',
            clickOutside
        ]);

    /**
     * @ngdoc directive
     * @name angular-click-outside.directive:clickOutside
     * @description Directive to add click outside capabilities to DOM elements
     * @requires $document
     * @requires $parse
     * @requires $timeout
     **/
    function clickOutside($document, $parse, $timeout) {
        return {
            restrict: 'A',
            link: function ($scope, elem, attr) {

                // postpone linking to next digest to allow for unique id generation
                $timeout(function () {
                    var classList = (attr.outsideIfNot !== undefined) ? attr.outsideIfNot.split(/[ ,]+/) : [],
                        fn;

                    function eventHandler(e) {
                        var i,
                            element,
                            r,
                            id,
                            classNames,
                            l;

                        // check if our element already hidden and abort if so
                        if (angular.element(elem).hasClass("ng-hide")) {
                            return;
                        }

                        // if there is no click target, no point going on
                        if (!e || !e.target) {
                            return;
                        }

                        // loop through the available elements, looking for classes in the class list that might match and so will eat
                        for (element = e.target; element; element = element.parentNode) {
                            // check if the element is the same element the directive is attached to and exit if so (props @CosticaPuntaru)
                            if (element === elem[0]) {
                                return;
                            }

                            // now we have done the initial checks, start gathering id's and classes
                            id = element.id,
                                classNames = element.className,
                                l = classList.length;

                            // Unwrap SVGAnimatedString classes
                            if (classNames && classNames.baseVal !== undefined) {
                                classNames = classNames.baseVal;
                            }

                            // if there are no class names on the element clicked, skip the check
                            if (classNames || id) {

                                // loop through the elements id's and classnames looking for exceptions
                                for (i = 0; i < l; i++) {
                                    //prepare regex for class word matching
                                    r = new RegExp('\\b' + classList[i] + '\\b');

                                    // check for exact matches on id's or classes, but only if they exist in the first place
                                    if ((id !== undefined && id === classList[i]) || (classNames && r.test(classNames))) {
                                        // now let's exit out as it is an element that has been defined as being ignored for clicking outside
                                        return;
                                    }
                                }
                            }
                        }

                        // if we have got this far, then we are good to go with processing the command passed in via the click-outside attribute
                        $timeout(function () {
                            fn = $parse(attr['clickOutside']);
                            fn($scope, { event: e });
                        });
                    }

                    // if the devices has a touchscreen, listen for this event
                    if (_hasTouch()) {
                        $document.on('touchstart', eventHandler);
                    }

                    // still listen for the click event even if there is touch to cater for touchscreen laptops
                    $document.on('click', eventHandler);

                    // when the scope is destroyed, clean up the documents event handlers as we don't want it hanging around
                    $scope.$on('$destroy', function () {
                        if (_hasTouch()) {
                            $document.off('touchstart', eventHandler);
                        }

                        $document.off('click', eventHandler);
                    });

                    /**
                     * @description Private function to attempt to figure out if we are on a touch device
                     * @private
                     **/
                    function _hasTouch() {
                        // works on most browsers, IE10/11 and Surface
                        return 'ontouchstart' in window || navigator.maxTouchPoints;
                    };
                });
            }
        };
    }
})();
/**
 * Bunch of useful filters for angularJS(with no external dependencies!)
 * @version v0.5.17 - 2017-09-22 * @link https://github.com/a8m/angular-filter
 * @author Ariel Mashraki <ariel@mashraki.co.il>
 * @license MIT License, http://www.opensource.org/licenses/MIT
 */!function (a, b, c) { "use strict"; function d(a) { return E(a) ? a : Object.keys(a).map(function (b) { return a[b] }) } function e(a) { return null === a } function f(a, b) { var d = Object.keys(a); return d.map(function (d) { return b[d] !== c && b[d] == a[d] }).indexOf(!1) == -1 } function g(a, b) { function c(a, b, c) { for (var d = 0; b + d <= a.length;) { if (a.charAt(b + d) == c) return d; d++ } return -1 } for (var d = 0, e = 0; e <= b.length; e++) { var f = c(a, d, b.charAt(e)); if (f == -1) return !1; d += f + 1 } return !0 } function h(a, b, c) { var d = 0; return a.filter(function (a) { var e = y(c) ? d < b && c(a) : d < b; return d = e ? d + 1 : d, e }) } function i(a, b) { return Math.round(a * Math.pow(10, b)) / Math.pow(10, b) } function j(a, b, c) { b = b || []; var d = Object.keys(a); return d.forEach(function (d) { if (D(a[d]) && !E(a[d])) { var e = c ? c + "." + d : c; j(a[d], b, e || d) } else { var f = c ? c + "." + d : d; b.push(f) } }), b } function k(a) { return a && a.$evalAsync && a.$watch } function l() { return function (a, b) { return a > b } } function m() { return function (a, b) { return a >= b } } function n() { return function (a, b) { return a < b } } function o() { return function (a, b) { return a <= b } } function p() { return function (a, b) { return a == b } } function q() { return function (a, b) { return a != b } } function r() { return function (a, b) { return a === b } } function s() { return function (a, b) { return a !== b } } function t(a) { return function (b, c) { return b = D(b) ? d(b) : b, !(!E(b) || z(c)) && b.some(function (b) { return B(c) && D(b) || A(c) ? a(c)(b) : b === c }) } } function u(a, b) { return b = b || 0, b >= a.length ? a : E(a[b]) ? u(a.slice(0, b).concat(a[b], a.slice(b + 1)), b) : u(a, b + 1) } function v(a) { return function (b, c) { function e(a, b) { return !z(b) && a.some(function (a) { return I(a, b) }) } if (b = D(b) ? d(b) : b, !E(b)) return b; var f = [], g = a(c); return z(c) ? b.filter(function (a, b, c) { return c.indexOf(a) === b }) : b.filter(function (a) { var b = g(a); return !e(f, b) && (f.push(b), !0) }) } } function w(a, b, c) { return b ? a + c + w(a, --b, c) : a } function x() { return function (a) { return B(a) ? a.split(" ").map(function (a) { return a.charAt(0).toUpperCase() + a.substring(1) }).join(" ") : a } } var y = b.isDefined, z = b.isUndefined, A = b.isFunction, B = b.isString, C = b.isNumber, D = b.isObject, E = b.isArray, F = b.forEach, G = b.extend, H = b.copy, I = b.equals; String.prototype.contains || (String.prototype.contains = function () { return String.prototype.indexOf.apply(this, arguments) !== -1 }), b.module("a8m.angular", []).filter("isUndefined", function () { return function (a) { return b.isUndefined(a) } }).filter("isDefined", function () { return function (a) { return b.isDefined(a) } }).filter("isFunction", function () { return function (a) { return b.isFunction(a) } }).filter("isString", function () { return function (a) { return b.isString(a) } }).filter("isNumber", function () { return function (a) { return b.isNumber(a) } }).filter("isArray", function () { return function (a) { return b.isArray(a) } }).filter("isObject", function () { return function (a) { return b.isObject(a) } }).filter("isEqual", function () { return function (a, c) { return b.equals(a, c) } }), b.module("a8m.conditions", []).filter({ isGreaterThan: l, ">": l, isGreaterThanOrEqualTo: m, ">=": m, isLessThan: n, "<": n, isLessThanOrEqualTo: o, "<=": o, isEqualTo: p, "==": p, isNotEqualTo: q, "!=": q, isIdenticalTo: r, "===": r, isNotIdenticalTo: s, "!==": s }), b.module("a8m.is-null", []).filter("isNull", function () { return function (a) { return e(a) } }), b.module("a8m.after-where", []).filter("afterWhere", function () { return function (a, b) { if (a = D(a) ? d(a) : a, !E(a) || z(b)) return a; var c = a.map(function (a) { return f(b, a) }).indexOf(!0); return a.slice(c === -1 ? 0 : c) } }), b.module("a8m.after", []).filter("after", function () { return function (a, b) { return a = D(a) ? d(a) : a, E(a) ? a.slice(b) : a } }), b.module("a8m.before-where", []).filter("beforeWhere", function () { return function (a, b) { if (a = D(a) ? d(a) : a, !E(a) || z(b)) return a; var c = a.map(function (a) { return f(b, a) }).indexOf(!0); return a.slice(0, c === -1 ? a.length : ++c) } }), b.module("a8m.before", []).filter("before", function () { return function (a, b) { return a = D(a) ? d(a) : a, E(a) ? a.slice(0, b ? --b : b) : a } }), b.module("a8m.chunk-by", ["a8m.filter-watcher"]).filter("chunkBy", ["filterWatcher", function (a) { return function (b, c, d) { function e(a, b) { for (var c = []; a--;) c[a] = b; return c } function f(a, b, c) { return E(a) ? a.map(function (a, d, f) { return d *= b, a = f.slice(d, d + b), !z(c) && a.length < b ? a.concat(e(b - a.length, c)) : a }).slice(0, Math.ceil(a.length / b)) : a } return a.isMemoized("chunkBy", arguments) || a.memoize("chunkBy", arguments, this, f(b, c, d)) } }]), b.module("a8m.concat", []).filter("concat", [function () { return function (a, b) { if (z(b)) return a; if (E(a)) return D(b) ? a.concat(d(b)) : a.concat(b); if (D(a)) { var c = d(a); return D(b) ? c.concat(d(b)) : c.concat(b) } return a } }]), b.module("a8m.contains", []).filter({ contains: ["$parse", t], some: ["$parse", t] }), b.module("a8m.count-by", []).filter("countBy", ["$parse", function (a) { return function (b, c) { var e, f = {}, g = a(c); return b = D(b) ? d(b) : b, !E(b) || z(c) ? b : (b.forEach(function (a) { e = g(a), f[e] || (f[e] = 0), f[e]++ }), f) } }]), b.module("a8m.defaults", []).filter("defaults", ["$parse", function (a) { return function (b, c) { if (b = D(b) ? d(b) : b, !E(b) || !D(c)) return b; var e = j(c); return b.forEach(function (b) { e.forEach(function (d) { var e = a(d), f = e.assign; z(e(b)) && f(b, e(c)) }) }), b } }]), b.module("a8m.every", []).filter("every", ["$parse", function (a) { return function (b, c) { return b = D(b) ? d(b) : b, !(E(b) && !z(c)) || b.every(function (b) { return D(b) || A(c) ? a(c)(b) : b === c }) } }]), b.module("a8m.filter-by", []).filter("filterBy", ["$parse", function (a) { return function (b, e, f, g) { var h; return f = B(f) || C(f) ? String(f).toLowerCase() : c, b = D(b) ? d(b) : b, !E(b) || z(f) ? b : b.filter(function (b) { return e.some(function (c) { if (~c.indexOf("+")) { var d = c.replace(/\s+/g, "").split("+"); h = d.map(function (c) { return a(c)(b) }).join(" ") } else h = a(c)(b); return !(!B(h) && !C(h)) && (h = String(h).toLowerCase(), g ? h === f : h.contains(f)) }) }) } }]), b.module("a8m.first", []).filter("first", ["$parse", function (a) { return function (b) { var e, f, g; return b = D(b) ? d(b) : b, E(b) ? (g = Array.prototype.slice.call(arguments, 1), e = C(g[0]) ? g[0] : 1, f = C(g[0]) ? C(g[1]) ? c : g[1] : g[0], g.length ? h(b, e, f ? a(f) : f) : b[0]) : b } }]), b.module("a8m.flatten", []).filter("flatten", function () { return function (a, b) { return b = b || !1, a = D(a) ? d(a) : a, E(a) ? b ? [].concat.apply([], a) : u(a, 0) : a } }), b.module("a8m.fuzzy-by", []).filter("fuzzyBy", ["$parse", function (a) { return function (b, c, e, f) { var h, i, j = f || !1; return b = D(b) ? d(b) : b, !E(b) || z(c) || z(e) ? b : (i = a(c), b.filter(function (a) { return h = i(a), !!B(h) && (h = j ? h : h.toLowerCase(), e = j ? e : e.toLowerCase(), g(h, e) !== !1) })) } }]), b.module("a8m.fuzzy", []).filter("fuzzy", function () { return function (a, b, c) { function e(a, b) { var c, d, e = Object.keys(a); return 0 < e.filter(function (e) { return c = a[e], !!d || !!B(c) && (c = f ? c : c.toLowerCase(), d = g(c, b) !== !1) }).length } var f = c || !1; return a = D(a) ? d(a) : a, !E(a) || z(b) ? a : (b = f ? b : b.toLowerCase(), a.filter(function (a) { return B(a) ? (a = f ? a : a.toLowerCase(), g(a, b) !== !1) : !!D(a) && e(a, b) })) } }), b.module("a8m.group-by", ["a8m.filter-watcher"]).filter("groupBy", ["$parse", "filterWatcher", function (a, b) { return function (c, d) { function e(a, b) { var c, d = {}; return F(a, function (a) { c = b(a), d[c] || (d[c] = []), d[c].push(a) }), d } return !D(c) || z(d) ? c : b.isMemoized("groupBy", arguments) || b.memoize("groupBy", arguments, this, e(c, a(d))) } }]), b.module("a8m.is-empty", []).filter("isEmpty", function () { return function (a) { return D(a) ? !d(a).length : !a.length } }), b.module("a8m.join", []).filter("join", function () { return function (a, b) { return z(a) || !E(a) ? a : (z(b) && (b = " "), a.join(b)) } }), b.module("a8m.last", []).filter("last", ["$parse", function (a) { return function (b) { var e, f, g, i = H(b); return i = D(i) ? d(i) : i, E(i) ? (g = Array.prototype.slice.call(arguments, 1), e = C(g[0]) ? g[0] : 1, f = C(g[0]) ? C(g[1]) ? c : g[1] : g[0], g.length ? h(i.reverse(), e, f ? a(f) : f).reverse() : i[i.length - 1]) : i } }]), b.module("a8m.map", []).filter("map", ["$parse", function (a) { return function (b, c) { return b = D(b) ? d(b) : b, !E(b) || z(c) ? b : b.map(function (b) { return a(c)(b) }) } }]), b.module("a8m.omit", []).filter("omit", ["$parse", function (a) { return function (b, c) { return b = D(b) ? d(b) : b, !E(b) || z(c) ? b : b.filter(function (b) { return !a(c)(b) }) } }]), b.module("a8m.pick", []).filter("pick", ["$parse", function (a) { return function (b, c) { return b = D(b) ? d(b) : b, !E(b) || z(c) ? b : b.filter(function (b) { return a(c)(b) }) } }]), b.module("a8m.range", []).filter("range", function () { return function (a, b, c, d, e) { c = c || 0, d = d || 1; for (var f = 0; f < parseInt(b) ; f++) { var g = c + f * d; a.push(A(e) ? e(g) : g) } return a } }), b.module("a8m.remove-with", []).filter("removeWith", function () { return function (a, b) { return z(b) ? a : (a = D(a) ? d(a) : a, a.filter(function (a) { return !f(b, a) })) } }), b.module("a8m.remove", []).filter("remove", function () { return function (a) { a = D(a) ? d(a) : a; var b = Array.prototype.slice.call(arguments, 1); return E(a) ? a.filter(function (a) { return !b.some(function (b) { return I(b, a) }) }) : a } }), b.module("a8m.reverse", []).filter("reverse", [function () { return function (a) { return a = D(a) ? d(a) : a, B(a) ? a.split("").reverse().join("") : E(a) ? a.slice().reverse() : a } }]), b.module("a8m.search-field", []).filter("searchField", ["$parse", function (a) { return function (b) { var c, e; b = D(b) ? d(b) : b; var f = Array.prototype.slice.call(arguments, 1); return E(b) && f.length ? b.map(function (b) { return e = f.map(function (d) { return (c = a(d))(b) }).join(" "), G(b, { searchField: e }) }) : b } }]), b.module("a8m.to-array", []).filter("toArray", function () { return function (a, b) { return D(a) ? b ? Object.keys(a).map(function (b) { return G(a[b], { $key: b }) }) : d(a) : a } }), b.module("a8m.unique", []).filter({ unique: ["$parse", v], uniq: ["$parse", v] }), b.module("a8m.where", []).filter("where", function () { return function (a, b) { return z(b) ? a : (a = D(a) ? d(a) : a, a.filter(function (a) { return f(b, a) })) } }), b.module("a8m.xor", []).filter("xor", ["$parse", function (a) { return function (b, c, e) { function f(b, c) { var d = a(e); return c.some(function (a) { return e ? I(d(a), d(b)) : I(a, b) }) } return e = e || !1, b = D(b) ? d(b) : b, c = D(c) ? d(c) : c, E(b) && E(c) ? b.concat(c).filter(function (a) { return !(f(a, b) && f(a, c)) }) : b } }]), b.module("a8m.math.abs", []).filter("abs", function () { return function (a) { return Math.abs(a) } }), b.module("a8m.math.byteFmt", []).filter("byteFmt", function () { var a = [{ str: "B", val: 1024 }]; return ["KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"].forEach(function (b, c) { a.push({ str: b, val: 1024 * a[c].val }) }), function (b, c) { if (C(c) && isFinite(c) && c % 1 === 0 && c >= 0 && C(b) && isFinite(b)) { for (var d = 0; d < a.length - 1 && b >= a[d].val;) d++; return b /= d > 0 ? a[d - 1].val : 1, i(b, c) + " " + a[d].str } return "NaN" } }), b.module("a8m.math.degrees", []).filter("degrees", function () { return function (a, b) { if (C(b) && isFinite(b) && b % 1 === 0 && b >= 0 && C(a) && isFinite(a)) { var c = 180 * a / Math.PI; return Math.round(c * Math.pow(10, b)) / Math.pow(10, b) } return "NaN" } }), b.module("a8m.math.kbFmt", []).filter("kbFmt", function () { var a = [{ str: "KB", val: 1024 }]; return ["MB", "GB", "TB", "PB", "EB", "ZB", "YB"].forEach(function (b, c) { a.push({ str: b, val: 1024 * a[c].val }) }), function (b, c) { if (C(c) && isFinite(c) && c % 1 === 0 && c >= 0 && C(b) && isFinite(b)) { for (var d = 0; d < a.length - 1 && b >= a[d].val;) d++; return b /= d > 0 ? a[d - 1].val : 1, i(b, c) + " " + a[d].str } return "NaN" } }), b.module("a8m.math.max", []).filter("max", ["$parse", function (a) { function b(b, c) { var d = b.map(function (b) { return a(c)(b) }); return d.indexOf(Math.max.apply(Math, d)) } return function (a, c) { return E(a) ? z(c) ? Math.max.apply(Math, a) : a[b(a, c)] : a } }]), b.module("a8m.math.min", []).filter("min", ["$parse", function (a) { function b(b, c) { var d = b.map(function (b) { return a(c)(b) }); return d.indexOf(Math.min.apply(Math, d)) } return function (a, c) { return E(a) ? z(c) ? Math.min.apply(Math, a) : a[b(a, c)] : a } }]), b.module("a8m.math.percent", []).filter("percent", function () { return function (a, b, c) { var d = B(a) ? Number(a) : a; return b = b || 100, c = c || !1, !C(d) || isNaN(d) ? a : c ? Math.round(d / b * 100) : d / b * 100 } }), b.module("a8m.math.radians", []).filter("radians", function () { return function (a, b) { if (C(b) && isFinite(b) && b % 1 === 0 && b >= 0 && C(a) && isFinite(a)) { var c = 3.14159265359 * a / 180; return Math.round(c * Math.pow(10, b)) / Math.pow(10, b) } return "NaN" } }), b.module("a8m.math.radix", []).filter("radix", function () { return function (a, b) { var c = /^[2-9]$|^[1-2]\d$|^3[0-6]$/; return C(a) && c.test(b) ? a.toString(b).toUpperCase() : a } }), b.module("a8m.math.shortFmt", []).filter("shortFmt", function () { return function (a, b) { return C(b) && isFinite(b) && b % 1 === 0 && b >= 0 && C(a) && isFinite(a) ? a < 1e3 ? "" + a : a < 1e6 ? i(a / 1e3, b) + " K" : a < 1e9 ? i(a / 1e6, b) + " M" : i(a / 1e9, b) + " B" : "NaN" } }), b.module("a8m.math.sum", []).filter("sum", function () { return function (a, b) { return E(a) ? a.reduce(function (a, b) { return a + b }, b || 0) : a } }), b.module("a8m.ends-with", []).filter("endsWith", function () { return function (a, b, c) { var d, e = c || !1; return !B(a) || z(b) ? a : (a = e ? a : a.toLowerCase(), d = a.length - b.length, a.indexOf(e ? b : b.toLowerCase(), d) !== -1) } }), b.module("a8m.latinize", []).filter("latinize", [function () { function a(a) { return a.replace(/[^\u0000-\u007E]/g, function (a) { return c[a] || a }) } for (var b = [{ base: "A", letters: "AⒶＡÀÁÂẦẤẪẨÃĀĂẰẮẴẲȦǠÄǞẢÅǺǍȀȂẠẬẶḀĄȺⱯ" }, { base: "AA", letters: "Ꜳ" }, { base: "AE", letters: "ÆǼǢ" }, { base: "AO", letters: "Ꜵ" }, { base: "AU", letters: "Ꜷ" }, { base: "AV", letters: "ꜸꜺ" }, { base: "AY", letters: "Ꜽ" }, { base: "B", letters: "BⒷＢḂḄḆɃƂƁ" }, { base: "C", letters: "CⒸＣĆĈĊČÇḈƇȻꜾ" }, { base: "D", letters: "DⒹＤḊĎḌḐḒḎĐƋƊƉꝹ" }, { base: "DZ", letters: "ǱǄ" }, { base: "Dz", letters: "ǲǅ" }, { base: "E", letters: "EⒺＥÈÉÊỀẾỄỂẼĒḔḖĔĖËẺĚȄȆẸỆȨḜĘḘḚƐƎ" }, { base: "F", letters: "FⒻＦḞƑꝻ" }, { base: "G", letters: "GⒼＧǴĜḠĞĠǦĢǤƓꞠꝽꝾ" }, { base: "H", letters: "HⒽＨĤḢḦȞḤḨḪĦⱧⱵꞍ" }, { base: "I", letters: "IⒾＩÌÍÎĨĪĬİÏḮỈǏȈȊỊĮḬƗ" }, { base: "J", letters: "JⒿＪĴɈ" }, { base: "K", letters: "KⓀＫḰǨḲĶḴƘⱩꝀꝂꝄꞢ" }, { base: "L", letters: "LⓁＬĿĹĽḶḸĻḼḺŁȽⱢⱠꝈꝆꞀ" }, { base: "LJ", letters: "Ǉ" }, { base: "Lj", letters: "ǈ" }, { base: "M", letters: "MⓂＭḾṀṂⱮƜ" }, { base: "N", letters: "NⓃＮǸŃÑṄŇṆŅṊṈȠƝꞐꞤ" }, { base: "NJ", letters: "Ǌ" }, { base: "Nj", letters: "ǋ" }, { base: "O", letters: "OⓄＯÒÓÔỒỐỖỔÕṌȬṎŌṐṒŎȮȰÖȪỎŐǑȌȎƠỜỚỠỞỢỌỘǪǬØǾƆƟꝊꝌ" }, { base: "OI", letters: "Ƣ" }, { base: "OO", letters: "Ꝏ" }, { base: "OU", letters: "Ȣ" }, { base: "OE", letters: "Œ" }, { base: "oe", letters: "œ" }, { base: "P", letters: "PⓅＰṔṖƤⱣꝐꝒꝔ" }, { base: "Q", letters: "QⓆＱꝖꝘɊ" }, { base: "R", letters: "RⓇＲŔṘŘȐȒṚṜŖṞɌⱤꝚꞦꞂ" }, { base: "S", letters: "SⓈＳẞŚṤŜṠŠṦṢṨȘŞⱾꞨꞄ" }, { base: "T", letters: "TⓉＴṪŤṬȚŢṰṮŦƬƮȾꞆ" }, { base: "TZ", letters: "Ꜩ" }, { base: "U", letters: "UⓊＵÙÚÛŨṸŪṺŬÜǛǗǕǙỦŮŰǓȔȖƯỪỨỮỬỰỤṲŲṶṴɄ" }, { base: "V", letters: "VⓋＶṼṾƲꝞɅ" }, { base: "VY", letters: "Ꝡ" }, { base: "W", letters: "WⓌＷẀẂŴẆẄẈⱲ" }, { base: "X", letters: "XⓍＸẊẌ" }, { base: "Y", letters: "YⓎＹỲÝŶỸȲẎŸỶỴƳɎỾ" }, { base: "Z", letters: "ZⓏＺŹẐŻŽẒẔƵȤⱿⱫꝢ" }, { base: "a", letters: "aⓐａẚàáâầấẫẩãāăằắẵẳȧǡäǟảåǻǎȁȃạậặḁąⱥɐ" }, { base: "aa", letters: "ꜳ" }, { base: "ae", letters: "æǽǣ" }, { base: "ao", letters: "ꜵ" }, { base: "au", letters: "ꜷ" }, { base: "av", letters: "ꜹꜻ" }, { base: "ay", letters: "ꜽ" }, { base: "b", letters: "bⓑｂḃḅḇƀƃɓ" }, { base: "c", letters: "cⓒｃćĉċčçḉƈȼꜿↄ" }, { base: "d", letters: "dⓓｄḋďḍḑḓḏđƌɖɗꝺ" }, { base: "dz", letters: "ǳǆ" }, { base: "e", letters: "eⓔｅèéêềếễểẽēḕḗĕėëẻěȅȇẹệȩḝęḙḛɇɛǝ" }, { base: "f", letters: "fⓕｆḟƒꝼ" }, { base: "g", letters: "gⓖｇǵĝḡğġǧģǥɠꞡᵹꝿ" }, { base: "h", letters: "hⓗｈĥḣḧȟḥḩḫẖħⱨⱶɥ" }, { base: "hv", letters: "ƕ" }, { base: "i", letters: "iⓘｉìíîĩīĭïḯỉǐȉȋịįḭɨı" }, { base: "j", letters: "jⓙｊĵǰɉ" }, { base: "k", letters: "kⓚｋḱǩḳķḵƙⱪꝁꝃꝅꞣ" }, { base: "l", letters: "lⓛｌŀĺľḷḹļḽḻſłƚɫⱡꝉꞁꝇ" }, { base: "lj", letters: "ǉ" }, { base: "m", letters: "mⓜｍḿṁṃɱɯ" }, { base: "n", letters: "nⓝｎǹńñṅňṇņṋṉƞɲŉꞑꞥ" }, { base: "nj", letters: "ǌ" }, { base: "o", letters: "oⓞｏòóôồốỗổõṍȭṏōṑṓŏȯȱöȫỏőǒȍȏơờớỡởợọộǫǭøǿɔꝋꝍɵ" }, { base: "oi", letters: "ƣ" }, { base: "ou", letters: "ȣ" }, { base: "oo", letters: "ꝏ" }, { base: "p", letters: "pⓟｐṕṗƥᵽꝑꝓꝕ" }, { base: "q", letters: "qⓠｑɋꝗꝙ" }, { base: "r", letters: "rⓡｒŕṙřȑȓṛṝŗṟɍɽꝛꞧꞃ" }, { base: "s", letters: "sⓢｓßśṥŝṡšṧṣṩșşȿꞩꞅẛ" }, { base: "t", letters: "tⓣｔṫẗťṭțţṱṯŧƭʈⱦꞇ" }, { base: "tz", letters: "ꜩ" }, { base: "u", letters: "uⓤｕùúûũṹūṻŭüǜǘǖǚủůűǔȕȗưừứữửựụṳųṷṵʉ" }, { base: "v", letters: "vⓥｖṽṿʋꝟʌ" }, { base: "vy", letters: "ꝡ" }, { base: "w", letters: "wⓦｗẁẃŵẇẅẘẉⱳ" }, { base: "x", letters: "xⓧｘẋẍ" }, { base: "y", letters: "yⓨｙỳýŷỹȳẏÿỷẙỵƴɏỿ" }, { base: "z", letters: "zⓩｚźẑżžẓẕƶȥɀⱬꝣ" }], c = {}, d = 0; d < b.length; d++) for (var e = b[d].letters.split(""), f = 0; f < e.length; f++) c[e[f]] = b[d].base; return function (b) { return B(b) ? a(b) : b } }]), b.module("a8m.ltrim", []).filter("ltrim", function () { return function (a, b) { var c = b || "\\s"; return B(a) ? a.replace(new RegExp("^" + c + "+"), "") : a } }), b.module("a8m.match", []).filter("match", function () { return function (a, b, c) { var d = new RegExp(b, c); return B(a) ? a.match(d) : null } }), b.module("a8m.phoneUS", []).filter("phoneUS", function () { return function (a) { return a += "", "(" + a.slice(0, 3) + ") " + a.slice(3, 6) + "-" + a.slice(6) } }), b.module("a8m.repeat", []).filter("repeat", [function () { return function (a, b, c) { var d = ~~b; return B(a) && d ? w(a, --b, c || "") : a } }]), b.module("a8m.rtrim", []).filter("rtrim", function () { return function (a, b) { var c = b || "\\s"; return B(a) ? a.replace(new RegExp(c + "+$"), "") : a } }), b.module("a8m.slugify", []).filter("slugify", [function () { return function (a, b) { var c = z(b) ? "-" : b; return B(a) ? a.toLowerCase().replace(/\s+/g, c) : a } }]), b.module("a8m.split", []).filter("split", function () { function a(a) { return a.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&") } return function (b, c, d) { var f, g, h, i; return z(b) || !B(b) ? null : (z(c) && (c = ""), isNaN(d) && (d = 0), f = new RegExp(a(c), "g"), g = b.match(f), e(g) || d >= g.length ? [b] : 0 === d ? b.split(c) : (h = b.split(c), i = h.splice(0, d + 1), h.unshift(i.join(c)), h)) } }), b.module("a8m.starts-with", []).filter("startsWith", function () { return function (a, b, c) { var d = c || !1; return !B(a) || z(b) ? a : (a = d ? a : a.toLowerCase(), !a.indexOf(d ? b : b.toLowerCase())) } }), b.module("a8m.stringular", []).filter("stringular", function () { return function (a) { var b = Array.prototype.slice.call(arguments, 1); return a.replace(/{(\d+)}/g, function (a, c) { return z(b[c]) ? a : b[c] }) } }), b.module("a8m.strip-tags", []).filter("stripTags", function () { return function (a) { return B(a) ? a.replace(/<\S[^><]*>/g, "") : a } }), b.module("a8m.test", []).filter("test", function () { return function (a, b, c) { var d = new RegExp(b, c); return B(a) ? d.test(a) : a } }), b.module("a8m.trim", []).filter("trim", function () { return function (a, b) { var c = b || "\\s"; return B(a) ? a.replace(new RegExp("^" + c + "+|" + c + "+$", "g"), "") : a } }), b.module("a8m.truncate", []).filter("truncate", function () { return function (a, b, c, d) { return b = z(b) ? a.length : b, d = d || !1, c = c || "", !B(a) || a.length <= b ? a : a.substring(0, d ? a.indexOf(" ", b) === -1 ? a.length : a.indexOf(" ", b) : b) + c } }), b.module("a8m.ucfirst", []).filter({ ucfirst: x, titleize: x }), b.module("a8m.uri-component-encode", []).filter("uriComponentEncode", ["$window", function (a) { return function (b) { return B(b) ? a.encodeURIComponent(b) : b } }]), b.module("a8m.uri-encode", []).filter("uriEncode", ["$window", function (a) { return function (b) { return B(b) ? a.encodeURI(b) : b } }]), b.module("a8m.wrap", []).filter("wrap", function () { return function (a, b, c) { return B(a) && y(b) ? [b, a, c || b].join("") : a } }), b.module("a8m.filter-watcher", []).provider("filterWatcher", function () { this.$get = ["$window", "$rootScope", function (a, b) { function c(b, c) { function d() { var b = []; return function (c, d) { if (D(d) && !e(d)) { if (~b.indexOf(d)) return "[Circular]"; b.push(d) } return a == d ? "$WINDOW" : a.document == d ? "$DOCUMENT" : k(d) ? "$SCOPE" : d } } return [b, JSON.stringify(c, d())].join("#").replace(/"/g, "") } function d(a) { var b = a.targetScope.$id; F(l[b], function (a) { delete j[a] }), delete l[b] } function f() { m(function () { b.$$phase || (j = {}) }, 2e3) } function g(a, b) { var c = a.$id; return z(l[c]) && (a.$on("$destroy", d), l[c] = []), l[c].push(b) } function h(a, b) { var d = c(a, b); return j[d] } function i(a, b, d, e) { var h = c(a, b); return j[h] = e, k(d) ? g(d, h) : f(), e } var j = {}, l = {}, m = a.setTimeout; return { isMemoized: h, memoize: i } }] }), b.module("angular.filter", ["a8m.ucfirst", "a8m.uri-encode", "a8m.uri-component-encode", "a8m.slugify", "a8m.latinize", "a8m.strip-tags", "a8m.stringular", "a8m.truncate", "a8m.starts-with", "a8m.ends-with", "a8m.wrap", "a8m.trim", "a8m.ltrim", "a8m.rtrim", "a8m.repeat", "a8m.test", "a8m.match", "a8m.split", "a8m.phoneUS", "a8m.to-array", "a8m.concat", "a8m.contains", "a8m.unique", "a8m.is-empty", "a8m.after", "a8m.after-where", "a8m.before", "a8m.before-where", "a8m.defaults", "a8m.where", "a8m.reverse", "a8m.remove", "a8m.remove-with", "a8m.group-by", "a8m.count-by", "a8m.chunk-by", "a8m.search-field", "a8m.fuzzy-by", "a8m.fuzzy", "a8m.omit", "a8m.pick", "a8m.every", "a8m.filter-by", "a8m.xor", "a8m.map", "a8m.first", "a8m.last", "a8m.flatten", "a8m.join", "a8m.range", "a8m.math.max", "a8m.math.min", "a8m.math.abs", "a8m.math.percent", "a8m.math.radix", "a8m.math.sum", "a8m.math.degrees", "a8m.math.radians", "a8m.math.byteFmt", "a8m.math.kbFmt", "a8m.math.shortFmt", "a8m.angular", "a8m.conditions", "a8m.is-null", "a8m.filter-watcher"]) }(window, window.angular);
/**
 * @ngdoc directive
 * @name fitVids.directive:fitVids
 * @restrict A
 *
 * @version  0.1.0
 *
 * @description
 * Angular direYctive port of FitVids (http://fitvidsjs.com/).
 */

angular.module('fitVids', []).directive('fitVids', [function() {
    'use strict';

    if (!document.getElementById('fit-vids-style')) {
        var div = document.createElement('div');
        var ref = document.getElementsByTagName('base')[0] || document.getElementsByTagName('script')[0];
        var cssStyles = '&shy;<style>.fluid-width-video-wrapper{width:100%;position:relative;padding:0;}.fluid-width-video-wrapper iframe,.fluid-width-video-wrapper object,.fluid-width-video-wrapper embed {position:absolute;top:0;left:0;width:100%;height:100%;}</style>';
        div.className = 'fit-vids-style';
        div.id = 'fit-vids-style';
        div.style.display = 'none';
        div.innerHTML = cssStyles;
        ref.parentNode.insertBefore(div, ref);
    }

    return {
        restrict: 'A',
        link: function (scope, element, attr) {

            var selectors = [
                "iframe[src*='player.vimeo.com']",
                "iframe[src*='youtube.com']",
                "iframe[src*='youtube-nocookie.com']",
                "iframe[src*='kickstarter.com'][src*='video.html']",
                "object",
                "embed"
            ];

            var videos;

            if (attr.customSelector) {
                selectors.push(attr.customSelector);
            }

            videos = element[0].querySelectorAll(selectors.join(','));

            angular.forEach(videos, function (item) {

                var $item = angular.element(item);
                var height, width, aspectRatio;

                if (item.tagName.toLowerCase() === 'embed' &&
                        ($item.parent().tagName === 'object' && $item.parent().length) ||
                        $item.parent().hasClass('.fluid-width-video-wrapper')) {
                    return;
                }

                height = (item.tagName.toLowerCase() === 'object' || $item.attr('height')) ? parseInt($item.attr('height'), 10) : $item.height();
                width = !isNaN(parseInt($item.attr('width'), 10)) ? parseInt($item.attr('width'), 10) : $item.width();
                aspectRatio = height / width;

                if (!$item.attr('id')) {
                    var videoID = 'fitvid' + Math.floor(Math.random()*999999);
                    $item.attr('id', videoID);
                }

                $item.wrap('<div class="fluid-width-video-wrapper" />').parent().css('padding-top', (aspectRatio * 100) + "%");
                $item.removeAttr('height').removeAttr('width');

            });

        }
    };

}]);

// # Angular-Inview
// - Author: [Nicola Peduzzi](https://github.com/thenikso)
// - Repository: https://github.com/thenikso/angular-inview
// - Install with: `npm install angular-inview`
// - Version: **3.0.0**
(function () {
    'use strict';

    // An [angular.js](https://angularjs.org) directive to evaluate an expression if
    // a DOM element is or not in the current visible browser viewport.
    // Use it in your AngularJS app by including the javascript and requireing it:
    //
    // `angular.module('myApp', ['angular-inview'])`
    var moduleName = 'angular-inview';
    angular.module(moduleName, [])

        // ## in-view directive
        //
        // ### Usage
        // ```html
        // <any in-view="{expression}" [in-view-options="{object}"]></any>
        // ```
        .directive('inView', ['$parse', inViewDirective])

        // ## in-view-container directive
        .directive('inViewContainer', inViewContainerDirective);

    // ## Implementation
    function inViewDirective($parse) {
        return {
            // Evaluate the expression passed to the attribute `in-view` when the DOM
            // element is visible in the viewport.
            restrict: 'A',
            require: '?^^inViewContainer',
            link: function inViewDirectiveLink(scope, element, attrs, container) {
                // in-view-options attribute can be specified with an object expression
                // containing:
                //   - `offset`: An array of values to offset the element position.
                //     Offsets are expressed as arrays of 4 numbers [top, right, bottom, left].
                //     Like CSS, you can also specify only 2 numbers [top/bottom, left/right].
                //     Instead of numbers, some array elements can be a string with a percentage.
                //     Positive numbers are offsets outside the element rectangle and
                //     negative numbers are offsets to the inside.
                //   - `viewportOffset`: Like the element offset but appied to the viewport.
                //   - `generateDirection`: Indicate if the `direction` information should
                //     be included in `$inviewInfo` (default false).
                //   - `generateParts`: Indicate if the `parts` information should
                //     be included in `$inviewInfo` (default false).
                //   - `throttle`: Specify a number of milliseconds by which to limit the
                //     number of incoming events.
                var options = {};
                if (attrs.inViewOptions) {
                    options = scope.$eval(attrs.inViewOptions);
                }
                if (options.offset) {
                    options.offset = normalizeOffset(options.offset);
                }
                if (options.viewportOffset) {
                    options.viewportOffset = normalizeOffset(options.viewportOffset);
                }

                // Build reactive chain from an initial event
                var viewportEventSignal = signalSingle({ type: 'initial' })

                    // Merged with the window events
                    .merge(signalFromEvent(window, 'checkInView click ready wheel mousewheel DomMouseScroll MozMousePixelScroll resize scroll touchmove mouseup keydown'))

                // Merge with container's events signal
                if (container) {
                    viewportEventSignal = viewportEventSignal.merge(container.eventsSignal);
                }

                // Throttle if option specified
                if (options.throttle) {
                    viewportEventSignal = viewportEventSignal.throttle(options.throttle);
                }

                // Map to viewport intersection and in-view informations
                var inviewInfoSignal = viewportEventSignal

                    // Inview information structure contains:
                    //   - `inView`: a boolean value indicating if the element is
                    //     visible in the viewport;
                    //   - `changed`: a boolean value indicating if the inview status
                    //     changed after the last event;
                    //   - `event`: the event that initiated the in-view check;
                    .map(function (event) {
                        var viewportRect;
                        if (container) {
                            viewportRect = container.getViewportRect();
                            // TODO merge with actual window!
                        } else {
                            viewportRect = getViewportRect();
                        }
                        viewportRect = offsetRect(viewportRect, options.viewportOffset);
                        var elementRect = offsetRect(element[0].getBoundingClientRect(), options.offset);
                        var isVisible = !!(element[0].offsetWidth || element[0].offsetHeight || element[0].getClientRects().length);
                        var info = {
                            inView: isVisible && intersectRect(elementRect, viewportRect),
                            event: event,
                            element: element,
                            elementRect: elementRect,
                            viewportRect: viewportRect
                        };
                        // Add inview parts
                        if (options.generateParts && info.inView) {
                            info.parts = {};
                            info.parts.top = elementRect.top >= viewportRect.top;
                            info.parts.left = elementRect.left >= viewportRect.left;
                            info.parts.bottom = elementRect.bottom <= viewportRect.bottom;
                            info.parts.right = elementRect.right <= viewportRect.right;
                        }
                        return info;
                    })

                    // Add the changed information to the inview structure.
                    .scan({}, function (lastInfo, newInfo) {
                        // Add inview direction info
                        if (options.generateDirection && newInfo.inView && lastInfo.elementRect) {
                            newInfo.direction = {
                                horizontal: newInfo.elementRect.left - lastInfo.elementRect.left,
                                vertical: newInfo.elementRect.top - lastInfo.elementRect.top
                            };
                        }
                        // Calculate changed flag
                        newInfo.changed =
                            newInfo.inView !== lastInfo.inView ||
                            !angular.equals(newInfo.parts, lastInfo.parts) ||
                            !angular.equals(newInfo.direction, lastInfo.direction);
                        return newInfo;
                    })

                    // Filters only informations that should be forwarded to the callback
                    .filter(function (info) {
                        // Don't forward if no relevant infomation changed
                        if (!info.changed) {
                            return false;
                        }
                        // Don't forward if not initially in-view
                        if (info.event.type === 'initial' && !info.inView) {
                            return false;
                        }
                        return true;
                    });

                // Execute in-view callback
                var inViewExpression = $parse(attrs.inView);
                var dispose = inviewInfoSignal.subscribe(function (info) {
                    scope.$applyAsync(function () {
                        inViewExpression(scope, {
                            '$inview': info.inView,
                            '$inviewInfo': info
                        });
                    });
                });

                // Dispose of reactive chain
                scope.$on('$destroy', dispose);
            }
        }
    }

    function inViewContainerDirective() {
        return {
            restrict: 'A',
            controller: ['$element', function ($element) {
                this.element = $element;
                this.eventsSignal = signalFromEvent($element, 'scroll');
                this.getViewportRect = function () {
                    return $element[0].getBoundingClientRect();
                };
            }]
        }
    }

    // ## Utilities

    function getViewportRect() {
        var result = {
            top: 0,
            left: 0,
            width: window.innerWidth,
            right: window.innerWidth,
            height: window.innerHeight,
            bottom: window.innerHeight
        };
        if (result.height) {
            return result;
        }
        var mode = document.compatMode;
        if (mode === 'CSS1Compat') {
            result.width = result.right = document.documentElement.clientWidth;
            result.height = result.bottom = document.documentElement.clientHeight;
        } else {
            result.width = result.right = document.body.clientWidth;
            result.height = result.bottom = document.body.clientHeight;
        }
        return result;
    }

    function intersectRect(r1, r2) {
        return !(r2.left > r1.right ||
            r2.right < r1.left ||
            r2.top > r1.bottom ||
            r2.bottom < r1.top);
    }

    function normalizeOffset(offset) {
        if (!angular.isArray(offset)) {
            return [offset, offset, offset, offset];
        }
        if (offset.length == 2) {
            return offset.concat(offset);
        }
        else if (offset.length == 3) {
            return offset.concat([offset[1]]);
        }
        return offset;
    }

    function offsetRect(rect, offset) {
        if (!offset) {
            return rect;
        }
        var offsetObject = {
            top: isPercent(offset[0]) ? (parseFloat(offset[0]) * rect.height / 100) : offset[0],
            right: isPercent(offset[1]) ? (parseFloat(offset[1]) * rect.width / 100) : offset[1],
            bottom: isPercent(offset[2]) ? (parseFloat(offset[2]) * rect.height / 100) : offset[2],
            left: isPercent(offset[3]) ? (parseFloat(offset[3]) * rect.width / 100) : offset[3]
        };
        // Note: ClientRect object does not allow its properties to be written to therefore a new object has to be created.
        return {
            top: rect.top - offsetObject.top,
            left: rect.left - offsetObject.left,
            bottom: rect.bottom + offsetObject.bottom,
            right: rect.right + offsetObject.right,
            height: rect.height + offsetObject.top + offsetObject.bottom,
            width: rect.width + offsetObject.left + offsetObject.right
        };
    }

    function isPercent(n) {
        return angular.isString(n) && n.indexOf('%') > 0;
    }

    // ## QuickSignal FRP
    // A quick and dirty implementation of Rx to have a streamlined code in the
    // directives.

    // ### QuickSignal
    //
    // - `didSubscribeFunc`: a function receiving a `subscriber` as described below
    //
    // Usage:
    //     var mySignal = new QuickSignal(function(subscriber) { ... })
    function QuickSignal(didSubscribeFunc) {
        this.didSubscribeFunc = didSubscribeFunc;
    }

    // Subscribe to a signal and consume the steam of data.
    //
    // Returns a function that can be called to stop the signal stream of data and
    // perform cleanup.
    //
    // A `subscriber` is a function that will be called when a new value arrives.
    // a `subscriber.$dispose` property can be set to a function to be called uppon
    // disposal. When setting the `$dispose` function, the previously set function
    // should be chained.
    QuickSignal.prototype.subscribe = function (subscriber) {
        this.didSubscribeFunc(subscriber);
        var dispose = function () {
            if (subscriber.$dispose) {
                subscriber.$dispose();
                subscriber.$dispose = null;
            }
        }
        return dispose;
    }

    QuickSignal.prototype.map = function (f) {
        var s = this;
        return new QuickSignal(function (subscriber) {
            subscriber.$dispose = s.subscribe(function (nextValue) {
                subscriber(f(nextValue));
            });
        });
    };

    QuickSignal.prototype.filter = function (f) {
        var s = this;
        return new QuickSignal(function (subscriber) {
            subscriber.$dispose = s.subscribe(function (nextValue) {
                if (f(nextValue)) {
                    subscriber(nextValue);
                }
            });
        });
    };

    QuickSignal.prototype.scan = function (initial, scanFunc) {
        var s = this;
        return new QuickSignal(function (subscriber) {
            var last = initial;
            subscriber.$dispose = s.subscribe(function (nextValue) {
                last = scanFunc(last, nextValue);
                subscriber(last);
            });
        });
    }

    QuickSignal.prototype.merge = function (signal) {
        return signalMerge(this, signal);
    };

    QuickSignal.prototype.throttle = function (threshhold) {
        var s = this, last, deferTimer;
        return new QuickSignal(function (subscriber) {
            var chainDisposable = s.subscribe(function () {
                var now = +new Date,
                    args = arguments;
                if (last && now < last + threshhold) {
                    clearTimeout(deferTimer);
                    deferTimer = setTimeout(function () {
                        last = now;
                        subscriber.apply(null, args);
                    }, threshhold);
                } else {
                    last = now;
                    subscriber.apply(null, args);
                }
            });
            subscriber.$dispose = function () {
                clearTimeout(deferTimer);
                if (chainDisposable) chainDisposable();
            };
        });
    };

    function signalMerge() {
        var signals = arguments;
        return new QuickSignal(function (subscriber) {
            var disposables = [];
            for (var i = signals.length - 1; i >= 0; i--) {
                disposables.push(signals[i].subscribe(function () {
                    subscriber.apply(null, arguments);
                }));
            }
            subscriber.$dispose = function () {
                for (var i = disposables.length - 1; i >= 0; i--) {
                    if (disposables[i]) disposables[i]();
                }
            }
        });
    }

    // Returns a signal from DOM events of a target.
    function signalFromEvent(target, event) {
        return new QuickSignal(function (subscriber) {
            var handler = function (e) {
                subscriber(e);
            };
            var el = angular.element(target);
            event.split(' ').map(function (e) {
                el[0].addEventListener(e, handler, true);
            });
            subscriber.$dispose = function () {
                event.split(' ').map(function (e) {
                    el[0].removeEventListener(e, handler, true);
                });
            };
        });
    }

    function signalSingle(value) {
        return new QuickSignal(function (subscriber) {
            setTimeout(function () { subscriber(value); });
        });
    }

    // Module loaders exports
    if (typeof define === 'function' && define.amd) {
        define(['angular'], moduleName);
    } else if (typeof module !== 'undefined' && module && module.exports) {
        module.exports = moduleName;
    }
})();
/*
 * angular-loading-bar
 *
 * intercepts XHR requests and creates a loading bar.
 * Based on the excellent nprogress work by rstacruz (more info in readme)
 *
 * (c) 2013 Wes Cruver
 * License: MIT
 */


(function() {

'use strict';

// Alias the loading bar for various backwards compatibilities since the project has matured:
angular.module('angular-loading-bar', ['cfp.loadingBarInterceptor']);
angular.module('chieffancypants.loadingBar', ['cfp.loadingBarInterceptor']);


/**
 * loadingBarInterceptor service
 *
 * Registers itself as an Angular interceptor and listens for XHR requests.
 */
angular.module('cfp.loadingBarInterceptor', ['cfp.loadingBar'])
  .config(['$httpProvider', function ($httpProvider) {

    var interceptor = ['$q', '$cacheFactory', '$timeout', '$rootScope', 'cfpLoadingBar', function ($q, $cacheFactory, $timeout, $rootScope, cfpLoadingBar) {

      /**
       * The total number of requests made
       */
      var reqsTotal = 0;

      /**
       * The number of requests completed (either successfully or not)
       */
      var reqsCompleted = 0;

      /**
       * The amount of time spent fetching before showing the loading bar
       */
      var latencyThreshold = cfpLoadingBar.latencyThreshold;

      /**
       * $timeout handle for latencyThreshold
       */
      var startTimeout;


      /**
       * calls cfpLoadingBar.complete() which removes the
       * loading bar from the DOM.
       */
      function setComplete() {
        $timeout.cancel(startTimeout);
        cfpLoadingBar.complete();
        reqsCompleted = 0;
        reqsTotal = 0;
      }

      /**
       * Determine if the response has already been cached
       * @param  {Object}  config the config option from the request
       * @return {Boolean} retrns true if cached, otherwise false
       */
      function isCached(config) {
        var cache;
        var defaultCache = $cacheFactory.get('$http');
        var defaults = $httpProvider.defaults;

        // Choose the proper cache source. Borrowed from angular: $http service
        if ((config.cache || defaults.cache) && config.cache !== false &&
          (config.method === 'GET' || config.method === 'JSONP')) {
            cache = angular.isObject(config.cache) ? config.cache
              : angular.isObject(defaults.cache) ? defaults.cache
              : defaultCache;
        }

        var cached = cache !== undefined ?
          cache.get(config.url) !== undefined : false;

        if (config.cached !== undefined && cached !== config.cached) {
          return config.cached;
        }
        config.cached = cached;
        return cached;
      }


      return {
        'request': function(config) {
          // Check to make sure this request hasn't already been cached and that
          // the requester didn't explicitly ask us to ignore this request:
          if (!config.ignoreLoadingBar && !isCached(config)) {
            $rootScope.$broadcast('cfpLoadingBar:loading', {url: config.url});
            if (reqsTotal === 0) {
              startTimeout = $timeout(function() {
                cfpLoadingBar.start();
              }, latencyThreshold);
            }
            reqsTotal++;
            cfpLoadingBar.set(reqsCompleted / reqsTotal);
          }
          return config;
        },

        'response': function(response) {
          if (!response.config.ignoreLoadingBar && !isCached(response.config)) {
            reqsCompleted++;
            $rootScope.$broadcast('cfpLoadingBar:loaded', {url: response.config.url, result: response});
            if (reqsCompleted >= reqsTotal) {
              setComplete();
            } else {
              cfpLoadingBar.set(reqsCompleted / reqsTotal);
            }
          }
          return response;
        },

        'responseError': function(rejection) {
          if (!rejection.config.ignoreLoadingBar && !isCached(rejection.config)) {
            reqsCompleted++;
            $rootScope.$broadcast('cfpLoadingBar:loaded', {url: rejection.config.url, result: rejection});
            if (reqsCompleted >= reqsTotal) {
              setComplete();
            } else {
              cfpLoadingBar.set(reqsCompleted / reqsTotal);
            }
          }
          return $q.reject(rejection);
        }
      };
    }];

    $httpProvider.interceptors.push(interceptor);
  }]);


/**
 * Loading Bar
 *
 * This service handles adding and removing the actual element in the DOM.
 * Generally, best practices for DOM manipulation is to take place in a
 * directive, but because the element itself is injected in the DOM only upon
 * XHR requests, and it's likely needed on every view, the best option is to
 * use a service.
 */
angular.module('cfp.loadingBar', [])
  .provider('cfpLoadingBar', function() {

    this.includeSpinner = true;
    this.includeBar = true;
    this.latencyThreshold = 100;
    this.startSize = 0.02;
    this.parentSelector = 'body';
    this.spinnerTemplate = '<div id="loading-bar-spinner"><div class="spinner-icon"></div></div>';
    this.loadingBarTemplate = '<div id="loading-bar"><div class="bar"><div class="peg"></div></div></div>';

    this.$get = ['$injector', '$document', '$timeout', '$rootScope', function ($injector, $document, $timeout, $rootScope) {
      var $animate;
      var $parentSelector = this.parentSelector,
        loadingBarContainer = angular.element(this.loadingBarTemplate),
        loadingBar = loadingBarContainer.find('div').eq(0),
        spinner = angular.element(this.spinnerTemplate);

      var incTimeout,
        completeTimeout,
        started = false,
        status = 0;

      var includeSpinner = this.includeSpinner;
      var includeBar = this.includeBar;
      var startSize = this.startSize;

      /**
       * Inserts the loading bar element into the dom, and sets it to 2%
       */
      function _start() {
        if (!$animate) {
          $animate = $injector.get('$animate');
        }

        var $parent = $document.find($parentSelector).eq(0);
        $timeout.cancel(completeTimeout);

        // do not continually broadcast the started event:
        if (started) {
          return;
        }

        $rootScope.$broadcast('cfpLoadingBar:started');
        started = true;

        if (includeBar) {
          $animate.enter(loadingBarContainer, $parent);
        }

        if (includeSpinner) {
          $animate.enter(spinner, $parent);
        }

        _set(startSize);
      }

      /**
       * Set the loading bar's width to a certain percent.
       *
       * @param n any value between 0 and 1
       */
      function _set(n) {
        if (!started) {
          return;
        }
        var pct = (n * 100) + '%';
        loadingBar.css('width', pct);
        status = n;

        // increment loadingbar to give the illusion that there is always
        // progress but make sure to cancel the previous timeouts so we don't
        // have multiple incs running at the same time.
        $timeout.cancel(incTimeout);
        incTimeout = $timeout(function() {
          _inc();
        }, 250);
      }

      /**
       * Increments the loading bar by a random amount
       * but slows down as it progresses
       */
      function _inc() {
        if (_status() >= 1) {
          return;
        }

        var rnd = 0;

        // TODO: do this mathmatically instead of through conditions

        var stat = _status();
        if (stat >= 0 && stat < 0.25) {
          // Start out between 3 - 6% increments
          rnd = (Math.random() * (5 - 3 + 1) + 3) / 100;
        } else if (stat >= 0.25 && stat < 0.65) {
          // increment between 0 - 3%
          rnd = (Math.random() * 3) / 100;
        } else if (stat >= 0.65 && stat < 0.9) {
          // increment between 0 - 2%
          rnd = (Math.random() * 2) / 100;
        } else if (stat >= 0.9 && stat < 0.99) {
          // finally, increment it .5 %
          rnd = 0.005;
        } else {
          // after 99%, don't increment:
          rnd = 0;
        }

        var pct = _status() + rnd;
        _set(pct);
      }

      function _status() {
        return status;
      }

      function _completeAnimation() {
        status = 0;
        started = false;
      }

      function _complete() {
        if (!$animate) {
          $animate = $injector.get('$animate');
        }

        $rootScope.$broadcast('cfpLoadingBar:completed');
        _set(1);

        $timeout.cancel(completeTimeout);

        // Attempt to aggregate any start/complete calls within 500ms:
        completeTimeout = $timeout(function() {
          var promise = $animate.leave(loadingBarContainer, _completeAnimation);
          if (promise && promise.then) {
            promise.then(_completeAnimation);
          }
          $animate.leave(spinner);
        }, 500);
      }

      return {
        start            : _start,
        set              : _set,
        status           : _status,
        inc              : _inc,
        complete         : _complete,
        includeSpinner   : this.includeSpinner,
        latencyThreshold : this.latencyThreshold,
        parentSelector   : this.parentSelector,
        startSize        : this.startSize
      };


    }];     //
  });       // wtf javascript. srsly
})();       //

/**
 * An Angular module that gives you access to the browsers local storage
 * @version v0.7.1 - 2017-06-21
 * @link https://github.com/grevory/angular-local-storage
 * @author grevory <greg@gregpike.ca>
 * @license MIT License, http://www.opensource.org/licenses/MIT
 */
!function (a, b) { var c = b.isDefined, d = b.isUndefined, e = b.isNumber, f = b.isObject, g = b.isArray, h = b.isString, i = b.extend, j = b.toJson; b.module("LocalStorageModule", []).provider("localStorageService", function () { this.prefix = "ls", this.storageType = "localStorage", this.cookie = { expiry: 30, path: "/", secure: !1 }, this.defaultToCookie = !0, this.notify = { setItem: !0, removeItem: !1 }, this.setPrefix = function (a) { return this.prefix = a, this }, this.setStorageType = function (a) { return this.storageType = a, this }, this.setDefaultToCookie = function (a) { return this.defaultToCookie = !!a, this }, this.setStorageCookie = function (a, b, c) { return this.cookie.expiry = a, this.cookie.path = b, this.cookie.secure = c, this }, this.setStorageCookieDomain = function (a) { return this.cookie.domain = a, this }, this.setNotify = function (a, b) { return this.notify = { setItem: a, removeItem: b }, this }, this.$get = ["$rootScope", "$window", "$document", "$parse", "$timeout", function (a, b, k, l, m) { function n(c) { if (c || (c = b.event), s.setItem && h(c.key) && w(c.key)) { var d = v(c.key); m(function () { a.$broadcast("LocalStorageModule.notification.changed", { key: d, newvalue: c.newValue, storageType: p.storageType }) }) } } var o, p = this, q = p.prefix, r = p.cookie, s = p.notify, t = p.storageType; k ? k[0] && (k = k[0]) : k = document, "." !== q.substr(-1) && (q = q ? q + "." : ""); var u = function (a) { return q + a }, v = function (a) { return a.replace(new RegExp("^" + q, "g"), "") }, w = function (a) { return 0 === a.indexOf(q) }, x = function () { try { var c = t in b && null !== b[t], d = u("__" + Math.round(1e7 * Math.random())); return c && (o = b[t], o.setItem(d, ""), o.removeItem(d)), c } catch (b) { return p.defaultToCookie && (t = "cookie"), a.$broadcast("LocalStorageModule.notification.error", b.message), !1 } }, y = x(), z = function (b, c, e) { var f = J(); try { if (K(e), c = d(c) ? null : j(c), !y && p.defaultToCookie || "cookie" === p.storageType) return y || a.$broadcast("LocalStorageModule.notification.warning", "LOCAL_STORAGE_NOT_SUPPORTED"), s.setItem && a.$broadcast("LocalStorageModule.notification.setitem", { key: b, newvalue: c, storageType: "cookie" }), F(b, c); try { o && o.setItem(u(b), c), s.setItem && a.$broadcast("LocalStorageModule.notification.setitem", { key: b, newvalue: c, storageType: p.storageType }) } catch (d) { return a.$broadcast("LocalStorageModule.notification.error", d.message), F(b, c) } return !0 } finally { K(f) } }, A = function (b, c) { var d = J(); try { if (K(c), !y && p.defaultToCookie || "cookie" === p.storageType) return y || a.$broadcast("LocalStorageModule.notification.warning", "LOCAL_STORAGE_NOT_SUPPORTED"), G(b); var e = o ? o.getItem(u(b)) : null; if (!e || "null" === e) return null; try { return JSON.parse(e) } catch (a) { return e } } finally { K(d) } }, B = function () { var b = J(); try { var c = 0; arguments.length >= 1 && ("localStorage" === arguments[arguments.length - 1] || "sessionStorage" === arguments[arguments.length - 1]) && (c = 1, K(arguments[arguments.length - 1])); var d, e; for (d = 0; d < arguments.length - c; d++)if (e = arguments[d], !y && p.defaultToCookie || "cookie" === p.storageType) y || a.$broadcast("LocalStorageModule.notification.warning", "LOCAL_STORAGE_NOT_SUPPORTED"), s.removeItem && a.$broadcast("LocalStorageModule.notification.removeitem", { key: e, storageType: "cookie" }), H(e); else try { o.removeItem(u(e)), s.removeItem && a.$broadcast("LocalStorageModule.notification.removeitem", { key: e, storageType: p.storageType }) } catch (b) { a.$broadcast("LocalStorageModule.notification.error", b.message), H(e) } } finally { K(b) } }, C = function (b) { var c = J(); try { if (K(b), !y) return a.$broadcast("LocalStorageModule.notification.warning", "LOCAL_STORAGE_NOT_SUPPORTED"), []; var d = q.length, e = []; for (var f in o) if (f.substr(0, d) === q) try { e.push(f.substr(d)) } catch (b) { return a.$broadcast("LocalStorageModule.notification.error", b.Description), [] } return e } finally { K(c) } }, D = function (b, c) { var d = J(); try { K(c); var e = q ? new RegExp("^" + q) : new RegExp, f = b ? new RegExp(b) : new RegExp; if (!y && p.defaultToCookie || "cookie" === p.storageType) return y || a.$broadcast("LocalStorageModule.notification.warning", "LOCAL_STORAGE_NOT_SUPPORTED"), I(); if (!y && !p.defaultToCookie) return !1; var g = q.length; for (var h in o) if (e.test(h) && f.test(h.substr(g))) try { B(h.substr(g)) } catch (b) { return a.$broadcast("LocalStorageModule.notification.error", b.message), I() } return !0 } finally { K(d) } }, E = function () { try { return b.navigator.cookieEnabled || "cookie" in k && (k.cookie.length > 0 || (k.cookie = "test").indexOf.call(k.cookie, "test") > -1) } catch (b) { return a.$broadcast("LocalStorageModule.notification.error", b.message), !1 } }(), F = function (b, c, h, i) { if (d(c)) return !1; if ((g(c) || f(c)) && (c = j(c)), !E) return a.$broadcast("LocalStorageModule.notification.error", "COOKIES_NOT_SUPPORTED"), !1; try { var l = "", m = new Date, n = ""; if (null === c ? (m.setTime(m.getTime() + -864e5), l = "; expires=" + m.toGMTString(), c = "") : e(h) && 0 !== h ? (m.setTime(m.getTime() + 24 * h * 60 * 60 * 1e3), l = "; expires=" + m.toGMTString()) : 0 !== r.expiry && (m.setTime(m.getTime() + 24 * r.expiry * 60 * 60 * 1e3), l = "; expires=" + m.toGMTString()), b) { var o = "; path=" + r.path; r.domain && (n = "; domain=" + r.domain), "boolean" == typeof i ? i === !0 && (n += "; secure") : r.secure === !0 && (n += "; secure"), k.cookie = u(b) + "=" + encodeURIComponent(c) + l + o + n } } catch (b) { return a.$broadcast("LocalStorageModule.notification.error", b.message), !1 } return !0 }, G = function (b) { if (!E) return a.$broadcast("LocalStorageModule.notification.error", "COOKIES_NOT_SUPPORTED"), !1; for (var c = k.cookie && k.cookie.split(";") || [], d = 0; d < c.length; d++) { for (var e = c[d]; " " === e.charAt(0);)e = e.substring(1, e.length); if (0 === e.indexOf(u(b) + "=")) { var f = decodeURIComponent(e.substring(q.length + b.length + 1, e.length)); try { var g = JSON.parse(f); return "number" == typeof g ? f : g } catch (a) { return f } } } return null }, H = function (a) { F(a, null) }, I = function () { for (var a = null, b = q.length, c = k.cookie.split(";"), d = 0; d < c.length; d++) { for (a = c[d]; " " === a.charAt(0);)a = a.substring(1, a.length); var e = a.substring(b, a.indexOf("=")); H(e) } }, J = function () { return t }, K = function (a) { return a && t !== a && (t = a, y = x()), y }, L = function (a, b, d, e, g) { e = e || b; var h = A(e, g); return null === h && c(d) ? h = d : f(h) && f(d) && (h = i(h, d)), l(b).assign(a, h), a.$watch(b, function (a) { z(e, a, g) }, f(a[b])) }; y && (b.addEventListener ? (b.addEventListener("storage", n, !1), a.$on("$destroy", function () { b.removeEventListener("storage", n) })) : b.attachEvent && (b.attachEvent("onstorage", n), a.$on("$destroy", function () { b.detachEvent("onstorage", n) }))); var M = function (a) { var c = J(); try { K(a); for (var d = 0, e = b[t], f = 0; f < e.length; f++)0 === e.key(f).indexOf(q) && d++; return d } finally { K(c) } }, N = function (a) { q = a }; return { isSupported: y, getStorageType: J, setStorageType: K, setPrefix: N, set: z, add: z, get: A, keys: C, remove: B, clearAll: D, bind: L, deriveKey: u, underiveKey: v, length: M, defaultToCookie: this.defaultToCookie, cookie: { isSupported: E, set: F, add: F, get: G, remove: H, clearAll: I } } }] }) }(window, window.angular);
//# sourceMappingURL=angular-local-storage.min.js.map
/* angular-moment.js / v1.3.0 / (c) 2013, 2014, 2015, 2016, 2017, 2018 Uri Shaked / MIT Licence */

'format amd';
/* global define */

(function () {
	'use strict';

	function isUndefinedOrNull(val) {
		return angular.isUndefined(val) || val === null;
	}

	function requireMoment() {
		try {
			return require('moment'); // Using nw.js or browserify?
		} catch (e) {
			throw new Error('Please install moment via npm. Please reference to: https://github.com/urish/angular-moment'); // Add wiki/troubleshooting section?
		}
	}

	function angularMoment(angular, moment) {

		if(typeof moment === 'undefined') {
			if(typeof require === 'function') {
				moment = requireMoment();
			}else{
				throw new Error('Moment cannot be found by angular-moment! Please reference to: https://github.com/urish/angular-moment'); // Add wiki/troubleshooting section?
			}
		}

		/**
		 * @ngdoc overview
		 * @name angularMoment
		 *
		 * @description
		 * angularMoment module provides moment.js functionality for angular.js apps.
		 */
		angular.module('angularMoment', [])

		/**
		 * @ngdoc object
		 * @name angularMoment.config:angularMomentConfig
		 *
		 * @description
		 * Common configuration of the angularMoment module
		 */
			.constant('angularMomentConfig', {
				/**
				 * @ngdoc property
				 * @name angularMoment.config.angularMomentConfig#preprocess
				 * @propertyOf angularMoment.config:angularMomentConfig
				 * @returns {function} A preprocessor function that will be applied on all incoming dates
				 *
				 * @description
				 * Defines a preprocessor function to apply on all input dates (e.g. the input of `am-time-ago`,
				 * `amCalendar`, etc.). The function must return a `moment` object.
				 *
				 * @example
				 *   // Causes angular-moment to always treat the input values as unix timestamps
				 *   angularMomentConfig.preprocess = function(value) {
				 * 	   return moment.unix(value);
				 *   }
				 */
				preprocess: null,

				/**
				 * @ngdoc property
				 * @name angularMoment.config.angularMomentConfig#timezone
				 * @propertyOf angularMoment.config:angularMomentConfig
				 * @returns {string} The default timezone
				 *
				 * @description
				 * The default timezone (e.g. 'Europe/London'). Empty string by default (does not apply
				 * any timezone shift).
				 *
				 * NOTE: This option requires moment-timezone >= 0.3.0.
				 */
				timezone: null,

				/**
				 * @ngdoc property
				 * @name angularMoment.config.angularMomentConfig#format
				 * @propertyOf angularMoment.config:angularMomentConfig
				 * @returns {string} The pre-conversion format of the date
				 *
				 * @description
				 * Specify the format of the input date. Essentially it's a
				 * default and saves you from specifying a format in every
				 * element. Overridden by element attr. Null by default.
				 */
				format: null,

				/**
				 * @ngdoc property
				 * @name angularMoment.config.angularMomentConfig#statefulFilters
				 * @propertyOf angularMoment.config:angularMomentConfig
				 * @returns {boolean} Whether angular-moment filters should be stateless (or not)
				 *
				 * @description
				 * Specifies whether the filters included with angular-moment are stateful.
				 * Stateful filters will automatically re-evaluate whenever you change the timezone
				 * or locale settings, but may negatively impact performance. true by default.
				 */
				statefulFilters: true
			})

		/**
		 * @ngdoc object
		 * @name angularMoment.object:moment
		 *
		 * @description
		 * moment global (as provided by the moment.js library)
		 */
			.constant('moment', moment)

		/**
		 * @ngdoc object
		 * @name angularMoment.config:amTimeAgoConfig
		 * @module angularMoment
		 *
		 * @description
		 * configuration specific to the amTimeAgo directive
		 */
			.constant('amTimeAgoConfig', {
				/**
				 * @ngdoc property
				 * @name angularMoment.config.amTimeAgoConfig#withoutSuffix
				 * @propertyOf angularMoment.config:amTimeAgoConfig
				 * @returns {boolean} Whether to include a suffix in am-time-ago directive
				 *
				 * @description
				 * Defaults to false.
				 */
				withoutSuffix: false,

				/**
				 * @ngdoc property
				 * @name angularMoment.config.amTimeAgoConfig#serverTime
				 * @propertyOf angularMoment.config:amTimeAgoConfig
				 * @returns {number} Server time in milliseconds since the epoch
				 *
				 * @description
				 * If set, time ago will be calculated relative to the given value.
				 * If null, local time will be used. Defaults to null.
				 */
				serverTime: null,

				/**
				 * @ngdoc property
				 * @name angularMoment.config.amTimeAgoConfig#titleFormat
				 * @propertyOf angularMoment.config:amTimeAgoConfig
				 * @returns {string} The format of the date to be displayed in the title of the element. If null,
				 *        the directive set the title of the element.
				 *
				 * @description
				 * The format of the date used for the title of the element. null by default.
				 */
				titleFormat: null,

				/**
				 * @ngdoc property
				 * @name angularMoment.config.amTimeAgoConfig#fullDateThreshold
				 * @propertyOf angularMoment.config:amTimeAgoConfig
				 * @returns {number} The minimum number of days for showing a full date instead of relative time
				 *
				 * @description
				 * The threshold for displaying a full date. The default is null, which means the date will always
				 * be relative, and full date will never be displayed.
				 */
				fullDateThreshold: null,

				/**
				 * @ngdoc property
				 * @name angularMoment.config.amTimeAgoConfig#fullDateFormat
				 * @propertyOf angularMoment.config:amTimeAgoConfig
				 * @returns {string} The format to use when displaying a full date.
				 *
				 * @description
				 * Specify the format of the date when displayed as full date. null by default.
				 */
				fullDateFormat: null,

				fullDateThresholdUnit: 'day'
			})

		/**
		 * @ngdoc directive
		 * @name angularMoment.directive:amTimeAgo
		 * @module angularMoment
		 *
		 * @restrict A
		 */
			.directive('amTimeAgo', ['$window', 'moment', 'amMoment', 'amTimeAgoConfig', function ($window, moment, amMoment, amTimeAgoConfig) {

				return function (scope, element, attr) {
					var activeTimeout = null;
					var currentValue;
					var withoutSuffix = amTimeAgoConfig.withoutSuffix;
					var titleFormat = amTimeAgoConfig.titleFormat;
					var fullDateThreshold = amTimeAgoConfig.fullDateThreshold;
					var fullDateFormat = amTimeAgoConfig.fullDateFormat;
					var fullDateThresholdUnit = amTimeAgoConfig.fullDateThresholdUnit;

					var localDate = new Date().getTime();
					var modelName = attr.amTimeAgo;
					var currentFrom;
					var isTimeElement = ('TIME' === element[0].nodeName.toUpperCase());
					var setTitleTime = !element.attr('title');

					function getNow() {
						var now;
						if (currentFrom) {
							now = currentFrom;
						} else if (amTimeAgoConfig.serverTime) {
							var localNow = new Date().getTime();
							var nowMillis = localNow - localDate + amTimeAgoConfig.serverTime;
							now = moment(nowMillis);
						}
						else {
							now = moment();
						}
						return now;
					}

					function cancelTimer() {
						if (activeTimeout) {
							$window.clearTimeout(activeTimeout);
							activeTimeout = null;
						}
					}

					function updateTime(momentInstance) {
						var timeAgo = getNow().diff(momentInstance, fullDateThresholdUnit);
						var showFullDate = fullDateThreshold && timeAgo >= fullDateThreshold;

						if (showFullDate) {
							element.text(momentInstance.format(fullDateFormat));
						} else {
							element.text(momentInstance.from(getNow(), withoutSuffix));
						}

						if (titleFormat && setTitleTime) {
							element.attr('title', momentInstance.format(titleFormat));
						}

						if (!showFullDate) {
							var howOld = Math.abs(getNow().diff(momentInstance, 'minute'));
							var secondsUntilUpdate = 3600;
							if (howOld < 1) {
								secondsUntilUpdate = 1;
							} else if (howOld < 60) {
								secondsUntilUpdate = 30;
							} else if (howOld < 180) {
								secondsUntilUpdate = 300;
							}

							activeTimeout = $window.setTimeout(function () {
								updateTime(momentInstance);
							}, secondsUntilUpdate * 1000);
						}
					}

					function updateDateTimeAttr(value) {
						if (isTimeElement) {
							element.attr('datetime', value);
						}
					}

					function updateMoment() {
						cancelTimer();
						if (currentValue) {
							var momentValue = amMoment.preprocessDate(currentValue);
							updateTime(momentValue);
							updateDateTimeAttr(momentValue.toISOString());
						}
					}

					scope.$watch(modelName, function (value) {
						if (isUndefinedOrNull(value) || (value === '')) {
							cancelTimer();
							if (currentValue) {
								element.text('');
								updateDateTimeAttr('');
								currentValue = null;
							}
							return;
						}

						currentValue = value;
						updateMoment();
					});

					if (angular.isDefined(attr.amFrom)) {
						scope.$watch(attr.amFrom, function (value) {
							if (isUndefinedOrNull(value) || (value === '')) {
								currentFrom = null;
							} else {
								currentFrom = moment(value);
							}
							updateMoment();
						});
					}

					if (angular.isDefined(attr.amWithoutSuffix)) {
						scope.$watch(attr.amWithoutSuffix, function (value) {
							if (typeof value === 'boolean') {
								withoutSuffix = value;
								updateMoment();
							} else {
								withoutSuffix = amTimeAgoConfig.withoutSuffix;
							}
						});
					}

					attr.$observe('amFullDateThreshold', function (newValue) {
						fullDateThreshold = newValue;
						updateMoment();
					});

					attr.$observe('amFullDateFormat', function (newValue) {
						fullDateFormat = newValue;
						updateMoment();
					});

					attr.$observe('amFullDateThresholdUnit', function (newValue) {
						fullDateThresholdUnit = newValue;
						updateMoment();
					});

					scope.$on('$destroy', function () {
						cancelTimer();
					});

					scope.$on('amMoment:localeChanged', function () {
						updateMoment();
					});
				};
			}])

		/**
		 * @ngdoc service
		 * @name angularMoment.service.amMoment
		 * @module angularMoment
		 */
			.service('amMoment', ['moment', '$rootScope', '$log', 'angularMomentConfig', function (moment, $rootScope, $log, angularMomentConfig) {
				var defaultTimezone = null;

				/**
				 * @ngdoc function
				 * @name angularMoment.service.amMoment#changeLocale
				 * @methodOf angularMoment.service.amMoment
				 *
				 * @description
				 * Changes the locale for moment.js and updates all the am-time-ago directive instances
				 * with the new locale. Also broadcasts an `amMoment:localeChanged` event on $rootScope.
				 *
				 * @param {string} locale Locale code (e.g. en, es, ru, pt-br, etc.)
				 * @param {object} customization object of locale strings to override
				 */
				this.changeLocale = function (locale, customization) {
					var result = moment.locale(locale, customization);
					if (angular.isDefined(locale)) {
						$rootScope.$broadcast('amMoment:localeChanged');

					}
					return result;
				};

				/**
				 * @ngdoc function
				 * @name angularMoment.service.amMoment#changeTimezone
				 * @methodOf angularMoment.service.amMoment
				 *
				 * @description
				 * Changes the default timezone for amCalendar, amDateFormat and amTimeAgo. Also broadcasts an
				 * `amMoment:timezoneChanged` event on $rootScope.
				 *
				 * Note: this method works only if moment-timezone > 0.3.0 is loaded
				 *
				 * @param {string} timezone Timezone name (e.g. UTC)
				 */
				this.changeTimezone = function (timezone) {
					if (moment.tz && moment.tz.setDefault) {
						moment.tz.setDefault(timezone);
						$rootScope.$broadcast('amMoment:timezoneChanged');
					} else {
						$log.warn('angular-moment: changeTimezone() works only with moment-timezone.js v0.3.0 or greater.');
					}
					angularMomentConfig.timezone = timezone;
					defaultTimezone = timezone;
				};

				/**
				 * @ngdoc function
				 * @name angularMoment.service.amMoment#preprocessDate
				 * @methodOf angularMoment.service.amMoment
				 *
				 * @description
				 * Preprocess a given value and convert it into a Moment instance appropriate for use in the
				 * am-time-ago directive and the filters. The behavior of this function can be overriden by
				 * setting `angularMomentConfig.preprocess`.
				 *
				 * @param {*} value The value to be preprocessed
				 * @return {Moment} A `moment` object
				 */
				this.preprocessDate = function (value) {
					// Configure the default timezone if needed
					if (defaultTimezone !== angularMomentConfig.timezone) {
						this.changeTimezone(angularMomentConfig.timezone);
					}

					if (angularMomentConfig.preprocess) {
						return angularMomentConfig.preprocess(value);
					}

					if (!isNaN(parseFloat(value)) && isFinite(value)) {
						// Milliseconds since the epoch
						return moment(parseInt(value, 10));
					}

					// else just returns the value as-is.
					return moment(value);
				};
			}])

		/**
		 * @ngdoc filter
		 * @name angularMoment.filter:amParse
		 * @module angularMoment
		 */
			.filter('amParse', ['moment', function (moment) {
				return function (value, format) {
					return moment(value, format);
				};
			}])

		/**
		 * @ngdoc filter
		 * @name angularMoment.filter:amFromUnix
		 * @module angularMoment
		 */
			.filter('amFromUnix', ['moment', function (moment) {
				return function (value) {
					return moment.unix(value);
				};
			}])

		/**
		 * @ngdoc filter
		 * @name angularMoment.filter:amUtc
		 * @module angularMoment
		 */
			.filter('amUtc', ['moment', function (moment) {
				return function (value) {
					return moment.utc(value);
				};
			}])

		/**
		 * @ngdoc filter
		 * @name angularMoment.filter:amUtcOffset
		 * @module angularMoment
		 *
		 * @description
		 * Adds a UTC offset to the given timezone object. The offset can be a number of minutes, or a string such as
		 * '+0300', '-0300' or 'Z'.
		 */
			.filter('amUtcOffset', ['amMoment', function (amMoment) {
				function amUtcOffset(value, offset) {
					return amMoment.preprocessDate(value).utcOffset(offset);
				}

				return amUtcOffset;
			}])

		/**
		 * @ngdoc filter
		 * @name angularMoment.filter:amLocal
		 * @module angularMoment
		 */
			.filter('amLocal', ['moment', function (moment) {
				return function (value) {
					return moment.isMoment(value) ? value.local() : null;
				};
			}])

		/**
		 * @ngdoc filter
		 * @name angularMoment.filter:amTimezone
		 * @module angularMoment
		 *
		 * @description
		 * Apply a timezone onto a given moment object, e.g. 'America/Phoenix').
		 *
		 * You need to include moment-timezone.js for timezone support.
		 */
			.filter('amTimezone', ['amMoment', 'angularMomentConfig', '$log', function (amMoment, angularMomentConfig, $log) {
				function amTimezone(value, timezone) {
					var aMoment = amMoment.preprocessDate(value);

					if (!timezone) {
						return aMoment;
					}

					if (aMoment.tz) {
						return aMoment.tz(timezone);
					} else {
						$log.warn('angular-moment: named timezone specified but moment.tz() is undefined. Did you forget to include moment-timezone.js ?');
						return aMoment;
					}
				}

				return amTimezone;
			}])

		/**
		 * @ngdoc filter
		 * @name angularMoment.filter:amCalendar
		 * @module angularMoment
		 */
			.filter('amCalendar', ['moment', 'amMoment', 'angularMomentConfig', function (moment, amMoment, angularMomentConfig) {
				function amCalendarFilter(value, referenceTime, formats) {
					if (isUndefinedOrNull(value)) {
						return '';
					}

					var date = amMoment.preprocessDate(value);
					return date.isValid() ? date.calendar(referenceTime, formats) : '';
				}

				// Since AngularJS 1.3, filters have to explicitly define being stateful
				// (this is no longer the default).
				amCalendarFilter.$stateful = angularMomentConfig.statefulFilters;

				return amCalendarFilter;
			}])

		/**
		 * @ngdoc filter
		 * @name angularMoment.filter:amDifference
		 * @module angularMoment
		 */
			.filter('amDifference', ['moment', 'amMoment', 'angularMomentConfig', function (moment, amMoment, angularMomentConfig) {
				function amDifferenceFilter(value, otherValue, unit, usePrecision) {
					if (isUndefinedOrNull(value)) {
						return '';
					}

					var date = amMoment.preprocessDate(value);
					var date2 = !isUndefinedOrNull(otherValue) ? amMoment.preprocessDate(otherValue) : moment();

					if (!date.isValid() || !date2.isValid()) {
						return '';
					}

					return date.diff(date2, unit, usePrecision);
				}

				amDifferenceFilter.$stateful = angularMomentConfig.statefulFilters;

				return amDifferenceFilter;
			}])

		/**
		 * @ngdoc filter
		 * @name angularMoment.filter:amDateFormat
		 * @module angularMoment
		 * @function
		 */
			.filter('amDateFormat', ['moment', 'amMoment', 'angularMomentConfig', function (moment, amMoment, angularMomentConfig) {
				function amDateFormatFilter(value, format) {
					if (isUndefinedOrNull(value)) {
						return '';
					}

					var date = amMoment.preprocessDate(value);
					if (!date.isValid()) {
						return '';
					}

					return date.format(format);
				}

				amDateFormatFilter.$stateful = angularMomentConfig.statefulFilters;

				return amDateFormatFilter;
			}])

		/**
		 * @ngdoc filter
		 * @name angularMoment.filter:amDurationFormat
		 * @module angularMoment
		 * @function
		 */
			.filter('amDurationFormat', ['moment', 'angularMomentConfig', function (moment, angularMomentConfig) {
				function amDurationFormatFilter(value, format, suffix) {
					if (isUndefinedOrNull(value)) {
						return '';
					}

					return moment.duration(value, format).humanize(suffix);
				}

				amDurationFormatFilter.$stateful = angularMomentConfig.statefulFilters;

				return amDurationFormatFilter;
			}])

		/**
		 * @ngdoc filter
		 * @name angularMoment.filter:amTimeAgo
		 * @module angularMoment
		 * @function
		 */
			.filter('amTimeAgo', ['moment', 'amMoment', 'angularMomentConfig', function (moment, amMoment, angularMomentConfig) {
				function amTimeAgoFilter(value, suffix, from) {
					var date, dateFrom;

					if (isUndefinedOrNull(value)) {
						return '';
					}

					value = amMoment.preprocessDate(value);
					date = moment(value);
					if (!date.isValid()) {
						return '';
					}

					dateFrom = moment(from);
					if (!isUndefinedOrNull(from) && dateFrom.isValid()) {
						return date.from(dateFrom, suffix);
					}

					return date.fromNow(suffix);
				}

				amTimeAgoFilter.$stateful = angularMomentConfig.statefulFilters;

				return amTimeAgoFilter;
			}])

		/**
		 * @ngdoc filter
		 * @name angularMoment.filter:amSubtract
		 * @module angularMoment
		 * @function
		 */
			.filter('amSubtract', ['moment', 'angularMomentConfig', function (moment, angularMomentConfig) {
				function amSubtractFilter(value, amount, type) {

					if (isUndefinedOrNull(value)) {
						return '';
					}

					return moment(value).subtract(parseInt(amount, 10), type);
				}

				amSubtractFilter.$stateful = angularMomentConfig.statefulFilters;

				return amSubtractFilter;
			}])

		/**
		 * @ngdoc filter
		 * @name angularMoment.filter:amAdd
		 * @module angularMoment
		 * @function
		 */
			.filter('amAdd', ['moment', 'angularMomentConfig', function (moment, angularMomentConfig) {
				function amAddFilter(value, amount, type) {

					if (isUndefinedOrNull(value)) {
						return '';
					}

					return moment(value).add(parseInt(amount, 10), type);
				}

				amAddFilter.$stateful = angularMomentConfig.statefulFilters;

				return amAddFilter;
			}])

		/**
		 * @ngdoc filter
		 * @name angularMoment.filter:amStartOf
		 * @module angularMoment
		 * @function
		 */
			.filter('amStartOf', ['moment', 'angularMomentConfig', function (moment, angularMomentConfig) {
				function amStartOfFilter(value, type) {

					if (isUndefinedOrNull(value)) {
						return '';
					}

					return moment(value).startOf(type);
				}

				amStartOfFilter.$stateful = angularMomentConfig.statefulFilters;

				return amStartOfFilter;
			}])

		/**
		 * @ngdoc filter
		 * @name angularMoment.filter:amEndOf
		 * @module angularMoment
		 * @function
		 */
			.filter('amEndOf', ['moment', 'angularMomentConfig', function (moment, angularMomentConfig) {
				function amEndOfFilter(value, type) {

					if (isUndefinedOrNull(value)) {
						return '';
					}

					return moment(value).endOf(type);
				}

				amEndOfFilter.$stateful = angularMomentConfig.statefulFilters;

				return amEndOfFilter;
 			}]);

		return 'angularMoment';
	}

	var isElectron = window && window.process && window.process.type;
	if (typeof define === 'function' && define.amd) {
		define(['angular', 'moment'], angularMoment);
	} else if (typeof module !== 'undefined' && module && module.exports && (typeof require === 'function') && !isElectron) {
		module.exports = angularMoment(require('angular'), require('moment'));
	} else {
		angularMoment(angular, (typeof global !== 'undefined' && typeof global.moment !== 'undefined' ? global : window).moment);
	}
})();

'use strict';

var directiveModule = angular.module('angularjs-dropdown-multiselect', []);

directiveModule.directive('ngDropdownMultiselect', ['$filter', '$document', '$compile', '$parse',
    function ($filter, $document, $compile, $parse) {

        return {
            restrict: 'AE',
            scope: {
                selectedModel: '=',
                options: '=',
                extraSettings: '=',
                events: '=',
                searchFilter: '=?',
                translationTexts: '=',
                groupBy: '@',
                open: '=?'
            },
            template: function (element, attrs) {
                var checkboxes = attrs.checkboxes ? true : false;
                var groups = attrs.groupBy ? true : false;

                var template = '<div class="multiselect-parent btn-group dropdown-multiselect">';
                template += '<button type="button" class="dropdown-toggle" ng-class="settings.buttonClasses" ng-click="toggleDropdown()">{{getButtonText()}}&nbsp;<span class="caret"></span></button>';
                template += '<ul class="webkit-scrollbar dropdown-menu dropdown-menu-form" ng-style="{display: open ? \'block\' : \'none\', height : settings.scrollable ? settings.scrollableHeight : \'auto\' }" style="overflow: scroll; overflow-x: visible;" >';
                template += '<li ng-hide="!settings.showCheckAll || settings.selectionLimit > 0"><a data-ng-click="selectAll()"><span class="glyphicon glyphicon-ok"></span>  {{texts.checkAll}}</a>';
                template += '<li ng-show="settings.showUncheckAll"><a data-ng-click="deselectAll();"><span class="glyphicon glyphicon-remove"></span>   {{texts.uncheckAll}}</a></li>';
                template += '<li ng-hide="(!settings.showCheckAll || settings.selectionLimit > 0) && !settings.showUncheckAll" class="divider"></li>';
                template += '<li ng-show="settings.enableSearch"><div class="dropdown-header"><input type="text" class="form-control" style="width: 100%;" ng-model="searchFilter" placeholder="{{texts.searchPlaceholder}}" /></li>';
                template += '<li ng-show="settings.enableSearch" class="divider"></li>';

                if (groups) {
                    template += '<li ng-repeat-start="option in orderedItems | filter: searchFilter" ng-show="getPropertyForObject(option, settings.groupBy) !== getPropertyForObject(orderedItems[$index - 1], settings.groupBy)" role="presentation" class="dropdown-header">{{ getGroupTitle(getPropertyForObject(option, settings.groupBy)) }}</li>';
                    template += '<li ng-repeat-end role="presentation">';
                } else {
                    template += '<li role="presentation" ng-repeat="option in options | filter: searchFilter">';
                }

                template += '<a role="menuitem" tabindex="-1" ng-click="setSelectedItem(getPropertyForObject(option,settings.idProp))">';

                if (checkboxes) {
                    template += '<div class="checkbox"><label><input class="checkboxInput" type="checkbox" ng-click="checkboxClick($event, getPropertyForObject(option,settings.idProp))" ng-checked="isChecked(getPropertyForObject(option,settings.idProp))" /> {{getPropertyForObject(option, settings.displayProp)}}</label></div></a>';
                } else {
                    template += '<span data-ng-class="{\'glyphicon glyphicon-ok\': isChecked(getPropertyForObject(option,settings.idProp))}"></span> {{getPropertyForObject(option, settings.displayProp)}}</a>';
                }

                template += '</li>';

                template += '<li class="divider" ng-show="settings.selectionLimit > 1"></li>';
                template += '<li role="presentation" ng-show="settings.selectionLimit > 1"><a role="menuitem" class="cursor-pointer">{{selectedModel.length}} {{texts.selectionOf}} {{settings.selectionLimit}} {{texts.selectionCount}}</a></li>';

                template += '</ul>';
                template += '</div>';

                element.html(template);
            },
            link: function ($scope, $element, $attrs) {
                var $dropdownTrigger = $element.children()[0];

                $scope.toggleDropdown = function () {
                    $scope.open = !$scope.open;
                };

                $scope.checkboxClick = function ($event, id) {
                    $scope.setSelectedItem(id);
                    $event.stopImmediatePropagation();
                };

                $scope.externalEvents = {
                    onItemSelect: angular.noop,
                    onItemDeselect: angular.noop,
                    onSelectAll: angular.noop,
                    onDeselectAll: angular.noop,
                    onInitDone: angular.noop,
                    onMaxSelectionReached: angular.noop
                };

                $scope.settings = {
                    dynamicTitle: true,
                    scrollable: false,
                    scrollableHeight: '300px',
                    closeOnBlur: true,
                    displayProp: 'label',
                    idProp: 'id',
                    externalIdProp: 'id',
                    enableSearch: false,
                    selectionLimit: 0,
                    showCheckAll: true,
                    showUncheckAll: true,
                    closeOnSelect: false,
                    buttonClasses: 'btn btn-default',
                    closeOnDeselect: false,
                    groupBy: $attrs.groupBy || undefined,
                    groupByTextProvider: null,
                    smartButtonMaxItems: 0,
                    smartButtonTextConverter: angular.noop
                };

                $scope.texts = {
                    checkAll: 'Check All',
                    uncheckAll: 'Uncheck All',
                    selectionCount: 'checked',
                    selectionOf: '/',
                    searchPlaceholder: 'Search...',
                    buttonDefaultText: 'Select',
                    dynamicButtonTextSuffix: 'checked'
                };

                $scope.searchFilter = $scope.searchFilter || '';

                if (angular.isDefined($scope.settings.groupBy)) {
                    $scope.$watch('options', function (newValue) {
                        if (angular.isDefined(newValue)) {
                            $scope.orderedItems = $filter('orderBy')(newValue, $scope.settings.groupBy);
                        }
                    });
                }

                $scope.settings = angular.extend({}, $scope.settings, $scope.extraSettings || []);
                $scope.externalEvents = angular.extend({}, $scope.externalEvents, $scope.events || []);
                $scope.texts = angular.extend({}, $scope.texts, $scope.translationTexts);

                $scope.singleSelection = $scope.settings.selectionLimit === 1;

                function getFindObj(id) {
                    var findObj = {};

                    if ($scope.settings.externalIdProp === '') {
                        findObj[$scope.settings.idProp] = id;
                    } else {
                        findObj[$scope.settings.externalIdProp] = id;
                    }

                    return findObj;
                }

                function clearObject(object) {
                    for (var prop in object) {
                        delete object[prop];
                    }
                }

                if ($scope.singleSelection) {
                    if (angular.isArray($scope.selectedModel) && $scope.selectedModel.length === 0) {
                        clearObject($scope.selectedModel);
                    }
                }

                if ($scope.settings.closeOnBlur) {
                    $document.on('click', function (e) {
                        var target = e.target.parentElement;
                        var parentFound = false;

                        while (angular.isDefined(target) && target !== null && !parentFound) {
                            if (_.contains(target.className.split(' '), 'multiselect-parent') && !parentFound) {
                                if (target === $dropdownTrigger) {
                                    parentFound = true;
                                }
                            }
                            target = target.parentElement;
                        }

                        if (!parentFound) {
                            $scope.$apply(function () {
                                $scope.open = false;
                            });
                        }
                    });
                }

                $scope.getGroupTitle = function (groupValue) {
                    if ($scope.settings.groupByTextProvider !== null) {
                        return $scope.settings.groupByTextProvider(groupValue);
                    }

                    return groupValue;
                };

                $scope.getButtonText = function () {
                    if ($scope.settings.dynamicTitle && ($scope.selectedModel.length > 0 || (angular.isObject($scope.selectedModel) && _.keys($scope.selectedModel).length > 0))) {
                        if ($scope.settings.smartButtonMaxItems > 0) {
                            var itemsText = [];

                            angular.forEach($scope.options, function (optionItem) {
                                if ($scope.isChecked($scope.getPropertyForObject(optionItem, $scope.settings.idProp))) {
                                    var displayText = $scope.getPropertyForObject(optionItem, $scope.settings.displayProp);
                                    var converterResponse = $scope.settings.smartButtonTextConverter(displayText, optionItem);

                                    itemsText.push(converterResponse ? converterResponse : displayText);
                                }
                            });

                            if ($scope.selectedModel.length > $scope.settings.smartButtonMaxItems) {
                                itemsText = itemsText.slice(0, $scope.settings.smartButtonMaxItems);
                                itemsText.push('...');
                            }

                            return itemsText.join(', ');
                        } else {
                            var totalSelected;

                            if ($scope.singleSelection) {
                                totalSelected = ($scope.selectedModel !== null && angular.isDefined($scope.selectedModel[$scope.settings.idProp])) ? 1 : 0;
                            } else {
                                totalSelected = angular.isDefined($scope.selectedModel) ? $scope.selectedModel.length : 0;
                            }

                            if (totalSelected === 0) {
                                return $scope.texts.buttonDefaultText;
                            } else {
                                return totalSelected + ' ' + $scope.texts.dynamicButtonTextSuffix;
                            }
                        }
                    } else {
                        return $scope.texts.buttonDefaultText;
                    }
                };

                $scope.getPropertyForObject = function (object, property) {
                    if (angular.isDefined(object) && object.hasOwnProperty(property)) {
                        return object[property];
                    }

                    return '';
                };

                $scope.selectAll = function () {
                    $scope.deselectAll(false);
                    $scope.externalEvents.onSelectAll();

                    angular.forEach($scope.options, function (value) {
                        $scope.setSelectedItem(value[$scope.settings.idProp], true);
                    });
                };

                $scope.deselectAll = function (sendEvent) {
                    sendEvent = sendEvent || true;

                    if (sendEvent) {
                        $scope.externalEvents.onDeselectAll();
                    }

                    if ($scope.singleSelection) {
                        clearObject($scope.selectedModel);
                    } else {
                        $scope.selectedModel.splice(0, $scope.selectedModel.length);
                    }
                };

                $scope.setSelectedItem = function (id, dontRemove) {
                    var findObj = getFindObj(id);
                    var finalObj = null;

                    if ($scope.settings.externalIdProp === '') {
                        finalObj = _.find($scope.options, findObj);
                    } else {
                        finalObj = findObj;
                    }

                    if ($scope.singleSelection) {
                        clearObject($scope.selectedModel);
                        angular.extend($scope.selectedModel, finalObj);
                        $scope.externalEvents.onItemSelect(finalObj);
                        if ($scope.settings.closeOnSelect) $scope.open = false;

                        return;
                    }

                    dontRemove = dontRemove || false;

                    var exists = _.findIndex($scope.selectedModel, findObj) !== -1;

                    if (!dontRemove && exists) {
                        $scope.selectedModel.splice(_.findIndex($scope.selectedModel, findObj), 1);
                        $scope.externalEvents.onItemDeselect(findObj);
                    } else if (!exists && ($scope.settings.selectionLimit === 0 || $scope.selectedModel.length < $scope.settings.selectionLimit)) {
                        $scope.selectedModel.push(finalObj);
                        $scope.externalEvents.onItemSelect(finalObj);
                    }
                    if ($scope.settings.closeOnSelect) $scope.open = false;
                };

                $scope.isChecked = function (id) {
                    if ($scope.singleSelection) {
                        return $scope.selectedModel !== null && angular.isDefined($scope.selectedModel[$scope.settings.idProp]) && $scope.selectedModel[$scope.settings.idProp] === getFindObj(id)[$scope.settings.idProp];
                    }

                    return _.findIndex($scope.selectedModel, getFindObj(id)) !== -1;
                };

                $scope.externalEvents.onInitDone();
            }
        };
    }]);
/**
 * @license
 * Lo-Dash 2.4.1 (Custom Build) lodash.com/license | Underscore.js 1.5.2 underscorejs.org/LICENSE
 * Build: `lodash modern -o ./dist/lodash.js`
 */
;(function(){function n(n,t,e){e=(e||0)-1;for(var r=n?n.length:0;++e<r;)if(n[e]===t)return e;return-1}function t(t,e){var r=typeof e;if(t=t.l,"boolean"==r||null==e)return t[e]?0:-1;"number"!=r&&"string"!=r&&(r="object");var u="number"==r?e:m+e;return t=(t=t[r])&&t[u],"object"==r?t&&-1<n(t,e)?0:-1:t?0:-1}function e(n){var t=this.l,e=typeof n;if("boolean"==e||null==n)t[n]=true;else{"number"!=e&&"string"!=e&&(e="object");var r="number"==e?n:m+n,t=t[e]||(t[e]={});"object"==e?(t[r]||(t[r]=[])).push(n):t[r]=true
}}function r(n){return n.charCodeAt(0)}function u(n,t){for(var e=n.m,r=t.m,u=-1,o=e.length;++u<o;){var i=e[u],a=r[u];if(i!==a){if(i>a||typeof i=="undefined")return 1;if(i<a||typeof a=="undefined")return-1}}return n.n-t.n}function o(n){var t=-1,r=n.length,u=n[0],o=n[r/2|0],i=n[r-1];if(u&&typeof u=="object"&&o&&typeof o=="object"&&i&&typeof i=="object")return false;for(u=f(),u["false"]=u["null"]=u["true"]=u.undefined=false,o=f(),o.k=n,o.l=u,o.push=e;++t<r;)o.push(n[t]);return o}function i(n){return"\\"+U[n]
}function a(){return h.pop()||[]}function f(){return g.pop()||{k:null,l:null,m:null,"false":false,n:0,"null":false,number:null,object:null,push:null,string:null,"true":false,undefined:false,o:null}}function l(n){n.length=0,h.length<_&&h.push(n)}function c(n){var t=n.l;t&&c(t),n.k=n.l=n.m=n.object=n.number=n.string=n.o=null,g.length<_&&g.push(n)}function p(n,t,e){t||(t=0),typeof e=="undefined"&&(e=n?n.length:0);var r=-1;e=e-t||0;for(var u=Array(0>e?0:e);++r<e;)u[r]=n[t+r];return u}function s(e){function h(n,t,e){if(!n||!V[typeof n])return n;
t=t&&typeof e=="undefined"?t:tt(t,e,3);for(var r=-1,u=V[typeof n]&&Fe(n),o=u?u.length:0;++r<o&&(e=u[r],false!==t(n[e],e,n)););return n}function g(n,t,e){var r;if(!n||!V[typeof n])return n;t=t&&typeof e=="undefined"?t:tt(t,e,3);for(r in n)if(false===t(n[r],r,n))break;return n}function _(n,t,e){var r,u=n,o=u;if(!u)return o;for(var i=arguments,a=0,f=typeof e=="number"?2:i.length;++a<f;)if((u=i[a])&&V[typeof u])for(var l=-1,c=V[typeof u]&&Fe(u),p=c?c.length:0;++l<p;)r=c[l],"undefined"==typeof o[r]&&(o[r]=u[r]);
return o}function U(n,t,e){var r,u=n,o=u;if(!u)return o;var i=arguments,a=0,f=typeof e=="number"?2:i.length;if(3<f&&"function"==typeof i[f-2])var l=tt(i[--f-1],i[f--],2);else 2<f&&"function"==typeof i[f-1]&&(l=i[--f]);for(;++a<f;)if((u=i[a])&&V[typeof u])for(var c=-1,p=V[typeof u]&&Fe(u),s=p?p.length:0;++c<s;)r=p[c],o[r]=l?l(o[r],u[r]):u[r];return o}function H(n){var t,e=[];if(!n||!V[typeof n])return e;for(t in n)me.call(n,t)&&e.push(t);return e}function J(n){return n&&typeof n=="object"&&!Te(n)&&me.call(n,"__wrapped__")?n:new Q(n)
}function Q(n,t){this.__chain__=!!t,this.__wrapped__=n}function X(n){function t(){if(r){var n=p(r);be.apply(n,arguments)}if(this instanceof t){var o=nt(e.prototype),n=e.apply(o,n||arguments);return wt(n)?n:o}return e.apply(u,n||arguments)}var e=n[0],r=n[2],u=n[4];return $e(t,n),t}function Z(n,t,e,r,u){if(e){var o=e(n);if(typeof o!="undefined")return o}if(!wt(n))return n;var i=ce.call(n);if(!K[i])return n;var f=Ae[i];switch(i){case T:case F:return new f(+n);case W:case P:return new f(n);case z:return o=f(n.source,C.exec(n)),o.lastIndex=n.lastIndex,o
}if(i=Te(n),t){var c=!r;r||(r=a()),u||(u=a());for(var s=r.length;s--;)if(r[s]==n)return u[s];o=i?f(n.length):{}}else o=i?p(n):U({},n);return i&&(me.call(n,"index")&&(o.index=n.index),me.call(n,"input")&&(o.input=n.input)),t?(r.push(n),u.push(o),(i?St:h)(n,function(n,i){o[i]=Z(n,t,e,r,u)}),c&&(l(r),l(u)),o):o}function nt(n){return wt(n)?ke(n):{}}function tt(n,t,e){if(typeof n!="function")return Ut;if(typeof t=="undefined"||!("prototype"in n))return n;var r=n.__bindData__;if(typeof r=="undefined"&&(De.funcNames&&(r=!n.name),r=r||!De.funcDecomp,!r)){var u=ge.call(n);
De.funcNames||(r=!O.test(u)),r||(r=E.test(u),$e(n,r))}if(false===r||true!==r&&1&r[1])return n;switch(e){case 1:return function(e){return n.call(t,e)};case 2:return function(e,r){return n.call(t,e,r)};case 3:return function(e,r,u){return n.call(t,e,r,u)};case 4:return function(e,r,u,o){return n.call(t,e,r,u,o)}}return Mt(n,t)}function et(n){function t(){var n=f?i:this;if(u){var h=p(u);be.apply(h,arguments)}return(o||c)&&(h||(h=p(arguments)),o&&be.apply(h,o),c&&h.length<a)?(r|=16,et([e,s?r:-4&r,h,null,i,a])):(h||(h=arguments),l&&(e=n[v]),this instanceof t?(n=nt(e.prototype),h=e.apply(n,h),wt(h)?h:n):e.apply(n,h))
}var e=n[0],r=n[1],u=n[2],o=n[3],i=n[4],a=n[5],f=1&r,l=2&r,c=4&r,s=8&r,v=e;return $e(t,n),t}function rt(e,r){var u=-1,i=st(),a=e?e.length:0,f=a>=b&&i===n,l=[];if(f){var p=o(r);p?(i=t,r=p):f=false}for(;++u<a;)p=e[u],0>i(r,p)&&l.push(p);return f&&c(r),l}function ut(n,t,e,r){r=(r||0)-1;for(var u=n?n.length:0,o=[];++r<u;){var i=n[r];if(i&&typeof i=="object"&&typeof i.length=="number"&&(Te(i)||yt(i))){t||(i=ut(i,t,e));var a=-1,f=i.length,l=o.length;for(o.length+=f;++a<f;)o[l++]=i[a]}else e||o.push(i)}return o
}function ot(n,t,e,r,u,o){if(e){var i=e(n,t);if(typeof i!="undefined")return!!i}if(n===t)return 0!==n||1/n==1/t;if(n===n&&!(n&&V[typeof n]||t&&V[typeof t]))return false;if(null==n||null==t)return n===t;var f=ce.call(n),c=ce.call(t);if(f==D&&(f=q),c==D&&(c=q),f!=c)return false;switch(f){case T:case F:return+n==+t;case W:return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case z:case P:return n==oe(t)}if(c=f==$,!c){var p=me.call(n,"__wrapped__"),s=me.call(t,"__wrapped__");if(p||s)return ot(p?n.__wrapped__:n,s?t.__wrapped__:t,e,r,u,o);
if(f!=q)return false;if(f=n.constructor,p=t.constructor,f!=p&&!(dt(f)&&f instanceof f&&dt(p)&&p instanceof p)&&"constructor"in n&&"constructor"in t)return false}for(f=!u,u||(u=a()),o||(o=a()),p=u.length;p--;)if(u[p]==n)return o[p]==t;var v=0,i=true;if(u.push(n),o.push(t),c){if(p=n.length,v=t.length,(i=v==p)||r)for(;v--;)if(c=p,s=t[v],r)for(;c--&&!(i=ot(n[c],s,e,r,u,o)););else if(!(i=ot(n[v],s,e,r,u,o)))break}else g(t,function(t,a,f){return me.call(f,a)?(v++,i=me.call(n,a)&&ot(n[a],t,e,r,u,o)):void 0}),i&&!r&&g(n,function(n,t,e){return me.call(e,t)?i=-1<--v:void 0
});return u.pop(),o.pop(),f&&(l(u),l(o)),i}function it(n,t,e,r,u){(Te(t)?St:h)(t,function(t,o){var i,a,f=t,l=n[o];if(t&&((a=Te(t))||Pe(t))){for(f=r.length;f--;)if(i=r[f]==t){l=u[f];break}if(!i){var c;e&&(f=e(l,t),c=typeof f!="undefined")&&(l=f),c||(l=a?Te(l)?l:[]:Pe(l)?l:{}),r.push(t),u.push(l),c||it(l,t,e,r,u)}}else e&&(f=e(l,t),typeof f=="undefined"&&(f=t)),typeof f!="undefined"&&(l=f);n[o]=l})}function at(n,t){return n+he(Re()*(t-n+1))}function ft(e,r,u){var i=-1,f=st(),p=e?e.length:0,s=[],v=!r&&p>=b&&f===n,h=u||v?a():s;
for(v&&(h=o(h),f=t);++i<p;){var g=e[i],y=u?u(g,i,e):g;(r?!i||h[h.length-1]!==y:0>f(h,y))&&((u||v)&&h.push(y),s.push(g))}return v?(l(h.k),c(h)):u&&l(h),s}function lt(n){return function(t,e,r){var u={};e=J.createCallback(e,r,3),r=-1;var o=t?t.length:0;if(typeof o=="number")for(;++r<o;){var i=t[r];n(u,i,e(i,r,t),t)}else h(t,function(t,r,o){n(u,t,e(t,r,o),o)});return u}}function ct(n,t,e,r,u,o){var i=1&t,a=4&t,f=16&t,l=32&t;if(!(2&t||dt(n)))throw new ie;f&&!e.length&&(t&=-17,f=e=false),l&&!r.length&&(t&=-33,l=r=false);
var c=n&&n.__bindData__;return c&&true!==c?(c=p(c),c[2]&&(c[2]=p(c[2])),c[3]&&(c[3]=p(c[3])),!i||1&c[1]||(c[4]=u),!i&&1&c[1]&&(t|=8),!a||4&c[1]||(c[5]=o),f&&be.apply(c[2]||(c[2]=[]),e),l&&we.apply(c[3]||(c[3]=[]),r),c[1]|=t,ct.apply(null,c)):(1==t||17===t?X:et)([n,t,e,r,u,o])}function pt(n){return Be[n]}function st(){var t=(t=J.indexOf)===Wt?n:t;return t}function vt(n){return typeof n=="function"&&pe.test(n)}function ht(n){var t,e;return n&&ce.call(n)==q&&(t=n.constructor,!dt(t)||t instanceof t)?(g(n,function(n,t){e=t
}),typeof e=="undefined"||me.call(n,e)):false}function gt(n){return We[n]}function yt(n){return n&&typeof n=="object"&&typeof n.length=="number"&&ce.call(n)==D||false}function mt(n,t,e){var r=Fe(n),u=r.length;for(t=tt(t,e,3);u--&&(e=r[u],false!==t(n[e],e,n)););return n}function bt(n){var t=[];return g(n,function(n,e){dt(n)&&t.push(e)}),t.sort()}function _t(n){for(var t=-1,e=Fe(n),r=e.length,u={};++t<r;){var o=e[t];u[n[o]]=o}return u}function dt(n){return typeof n=="function"}function wt(n){return!(!n||!V[typeof n])
}function jt(n){return typeof n=="number"||n&&typeof n=="object"&&ce.call(n)==W||false}function kt(n){return typeof n=="string"||n&&typeof n=="object"&&ce.call(n)==P||false}function xt(n){for(var t=-1,e=Fe(n),r=e.length,u=Xt(r);++t<r;)u[t]=n[e[t]];return u}function Ct(n,t,e){var r=-1,u=st(),o=n?n.length:0,i=false;return e=(0>e?Ie(0,o+e):e)||0,Te(n)?i=-1<u(n,t,e):typeof o=="number"?i=-1<(kt(n)?n.indexOf(t,e):u(n,t,e)):h(n,function(n){return++r<e?void 0:!(i=n===t)}),i}function Ot(n,t,e){var r=true;t=J.createCallback(t,e,3),e=-1;
var u=n?n.length:0;if(typeof u=="number")for(;++e<u&&(r=!!t(n[e],e,n)););else h(n,function(n,e,u){return r=!!t(n,e,u)});return r}function Nt(n,t,e){var r=[];t=J.createCallback(t,e,3),e=-1;var u=n?n.length:0;if(typeof u=="number")for(;++e<u;){var o=n[e];t(o,e,n)&&r.push(o)}else h(n,function(n,e,u){t(n,e,u)&&r.push(n)});return r}function It(n,t,e){t=J.createCallback(t,e,3),e=-1;var r=n?n.length:0;if(typeof r!="number"){var u;return h(n,function(n,e,r){return t(n,e,r)?(u=n,false):void 0}),u}for(;++e<r;){var o=n[e];
if(t(o,e,n))return o}}function St(n,t,e){var r=-1,u=n?n.length:0;if(t=t&&typeof e=="undefined"?t:tt(t,e,3),typeof u=="number")for(;++r<u&&false!==t(n[r],r,n););else h(n,t);return n}function Et(n,t,e){var r=n?n.length:0;if(t=t&&typeof e=="undefined"?t:tt(t,e,3),typeof r=="number")for(;r--&&false!==t(n[r],r,n););else{var u=Fe(n),r=u.length;h(n,function(n,e,o){return e=u?u[--r]:--r,t(o[e],e,o)})}return n}function Rt(n,t,e){var r=-1,u=n?n.length:0;if(t=J.createCallback(t,e,3),typeof u=="number")for(var o=Xt(u);++r<u;)o[r]=t(n[r],r,n);
else o=[],h(n,function(n,e,u){o[++r]=t(n,e,u)});return o}function At(n,t,e){var u=-1/0,o=u;if(typeof t!="function"&&e&&e[t]===n&&(t=null),null==t&&Te(n)){e=-1;for(var i=n.length;++e<i;){var a=n[e];a>o&&(o=a)}}else t=null==t&&kt(n)?r:J.createCallback(t,e,3),St(n,function(n,e,r){e=t(n,e,r),e>u&&(u=e,o=n)});return o}function Dt(n,t,e,r){if(!n)return e;var u=3>arguments.length;t=J.createCallback(t,r,4);var o=-1,i=n.length;if(typeof i=="number")for(u&&(e=n[++o]);++o<i;)e=t(e,n[o],o,n);else h(n,function(n,r,o){e=u?(u=false,n):t(e,n,r,o)
});return e}function $t(n,t,e,r){var u=3>arguments.length;return t=J.createCallback(t,r,4),Et(n,function(n,r,o){e=u?(u=false,n):t(e,n,r,o)}),e}function Tt(n){var t=-1,e=n?n.length:0,r=Xt(typeof e=="number"?e:0);return St(n,function(n){var e=at(0,++t);r[t]=r[e],r[e]=n}),r}function Ft(n,t,e){var r;t=J.createCallback(t,e,3),e=-1;var u=n?n.length:0;if(typeof u=="number")for(;++e<u&&!(r=t(n[e],e,n)););else h(n,function(n,e,u){return!(r=t(n,e,u))});return!!r}function Bt(n,t,e){var r=0,u=n?n.length:0;if(typeof t!="number"&&null!=t){var o=-1;
for(t=J.createCallback(t,e,3);++o<u&&t(n[o],o,n);)r++}else if(r=t,null==r||e)return n?n[0]:v;return p(n,0,Se(Ie(0,r),u))}function Wt(t,e,r){if(typeof r=="number"){var u=t?t.length:0;r=0>r?Ie(0,u+r):r||0}else if(r)return r=zt(t,e),t[r]===e?r:-1;return n(t,e,r)}function qt(n,t,e){if(typeof t!="number"&&null!=t){var r=0,u=-1,o=n?n.length:0;for(t=J.createCallback(t,e,3);++u<o&&t(n[u],u,n);)r++}else r=null==t||e?1:Ie(0,t);return p(n,r)}function zt(n,t,e,r){var u=0,o=n?n.length:u;for(e=e?J.createCallback(e,r,1):Ut,t=e(t);u<o;)r=u+o>>>1,e(n[r])<t?u=r+1:o=r;
return u}function Pt(n,t,e,r){return typeof t!="boolean"&&null!=t&&(r=e,e=typeof t!="function"&&r&&r[t]===n?null:t,t=false),null!=e&&(e=J.createCallback(e,r,3)),ft(n,t,e)}function Kt(){for(var n=1<arguments.length?arguments:arguments[0],t=-1,e=n?At(Ve(n,"length")):0,r=Xt(0>e?0:e);++t<e;)r[t]=Ve(n,t);return r}function Lt(n,t){var e=-1,r=n?n.length:0,u={};for(t||!r||Te(n[0])||(t=[]);++e<r;){var o=n[e];t?u[o]=t[e]:o&&(u[o[0]]=o[1])}return u}function Mt(n,t){return 2<arguments.length?ct(n,17,p(arguments,2),null,t):ct(n,1,null,null,t)
}function Vt(n,t,e){function r(){c&&ve(c),i=c=p=v,(g||h!==t)&&(s=Ue(),a=n.apply(l,o),c||i||(o=l=null))}function u(){var e=t-(Ue()-f);0<e?c=_e(u,e):(i&&ve(i),e=p,i=c=p=v,e&&(s=Ue(),a=n.apply(l,o),c||i||(o=l=null)))}var o,i,a,f,l,c,p,s=0,h=false,g=true;if(!dt(n))throw new ie;if(t=Ie(0,t)||0,true===e)var y=true,g=false;else wt(e)&&(y=e.leading,h="maxWait"in e&&(Ie(t,e.maxWait)||0),g="trailing"in e?e.trailing:g);return function(){if(o=arguments,f=Ue(),l=this,p=g&&(c||!y),false===h)var e=y&&!c;else{i||y||(s=f);var v=h-(f-s),m=0>=v;
m?(i&&(i=ve(i)),s=f,a=n.apply(l,o)):i||(i=_e(r,v))}return m&&c?c=ve(c):c||t===h||(c=_e(u,t)),e&&(m=true,a=n.apply(l,o)),!m||c||i||(o=l=null),a}}function Ut(n){return n}function Gt(n,t,e){var r=true,u=t&&bt(t);t&&(e||u.length)||(null==e&&(e=t),o=Q,t=n,n=J,u=bt(t)),false===e?r=false:wt(e)&&"chain"in e&&(r=e.chain);var o=n,i=dt(o);St(u,function(e){var u=n[e]=t[e];i&&(o.prototype[e]=function(){var t=this.__chain__,e=this.__wrapped__,i=[e];if(be.apply(i,arguments),i=u.apply(n,i),r||t){if(e===i&&wt(i))return this;
i=new o(i),i.__chain__=t}return i})})}function Ht(){}function Jt(n){return function(t){return t[n]}}function Qt(){return this.__wrapped__}e=e?Y.defaults(G.Object(),e,Y.pick(G,A)):G;var Xt=e.Array,Yt=e.Boolean,Zt=e.Date,ne=e.Function,te=e.Math,ee=e.Number,re=e.Object,ue=e.RegExp,oe=e.String,ie=e.TypeError,ae=[],fe=re.prototype,le=e._,ce=fe.toString,pe=ue("^"+oe(ce).replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/toString| for [^\]]+/g,".*?")+"$"),se=te.ceil,ve=e.clearTimeout,he=te.floor,ge=ne.prototype.toString,ye=vt(ye=re.getPrototypeOf)&&ye,me=fe.hasOwnProperty,be=ae.push,_e=e.setTimeout,de=ae.splice,we=ae.unshift,je=function(){try{var n={},t=vt(t=re.defineProperty)&&t,e=t(n,n,n)&&t
}catch(r){}return e}(),ke=vt(ke=re.create)&&ke,xe=vt(xe=Xt.isArray)&&xe,Ce=e.isFinite,Oe=e.isNaN,Ne=vt(Ne=re.keys)&&Ne,Ie=te.max,Se=te.min,Ee=e.parseInt,Re=te.random,Ae={};Ae[$]=Xt,Ae[T]=Yt,Ae[F]=Zt,Ae[B]=ne,Ae[q]=re,Ae[W]=ee,Ae[z]=ue,Ae[P]=oe,Q.prototype=J.prototype;var De=J.support={};De.funcDecomp=!vt(e.a)&&E.test(s),De.funcNames=typeof ne.name=="string",J.templateSettings={escape:/<%-([\s\S]+?)%>/g,evaluate:/<%([\s\S]+?)%>/g,interpolate:N,variable:"",imports:{_:J}},ke||(nt=function(){function n(){}return function(t){if(wt(t)){n.prototype=t;
var r=new n;n.prototype=null}return r||e.Object()}}());var $e=je?function(n,t){M.value=t,je(n,"__bindData__",M)}:Ht,Te=xe||function(n){return n&&typeof n=="object"&&typeof n.length=="number"&&ce.call(n)==$||false},Fe=Ne?function(n){return wt(n)?Ne(n):[]}:H,Be={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"},We=_t(Be),qe=ue("("+Fe(We).join("|")+")","g"),ze=ue("["+Fe(Be).join("")+"]","g"),Pe=ye?function(n){if(!n||ce.call(n)!=q)return false;var t=n.valueOf,e=vt(t)&&(e=ye(t))&&ye(e);return e?n==e||ye(n)==e:ht(n)
}:ht,Ke=lt(function(n,t,e){me.call(n,e)?n[e]++:n[e]=1}),Le=lt(function(n,t,e){(me.call(n,e)?n[e]:n[e]=[]).push(t)}),Me=lt(function(n,t,e){n[e]=t}),Ve=Rt,Ue=vt(Ue=Zt.now)&&Ue||function(){return(new Zt).getTime()},Ge=8==Ee(d+"08")?Ee:function(n,t){return Ee(kt(n)?n.replace(I,""):n,t||0)};return J.after=function(n,t){if(!dt(t))throw new ie;return function(){return 1>--n?t.apply(this,arguments):void 0}},J.assign=U,J.at=function(n){for(var t=arguments,e=-1,r=ut(t,true,false,1),t=t[2]&&t[2][t[1]]===n?1:r.length,u=Xt(t);++e<t;)u[e]=n[r[e]];
return u},J.bind=Mt,J.bindAll=function(n){for(var t=1<arguments.length?ut(arguments,true,false,1):bt(n),e=-1,r=t.length;++e<r;){var u=t[e];n[u]=ct(n[u],1,null,null,n)}return n},J.bindKey=function(n,t){return 2<arguments.length?ct(t,19,p(arguments,2),null,n):ct(t,3,null,null,n)},J.chain=function(n){return n=new Q(n),n.__chain__=true,n},J.compact=function(n){for(var t=-1,e=n?n.length:0,r=[];++t<e;){var u=n[t];u&&r.push(u)}return r},J.compose=function(){for(var n=arguments,t=n.length;t--;)if(!dt(n[t]))throw new ie;
return function(){for(var t=arguments,e=n.length;e--;)t=[n[e].apply(this,t)];return t[0]}},J.constant=function(n){return function(){return n}},J.countBy=Ke,J.create=function(n,t){var e=nt(n);return t?U(e,t):e},J.createCallback=function(n,t,e){var r=typeof n;if(null==n||"function"==r)return tt(n,t,e);if("object"!=r)return Jt(n);var u=Fe(n),o=u[0],i=n[o];return 1!=u.length||i!==i||wt(i)?function(t){for(var e=u.length,r=false;e--&&(r=ot(t[u[e]],n[u[e]],null,true)););return r}:function(n){return n=n[o],i===n&&(0!==i||1/i==1/n)
}},J.curry=function(n,t){return t=typeof t=="number"?t:+t||n.length,ct(n,4,null,null,null,t)},J.debounce=Vt,J.defaults=_,J.defer=function(n){if(!dt(n))throw new ie;var t=p(arguments,1);return _e(function(){n.apply(v,t)},1)},J.delay=function(n,t){if(!dt(n))throw new ie;var e=p(arguments,2);return _e(function(){n.apply(v,e)},t)},J.difference=function(n){return rt(n,ut(arguments,true,true,1))},J.filter=Nt,J.flatten=function(n,t,e,r){return typeof t!="boolean"&&null!=t&&(r=e,e=typeof t!="function"&&r&&r[t]===n?null:t,t=false),null!=e&&(n=Rt(n,e,r)),ut(n,t)
},J.forEach=St,J.forEachRight=Et,J.forIn=g,J.forInRight=function(n,t,e){var r=[];g(n,function(n,t){r.push(t,n)});var u=r.length;for(t=tt(t,e,3);u--&&false!==t(r[u--],r[u],n););return n},J.forOwn=h,J.forOwnRight=mt,J.functions=bt,J.groupBy=Le,J.indexBy=Me,J.initial=function(n,t,e){var r=0,u=n?n.length:0;if(typeof t!="number"&&null!=t){var o=u;for(t=J.createCallback(t,e,3);o--&&t(n[o],o,n);)r++}else r=null==t||e?1:t||r;return p(n,0,Se(Ie(0,u-r),u))},J.intersection=function(){for(var e=[],r=-1,u=arguments.length,i=a(),f=st(),p=f===n,s=a();++r<u;){var v=arguments[r];
(Te(v)||yt(v))&&(e.push(v),i.push(p&&v.length>=b&&o(r?e[r]:s)))}var p=e[0],h=-1,g=p?p.length:0,y=[];n:for(;++h<g;){var m=i[0],v=p[h];if(0>(m?t(m,v):f(s,v))){for(r=u,(m||s).push(v);--r;)if(m=i[r],0>(m?t(m,v):f(e[r],v)))continue n;y.push(v)}}for(;u--;)(m=i[u])&&c(m);return l(i),l(s),y},J.invert=_t,J.invoke=function(n,t){var e=p(arguments,2),r=-1,u=typeof t=="function",o=n?n.length:0,i=Xt(typeof o=="number"?o:0);return St(n,function(n){i[++r]=(u?t:n[t]).apply(n,e)}),i},J.keys=Fe,J.map=Rt,J.mapValues=function(n,t,e){var r={};
return t=J.createCallback(t,e,3),h(n,function(n,e,u){r[e]=t(n,e,u)}),r},J.max=At,J.memoize=function(n,t){function e(){var r=e.cache,u=t?t.apply(this,arguments):m+arguments[0];return me.call(r,u)?r[u]:r[u]=n.apply(this,arguments)}if(!dt(n))throw new ie;return e.cache={},e},J.merge=function(n){var t=arguments,e=2;if(!wt(n))return n;if("number"!=typeof t[2]&&(e=t.length),3<e&&"function"==typeof t[e-2])var r=tt(t[--e-1],t[e--],2);else 2<e&&"function"==typeof t[e-1]&&(r=t[--e]);for(var t=p(arguments,1,e),u=-1,o=a(),i=a();++u<e;)it(n,t[u],r,o,i);
return l(o),l(i),n},J.min=function(n,t,e){var u=1/0,o=u;if(typeof t!="function"&&e&&e[t]===n&&(t=null),null==t&&Te(n)){e=-1;for(var i=n.length;++e<i;){var a=n[e];a<o&&(o=a)}}else t=null==t&&kt(n)?r:J.createCallback(t,e,3),St(n,function(n,e,r){e=t(n,e,r),e<u&&(u=e,o=n)});return o},J.omit=function(n,t,e){var r={};if(typeof t!="function"){var u=[];g(n,function(n,t){u.push(t)});for(var u=rt(u,ut(arguments,true,false,1)),o=-1,i=u.length;++o<i;){var a=u[o];r[a]=n[a]}}else t=J.createCallback(t,e,3),g(n,function(n,e,u){t(n,e,u)||(r[e]=n)
});return r},J.once=function(n){var t,e;if(!dt(n))throw new ie;return function(){return t?e:(t=true,e=n.apply(this,arguments),n=null,e)}},J.pairs=function(n){for(var t=-1,e=Fe(n),r=e.length,u=Xt(r);++t<r;){var o=e[t];u[t]=[o,n[o]]}return u},J.partial=function(n){return ct(n,16,p(arguments,1))},J.partialRight=function(n){return ct(n,32,null,p(arguments,1))},J.pick=function(n,t,e){var r={};if(typeof t!="function")for(var u=-1,o=ut(arguments,true,false,1),i=wt(n)?o.length:0;++u<i;){var a=o[u];a in n&&(r[a]=n[a])
}else t=J.createCallback(t,e,3),g(n,function(n,e,u){t(n,e,u)&&(r[e]=n)});return r},J.pluck=Ve,J.property=Jt,J.pull=function(n){for(var t=arguments,e=0,r=t.length,u=n?n.length:0;++e<r;)for(var o=-1,i=t[e];++o<u;)n[o]===i&&(de.call(n,o--,1),u--);return n},J.range=function(n,t,e){n=+n||0,e=typeof e=="number"?e:+e||1,null==t&&(t=n,n=0);var r=-1;t=Ie(0,se((t-n)/(e||1)));for(var u=Xt(t);++r<t;)u[r]=n,n+=e;return u},J.reject=function(n,t,e){return t=J.createCallback(t,e,3),Nt(n,function(n,e,r){return!t(n,e,r)
})},J.remove=function(n,t,e){var r=-1,u=n?n.length:0,o=[];for(t=J.createCallback(t,e,3);++r<u;)e=n[r],t(e,r,n)&&(o.push(e),de.call(n,r--,1),u--);return o},J.rest=qt,J.shuffle=Tt,J.sortBy=function(n,t,e){var r=-1,o=Te(t),i=n?n.length:0,p=Xt(typeof i=="number"?i:0);for(o||(t=J.createCallback(t,e,3)),St(n,function(n,e,u){var i=p[++r]=f();o?i.m=Rt(t,function(t){return n[t]}):(i.m=a())[0]=t(n,e,u),i.n=r,i.o=n}),i=p.length,p.sort(u);i--;)n=p[i],p[i]=n.o,o||l(n.m),c(n);return p},J.tap=function(n,t){return t(n),n
},J.throttle=function(n,t,e){var r=true,u=true;if(!dt(n))throw new ie;return false===e?r=false:wt(e)&&(r="leading"in e?e.leading:r,u="trailing"in e?e.trailing:u),L.leading=r,L.maxWait=t,L.trailing=u,Vt(n,t,L)},J.times=function(n,t,e){n=-1<(n=+n)?n:0;var r=-1,u=Xt(n);for(t=tt(t,e,1);++r<n;)u[r]=t(r);return u},J.toArray=function(n){return n&&typeof n.length=="number"?p(n):xt(n)},J.transform=function(n,t,e,r){var u=Te(n);if(null==e)if(u)e=[];else{var o=n&&n.constructor;e=nt(o&&o.prototype)}return t&&(t=J.createCallback(t,r,4),(u?St:h)(n,function(n,r,u){return t(e,n,r,u)
})),e},J.union=function(){return ft(ut(arguments,true,true))},J.uniq=Pt,J.values=xt,J.where=Nt,J.without=function(n){return rt(n,p(arguments,1))},J.wrap=function(n,t){return ct(t,16,[n])},J.xor=function(){for(var n=-1,t=arguments.length;++n<t;){var e=arguments[n];if(Te(e)||yt(e))var r=r?ft(rt(r,e).concat(rt(e,r))):e}return r||[]},J.zip=Kt,J.zipObject=Lt,J.collect=Rt,J.drop=qt,J.each=St,J.eachRight=Et,J.extend=U,J.methods=bt,J.object=Lt,J.select=Nt,J.tail=qt,J.unique=Pt,J.unzip=Kt,Gt(J),J.clone=function(n,t,e,r){return typeof t!="boolean"&&null!=t&&(r=e,e=t,t=false),Z(n,t,typeof e=="function"&&tt(e,r,1))
},J.cloneDeep=function(n,t,e){return Z(n,true,typeof t=="function"&&tt(t,e,1))},J.contains=Ct,J.escape=function(n){return null==n?"":oe(n).replace(ze,pt)},J.every=Ot,J.find=It,J.findIndex=function(n,t,e){var r=-1,u=n?n.length:0;for(t=J.createCallback(t,e,3);++r<u;)if(t(n[r],r,n))return r;return-1},J.findKey=function(n,t,e){var r;return t=J.createCallback(t,e,3),h(n,function(n,e,u){return t(n,e,u)?(r=e,false):void 0}),r},J.findLast=function(n,t,e){var r;return t=J.createCallback(t,e,3),Et(n,function(n,e,u){return t(n,e,u)?(r=n,false):void 0
}),r},J.findLastIndex=function(n,t,e){var r=n?n.length:0;for(t=J.createCallback(t,e,3);r--;)if(t(n[r],r,n))return r;return-1},J.findLastKey=function(n,t,e){var r;return t=J.createCallback(t,e,3),mt(n,function(n,e,u){return t(n,e,u)?(r=e,false):void 0}),r},J.has=function(n,t){return n?me.call(n,t):false},J.identity=Ut,J.indexOf=Wt,J.isArguments=yt,J.isArray=Te,J.isBoolean=function(n){return true===n||false===n||n&&typeof n=="object"&&ce.call(n)==T||false},J.isDate=function(n){return n&&typeof n=="object"&&ce.call(n)==F||false
},J.isElement=function(n){return n&&1===n.nodeType||false},J.isEmpty=function(n){var t=true;if(!n)return t;var e=ce.call(n),r=n.length;return e==$||e==P||e==D||e==q&&typeof r=="number"&&dt(n.splice)?!r:(h(n,function(){return t=false}),t)},J.isEqual=function(n,t,e,r){return ot(n,t,typeof e=="function"&&tt(e,r,2))},J.isFinite=function(n){return Ce(n)&&!Oe(parseFloat(n))},J.isFunction=dt,J.isNaN=function(n){return jt(n)&&n!=+n},J.isNull=function(n){return null===n},J.isNumber=jt,J.isObject=wt,J.isPlainObject=Pe,J.isRegExp=function(n){return n&&typeof n=="object"&&ce.call(n)==z||false
},J.isString=kt,J.isUndefined=function(n){return typeof n=="undefined"},J.lastIndexOf=function(n,t,e){var r=n?n.length:0;for(typeof e=="number"&&(r=(0>e?Ie(0,r+e):Se(e,r-1))+1);r--;)if(n[r]===t)return r;return-1},J.mixin=Gt,J.noConflict=function(){return e._=le,this},J.noop=Ht,J.now=Ue,J.parseInt=Ge,J.random=function(n,t,e){var r=null==n,u=null==t;return null==e&&(typeof n=="boolean"&&u?(e=n,n=1):u||typeof t!="boolean"||(e=t,u=true)),r&&u&&(t=1),n=+n||0,u?(t=n,n=0):t=+t||0,e||n%1||t%1?(e=Re(),Se(n+e*(t-n+parseFloat("1e-"+((e+"").length-1))),t)):at(n,t)
},J.reduce=Dt,J.reduceRight=$t,J.result=function(n,t){if(n){var e=n[t];return dt(e)?n[t]():e}},J.runInContext=s,J.size=function(n){var t=n?n.length:0;return typeof t=="number"?t:Fe(n).length},J.some=Ft,J.sortedIndex=zt,J.template=function(n,t,e){var r=J.templateSettings;n=oe(n||""),e=_({},e,r);var u,o=_({},e.imports,r.imports),r=Fe(o),o=xt(o),a=0,f=e.interpolate||S,l="__p+='",f=ue((e.escape||S).source+"|"+f.source+"|"+(f===N?x:S).source+"|"+(e.evaluate||S).source+"|$","g");n.replace(f,function(t,e,r,o,f,c){return r||(r=o),l+=n.slice(a,c).replace(R,i),e&&(l+="'+__e("+e+")+'"),f&&(u=true,l+="';"+f+";\n__p+='"),r&&(l+="'+((__t=("+r+"))==null?'':__t)+'"),a=c+t.length,t
}),l+="';",f=e=e.variable,f||(e="obj",l="with("+e+"){"+l+"}"),l=(u?l.replace(w,""):l).replace(j,"$1").replace(k,"$1;"),l="function("+e+"){"+(f?"":e+"||("+e+"={});")+"var __t,__p='',__e=_.escape"+(u?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":";")+l+"return __p}";try{var c=ne(r,"return "+l).apply(v,o)}catch(p){throw p.source=l,p}return t?c(t):(c.source=l,c)},J.unescape=function(n){return null==n?"":oe(n).replace(qe,gt)},J.uniqueId=function(n){var t=++y;return oe(null==n?"":n)+t
},J.all=Ot,J.any=Ft,J.detect=It,J.findWhere=It,J.foldl=Dt,J.foldr=$t,J.include=Ct,J.inject=Dt,Gt(function(){var n={};return h(J,function(t,e){J.prototype[e]||(n[e]=t)}),n}(),false),J.first=Bt,J.last=function(n,t,e){var r=0,u=n?n.length:0;if(typeof t!="number"&&null!=t){var o=u;for(t=J.createCallback(t,e,3);o--&&t(n[o],o,n);)r++}else if(r=t,null==r||e)return n?n[u-1]:v;return p(n,Ie(0,u-r))},J.sample=function(n,t,e){return n&&typeof n.length!="number"&&(n=xt(n)),null==t||e?n?n[at(0,n.length-1)]:v:(n=Tt(n),n.length=Se(Ie(0,t),n.length),n)
},J.take=Bt,J.head=Bt,h(J,function(n,t){var e="sample"!==t;J.prototype[t]||(J.prototype[t]=function(t,r){var u=this.__chain__,o=n(this.__wrapped__,t,r);return u||null!=t&&(!r||e&&typeof t=="function")?new Q(o,u):o})}),J.VERSION="2.4.1",J.prototype.chain=function(){return this.__chain__=true,this},J.prototype.toString=function(){return oe(this.__wrapped__)},J.prototype.value=Qt,J.prototype.valueOf=Qt,St(["join","pop","shift"],function(n){var t=ae[n];J.prototype[n]=function(){var n=this.__chain__,e=t.apply(this.__wrapped__,arguments);
return n?new Q(e,n):e}}),St(["push","reverse","sort","unshift"],function(n){var t=ae[n];J.prototype[n]=function(){return t.apply(this.__wrapped__,arguments),this}}),St(["concat","slice","splice"],function(n){var t=ae[n];J.prototype[n]=function(){return new Q(t.apply(this.__wrapped__,arguments),this.__chain__)}}),J}var v,h=[],g=[],y=0,m=+new Date+"",b=75,_=40,d=" \t\x0B\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000",w=/\b__p\+='';/g,j=/\b(__p\+=)''\+/g,k=/(__e\(.*?\)|\b__t\))\+'';/g,x=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,C=/\w*$/,O=/^\s*function[ \n\r\t]+\w/,N=/<%=([\s\S]+?)%>/g,I=RegExp("^["+d+"]*0+(?=.$)"),S=/($^)/,E=/\bthis\b/,R=/['\n\r\t\u2028\u2029\\]/g,A="Array Boolean Date Function Math Number Object RegExp String _ attachEvent clearTimeout isFinite isNaN parseInt setTimeout".split(" "),D="[object Arguments]",$="[object Array]",T="[object Boolean]",F="[object Date]",B="[object Function]",W="[object Number]",q="[object Object]",z="[object RegExp]",P="[object String]",K={};
K[B]=false,K[D]=K[$]=K[T]=K[F]=K[W]=K[q]=K[z]=K[P]=true;var L={leading:false,maxWait:0,trailing:false},M={configurable:false,enumerable:false,value:null,writable:false},V={"boolean":false,"function":true,object:true,number:false,string:false,undefined:false},U={"\\":"\\","'":"'","\n":"n","\r":"r","\t":"t","\u2028":"u2028","\u2029":"u2029"},G=V[typeof window]&&window||this,H=V[typeof exports]&&exports&&!exports.nodeType&&exports,J=V[typeof module]&&module&&!module.nodeType&&module,Q=J&&J.exports===H&&H,X=V[typeof global]&&global;!X||X.global!==X&&X.window!==X||(G=X);
var Y=s();typeof define=="function"&&typeof define.amd=="object"&&define.amd?(G._=Y, define(function(){return Y})):H&&J?Q?(J.exports=Y)._=Y:H._=Y:G._=Y}).call(this);
/* ng-infinite-scroll - v1.0.0 - 2013-02-23 */
var mod;

mod = angular.module('infinite-scroll', []);

mod.directive('infiniteScroll', [
  '$rootScope', '$window', '$timeout', function ($rootScope, $window, $timeout) {
      return {
          link: function (scope, elem, attrs) {
              var checkWhenEnabled, handler, scrollDistance, scrollEnabled;
              $window = angular.element($window);
              scrollDistance = 0;
              if (attrs.infiniteScrollDistance != null) {
                  scope.$watch(attrs.infiniteScrollDistance, function (value) {
                      return scrollDistance = parseInt(value, 10);
                  });
              }
              scrollEnabled = true;
              checkWhenEnabled = false;
              if (attrs.infiniteScrollDisabled != null) {
                  scope.$watch(attrs.infiniteScrollDisabled, function (value) {
                      scrollEnabled = !value;
                      if (scrollEnabled && checkWhenEnabled) {
                          checkWhenEnabled = false;
                          return handler();
                      }
                  });
              }
              handler = function () {
                  var elementBottom, remaining, shouldScroll, windowBottom;
                  windowBottom = $window.height() + $window.scrollTop();
                  elementBottom = elem.offset().top + elem.height();
                  remaining = elementBottom - windowBottom;
                  shouldScroll = remaining <= $window.height() * scrollDistance;
                  if (shouldScroll && scrollEnabled) {
                      if ($rootScope.$$phase) {
                          return scope.$eval(attrs.infiniteScroll);
                      } else {
                          return scope.$apply(attrs.infiniteScroll);
                      }
                  } else if (shouldScroll) {
                      return checkWhenEnabled = true;
                  }
              };
              $window.on('scroll', handler);
              scope.$on('$destroy', function () {
                  return $window.off('scroll', handler);
              });
              return $timeout((function () {
                  if (attrs.infiniteScrollImmediateCheck) {
                      if (scope.$eval(attrs.infiniteScrollImmediateCheck)) {
                          return handler();
                      }
                  } else {
                      return handler();
                  }
              }), 0);
          }
      };
  }
]);

!function (e, t) { "object" == typeof exports ? module.exports = t(require("angular")) : "function" == typeof define && define.amd ? define(["angular"], t) : t(e.angular) }(this, function (angular) {/**
 * AngularJS Google Maps Ver. 1.18.4
 *
 * The MIT License (MIT)
 * 
 * Copyright (c) 2014, 2015, 1016 Allen Kim
 * 
 * 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.
 */
    return angular.module("ngMap", []), function () { "use strict"; var e, t = function (t, n, o, r, a, i, s, p, c) { e = i; var u = this, l = a.startSymbol(), g = a.endSymbol(); u.mapOptions, u.mapEvents, u.eventListeners, u.addObject = function (e, t) { if (u.map) { u.map[e] = u.map[e] || {}; var n = Object.keys(u.map[e]).length; u.map[e][t.id || n] = t, u.map instanceof google.maps.Map && ("infoWindows" != e && t.setMap && t.setMap && t.setMap(u.map), t.centered && t.position && u.map.setCenter(t.position), "markers" == e && u.objectChanged("markers"), "customMarkers" == e && u.objectChanged("customMarkers")) } }, u.deleteObject = function (e, t) { if (t.map) { var n = t.map[e]; for (var o in n) n[o] === t && (google.maps.event.clearInstanceListeners(t), delete n[o]); t.map && t.setMap && t.setMap(null), "markers" == e && u.objectChanged("markers"), "customMarkers" == e && u.objectChanged("customMarkers") } }, u.observeAttrSetObj = function (t, n, o) { if (n.noWatcher) return !1; for (var r = e.getAttrsToObserve(t), a = 0; a < r.length; a++) { var i = r[a]; n.$observe(i, s.observeAndSet(i, o)) } }, u.zoomToIncludeMarkers = function () { if (null != u.map.markers && Object.keys(u.map.markers).length > 0 || null != u.map.customMarkers && Object.keys(u.map.customMarkers).length > 0) { var e = new google.maps.LatLngBounds; for (var t in u.map.markers) e.extend(u.map.markers[t].getPosition()); for (var n in u.map.customMarkers) e.extend(u.map.customMarkers[n].getPosition()); u.mapOptions.maximumZoom && (u.enableMaximumZoomCheck = !0), u.map.fitBounds(e) } }, u.objectChanged = function (e) { !u.map || "markers" != e && "customMarkers" != e || "auto" != u.map.zoomToIncludeMarkers || u.zoomToIncludeMarkers() }, u.initializeMap = function () { var a = u.mapOptions, i = u.mapEvents, m = u.map; if (u.map = p.getMapInstance(n[0]), s.setStyle(n[0]), m) { var f = e.filter(o), v = e.getOptions(f), y = e.getControlOptions(f); a = angular.extend(v, y); for (var h in m) { var b = m[h]; if ("object" == typeof b) for (var M in b) u.addObject(h, b[M]) } u.map.showInfoWindow = u.showInfoWindow, u.map.hideInfoWindow = u.hideInfoWindow } a.zoom = a.zoom && !isNaN(a.zoom) ? +a.zoom : 15; var O = a.center, w = new RegExp(c(l) + ".*" + c(g)); if (!a.center || "string" == typeof O && O.match(w)) a.center = new google.maps.LatLng(0, 0); else if ("string" == typeof O && O.match(/^[0-9.-]*,[0-9.-]*$/)) { var L = parseFloat(O.split(",")[0]), k = parseFloat(O.split(",")[1]); a.center = new google.maps.LatLng(L, k) } else if (!(O instanceof google.maps.LatLng)) { var $ = a.center; delete a.center, s.getGeoLocation($, a.geoLocationOptions).then(function (e) { u.map.setCenter(e); var n = a.geoCallback; n && r(n)(t) }, function () { a.geoFallbackCenter && u.map.setCenter(a.geoFallbackCenter) }) } u.map.setOptions(a); for (var C in i) { var j = i[C], A = google.maps.event.addListener(u.map, C, j); u.eventListeners[C] = A } u.observeAttrSetObj(d, o, u.map), u.singleInfoWindow = a.singleInfoWindow, google.maps.event.trigger(u.map, "resize"), google.maps.event.addListenerOnce(u.map, "idle", function () { s.addMap(u), a.zoomToIncludeMarkers && u.zoomToIncludeMarkers(), t.map = u.map, t.$emit("mapInitialized", u.map), o.mapInitialized && r(o.mapInitialized)(t, { map: u.map }) }), a.zoomToIncludeMarkers && a.maximumZoom && google.maps.event.addListener(u.map, "zoom_changed", function () { 1 == u.enableMaximumZoomCheck && (u.enableMaximumZoomCheck = !1, google.maps.event.addListenerOnce(u.map, "bounds_changed", function () { u.map.setZoom(Math.min(a.maximumZoom, u.map.getZoom())) })) }) }, t.google = google; var d = e.orgAttributes(n), m = e.filter(o), f = e.getOptions(m, { scope: t }), v = e.getControlOptions(m), y = angular.extend(f, v), h = e.getEvents(t, m); if (Object.keys(h).length && void 0, u.mapOptions = y, u.mapEvents = h, u.eventListeners = {}, f.lazyInit) { if (o.id && 0 === o.id.indexOf(l, 0) && -1 !== o.id.indexOf(g, o.id.length - g.length)) var b = o.id.slice(2, -2), M = r(b)(t); else var M = o.id; u.map = { id: M }, s.addMap(u) } else u.initializeMap(); f.triggerResize && google.maps.event.trigger(u.map, "resize"), n.bind("$destroy", function () { p.returnMapInstance(u.map), s.deleteMap(u) }) }; t.$inject = ["$scope", "$element", "$attrs", "$parse", "$interpolate", "Attr2MapOptions", "NgMap", "NgMapPool", "escapeRegexpFilter"], angular.module("ngMap").controller("__MapController", t) }(), function () { "use strict"; var e, t = function (t, o, r, a) { a = a[0] || a[1]; var i = e.orgAttributes(o), s = e.filter(r), p = e.getOptions(s, { scope: t }), c = e.getEvents(t, s), u = n(p, c); a.addObject("bicyclingLayers", u), a.observeAttrSetObj(i, r, u), o.bind("$destroy", function () { a.deleteObject("bicyclingLayers", u) }) }, n = function (e, t) { var n = new google.maps.BicyclingLayer(e); for (var o in t) google.maps.event.addListener(n, o, t[o]); return n }, o = function (n) { return e = n, { restrict: "E", require: ["?^map", "?^ngMap"], link: t } }; o.$inject = ["Attr2MapOptions"], angular.module("ngMap").directive("bicyclingLayer", o) }(), function () { "use strict"; var e, t, n = function (t, n, o, r, a) { r = r[0] || r[1]; { var i = e.filter(o), s = e.getOptions(i, { scope: t }), p = e.getEvents(t, i), c = t.$new(), u = n[0].parentElement.removeChild(n[0]); a(c, function (e) { n.empty(), n.append(e), n.on("$destroy", function () { c.$destroy() }) }) } for (var l in p) google.maps.event.addDomListener(u, l, p[l]); r.addObject("customControls", u); var g = s.position; r.map.controls[google.maps.ControlPosition[g]].push(u), n.bind("$destroy", function () { r.deleteObject("customControls", u) }) }, o = function (o, r) { return e = o, t = r, { restrict: "E", require: ["?^map", "?^ngMap"], link: n, transclude: !0 } }; o.$inject = ["Attr2MapOptions", "NgMap"], angular.module("ngMap").directive("customControl", o) }(), function () { "use strict"; var e, t, n, o, r = function () { for (var e = "transform WebkitTransform MozTransform OTransform msTransform".split(" "), t = document.createElement("div"), n = 0; n < e.length; n++) if (t && void 0 !== t.style[e[n]]) return e[n]; return !1 }(), a = function (e) { e = e || {}, this.el = document.createElement("div"), this.el.style.display = "block", this.el.style.visibility = "hidden", this.visible = !0; for (var t in e) this[t] = e[t] }, i = function () { a.prototype = new google.maps.OverlayView, a.prototype.setContent = function (e, t) { this.el.innerHTML = e, this.el.style.position = "absolute", this.el.style.top = 0, this.el.style.left = 0, t && n(angular.element(this.el).contents())(t) }, a.prototype.getDraggable = function () { return this.draggable }, a.prototype.setDraggable = function (e) { this.draggable = e }, a.prototype.getPosition = function () { return this.position }, a.prototype.setPosition = function (e) { e && (this.position = e); var n = this; if (this.getProjection() && "function" == typeof this.position.lng) { var o = function () { if (n.getProjection()) { var e = n.getProjection().fromLatLngToDivPixel(n.position), t = Math.round(e.x - n.el.offsetWidth / 2), o = Math.round(e.y - n.el.offsetHeight - 10); r ? n.el.style[r] = "translate(" + t + "px, " + o + "px)" : (n.el.style.left = t + "px", n.el.style.top = o + "px"), n.el.style.visibility = "visible" } }; n.el.offsetWidth && n.el.offsetHeight ? o() : t(o, 300) } }, a.prototype.setZIndex = function (e) { void 0 !== e && (this.zIndex !== e && (this.zIndex = e), this.el.style.zIndex !== this.zIndex && (this.el.style.zIndex = this.zIndex)) }, a.prototype.getVisible = function () { return this.visible }, a.prototype.setVisible = function (e) { "none" === this.el.style.display && e ? this.el.style.display = "block" : "none" === this.el.style.display || e || (this.el.style.display = "none"), this.visible = e }, a.prototype.addClass = function (e) { var t = this.el.className.trim().split(" "); -1 == t.indexOf(e) && t.push(e), this.el.className = t.join(" ") }, a.prototype.removeClass = function (e) { var t = this.el.className.split(" "), n = t.indexOf(e); n > -1 && t.splice(n, 1), this.el.className = t.join(" ") }, a.prototype.onAdd = function () { this.getPanes().overlayMouseTarget.appendChild(this.el) }, a.prototype.draw = function () { this.setPosition(), this.setZIndex(this.zIndex), this.setVisible(this.visible) }, a.prototype.onRemove = function () { this.el.parentNode.removeChild(this.el) } }, s = function (t, n) { return function (r, i, s, p) { p = p[0] || p[1]; var c = e.orgAttributes(i), u = e.filter(s), l = e.getOptions(u, { scope: r }), g = e.getEvents(r, u); i[0].style.display = "none"; var d = new a(l); setTimeout(function () { r.$watch("[" + n.join(",") + "]", function () { d.setContent(t, r) }, !0), d.setContent(i[0].innerHTML, r); var e = i[0].firstElementChild && (i[0].firstElementChild.className || ""); d.class && (e += " " + d.class), d.addClass("custom-marker"), e && d.addClass(e), l.position instanceof google.maps.LatLng || o.getGeoLocation(l.position).then(function (e) { d.setPosition(e) }) }); for (var m in g) google.maps.event.addDomListener(d.el, m, g[m]); p.addObject("customMarkers", d), p.observeAttrSetObj(c, s, d), i.bind("$destroy", function () { p.deleteObject("customMarkers", d) }) } }, p = function (r, a, p, c, u, l) { e = c, t = r, n = a, o = u; var g = p.startSymbol(), d = p.endSymbol(), m = new RegExp(l(g) + "([^" + d.substring(0, 1) + "]+)" + l(d), "g"); return { restrict: "E", require: ["?^map", "?^ngMap"], compile: function (e) { i(), e[0].style.display = "none"; var t = e.html(), n = t.match(m), o = []; return (n || []).forEach(function (e) { var t = e.replace(g, "").replace(d, ""); -1 == e.indexOf("::") && -1 == e.indexOf("this.") && -1 == o.indexOf(t) && o.push(e.replace(g, "").replace(d, "")) }), s(t, o) } } }; p.$inject = ["$timeout", "$compile", "$interpolate", "Attr2MapOptions", "NgMap", "escapeRegexpFilter"], angular.module("ngMap").directive("customMarker", p) }(), function () { "use strict"; var e, t, n, o, r, a = 20, i = function (e, t) { e.panel && (e.panel = document.getElementById(e.panel) || document.querySelector(e.panel)); var n = new google.maps.DirectionsRenderer(e); for (var o in t) google.maps.event.addListener(n, o, t[o]); return n }, s = function (e, i) { var s = new google.maps.DirectionsService, p = i; p.travelMode = p.travelMode || "DRIVING"; var c = ["origin", "destination", "travelMode", "transitOptions", "unitSystem", "durationInTraffic", "waypoints", "optimizeWaypoints", "provideRouteAlternatives", "avoidHighways", "avoidTolls", "region"]; if (p) for (var u in p) p.hasOwnProperty(u) && -1 === c.indexOf(u) && delete p[u]; p.waypoints && (Array.isArray(p.waypoints) || delete p.waypoints); var l = function (n) { if (o && n) if (r) for (var i in n) n.hasOwnProperty(i) && (r[i] = n[i]); else r = n; else o = t(function () { r || (r = n), s.route(r, function (t, n) { n == google.maps.DirectionsStatus.OK && (e.setDirections(t), r = void 0) }), t.cancel(o), o = void 0 }, a) }; p && p.origin && p.destination && ("current-location" == p.origin ? n.getCurrentPosition().then(function (e) { p.origin = new google.maps.LatLng(e.coords.latitude, e.coords.longitude), l(p) }) : "current-location" == p.destination ? n.getCurrentPosition().then(function (e) { p.destination = new google.maps.LatLng(e.coords.latitude, e.coords.longitude), l(p) }) : l(p)) }, p = function (o, r, a, p) { var c = o; e = p, t = r, n = a; var u = function (n, o, r, a) { a = a[0] || a[1]; var p = c.orgAttributes(o), u = c.filter(r), l = c.getOptions(u, { scope: n }), g = c.getEvents(n, u), d = c.getAttrsToObserve(p), d = []; u.noWatcher || (d = c.getAttrsToObserve(p)); var m = i(l, g); a.addObject("directionsRenderers", m), d.forEach(function (e) { !function (e) { r.$observe(e, function (n) { if ("panel" == e) t(function () { var e = document.getElementById(n) || document.querySelector(n); e && m.setPanel(e) }); else if (l[e] !== n) { var o = c.toOptionValue(n, { key: e }); l[e] = o, s(m, l) } }) }(e) }), e.getMap().then(function () { s(m, l) }), o.bind("$destroy", function () { a.deleteObject("directionsRenderers", m) }) }; return { restrict: "E", require: ["?^map", "?^ngMap"], link: u } }; p.$inject = ["Attr2MapOptions", "$timeout", "NavigatorGeolocation", "NgMap"], angular.module("ngMap").directive("directions", p) }(), function () { "use strict"; angular.module("ngMap").directive("drawingManager", ["Attr2MapOptions", function (e) { var t = e; return { restrict: "E", require: ["?^map", "?^ngMap"], link: function (e, n, o, r) { r = r[0] || r[1]; var a = t.filter(o), i = t.getOptions(a, { scope: e }), s = t.getControlOptions(a), p = t.getEvents(e, a), c = new google.maps.drawing.DrawingManager({ drawingMode: i.drawingmode, drawingControl: i.drawingcontrol, drawingControlOptions: s.drawingControlOptions, circleOptions: i.circleoptions, markerOptions: i.markeroptions, polygonOptions: i.polygonoptions, polylineOptions: i.polylineoptions, rectangleOptions: i.rectangleoptions }); o.$observe("drawingControlOptions", function (e) { c.drawingControlOptions = t.getControlOptions({ drawingControlOptions: e }).drawingControlOptions, c.setDrawingMode(null), c.setMap(r.map) }); for (var u in p) google.maps.event.addListener(c, u, p[u]); r.addObject("mapDrawingManager", c), n.bind("$destroy", function () { r.deleteObject("mapDrawingManager", c) }) } } }]) }(), function () { "use strict"; angular.module("ngMap").directive("dynamicMapsEngineLayer", ["Attr2MapOptions", function (e) { var t = e, n = function (e, t) { var n = new google.maps.visualization.DynamicMapsEngineLayer(e); for (var o in t) google.maps.event.addListener(n, o, t[o]); return n }; return { restrict: "E", require: ["?^map", "?^ngMap"], link: function (e, o, r, a) { a = a[0] || a[1]; var i = t.filter(r), s = t.getOptions(i, { scope: e }), p = t.getEvents(e, i, p), c = n(s, p); a.addObject("mapsEngineLayers", c) } } }]) }(), function () { "use strict"; angular.module("ngMap").directive("fusionTablesLayer", ["Attr2MapOptions", function (e) { var t = e, n = function (e, t) { var n = new google.maps.FusionTablesLayer(e); for (var o in t) google.maps.event.addListener(n, o, t[o]); return n }; return { restrict: "E", require: ["?^map", "?^ngMap"], link: function (e, o, r, a) { a = a[0] || a[1]; var i = t.filter(r), s = t.getOptions(i, { scope: e }), p = t.getEvents(e, i, p), c = n(s, p); a.addObject("fusionTablesLayers", c), o.bind("$destroy", function () { a.deleteObject("fusionTablesLayers", c) }) } } }]) }(), function () { "use strict"; angular.module("ngMap").directive("heatmapLayer", ["Attr2MapOptions", "$window", function (e, t) { var n = e; return { restrict: "E", require: ["?^map", "?^ngMap"], link: function (e, o, r, a) { function i(e, t) { return e.split(".").reduce(function (e, t) { return e[t] }, t || this) } a = a[0] || a[1]; var s = n.filter(r), p = n.getOptions(s, { scope: e }); if (p.data = t[r.data] || i(r.data, e), !(p.data instanceof Array)) throw "invalid heatmap data"; p.data = new google.maps.MVCArray(p.data); { var c = new google.maps.visualization.HeatmapLayer(p); n.getEvents(e, s) } a.addObject("heatmapLayers", c) } } }]) }(), function () { "use strict"; var e = function (e, t, n, o, r, a, i) { var s = e, p = function (e, a, i) { var s; !e.position || e.position instanceof google.maps.LatLng || delete e.position, s = new google.maps.InfoWindow(e); for (var p in a) p && google.maps.event.addListener(s, p, a[p]); var c = n(function (e) { angular.isString(i) ? o(i).then(function (t) { e(angular.element(t).wrap("<div>").parent()) }, function (e) { throw "info-window template request failed: " + e }) : e(i) }).then(function (e) { var t = e.html().trim(); if (1 != angular.element(t).length) throw "info-window working as a template must have a container"; s.__template = t.replace(/\s?ng-non-bindable[='"]+/, "") }); return s.__open = function (e, n, o) { c.then(function () { r(function () { o && (n.anchor = o); var r = t(s.__template)(n); s.setContent(r[0]), n.$apply(), o && o.getPosition ? s.open(e, o) : o && o instanceof google.maps.LatLng ? (s.open(e), s.setPosition(o)) : s.open(e); var a = s.content.parentElement.parentElement.parentElement; a.className = "ng-map-info-window" }) }) }, s }, c = function (e, t, n, o) { o = o[0] || o[1], t.css("display", "none"); var r, c = s.orgAttributes(t), u = s.filter(n), l = s.getOptions(u, { scope: e }), g = s.getEvents(e, u), d = p(l, g, l.template || t); !l.position || l.position instanceof google.maps.LatLng || (r = l.position), r && i.getGeoLocation(r).then(function (t) { d.setPosition(t), d.__open(o.map, e, t); var r = n.geoCallback; r && a(r)(e) }), o.addObject("infoWindows", d), o.observeAttrSetObj(c, n, d), o.showInfoWindow = o.map.showInfoWindow = o.showInfoWindow || function (t, n, r) { var a = "string" == typeof t ? t : n, i = "string" == typeof t ? n : r; if ("string" == typeof i) if ("undefined" != typeof o.map.markers && "undefined" != typeof o.map.markers[i]) i = o.map.markers[i]; else { if ("undefined" == typeof o.map.customMarkers || "undefined" == typeof o.map.customMarkers[i]) throw new Error("Cant open info window for id " + i + ". Marker or CustomMarker is not defined"); i = o.map.customMarkers[i] } var s = o.map.infoWindows[a], p = i ? i : this.getPosition ? this : null; s.__open(o.map, e, p), o.singleInfoWindow && (o.lastInfoWindow && e.hideInfoWindow(o.lastInfoWindow), o.lastInfoWindow = a) }, o.hideInfoWindow = o.map.hideInfoWindow = o.hideInfoWindow || function (e, t) { var n = "string" == typeof e ? e : t, r = o.map.infoWindows[n]; r.close() }, e.showInfoWindow = o.map.showInfoWindow, e.hideInfoWindow = o.map.hideInfoWindow; var m = d.mapId ? { id: d.mapId } : 0; i.getMap(m).then(function (t) { if (d.visible && d.__open(t, e), d.visibleOnMarker) { var n = d.visibleOnMarker; d.__open(t, e, t.markers[n]) } }) }; return { restrict: "E", require: ["?^map", "?^ngMap"], link: c } }; e.$inject = ["Attr2MapOptions", "$compile", "$q", "$templateRequest", "$timeout", "$parse", "NgMap"], angular.module("ngMap").directive("infoWindow", e) }(), function () { "use strict"; angular.module("ngMap").directive("kmlLayer", ["Attr2MapOptions", function (e) { var t = e, n = function (e, t) { var n = new google.maps.KmlLayer(e); for (var o in t) google.maps.event.addListener(n, o, t[o]); return n }; return { restrict: "E", require: ["?^map", "?^ngMap"], link: function (e, o, r, a) { a = a[0] || a[1]; var i = t.orgAttributes(o), s = t.filter(r), p = t.getOptions(s, { scope: e }), c = t.getEvents(e, s), u = n(p, c); a.addObject("kmlLayers", u), a.observeAttrSetObj(i, r, u), o.bind("$destroy", function () { a.deleteObject("kmlLayers", u) }) } } }]) }(), function () { "use strict"; angular.module("ngMap").directive("mapData", ["Attr2MapOptions", "NgMap", function (e, t) { var n = e; return { restrict: "E", require: ["?^map", "?^ngMap"], link: function (e, o, r, a) { a = a[0] || a[1]; var i = n.filter(r), s = n.getOptions(i, { scope: e }), p = n.getEvents(e, i, p); t.getMap(a.map.id).then(function (t) { for (var n in s) { var o = s[n]; "function" == typeof e[o] ? t.data[n](e[o]) : t.data[n](o) } for (var r in p) t.data.addListener(r, p[r]) }) } } }]) }(), function () { "use strict"; var e, t, n, o = [], r = [], a = function (n, a, i) { var s = i.mapLazyLoadParams || i.mapLazyLoad; if (void 0 === window.google || void 0 === window.google.maps) { r.push({ scope: n, element: a, savedHtml: o[r.length] }), window.lazyLoadCallback = function () { e(function () { r.forEach(function (e) { e.element.html(e.savedHtml), t(e.element.contents())(e.scope) }) }, 100) }; var p = document.createElement("script"); p.src = s + (s.indexOf("?") > -1 ? "&" : "?") + "callback=lazyLoadCallback", document.querySelector('script[src="' + p.src + '"]') || document.body.appendChild(p) } else a.html(o), t(a.contents())(n) }, i = function (e, t) { return !t.mapLazyLoad && void 0, o.push(e.html()), n = t.mapLazyLoad, void 0 !== window.google && void 0 !== window.google.maps ? !1 : (e.html(""), { pre: a }) }, s = function (n, o) { return t = n, e = o, { compile: i } }; s.$inject = ["$compile", "$timeout"], angular.module("ngMap").directive("mapLazyLoad", s) }(), function () { "use strict"; angular.module("ngMap").directive("mapType", ["$parse", "NgMap", function (e, t) { return { restrict: "E", require: ["?^map", "?^ngMap"], link: function (n, o, r, a) { a = a[0] || a[1]; var i, s = r.name; if (!s) throw "invalid map-type name"; if (i = e(r.object)(n), !i) throw "invalid map-type object"; t.getMap().then(function (e) { e.mapTypes.set(s, i) }), a.addObject("mapTypes", i) } } }]) }(), function () { "use strict"; var e = function () { return { restrict: "AE", controller: "__MapController", controllerAs: "ngmap" } }; angular.module("ngMap").directive("map", [e]), angular.module("ngMap").directive("ngMap", [e]) }(), function () { "use strict"; angular.module("ngMap").directive("mapsEngineLayer", ["Attr2MapOptions", function (e) { var t = e, n = function (e, t) { var n = new google.maps.visualization.MapsEngineLayer(e); for (var o in t) google.maps.event.addListener(n, o, t[o]); return n }; return { restrict: "E", require: ["?^map", "?^ngMap"], link: function (e, o, r, a) { a = a[0] || a[1]; var i = t.filter(r), s = t.getOptions(i, { scope: e }), p = t.getEvents(e, i, p), c = n(s, p); a.addObject("mapsEngineLayers", c) } } }]) }(), function () { "use strict"; var e, t, n, o = function (e, t) { var o; if (n.defaultOptions.marker) for (var r in n.defaultOptions.marker) "undefined" == typeof e[r] && (e[r] = n.defaultOptions.marker[r]); e.position instanceof google.maps.LatLng || (e.position = new google.maps.LatLng(0, 0)), o = new google.maps.Marker(e), Object.keys(t).length > 0; for (var a in t) a && google.maps.event.addListener(o, a, t[a]); return o }, r = function (r, a, i, s) { s = s[0] || s[1]; var p, c = e.orgAttributes(a), u = e.filter(i), l = e.getOptions(u, r, { scope: r }), g = e.getEvents(r, u); l.position instanceof google.maps.LatLng || (p = l.position); var d = o(l, g); s.addObject("markers", d), p && n.getGeoLocation(p).then(function (e) { d.setPosition(e), l.centered && d.map.setCenter(e); var n = i.geoCallback; n && t(n)(r) }), s.observeAttrSetObj(c, i, d), a.bind("$destroy", function () { s.deleteObject("markers", d) }) }, a = function (o, a, i) { return e = o, t = a, n = i, { restrict: "E", require: ["^?map", "?^ngMap"], link: r } }; a.$inject = ["Attr2MapOptions", "$parse", "NgMap"], angular.module("ngMap").directive("marker", a) }(), function () { "use strict"; angular.module("ngMap").directive("overlayMapType", ["NgMap", function (e) { return { restrict: "E", require: ["?^map", "?^ngMap"], link: function (t, n, o, r) { r = r[0] || r[1]; var a = o.initMethod || "insertAt", i = t[o.object]; e.getMap().then(function (e) { if ("insertAt" == a) { var t = parseInt(o.index, 10); e.overlayMapTypes.insertAt(t, i) } else "push" == a && e.overlayMapTypes.push(i) }), r.addObject("overlayMapTypes", i) } } }]) }(), function () { "use strict"; var e = function (e, t) { var n = e, o = function (e, o, r, a) { if ("false" === r.placesAutoComplete) return !1; var i = n.filter(r), s = n.getOptions(i, { scope: e }), p = n.getEvents(e, i), c = new google.maps.places.Autocomplete(o[0], s); c.setOptions({ strictBounds: s.strictBounds === !0 }); for (var u in p) google.maps.event.addListener(c, u, p[u]); var l = function () { t(function () { a && a.$setViewValue(o.val()) }, 100) }; google.maps.event.addListener(c, "place_changed", l), o[0].addEventListener("change", l), r.$observe("rectBounds", function (e) { if (e) { var t = n.toOptionValue(e, { key: "rectBounds" }); c.setBounds(new google.maps.LatLngBounds(new google.maps.LatLng(t.south_west.lat, t.south_west.lng), new google.maps.LatLng(t.north_east.lat, t.north_east.lng))) } }), r.$observe("circleBounds", function (e) { if (e) { var t = n.toOptionValue(e, { key: "circleBounds" }), o = new google.maps.Circle(t); c.setBounds(o.getBounds()) } }), r.$observe("types", function (e) { if (e) { var t = n.toOptionValue(e, { key: "types" }); c.setTypes(t) } }), r.$observe("componentRestrictions", function (t) { t && c.setComponentRestrictions(e.$eval(t)) }) }; return { restrict: "A", require: "?ngModel", link: o } }; e.$inject = ["Attr2MapOptions", "$timeout"], angular.module("ngMap").directive("placesAutoComplete", e) }(), function () { "use strict"; var e = function (e, t) { var n, o = e.name; switch (delete e.name, o) { case "circle": e.center instanceof google.maps.LatLng || (e.center = new google.maps.LatLng(0, 0)), n = new google.maps.Circle(e); break; case "polygon": n = new google.maps.Polygon(e); break; case "polyline": n = new google.maps.Polyline(e); break; case "rectangle": n = new google.maps.Rectangle(e); break; case "groundOverlay": case "image": var r = e.url, a = { opacity: e.opacity, clickable: e.clickable, id: e.id }; n = new google.maps.GroundOverlay(r, e.bounds, a) } for (var i in t) t[i] && google.maps.event.addListener(n, i, t[i]); return n }, t = function (t, n, o) { var r = t, a = function (t, a, i, s) { s = s[0] || s[1]; var p, c, u = r.orgAttributes(a), l = r.filter(i), g = r.getOptions(l, { scope: t }), d = r.getEvents(t, l); c = g.name, g.center instanceof google.maps.LatLng || (p = g.center); var m = e(g, d); s.addObject("shapes", m), p && "circle" == c && o.getGeoLocation(p).then(function (e) { m.setCenter(e), m.centered && m.map.setCenter(e); var o = i.geoCallback; o && n(o)(t) }), s.observeAttrSetObj(u, i, m), a.bind("$destroy", function () { s.deleteObject("shapes", m) }) }; return { restrict: "E", require: ["?^map", "?^ngMap"], link: a } }; t.$inject = ["Attr2MapOptions", "$parse", "NgMap"], angular.module("ngMap").directive("shape", t) }(), function () { "use strict"; var e = function (e, t) { var n = e, o = function (e, t, n) { var o, r; t.container && (r = document.getElementById(t.container), r = r || document.querySelector(t.container)), r ? o = new google.maps.StreetViewPanorama(r, t) : (o = e.getStreetView(), o.setOptions(t)); for (var a in n) a && google.maps.event.addListener(o, a, n[a]); return o }, r = function (e, r, a) { var i = n.filter(a), s = n.getOptions(i, { scope: e }), p = n.getControlOptions(i), c = angular.extend(s, p), u = n.getEvents(e, i); t.getMap().then(function (e) { var t = o(e, c, u); e.setStreetView(t), !t.getPosition() && t.setPosition(e.getCenter()), google.maps.event.addListener(t, "position_changed", function () { t.getPosition() !== e.getCenter() && e.setCenter(t.getPosition()) }); var n = google.maps.event.addListener(e, "center_changed", function () { t.setPosition(e.getCenter()), google.maps.event.removeListener(n) }) }) }; return { restrict: "E", require: ["?^map", "?^ngMap"], link: r } }; e.$inject = ["Attr2MapOptions", "NgMap"], angular.module("ngMap").directive("streetViewPanorama", e) }(), function () { "use strict"; angular.module("ngMap").directive("trafficLayer", ["Attr2MapOptions", function (e) { var t = e, n = function (e, t) { var n = new google.maps.TrafficLayer(e); for (var o in t) google.maps.event.addListener(n, o, t[o]); return n }; return { restrict: "E", require: ["?^map", "?^ngMap"], link: function (e, o, r, a) { a = a[0] || a[1]; var i = t.orgAttributes(o), s = t.filter(r), p = t.getOptions(s, { scope: e }), c = t.getEvents(e, s), u = n(p, c); a.addObject("trafficLayers", u), a.observeAttrSetObj(i, r, u), o.bind("$destroy", function () { a.deleteObject("trafficLayers", u) }) } } }]) }(), function () { "use strict"; angular.module("ngMap").directive("transitLayer", ["Attr2MapOptions", function (e) { var t = e, n = function (e, t) { var n = new google.maps.TransitLayer(e); for (var o in t) google.maps.event.addListener(n, o, t[o]); return n }; return { restrict: "E", require: ["?^map", "?^ngMap"], link: function (e, o, r, a) { a = a[0] || a[1]; var i = t.orgAttributes(o), s = t.filter(r), p = t.getOptions(s, { scope: e }), c = t.getEvents(e, s), u = n(p, c); a.addObject("transitLayers", u), a.observeAttrSetObj(i, r, u), o.bind("$destroy", function () { a.deleteObject("transitLayers", u) }) } } }]) }(), function () { "use strict"; var e = /([\:\-\_]+(.))/g, t = /^moz([A-Z])/, n = function () { return function (n) { return n.replace(e, function (e, t, n, o) { return o ? n.toUpperCase() : n }).replace(t, "Moz$1") } }; angular.module("ngMap").filter("camelCase", n) }(), function () { "use strict"; var e = function () { return function (e) { return e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") } }; angular.module("ngMap").filter("escapeRegexp", e) }(), function () { "use strict"; var e = function () { return function (e) { try { return JSON.parse(e), e } catch (t) { return e.replace(/([\$\w]+)\s*:/g, function (e, t) { return '"' + t + '":' }).replace(/'([^']+)'/g, function (e, t) { return '"' + t + '"' }).replace(/''/g, '""') } } }; angular.module("ngMap").filter("jsonize", e) }(), function () { "use strict"; var isoDateRE = /^(\d{4}\-\d\d\-\d\d([tT][\d:\.]*)?)([zZ]|([+\-])(\d\d):?(\d\d))?$/, Attr2MapOptions = function ($parse, $timeout, $log, $interpolate, NavigatorGeolocation, GeoCoder, camelCaseFilter, jsonizeFilter, escapeRegExp) { var exprStartSymbol = $interpolate.startSymbol(), exprEndSymbol = $interpolate.endSymbol(), orgAttributes = function (e) { e.length > 0 && (e = e[0]); for (var t = {}, n = 0; n < e.attributes.length; n++) { var o = e.attributes[n]; t[o.name] = o.value } return t }, getJSON = function (e) { var t = /^[\+\-]?[0-9\.]+,[ ]*\ ?[\+\-]?[0-9\.]+$/; return e.match(t) && (e = "[" + e + "]"), JSON.parse(jsonizeFilter(e)) }, getLatLng = function (e) { var t = e; if (e[0].constructor == Array) if (e[0][0].constructor == Array && 2 == e[0][0].length || e[0][0].constructor == Object) { for (var n, o = [], r = 0; r < e.length; r++) n = e[r].map(function (e) { return new google.maps.LatLng(e[0], e[1]) }), o.push(n); t = o } else t = e.map(function (e) { return new google.maps.LatLng(e[0], e[1]) }); else !isNaN(parseFloat(e[0])) && isFinite(e[0]) && (t = new google.maps.LatLng(t[0], t[1])); return t }, toOptionValue = function (input, options) { var output; try { output = getNumber(input) } catch (err) { try { var output = getJSON(input); if (output instanceof Array) output = output[0].constructor == Object ? output : output[0] instanceof Array ? output[0][0].constructor == Object ? output : getLatLng(output) : getLatLng(output); else if (output === Object(output)) { var newOptions = options; newOptions.doNotConverStringToNumber = !0, output = getOptions(output, newOptions) } } catch (err2) { if (input.match(/^[A-Z][a-zA-Z0-9]+\(.*\)$/)) try { var exp = "new google.maps." + input; output = eval(exp) } catch (e) { output = input } else if (input.match(/^([A-Z][a-zA-Z0-9]+)\.([A-Z]+)$/)) try { var matches = input.match(/^([A-Z][a-zA-Z0-9]+)\.([A-Z]+)$/); output = google.maps[matches[1]][matches[2]] } catch (e) { output = input } else if (input.match(/^[A-Z]+$/)) try { var capitalizedKey = options.key.charAt(0).toUpperCase() + options.key.slice(1); options.key.match(/temperatureUnit|windSpeedUnit|labelColor/) ? (capitalizedKey = capitalizedKey.replace(/s$/, ""), output = google.maps.weather[capitalizedKey][input]) : output = google.maps[capitalizedKey][input] } catch (e) { output = input } else if (input.match(isoDateRE)) try { output = new Date(input) } catch (e) { output = input } else if (input.match(new RegExp("^" + escapeRegExp(exprStartSymbol))) && options.scope) try { var expr = input.replace(new RegExp(escapeRegExp(exprStartSymbol)), "").replace(new RegExp(escapeRegExp(exprEndSymbol), "g"), ""); output = options.scope.$eval(expr) } catch (err) { output = input } else output = input } } if (("center" == options.key || "position" == options.key) && output instanceof Array && (output = new google.maps.LatLng(output[0], output[1])), "bounds" == options.key && output instanceof Array && (output = new google.maps.LatLngBounds(output[0], output[1])), "icons" == options.key && output instanceof Array) for (var i = 0; i < output.length; i++) { var el = output[i]; el.icon.path.match(/^[A-Z_]+$/) && (el.icon.path = google.maps.SymbolPath[el.icon.path]) } if ("icon" == options.key && output instanceof Object) { ("" + output.path).match(/^[A-Z_]+$/) && (output.path = google.maps.SymbolPath[output.path]); for (var key in output) { var arr = output[key]; "anchor" == key || "origin" == key || "labelOrigin" == key ? output[key] = new google.maps.Point(arr[0], arr[1]) : ("size" == key || "scaledSize" == key) && (output[key] = new google.maps.Size(arr[0], arr[1])) } } return output }, getAttrsToObserve = function (e) { var t = [], n = new RegExp(escapeRegExp(exprStartSymbol) + ".*" + escapeRegExp(exprEndSymbol), "g"); if (!e.noWatcher) for (var o in e) { var r = e[o]; r && r.match(n) && t.push(camelCaseFilter(o)) } return t }, filter = function (e) { var t = {}; for (var n in e) n.match(/^\$/) || n.match(/^ng[A-Z]/) || (t[n] = e[n]); return t }, getOptions = function (e, t) { t = t || {}; var n = {}; for (var o in e) if (e[o] || 0 === e[o]) { if (o.match(/^on[A-Z]/)) continue; if (o.match(/ControlOptions$/)) continue; n[o] = "string" != typeof e[o] ? e[o] : t.doNotConverStringToNumber && e[o].match(/^[0-9]+$/) ? e[o] : toOptionValue(e[o], { key: o, scope: t.scope }) } return n }, getEvents = function (e, t) { var n = {}, o = function (e) { return "_" + e.toLowerCase() }, r = function (t) { var n = t.match(/([^\(]+)\(([^\)]*)\)/), o = n[1], r = n[2].replace(/event[ ,]*/, ""), a = $parse("[" + r + "]"); return function (t) { function n(e, t) { return e[t] } var r = a(e), i = o.split(".").reduce(n, e); i && i.apply(this, [t].concat(r)), $timeout(function () { e.$apply() }) } }; for (var a in t) if (t[a]) { if (!a.match(/^on[A-Z]/)) continue; var i = a.replace(/^on/, ""); i = i.charAt(0).toLowerCase() + i.slice(1), i = i.replace(/([A-Z])/g, o); var s = t[a]; n[i] = new r(s) } return n }, getControlOptions = function (e) { var t = {}; if ("object" != typeof e) return !1; for (var n in e) if (e[n]) { if (!n.match(/(.*)ControlOptions$/)) continue; var o = e[n], r = o.replace(/'/g, '"'); r = r.replace(/([^"]+)|("[^"]+")/g, function (e, t, n) { return t ? t.replace(/([a-zA-Z0-9]+?):/g, '"$1":') : n }); try { var a = JSON.parse(r); for (var i in a) if (a[i]) { var s = a[i]; if ("string" == typeof s ? s = s.toUpperCase() : "mapTypeIds" === i && (s = s.map(function (e) { return e.match(/^[A-Z]+$/) ? google.maps.MapTypeId[e.toUpperCase()] : e })), "style" === i) { var p = n.charAt(0).toUpperCase() + n.slice(1), c = p.replace(/Options$/, "") + "Style"; a[i] = google.maps[c][s] } else a[i] = "position" === i ? google.maps.ControlPosition[s] : s } t[n] = a } catch (u) { } } return t }; return { filter: filter, getOptions: getOptions, getEvents: getEvents, getControlOptions: getControlOptions, toOptionValue: toOptionValue, getAttrsToObserve: getAttrsToObserve, orgAttributes: orgAttributes } }; Attr2MapOptions.$inject = ["$parse", "$timeout", "$log", "$interpolate", "NavigatorGeolocation", "GeoCoder", "camelCaseFilter", "jsonizeFilter", "escapeRegexpFilter"], angular.module("ngMap").service("Attr2MapOptions", Attr2MapOptions) }(), function () { "use strict"; var e, t = function (t) { var n = e.defer(), o = new google.maps.Geocoder; return o.geocode(t, function (e, t) { t == google.maps.GeocoderStatus.OK ? n.resolve(e) : n.reject(t) }), n.promise }, n = function (n) { return e = n, { geocode: t } }; n.$inject = ["$q"], angular.module("ngMap").service("GeoCoder", n) }(), function () { "use strict"; var e, t, n = function (n, o) { return e = n, t = o, { load: function (n) { var o = e.defer(); if (void 0 === window.google || void 0 === window.google.maps) { window.lazyLoadCallback = function () { t(function () { o.resolve(window.google) }, 100) }; var r = document.createElement("script"); r.src = n + (n.indexOf("?") > -1 ? "&" : "?") + "callback=lazyLoadCallback", document.querySelector('script[src="' + r.src + '"]') || document.body.appendChild(r) } else o.resolve(window.google); return o.promise } } }; n.$inject = ["$q", "$timeout"], angular.module("ngMap").service("GoogleMapsApi", n) }(), function () { "use strict"; var e, t = function (t) { var n = e.defer(); return navigator.geolocation ? (void 0 === t ? t = { timeout: 5e3 } : void 0 === t.timeout && (t.timeout = 5e3), navigator.geolocation.getCurrentPosition(function (e) { n.resolve(e) }, function (e) { n.reject(e) }, t)) : n.reject("Browser Geolocation service failed."), n.promise }, n = function (n) { return e = n, { getCurrentPosition: t } }; n.$inject = ["$q"], angular.module("ngMap").service("NavigatorGeolocation", n) }(), function () {
        "use strict";
        var e, t, n, o = [], r = function (n) { var r = t.createElement("div"); r.style.width = "100%", r.style.height = "100%", n.appendChild(r); var a = new e.google.maps.Map(r, {}); return o.push(a), a }, a = function (e, t) { for (var n, r = 0; r < o.length; r++) { var a = o[r]; if (a.id == t && !a.inUse) { var i = a.getDiv(); e.appendChild(i), n = a; break } } return n }, i = function (e) { for (var t, n = 0; n < o.length; n++) { var r = o[n]; if (!r.id && !r.inUse) { var a = r.getDiv(); e.appendChild(a), t = r; break } } return t }, s = function (e) { var t = a(e, e.id) || i(e); return t ? n(function () { google.maps.event.trigger(t, "idle") }, 100) : t = r(e), t.inUse = !0, t }, p = function (e) { e.inUse = !1 }, c = function () { for (var e = 0; e < o.length; e++) o[e] = null; o = [] }, u = function (e) { for (var t = 0; t < o.length; t++) null !== o[t] && o[t].id == e && (o[t] = null, o.splice(t, 1)) }, l = function (r, a, i) { return t = r[0], e = a, n = i, { mapInstances: o, resetMapInstances: c, getMapInstance: s, returnMapInstance: p, deleteMapInstance: u } }; l.$inject = ["$document", "$window", "$timeout"], angular.module("ngMap").factory("NgMapPool", l)
    }(), function () { "use strict"; var e, t, n, o, r, a, i, s, p = {}, c = function (n, o) { var r; return n.currentStyle ? r = n.currentStyle[o] : e.getComputedStyle && (r = t.defaultView.getComputedStyle(n, null).getPropertyValue(o)), r }, u = function (e) { var t = p[e || 0]; return t.map instanceof google.maps.Map ? void 0 : (t.initializeMap(), t.map) }, l = function (t, o) { function r(n) { var o = Object.keys(p), s = p[o[0]]; t && p[t] ? a.resolve(p[t].map) : !t && s && s.map ? a.resolve(s.map) : n > i ? a.reject("could not find map") : e.setTimeout(function () { r(n + 100) }, 100) } o = o || {}, t = "object" == typeof t ? t.id : t; var a = n.defer(), i = o.timeout || 1e4; return r(0), a.promise }, g = function (e) { if (e.map) { var t = Object.keys(p).length; p[e.map.id || t] = e } }, d = function (e) { var t = Object.keys(p).length - 1, n = e.map.id || t; if (e.map) { for (var o in e.eventListeners) { var r = e.eventListeners[o]; google.maps.event.removeListener(r) } e.map.controls && e.map.controls.forEach(function (e) { e.clear() }) } e.map.heatmapLayers && Object.keys(e.map.heatmapLayers).forEach(function (t) { e.deleteObject("heatmapLayers", e.map.heatmapLayers[t]) }), s.deleteMapInstance(n), delete p[n] }, m = function (e, t) { var r = n.defer(); return !e || e.match(/^current/i) ? o.getCurrentPosition(t).then(function (e) { var t = e.coords.latitude, n = e.coords.longitude, o = new google.maps.LatLng(t, n); r.resolve(o) }, function (e) { r.reject(e) }) : a.geocode({ address: e }).then(function (e) { r.resolve(e[0].geometry.location) }, function (e) { r.reject(e) }), r.promise }, f = function (e, t) { return function (n) { if (n) { var o = i("set-" + e), a = r.toOptionValue(n, { key: e }); t[o] && (e.match(/center|position/) && "string" == typeof a ? m(a).then(function (e) { t[o](e) }) : t[o](a)) } } }, v = function (e) { var t = e.getAttribute("default-style"); "true" == t ? (e.style.display = "block", e.style.height = "300px") : ("block" != c(e, "display") && (e.style.display = "block"), c(e, "height").match(/^(0|auto)/) && (e.style.height = "300px")) }; angular.module("ngMap").provider("NgMap", function () { var p = {}; this.setDefaultOptions = function (e) { p = e }; var c = function (c, y, h, b, M, O, w, L) { return e = c, t = y[0], n = h, o = b, r = M, a = O, i = w, s = L, { defaultOptions: p, addMap: g, deleteMap: d, getMap: l, initMap: u, setStyle: v, getGeoLocation: m, observeAndSet: f } }; c.$inject = ["$window", "$document", "$q", "NavigatorGeolocation", "Attr2MapOptions", "GeoCoder", "camelCaseFilter", "NgMapPool"], this.$get = c }) }(), function () { "use strict"; var e, t = function (t, n) { n = n || t.getCenter(); var o = e.defer(), r = new google.maps.StreetViewService; return r.getPanoramaByLocation(n || t.getCenter, 100, function (e, t) { t === google.maps.StreetViewStatus.OK ? o.resolve(e.location.pano) : o.resolve(!1) }), o.promise }, n = function (e, t) { var n = new google.maps.StreetViewPanorama(e.getDiv(), { enableCloseButton: !0 }); n.setPano(t) }, o = function (o) { return e = o, { getPanorama: t, setPanorama: n } }; o.$inject = ["$q"], angular.module("ngMap").service("StreetView", o) }(), "ngMap"
});
(function() {
  'use strict';
  angular.module('ngMask', []);
})();(function() {
  'use strict';
  angular.module('ngMask')
    .directive('mask', ['$log', '$timeout', 'MaskService', function($log, $timeout, MaskService) {
      return {
        restrict: 'A',
        require: 'ngModel',
        compile: function($element, $attrs) { 
         if (!$attrs.mask || !$attrs.ngModel) {
            $log.info('Mask and ng-model attributes are required!');
            return;
          }

          var maskService = MaskService.create();
          var timeout;
          var promise;

          function setSelectionRange(selectionStart){
            if (typeof selectionStart !== 'number') {
              return;
            }

            // using $timeout:
            // it should run after the DOM has been manipulated by Angular
            // and after the browser renders (which may cause flicker in some cases)
            $timeout.cancel(timeout);
            timeout = $timeout(function(){
              var selectionEnd = selectionStart + 1;
              var input = $element[0];

              if (input.setSelectionRange) {
                input.focus();
                input.setSelectionRange(selectionStart, selectionEnd);
              } else if (input.createTextRange) {
                var range = input.createTextRange();

                range.collapse(true);
                range.moveEnd('character', selectionEnd);
                range.moveStart('character', selectionStart);
                range.select();
              }
            });
          }

          return {
            pre: function($scope, $element, $attrs, controller) {
              promise = maskService.generateRegex({
                mask: $attrs.mask,
                // repeat mask expression n times
                repeat: ($attrs.repeat || $attrs.maskRepeat),
                // clean model value - without divisors
                clean: (($attrs.clean || $attrs.maskClean) === 'true'),
                // limit length based on mask length
                limit: (($attrs.limit || $attrs.maskLimit || 'true') === 'true'),
                // how to act with a wrong value
                restrict: ($attrs.restrict || $attrs.maskRestrict || 'select'), //select, reject, accept
                // set validity mask
                validate: (($attrs.validate || $attrs.maskValidate || 'true') === 'true'),
                // default model value
                model: $attrs.ngModel,
                // default input value
                value: $attrs.ngValue
              });
            },
            post: function($scope, $element, $attrs, controller) {
              promise.then(function() {
                // get initial options
                var timeout;
                var options = maskService.getOptions();

                function parseViewValue(value) {
                  var untouchedValue = value;
                  // set default value equal 0
                  value = value || '';

                  // get view value object
                  var viewValue = maskService.getViewValue(value);

                  // get mask without question marks
                  var maskWithoutOptionals = options['maskWithoutOptionals'] || '';

                  // get view values capped
                  // used on view
                  var viewValueWithDivisors = viewValue.withDivisors(true);
                  // used on model
                  var viewValueWithoutDivisors = viewValue.withoutDivisors(true);

                  try {
                    // get current regex
                    var regex = maskService.getRegex(viewValueWithDivisors.length - 1);
                    var fullRegex = maskService.getRegex(maskWithoutOptionals.length - 1);

                    // current position is valid
                    var validCurrentPosition = regex.test(viewValueWithDivisors) || fullRegex.test(viewValueWithDivisors);

                    // difference means for select option
                    var diffValueAndViewValueLengthIsOne = (value.length - viewValueWithDivisors.length) === 1;
                    var diffMaskAndViewValueIsGreaterThanZero = (maskWithoutOptionals.length - viewValueWithDivisors.length) > 0;

                    if (options.restrict !== 'accept') {
                      if (options.restrict === 'select' && (!validCurrentPosition || diffValueAndViewValueLengthIsOne)) {
                        var lastCharInputed = value[(value.length-1)];
                        var lastCharGenerated = viewValueWithDivisors[(viewValueWithDivisors.length-1)];

                        if ((lastCharInputed !== lastCharGenerated) && diffMaskAndViewValueIsGreaterThanZero) {
                          viewValueWithDivisors = viewValueWithDivisors + lastCharInputed;
                        }

                        var wrongPosition = maskService.getFirstWrongPosition(viewValueWithDivisors);
                        if (angular.isDefined(wrongPosition)) {
                          setSelectionRange(wrongPosition);
                        }
                      } else if (options.restrict === 'reject' && !validCurrentPosition) {
                        viewValue = maskService.removeWrongPositions(viewValueWithDivisors);
                        viewValueWithDivisors = viewValue.withDivisors(true);
                        viewValueWithoutDivisors = viewValue.withoutDivisors(true);

                        // setSelectionRange(viewValueWithDivisors.length);
                      }
                    }

                    if (!options.limit) {
                      viewValueWithDivisors = viewValue.withDivisors(false);
                      viewValueWithoutDivisors = viewValue.withoutDivisors(false);
                    }

                    // Set validity
                    if (options.validate && controller.$dirty) {
                      if (fullRegex.test(viewValueWithDivisors) || controller.$isEmpty(untouchedValue)) {
                        controller.$setValidity('mask', true);
                      } else {
                        controller.$setValidity('mask', false);
                      }
                    }

                    // Update view and model values
                    if(value !== viewValueWithDivisors){
                      controller.$setViewValue(angular.copy(viewValueWithDivisors), 'input');
                      controller.$render();
                    }
                  } catch (e) {
                    $log.error('[mask - parseViewValue]');
                    throw e;
                  }

                  // Update model, can be different of view value
                  if (options.clean) {
                    return viewValueWithoutDivisors;
                  } else {
                    return viewValueWithDivisors;
                  }
                }

                controller.$parsers.push(parseViewValue);

                $element.on('click input paste keyup', function() {
                  timeout = $timeout(function() {
                    // Manual debounce to prevent multiple execution
                    $timeout.cancel(timeout);

                    parseViewValue($element.val());
                    $scope.$apply();
                  }, 100);
                });

                // Register the watch to observe remote loading or promised data
                // Deregister calling returned function
                var watcher = $scope.$watch($attrs.ngModel, function (newValue, oldValue) {
                  if (angular.isDefined(newValue)) {
                    parseViewValue(newValue);
                    watcher();
                  }
                });

                // $evalAsync from a directive
                // it should run after the DOM has been manipulated by Angular
                // but before the browser renders
                if(options.value) {
                  $scope.$evalAsync(function($scope) {
                    controller.$setViewValue(angular.copy(options.value), 'input');
                    controller.$render();
                  });
                }
              });
            }
          }
        }
      }
    }]);
})();
(function() {
  'use strict';
  angular.module('ngMask')
    .factory('MaskService', ['$q', 'OptionalService', 'UtilService', function($q, OptionalService, UtilService) {
      function create() {
        var options;
        var maskWithoutOptionals;
        var maskWithoutOptionalsLength = 0;
        var maskWithoutOptionalsAndDivisorsLength = 0;
        var optionalIndexes = [];
        var optionalDivisors = {};
        var optionalDivisorsCombinations = [];
        var divisors = [];
        var divisorElements = {};
        var regex = [];
        var patterns = {
          '9': /[0-9]/,
          '8': /[0-8]/,
          '7': /[0-7]/,
          '6': /[0-6]/,
          '5': /[0-5]/,
          '4': /[0-4]/,
          '3': /[0-3]/,
          '2': /[0-2]/,
          '1': /[0-1]/,
          '0': /[0]/,
          '*': /./,
          'w': /\w/,
          'W': /\W/,
          'd': /\d/,
          'D': /\D/,
          's': /\s/,
          'S': /\S/,
          'b': /\b/,
          'A': /[A-Z]/,
          'a': /[a-z]/,
          'Z': /[A-ZÇÀÁÂÃÈÉÊẼÌÍÎĨÒÓÔÕÙÚÛŨ]/,
          'z': /[a-zçáàãâéèêẽíìĩîóòôõúùũüû]/,
          '@': /[a-zA-Z]/,
          '#': /[a-zA-ZçáàãâéèêẽíìĩîóòôõúùũüûÇÀÁÂÃÈÉÊẼÌÍÎĨÒÓÔÕÙÚÛŨ]/,
          '%': /[0-9a-zA-ZçáàãâéèêẽíìĩîóòôõúùũüûÇÀÁÂÃÈÉÊẼÌÍÎĨÒÓÔÕÙÚÛŨ]/,
          '&': /[0-9a-zA-Z]/
        };

        // REGEX

        function generateIntermetiateElementRegex(i, forceOptional) {
          var charRegex;
          try {
            var element = maskWithoutOptionals[i];
            var elementRegex = patterns[element];
            var hasOptional = isOptional(i);

            if (elementRegex) {
              charRegex = '(' + elementRegex.source + ')';
            } else { // is a divisor
              if (!isDivisor(i)) {
                divisors.push(i);
                divisorElements[i] = element;
              }

              charRegex = '(' + '\\' + element + ')';
            }
          } catch (e) {
            throw e;
          }

          if (hasOptional || forceOptional) {
            charRegex += '?';
          }

          return new RegExp(charRegex);
        }

        function generateIntermetiateRegex(i, forceOptional) {


          var elementRegex
          var elementOptionalRegex;
          try {
            var intermetiateElementRegex = generateIntermetiateElementRegex(i, forceOptional);
            elementRegex = intermetiateElementRegex;

            var hasOptional = isOptional(i);
            var currentRegex = intermetiateElementRegex.source;

            if (hasOptional && ((i+1) < maskWithoutOptionalsLength)) {
              var intermetiateRegex = generateIntermetiateRegex((i+1), true).elementOptionalRegex();
              currentRegex += intermetiateRegex.source;
            }

            elementOptionalRegex = new RegExp(currentRegex);
          } catch (e) {
            throw e;
          }
          return {
            elementRegex: function() {
              return elementRegex;
            },
            elementOptionalRegex: function() {
              // from element regex, gets the flow of regex until first not optional
              return elementOptionalRegex;
            }
          };
        }

        function generateRegex(opts) {
          var deferred = $q.defer();
          options = opts;

          try {
            var mask = opts['mask'];
            var repeat = opts['repeat'];

            if (!mask)
              return;

            if (repeat) {
              mask = Array((parseInt(repeat)+1)).join(mask);
            }

            optionalIndexes = OptionalService.getOptionals(mask).fromMaskWithoutOptionals();
            options['maskWithoutOptionals'] = maskWithoutOptionals = OptionalService.removeOptionals(mask);
            maskWithoutOptionalsLength = maskWithoutOptionals.length;

            var cumulativeRegex;
            for (var i=0; i<maskWithoutOptionalsLength; i++) {
              var charRegex = generateIntermetiateRegex(i);
              var elementRegex = charRegex.elementRegex();
              var elementOptionalRegex = charRegex.elementOptionalRegex();

              var newRegex = cumulativeRegex ? cumulativeRegex.source + elementOptionalRegex.source : elementOptionalRegex.source;
              newRegex = new RegExp(newRegex);
              cumulativeRegex = cumulativeRegex ? cumulativeRegex.source + elementRegex.source : elementRegex.source;
              cumulativeRegex = new RegExp(cumulativeRegex);

              regex.push(newRegex);
            }

            generateOptionalDivisors();
            maskWithoutOptionalsAndDivisorsLength = removeDivisors(maskWithoutOptionals).length;

            deferred.resolve({
              options: options,
              divisors: divisors,
              divisorElements: divisorElements,
              optionalIndexes: optionalIndexes,
              optionalDivisors: optionalDivisors,
              optionalDivisorsCombinations: optionalDivisorsCombinations
            });
          } catch (e) {
            deferred.reject(e);
            throw e;
          }

          return deferred.promise;
        }

        function getRegex(index) {
          var currentRegex;

          try {
            currentRegex = regex[index] ? regex[index].source : '';
          } catch (e) {
            throw e;
          }

          return (new RegExp('^' + currentRegex + '$'));
        }

        // DIVISOR

        function isOptional(currentPos) {
          return UtilService.inArray(currentPos, optionalIndexes);
        }

        function isDivisor(currentPos) {
          return UtilService.inArray(currentPos, divisors);
        }

        function generateOptionalDivisors() {
          function sortNumber(a,b) {
              return a - b;
          }

          var sortedDivisors = divisors.sort(sortNumber);
          var sortedOptionals = optionalIndexes.sort(sortNumber);
          for (var i = 0; i<sortedDivisors.length; i++) {
            var divisor = sortedDivisors[i];
            for (var j = 1; j<=sortedOptionals.length; j++) {
              var optional = sortedOptionals[(j-1)];
              if (optional >= divisor) {
                break;
              }

              if (optionalDivisors[divisor]) {
                optionalDivisors[divisor] = optionalDivisors[divisor].concat(divisor-j);
              } else {
                optionalDivisors[divisor] = [(divisor-j)];
              }

              // get the original divisor for alternative divisor
              divisorElements[(divisor-j)] = divisorElements[divisor];
            }
          }
        }

        function removeDivisors(value) {
              value = value.toString();
          try {
            if (divisors.length > 0 && value) {
              var keys = Object.keys(divisorElements);
              var elments = [];

              for (var i = keys.length - 1; i >= 0; i--) {
                var divisor = divisorElements[keys[i]];
                if (divisor) {
                  elments.push(divisor);
                }
              }

              elments = UtilService.uniqueArray(elments);

              // remove if it is not pattern
              var regex = new RegExp(('[' + '\\' + elments.join('\\') + ']'), 'g');
              return value.replace(regex, '');
            } else {
              return value;
            }
          } catch (e) {
            throw e;
          }
        }

        function insertDivisors(array, combination) {
          function insert(array, output) {
            var out = output;
            for (var i=0; i<array.length; i++) {
              var divisor = array[i];
              if (divisor < out.length) {
                out.splice(divisor, 0, divisorElements[divisor]);
              }
            }
            return out;
          }

          var output = array;
          var divs = divisors.filter(function(it) {
            var optionalDivisorsKeys = Object.keys(optionalDivisors).map(function(it){
              return parseInt(it);
            });

            return !UtilService.inArray(it, combination) && !UtilService.inArray(it, optionalDivisorsKeys);
          });

          if (!angular.isArray(array) || !angular.isArray(combination)) {
            return output;
          }

          // insert not optional divisors
          output = insert(divs, output);

          // insert optional divisors
          output = insert(combination, output);

          return output;
        }

        function tryDivisorConfiguration(value) {
          var output = value.split('');
          var defaultDivisors = true;

          // has optional?
          if (optionalIndexes.length > 0) {
            var lazyArguments = [];
            var optionalDivisorsKeys = Object.keys(optionalDivisors);

            // get all optional divisors as array of arrays [[], [], []...]
            for (var i=0; i<optionalDivisorsKeys.length; i++) {
              var val = optionalDivisors[optionalDivisorsKeys[i]];
              lazyArguments.push(val);
            }

            // generate all possible configurations
            if (optionalDivisorsCombinations.length === 0) {
              UtilService.lazyProduct(lazyArguments, function() {
                // convert arguments to array
                optionalDivisorsCombinations.push(Array.prototype.slice.call(arguments));
              });
            }

            for (var i = optionalDivisorsCombinations.length - 1; i >= 0; i--) {
              var outputClone = angular.copy(output);
              outputClone = insertDivisors(outputClone, optionalDivisorsCombinations[i]);

              // try validation
              var viewValueWithDivisors = outputClone.join('');
              var regex = getRegex(maskWithoutOptionals.length - 1);

              if (regex.test(viewValueWithDivisors)) {
                defaultDivisors = false;
                output = outputClone;
                break;
              }
            }
          }

          if (defaultDivisors) {
            output = insertDivisors(output, divisors);
          }

          return output.join('');
        }

        // MASK

        function getOptions() {
          return options;
        }

        function getViewValue(value) {
          try {
            var outputWithoutDivisors = removeDivisors(value);
            var output = tryDivisorConfiguration(outputWithoutDivisors);

            return {
              withDivisors: function(capped) {
                if (capped) {
                  return output.substr(0, maskWithoutOptionalsLength);
                } else {
                  return output;
                }
              },
              withoutDivisors: function(capped) {
                if (capped) {
                  return outputWithoutDivisors.substr(0, maskWithoutOptionalsAndDivisorsLength);
                } else {
                  return outputWithoutDivisors;
                }
              }
            };
          } catch (e) {
            throw e;
          }
        }

        // SELECTOR

        function getWrongPositions(viewValueWithDivisors, onlyFirst) {
          var pos = [];

          if (!viewValueWithDivisors) {
            return 0;
          }

          for (var i=0; i<viewValueWithDivisors.length; i++){
            var pattern = getRegex(i);
            var value = viewValueWithDivisors.substr(0, (i+1));

            if(pattern && !pattern.test(value)){
              pos.push(i);

              if (onlyFirst) {
                break;
              }
            }
          }

          return pos;
        }

        function getFirstWrongPosition(viewValueWithDivisors) {
          return getWrongPositions(viewValueWithDivisors, true)[0];
        }

        function removeWrongPositions(viewValueWithDivisors) {
          var wrongPositions = getWrongPositions(viewValueWithDivisors, false);
          var newViewValue = viewValueWithDivisors;

          for(var i = 0; i < wrongPositions.length; i++){
            var wrongPosition = wrongPositions[i];
            var viewValueArray = viewValueWithDivisors.split('');
            viewValueArray.splice(wrongPosition, 1);
            newViewValue = viewValueArray.join('');
          }

          return getViewValue(newViewValue);
        }

        return {
          getViewValue: getViewValue,
          generateRegex: generateRegex,
          getRegex: getRegex,
          getOptions: getOptions,
          removeDivisors: removeDivisors,
          getFirstWrongPosition: getFirstWrongPosition,
          removeWrongPositions: removeWrongPositions
        }
      }

      return {
        create: create
      }
    }]);
})();
(function() {
  'use strict';
  angular.module('ngMask')
    .factory('OptionalService', [function() {
      function getOptionalsIndexes(mask) {
        var indexes = [];

        try {
          var regexp = /\?/g;
          var match = [];

          while ((match = regexp.exec(mask)) != null) {
            // Save the optional char
            indexes.push((match.index - 1));
          }
        } catch (e) {
          throw e;
        }

        return {
          fromMask: function() {
            return indexes;
          },
          fromMaskWithoutOptionals: function() {
            return getOptionalsRelativeMaskWithoutOptionals(indexes);
          }
        };
      }

      function getOptionalsRelativeMaskWithoutOptionals(optionals) {
        var indexes = [];
        for (var i=0; i<optionals.length; i++) {
          indexes.push(optionals[i]-i);
        }
        return indexes;
      }

      function removeOptionals(mask) {
        var newMask;

        try {
          newMask = mask.replace(/\?/g, '');
        } catch (e) {
          throw e;
        }

        return newMask;
      }

      return {
        removeOptionals: removeOptionals,
        getOptionals: getOptionalsIndexes
      }
    }]);
})();(function() {
  'use strict';
  angular.module('ngMask')
    .factory('UtilService', [function() {

      // sets: an array of arrays
      // f: your callback function
      // context: [optional] the `this` to use for your callback
      // http://phrogz.net/lazy-cartesian-product
      function lazyProduct(sets, f, context){
        if (!context){
          context=this;
        }

        var p = [];
        var max = sets.length-1;
        var lens = [];

        for (var i=sets.length;i--;) {
          lens[i] = sets[i].length;
        }

        function dive(d){
          var a = sets[d];
          var len = lens[d];

          if (d === max) {
            for (var i=0;i<len;++i) {
              p[d] = a[i];
              f.apply(context, p);
            }
          } else {
            for (var i=0;i<len;++i) {
              p[d]=a[i];
              dive(d+1);
            }
          }

          p.pop();
        }

        dive(0);
      }

      function inArray(i, array) {
        var output;

        try {
          output = array.indexOf(i) > -1;
        } catch (e) {
          throw e;
        }

        return output;
      }

      function uniqueArray(array) {
        var u = {};
        var a = [];

        for (var i = 0, l = array.length; i < l; ++i) {
          if(u.hasOwnProperty(array[i])) {
            continue;
          }

          a.push(array[i]);
          u[array[i]] = 1;
        }

        return a;
      }

      return {
        lazyProduct: lazyProduct,
        inArray: inArray,
        uniqueArray: uniqueArray
      }
    }]);
})();
/*global angular: true, document: true, setInterval: true,
clearInterval: true, setTimeout: true */
/*
ngProgress v0.0.3 - slim, site-wide progressbar for AngularJS
(C) 2013 - Victor Bjelkholm
License: MIT (see LICENSE)
Source: https://github.com/victorbjelkholm/ngprogress
*/

var module = angular.module('ngProgress', []);

module.provider('progressbar', function () {

    'use strict';
    //Default values for provider
    this.count = 0;
    this.height = '2px';
    this.color = 'firebrick';

    this.$get = ['$document', '$window', function ($document, $window) {
        var count = this.count,
            height = this.height,
            color = this.color,
            $body = $document.find('body'),
        // Create elements that is needed
            progressbarContainer = angular.element('<div class="progressbar-container"></div>'),
            progressbar = angular.element('<div class="progressbar"></div>'),

        //Add CSS3 styles for transition smoothing
            css = document.createElement("style");
        css.type = "text/css";
        css.innerHTML = ".progressbar {-webkit-transition: all 0.5s ease-in-out; -moz-transition: all 0.5s ease-in-out; -o-transition: all 0.5s ease-in-out; transition: all 0.5s ease-in-out;}";
        document.body.appendChild(css);

        //Styling for the progressbar-container
        progressbarContainer.css('position', 'fixed');
        progressbarContainer.css('margin', '0');
        progressbarContainer.css('padding', '0');
        progressbarContainer.css('top', '0px');
        progressbarContainer.css('left', '0px');
        progressbarContainer.css('right', '0px');
        progressbarContainer.css('z-index', '99999');

        //Styling for the progressbar itself
        progressbar.css('height', height);
        progressbar.css('box-shadow', '0px 0px 10px 0px ' + color);
        progressbar.css('width', count + '%');
        progressbar.css('margin', '0');
        progressbar.css('padding', '0');
        progressbar.css('background-color', color);
        progressbar.css('z-index', '99998');

        //Add progressbar to progressbar-container and progressbar-container
        // to body
        progressbarContainer.append(progressbar);
        $body.append(progressbarContainer);


        return {
            // Starts the animation and adds between 0 - 5 percent to loading
            // each 400 milliseconds. Should always be finished with progressbar.complete()
            // to hide it
            start: function () {
                progressbar.css('width', count + '%');
                progressbar.css('opacity', '1');
                $window.interval = setInterval(function () {
                    var remaining = 100 - count;
                    count = count + (0.15 * Math.pow(1-Math.sqrt(remaining), 2));
                    progressbar.css('width', count + '%');
                }, 400);
            },
            // Sets the height of the progressbar. Use any valid CSS value
            // Eg '10px', '1em' or '1%'
            height: function (new_height) {
                progressbar.css('height', new_height);
            },
            // Sets the color of the progressbar and it's shadow. Use any valid HTML
            // color
            color: function (color) {
                progressbar.css('box-shadow', '0px 0px 10px 0px ' + color);
                progressbar.css('background-color', color);
            },
            // Returns on how many percent the progressbar is at. Should'nt be needed
            status: function () {
                return this.count;
            },
            // Stops the progressbar at it's current location
            stop: function () {
                clearInterval($window.interval);
            },
            // Set's the progressbar percentage. Use a number between 0 - 100. 
            // If 100 is provided, complete will be called.
            set: function (new_count) {
                clearInterval($window.interval);
                if (new_count >= 100) {
                    this.complete();
                }
                count = new_count;
                progressbar.css('width', count + '%');
                progressbar.css('opacity', '1');
                return count;
            },
            // Resets the progressbar to percetage 0 and therefore will be hided after
            // it's rollbacked
            reset: function () {
                clearInterval($window.interval);
                count = 0;
                progressbar.css('width', count + '%');
                progressbar.css('opacity', '1');
                return 0;
            },
            // Jumps to 100% progress and fades away progressbar.
            complete: function () {
                clearInterval($window.interval);
                count = 100;
                progressbar.css('width', count + '%');
                setTimeout(function () {
                    progressbar.css('opacity', '0');
                }, 500);
                setTimeout(function () {
                    count = 0;
                    progressbar.css('width', count + '%');
                }, 1000);
                return count;
            }
        };
    }];

    this.setColor = function (color) {
        this.color = color;
    };

    this.setHeight = function (height) {
        this.height = height;
    };

});

(function (window, angular, undefined) {
    'use strict';

    var $el = angular.element;
    var globalId = 0;
    var isDef = angular.isDefined;
    var module = angular.module('nsPopover', []);

    module.provider('nsPopover', function () {
        var defaults = {
            angularEvent: null,
            container: 'body',
            hideOnButtonClick: true,
            hideOnInsideClick: false,
            hideOnOutsideClick: true,
            mouseRelative: '',
            onClose: angular.noop,
            onOpen: angular.noop,
            placement: 'bottom|left',
            plain: 'false',
            popupDelay: 0,
            restrictBounds: false,
            scopeEvent: null,
            template: '',
            theme: 'ns-popover-list-theme',
            timeout: 1.5,
            trigger: 'click',
            triggerPrevent: true,
        };

        this.setDefaults = function (newDefaults) {
            angular.extend(defaults, newDefaults);
        };

        this.$get = function () {
            return {
                getDefaults: function () {
                    return defaults;
                }
            };
        };
    });

    module.directive('nsPopover', [
      'nsPopover',
      '$rootScope',
      '$timeout',
      '$templateCache',
      '$q',
      '$http',
      '$compile',
      '$document',
      '$parse',
      function (
        nsPopover,
        $rootScope,
        $timeout,
        $templateCache,
        $q,
        $http,
        $compile,
        $document,
        $parse
      ) {
          return {
              restrict: 'A',
              scope: true,
              link: function (scope, elm, attrs) {
                  var $container;
                  var $popover;
                  var $triangle;
                  var align_;
                  var defaults = nsPopover.getDefaults();
                  var displayer_;
                  var hider_;
                  var match;
                  var options = {
                      angularEvent: attrs.nsPopoverAngularEvent || defaults.angularEvent,
                      container: attrs.nsPopoverContainer || defaults.container,
                      group: attrs.nsPopoverGroup,
                      hideOnButtonClick: toBoolean(attrs.nsPopoverHideOnButtonClick || defaults.hideOnButtonClick),
                      hideOnInsideClick: toBoolean(attrs.nsPopoverHideOnInsideClick || defaults.hideOnInsideClick),
                      hideOnOutsideClick: toBoolean(attrs.nsPopoverHideOnOutsideClick || defaults.hideOnOutsideClick),
                      mouseRelative: attrs.nsPopoverMouseRelative,
                      onClose: $parse(attrs.nsPopoverOnClose) || defaults.onClose,
                      onOpen: $parse(attrs.nsPopoverOnOpen) || defaults.onOpen,
                      placement: attrs.nsPopoverPlacement || defaults.placement,
                      plain: toBoolean(attrs.nsPopoverPlain || defaults.plain),
                      popupDelay: attrs.nsPopoverPopupDelay || defaults.popupDelay,
                      restrictBounds: Boolean(attrs.nsPopoverRestrictBounds) || defaults.restrictBounds,
                      scopeEvent: attrs.nsPopoverScopeEvent || defaults.scopeEvent,
                      template: attrs.nsPopoverTemplate || defaults.template,
                      theme: attrs.nsPopoverTheme || defaults.theme,
                      timeout: attrs.nsPopoverTimeout || defaults.timeout,
                      trigger: attrs.nsPopoverTrigger || defaults.trigger,
                      triggerPrevent: attrs.nsPopoverTriggerPrevent || defaults.triggerPrevent,
                  };
                  var placement_;
                  var unregisterActivePopoverListeners;
                  var unregisterDisplayMethod;

                  if (options.mouseRelative) {
                      options.mouseRelativeX = options.mouseRelative.indexOf('x') !== -1;
                      options.mouseRelativeY = options.mouseRelative.indexOf('y') !== -1;
                  }

                  function addEventListeners() {
                      function cancel() {
                          hider_.cancel();
                      }

                      function hide() {
                          hider_.hide(options.timeout);
                      }

                      elm
                        .on('mouseout', hide)
                        .on('mouseover', cancel)
                      ;

                      $popover
                        .on('mouseout', hide)
                        .on('mouseover', cancel)
                      ;

                      unregisterActivePopoverListeners = function () {
                          elm
                            .off('mouseout', hide)
                            .off('mouseover', cancel)
                          ;

                          $popover
                            .off('mouseout', hide)
                            .off('mouseover', cancel)
                          ;
                      }
                  }

                  /**
                   * Adjust a rect accordingly to the given x and y mouse positions.
                   *
                   * @param rect {ClientRect} The rect to be adjusted.
                   */
                  function adjustRect(rect, adjustX, adjustY, ev) {
                      // if pageX or pageY is defined we need to lock the popover to the given
                      // x and y position.
                      // clone the rect, so we can manipulate its properties.
                      var localRect = {
                          bottom: rect.bottom,
                          height: rect.height,
                          left: rect.left,
                          right: rect.right,
                          top: rect.top,
                          width: rect.width
                      };

                      if (adjustX) {
                          localRect.left = ev.pageX;
                          localRect.right = ev.pageX;
                          localRect.width = 0;
                      }

                      if (adjustY) {
                          localRect.top = ev.pageY;
                          localRect.bottom = ev.pageY;
                          localRect.height = 0;
                      }

                      return localRect;
                  }

                  function buttonClickHandler() {
                      if ($popover.isOpen) {
                          scope.hidePopover();
                      }
                  }

                  function display(e) {
                      if (
                        angular.isObject(e) &&
                        false !== options.triggerPrevent
                      ) {
                          e.preventDefault();
                      }

                      hider_.cancel();
                      displayer_.display(options.popupDelay, e);
                  }

                  function getBoundingClientRect(elm) {
                      var w = window;
                      var doc = document.documentElement || document.body.parentNode || document.body;
                      var x = (isDef(w.pageXOffset)) ? w.pageXOffset : doc.scrollLeft;
                      var y = (isDef(w.pageYOffset)) ? w.pageYOffset : doc.scrollTop;
                      var rect = elm.getBoundingClientRect();

                      // ClientRect class is immutable, so we need to return a modified copy
                      // of it when the window has been scrolled.
                      if (x || y) {
                          return {
                              bottom: rect.bottom + y,
                              left: rect.left + x,
                              right: rect.right + x,
                              top: rect.top + y,
                              height: rect.height,
                              width: rect.width
                          };
                      }
                      return rect;
                  }

                  function insideClickHandler() {
                      if ($popover.isOpen) {
                          scope.hidePopover();
                      }
                  }

                  /**
                   * Load the given template in the cache if it is not already loaded.
                   *
                   * @param template The URI of the template to be loaded.
                   * @returns {String} A promise that the template will be loaded.
                   * @remarks If the template is null or undefined a empty string will be returned.
                   */
                  function loadTemplate(template, plain) {
                      if (!template) {
                          return '';
                      }

                      if (angular.isString(template) && plain) {
                          return template;
                      }

                      return $templateCache.get(template) || $http.get(template, { cache: true });
                  }

                  /**
                   * Move the popover to the |placement| position of the object located on the |rect|.
                   *
                   * @param popover {Object} The popover object to be moved.
                   * @param placement {String} The relative position to move the popover - top | bottom | left | right.
                   * @param align {String} The way the popover should be aligned - center | left | right.
                   * @param rect {ClientRect} The ClientRect of the object to move the popover around.
                   * @param triangle {Object} The element that contains the popover's triangle. This can be null.
                   */
                  function move(popover, placement, align, rect, triangle) {
                      var containerRect;
                      var popoverRect = getBoundingClientRect(popover[0]);
                      var popoverRight;
                      var top, left;

                      var positionX = function () {
                          if (align === 'center') {
                              return Math.round(rect.left + rect.width / 2 - popoverRect.width / 2);
                          } else if (align === 'right') {
                              return rect.right - popoverRect.width;
                          }
                          return rect.left;
                      };

                      var positionY = function () {
                          if (align === 'center') {
                              return Math.round(rect.top + rect.height / 2 - popoverRect.height / 2);
                          } else if (align === 'bottom') {
                              return rect.bottom - popoverRect.height;
                          }
                          return rect.top;
                      };

                      if (placement === 'top') {
                          top = rect.top - popoverRect.height;
                          left = positionX();
                      } else if (placement === 'right') {
                          top = positionY();
                          left = rect.right;
                      } else if (placement === 'bottom') {
                          top = rect.bottom;
                          left = positionX();
                      } else if (placement === 'left') {
                          top = positionY();
                          left = rect.left - popoverRect.width;
                      }

                      // Rescrict the popover to the bounds of the container
                      if (true === options.restrictBounds) {
                          containerRect = getBoundingClientRect($container[0]);

                          // The left should be below the left of the container.
                          left = Math.max(containerRect.left, left);

                          // Prevent the left from causing the right to go outside
                          // the conatiner.
                          popoverRight = left + popoverRect.width;
                          if (popoverRight > containerRect.width) {
                              left = left - (popoverRight - containerRect.width);
                          }
                      }

                      popover
                        .css('top', top.toString() + 'px')
                        .css('left', left.toString() + 'px');

                      if (triangle && triangle.length) {
                          if (placement === 'top' || placement === 'bottom') {
                              left = rect.left + rect.width / 2 - left;
                              triangle.css('left', left.toString() + 'px');
                          } else {
                              top = rect.top + rect.height / 2 - top;
                              triangle.css('top', top.toString() + 'px');
                          }
                      }
                  }

                  function outsideClickHandler(e) {
                      function isInPopover(el) {
                          if (el.id === id) {
                              return true;
                          }

                          var parent = angular.element(el).parent()[0];

                          if (!parent) {
                              return false;
                          }

                          if (parent.id === id) {
                              return true;
                          }
                          else {
                              return isInPopover(parent);
                          }
                      }

                      if ($popover.isOpen && e.target !== elm[0]) {
                          var id = $popover[0].id;

                          if (!isInPopover(e.target)) {
                              scope.hidePopover();
                          }
                      }
                  }

                  function removeEventListeners() {
                      unregisterActivePopoverListeners();
                  }

                  function toBoolean(value) {
                      if (value && value.length !== 0) {
                          var v = ("" + value).toLowerCase();
                          value = (v == 'true');
                      } else {
                          value = false;
                      }
                      return value;
                  }

                  /**
                   * Responsible for displaying of popover.
                   * @type {Object}
                   */
                  displayer_ = {
                      id_: undefined,

                      /**
                       * Set the display property of the popover to 'block' after |delay| milliseconds.
                       *
                       * @param delay {Number}  The time (in seconds) to wait before set the display property.
                       * @param e {Event}  The event which caused the popover to be shown.
                       */
                      display: function (delay, e) {
                          // Disable popover if ns-popover value is false
                          if ($parse(attrs.nsPopover)(scope) === false) {
                              return;
                          }

                          $timeout.cancel(displayer_.id_);

                          if (!isDef(delay)) {
                              delay = 0;
                          }

                          // hide any popovers being displayed
                          if (options.group) {
                              $rootScope.$broadcast('ns:popover:hide', options.group);
                          }

                          displayer_.id_ = $timeout(function () {
                              if (true === $popover.isOpen) {
                                  return;
                              }

                              $popover.isOpen = true;
                              $popover.css('display', 'block');

                              // position the popover accordingly to the defined placement around the
                              // |elm|.
                              var elmRect = getBoundingClientRect(elm[0]);

                              // If the mouse-relative options is specified we need to adjust the
                              // element client rect to the current mouse coordinates.
                              if (options.mouseRelative) {
                                  elmRect = adjustRect(elmRect, options.mouseRelativeX, options.mouseRelativeY, e);
                              }

                              move($popover, placement_, align_, elmRect, $triangle);
                              addEventListeners();

                              // Hide the popover without delay on the popover click events.
                              if (true === options.hideOnInsideClick) {
                                  $popover.on('click', insideClickHandler);
                              }

                              // Hide the popover without delay on outside click events.
                              if (true === options.hideOnOutsideClick) {
                                  $document.on('click', outsideClickHandler);
                              }

                              // Hide the popover without delay on the button click events.
                              if (true === options.hideOnButtonClick) {
                                  elm.on('click', buttonClickHandler);
                              }

                              // Call the open callback
                              options.onOpen(scope);
                          }, delay * 1000);
                      },

                      cancel: function () {
                          $timeout.cancel(displayer_.id_);
                      }
                  };

                  /**
                   * Responsible for hiding of popover.
                   * @type {Object}
                   */
                  hider_ = {
                      id_: undefined,

                      /**
                       * Set the display property of the popover to 'none' after |delay| milliseconds.
                       *
                       * @param delay {Number}  The time (in seconds) to wait before set the display property.
                       */
                      hide: function (delay) {
                          $timeout.cancel(hider_.id_);

                          // do not hide if -1 is passed in.
                          if (delay !== "-1") {
                              // delay the hiding operation for 1.5s by default.
                              if (!isDef(delay)) {
                                  delay = 1.5;
                              }

                              hider_.id_ = $timeout(function () {
                                  $popover.off('click', insideClickHandler);
                                  $document.off('click', outsideClickHandler);
                                  elm.off('click', buttonClickHandler);
                                  $popover.isOpen = false;
                                  displayer_.cancel();
                                  $popover.css('display', 'none');
                                  removeEventListeners();

                                  // Call the close callback
                                  options.onClose(scope);
                              }, delay * 1000);
                          }
                      },

                      cancel: function () {
                          $timeout.cancel(hider_.id_);
                      }
                  };

                  // Set the container to the passed selector. If the container element
                  // was not found, use the body as the container.
                  $container = $document.find(options.container);
                  if (!$container.length) {
                      $container = $document.find('body');
                  }

                  // Parse the desired placement and alignment values.
                  match = options
                    .placement
                    .match(/^(top|bottom|left|right)$|((top|bottom)\|(center|left|right)+)|((left|right)\|(center|top|bottom)+)/)
                  ;
                  if (!match) {
                      throw new Error(
                        '"' + options.placement + '" is not a valid placement or has ' +
                        'an invalid combination of placements.'
                      );
                  }
                  placement_ = match[6] || match[3] || match[1];
                  align_ = match[7] || match[4] || match[2] || 'center';

                  // Create the popover element and add it to the cached list of all
                  // popovers.
                  globalId += 1;
                  $popover = $el('<div id="nspopover-' + globalId + '"></div>')
                    .addClass('ns-popover-' + placement_ + '-placement')
                    .addClass('ns-popover-' + align_ + '-align')
                    .css('position', 'absolute')
                    .css('display', 'none')
                  ;

                  // Allow closing the popover programatically.
                  scope.hidePopover = function () {
                      hider_.hide(0);
                  };

                  // Hide popovers that are associated with the passed group.
                  scope.$on('ns:popover:hide', function (ev, group) {
                      if (options.group === group) {
                          scope.hidePopover();
                      }
                  });

                  // Clean up after yourself.
                  scope.$on('$destroy', function () {
                      $popover.remove();
                      unregisterDisplayMethod();
                  });

                  // Display the popover when a message is broadcasted on the
                  // $rootScope if `angular-event` was given.
                  if (angular.isString(options.angularEvent)) {
                      unregisterDisplayMethod = $rootScope.$on(
                        options.angularEvent,
                        display
                      );

                      // Display the popover when a message is broadcasted on the
                      // scope if `scope-event` was given.
                  } else if (angular.isString(options.scopeEvent)) {
                      unregisterDisplayMethod = scope.$on(
                        options.scopeEvent,
                        display
                      )

                      // Otherwise just display the popover whenever the event that was
                      // passed to the `trigger` attribute occurs on the element.
                  } else {
                      elm.on(options.trigger, display);
                      unregisterDisplayMethod = function () {
                          elm.off(options.trigger, display);
                      }
                  }

                  // Load the template and compile the popover.
                  $q
                    .when(loadTemplate(options.template, options.plain))
                    .then(function (template) {
                        if (angular.isObject(template)) {
                            template = angular.isString(template.data) ?
                              template.data : ''
                            ;
                        }

                        // Set the popover element HTML.
                        $popover.html(template);

                        // Add the "theme" class to the element.
                        if (options.theme) {
                            $popover.addClass(options.theme);
                        }

                        // Compile the element.
                        $compile($popover)(scope);

                        // Cache the triangle element (works in ie8+).
                        $triangle = $el(
                          $popover[0].querySelectorAll('.triangle')
                        );

                        // Append it to the DOM
                        $container.append($popover);
                    })
                  ;
              }
          };
      }
    ]);
})(window, window.angular);
/**
 * oclazyload - Load modules on demand (lazy load) with angularJS
 * @version v1.0.4
 * @link https://github.com/ocombe/ocLazyLoad
 * @license MIT
 * @author Olivier Combe <olivier.combe@gmail.com>
 */
(function (angular, window) {
    'use strict';

    var regModules = ['ng', 'oc.lazyLoad'],
        regInvokes = {},
        regConfigs = [],
        modulesToLoad = [],
        // modules to load from angular.module or other sources
    realModules = [],
        // real modules called from angular.module
    recordDeclarations = [],
        broadcast = angular.noop,
        runBlocks = {},
        justLoaded = [];

    var ocLazyLoad = angular.module('oc.lazyLoad', ['ng']);

    ocLazyLoad.provider('$ocLazyLoad', ["$controllerProvider", "$provide", "$compileProvider", "$filterProvider", "$injector", "$animateProvider", function ($controllerProvider, $provide, $compileProvider, $filterProvider, $injector, $animateProvider) {
        var modules = {},
            providers = {
            $controllerProvider: $controllerProvider,
            $compileProvider: $compileProvider,
            $filterProvider: $filterProvider,
            $provide: $provide, // other things (constant, decorator, provider, factory, service)
            $injector: $injector,
            $animateProvider: $animateProvider
        },
            debug = false,
            events = false,
            moduleCache = [],
            modulePromises = {};

        moduleCache.push = function (value) {
            if (this.indexOf(value) === -1) {
                Array.prototype.push.apply(this, arguments);
            }
        };

        this.config = function (config) {
            // If we want to define modules configs
            if (angular.isDefined(config.modules)) {
                if (angular.isArray(config.modules)) {
                    angular.forEach(config.modules, function (moduleConfig) {
                        modules[moduleConfig.name] = moduleConfig;
                    });
                } else {
                    modules[config.modules.name] = config.modules;
                }
            }

            if (angular.isDefined(config.debug)) {
                debug = config.debug;
            }

            if (angular.isDefined(config.events)) {
                events = config.events;
            }
        };

        /**
         * Get the list of existing registered modules
         * @param element
         */
        this._init = function _init(element) {
            // this is probably useless now because we override angular.bootstrap
            if (modulesToLoad.length === 0) {
                var elements = [element],
                    names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'],
                    NG_APP_CLASS_REGEXP = /\sng[:\-]app(:\s*([\w\d_]+);?)?\s/,
                    append = function append(elm) {
                    return elm && elements.push(elm);
                };

                angular.forEach(names, function (name) {
                    names[name] = true;
                    append(document.getElementById(name));
                    name = name.replace(':', '\\:');
                    if (typeof element[0] !== 'undefined' && element[0].querySelectorAll) {
                        angular.forEach(element[0].querySelectorAll('.' + name), append);
                        angular.forEach(element[0].querySelectorAll('.' + name + '\\:'), append);
                        angular.forEach(element[0].querySelectorAll('[' + name + ']'), append);
                    }
                });

                angular.forEach(elements, function (elm) {
                    if (modulesToLoad.length === 0) {
                        var className = ' ' + element.className + ' ';
                        var match = NG_APP_CLASS_REGEXP.exec(className);
                        if (match) {
                            modulesToLoad.push((match[2] || '').replace(/\s+/g, ','));
                        } else {
                            angular.forEach(elm.attributes, function (attr) {
                                if (modulesToLoad.length === 0 && names[attr.name]) {
                                    modulesToLoad.push(attr.value);
                                }
                            });
                        }
                    }
                });
            }

            if (modulesToLoad.length === 0 && !((window.jasmine || window.mocha) && angular.isDefined(angular.mock))) {
                console.error('No module found during bootstrap, unable to init ocLazyLoad. You should always use the ng-app directive or angular.boostrap when you use ocLazyLoad.');
            }

            var addReg = function addReg(moduleName) {
                if (regModules.indexOf(moduleName) === -1) {
                    // register existing modules
                    regModules.push(moduleName);
                    var mainModule = angular.module(moduleName);

                    // register existing components (directives, services, ...)
                    _invokeQueue(null, mainModule._invokeQueue, moduleName);
                    _invokeQueue(null, mainModule._configBlocks, moduleName); // angular 1.3+

                    angular.forEach(mainModule.requires, addReg);
                }
            };

            angular.forEach(modulesToLoad, function (moduleName) {
                addReg(moduleName);
            });

            modulesToLoad = []; // reset for next bootstrap
            recordDeclarations.pop(); // wait for the next lazy load
        };

        /**
         * Like JSON.stringify but that doesn't throw on circular references
         * @param obj
         */
        var stringify = function stringify(obj) {
            var cache = [];
            return JSON.stringify(obj, function (key, value) {
                if (angular.isObject(value) && value !== null) {
                    if (cache.indexOf(value) !== -1) {
                        // Circular reference found, discard key
                        return;
                    }
                    // Store value in our collection
                    cache.push(value);
                }
                return value;
            });
        };

        var hashCode = function hashCode(str) {
            var hash = 0,
                i,
                chr,
                len;
            if (str.length == 0) {
                return hash;
            }
            for (i = 0, len = str.length; i < len; i++) {
                chr = str.charCodeAt(i);
                hash = (hash << 5) - hash + chr;
                hash |= 0; // Convert to 32bit integer
            }
            return hash;
        };

        function _register(providers, registerModules, params) {
            if (registerModules) {
                var k,
                    moduleName,
                    moduleFn,
                    tempRunBlocks = [];
                for (k = registerModules.length - 1; k >= 0; k--) {
                    moduleName = registerModules[k];
                    if (!angular.isString(moduleName)) {
                        moduleName = getModuleName(moduleName);
                    }
                    if (!moduleName || justLoaded.indexOf(moduleName) !== -1 || modules[moduleName] && realModules.indexOf(moduleName) === -1) {
                        continue;
                    }
                    // new if not registered
                    var newModule = regModules.indexOf(moduleName) === -1;
                    moduleFn = ngModuleFct(moduleName);
                    if (newModule) {
                        regModules.push(moduleName);
                        _register(providers, moduleFn.requires, params);
                    }
                    if (moduleFn._runBlocks.length > 0) {
                        // new run blocks detected! Replace the old ones (if existing)
                        runBlocks[moduleName] = [];
                        while (moduleFn._runBlocks.length > 0) {
                            runBlocks[moduleName].push(moduleFn._runBlocks.shift());
                        }
                    }
                    if (angular.isDefined(runBlocks[moduleName]) && (newModule || params.rerun)) {
                        tempRunBlocks = tempRunBlocks.concat(runBlocks[moduleName]);
                    }
                    _invokeQueue(providers, moduleFn._invokeQueue, moduleName, params.reconfig);
                    _invokeQueue(providers, moduleFn._configBlocks, moduleName, params.reconfig); // angular 1.3+
                    broadcast(newModule ? 'ocLazyLoad.moduleLoaded' : 'ocLazyLoad.moduleReloaded', moduleName);
                    registerModules.pop();
                    justLoaded.push(moduleName);
                }
                // execute the run blocks at the end
                var instanceInjector = providers.getInstanceInjector();
                angular.forEach(tempRunBlocks, function (fn) {
                    instanceInjector.invoke(fn);
                });
            }
        }

        function _registerInvokeList(args, moduleName) {
            var invokeList = args[2][0],
                type = args[1],
                newInvoke = false;
            if (angular.isUndefined(regInvokes[moduleName])) {
                regInvokes[moduleName] = {};
            }
            if (angular.isUndefined(regInvokes[moduleName][type])) {
                regInvokes[moduleName][type] = {};
            }
            var onInvoke = function onInvoke(invokeName, signature) {
                if (!regInvokes[moduleName][type].hasOwnProperty(invokeName)) {
                    regInvokes[moduleName][type][invokeName] = [];
                }
                if (regInvokes[moduleName][type][invokeName].indexOf(signature) === -1) {
                    newInvoke = true;
                    regInvokes[moduleName][type][invokeName].push(signature);
                    broadcast('ocLazyLoad.componentLoaded', [moduleName, type, invokeName]);
                }
            };

            function signature(data) {
                if (angular.isArray(data)) {
                    // arrays are objects, we need to test for it first
                    return hashCode(data.toString());
                } else if (angular.isObject(data)) {
                    // constants & values for example
                    return hashCode(stringify(data));
                } else {
                    if (angular.isDefined(data) && data !== null) {
                        return hashCode(data.toString());
                    } else {
                        // null & undefined constants
                        return data;
                    }
                }
            }

            if (angular.isString(invokeList)) {
                onInvoke(invokeList, signature(args[2][1]));
            } else if (angular.isObject(invokeList)) {
                angular.forEach(invokeList, function (invoke, key) {
                    if (angular.isString(invoke)) {
                        // decorators for example
                        onInvoke(invoke, signature(invokeList[1]));
                    } else {
                        // components registered as object lists {"componentName": function() {}}
                        onInvoke(key, signature(invoke));
                    }
                });
            } else {
                return false;
            }
            return newInvoke;
        }

        function _invokeQueue(providers, queue, moduleName, reconfig) {
            if (!queue) {
                return;
            }

            var i, len, args, provider;
            for (i = 0, len = queue.length; i < len; i++) {
                args = queue[i];
                if (angular.isArray(args)) {
                    if (providers !== null) {
                        if (providers.hasOwnProperty(args[0])) {
                            provider = providers[args[0]];
                        } else {
                            throw new Error('unsupported provider ' + args[0]);
                        }
                    }
                    var isNew = _registerInvokeList(args, moduleName);
                    if (args[1] !== 'invoke') {
                        if (isNew && angular.isDefined(provider)) {
                            provider[args[1]].apply(provider, args[2]);
                        }
                    } else {
                        // config block
                        var callInvoke = function callInvoke(fct) {
                            var invoked = regConfigs.indexOf('' + moduleName + '-' + fct);
                            if (invoked === -1 || reconfig) {
                                if (invoked === -1) {
                                    regConfigs.push('' + moduleName + '-' + fct);
                                }
                                if (angular.isDefined(provider)) {
                                    provider[args[1]].apply(provider, args[2]);
                                }
                            }
                        };
                        if (angular.isFunction(args[2][0])) {
                            callInvoke(args[2][0]);
                        } else if (angular.isArray(args[2][0])) {
                            for (var j = 0, jlen = args[2][0].length; j < jlen; j++) {
                                if (angular.isFunction(args[2][0][j])) {
                                    callInvoke(args[2][0][j]);
                                }
                            }
                        }
                    }
                }
            }
        }

        function getModuleName(module) {
            var moduleName = null;
            if (angular.isString(module)) {
                moduleName = module;
            } else if (angular.isObject(module) && module.hasOwnProperty('name') && angular.isString(module.name)) {
                moduleName = module.name;
            }
            return moduleName;
        }

        function moduleExists(moduleName) {
            if (!angular.isString(moduleName)) {
                return false;
            }
            try {
                return ngModuleFct(moduleName);
            } catch (e) {
                if (/No module/.test(e) || e.message.indexOf('$injector:nomod') > -1) {
                    return false;
                }
            }
        }

        this.$get = ["$log", "$rootElement", "$rootScope", "$cacheFactory", "$q", function ($log, $rootElement, $rootScope, $cacheFactory, $q) {
            var instanceInjector,
                filesCache = $cacheFactory('ocLazyLoad');

            if (!debug) {
                $log = {};
                $log['error'] = angular.noop;
                $log['warn'] = angular.noop;
                $log['info'] = angular.noop;
            }

            // Make this lazy because when $get() is called the instance injector hasn't been assigned to the rootElement yet
            providers.getInstanceInjector = function () {
                return instanceInjector ? instanceInjector : instanceInjector = $rootElement.data('$injector') || angular.injector();
            };

            broadcast = function broadcast(eventName, params) {
                if (events) {
                    $rootScope.$broadcast(eventName, params);
                }
                if (debug) {
                    $log.info(eventName, params);
                }
            };

            function reject(e) {
                var deferred = $q.defer();
                $log.error(e.message);
                deferred.reject(e);
                return deferred.promise;
            }

            return {
                _broadcast: broadcast,

                _$log: $log,

                /**
                 * Returns the files cache used by the loaders to store the files currently loading
                 * @returns {*}
                 */
                _getFilesCache: function getFilesCache() {
                    return filesCache;
                },

                /**
                 * Let the service know that it should monitor angular.module because files are loading
                 * @param watch boolean
                 */
                toggleWatch: function toggleWatch(watch) {
                    if (watch) {
                        recordDeclarations.push(true);
                    } else {
                        recordDeclarations.pop();
                    }
                },

                /**
                 * Let you get a module config object
                 * @param moduleName String the name of the module
                 * @returns {*}
                 */
                getModuleConfig: function getModuleConfig(moduleName) {
                    if (!angular.isString(moduleName)) {
                        throw new Error('You need to give the name of the module to get');
                    }
                    if (!modules[moduleName]) {
                        return null;
                    }
                    return angular.copy(modules[moduleName]);
                },

                /**
                 * Let you define a module config object
                 * @param moduleConfig Object the module config object
                 * @returns {*}
                 */
                setModuleConfig: function setModuleConfig(moduleConfig) {
                    if (!angular.isObject(moduleConfig)) {
                        throw new Error('You need to give the module config object to set');
                    }
                    modules[moduleConfig.name] = moduleConfig;
                    return moduleConfig;
                },

                /**
                 * Returns the list of loaded modules
                 * @returns {string[]}
                 */
                getModules: function getModules() {
                    return regModules;
                },

                /**
                 * Let you check if a module has been loaded into Angular or not
                 * @param modulesNames String/Object a module name, or a list of module names
                 * @returns {boolean}
                 */
                isLoaded: function isLoaded(modulesNames) {
                    var moduleLoaded = function moduleLoaded(module) {
                        var isLoaded = regModules.indexOf(module) > -1;
                        if (!isLoaded) {
                            isLoaded = !!moduleExists(module);
                        }
                        return isLoaded;
                    };
                    if (angular.isString(modulesNames)) {
                        modulesNames = [modulesNames];
                    }
                    if (angular.isArray(modulesNames)) {
                        var i, len;
                        for (i = 0, len = modulesNames.length; i < len; i++) {
                            if (!moduleLoaded(modulesNames[i])) {
                                return false;
                            }
                        }
                        return true;
                    } else {
                        throw new Error('You need to define the module(s) name(s)');
                    }
                },

                /**
                 * Given a module, return its name
                 * @param module
                 * @returns {String}
                 */
                _getModuleName: getModuleName,

                /**
                 * Returns a module if it exists
                 * @param moduleName
                 * @returns {module}
                 */
                _getModule: function getModule(moduleName) {
                    try {
                        return ngModuleFct(moduleName);
                    } catch (e) {
                        // this error message really suxx
                        if (/No module/.test(e) || e.message.indexOf('$injector:nomod') > -1) {
                            e.message = 'The module "' + stringify(moduleName) + '" that you are trying to load does not exist. ' + e.message;
                        }
                        throw e;
                    }
                },

                /**
                 * Check if a module exists and returns it if it does
                 * @param moduleName
                 * @returns {boolean}
                 */
                moduleExists: moduleExists,

                /**
                 * Load the dependencies, and might try to load new files depending on the config
                 * @param moduleName (String or Array of Strings)
                 * @param localParams
                 * @returns {*}
                 * @private
                 */
                _loadDependencies: function _loadDependencies(moduleName, localParams) {
                    var loadedModule,
                        requires,
                        diff,
                        promisesList = [],
                        self = this;

                    moduleName = self._getModuleName(moduleName);

                    if (moduleName === null) {
                        return $q.when();
                    } else {
                        try {
                            loadedModule = self._getModule(moduleName);
                        } catch (e) {
                            return reject(e);
                        }
                        // get unloaded requires
                        requires = self.getRequires(loadedModule);
                    }

                    angular.forEach(requires, function (requireEntry) {
                        // If no configuration is provided, try and find one from a previous load.
                        // If there isn't one, bail and let the normal flow run
                        if (angular.isString(requireEntry)) {
                            var config = self.getModuleConfig(requireEntry);
                            if (config === null) {
                                moduleCache.push(requireEntry); // We don't know about this module, but something else might, so push it anyway.
                                return;
                            }
                            requireEntry = config;
                            // ignore the name because it's probably not a real module name
                            config.name = undefined;
                        }

                        // Check if this dependency has been loaded previously
                        if (self.moduleExists(requireEntry.name)) {
                            // compare against the already loaded module to see if the new definition adds any new files
                            diff = requireEntry.files.filter(function (n) {
                                return self.getModuleConfig(requireEntry.name).files.indexOf(n) < 0;
                            });

                            // If the module was redefined, advise via the console
                            if (diff.length !== 0) {
                                self._$log.warn('Module "', moduleName, '" attempted to redefine configuration for dependency. "', requireEntry.name, '"\n Additional Files Loaded:', diff);
                            }

                            // Push everything to the file loader, it will weed out the duplicates.
                            if (angular.isDefined(self.filesLoader)) {
                                // if a files loader is defined
                                promisesList.push(self.filesLoader(requireEntry, localParams).then(function () {
                                    return self._loadDependencies(requireEntry);
                                }));
                            } else {
                                return reject(new Error('Error: New dependencies need to be loaded from external files (' + requireEntry.files + '), but no loader has been defined.'));
                            }
                            return;
                        } else if (angular.isArray(requireEntry)) {
                            var files = [];
                            angular.forEach(requireEntry, function (entry) {
                                // let's check if the entry is a file name or a config name
                                var config = self.getModuleConfig(entry);
                                if (config === null) {
                                    files.push(entry);
                                } else if (config.files) {
                                    files = files.concat(config.files);
                                }
                            });
                            if (files.length > 0) {
                                requireEntry = {
                                    files: files
                                };
                            }
                        } else if (angular.isObject(requireEntry)) {
                            if (requireEntry.hasOwnProperty('name') && requireEntry['name']) {
                                // The dependency doesn't exist in the module cache and is a new configuration, so store and push it.
                                self.setModuleConfig(requireEntry);
                                moduleCache.push(requireEntry['name']);
                            }
                        }

                        // Check if the dependency has any files that need to be loaded. If there are, push a new promise to the promise list.
                        if (angular.isDefined(requireEntry.files) && requireEntry.files.length !== 0) {
                            if (angular.isDefined(self.filesLoader)) {
                                // if a files loader is defined
                                promisesList.push(self.filesLoader(requireEntry, localParams).then(function () {
                                    return self._loadDependencies(requireEntry);
                                }));
                            } else {
                                return reject(new Error('Error: the module "' + requireEntry.name + '" is defined in external files (' + requireEntry.files + '), but no loader has been defined.'));
                            }
                        }
                    });

                    // Create a wrapper promise to watch the promise list and resolve it once everything is done.
                    return $q.all(promisesList);
                },

                /**
                 * Inject new modules into Angular
                 * @param moduleName
                 * @param localParams
                 */
                inject: function inject(moduleName) {
                    var localParams = arguments[1] === undefined ? {} : arguments[1];

                    var self = this,
                        deferred = $q.defer();
                    if (angular.isDefined(moduleName) && moduleName !== null) {
                        if (angular.isArray(moduleName)) {
                            var promisesList = [];
                            angular.forEach(moduleName, function (module) {
                                promisesList.push(self.inject(module));
                            });
                            return $q.all(promisesList);
                        } else {
                            self._addToLoadList(self._getModuleName(moduleName), true);
                        }
                    }
                    if (modulesToLoad.length > 0) {
                        var res = modulesToLoad.slice(); // clean copy
                        var loadNext = function loadNext(moduleName) {
                            moduleCache.push(moduleName);
                            modulePromises[moduleName] = deferred.promise;
                            self._loadDependencies(moduleName, localParams).then(function success() {
                                try {
                                    justLoaded = [];
                                    _register(providers, moduleCache, localParams);
                                } catch (e) {
                                    self._$log.error(e.message);
                                    deferred.reject(e);
                                    return;
                                }

                                if (modulesToLoad.length > 0) {
                                    loadNext(modulesToLoad.shift()); // load the next in list
                                } else {
                                    deferred.resolve(res); // everything has been loaded, resolve
                                }
                            }, function error(err) {
                                deferred.reject(err);
                            });
                        };

                        // load the first in list
                        loadNext(modulesToLoad.shift());
                    } else if (localParams && localParams.name && modulePromises[localParams.name]) {
                        return modulePromises[localParams.name];
                    } else {
                        deferred.resolve();
                    }
                    return deferred.promise;
                },

                /**
                 * Get the list of required modules/services/... for this module
                 * @param module
                 * @returns {Array}
                 */
                getRequires: function getRequires(module) {
                    var requires = [];
                    angular.forEach(module.requires, function (requireModule) {
                        if (regModules.indexOf(requireModule) === -1) {
                            requires.push(requireModule);
                        }
                    });
                    return requires;
                },

                /**
                 * Invoke the new modules & component by their providers
                 * @param providers
                 * @param queue
                 * @param moduleName
                 * @param reconfig
                 * @private
                 */
                _invokeQueue: _invokeQueue,

                /**
                 * Check if a module has been invoked and registers it if not
                 * @param args
                 * @param moduleName
                 * @returns {boolean} is new
                 */
                _registerInvokeList: _registerInvokeList,

                /**
                 * Register a new module and loads it, executing the run/config blocks if needed
                 * @param providers
                 * @param registerModules
                 * @param params
                 * @private
                 */
                _register: _register,

                /**
                 * Add a module name to the list of modules that will be loaded in the next inject
                 * @param name
                 * @param force
                 * @private
                 */
                _addToLoadList: _addToLoadList
            };
        }];

        // Let's get the list of loaded modules & components
        this._init(angular.element(window.document));
    }]);

    var bootstrapFct = angular.bootstrap;
    angular.bootstrap = function (element, modules, config) {
        // we use slice to make a clean copy
        angular.forEach(modules.slice(), function (module) {
            _addToLoadList(module, true, true);
        });
        return bootstrapFct(element, modules, config);
    };

    var _addToLoadList = function _addToLoadList(name, force, real) {
        if ((recordDeclarations.length > 0 || force) && angular.isString(name) && modulesToLoad.indexOf(name) === -1) {
            modulesToLoad.push(name);
            if (real) {
                realModules.push(name);
            }
        }
    };

    var ngModuleFct = angular.module;
    angular.module = function (name, requires, configFn) {
        _addToLoadList(name, false, true);
        return ngModuleFct(name, requires, configFn);
    };

    // CommonJS package manager support:
    if (typeof module !== 'undefined' && typeof exports !== 'undefined' && module.exports === exports) {
        module.exports = 'oc.lazyLoad';
    }
})(angular, window);
(function (angular) {
    'use strict';

    angular.module('oc.lazyLoad').directive('ocLazyLoad', ["$ocLazyLoad", "$compile", "$animate", "$parse", function ($ocLazyLoad, $compile, $animate, $parse) {
        return {
            restrict: 'A',
            terminal: true,
            priority: 1000,
            compile: function compile(element, attrs) {
                // we store the content and remove it before compilation
                var content = element[0].innerHTML;
                element.html('');

                return function ($scope, $element, $attr) {
                    var model = $parse($attr.ocLazyLoad);
                    $scope.$watch(function () {
                        return model($scope) || $attr.ocLazyLoad; // it can be a module name (string), an object, an array, or a scope reference to any of this
                    }, function (moduleName) {
                        if (angular.isDefined(moduleName)) {
                            $ocLazyLoad.load(moduleName).then(function () {
                                $animate.enter(content, $element);
                                var contents = element.contents();
                                angular.forEach(contents, function (content) {
                                    if (content.nodeType !== 3) {
                                        // 3 is a text node
                                        $compile(content)($scope);
                                    }
                                });
                            });
                        }
                    }, true);
                };
            }
        };
    }]);
})(angular);
(function (angular) {
    'use strict';

    angular.module('oc.lazyLoad').config(["$provide", function ($provide) {
        $provide.decorator('$ocLazyLoad', ["$delegate", "$q", "$window", "$interval", function ($delegate, $q, $window, $interval) {
            var uaCssChecked = false,
                useCssLoadPatch = false,
                anchor = $window.document.getElementsByTagName('head')[0] || $window.document.getElementsByTagName('body')[0];

            /**
             * Load a js/css file
             * @param type
             * @param path
             * @param params
             * @returns promise
             */
            $delegate.buildElement = function buildElement(type, path, params) {
                var deferred = $q.defer(),
                    el,
                    loaded,
                    filesCache = $delegate._getFilesCache(),
                    cacheBuster = function cacheBuster(url) {
                    var dc = new Date().getTime();
                    if (url.indexOf('?') >= 0) {
                        if (url.substring(0, url.length - 1) === '&') {
                            return '' + url + '_dc=' + dc;
                        }
                        return '' + url + '&_dc=' + dc;
                    } else {
                        return '' + url + '?_dc=' + dc;
                    }
                };

                // Store the promise early so the file load can be detected by other parallel lazy loads
                // (ie: multiple routes on one page) a 'true' value isn't sufficient
                // as it causes false positive load results.
                if (angular.isUndefined(filesCache.get(path))) {
                    filesCache.put(path, deferred.promise);
                }

                // Switch in case more content types are added later
                switch (type) {
                    case 'css':
                        el = $window.document.createElement('link');
                        el.type = 'text/css';
                        el.rel = 'stylesheet';
                        el.href = params.cache === false ? cacheBuster(path) : path;
                        break;
                    case 'js':
                        el = $window.document.createElement('script');
                        el.src = params.cache === false ? cacheBuster(path) : path;
                        break;
                    default:
                        filesCache.remove(path);
                        deferred.reject(new Error('Requested type "' + type + '" is not known. Could not inject "' + path + '"'));
                        break;
                }
                el.onload = el['onreadystatechange'] = function (e) {
                    if (el['readyState'] && !/^c|loade/.test(el['readyState']) || loaded) return;
                    el.onload = el['onreadystatechange'] = null;
                    loaded = 1;
                    $delegate._broadcast('ocLazyLoad.fileLoaded', path);
                    deferred.resolve();
                };
                el.onerror = function () {
                    filesCache.remove(path);
                    deferred.reject(new Error('Unable to load ' + path));
                };
                el.async = params.serie ? 0 : 1;

                var insertBeforeElem = anchor.lastChild;
                if (params.insertBefore) {
                    var element = angular.element(angular.isDefined(window.jQuery) ? params.insertBefore : document.querySelector(params.insertBefore));
                    if (element && element.length > 0) {
                        insertBeforeElem = element[0];
                    }
                }
                insertBeforeElem.parentNode.insertBefore(el, insertBeforeElem);

                /*
                 The event load or readystatechange doesn't fire in:
                 - iOS < 6       (default mobile browser)
                 - Android < 4.4 (default mobile browser)
                 - Safari < 6    (desktop browser)
                 */
                if (type == 'css') {
                    if (!uaCssChecked) {
                        var ua = $window.navigator.userAgent.toLowerCase();

                        // iOS < 6
                        if (/iP(hone|od|ad)/.test($window.navigator.platform)) {
                            var v = $window.navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/);
                            var iOSVersion = parseFloat([parseInt(v[1], 10), parseInt(v[2], 10), parseInt(v[3] || 0, 10)].join('.'));
                            useCssLoadPatch = iOSVersion < 6;
                        } else if (ua.indexOf('android') > -1) {
                            // Android < 4.4
                            var androidVersion = parseFloat(ua.slice(ua.indexOf('android') + 8));
                            useCssLoadPatch = androidVersion < 4.4;
                        } else if (ua.indexOf('safari') > -1) {
                            var versionMatch = ua.match(/version\/([\.\d]+)/i);
                            useCssLoadPatch = versionMatch && versionMatch[1] && parseFloat(versionMatch[1]) < 6;
                        }
                    }

                    if (useCssLoadPatch) {
                        var tries = 1000; // * 20 = 20000 miliseconds
                        var interval = $interval(function () {
                            try {
                                el.sheet.cssRules;
                                $interval.cancel(interval);
                                el.onload();
                            } catch (e) {
                                if (--tries <= 0) {
                                    el.onerror();
                                }
                            }
                        }, 20);
                    }
                }

                return deferred.promise;
            };

            return $delegate;
        }]);
    }]);
})(angular);
(function (angular) {
    'use strict';

    angular.module('oc.lazyLoad').config(["$provide", function ($provide) {
        $provide.decorator('$ocLazyLoad', ["$delegate", "$q", function ($delegate, $q) {
            /**
             * The function that loads new files
             * @param config
             * @param params
             * @returns {*}
             */
            $delegate.filesLoader = function filesLoader(config) {
                var params = arguments[1] === undefined ? {} : arguments[1];

                var cssFiles = [],
                    templatesFiles = [],
                    jsFiles = [],
                    promises = [],
                    cachePromise = null,
                    filesCache = $delegate._getFilesCache();

                $delegate.toggleWatch(true); // start watching angular.module calls

                angular.extend(params, config);

                var pushFile = function pushFile(path) {
                    var file_type = null,
                        m;
                    if (angular.isObject(path)) {
                        file_type = path.type;
                        path = path.path;
                    }
                    cachePromise = filesCache.get(path);
                    if (angular.isUndefined(cachePromise) || params.cache === false) {

                        // always check for requirejs syntax just in case
                        if ((m = /^(css|less|html|htm|js)?(?=!)/.exec(path)) !== null) {
                            // Detect file type using preceding type declaration (ala requireJS)
                            file_type = m[1];
                            path = path.substr(m[1].length + 1, path.length); // Strip the type from the path
                        }

                        if (!file_type) {
                            if ((m = /[.](css|less|html|htm|js)?((\?|#).*)?$/.exec(path)) !== null) {
                                // Detect file type via file extension
                                file_type = m[1];
                            } else if (!$delegate.jsLoader.hasOwnProperty('ocLazyLoadLoader') && $delegate.jsLoader.hasOwnProperty('load')) {
                                // requirejs
                                file_type = 'js';
                            } else {
                                $delegate._$log.error('File type could not be determined. ' + path);
                                return;
                            }
                        }

                        if ((file_type === 'css' || file_type === 'less') && cssFiles.indexOf(path) === -1) {
                            cssFiles.push(path);
                        } else if ((file_type === 'html' || file_type === 'htm') && templatesFiles.indexOf(path) === -1) {
                            templatesFiles.push(path);
                        } else if (file_type === 'js' || jsFiles.indexOf(path) === -1) {
                            jsFiles.push(path);
                        } else {
                            $delegate._$log.error('File type is not valid. ' + path);
                        }
                    } else if (cachePromise) {
                        promises.push(cachePromise);
                    }
                };

                if (params.serie) {
                    pushFile(params.files.shift());
                } else {
                    angular.forEach(params.files, function (path) {
                        pushFile(path);
                    });
                }

                if (cssFiles.length > 0) {
                    var cssDeferred = $q.defer();
                    $delegate.cssLoader(cssFiles, function (err) {
                        if (angular.isDefined(err) && $delegate.cssLoader.hasOwnProperty('ocLazyLoadLoader')) {
                            $delegate._$log.error(err);
                            cssDeferred.reject(err);
                        } else {
                            cssDeferred.resolve();
                        }
                    }, params);
                    promises.push(cssDeferred.promise);
                }

                if (templatesFiles.length > 0) {
                    var templatesDeferred = $q.defer();
                    $delegate.templatesLoader(templatesFiles, function (err) {
                        if (angular.isDefined(err) && $delegate.templatesLoader.hasOwnProperty('ocLazyLoadLoader')) {
                            $delegate._$log.error(err);
                            templatesDeferred.reject(err);
                        } else {
                            templatesDeferred.resolve();
                        }
                    }, params);
                    promises.push(templatesDeferred.promise);
                }

                if (jsFiles.length > 0) {
                    var jsDeferred = $q.defer();
                    $delegate.jsLoader(jsFiles, function (err) {
                        if (angular.isDefined(err) && $delegate.jsLoader.hasOwnProperty('ocLazyLoadLoader')) {
                            $delegate._$log.error(err);
                            jsDeferred.reject(err);
                        } else {
                            jsDeferred.resolve();
                        }
                    }, params);
                    promises.push(jsDeferred.promise);
                }

                if (promises.length === 0) {
                    var deferred = $q.defer(),
                        err = 'Error: no file to load has been found, if you\'re trying to load an existing module you should use the \'inject\' method instead of \'load\'.';
                    $delegate._$log.error(err);
                    deferred.reject(err);
                    return deferred.promise;
                } else if (params.serie && params.files.length > 0) {
                    return $q.all(promises).then(function () {
                        return $delegate.filesLoader(config, params);
                    });
                } else {
                    return $q.all(promises)['finally'](function (res) {
                        $delegate.toggleWatch(false); // stop watching angular.module calls
                        return res;
                    });
                }
            };

            /**
             * Load a module or a list of modules into Angular
             * @param module Mixed the name of a predefined module config object, or a module config object, or an array of either
             * @param params Object optional parameters
             * @returns promise
             */
            $delegate.load = function (originalModule) {
                var originalParams = arguments[1] === undefined ? {} : arguments[1];

                var self = this,
                    config = null,
                    deferredList = [],
                    deferred = $q.defer(),
                    errText;

                // clean copy
                var module = angular.copy(originalModule);
                var params = angular.copy(originalParams);

                // If module is an array, break it down
                if (angular.isArray(module)) {
                    // Resubmit each entry as a single module
                    angular.forEach(module, function (m) {
                        deferredList.push(self.load(m, params));
                    });

                    // Resolve the promise once everything has loaded
                    $q.all(deferredList).then(function (res) {
                        deferred.resolve(res);
                    }, function (err) {
                        deferred.reject(err);
                    });

                    return deferred.promise;
                }

                // Get or Set a configuration depending on what was passed in
                if (angular.isString(module)) {
                    config = self.getModuleConfig(module);
                    if (!config) {
                        config = {
                            files: [module]
                        };
                    }
                } else if (angular.isObject(module)) {
                    // case {type: 'js', path: lazyLoadUrl + 'testModule.fakejs'}
                    if (angular.isDefined(module.path) && angular.isDefined(module.type)) {
                        config = {
                            files: [module]
                        };
                    } else {
                        config = self.setModuleConfig(module);
                    }
                }

                if (config === null) {
                    var moduleName = self._getModuleName(module);
                    errText = 'Module "' + (moduleName || 'unknown') + '" is not configured, cannot load.';
                    $delegate._$log.error(errText);
                    deferred.reject(new Error(errText));
                    return deferred.promise;
                } else {
                    // deprecated
                    if (angular.isDefined(config.template)) {
                        if (angular.isUndefined(config.files)) {
                            config.files = [];
                        }
                        if (angular.isString(config.template)) {
                            config.files.push(config.template);
                        } else if (angular.isArray(config.template)) {
                            config.files.concat(config.template);
                        }
                    }
                }

                var localParams = angular.extend({}, params, config);

                // if someone used an external loader and called the load function with just the module name
                if (angular.isUndefined(config.files) && angular.isDefined(config.name) && $delegate.moduleExists(config.name)) {
                    return $delegate.inject(config.name, localParams);
                }

                $delegate.filesLoader(config, localParams).then(function () {
                    $delegate.inject(null, localParams).then(function (res) {
                        deferred.resolve(res);
                    }, function (err) {
                        deferred.reject(err);
                    });
                }, function (err) {
                    deferred.reject(err);
                });

                return deferred.promise;
            };

            // return the patched service
            return $delegate;
        }]);
    }]);
})(angular);
(function (angular) {
    'use strict';

    angular.module('oc.lazyLoad').config(["$provide", function ($provide) {
        $provide.decorator('$ocLazyLoad', ["$delegate", "$q", function ($delegate, $q) {
            /**
             * cssLoader function
             * @type Function
             * @param paths array list of css files to load
             * @param callback to call when everything is loaded. We use a callback and not a promise
             * @param params object config parameters
             * because the user can overwrite cssLoader and it will probably not use promises :(
             */
            $delegate.cssLoader = function (paths, callback, params) {
                var promises = [];
                angular.forEach(paths, function (path) {
                    promises.push($delegate.buildElement('css', path, params));
                });
                $q.all(promises).then(function () {
                    callback();
                }, function (err) {
                    callback(err);
                });
            };
            $delegate.cssLoader.ocLazyLoadLoader = true;

            return $delegate;
        }]);
    }]);
})(angular);
(function (angular) {
    'use strict';

    angular.module('oc.lazyLoad').config(["$provide", function ($provide) {
        $provide.decorator('$ocLazyLoad', ["$delegate", "$q", function ($delegate, $q) {
            /**
             * jsLoader function
             * @type Function
             * @param paths array list of js files to load
             * @param callback to call when everything is loaded. We use a callback and not a promise
             * @param params object config parameters
             * because the user can overwrite jsLoader and it will probably not use promises :(
             */
            $delegate.jsLoader = function (paths, callback, params) {
                var promises = [];
                angular.forEach(paths, function (path) {
                    promises.push($delegate.buildElement('js', path, params));
                });
                $q.all(promises).then(function () {
                    callback();
                }, function (err) {
                    callback(err);
                });
            };
            $delegate.jsLoader.ocLazyLoadLoader = true;

            return $delegate;
        }]);
    }]);
})(angular);
(function (angular) {
    'use strict';

    angular.module('oc.lazyLoad').config(["$provide", function ($provide) {
        $provide.decorator('$ocLazyLoad', ["$delegate", "$templateCache", "$q", "$http", function ($delegate, $templateCache, $q, $http) {
            /**
             * templatesLoader function
             * @type Function
             * @param paths array list of css files to load
             * @param callback to call when everything is loaded. We use a callback and not a promise
             * @param params object config parameters for $http
             * because the user can overwrite templatesLoader and it will probably not use promises :(
             */
            $delegate.templatesLoader = function (paths, callback, params) {
                var promises = [],
                    filesCache = $delegate._getFilesCache();

                angular.forEach(paths, function (url) {
                    var deferred = $q.defer();
                    promises.push(deferred.promise);
                    $http.get(url, params).success(function (data) {
                        if (angular.isString(data) && data.length > 0) {
                            angular.forEach(angular.element(data), function (node) {
                                if (node.nodeName === 'SCRIPT' && node.type === 'text/ng-template') {
                                    $templateCache.put(node.id, node.innerHTML);
                                }
                            });
                        }
                        if (angular.isUndefined(filesCache.get(url))) {
                            filesCache.put(url, true);
                        }
                        deferred.resolve();
                    }).error(function (err) {
                        deferred.reject(new Error('Unable to load template file "' + url + '": ' + err));
                    });
                });
                return $q.all(promises).then(function () {
                    callback();
                }, function (err) {
                    callback(err);
                });
            };
            $delegate.templatesLoader.ocLazyLoadLoader = true;

            return $delegate;
        }]);
    }]);
})(angular);
// Array.indexOf polyfill for IE8
if (!Array.prototype.indexOf) {
        Array.prototype.indexOf = function (searchElement, fromIndex) {
                var k;

                // 1. Let O be the result of calling ToObject passing
                //    the this value as the argument.
                if (this == null) {
                        throw new TypeError('"this" is null or not defined');
                }

                var O = Object(this);

                // 2. Let lenValue be the result of calling the Get
                //    internal method of O with the argument "length".
                // 3. Let len be ToUint32(lenValue).
                var len = O.length >>> 0;

                // 4. If len is 0, return -1.
                if (len === 0) {
                        return -1;
                }

                // 5. If argument fromIndex was passed let n be
                //    ToInteger(fromIndex); else let n be 0.
                var n = +fromIndex || 0;

                if (Math.abs(n) === Infinity) {
                        n = 0;
                }

                // 6. If n >= len, return -1.
                if (n >= len) {
                        return -1;
                }

                // 7. If n >= 0, then Let k be n.
                // 8. Else, n<0, Let k be len - abs(n).
                //    If k is less than 0, then let k be 0.
                k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);

                // 9. Repeat, while k < len
                while (k < len) {
                        // a. Let Pk be ToString(k).
                        //   This is implicit for LHS operands of the in operator
                        // b. Let kPresent be the result of calling the
                        //    HasProperty internal method of O with argument Pk.
                        //   This step can be combined with c
                        // c. If kPresent is true, then
                        //    i.  Let elementK be the result of calling the Get
                        //        internal method of O with the argument ToString(k).
                        //   ii.  Let same be the result of applying the
                        //        Strict Equality Comparison Algorithm to
                        //        searchElement and elementK.
                        //  iii.  If same is true, return k.
                        if (k in O && O[k] === searchElement) {
                                return k;
                        }
                        k++;
                }
                return -1;
        };
}
/**
* @version: 1.3.11
* @author: Dan Grossman http://www.dangrossman.info/
* @date: 2014-08-11
* @copyright: Copyright (c) 2012-2014 Dan Grossman. All rights reserved.
* @license: Licensed under Apache License v2.0. See http://www.apache.org/licenses/LICENSE-2.0
* @website: http://www.improvely.com/
*/

(function (root, factory) {

    if (typeof define === 'function' && define.amd) {
        define(['moment', 'jquery', 'exports'], function (momentjs, $, exports) {
            root.daterangepicker = factory(root, exports, momentjs, $);
        });

    } else if (typeof exports !== 'undefined') {
        var momentjs = require('moment');
        var jQuery;
        try {
            jQuery = require('jquery');
        } catch (err) {
            jQuery = window.jQuery;
            if (!jQuery) throw new Error('jQuery dependency not found');
        }

        factory(root, exports, momentjs, jQuery);

        // Finally, as a browser global.
    } else {
        root.daterangepicker = factory(root, {}, root.moment, (root.jQuery || root.Zepto || root.ender || root.$));
    }

}(this, function (root, daterangepicker, moment, $) {

    var DateRangePicker = function (element, options, cb) {

        // by default, the daterangepicker element is placed at the bottom of HTML body
        this.parentEl = 'body';

        //element that triggered the date range picker
        this.element = $(element);

        //tracks visible state
        this.isShowing = false;

        //create the picker HTML object
        var DRPTemplate = '<div class="daterangepicker dropdown-menu">' +
                '<div class="calendar left"></div>' +
                '<div class="calendar right"></div>' +
                '<div class="ranges">' +
                  '<div class="range_inputs">' +
                    '<div class="daterangepicker_start_input">' +
                      '<label for="daterangepicker_start"></label>' +
                      '<input class="input-mini" type="text" name="daterangepicker_start" value="" />' +
                    '</div>' +
                    '<div class="daterangepicker_end_input">' +
                      '<label for="daterangepicker_end"></label>' +
                      '<input class="input-mini" type="text" name="daterangepicker_end" value="" />' +
                    '</div>' +
                    '<button class="applyBtn" disabled="disabled"></button>&nbsp;' +
                    '<button class="cancelBtn"></button>' +
                  '</div>' +
                '</div>' +
              '</div>';

        //custom options
        if (typeof options !== 'object' || options === null)
            options = {};

        this.parentEl = (typeof options === 'object' && options.parentEl && $(options.parentEl).length) ? $(options.parentEl) : $(this.parentEl);
        this.container = $(DRPTemplate).appendTo(this.parentEl);

        this.setOptions(options, cb);

        //apply CSS classes and labels to buttons
        var c = this.container;
        $.each(this.buttonClasses, function (idx, val) {
            c.find('button').addClass(val);
        });
        this.container.find('.daterangepicker_start_input label').html(this.locale.fromLabel);
        this.container.find('.daterangepicker_end_input label').html(this.locale.toLabel);
        if (this.applyClass.length)
            this.container.find('.applyBtn').addClass(this.applyClass);
        if (this.cancelClass.length)
            this.container.find('.cancelBtn').addClass(this.cancelClass);
        this.container.find('.applyBtn').html(this.locale.applyLabel);
        this.container.find('.cancelBtn').html(this.locale.cancelLabel);

        //event listeners

        this.container.find('.calendar')
            .on('click.daterangepicker', '.prev', $.proxy(this.clickPrev, this))
            .on('click.daterangepicker', '.next', $.proxy(this.clickNext, this))
            .on('click.daterangepicker', 'td.available', $.proxy(this.clickDate, this))
            .on('mouseenter.daterangepicker', 'td.available', $.proxy(this.hoverDate, this))
            .on('mouseleave.daterangepicker', 'td.available', $.proxy(this.updateFormInputs, this))
            .on('change.daterangepicker', 'select.yearselect', $.proxy(this.updateMonthYear, this))
            .on('change.daterangepicker', 'select.monthselect', $.proxy(this.updateMonthYear, this))
            .on('change.daterangepicker', 'select.hourselect,select.minuteselect,select.ampmselect', $.proxy(this.updateTime, this));

        this.container.find('.ranges')
            .on('click.daterangepicker', 'button.applyBtn', $.proxy(this.clickApply, this))
            .on('click.daterangepicker', 'button.cancelBtn', $.proxy(this.clickCancel, this))
            .on('click.daterangepicker', '.daterangepicker_start_input,.daterangepicker_end_input', $.proxy(this.showCalendars, this))
            .on('change.daterangepicker', '.daterangepicker_start_input,.daterangepicker_end_input', $.proxy(this.inputsChanged, this))
            .on('keydown.daterangepicker', '.daterangepicker_start_input,.daterangepicker_end_input', $.proxy(this.inputsKeydown, this))
            .on('click.daterangepicker', 'li', $.proxy(this.clickRange, this))
            .on('mouseenter.daterangepicker', 'li', $.proxy(this.enterRange, this))
            .on('mouseleave.daterangepicker', 'li', $.proxy(this.updateFormInputs, this));

        if (this.element.is('input')) {
            this.element.on({
                'click.daterangepicker': $.proxy(this.show, this),
                'focus.daterangepicker': $.proxy(this.show, this),
                'keyup.daterangepicker': $.proxy(this.updateFromControl, this)
            });
        } else {
            this.element.on('click.daterangepicker', $.proxy(this.toggle, this));
        }

    };

    DateRangePicker.prototype = {

        constructor: DateRangePicker,

        setOptions: function (options, callback) {

            this.startDate = moment().startOf('day');
            this.endDate = moment().endOf('day');
            this.minDate = false;
            this.maxDate = false;
            this.dateLimit = false;

            this.showDropdowns = false;
            this.showWeekNumbers = false;
            this.timePicker = false;
            this.timePickerIncrement = 30;
            this.timePicker12Hour = true;
            this.singleDatePicker = false;
            this.ranges = {};

            this.opens = 'right';
            if (this.element.hasClass('pull-right'))
                this.opens = 'left';

            this.buttonClasses = ['btn', 'btn-small btn-sm'];
            this.applyClass = 'btn-success';
            this.cancelClass = 'btn-default';

            this.format = 'MM/DD/YYYY';
            this.separator = ' - ';

            this.locale = {
                applyLabel: 'Apply',
                cancelLabel: 'Cancel',
                fromLabel: 'From',
                toLabel: 'To',
                weekLabel: 'W',
                customRangeLabel: 'Custom Range',
                daysOfWeek: moment.weekdaysMin(),
                monthNames: moment.monthsShort(),
                firstDay: moment.localeData()._week.dow
            };

            this.cb = function () { };

            if (typeof options.format === 'string')
                this.format = options.format;

            if (typeof options.separator === 'string')
                this.separator = options.separator;

            if (typeof options.startDate === 'string')
                this.startDate = moment(options.startDate, this.format);

            if (typeof options.endDate === 'string')
                this.endDate = moment(options.endDate, this.format);

            if (typeof options.minDate === 'string')
                this.minDate = moment(options.minDate, this.format);

            if (typeof options.maxDate === 'string')
                this.maxDate = moment(options.maxDate, this.format);

            if (typeof options.startDate === 'object')
                this.startDate = moment(options.startDate);

            if (typeof options.endDate === 'object')
                this.endDate = moment(options.endDate);

            if (typeof options.minDate === 'object')
                this.minDate = moment(options.minDate);

            if (typeof options.maxDate === 'object')
                this.maxDate = moment(options.maxDate);

            if (typeof options.applyClass === 'string')
                this.applyClass = options.applyClass;

            if (typeof options.cancelClass === 'string')
                this.cancelClass = options.cancelClass;

            if (typeof options.dateLimit === 'object')
                this.dateLimit = options.dateLimit;

            if (typeof options.locale === 'object') {

                if (typeof options.locale.daysOfWeek === 'object') {
                    // Create a copy of daysOfWeek to avoid modification of original
                    // options object for reusability in multiple daterangepicker instances
                    this.locale.daysOfWeek = options.locale.daysOfWeek.slice();
                }

                if (typeof options.locale.monthNames === 'object') {
                    this.locale.monthNames = options.locale.monthNames.slice();
                }

                if (typeof options.locale.firstDay === 'number') {
                    this.locale.firstDay = options.locale.firstDay;
                }

                if (typeof options.locale.applyLabel === 'string') {
                    this.locale.applyLabel = options.locale.applyLabel;
                }

                if (typeof options.locale.cancelLabel === 'string') {
                    this.locale.cancelLabel = options.locale.cancelLabel;
                }

                if (typeof options.locale.fromLabel === 'string') {
                    this.locale.fromLabel = options.locale.fromLabel;
                }

                if (typeof options.locale.toLabel === 'string') {
                    this.locale.toLabel = options.locale.toLabel;
                }

                if (typeof options.locale.weekLabel === 'string') {
                    this.locale.weekLabel = options.locale.weekLabel;
                }

                if (typeof options.locale.customRangeLabel === 'string') {
                    this.locale.customRangeLabel = options.locale.customRangeLabel;
                }
            }

            if (typeof options.opens === 'string')
                this.opens = options.opens;

            if (typeof options.showWeekNumbers === 'boolean') {
                this.showWeekNumbers = options.showWeekNumbers;
            }

            if (typeof options.buttonClasses === 'string') {
                this.buttonClasses = [options.buttonClasses];
            }

            if (typeof options.buttonClasses === 'object') {
                this.buttonClasses = options.buttonClasses;
            }

            if (typeof options.showDropdowns === 'boolean') {
                this.showDropdowns = options.showDropdowns;
            }

            if (typeof options.singleDatePicker === 'boolean') {
                this.singleDatePicker = options.singleDatePicker;
            }

            if (typeof options.timePicker === 'boolean') {
                this.timePicker = options.timePicker;
            }

            if (typeof options.timePickerIncrement === 'number') {
                this.timePickerIncrement = options.timePickerIncrement;
            }

            if (typeof options.timePicker12Hour === 'boolean') {
                this.timePicker12Hour = options.timePicker12Hour;
            }

            // update day names order to firstDay
            if (this.locale.firstDay != 0) {
                var iterator = this.locale.firstDay;
                while (iterator > 0) {
                    this.locale.daysOfWeek.push(this.locale.daysOfWeek.shift());
                    iterator--;
                }
            }

            var start, end, range;

            //if no start/end dates set, check if an input element contains initial values
            if (typeof options.startDate === 'undefined' && typeof options.endDate === 'undefined') {
                if ($(this.element).is('input[type=text]')) {
                    var val = $(this.element).val();
                    var split = val.split(this.separator);
                    start = end = null;
                    if (split.length == 2) {
                        start = moment(split[0], this.format);
                        end = moment(split[1], this.format);
                    } else if (this.singleDatePicker) {
                        start = moment(val, this.format);
                        end = moment(val, this.format);
                    }
                    if (start !== null && end !== null) {
                        this.startDate = start;
                        this.endDate = end;
                    }
                }
            }

            if (typeof options.ranges === 'object') {
                for (range in options.ranges) {

                    start = moment(options.ranges[range][0]);
                    end = moment(options.ranges[range][1]);

                    // If we have a min/max date set, bound this range
                    // to it, but only if it would otherwise fall
                    // outside of the min/max.
                    if (this.minDate && start.isBefore(this.minDate))
                        start = moment(this.minDate);

                    if (this.maxDate && end.isAfter(this.maxDate))
                        end = moment(this.maxDate);

                    // If the end of the range is before the minimum (if min is set) OR
                    // the start of the range is after the max (also if set) don't display this
                    // range option.
                    if ((this.minDate && end.isBefore(this.minDate)) || (this.maxDate && start.isAfter(this.maxDate))) {
                        continue;
                    }

                    this.ranges[range] = [start, end];
                }

                var list = '<ul>';
                for (range in this.ranges) {
                    list += '<li>' + range + '</li>';
                }
                list += '<li>' + this.locale.customRangeLabel + '</li>';
                list += '</ul>';
                this.container.find('.ranges ul').remove();
                this.container.find('.ranges').prepend(list);
            }

            if (typeof callback === 'function') {
                this.cb = callback;
            }

            if (!this.timePicker) {
                this.startDate = this.startDate.startOf('day');
                this.endDate = this.endDate.endOf('day');
            }

            if (this.singleDatePicker) {
                this.opens = 'right';
                this.container.find('.calendar.right').show();
                this.container.find('.calendar.left').hide();
                this.container.find('.ranges').hide();
                if (!this.container.find('.calendar.right').hasClass('single'))
                    this.container.find('.calendar.right').addClass('single');
            } else {
                this.container.find('.calendar.right').removeClass('single');
                this.container.find('.ranges').show();
            }

            this.oldStartDate = this.startDate.clone();
            this.oldEndDate = this.endDate.clone();
            this.oldChosenLabel = this.chosenLabel;

            this.leftCalendar = {
                month: moment([this.startDate.year(), this.startDate.month(), 1, this.startDate.hour(), this.startDate.minute()]),
                calendar: []
            };

            this.rightCalendar = {
                month: moment([this.endDate.year(), this.endDate.month(), 1, this.endDate.hour(), this.endDate.minute()]),
                calendar: []
            };

            if (this.opens == 'right') {
                //swap calendar positions
                var left = this.container.find('.calendar.left');
                var right = this.container.find('.calendar.right');

                if (right.hasClass('single')) {
                    right.removeClass('single');
                    left.addClass('single');
                }

                left.removeClass('left').addClass('right');
                right.removeClass('right').addClass('left');

                if (this.singleDatePicker) {
                    left.show();
                    right.hide();
                }
            }

            if (typeof options.ranges === 'undefined' && !this.singleDatePicker) {
                this.container.addClass('show-calendar');
            }

            this.container.addClass('opens' + this.opens);

            this.updateView();
            this.updateCalendars();

        },

        setStartDate: function (startDate) {
            if (typeof startDate === 'string')
                this.startDate = moment(startDate, this.format);

            if (typeof startDate === 'object')
                this.startDate = moment(startDate);

            if (!this.timePicker)
                this.startDate = this.startDate.startOf('day');

            this.oldStartDate = this.startDate.clone();

            this.updateView();
            this.updateCalendars();
            this.updateInputText();
        },

        setEndDate: function (endDate) {
            if (typeof endDate === 'string')
                this.endDate = moment(endDate, this.format);

            if (typeof endDate === 'object')
                this.endDate = moment(endDate);

            if (!this.timePicker)
                this.endDate = this.endDate.endOf('day');

            this.oldEndDate = this.endDate.clone();

            this.updateView();
            this.updateCalendars();
            this.updateInputText();
        },

        updateView: function () {
            this.leftCalendar.month.month(this.startDate.month()).year(this.startDate.year()).hour(this.startDate.hour()).minute(this.startDate.minute());
            this.rightCalendar.month.month(this.endDate.month()).year(this.endDate.year()).hour(this.endDate.hour()).minute(this.endDate.minute());
            this.updateFormInputs();
        },

        updateFormInputs: function () {
            this.container.find('input[name=daterangepicker_start]').val(this.startDate.format(this.format));
            this.container.find('input[name=daterangepicker_end]').val(this.endDate.format(this.format));

            if (this.startDate.isSame(this.endDate) || this.startDate.isBefore(this.endDate)) {
                this.container.find('button.applyBtn').removeAttr('disabled');
            } else {
                this.container.find('button.applyBtn').attr('disabled', 'disabled');
            }
        },

        updateFromControl: function () {
            if (!this.element.is('input')) return;
            if (!this.element.val().length) return;

            var dateString = this.element.val().split(this.separator),
                start = null,
                end = null;

            if (dateString.length === 2) {
                start = moment(dateString[0], this.format);
                end = moment(dateString[1], this.format);
            }

            if (this.singleDatePicker || start === null || end === null) {
                start = moment(this.element.val(), this.format);
                end = start;
            }

            if (end.isBefore(start)) return;

            this.oldStartDate = this.startDate.clone();
            this.oldEndDate = this.endDate.clone();

            this.startDate = start;
            this.endDate = end;

            if (!this.startDate.isSame(this.oldStartDate) || !this.endDate.isSame(this.oldEndDate))
                this.notify();

            this.updateCalendars();
        },

        notify: function () {
            this.updateView();
            this.cb(this.startDate, this.endDate, this.chosenLabel);
        },

        move: function () {
            var parentOffset = { top: 0, left: 0 };
            var parentRightEdge = $(window).width();
            if (!this.parentEl.is('body')) {
                parentOffset = {
                    top: this.parentEl.offset().top - this.parentEl.scrollTop(),
                    left: this.parentEl.offset().left - this.parentEl.scrollLeft()
                };
                parentRightEdge = this.parentEl[0].clientWidth + this.parentEl.offset().left;
            }

            if (this.opens == 'left') {
                this.container.css({
                    top: this.element.offset().top + this.element.outerHeight() - parentOffset.top,
                    right: parentRightEdge - this.element.offset().left - this.element.outerWidth(),
                    left: 'auto'
                });
                if (this.container.offset().left < 0) {
                    this.container.css({
                        right: 'auto',
                        left: 9
                    });
                }
            } else {
                this.container.css({
                    top: this.element.offset().top + this.element.outerHeight() - parentOffset.top,
                    left: this.element.offset().left - parentOffset.left,
                    right: 'auto'
                });
                if (this.container.offset().left + this.container.outerWidth() > $(window).width()) {
                    this.container.css({
                        left: 'auto',
                        right: 0
                    });
                }
            }
        },

        toggle: function (e) {
            if (this.element.hasClass('active')) {
                this.hide();
            } else {
                this.show();
            }
        },

        show: function (e) {
            if (this.isShowing) return;

            this.element.addClass('active');
            this.container.show();
            this.move();

            // Create a click proxy that is private to this instance of datepicker, for unbinding
            this._outsideClickProxy = $.proxy(function (e) { this.outsideClick(e); }, this);
            // Bind global datepicker mousedown for hiding and
            $(document)
              .on('mousedown.daterangepicker', this._outsideClickProxy)
              // also explicitly play nice with Bootstrap dropdowns, which stopPropagation when clicking them
              .on('click.daterangepicker', '[data-toggle=dropdown]', this._outsideClickProxy)
              // and also close when focus changes to outside the picker (eg. tabbing between controls)
              .on('focusin.daterangepicker', this._outsideClickProxy);

            this.isShowing = true;
            this.element.trigger('show.daterangepicker', this);
        },

        outsideClick: function (e) {
            var target = $(e.target);
            // if the page is clicked anywhere except within the daterangerpicker/button
            // itself then call this.hide()
            if (
                target.closest(this.element).length ||
                target.closest(this.container).length ||
                target.closest('.calendar-date').length
                ) return;
            this.hide();
        },

        hide: function (e) {
            if (!this.isShowing) return;

            $(document)
              .off('mousedown.daterangepicker')
              .off('click.daterangepicker', '[data-toggle=dropdown]')
              .off('focusin.daterangepicker');

            this.element.removeClass('active');
            this.container.hide();

            if (!this.startDate.isSame(this.oldStartDate) || !this.endDate.isSame(this.oldEndDate))
                this.notify();

            this.oldStartDate = this.startDate.clone();
            this.oldEndDate = this.endDate.clone();

            this.isShowing = false;
            this.element.trigger('hide.daterangepicker', this);
        },

        enterRange: function (e) {
            // mouse pointer has entered a range label
            var label = e.target.innerHTML;
            if (label == this.locale.customRangeLabel) {
                this.updateView();
            } else {
                var dates = this.ranges[label];
                this.container.find('input[name=daterangepicker_start]').val(dates[0].format(this.format));
                this.container.find('input[name=daterangepicker_end]').val(dates[1].format(this.format));
            }
        },

        showCalendars: function () {
            this.container.addClass('show-calendar');
            this.move();
            this.element.trigger('showCalendar.daterangepicker', this);
        },

        hideCalendars: function () {
            this.container.removeClass('show-calendar');
            this.element.trigger('hideCalendar.daterangepicker', this);
        },

        // when a date is typed into the start to end date textboxes
        inputsChanged: function (e) {
            var el = $(e.target);
            var date = moment(el.val());
            if (!date.isValid()) return;

            var startDate, endDate;
            if (el.attr('name') === 'daterangepicker_start') {
                startDate = date;
                endDate = this.endDate;
            } else {
                startDate = this.startDate;
                endDate = date;
            }
            this.setCustomDates(startDate, endDate);
        },

        inputsKeydown: function (e) {
            if (e.keyCode === 13) {
                this.inputsChanged(e);
                this.notify();
            }
        },

        updateInputText: function () {
            if (this.element.is('input') && !this.singleDatePicker) {
                this.element.val(this.startDate.format(this.format) + this.separator + this.endDate.format(this.format));
            } else if (this.element.is('input')) {
                this.element.val(this.startDate.format(this.format));
            }
        },

        clickRange: function (e) {
            var label = e.target.innerHTML;
            this.chosenLabel = label;
            if (label == this.locale.customRangeLabel) {
                this.showCalendars();
            } else {
                var dates = this.ranges[label];

                this.startDate = dates[0];
                this.endDate = dates[1];

                if (!this.timePicker) {
                    this.startDate.startOf('day');
                    this.endDate.endOf('day');
                }

                this.leftCalendar.month.month(this.startDate.month()).year(this.startDate.year()).hour(this.startDate.hour()).minute(this.startDate.minute());
                this.rightCalendar.month.month(this.endDate.month()).year(this.endDate.year()).hour(this.endDate.hour()).minute(this.endDate.minute());
                this.updateCalendars();

                this.updateInputText();

                this.hideCalendars();
                this.hide();
                this.element.trigger('apply.daterangepicker', this);
            }
        },

        clickPrev: function (e) {
            var cal = $(e.target).parents('.calendar');
            if (cal.hasClass('left')) {
                this.leftCalendar.month.subtract(1, 'month');
            } else {
                this.rightCalendar.month.subtract(1, 'month');
            }
            this.updateCalendars();
        },

        clickNext: function (e) {
            var cal = $(e.target).parents('.calendar');
            if (cal.hasClass('left')) {
                this.leftCalendar.month.add(1, 'month');
            } else {
                this.rightCalendar.month.add(1, 'month');
            }
            this.updateCalendars();
        },

        hoverDate: function (e) {
            var title = $(e.target).attr('data-title');
            var row = title.substr(1, 1);
            var col = title.substr(3, 1);
            var cal = $(e.target).parents('.calendar');

            if (cal.hasClass('left')) {
                this.container.find('input[name=daterangepicker_start]').val(this.leftCalendar.calendar[row][col].format(this.format));
            } else {
                this.container.find('input[name=daterangepicker_end]').val(this.rightCalendar.calendar[row][col].format(this.format));
            }
        },

        setCustomDates: function (startDate, endDate) {
            this.chosenLabel = this.locale.customRangeLabel;
            if (startDate.isAfter(endDate)) {
                var difference = this.endDate.diff(this.startDate);
                endDate = moment(startDate).add(difference, 'ms');
            }
            this.startDate = startDate;
            this.endDate = endDate;

            this.updateView();
            this.updateCalendars();
        },

        clickDate: function (e) {
            var title = $(e.target).attr('data-title');
            var row = title.substr(1, 1);
            var col = title.substr(3, 1);
            var cal = $(e.target).parents('.calendar');

            var startDate, endDate;
            if (cal.hasClass('left')) {
                startDate = this.leftCalendar.calendar[row][col];
                endDate = this.endDate;
                if (typeof this.dateLimit === 'object') {
                    var maxDate = moment(startDate).add(this.dateLimit).startOf('day');
                    if (endDate.isAfter(maxDate)) {
                        endDate = maxDate;
                    }
                }
            } else {
                startDate = this.startDate;
                endDate = this.rightCalendar.calendar[row][col];
                if (typeof this.dateLimit === 'object') {
                    var minDate = moment(endDate).subtract(this.dateLimit).startOf('day');
                    if (startDate.isBefore(minDate)) {
                        startDate = minDate;
                    }
                }
            }

            if (this.singleDatePicker && cal.hasClass('left')) {
                endDate = startDate.clone();
            } else if (this.singleDatePicker && cal.hasClass('right')) {
                startDate = endDate.clone();
            }

            cal.find('td').removeClass('active');

            $(e.target).addClass('active');

            this.setCustomDates(startDate, endDate);

            if (!this.timePicker)
                endDate.endOf('day');

            if (this.singleDatePicker)
                this.clickApply();
        },

        clickApply: function (e) {
            this.updateInputText();
            this.hide();
            this.element.trigger('apply.daterangepicker', this);
        },

        clickCancel: function (e) {
            this.startDate = this.oldStartDate;
            this.endDate = this.oldEndDate;
            this.chosenLabel = this.oldChosenLabel;
            this.updateView();
            this.updateCalendars();
            this.hide();
            this.element.trigger('cancel.daterangepicker', this);
        },

        updateMonthYear: function (e) {
            var isLeft = $(e.target).closest('.calendar').hasClass('left'),
                leftOrRight = isLeft ? 'left' : 'right',
                cal = this.container.find('.calendar.' + leftOrRight);

            // Month must be Number for new moment versions
            var month = parseInt(cal.find('.monthselect').val(), 10);
            var year = cal.find('.yearselect').val();

            this[leftOrRight + 'Calendar'].month.month(month).year(year);
            this.updateCalendars();
        },

        updateTime: function (e) {

            var cal = $(e.target).closest('.calendar'),
                isLeft = cal.hasClass('left');

            var hour = parseInt(cal.find('.hourselect').val(), 10);
            var minute = parseInt(cal.find('.minuteselect').val(), 10);

            if (this.timePicker12Hour) {
                var ampm = cal.find('.ampmselect').val();
                if (ampm === 'PM' && hour < 12)
                    hour += 12;
                if (ampm === 'AM' && hour === 12)
                    hour = 0;
            }

            if (isLeft) {
                var start = this.startDate.clone();
                start.hour(hour);
                start.minute(minute);
                this.startDate = start;
                this.leftCalendar.month.hour(hour).minute(minute);
            } else {
                var end = this.endDate.clone();
                end.hour(hour);
                end.minute(minute);
                this.endDate = end;
                this.rightCalendar.month.hour(hour).minute(minute);
            }

            this.updateCalendars();
        },

        updateCalendars: function () {
            this.leftCalendar.calendar = this.buildCalendar(this.leftCalendar.month.month(), this.leftCalendar.month.year(), this.leftCalendar.month.hour(), this.leftCalendar.month.minute(), 'left');
            this.rightCalendar.calendar = this.buildCalendar(this.rightCalendar.month.month(), this.rightCalendar.month.year(), this.rightCalendar.month.hour(), this.rightCalendar.month.minute(), 'right');
            this.container.find('.calendar.left').empty().html(this.renderCalendar(this.leftCalendar.calendar, this.startDate, this.minDate, this.maxDate));
            this.container.find('.calendar.right').empty().html(this.renderCalendar(this.rightCalendar.calendar, this.endDate, this.startDate, this.maxDate));

            this.container.find('.ranges li').removeClass('active');
            var customRange = true;
            var i = 0;
            for (var range in this.ranges) {
                if (this.timePicker) {
                    if (this.startDate.isSame(this.ranges[range][0]) && this.endDate.isSame(this.ranges[range][1])) {
                        customRange = false;
                        this.chosenLabel = this.container.find('.ranges li:eq(' + i + ')')
                            .addClass('active').html();
                    }
                } else {
                    //ignore times when comparing dates if time picker is not enabled
                    if (this.startDate.format('YYYY-MM-DD') == this.ranges[range][0].format('YYYY-MM-DD') && this.endDate.format('YYYY-MM-DD') == this.ranges[range][1].format('YYYY-MM-DD')) {
                        customRange = false;
                        this.chosenLabel = this.container.find('.ranges li:eq(' + i + ')')
                            .addClass('active').html();
                    }
                }
                i++;
            }
            if (customRange) {
                this.chosenLabel = this.container.find('.ranges li:last').addClass('active').html();
                this.showCalendars();
            }
        },

        buildCalendar: function (month, year, hour, minute, side) {
            var daysInMonth = moment([year, month]).daysInMonth();
            var firstDay = moment([year, month, 1]);
            var lastDay = moment([year, month, daysInMonth]);
            var lastMonth = moment(firstDay).subtract(1, 'month').month();
            var lastYear = moment(firstDay).subtract(1, 'month').year();

            var daysInLastMonth = moment([lastYear, lastMonth]).daysInMonth();

            var dayOfWeek = firstDay.day();

            var i;

            //initialize a 6 rows x 7 columns array for the calendar
            var calendar = [];
            calendar.firstDay = firstDay;
            calendar.lastDay = lastDay;

            for (i = 0; i < 6; i++) {
                calendar[i] = [];
            }

            //populate the calendar with date objects
            var startDay = daysInLastMonth - dayOfWeek + this.locale.firstDay + 1;
            if (startDay > daysInLastMonth)
                startDay -= 7;

            if (dayOfWeek == this.locale.firstDay)
                startDay = daysInLastMonth - 6;

            var curDate = moment([lastYear, lastMonth, startDay, 12, minute]);
            var col, row;
            for (i = 0, col = 0, row = 0; i < 42; i++, col++, curDate = moment(curDate).add(24, 'hour')) {
                if (i > 0 && col % 7 === 0) {
                    col = 0;
                    row++;
                }
                calendar[row][col] = curDate.clone().hour(hour);
                curDate.hour(12);
            }

            return calendar;
        },

        renderDropdowns: function (selected, minDate, maxDate) {
            var currentMonth = selected.month();
            var monthHtml = '<select class="monthselect">';
            var inMinYear = false;
            var inMaxYear = false;

            for (var m = 0; m < 12; m++) {
                if ((!inMinYear || m >= minDate.month()) && (!inMaxYear || m <= maxDate.month())) {
                    monthHtml += "<option value='" + m + "'" +
                        (m === currentMonth ? " selected='selected'" : "") +
                        ">" + this.locale.monthNames[m] + "</option>";
                }
            }
            monthHtml += "</select>";

            var currentYear = selected.year();
            var maxYear = (maxDate && maxDate.year()) || (currentYear + 5);
            var minYear = (minDate && minDate.year()) || (currentYear - 50);
            var yearHtml = '<select class="yearselect">';

            for (var y = minYear; y <= maxYear; y++) {
                yearHtml += '<option value="' + y + '"' +
                    (y === currentYear ? ' selected="selected"' : '') +
                    '>' + y + '</option>';
            }

            yearHtml += '</select>';

            return monthHtml + yearHtml;
        },

        renderCalendar: function (calendar, selected, minDate, maxDate) {

            var html = '<div class="calendar-date">';
            html += '<table class="table-condensed">';
            html += '<thead>';
            html += '<tr>';

            // add empty cell for week number
            if (this.showWeekNumbers)
                html += '<th></th>';

            if (!minDate || minDate.isBefore(calendar.firstDay)) {
                html += '<th class="prev available"><i class="fa fa-arrow-left icon-arrow-left glyphicon glyphicon-arrow-left"></i></th>';
            } else {
                html += '<th></th>';
            }

            var dateHtml = this.locale.monthNames[calendar[1][1].month()] + calendar[1][1].format(" YYYY");

            if (this.showDropdowns) {
                dateHtml = this.renderDropdowns(calendar[1][1], minDate, maxDate);
            }

            html += '<th colspan="5" class="month">' + dateHtml + '</th>';
            if (!maxDate || maxDate.isAfter(calendar.lastDay)) {
                html += '<th class="next available"><i class="fa fa-arrow-right icon-arrow-right glyphicon glyphicon-arrow-right"></i></th>';
            } else {
                html += '<th></th>';
            }

            html += '</tr>';
            html += '<tr>';

            // add week number label
            if (this.showWeekNumbers)
                html += '<th class="week">' + this.locale.weekLabel + '</th>';

            $.each(this.locale.daysOfWeek, function (index, dayOfWeek) {
                html += '<th>' + dayOfWeek + '</th>';
            });

            html += '</tr>';
            html += '</thead>';
            html += '<tbody>';

            for (var row = 0; row < 6; row++) {
                html += '<tr>';

                // add week number
                if (this.showWeekNumbers)
                    html += '<td class="week">' + calendar[row][0].week() + '</td>';

                for (var col = 0; col < 7; col++) {
                    var cname = 'available ';
                    cname += (calendar[row][col].month() == calendar[1][1].month()) ? '' : 'off';

                    if ((minDate && calendar[row][col].isBefore(minDate, 'day')) || (maxDate && calendar[row][col].isAfter(maxDate, 'day'))) {
                        cname = ' off disabled ';
                    } else if (calendar[row][col].format('YYYY-MM-DD') == selected.format('YYYY-MM-DD')) {
                        cname += ' active ';
                        if (calendar[row][col].format('YYYY-MM-DD') == this.startDate.format('YYYY-MM-DD')) {
                            cname += ' start-date ';
                        }
                        if (calendar[row][col].format('YYYY-MM-DD') == this.endDate.format('YYYY-MM-DD')) {
                            cname += ' end-date ';
                        }
                    } else if (calendar[row][col] >= this.startDate && calendar[row][col] <= this.endDate) {
                        cname += ' in-range ';
                        if (calendar[row][col].isSame(this.startDate)) { cname += ' start-date '; }
                        if (calendar[row][col].isSame(this.endDate)) { cname += ' end-date '; }
                    }

                    var title = 'r' + row + 'c' + col;
                    html += '<td class="' + cname.replace(/\s+/g, ' ').replace(/^\s?(.*?)\s?$/, '$1') + '" data-title="' + title + '">' + calendar[row][col].date() + '</td>';
                }
                html += '</tr>';
            }

            html += '</tbody>';
            html += '</table>';
            html += '</div>';

            var i;
            if (this.timePicker) {

                html += '<div class="calendar-time">';
                html += '<select class="hourselect">';
                var start = 0;
                var end = 23;
                var selected_hour = selected.hour();
                if (this.timePicker12Hour) {
                    start = 1;
                    end = 12;
                    if (selected_hour >= 12)
                        selected_hour -= 12;
                    if (selected_hour === 0)
                        selected_hour = 12;
                }

                for (i = start; i <= end; i++) {
                    if (i == selected_hour) {
                        html += '<option value="' + i + '" selected="selected">' + i + '</option>';
                    } else {
                        html += '<option value="' + i + '">' + i + '</option>';
                    }
                }

                html += '</select> : ';

                html += '<select class="minuteselect">';

                for (i = 0; i < 60; i += this.timePickerIncrement) {
                    var num = i;
                    if (num < 10)
                        num = '0' + num;
                    if (i == selected.minute()) {
                        html += '<option value="' + i + '" selected="selected">' + num + '</option>';
                    } else {
                        html += '<option value="' + i + '">' + num + '</option>';
                    }
                }

                html += '</select> ';

                if (this.timePicker12Hour) {
                    html += '<select class="ampmselect">';
                    if (selected.hour() >= 12) {
                        html += '<option value="AM">AM</option><option value="PM" selected="selected">PM</option>';
                    } else {
                        html += '<option value="AM" selected="selected">AM</option><option value="PM">PM</option>';
                    }
                    html += '</select>';
                }

                html += '</div>';

            }

            return html;

        },

        remove: function () {

            this.container.remove();
            this.element.off('.daterangepicker');
            this.element.removeData('daterangepicker');

        }

    };

    $.fn.daterangepicker = function (options, cb) {
        this.each(function () {
            var el = $(this);
            if (el.data('daterangepicker'))
                el.data('daterangepicker').remove();
            el.data('daterangepicker', new DateRangePicker(el, options, cb));
        });
        return this;
    };

}));
/**
 * @license ng-bs-daterangepicker v0.0.1
 * (c) 2013 Luis Farzati http://github.com/luisfarzati/ng-bs-daterangepicker
 * License: MIT
 */
(function (angular) {
    'use strict';

    ///THIS FILE HAS BEEN MODIFIED!!!
    ///do NOT STRAIGHT UPDATE THIS FILE
    angular.module('ngBootstrap', []).directive('input', ['$compile', '$parse', function ($compile, $parse) {
        return {
            restrict: 'E',
            require: '?ngModel',
            link: function ($scope, $element, $attributes, ngModel) {
                if ($attributes.type !== 'daterange' || ngModel === null) return;

                var options = {};
                options.format = $attributes.format || 'YYYY-MM-DD';
                options.separator = $attributes.separator || ' - ';
                options.minDate = $attributes.minDate && moment.utc($attributes.minDate);
                options.maxDate = $attributes.maxDate && moment.utc($attributes.maxDate);
                options.dateLimit = $attributes.limit && moment.duration.apply(this, $attributes.limit.split(' ').map(function (elem, index) { return index === 0 && parseInt(elem, 10) || elem; }));
                //options.startDate = moment().subtract('days', 1);
                //options.endDate = moment().subtract('days', 1);

                if (typeof $attributes.ranges == "undefined") {
                    options.ranges = {
                        'Today': [moment(), moment()],
                        'Yesterday': [moment().subtract(1, 'days'), moment().subtract(1, 'days')],
                        'Last 7 Days': [moment().subtract(6, 'days'), moment()],
                        'Last 30 Days': [moment().subtract(29, 'days'), moment()],
                        'This Month': [moment().startOf('month'), moment().endOf('month')],
                        'Last Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')],
                        'This Year': [moment().startOf('year'), moment().endOf('year')],
                        'Last Year': [moment().subtract(1, 'year').startOf('year'), moment().subtract(1, 'year').endOf('year')],
                        'Last 3 Years': [moment().subtract(3, 'year').startOf('year'), moment()],
                        'All Time': [moment.utc('20130101', 'YYYYMMDD'), moment()]
                    };
                } else {
                    options.ranges = $attributes.ranges && $parse($attributes.ranges)($scope);
                }
                options.locale = $attributes.locale && $parse($attributes.locale)($scope);
                options.opens = $attributes.opens;

                function format(date) {
                    return date.format(options.format);
                }

                function formatted(dates) {
                    return [format(dates.startDate), format(dates.endDate)].join(options.separator);
                }

                ngModel.$formatters.unshift(function (modelValue) {
                    if (!modelValue) return '';
                    return modelValue;
                });

                ngModel.$parsers.unshift(function (viewValue) {
                    return viewValue;
                });

                ngModel.$render = function () {
                    if (!ngModel.$viewValue || !ngModel.$viewValue.startDate) return;
                    $element.val(formatted(ngModel.$viewValue));
                };

                $scope.$watch($attributes.ngModel, function (modelValue) {
                    if (!modelValue || (!modelValue.startDate)) {
                        ngModel.$setViewValue({ startDate: moment().startOf('day'), endDate: moment().startOf('day') });
                        return;
                    }

                    $element.data('daterangepicker').setStartDate(modelValue.startDate);
                    $element.data('daterangepicker').setEndDate(modelValue.endDate);
                    $element.data('daterangepicker').updateView();
                    $element.data('daterangepicker').updateCalendars();
                    $element.data('daterangepicker').updateInputText();
                });



                $element.daterangepicker(options, function (start, end) {
                    $scope.$apply(function () {
                        ngModel.$setViewValue({ startDate: start, endDate: end });
                        ngModel.$render();
                    });
                });
            }
        };
    }]);

})(angular);
(function () {
    var module,
      __indexOf = [].indexOf || function (item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };

    module = angular.module('angularBootstrapNavTree', []);

    module.directive('abnTree', [
      '$timeout', function ($timeout) {
          return {
              restrict: 'E',
              template: "<ul class=\"nav nav-list nav-pills nav-stacked abn-tree\">\n  <li ng-repeat=\"row in tree_rows | filter:{visible:true} track by row.branch.uid\" ng-animate=\"'abn-tree-animate'\" ng-class=\"'level-' + {{ row.level }} + (row.branch.selected ? ' active':'') + ' ' +row.classes.join(' ')\" class=\"abn-tree-row\"><a ng-click=\"user_clicks_branch(row.branch)\"><i ng-class=\"row.tree_icon\" ng-click=\"row.branch.expanded = !row.branch.expanded\" class=\"indented tree-icon\"> </i><span class=\"indented tree-label\">{{ row.label }} </span></a></li>\n</ul>",
              replace: true,
              scope: {
                  treeData: '=',
                  onSelect: '&',
                  initialSelection: '@',
                  treeControl: '='
              },
              link: function (scope, element, attrs) {
                  var error, expand_all_parents, expand_level, for_all_ancestors, for_each_branch, get_parent, n, on_treeData_change, select_branch, selected_branch, tree;
                  error = function (s) {
                      console.log('ERROR:' + s);
                      
                      return void 0;
                  };
                  if (attrs.iconExpand == null) {
                      attrs.iconExpand = 'icon-plus  glyphicon glyphicon-plus  fa fa-plus';
                  }
                  if (attrs.iconCollapse == null) {
                      attrs.iconCollapse = 'icon-minus glyphicon glyphicon-minus fa fa-minus';
                  }
                  if (attrs.iconLeaf == null) {
                      attrs.iconLeaf = 'icon-file  glyphicon glyphicon-file  fa fa-file';
                  }
                  if (attrs.expandLevel == null) {
                      attrs.expandLevel = '3';
                  }
                  expand_level = parseInt(attrs.expandLevel, 10);
                  if (!scope.treeData) {
                      alert('no treeData defined for the tree!');
                      return;
                  }
                  if (scope.treeData.length == null) {
                      if (treeData.label != null) {
                          scope.treeData = [treeData];
                      } else {
                          alert('treeData should be an array of root branches');
                          return;
                      }
                  }
                  for_each_branch = function (f) {
                      var do_f, root_branch, _i, _len, _ref, _results;
                      do_f = function (branch, level) {
                          var child, _i, _len, _ref, _results;
                          f(branch, level);
                          if (branch.children != null) {
                              _ref = branch.children;
                              _results = [];
                              for (_i = 0, _len = _ref.length; _i < _len; _i++) {
                                  child = _ref[_i];
                                  _results.push(do_f(child, level + 1));
                              }
                              return _results;
                          }
                      };
                      _ref = scope.treeData;
                      _results = [];
                      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
                          root_branch = _ref[_i];
                          _results.push(do_f(root_branch, 1));
                      }
                      return _results;
                  };
                  selected_branch = null;
                  select_branch = function (branch) {
                      if (!branch) {
                          if (selected_branch != null) {
                              selected_branch.selected = false;
                          }
                          selected_branch = null;
                          return;
                      }
                      if (branch !== selected_branch) {
                          if (selected_branch != null) {
                              selected_branch.selected = false;
                          }
                          branch.selected = true;
                          selected_branch = branch;
                          expand_all_parents(branch);
                          if (branch.onSelect != null) {
                              return $timeout(function () {
                                  return branch.onSelect(branch);
                              });
                          } else {
                              if (scope.onSelect != null) {
                                  return $timeout(function () {
                                      return scope.onSelect({
                                          branch: branch
                                      });
                                  });
                              }
                          }
                      }
                  };
                  scope.user_clicks_branch = function (branch) {
                      if (branch !== selected_branch) {
                          return select_branch(branch);
                      }
                  };
                  get_parent = function (child) {
                      var parent;
                      parent = void 0;
                      if (child.parent_uid) {
                          for_each_branch(function (b) {
                              if (b.uid === child.parent_uid) {
                                  return parent = b;
                              }
                          });
                      }
                      return parent;
                  };
                  for_all_ancestors = function (child, fn) {
                      var parent;
                      parent = get_parent(child);
                      if (parent != null) {
                          fn(parent);
                          return for_all_ancestors(parent, fn);
                      }
                  };
                  expand_all_parents = function (child) {
                      return for_all_ancestors(child, function (b) {
                          return b.expanded = true;
                      });
                  };
                  scope.tree_rows = [];
                  on_treeData_change = function () {
                      var add_branch_to_list, root_branch, _i, _len, _ref, _results;
                      for_each_branch(function (b, level) {
                          if (!b.uid) {
                              return b.uid = "" + Math.random();
                          }
                      });
                      for_each_branch(function (b) {
                          var child, _i, _len, _ref, _results;
                          if (angular.isArray(b.children)) {
                              _ref = b.children;
                              _results = [];
                              for (_i = 0, _len = _ref.length; _i < _len; _i++) {
                                  child = _ref[_i];
                                  _results.push(child.parent_uid = b.uid);
                              }
                              return _results;
                          }
                      });
                      scope.tree_rows = [];
                      for_each_branch(function (branch) {
                          var child, f;
                          if (branch.children) {
                              if (branch.children.length > 0) {
                                  f = function (e) {
                                      if (typeof e === 'string') {
                                          return {
                                              label: e,
                                              children: []
                                          };
                                      } else {
                                          return e;
                                      }
                                  };
                                  return branch.children = (function () {
                                      var _i, _len, _ref, _results;
                                      _ref = branch.children;
                                      _results = [];
                                      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
                                          child = _ref[_i];
                                          _results.push(f(child));
                                      }
                                      return _results;
                                  })();
                              }
                          } else {
                              return branch.children = [];
                          }
                      });
                      add_branch_to_list = function (level, branch, visible) {
                          var child, child_visible, tree_icon, _i, _len, _ref, _results;
                          if (branch.expanded == null) {
                              branch.expanded = false;
                          }
                          if (branch.classes == null) {
                              branch.classes = [];
                          }
                          if (!branch.noLeaf && (!branch.children || branch.children.length === 0)) {
                              tree_icon = attrs.iconLeaf;
                              if (__indexOf.call(branch.classes, "leaf") < 0) {
                                  branch.classes.push("leaf");
                              }
                          } else {
                              if (branch.expanded) {
                                  tree_icon = attrs.iconCollapse;
                              } else {
                                  tree_icon = attrs.iconExpand;
                              }
                          }
                          scope.tree_rows.push({
                              level: level,
                              branch: branch,
                              label: branch.label,
                              classes: branch.classes,
                              tree_icon: tree_icon,
                              visible: visible
                          });
                          if (branch.children != null) {
                              _ref = branch.children;
                              _results = [];
                              for (_i = 0, _len = _ref.length; _i < _len; _i++) {
                                  child = _ref[_i];
                                  child_visible = visible && branch.expanded;
                                  _results.push(add_branch_to_list(level + 1, child, child_visible));
                              }
                              return _results;
                          }
                      };
                      _ref = scope.treeData;
                      _results = [];
                      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
                          root_branch = _ref[_i];
                          _results.push(add_branch_to_list(1, root_branch, true));
                      }
                      return _results;
                  };
                  scope.$watch('treeData', on_treeData_change, true);
                  if (attrs.initialSelection != null) {
                      for_each_branch(function (b) {
                          if (b.label === attrs.initialSelection) {
                              return $timeout(function () {
                                  return select_branch(b);
                              });
                          }
                      });
                  }
                  n = scope.treeData.length;
                  for_each_branch(function (b, level) {
                      b.level = level;
                      return b.expanded = b.level < expand_level;
                  });
                  if (scope.treeControl != null) {
                      if (angular.isObject(scope.treeControl)) {
                          tree = scope.treeControl;
                          tree.expand_all = function () {
                              return for_each_branch(function (b, level) {
                                  return b.expanded = true;
                              });
                          };
                          tree.collapse_all = function () {
                              return for_each_branch(function (b, level) {
                                  return b.expanded = false;
                              });
                          };
                          tree.get_first_branch = function () {
                              n = scope.treeData.length;
                              if (n > 0) {
                                  return scope.treeData[0];
                              }
                          };
                          tree.select_first_branch = function () {
                              var b;
                              b = tree.get_first_branch();
                              return tree.select_branch(b);
                          };
                          tree.get_selected_branch = function () {
                              return selected_branch;
                          };
                          tree.get_parent_branch = function (b) {
                              return get_parent(b);
                          };
                          tree.select_branch = function (b) {
                              select_branch(b);
                              return b;
                          };
                          tree.get_children = function (b) {
                              return b.children;
                          };
                          tree.select_parent_branch = function (b) {
                              var p;
                              if (b == null) {
                                  b = tree.get_selected_branch();
                              }
                              if (b != null) {
                                  p = tree.get_parent_branch(b);
                                  if (p != null) {
                                      tree.select_branch(p);
                                      return p;
                                  }
                              }
                          };
                          tree.add_branch = function (parent, new_branch) {
                              if (parent != null) {
                                  parent.children.push(new_branch);
                                  parent.expanded = true;
                              } else {
                                  scope.treeData.push(new_branch);
                              }
                              return new_branch;
                          };
                          tree.add_root_branch = function (new_branch) {
                              tree.add_branch(null, new_branch);
                              return new_branch;
                          };
                          tree.expand_branch = function (b) {
                              if (b == null) {
                                  b = tree.get_selected_branch();
                              }
                              if (b != null) {
                                  b.expanded = true;
                                  return b;
                              }
                          };
                          tree.collapse_branch = function (b) {
                              if (b == null) {
                                  b = selected_branch;
                              }
                              if (b != null) {
                                  b.expanded = false;
                                  return b;
                              }
                          };
                          tree.get_siblings = function (b) {
                              var p, siblings;
                              if (b == null) {
                                  b = selected_branch;
                              }
                              if (b != null) {
                                  p = tree.get_parent_branch(b);
                                  if (p) {
                                      siblings = p.children;
                                  } else {
                                      siblings = scope.treeData;
                                  }
                                  return siblings;
                              }
                          };
                          tree.get_next_sibling = function (b) {
                              var i, siblings;
                              if (b == null) {
                                  b = selected_branch;
                              }
                              if (b != null) {
                                  siblings = tree.get_siblings(b);
                                  n = siblings.length;
                                  i = siblings.indexOf(b);
                                  if (i < n) {
                                      return siblings[i + 1];
                                  }
                              }
                          };
                          tree.get_prev_sibling = function (b) {
                              var i, siblings;
                              if (b == null) {
                                  b = selected_branch;
                              }
                              siblings = tree.get_siblings(b);
                              n = siblings.length;
                              i = siblings.indexOf(b);
                              if (i > 0) {
                                  return siblings[i - 1];
                              }
                          };
                          tree.select_next_sibling = function (b) {
                              var next;
                              if (b == null) {
                                  b = selected_branch;
                              }
                              if (b != null) {
                                  next = tree.get_next_sibling(b);
                                  if (next != null) {
                                      return tree.select_branch(next);
                                  }
                              }
                          };
                          tree.select_prev_sibling = function (b) {
                              var prev;
                              if (b == null) {
                                  b = selected_branch;
                              }
                              if (b != null) {
                                  prev = tree.get_prev_sibling(b);
                                  if (prev != null) {
                                      return tree.select_branch(prev);
                                  }
                              }
                          };
                          tree.get_first_child = function (b) {
                              var _ref;
                              if (b == null) {
                                  b = selected_branch;
                              }
                              if (b != null) {
                                  if (((_ref = b.children) != null ? _ref.length : void 0) > 0) {
                                      return b.children[0];
                                  }
                              }
                          };
                          tree.get_closest_ancestor_next_sibling = function (b) {
                              var next, parent;
                              next = tree.get_next_sibling(b);
                              if (next != null) {
                                  return next;
                              } else {
                                  parent = tree.get_parent_branch(b);
                                  return tree.get_closest_ancestor_next_sibling(parent);
                              }
                          };
                          tree.get_next_branch = function (b) {
                              var next;
                              if (b == null) {
                                  b = selected_branch;
                              }
                              if (b != null) {
                                  next = tree.get_first_child(b);
                                  if (next != null) {
                                      return next;
                                  } else {
                                      next = tree.get_closest_ancestor_next_sibling(b);
                                      return next;
                                  }
                              }
                          };
                          tree.select_next_branch = function (b) {
                              var next;
                              if (b == null) {
                                  b = selected_branch;
                              }
                              if (b != null) {
                                  next = tree.get_next_branch(b);
                                  if (next != null) {
                                      tree.select_branch(next);
                                      return next;
                                  }
                              }
                          };
                          tree.last_descendant = function (b) {
                              var last_child;
                              if (b == null) {
                                  
                              }
                              n = b.children.length;
                              if (n === 0) {
                                  return b;
                              } else {
                                  last_child = b.children[n - 1];
                                  return tree.last_descendant(last_child);
                              }
                          };
                          tree.get_prev_branch = function (b) {
                              var parent, prev_sibling;
                              if (b == null) {
                                  b = selected_branch;
                              }
                              if (b != null) {
                                  prev_sibling = tree.get_prev_sibling(b);
                                  if (prev_sibling != null) {
                                      return tree.last_descendant(prev_sibling);
                                  } else {
                                      parent = tree.get_parent_branch(b);
                                      return parent;
                                  }
                              }
                          };
                          return tree.select_prev_branch = function (b) {
                              var prev;
                              if (b == null) {
                                  b = selected_branch;
                              }
                              if (b != null) {
                                  prev = tree.get_prev_branch(b);
                                  if (prev != null) {
                                      tree.select_branch(prev);
                                      return prev;
                                  }
                              }
                          };
                      }
                  }
              }
          };
      }
    ]);

}).call(this);

angular.module('uiSwitch', [])

.directive('switch', function(){
  return {
    restrict: 'AE'
  , replace: true
  , transclude: true
  , template: function(element, attrs) {
      var html = '';
      html += '<span';
      html +=   ' class="switch' + (attrs.class ? ' ' + attrs.class : '') + '"';
      html +=   attrs.ngModel ? ' ng-click="' + attrs.ngModel + '=!' + attrs.ngModel + (attrs.ngChange ? '; ' + attrs.ngChange + '()"' : '"') : '';
      html +=   ' ng-class="{ checked:' + attrs.ngModel + ' }"';
      html +=   '>';
      html +=   '<small></small>';
      html +=   '<input type="checkbox"';
      html +=     attrs.id ? ' id="' + attrs.id + '"' : '';
      html +=     attrs.name ? ' name="' + attrs.name + '"' : '';
      html +=     attrs.ngModel ? ' ng-model="' + attrs.ngModel + '"' : '';
      html +=     ' style="display:none" />';
      html += '</span>';
      return html;
    }
  }
});

/*
 jQuery UI Sortable plugin wrapper

 @param [ui-sortable] {object} Options to pass to $.fn.sortable() merged onto ui.config
 */
angular.module('ui.sortable', [])
  .value('uiSortableConfig', {})
  .directive('uiSortable', [
    'uiSortableConfig', '$timeout', '$log',
    function (uiSortableConfig, $timeout, $log) {
        return {
            require: '?ngModel',
            scope: {
                ngModel: '=',
                uiSortable: '='
            },
            link: function (scope, element, attrs, ngModel) {
                var savedNodes;

                function combineCallbacks(first, second) {
                    if (second && (typeof second === 'function')) {
                        return function () {
                            first.apply(this, arguments);
                            second.apply(this, arguments);
                        };
                    }
                    return first;
                }

                function getSortableWidgetInstance(element) {
                    // this is a fix to support jquery-ui prior to v1.11.x
                    // otherwise we should be using `element.sortable('instance')`
                    var data = element.data('ui-sortable');
                    if (data && typeof data === 'object' && data.widgetFullName === 'ui-sortable') {
                        return data;
                    }
                    return null;
                }

                function hasSortingHelper(element, ui) {
                    var helperOption = element.sortable('option', 'helper');
                    return helperOption === 'clone' || (typeof helperOption === 'function' && ui.item.sortable.isCustomHelperUsed());
                }

                // thanks jquery-ui
                function isFloating(item) {
                    return (/left|right/).test(item.css('float')) || (/inline|table-cell/).test(item.css('display'));
                }

                function getElementScope(elementScopes, element) {
                    var result = null;
                    for (var i = 0; i < elementScopes.length; i++) {
                        var x = elementScopes[i];
                        if (x.element[0] === element[0]) {
                            result = x.scope;
                            break;
                        }
                    }
                    return result;
                }

                function afterStop(e, ui) {
                    ui.item.sortable._destroy();
                }

                var opts = {};

                // directive specific options
                var directiveOpts = {
                    'ui-floating': undefined
                };

                var callbacks = {
                    receive: null,
                    remove: null,
                    start: null,
                    stop: null,
                    update: null
                };

                var wrappers = {
                    helper: null
                };

                angular.extend(opts, directiveOpts, uiSortableConfig, scope.uiSortable);

                if (!angular.element.fn || !angular.element.fn.jquery) {
                    $log.error('ui.sortable: jQuery should be included before AngularJS!');
                    return;
                }

                if (ngModel) {

                    // When we add or remove elements, we need the sortable to 'refresh'
                    // so it can find the new/removed elements.
                    scope.$watch('ngModel.length', function () {
                        // Timeout to let ng-repeat modify the DOM
                        $timeout(function () {
                            // ensure that the jquery-ui-sortable widget instance
                            // is still bound to the directive's element
                            if (!!getSortableWidgetInstance(element)) {
                                element.sortable('refresh');
                            }
                        }, 0, false);
                    });

                    callbacks.start = function (e, ui) {
                        if (opts['ui-floating'] === 'auto') {
                            // since the drag has started, the element will be
                            // absolutely positioned, so we check its siblings
                            var siblings = ui.item.siblings();
                            var sortableWidgetInstance = getSortableWidgetInstance(angular.element(e.target));
                            sortableWidgetInstance.floating = isFloating(siblings);
                        }

                        // Save the starting position of dragged item
                        ui.item.sortable = {
                            model: ngModel.$modelValue[ui.item.index()],
                            index: ui.item.index(),
                            source: ui.item.parent(),
                            sourceModel: ngModel.$modelValue,
                            cancel: function () {
                                ui.item.sortable._isCanceled = true;
                            },
                            isCanceled: function () {
                                return ui.item.sortable._isCanceled;
                            },
                            isCustomHelperUsed: function () {
                                return !!ui.item.sortable._isCustomHelperUsed;
                            },
                            _isCanceled: false,
                            _isCustomHelperUsed: ui.item.sortable._isCustomHelperUsed,
                            _destroy: function () {
                                angular.forEach(ui.item.sortable, function (value, key) {
                                    ui.item.sortable[key] = undefined;
                                });
                            }
                        };
                    };

                    callbacks.activate = function (e, ui) {
                        // We need to make a copy of the current element's contents so
                        // we can restore it after sortable has messed it up.
                        // This is inside activate (instead of start) in order to save
                        // both lists when dragging between connected lists.
                        savedNodes = element.contents();

                        // If this list has a placeholder (the connected lists won't),
                        // don't inlcude it in saved nodes.
                        var placeholder = element.sortable('option', 'placeholder');

                        // placeholder.element will be a function if the placeholder, has
                        // been created (placeholder will be an object).  If it hasn't
                        // been created, either placeholder will be false if no
                        // placeholder class was given or placeholder.element will be
                        // undefined if a class was given (placeholder will be a string)
                        if (placeholder && placeholder.element && typeof placeholder.element === 'function') {
                            var phElement = placeholder.element();
                            // workaround for jquery ui 1.9.x,
                            // not returning jquery collection
                            phElement = angular.element(phElement);

                            // exact match with the placeholder's class attribute to handle
                            // the case that multiple connected sortables exist and
                            // the placehoilder option equals the class of sortable items
                            var excludes = element.find('[class="' + phElement.attr('class') + '"]:not([ng-repeat], [data-ng-repeat])');

                            savedNodes = savedNodes.not(excludes);
                        }

                        // save the directive's scope so that it is accessible from ui.item.sortable
                        var connectedSortables = ui.item.sortable._connectedSortables || [];

                        connectedSortables.push({
                            element: element,
                            scope: scope
                        });

                        ui.item.sortable._connectedSortables = connectedSortables;
                    };

                    callbacks.update = function (e, ui) {
                        // Save current drop position but only if this is not a second
                        // update that happens when moving between lists because then
                        // the value will be overwritten with the old value
                        if (!ui.item.sortable.received) {
                            ui.item.sortable.dropindex = ui.item.index();
                            var droptarget = ui.item.parent();
                            ui.item.sortable.droptarget = droptarget;

                            var droptargetScope = getElementScope(ui.item.sortable._connectedSortables, droptarget);
                            ui.item.sortable.droptargetModel = droptargetScope.ngModel;

                            // Cancel the sort (let ng-repeat do the sort for us)
                            // Don't cancel if this is the received list because it has
                            // already been canceled in the other list, and trying to cancel
                            // here will mess up the DOM.
                            element.sortable('cancel');
                        }

                        // Put the nodes back exactly the way they started (this is very
                        // important because ng-repeat uses comment elements to delineate
                        // the start and stop of repeat sections and sortable doesn't
                        // respect their order (even if we cancel, the order of the
                        // comments are still messed up).
                        if (hasSortingHelper(element, ui) && !ui.item.sortable.received &&
                            element.sortable('option', 'appendTo') === 'parent') {
                            // restore all the savedNodes except .ui-sortable-helper element
                            // (which is placed last). That way it will be garbage collected.
                            savedNodes = savedNodes.not(savedNodes.last());
                        }
                        savedNodes.appendTo(element);

                        // If this is the target connected list then
                        // it's safe to clear the restored nodes since:
                        // update is currently running and
                        // stop is not called for the target list.
                        if (ui.item.sortable.received) {
                            savedNodes = null;
                        }

                        // If received is true (an item was dropped in from another list)
                        // then we add the new item to this list otherwise wait until the
                        // stop event where we will know if it was a sort or item was
                        // moved here from another list
                        if (ui.item.sortable.received && !ui.item.sortable.isCanceled()) {
                            scope.$apply(function () {
                                ngModel.$modelValue.splice(ui.item.sortable.dropindex, 0,
                                                           ui.item.sortable.moved);
                            });
                        }
                    };

                    callbacks.stop = function (e, ui) {
                        // If the received flag hasn't be set on the item, this is a
                        // normal sort, if dropindex is set, the item was moved, so move
                        // the items in the list.
                        if (!ui.item.sortable.received &&
                           ('dropindex' in ui.item.sortable) &&
                           !ui.item.sortable.isCanceled()) {

                            scope.$apply(function () {
                                ngModel.$modelValue.splice(
                                  ui.item.sortable.dropindex, 0,
                                  ngModel.$modelValue.splice(ui.item.sortable.index, 1)[0]);
                            });
                        } else {
                            // if the item was not moved, then restore the elements
                            // so that the ngRepeat's comment are correct.
                            if ((!('dropindex' in ui.item.sortable) || ui.item.sortable.isCanceled()) &&
                                !hasSortingHelper(element, ui)) {
                                savedNodes.appendTo(element);
                            }
                        }

                        // It's now safe to clear the savedNodes
                        // since stop is the last callback.
                        savedNodes = null;
                    };

                    callbacks.receive = function (e, ui) {
                        // An item was dropped here from another list, set a flag on the
                        // item.
                        ui.item.sortable.received = true;
                    };

                    callbacks.remove = function (e, ui) {
                        // Workaround for a problem observed in nested connected lists.
                        // There should be an 'update' event before 'remove' when moving
                        // elements. If the event did not fire, cancel sorting.
                        if (!('dropindex' in ui.item.sortable)) {
                            element.sortable('cancel');
                            ui.item.sortable.cancel();
                        }

                        // Remove the item from this list's model and copy data into item,
                        // so the next list can retrive it
                        if (!ui.item.sortable.isCanceled()) {
                            scope.$apply(function () {
                                ui.item.sortable.moved = ngModel.$modelValue.splice(
                                  ui.item.sortable.index, 1)[0];
                            });
                        }
                    };

                    wrappers.helper = function (inner) {
                        if (inner && typeof inner === 'function') {
                            return function (e, item) {
                                var innerResult = inner.apply(this, arguments);
                                item.sortable._isCustomHelperUsed = item !== innerResult;
                                return innerResult;
                            };
                        }
                        return inner;
                    };

                    scope.$watch('uiSortable', function (newVal /*, oldVal*/) {
                        // ensure that the jquery-ui-sortable widget instance
                        // is still bound to the directive's element
                        var sortableWidgetInstance = getSortableWidgetInstance(element);
                        if (!!sortableWidgetInstance) {
                            angular.forEach(newVal, function (value, key) {
                                // if it's a custom option of the directive,
                                // handle it approprietly
                                if (key in directiveOpts) {
                                    if (key === 'ui-floating' && (value === false || value === true)) {
                                        sortableWidgetInstance.floating = value;
                                    }

                                    opts[key] = value;
                                    return;
                                }

                                if (callbacks[key]) {
                                    if (key === 'stop') {
                                        // call apply after stop
                                        value = combineCallbacks(
                                          value, function () { scope.$apply(); });

                                        value = combineCallbacks(value, afterStop);
                                    }
                                    // wrap the callback
                                    value = combineCallbacks(callbacks[key], value);
                                } else if (wrappers[key]) {
                                    value = wrappers[key](value);
                                }

                                opts[key] = value;
                                element.sortable('option', key, value);
                            });
                        }
                    }, true);

                    angular.forEach(callbacks, function (value, key) {
                        opts[key] = combineCallbacks(value, opts[key]);
                        if (key === 'stop') {
                            opts[key] = combineCallbacks(opts[key], afterStop);
                        }
                    });

                } else {
                    $log.info('ui.sortable: ngModel not provided!', element);
                }

                // Create sortable
                element.sortable(opts);
            }
        };
    }
  ]);
