vendor: 635 bytes, lines 1-8
1/** include: angular.min.js **/ 2window.core&&core.registerScript&&core.registerScript('/public/javascripts/angular-1.8.2/angular.min.js'); 3/* 4 AngularJS v1.8.2 5 (c) 2010-2020 Google LLC. http://angularjs.org 6 License: MIT 7*/ 8(function(z){'use strict';function ve(a){if(D(a))w(a.objectMaxDepth)&&(Xb.objectMaxDepth=Yb(a.objectMaxDepth)?a.objectMaxDepth:NaN),w(a.urlErrorParamsEnabled)&&Ga(a.urlErrorParamsEnabled)&&(Xb.urlErrorParamsEnabled=a.urlErrorParamsEnabled);else return Xb}function Yb(a){return X(a)&&0<a}function F(a,b){b=b||Error;return function(){var d=arguments[0],c;c="["+(a?a+":":"")+d+"] http://errors.angularjs.org/1.8.2
vendor: 13,480 bytes, lines 8-34
8/"+(a?a+"/":"")+d;for(d=1;d<arguments.length;d++){c=c+(1==d?"?":"&")+"p"+(d-1)+"=";var e=encodeURIComponent, 9f;f=arguments[d];f="function"==typeof f?f.toString().replace(/ \{[\s\S]*$/,""):"undefined"==typeof f?"undefined":"string"!=typeof f?JSON.stringify(f):f;c+=e(f)}return new b(c)}}function za(a){if(null==a||$a(a))return!1;if(H(a)||C(a)||x&&a instanceof x)return!0;var b="length"in Object(a)&&a.length;return X(b)&&(0<=b&&b-1 in a||"function"===typeof a.item)}function r(a,b,d){var c,e;if(a)if(B(a))for(c in a)"prototype"!==c&&"length"!==c&&"name"!==c&&a.hasOwnProperty(c)&&b.call(d,a[c],c,a);else if(H(a)|| 10za(a)){var f="object"!==typeof a;c=0;for(e=a.length;c<e;c++)(f||c in a)&&b.call(d,a[c],c,a)}else if(a.forEach&&a.forEach!==r)a.forEach(b,d,a);else if(Pc(a))for(c in a)b.call(d,a[c],c,a);else if("function"===typeof a.hasOwnProperty)for(c in a)a.hasOwnProperty(c)&&b.call(d,a[c],c,a);else for(c in a)ta.call(a,c)&&b.call(d,a[c],c,a);return a}function Qc(a,b,d){for(var c=Object.keys(a).sort(),e=0;e<c.length;e++)b.call(d,a[c[e]],c[e]);return c}function Zb(a){return function(b,d){a(d,b)}}function we(){return++qb} 11function $b(a,b,d){for(var c=a.$$hashKey,e=0,f=b.length;e<f;++e){var g=b[e];if(D(g)||B(g))for(var k=Object.keys(g),h=0,l=k.length;h<l;h++){var m=k[h],p=g[m];d&&D(p)?ha(p)?a[m]=new Date(p.valueOf()):ab(p)?a[m]=new RegExp(p):p.nodeName?a[m]=p.cloneNode(!0):ac(p)?a[m]=p.clone():"__proto__"!==m&&(D(a[m])||(a[m]=H(p)?[]:{}),$b(a[m],[p],!0)):a[m]=p}}c?a.$$hashKey=c:delete a.$$hashKey;return a}function S(a){return $b(a,Ha.call(arguments,1),!1)}function xe(a){return $b(a,Ha.call(arguments,1),!0)}function fa(a){return parseInt(a, 1210)}function bc(a,b){return S(Object.create(a),b)}function E(){}function Ta(a){return a}function ia(a){return function(){return a}}function cc(a){return B(a.toString)&&a.toString!==la}function A(a){return"undefined"===typeof a}function w(a){return"undefined"!==typeof a}function D(a){return null!==a&&"object"===typeof a}function Pc(a){return null!==a&&"object"===typeof a&&!Rc(a)}function C(a){return"string"===typeof a}function X(a){return"number"===typeof a}function ha(a){return"[object Date]"===la.call(a)} 13function H(a){return Array.isArray(a)||a instanceof Array}function dc(a){switch(la.call(a)){case "[object Error]":return!0;case "[object Exception]":return!0;case "[object DOMException]":return!0;default:return a instanceof Error}}function B(a){return"function"===typeof a}function ab(a){return"[object RegExp]"===la.call(a)}function $a(a){return a&&a.window===a}function bb(a){return a&&a.$evalAsync&&a.$watch}function Ga(a){return"boolean"===typeof a}function ye(a){return a&&X(a.length)&&ze.test(la.call(a))} 14function ac(a){return!(!a||!(a.nodeName||a.prop&&a.attr&&a.find))}function Ae(a){var b={};a=a.split(",");var d;for(d=0;d<a.length;d++)b[a[d]]=!0;return b}function ua(a){return K(a.nodeName||a[0]&&a[0].nodeName)}function cb(a,b){var d=a.indexOf(b);0<=d&&a.splice(d,1);return d}function Ia(a,b,d){function c(a,b,c){c--;if(0>c)return"...";var d=b.$$hashKey,f;if(H(a)){f=0;for(var g=a.length;f<g;f++)b.push(e(a[f],c))}else if(Pc(a))for(f in a)b[f]=e(a[f],c);else if(a&&"function"===typeof a.hasOwnProperty)for(f in a)a.hasOwnProperty(f)&& 15(b[f]=e(a[f],c));else for(f in a)ta.call(a,f)&&(b[f]=e(a[f],c));d?b.$$hashKey=d:delete b.$$hashKey;return b}function e(a,b){if(!D(a))return a;var d=g.indexOf(a);if(-1!==d)return k[d];if($a(a)||bb(a))throw oa("cpws");var d=!1,e=f(a);void 0===e&&(e=H(a)?[]:Object.create(Rc(a)),d=!0);g.push(a);k.push(e);return d?c(a,e,b):e}function f(a){switch(la.call(a)){case "[object Int8Array]":case "[object Int16Array]":case "[object Int32Array]":case "[object Float32Array]":case "[object Float64Array]":case "[object Uint8Array]":case "[object Uint8ClampedArray]":case "[object Uint16Array]":case "[object Uint32Array]":return new a.constructor(e(a.buffer), 16a.byteOffset,a.length);case "[object ArrayBuffer]":if(!a.slice){var b=new ArrayBuffer(a.byteLength);(new Uint8Array(b)).set(new Uint8Array(a));return b}return a.slice(0);case "[object Boolean]":case "[object Number]":case "[object String]":case "[object Date]":return new a.constructor(a.valueOf());case "[object RegExp]":return b=new RegExp(a.source,a.toString().match(/[^/]*$/)[0]),b.lastIndex=a.lastIndex,b;case "[object Blob]":return new a.constructor([a],{type:a.type})}if(B(a.cloneNode))return a.cloneNode(!0)} 17var g=[],k=[];d=Yb(d)?d:NaN;if(b){if(ye(b)||"[object ArrayBuffer]"===la.call(b))throw oa("cpta");if(a===b)throw oa("cpi");H(b)?b.length=0:r(b,function(a,c){"$$hashKey"!==c&&delete b[c]});g.push(a);k.push(b);return c(a,b,d)}return e(a,d)}function ec(a,b){return a===b||a!==a&&b!==b}function va(a,b){if(a===b)return!0;if(null===a||null===b)return!1;if(a!==a&&b!==b)return!0;var d=typeof a,c;if(d===typeof b&&"object"===d)if(H(a)){if(!H(b))return!1;if((d=a.length)===b.length){for(c=0;c<d;c++)if(!va(a[c], 18b[c]))return!1;return!0}}else{if(ha(a))return ha(b)?ec(a.getTime(),b.getTime()):!1;if(ab(a))return ab(b)?a.toString()===b.toString():!1;if(bb(a)||bb(b)||$a(a)||$a(b)||H(b)||ha(b)||ab(b))return!1;d=T();for(c in a)if("$"!==c.charAt(0)&&!B(a[c])){if(!va(a[c],b[c]))return!1;d[c]=!0}for(c in b)if(!(c in d)&&"$"!==c.charAt(0)&&w(b[c])&&!B(b[c]))return!1;return!0}return!1}function db(a,b,d){return a.concat(Ha.call(b,d))}function Va(a,b){var d=2<arguments.length?Ha.call(arguments,2):[];return!B(b)||b instanceof 19RegExp?b:d.length?function(){return arguments.length?b.apply(a,db(d,arguments,0)):b.apply(a,d)}:function(){return arguments.length?b.apply(a,arguments):b.call(a)}}function Sc(a,b){var d=b;"string"===typeof a&&"$"===a.charAt(0)&&"$"===a.charAt(1)?d=void 0:$a(b)?d="$WINDOW":b&&z.document===b?d="$DOCUMENT":bb(b)&&(d="$SCOPE");return d}function eb(a,b){if(!A(a))return X(b)||(b=b?2:null),JSON.stringify(a,Sc,b)}function Tc(a){return C(a)?JSON.parse(a):a}function fc(a,b){a=a.replace(Be,"");var d=Date.parse("Jan 01, 1970 00:00:00 "+ 20a)/6E4;return Y(d)?b:d}function Uc(a,b){a=new Date(a.getTime());a.setMinutes(a.getMinutes()+b);return a}function gc(a,b,d){d=d?-1:1;var c=a.getTimezoneOffset();b=fc(b,c);return Uc(a,d*(b-c))}function Aa(a){a=x(a).clone().empty();var b=x("<div></div>").append(a).html();try{return a[0].nodeType===Pa?K(b):b.match(/^(<[^>]+>)/)[1].replace(/^<([\w-]+)/,function(a,b){return"<"+K(b)})}catch(d){return K(b)}}function Vc(a){try{return decodeURIComponent(a)}catch(b){}}function hc(a){var b={};r((a||"").split("&"), 21function(a){var c,e,f;a&&(e=a=a.replace(/\+/g,"%20"),c=a.indexOf("="),-1!==c&&(e=a.substring(0,c),f=a.substring(c+1)),e=Vc(e),w(e)&&(f=w(f)?Vc(f):!0,ta.call(b,e)?H(b[e])?b[e].push(f):b[e]=[b[e],f]:b[e]=f))});return b}function Ce(a){var b=[];r(a,function(a,c){H(a)?r(a,function(a){b.push(ba(c,!0)+(!0===a?"":"="+ba(a,!0)))}):b.push(ba(c,!0)+(!0===a?"":"="+ba(a,!0)))});return b.length?b.join("&"):""}function ic(a){return ba(a,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function ba(a, 22b){return encodeURIComponent(a).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%3B/gi,";").replace(/%20/g,b?"%20":"+")}function De(a,b){var d,c,e=Qa.length;for(c=0;c<e;++c)if(d=Qa[c]+b,C(d=a.getAttribute(d)))return d;return null}function Ee(a,b){var d,c,e={};r(Qa,function(b){b+="app";!d&&a.hasAttribute&&a.hasAttribute(b)&&(d=a,c=a.getAttribute(b))});r(Qa,function(b){b+="app";var e;!d&&(e=a.querySelector("["+b.replace(":","\\:")+"]"))&&(d=e,c=e.getAttribute(b))}); 23d&&(Fe?(e.strictDi=null!==De(d,"strict-di"),b(d,c?[c]:[],e)):z.console.error("AngularJS: disabling automatic bootstrap. <script> protocol indicates an extension, document.location.href does not match."))}function Wc(a,b,d){D(d)||(d={});d=S({strictDi:!1},d);var c=function(){a=x(a);if(a.injector()){var c=a[0]===z.document?"document":Aa(a);throw oa("btstrpd",c.replace(/</,"<").replace(/>/,">"));}b=b||[];b.unshift(["$provide",function(b){b.value("$rootElement",a)}]);d.debugInfoEnabled&&b.push(["$compileProvider", 24function(a){a.debugInfoEnabled(!0)}]);b.unshift("ng");c=fb(b,d.strictDi);c.invoke(["$rootScope","$rootElement","$compile","$injector",function(a,b,c,d){a.$apply(function(){b.data("$injector",d);c(b)(a)})}]);return c},e=/^NG_ENABLE_DEBUG_INFO!/,f=/^NG_DEFER_BOOTSTRAP!/;z&&e.test(z.name)&&(d.debugInfoEnabled=!0,z.name=z.name.replace(e,""));if(z&&!f.test(z.name))return c();z.name=z.name.replace(f,"");ca.resumeBootstrap=function(a){r(a,function(a){b.push(a)});return c()};B(ca.resumeDeferredBootstrap)&& 25ca.resumeDeferredBootstrap()}function Ge(){z.name="NG_ENABLE_DEBUG_INFO!"+z.name;z.location.reload()}function He(a){a=ca.element(a).injector();if(!a)throw oa("test");return a.get("$$testability")}function Xc(a,b){b=b||"_";return a.replace(Ie,function(a,c){return(c?b:"")+a.toLowerCase()})}function Je(){var a;if(!Yc){var b=rb();(sb=A(b)?z.jQuery:b?z[b]:void 0)&&sb.fn.on?(x=sb,S(sb.fn,{scope:Wa.scope,isolateScope:Wa.isolateScope,controller:Wa.controller,injector:Wa.injector,inheritedData:Wa.inheritedData})): 26x=U;a=x.cleanData;x.cleanData=function(b){for(var c,e=0,f;null!=(f=b[e]);e++)(c=(x._data(f)||{}).events)&&c.$destroy&&x(f).triggerHandler("$destroy");a(b)};ca.element=x;Yc=!0}}function Ke(){U.legacyXHTMLReplacement=!0}function gb(a,b,d){if(!a)throw oa("areq",b||"?",d||"required");return a}function tb(a,b,d){d&&H(a)&&(a=a[a.length-1]);gb(B(a),b,"not a function, got "+(a&&"object"===typeof a?a.constructor.name||"Object":typeof a));return a}function Ja(a,b){if("hasOwnProperty"===a)throw oa("badname", 27b);}function Le(a,b,d){if(!b)return a;b=b.split(".");for(var c,e=a,f=b.length,g=0;g<f;g++)c=b[g],a&&(a=(e=a)[c]);return!d&&B(a)?Va(e,a):a}function ub(a){for(var b=a[0],d=a[a.length-1],c,e=1;b!==d&&(b=b.nextSibling);e++)if(c||a[e]!==b)c||(c=x(Ha.call(a,0,e))),c.push(b);return c||a}function T(){return Object.create(null)}function jc(a){if(null==a)return"";switch(typeof a){case "string":break;case "number":a=""+a;break;default:a=!cc(a)||H(a)||ha(a)?eb(a):a.toString()}return a}function Me(a){function b(a, 28b,c){return a[b]||(a[b]=c())}var d=F("$injector"),c=F("ng");a=b(a,"angular",Object);a.$$minErr=a.$$minErr||F;return b(a,"module",function(){var a={};return function(f,g,k){var h={};if("hasOwnProperty"===f)throw c("badname","module");g&&a.hasOwnProperty(f)&&(a[f]=null);return b(a,f,function(){function a(b,c,d,f){f||(f=e);return function(){f[d||"push"]([b,c,arguments]);return t}}function b(a,c,d){d||(d=e);return function(b,e){e&&B(e)&&(e.$$moduleName=f);d.push([a,c,arguments]);return t}}if(!g)throw d("nomod", 29f);var e=[],n=[],s=[],G=a("$injector","invoke","push",n),t={_invokeQueue:e,_configBlocks:n,_runBlocks:s,info:function(a){if(w(a)){if(!D(a))throw c("aobj","value");h=a;return this}return h},requires:g,name:f,provider:b("$provide","provider"),factory:b("$provide","factory"),service:b("$provide","service"),value:a("$provide","value"),constant:a("$provide","constant","unshift"),decorator:b("$provide","decorator",n),animation:b("$animateProvider","register"),filter:b("$filterProvider","register"),controller:b("$controllerProvider", 30"register"),directive:b("$compileProvider","directive"),component:b("$compileProvider","component"),config:G,run:function(a){s.push(a);return this}};k&&G(k);return t})}})}function ja(a,b){if(H(a)){b=b||[];for(var d=0,c=a.length;d<c;d++)b[d]=a[d]}else if(D(a))for(d in b=b||{},a)if("$"!==d.charAt(0)||"$"!==d.charAt(1))b[d]=a[d];return b||a}function Ne(a,b){var d=[];Yb(b)&&(a=ca.copy(a,null,b));return JSON.stringify(a,function(a,b){b=Sc(a,b);if(D(b)){if(0<=d.indexOf(b))return"...";d.push(b)}return b})} 31function Oe(a){S(a,{errorHandlingConfig:ve,bootstrap:Wc,copy:Ia,extend:S,merge:xe,equals:va,element:x,forEach:r,injector:fb,noop:E,bind:Va,toJson:eb,fromJson:Tc,identity:Ta,isUndefined:A,isDefined:w,isString:C,isFunction:B,isObject:D,isNumber:X,isElement:ac,isArray:H,version:Pe,isDate:ha,callbacks:{$$counter:0},getTestability:He,reloadWithDebugInfo:Ge,UNSAFE_restoreLegacyJqLiteXHTMLReplacement:Ke,$$minErr:F,$$csp:Ba,$$encodeUriSegment:ic,$$encodeUriQuery:ba,$$lowercase:K,$$stringify:jc,$$uppercase:vb}); 32lc=Me(z);lc("ng",["ngLocale"],["$provide",function(a){a.provider({$$sanitizeUri:Qe});a.provider("$compile",Zc).directive({a:Re,input:$c,textarea:$c,form:Se,script:Te,select:Ue,option:Ve,ngBind:We,ngBindHtml:Xe,ngBindTemplate:Ye,ngClass:Ze,ngClassEven:$e,ngClassOdd:af,ngCloak:bf,ngController:cf,ngForm:df,ngHide:ef,ngIf:ff,ngInclude:gf,ngInit:hf,ngNonBindable:jf,ngPluralize:kf,ngRef:lf,ngRepeat:mf,ngShow:nf,ngStyle:of,ngSwitch:pf,ngSwitchWhen:qf,ngSwitchDefault:rf,ngOptions:sf,ngTransclude:tf,ngModel:uf, 33ngList:vf,ngChange:wf,pattern:ad,ngPattern:ad,required:bd,ngRequired:bd,minlength:cd,ngMinlength:cd,maxlength:dd,ngMaxlength:dd,ngValue:xf,ngModelOptions:yf}).directive({ngInclude:zf,input:Af}).directive(wb).directive(ed);a.provider({$anchorScroll:Bf,$animate:Cf,$animateCss:Df,$$animateJs:Ef,$$animateQueue:Ff,$$AnimateRunner:Gf,$$animateAsyncRun:Hf,$browser:If,$cacheFactory:Jf,$controller:Kf,$document:Lf,$$isDocumentHidden:Mf,$exceptionHandler:Nf,$filter:fd,$$forceReflow:Of,$interpolate:Pf,$interval:Qf, 34$$intervalFactory:Rf,$http:Sf,$httpParamSerializer:Tf,$httpParamSerializerJQLike:Uf,$httpBackend:Vf,$xhrFactory:Wf,$jsonpCallbacks:Xf,$location:Yf,$log:Zf,$parse:$f,$rootScope:ag,$q:bg,$$q:cg,$sce:dg,$sceDelegate:eg,$sniffer:fg,$$taskTrackerFactory:gg,$templateCache:hg,$templateRequest:ig,$$testability:jg,$timeout:kg,$window:lg,$$rAF:mg,$$jqLite:ng,$$Map:og,$$cookieReader:pg})}]).info({angularVersion:"1.8.
342
vendor: 89,049 bytes, lines 34-209
34"})}function xb(a,b){return b.toUpperCase()}function yb(a){return a.replace(qg,xb)}function mc(a){a= 35a.nodeType;return 1===a||!a||9===a}function gd(a,b){var d,c,e,f=b.createDocumentFragment(),g=[],k;if(nc.test(a)){d=f.appendChild(b.createElement("div"));c=(rg.exec(a)||["",""])[1].toLowerCase();e=U.legacyXHTMLReplacement?a.replace(sg,"<$1></$2>"):a;if(10>wa)for(c=hb[c]||hb._default,d.innerHTML=c[1]+e+c[2],k=c[0];k--;)d=d.firstChild;else{c=qa[c]||[];for(k=c.length;-1<--k;)d.appendChild(z.document.createElement(c[k])),d=d.firstChild;d.innerHTML=e}g=db(g,d.childNodes);d=f.firstChild;d.textContent=""}else g.push(b.createTextNode(a)); 36f.textContent="";f.innerHTML="";r(g,function(a){f.appendChild(a)});return f}function U(a){if(a instanceof U)return a;var b;C(a)&&(a=V(a),b=!0);if(!(this instanceof U)){if(b&&"<"!==a.charAt(0))throw oc("nosel");return new U(a)}if(b){b=z.document;var d;a=(d=tg.exec(a))?[b.createElement(d[1])]:(d=gd(a,b))?d.childNodes:[];pc(this,a)}else B(a)?hd(a):pc(this,a)}function qc(a){return a.cloneNode(!0)}function zb(a,b){!b&&mc(a)&&x.cleanData([a]);a.querySelectorAll&&x.cleanData(a.querySelectorAll("*"))}function id(a){for(var b in a)return!1; 37return!0}function jd(a){var b=a.ng339,d=b&&Ka[b],c=d&&d.events,d=d&&d.data;d&&!id(d)||c&&!id(c)||(delete Ka[b],a.ng339=void 0)}function kd(a,b,d,c){if(w(c))throw oc("offargs");var e=(c=Ab(a))&&c.events,f=c&&c.handle;if(f){if(b){var g=function(b){var c=e[b];w(d)&&cb(c||[],d);w(d)&&c&&0<c.length||(a.removeEventListener(b,f),delete e[b])};r(b.split(" "),function(a){g(a);Bb[a]&&g(Bb[a])})}else for(b in e)"$destroy"!==b&&a.removeEventListener(b,f),delete e[b];jd(a)}}function rc(a,b){var d=a.ng339;if(d= 38d&&Ka[d])b?delete d.data[b]:d.data={},jd(a)}function Ab(a,b){var d=a.ng339,d=d&&Ka[d];b&&!d&&(a.ng339=d=++ug,d=Ka[d]={events:{},data:{},handle:void 0});return d}function sc(a,b,d){if(mc(a)){var c,e=w(d),f=!e&&b&&!D(b),g=!b;a=(a=Ab(a,!f))&&a.data;if(e)a[yb(b)]=d;else{if(g)return a;if(f)return a&&a[yb(b)];for(c in b)a[yb(c)]=b[c]}}}function Cb(a,b){return a.getAttribute?-1<(" "+(a.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ").indexOf(" "+b+" "):!1}function Db(a,b){if(b&&a.setAttribute){var d= 39(" "+(a.getAttribute("class")||"")+" ").replace(/[\n\t]/g," "),c=d;r(b.split(" "),function(a){a=V(a);c=c.replace(" "+a+" "," ")});c!==d&&a.setAttribute("class",V(c))}}function Eb(a,b){if(b&&a.setAttribute){var d=(" "+(a.getAttribute("class")||"")+" ").replace(/[\n\t]/g," "),c=d;r(b.split(" "),function(a){a=V(a);-1===c.indexOf(" "+a+" ")&&(c+=a+" ")});c!==d&&a.setAttribute("class",V(c))}}function pc(a,b){if(b)if(b.nodeType)a[a.length++]=b;else{var d=b.length;if("number"===typeof d&&b.window!==b){if(d)for(var c= 400;c<d;c++)a[a.length++]=b[c]}else a[a.length++]=b}}function ld(a,b){return Fb(a,"$"+(b||"ngController")+"Controller")}function Fb(a,b,d){9===a.nodeType&&(a=a.documentElement);for(b=H(b)?b:[b];a;){for(var c=0,e=b.length;c<e;c++)if(w(d=x.data(a,b[c])))return d;a=a.parentNode||11===a.nodeType&&a.host}}function md(a){for(zb(a,!0);a.firstChild;)a.removeChild(a.firstChild)}function Gb(a,b){b||zb(a);var d=a.parentNode;d&&d.removeChild(a)}function vg(a,b){b=b||z;if("complete"===b.document.readyState)b.setTimeout(a); 41else x(b).on("load",a)}function hd(a){function b(){z.document.removeEventListener("DOMContentLoaded",b);z.removeEventListener("load",b);a()}"complete"===z.document.readyState?z.setTimeout(a):(z.document.addEventListener("DOMContentLoaded",b),z.addEventListener("load",b))}function nd(a,b){var d=Hb[b.toLowerCase()];return d&&od[ua(a)]&&d}function wg(a,b){var d=function(c,d){c.isDefaultPrevented=function(){return c.defaultPrevented};var f=b[d||c.type],g=f?f.length:0;if(g){if(A(c.immediatePropagationStopped)){var k= 42c.stopImmediatePropagation;c.stopImmediatePropagation=function(){c.immediatePropagationStopped=!0;c.stopPropagation&&c.stopPropagation();k&&k.call(c)}}c.isImmediatePropagationStopped=function(){return!0===c.immediatePropagationStopped};var h=f.specialHandlerWrapper||xg;1<g&&(f=ja(f));for(var l=0;l<g;l++)c.isImmediatePropagationStopped()||h(a,c,f[l])}};d.elem=a;return d}function xg(a,b,d){d.call(a,b)}function yg(a,b,d){var c=b.relatedTarget;c&&(c===a||zg.call(a,c))||d.call(a,b)}function ng(){this.$get= 43function(){return S(U,{hasClass:function(a,b){a.attr&&(a=a[0]);return Cb(a,b)},addClass:function(a,b){a.attr&&(a=a[0]);return Eb(a,b)},removeClass:function(a,b){a.attr&&(a=a[0]);return Db(a,b)}})}}function La(a,b){var d=a&&a.$$hashKey;if(d)return"function"===typeof d&&(d=a.$$hashKey()),d;d=typeof a;return d="function"===d||"object"===d&&null!==a?a.$$hashKey=d+":"+(b||we)():d+":"+a}function pd(){this._keys=[];this._values=[];this._lastKey=NaN;this._lastIndex=-1}function qd(a){a=Function.prototype.toString.call(a).replace(Ag, 44"");return a.match(Bg)||a.match(Cg)}function Dg(a){return(a=qd(a))?"function("+(a[1]||"").replace(/[\s\r\n]+/," ")+")":"fn"}function fb(a,b){function d(a){return function(b,c){if(D(b))r(b,Zb(a));else return a(b,c)}}function c(a,b){Ja(a,"service");if(B(b)||H(b))b=n.instantiate(b);if(!b.$get)throw Ca("pget",a);return p[a+"Provider"]=b}function e(a,b){return function(){var c=t.invoke(b,this);if(A(c))throw Ca("undef",a);return c}}function f(a,b,d){return c(a,{$get:!1!==d?e(a,b):b})}function g(a){gb(A(a)|| 45H(a),"modulesToLoad","not an array");var b=[],c;r(a,function(a){function d(a){var b,c;b=0;for(c=a.length;b<c;b++){var e=a[b],f=n.get(e[0]);f[e[1]].apply(f,e[2])}}if(!m.get(a)){m.set(a,!0);try{C(a)?(c=lc(a),t.modules[a]=c,b=b.concat(g(c.requires)).concat(c._runBlocks),d(c._invokeQueue),d(c._configBlocks)):B(a)?b.push(n.invoke(a)):H(a)?b.push(n.invoke(a)):tb(a,"module")}catch(e){throw H(a)&&(a=a[a.length-1]),e.message&&e.stack&&-1===e.stack.indexOf(e.message)&&(e=e.message+"\n"+e.stack),Ca("modulerr", 46a,e.stack||e.message||e);}}});return b}function k(a,c){function d(b,e){if(a.hasOwnProperty(b)){if(a[b]===h)throw Ca("cdep",b+" <- "+l.join(" <- "));return a[b]}try{return l.unshift(b),a[b]=h,a[b]=c(b,e),a[b]}catch(f){throw a[b]===h&&delete a[b],f;}finally{l.shift()}}function e(a,c,f){var g=[];a=fb.$$annotate(a,b,f);for(var h=0,k=a.length;h<k;h++){var l=a[h];if("string"!==typeof l)throw Ca("itkn",l);g.push(c&&c.hasOwnProperty(l)?c[l]:d(l,f))}return g}return{invoke:function(a,b,c,d){"string"===typeof c&& 47(d=c,c=null);c=e(a,c,d);H(a)&&(a=a[a.length-1]);d=a;if(wa||"function"!==typeof d)d=!1;else{var f=d.$$ngIsClass;Ga(f)||(f=d.$$ngIsClass=/^class\b/.test(Function.prototype.toString.call(d)));d=f}return d?(c.unshift(null),new (Function.prototype.bind.apply(a,c))):a.apply(b,c)},instantiate:function(a,b,c){var d=H(a)?a[a.length-1]:a;a=e(a,b,c);a.unshift(null);return new (Function.prototype.bind.apply(d,a))},get:d,annotate:fb.$$annotate,has:function(b){return p.hasOwnProperty(b+"Provider")||a.hasOwnProperty(b)}}} 48b=!0===b;var h={},l=[],m=new Ib,p={$provide:{provider:d(c),factory:d(f),service:d(function(a,b){return f(a,["$injector",function(a){return a.instantiate(b)}])}),value:d(function(a,b){return f(a,ia(b),!1)}),constant:d(function(a,b){Ja(a,"constant");p[a]=b;s[a]=b}),decorator:function(a,b){var c=n.get(a+"Provider"),d=c.$get;c.$get=function(){var a=t.invoke(d,c);return t.invoke(b,null,{$delegate:a})}}}},n=p.$injector=k(p,function(a,b){ca.isString(b)&&l.push(b);throw Ca("unpr",l.join(" <- "));}),s={}, 49G=k(s,function(a,b){var c=n.get(a+"Provider",b);return t.invoke(c.$get,c,void 0,a)}),t=G;p.$injectorProvider={$get:ia(G)};t.modules=n.modules=T();var N=g(a),t=G.get("$injector");t.strictDi=b;r(N,function(a){a&&t.invoke(a)});t.loadNewModules=function(a){r(g(a),function(a){a&&t.invoke(a)})};return t}function Bf(){var a=!0;this.disableAutoScrolling=function(){a=!1};this.$get=["$window","$location","$rootScope",function(b,d,c){function e(a){var b=null;Array.prototype.some.call(a,function(a){if("a"=== 50ua(a))return b=a,!0});return b}function f(a){if(a){a.scrollIntoView();var c;c=g.yOffset;B(c)?c=c():ac(c)?(c=c[0],c="fixed"!==b.getComputedStyle(c).position?0:c.getBoundingClientRect().bottom):X(c)||(c=0);c&&(a=a.getBoundingClientRect().top,b.scrollBy(0,a-c))}else b.scrollTo(0,0)}function g(a){a=C(a)?a:X(a)?a.toString():d.hash();var b;a?(b=k.getElementById(a))?f(b):(b=e(k.getElementsByName(a)))?f(b):"top"===a&&f(null):f(null)}var k=b.document;a&&c.$watch(function(){return d.hash()},function(a,b){a=== 51b&&""===a||vg(function(){c.$evalAsync(g)})});return g}]}function ib(a,b){if(!a&&!b)return"";if(!a)return b;if(!b)return a;H(a)&&(a=a.join(" "));H(b)&&(b=b.join(" "));return a+" "+b}function Eg(a){C(a)&&(a=a.split(" "));var b=T();r(a,function(a){a.length&&(b[a]=!0)});return b}function ra(a){return D(a)?a:{}}function Fg(a,b,d,c,e){function f(){pa=null;k()}function g(){t=y();t=A(t)?null:t;va(t,P)&&(t=P);N=P=t}function k(){var a=N;g();if(v!==h.url()||a!==t)v=h.url(),N=t,r(J,function(a){a(h.url(),t)})} 52var h=this,l=a.location,m=a.history,p=a.setTimeout,n=a.clearTimeout,s={},G=e(d);h.isMock=!1;h.$$completeOutstandingRequest=G.completeTask;h.$$incOutstandingRequestCount=G.incTaskCount;h.notifyWhenNoOutstandingRequests=G.notifyWhenNoPendingTasks;var t,N,v=l.href,kc=b.find("base"),pa=null,y=c.history?function(){try{return m.state}catch(a){}}:E;g();h.url=function(b,d,e){A(e)&&(e=null);l!==a.location&&(l=a.location);m!==a.history&&(m=a.history);if(b){var f=N===e;b=ga(b).href;if(v===b&&(!c.history||f))return h; 53var k=v&&Da(v)===Da(b);v=b;N=e;!c.history||k&&f?(k||(pa=b),d?l.replace(b):k?(d=l,e=b,f=e.indexOf("#"),e=-1===f?"":e.substr(f),d.hash=e):l.href=b,l.href!==b&&(pa=b)):(m[d?"replaceState":"pushState"](e,"",b),g());pa&&(pa=b);return h}return(pa||l.href).replace(/#$/,"")};h.state=function(){return t};var J=[],I=!1,P=null;h.onUrlChange=function(b){if(!I){if(c.history)x(a).on("popstate",f);x(a).on("hashchange",f);I=!0}J.push(b);return b};h.$$applicationDestroyed=function(){x(a).off("hashchange popstate", 54f)};h.$$checkUrlChange=k;h.baseHref=function(){var a=kc.attr("href");return a?a.replace(/^(https?:)?\/\/[^/]*/,""):""};h.defer=function(a,b,c){var d;b=b||0;c=c||G.DEFAULT_TASK_TYPE;G.incTaskCount(c);d=p(function(){delete s[d];G.completeTask(a,c)},b);s[d]=c;return d};h.defer.cancel=function(a){if(s.hasOwnProperty(a)){var b=s[a];delete s[a];n(a);G.completeTask(E,b);return!0}return!1}}function If(){this.$get=["$window","$log","$sniffer","$document","$$taskTrackerFactory",function(a,b,d,c,e){return new Fg(a, 55c,b,d,e)}]}function Jf(){this.$get=function(){function a(a,c){function e(a){a!==p&&(n?n===a&&(n=a.n):n=a,f(a.n,a.p),f(a,p),p=a,p.n=null)}function f(a,b){a!==b&&(a&&(a.p=b),b&&(b.n=a))}if(a in b)throw F("$cacheFactory")("iid",a);var g=0,k=S({},c,{id:a}),h=T(),l=c&&c.capacity||Number.MAX_VALUE,m=T(),p=null,n=null;return b[a]={put:function(a,b){if(!A(b)){if(l<Number.MAX_VALUE){var c=m[a]||(m[a]={key:a});e(c)}a in h||g++;h[a]=b;g>l&&this.remove(n.key);return b}},get:function(a){if(l<Number.MAX_VALUE){var b= 56m[a];if(!b)return;e(b)}return h[a]},remove:function(a){if(l<Number.MAX_VALUE){var b=m[a];if(!b)return;b===p&&(p=b.p);b===n&&(n=b.n);f(b.n,b.p);delete m[a]}a in h&&(delete h[a],g--)},removeAll:function(){h=T();g=0;m=T();p=n=null},destroy:function(){m=k=h=null;delete b[a]},info:function(){return S({},k,{size:g})}}}var b={};a.info=function(){var a={};r(b,function(b,e){a[e]=b.info()});return a};a.get=function(a){return b[a]};return a}}function hg(){this.$get=["$cacheFactory",function(a){return a("templates")}]} 57function Zc(a,b){function d(a,b,c){var d=/^([@&]|[=<](\*?))(\??)\s*([\w$]*)$/,e=T();r(a,function(a,f){a=a.trim();if(a in p)e[f]=p[a];else{var g=a.match(d);if(!g)throw $("iscp",b,f,a,c?"controller bindings definition":"isolate scope definition");e[f]={mode:g[1][0],collection:"*"===g[2],optional:"?"===g[3],attrName:g[4]||f};g[4]&&(p[a]=e[f])}});return e}function c(a){var b=a.charAt(0);if(!b||b!==K(b))throw $("baddir",a);if(a!==a.trim())throw $("baddir",a);}function e(a){var b=a.require||a.controller&& 58a.name;!H(b)&&D(b)&&r(b,function(a,c){var d=a.match(l);a.substring(d[0].length)||(b[c]=d[0]+c)});return b}var f={},g=/^\s*directive:\s*([\w-]+)\s+(.*)$/,k=/(([\w-]+)(?::([^;]+))?;?)/,h=Ae("ngSrc,ngSrcset,src,srcset"),l=/^(?:(\^\^?)?(\?)?(\^\^?)?)?/,m=/^(on[a-z]+|formaction)$/,p=T();this.directive=function pa(b,d){gb(b,"name");Ja(b,"directive");C(b)?(c(b),gb(d,"directiveFactory"),f.hasOwnProperty(b)||(f[b]=[],a.factory(b+"Directive",["$injector","$exceptionHandler",function(a,c){var d=[];r(f[b],function(f, 59g){try{var h=a.invoke(f);B(h)?h={compile:ia(h)}:!h.compile&&h.link&&(h.compile=ia(h.link));h.priority=h.priority||0;h.index=g;h.name=h.name||b;h.require=e(h);var k=h,l=h.restrict;if(l&&(!C(l)||!/[EACM]/.test(l)))throw $("badrestrict",l,b);k.restrict=l||"EA";h.$$moduleName=f.$$moduleName;d.push(h)}catch(m){c(m)}});return d}])),f[b].push(d)):r(b,Zb(pa));return this};this.component=function y(a,b){function c(a){function e(b){return B(b)||H(b)?function(c,d){return a.invoke(b,this,{$element:c,$attrs:d})}: 60b}var f=b.template||b.templateUrl?b.template:"",g={controller:d,controllerAs:Gg(b.controller)||b.controllerAs||"$ctrl",template:e(f),templateUrl:e(b.templateUrl),transclude:b.transclude,scope:{},bindToController:b.bindings||{},restrict:"E",require:b.require};r(b,function(a,b){"$"===b.charAt(0)&&(g[b]=a)});return g}if(!C(a))return r(a,Zb(Va(this,y))),this;var d=b.controller||function(){};r(b,function(a,b){"$"===b.charAt(0)&&(c[b]=a,B(d)&&(d[b]=a))});c.$inject=["$injector"];return this.directive(a, 61c)};this.aHrefSanitizationTrustedUrlList=function(a){return w(a)?(b.aHrefSanitizationTrustedUrlList(a),this):b.aHrefSanitizationTrustedUrlList()};Object.defineProperty(this,"aHrefSanitizationWhitelist",{get:function(){return this.aHrefSanitizationTrustedUrlList},set:function(a){this.aHrefSanitizationTrustedUrlList=a}});this.imgSrcSanitizationTrustedUrlList=function(a){return w(a)?(b.imgSrcSanitizationTrustedUrlList(a),this):b.imgSrcSanitizationTrustedUrlList()};Object.defineProperty(this,"imgSrcSanitizationWhitelist", 62{get:function(){return this.imgSrcSanitizationTrustedUrlList},set:function(a){this.imgSrcSanitizationTrustedUrlList=a}});var n=!0;this.debugInfoEnabled=function(a){return w(a)?(n=a,this):n};var s=!1;this.strictComponentBindingsEnabled=function(a){return w(a)?(s=a,this):s};var G=10;this.onChangesTtl=function(a){return arguments.length?(G=a,this):G};var t=!0;this.commentDirectivesEnabled=function(a){return arguments.length?(t=a,this):t};var N=!0;this.cssClassDirectivesEnabled=function(a){return arguments.length? 63(N=a,this):N};var v=T();this.addPropertySecurityContext=function(a,b,c){var d=a.toLowerCase()+"|"+b.toLowerCase();if(d in v&&v[d]!==c)throw $("ctxoverride",a,b,v[d],c);v[d]=c;return this};(function(){function a(b,c){r(c,function(a){v[a.toLowerCase()]=b})}a(W.HTML,["iframe|srcdoc","*|innerHTML","*|outerHTML"]);a(W.CSS,["*|style"]);a(W.URL,"area|href area|ping a|href a|ping blockquote|cite body|background del|cite input|src ins|cite q|cite".split(" "));a(W.MEDIA_URL,"audio|src img|src img|srcset source|src source|srcset track|src video|src video|poster".split(" ")); 64a(W.RESOURCE_URL,"*|formAction applet|code applet|codebase base|href embed|src frame|src form|action head|profile html|manifest iframe|src link|href media|src object|codebase object|data script|src".split(" "))})();this.$get=["$injector","$interpolate","$exceptionHandler","$templateRequest","$parse","$controller","$rootScope","$sce","$animate",function(a,b,c,e,p,M,L,u,R){function q(){try{if(!--Ja)throw Ua=void 0,$("infchng",G);L.$apply(function(){for(var a=0,b=Ua.length;a<b;++a)try{Ua[a]()}catch(d){c(d)}Ua= 65void 0})}finally{Ja++}}function ma(a,b){if(!a)return a;if(!C(a))throw $("srcset",b,a.toString());for(var c="",d=V(a),e=/(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/,e=/\s/.test(d)?e:/(,)/,d=d.split(e),e=Math.floor(d.length/2),f=0;f<e;f++)var g=2*f,c=c+u.getTrustedMediaUrl(V(d[g])),c=c+(" "+V(d[g+1]));d=V(d[2*f]).split(/\s/);c+=u.getTrustedMediaUrl(V(d[0]));2===d.length&&(c+=" "+V(d[1]));return c}function w(a,b){if(b){var c=Object.keys(b),d,e,f;d=0;for(e=c.length;d<e;d++)f=c[d],this[f]=b[f]}else this.$attr= 66{};this.$$element=a}function O(a,b,c){Fa.innerHTML="<span "+b+">";b=Fa.firstChild.attributes;var d=b[0];b.removeNamedItem(d.name);d.value=c;a.attributes.setNamedItem(d)}function sa(a,b){try{a.addClass(b)}catch(c){}}function da(a,b,c,d,e){a instanceof x||(a=x(a));var f=Xa(a,b,a,c,d,e);da.$$addScopeClass(a);var g=null;return function(b,c,d){if(!a)throw $("multilink");gb(b,"scope");e&&e.needsNewScope&&(b=b.$parent.$new());d=d||{};var h=d.parentBoundTranscludeFn,k=d.transcludeControllers;d=d.futureParentElement; 67h&&h.$$boundTransclude&&(h=h.$$boundTransclude);g||(g=(d=d&&d[0])?"foreignobject"!==ua(d)&&la.call(d).match(/SVG/)?"svg":"html":"html");d="html"!==g?x(ja(g,x("<div></div>").append(a).html())):c?Wa.clone.call(a):a;if(k)for(var l in k)d.data("$"+l+"Controller",k[l].instance);da.$$addScopeInfo(d,b);c&&c(d,b);f&&f(b,d,d,h);c||(a=f=null);return d}}function Xa(a,b,c,d,e,f){function g(a,c,d,e){var f,k,l,m,p,I,t;if(n)for(t=Array(c.length),m=0;m<h.length;m+=3)f=h[m],t[f]=c[f];else t=c;m=0;for(p=h.length;m< 68p;)k=t[h[m++]],c=h[m++],f=h[m++],c?(c.scope?(l=a.$new(),da.$$addScopeInfo(x(k),l)):l=a,I=c.transcludeOnThisElement?ka(a,c.transclude,e):!c.templateOnThisElement&&e?e:!e&&b?ka(a,b):null,c(f,l,k,d,I)):f&&f(a,k.childNodes,void 0,e)}for(var h=[],k=H(a)||a instanceof x,l,m,p,I,n,t=0;t<a.length;t++){l=new w;11===wa&&jb(a,t,k);m=tc(a[t],[],l,0===t?d:void 0,e);(f=m.length?aa(m,a[t],l,b,c,null,[],[],f):null)&&f.scope&&da.$$addScopeClass(l.$$element);l=f&&f.terminal||!(p=a[t].childNodes)||!p.length?null:Xa(p, 69f?(f.transcludeOnThisElement||!f.templateOnThisElement)&&f.transclude:b);if(f||l)h.push(t,f,l),I=!0,n=n||f;f=null}return I?g:null}function jb(a,b,c){var d=a[b],e=d.parentNode,f;if(d.nodeType===Pa)for(;;){f=e?d.nextSibling:a[b+1];if(!f||f.nodeType!==Pa)break;d.nodeValue+=f.nodeValue;f.parentNode&&f.parentNode.removeChild(f);c&&f===a[b+1]&&a.splice(b+1,1)}}function ka(a,b,c){function d(e,f,g,h,k){e||(e=a.$new(!1,k),e.$$transcluded=!0);return b(e,f,{parentBoundTranscludeFn:c,transcludeControllers:g, 70futureParentElement:h})}var e=d.$$slots=T(),f;for(f in b.$$slots)e[f]=b.$$slots[f]?ka(a,b.$$slots[f],c):null;return d}function tc(a,b,d,e,f){var g=d.$attr,h;switch(a.nodeType){case 1:h=ua(a);Y(b,xa(h),"E",e,f);for(var l,m,n,t,J,s=a.attributes,v=0,G=s&&s.length;v<G;v++){var P=!1,N=!1,r=!1,y=!1,u=!1,M;l=s[v];m=l.name;t=l.value;n=xa(m.toLowerCase());(J=n.match(Ra))?(r="Attr"===J[1],y="Prop"===J[1],u="On"===J[1],m=m.replace(rd,"").toLowerCase().substr(4+J[1].length).replace(/_(.)/g,function(a,b){return b.toUpperCase()})): 71(M=n.match(Sa))&&ca(M[1])&&(P=m,N=m.substr(0,m.length-5)+"end",m=m.substr(0,m.length-6));if(y||u)d[n]=t,g[n]=l.name,y?Ea(a,b,n,m):b.push(sd(p,L,c,n,m,!1));else{n=xa(m.toLowerCase());g[n]=m;if(r||!d.hasOwnProperty(n))d[n]=t,nd(a,n)&&(d[n]=!0);Ia(a,b,t,n,r);Y(b,n,"A",e,f,P,N)}}"input"===h&&"hidden"===a.getAttribute("type")&&a.setAttribute("autocomplete","off");if(!Qa)break;g=a.className;D(g)&&(g=g.animVal);if(C(g)&&""!==g)for(;a=k.exec(g);)n=xa(a[2]),Y(b,n,"C",e,f)&&(d[n]=V(a[3])),g=g.substr(a.index+ 72a[0].length);break;case Pa:na(b,a.nodeValue);break;case 8:if(!Oa)break;F(a,b,d,e,f)}b.sort(ia);return b}function F(a,b,c,d,e){try{var f=g.exec(a.nodeValue);if(f){var h=xa(f[1]);Y(b,h,"M",d,e)&&(c[h]=V(f[2]))}}catch(k){}}function U(a,b,c){var d=[],e=0;if(b&&a.hasAttribute&&a.hasAttribute(b)){do{if(!a)throw $("uterdir",b,c);1===a.nodeType&&(a.hasAttribute(b)&&e++,a.hasAttribute(c)&&e--);d.push(a);a=a.nextSibling}while(0<e)}else d.push(a);return x(d)}function W(a,b,c){return function(d,e,f,g,h){e=U(e[0], 73b,c);return a(d,e,f,g,h)}}function Z(a,b,c,d,e,f){var g;return a?da(b,c,d,e,f):function(){g||(g=da(b,c,d,e,f),b=c=f=null);return g.apply(this,arguments)}}function aa(a,b,d,e,f,g,h,k,l){function m(a,b,c,d){if(a){c&&(a=W(a,c,d));a.require=u.require;a.directiveName=Q;if(s===u||u.$$isolateScope)a=Ba(a,{isolateScope:!0});h.push(a)}if(b){c&&(b=W(b,c,d));b.require=u.require;b.directiveName=Q;if(s===u||u.$$isolateScope)b=Ba(b,{isolateScope:!0});k.push(b)}}function p(a,e,f,g,l){function m(a,b,c,d){var e;bb(a)|| 74(d=c,c=b,b=a,a=void 0);N&&(e=P);c||(c=N?Q.parent():Q);if(d){var f=l.$$slots[d];if(f)return f(a,b,e,c,R);if(A(f))throw $("noslot",d,Aa(Q));}else return l(a,b,e,c,R)}var n,u,L,y,G,P,M,Q;b===f?(g=d,Q=d.$$element):(Q=x(f),g=new w(Q,d));G=e;s?y=e.$new(!0):t&&(G=e.$parent);l&&(M=m,M.$$boundTransclude=l,M.isSlotFilled=function(a){return!!l.$$slots[a]});J&&(P=ea(Q,g,M,J,y,e,s));s&&(da.$$addScopeInfo(Q,y,!0,!(v&&(v===s||v===s.$$originalDirective))),da.$$addScopeClass(Q,!0),y.$$isolateBindings=s.$$isolateBindings, 75u=Da(e,g,y,y.$$isolateBindings,s),u.removeWatches&&y.$on("$destroy",u.removeWatches));for(n in P){u=J[n];L=P[n];var Hg=u.$$bindings.bindToController;L.instance=L();Q.data("$"+u.name+"Controller",L.instance);L.bindingInfo=Da(G,g,L.instance,Hg,u)}r(J,function(a,b){var c=a.require;a.bindToController&&!H(c)&&D(c)&&S(P[b].instance,X(b,c,Q,P))});r(P,function(a){var b=a.instance;if(B(b.$onChanges))try{b.$onChanges(a.bindingInfo.initialChanges)}catch(d){c(d)}if(B(b.$onInit))try{b.$onInit()}catch(e){c(e)}B(b.$doCheck)&& 76(G.$watch(function(){b.$doCheck()}),b.$doCheck());B(b.$onDestroy)&&G.$on("$destroy",function(){b.$onDestroy()})});n=0;for(u=h.length;n<u;n++)L=h[n],Ca(L,L.isolateScope?y:e,Q,g,L.require&&X(L.directiveName,L.require,Q,P),M);var R=e;s&&(s.template||null===s.templateUrl)&&(R=y);a&&a(R,f.childNodes,void 0,l);for(n=k.length-1;0<=n;n--)L=k[n],Ca(L,L.isolateScope?y:e,Q,g,L.require&&X(L.directiveName,L.require,Q,P),M);r(P,function(a){a=a.instance;B(a.$postLink)&&a.$postLink()})}l=l||{};for(var n=-Number.MAX_VALUE, 77t=l.newScopeDirective,J=l.controllerDirectives,s=l.newIsolateScopeDirective,v=l.templateDirective,L=l.nonTlbTranscludeDirective,G=!1,P=!1,N=l.hasElementTranscludeDirective,y=d.$$element=x(b),u,Q,M,R=e,q,ma=!1,Jb=!1,O,sa=0,C=a.length;sa<C;sa++){u=a[sa];var E=u.$$start,jb=u.$$end;E&&(y=U(b,E,jb));M=void 0;if(n>u.priority)break;if(O=u.scope)u.templateUrl||(D(O)?(ba("new/isolated scope",s||t,u,y),s=u):ba("new/isolated scope",s,u,y)),t=t||u;Q=u.name;if(!ma&&(u.replace&&(u.templateUrl||u.template)||u.transclude&& 78!u.$$tlb)){for(O=sa+1;ma=a[O++];)if(ma.transclude&&!ma.$$tlb||ma.replace&&(ma.templateUrl||ma.template)){Jb=!0;break}ma=!0}!u.templateUrl&&u.controller&&(J=J||T(),ba("'"+Q+"' controller",J[Q],u,y),J[Q]=u);if(O=u.transclude)if(G=!0,u.$$tlb||(ba("transclusion",L,u,y),L=u),"element"===O)N=!0,n=u.priority,M=y,y=d.$$element=x(da.$$createComment(Q,d[Q])),b=y[0],oa(f,Ha.call(M,0),b),R=Z(Jb,M,e,n,g&&g.name,{nonTlbTranscludeDirective:L});else{var ka=T();if(D(O)){M=z.document.createDocumentFragment();var Xa= 79T(),F=T();r(O,function(a,b){var c="?"===a.charAt(0);a=c?a.substring(1):a;Xa[a]=b;ka[b]=null;F[b]=c});r(y.contents(),function(a){var b=Xa[xa(ua(a))];b?(F[b]=!0,ka[b]=ka[b]||z.document.createDocumentFragment(),ka[b].appendChild(a)):M.appendChild(a)});r(F,function(a,b){if(!a)throw $("reqslot",b);});for(var K in ka)ka[K]&&(R=x(ka[K].childNodes),ka[K]=Z(Jb,R,e));M=x(M.childNodes)}else M=x(qc(b)).contents();y.empty();R=Z(Jb,M,e,void 0,void 0,{needsNewScope:u.$$isolateScope||u.$$newScope});R.$$slots=ka}if(u.template)if(P= 80!0,ba("template",v,u,y),v=u,O=B(u.template)?u.template(y,d):u.template,O=Na(O),u.replace){g=u;M=nc.test(O)?td(ja(u.templateNamespace,V(O))):[];b=M[0];if(1!==M.length||1!==b.nodeType)throw $("tplrt",Q,"");oa(f,y,b);C={$attr:{}};O=tc(b,[],C);var Ig=a.splice(sa+1,a.length-(sa+1));(s||t)&&fa(O,s,t);a=a.concat(O).concat(Ig);ga(d,C);C=a.length}else y.html(O);if(u.templateUrl)P=!0,ba("template",v,u,y),v=u,u.replace&&(g=u),p=ha(a.splice(sa,a.length-sa),y,d,f,G&&R,h,k,{controllerDirectives:J,newScopeDirective:t!== 81u&&t,newIsolateScopeDirective:s,templateDirective:v,nonTlbTranscludeDirective:L}),C=a.length;else if(u.compile)try{q=u.compile(y,d,R);var Y=u.$$originalDirective||u;B(q)?m(null,Va(Y,q),E,jb):q&&m(Va(Y,q.pre),Va(Y,q.post),E,jb)}catch(ca){c(ca,Aa(y))}u.terminal&&(p.terminal=!0,n=Math.max(n,u.priority))}p.scope=t&&!0===t.scope;p.transcludeOnThisElement=G;p.templateOnThisElement=P;p.transclude=R;l.hasElementTranscludeDirective=N;return p}function X(a,b,c,d){var e;if(C(b)){var f=b.match(l);b=b.substring(f[0].length); 82var g=f[1]||f[3],f="?"===f[2];"^^"===g?c=c.parent():e=(e=d&&d[b])&&e.instance;if(!e){var h="$"+b+"Controller";e="^^"===g&&c[0]&&9===c[0].nodeType?null:g?c.inheritedData(h):c.data(h)}if(!e&&!f)throw $("ctreq",b,a);}else if(H(b))for(e=[],g=0,f=b.length;g<f;g++)e[g]=X(a,b[g],c,d);else D(b)&&(e={},r(b,function(b,f){e[f]=X(a,b,c,d)}));return e||null}function ea(a,b,c,d,e,f,g){var h=T(),k;for(k in d){var l=d[k],m={$scope:l===g||l.$$isolateScope?e:f,$element:a,$attrs:b,$transclude:c},p=l.controller;"@"=== 83p&&(p=b[l.name]);m=M(p,m,!0,l.controllerAs);h[l.name]=m;a.data("$"+l.name+"Controller",m.instance)}return h}function fa(a,b,c){for(var d=0,e=a.length;d<e;d++)a[d]=bc(a[d],{$$isolateScope:b,$$newScope:c})}function Y(b,c,e,g,h,k,l){if(c===h)return null;var m=null;if(f.hasOwnProperty(c)){h=a.get(c+"Directive");for(var p=0,n=h.length;p<n;p++)if(c=h[p],(A(g)||g>c.priority)&&-1!==c.restrict.indexOf(e)){k&&(c=bc(c,{$$start:k,$$end:l}));if(!c.$$bindings){var I=m=c,t=c.name,u={isolateScope:null,bindToController:null}; 84D(I.scope)&&(!0===I.bindToController?(u.bindToController=d(I.scope,t,!0),u.isolateScope={}):u.isolateScope=d(I.scope,t,!1));D(I.bindToController)&&(u.bindToController=d(I.bindToController,t,!0));if(u.bindToController&&!I.controller)throw $("noctrl",t);m=m.$$bindings=u;D(m.isolateScope)&&(c.$$isolateBindings=m.isolateScope)}b.push(c);m=c}}return m}function ca(b){if(f.hasOwnProperty(b))for(var c=a.get(b+"Directive"),d=0,e=c.length;d<e;d++)if(b=c[d],b.multiElement)return!0;return!1}function ga(a,b){var c= 85b.$attr,d=a.$attr;r(a,function(d,e){"$"!==e.charAt(0)&&(b[e]&&b[e]!==d&&(d=d.length?d+(("style"===e?";":" ")+b[e]):b[e]),a.$set(e,d,!0,c[e]))});r(b,function(b,e){a.hasOwnProperty(e)||"$"===e.charAt(0)||(a[e]=b,"class"!==e&&"style"!==e&&(d[e]=c[e]))})}function ha(a,b,d,f,g,h,k,l){var m=[],p,n,t=b[0],u=a.shift(),J=bc(u,{templateUrl:null,transclude:null,replace:null,$$originalDirective:u}),s=B(u.templateUrl)?u.templateUrl(b,d):u.templateUrl,L=u.templateNamespace;b.empty();e(s).then(function(c){var e, 86I;c=Na(c);if(u.replace){c=nc.test(c)?td(ja(L,V(c))):[];e=c[0];if(1!==c.length||1!==e.nodeType)throw $("tplrt",u.name,s);c={$attr:{}};oa(f,b,e);var v=tc(e,[],c);D(u.scope)&&fa(v,!0);a=v.concat(a);ga(d,c)}else e=t,b.html(c);a.unshift(J);p=aa(a,e,d,g,b,u,h,k,l);r(f,function(a,c){a===e&&(f[c]=b[0])});for(n=Xa(b[0].childNodes,g);m.length;){c=m.shift();I=m.shift();var y=m.shift(),P=m.shift(),v=b[0];if(!c.$$destroyed){if(I!==t){var G=I.className;l.hasElementTranscludeDirective&&u.replace||(v=qc(e));oa(y, 87x(I),v);sa(x(v),G)}I=p.transcludeOnThisElement?ka(c,p.transclude,P):P;p(n,c,v,f,I)}}m=null}).catch(function(a){dc(a)&&c(a)});return function(a,b,c,d,e){a=e;b.$$destroyed||(m?m.push(b,c,d,a):(p.transcludeOnThisElement&&(a=ka(b,p.transclude,e)),p(n,b,c,d,a)))}}function ia(a,b){var c=b.priority-a.priority;return 0!==c?c:a.name!==b.name?a.name<b.name?-1:1:a.index-b.index}function ba(a,b,c,d){function e(a){return a?" (module: "+a+")":""}if(b)throw $("multidir",b.name,e(b.$$moduleName),c.name,e(c.$$moduleName), 88a,Aa(d));}function na(a,c){var d=b(c,!0);d&&a.push({priority:0,compile:function(a){a=a.parent();var b=!!a.length;b&&da.$$addBindingClass(a);return function(a,c){var e=c.parent();b||da.$$addBindingClass(e);da.$$addBindingInfo(e,d.expressions);a.$watch(d,function(a){c[0].nodeValue=a})}}})}function ja(a,b){a=K(a||"html");switch(a){case "svg":case "math":var c=z.document.createElement("div");c.innerHTML="<"+a+">"+b+"</"+a+">";return c.childNodes[0].childNodes;default:return b}}function qa(a,b){if("srcdoc"=== 89b)return u.HTML;if("src"===b||"ngSrc"===b)return-1===["img","video","audio","source","track"].indexOf(a)?u.RESOURCE_URL:u.MEDIA_URL;if("xlinkHref"===b)return"image"===a?u.MEDIA_URL:"a"===a?u.URL:u.RESOURCE_URL;if("form"===a&&"action"===b||"base"===a&&"href"===b||"link"===a&&"href"===b)return u.RESOURCE_URL;if("a"===a&&("href"===b||"ngHref"===b))return u.URL}function ya(a,b){var c=b.toLowerCase();return v[a+"|"+c]||v["*|"+c]}function za(a){return ma(u.valueOf(a),"ng-prop-srcset")}function Ea(a,b,c, 90d){if(m.test(d))throw $("nodomevents");a=ua(a);var e=ya(a,d),f=Ta;"srcset"!==d||"img"!==a&&"source"!==a?e&&(f=u.getTrusted.bind(u,e)):f=za;b.push({priority:100,compile:function(a,b){var e=p(b[c]),g=p(b[c],function(a){return u.valueOf(a)});return{pre:function(a,b){function c(){var g=e(a);b[0][d]=f(g)}c();a.$watch(g,c)}}}})}function Ia(a,c,d,e,f){var g=ua(a),k=qa(g,e),l=h[e]||f,p=b(d,!f,k,l);if(p){if("multiple"===e&&"select"===g)throw $("selmulti",Aa(a));if(m.test(e))throw $("nodomevents");c.push({priority:100, 91compile:function(){return{pre:function(a,c,f){c=f.$$observers||(f.$$observers=T());var g=f[e];g!==d&&(p=g&&b(g,!0,k,l),d=g);p&&(f[e]=p(a),(c[e]||(c[e]=[])).$$inter=!0,(f.$$observers&&f.$$observers[e].$$scope||a).$watch(p,function(a,b){"class"===e&&a!==b?f.$updateClass(a,b):f.$set(e,a)}))}}}})}}function oa(a,b,c){var d=b[0],e=b.length,f=d.parentNode,g,h;if(a)for(g=0,h=a.length;g<h;g++)if(a[g]===d){a[g++]=c;h=g+e-1;for(var k=a.length;g<k;g++,h++)h<k?a[g]=a[h]:delete a[g];a.length-=e-1;a.context===d&& 92(a.context=c);break}f&&f.replaceChild(c,d);a=z.document.createDocumentFragment();for(g=0;g<e;g++)a.appendChild(b[g]);x.hasData(d)&&(x.data(c,x.data(d)),x(d).off("$destroy"));x.cleanData(a.querySelectorAll("*"));for(g=1;g<e;g++)delete b[g];b[0]=c;b.length=1}function Ba(a,b){return S(function(){return a.apply(null,arguments)},a,b)}function Ca(a,b,d,e,f,g){try{a(b,d,e,f,g)}catch(h){c(h,Aa(d))}}function ra(a,b){if(s)throw $("missingattr",a,b);}function Da(a,c,d,e,f){function g(b,c,e){B(d.$onChanges)&& 93!ec(c,e)&&(Ua||(a.$$postDigest(q),Ua=[]),m||(m={},Ua.push(h)),m[b]&&(e=m[b].previousValue),m[b]=new Kb(e,c))}function h(){d.$onChanges(m);m=void 0}var k=[],l={},m;r(e,function(e,h){var m=e.attrName,n=e.optional,I,t,u,s;switch(e.mode){case "@":n||ta.call(c,m)||(ra(m,f.name),d[h]=c[m]=void 0);n=c.$observe(m,function(a){if(C(a)||Ga(a))g(h,a,d[h]),d[h]=a});c.$$observers[m].$$scope=a;I=c[m];C(I)?d[h]=b(I)(a):Ga(I)&&(d[h]=I);l[h]=new Kb(uc,d[h]);k.push(n);break;case "=":if(!ta.call(c,m)){if(n)break;ra(m, 94f.name);c[m]=void 0}if(n&&!c[m])break;t=p(c[m]);s=t.literal?va:ec;u=t.assign||function(){I=d[h]=t(a);throw $("nonassign",c[m],m,f.name);};I=d[h]=t(a);n=function(b){s(b,d[h])||(s(b,I)?u(a,b=d[h]):d[h]=b);return I=b};n.$stateful=!0;n=e.collection?a.$watchCollection(c[m],n):a.$watch(p(c[m],n),null,t.literal);k.push(n);break;case "<":if(!ta.call(c,m)){if(n)break;ra(m,f.name);c[m]=void 0}if(n&&!c[m])break;t=p(c[m]);var v=t.literal,L=d[h]=t(a);l[h]=new Kb(uc,d[h]);n=a[e.collection?"$watchCollection":"$watch"](t, 95function(a,b){if(b===a){if(b===L||v&&va(b,L))return;b=L}g(h,a,b);d[h]=a});k.push(n);break;case "&":n||ta.call(c,m)||ra(m,f.name);t=c.hasOwnProperty(m)?p(c[m]):E;if(t===E&&n)break;d[h]=function(b){return t(a,b)}}});return{initialChanges:l,removeWatches:k.length&&function(){for(var a=0,b=k.length;a<b;++a)k[a]()}}}var Ma=/^\w/,Fa=z.document.createElement("div"),Oa=t,Qa=N,Ja=G,Ua;w.prototype={$normalize:xa,$addClass:function(a){a&&0<a.length&&R.addClass(this.$$element,a)},$removeClass:function(a){a&& 960<a.length&&R.removeClass(this.$$element,a)},$updateClass:function(a,b){var c=ud(a,b);c&&c.length&&R.addClass(this.$$element,c);(c=ud(b,a))&&c.length&&R.removeClass(this.$$element,c)},$set:function(a,b,d,e){var f=nd(this.$$element[0],a),g=vd[a],h=a;f?(this.$$element.prop(a,b),e=f):g&&(this[g]=b,h=g);this[a]=b;e?this.$attr[a]=e:(e=this.$attr[a])||(this.$attr[a]=e=Xc(a,"-"));"img"===ua(this.$$element)&&"srcset"===a&&(this[a]=b=ma(b,"$set('srcset', value)"));!1!==d&&(null===b||A(b)?this.$$element.removeAttr(e): 97Ma.test(e)?f&&!1===b?this.$$element.removeAttr(e):this.$$element.attr(e,b):O(this.$$element[0],e,b));(a=this.$$observers)&&r(a[h],function(a){try{a(b)}catch(d){c(d)}})},$observe:function(a,b){var c=this,d=c.$$observers||(c.$$observers=T()),e=d[a]||(d[a]=[]);e.push(b);L.$evalAsync(function(){e.$$inter||!c.hasOwnProperty(a)||A(c[a])||b(c[a])});return function(){cb(e,b)}}};var Ka=b.startSymbol(),La=b.endSymbol(),Na="{{"===Ka&&"}}"===La?Ta:function(a){return a.replace(/\{\{/g,Ka).replace(/}}/g,La)},Ra= 98/^ng(Attr|Prop|On)([A-Z].*)$/,Sa=/^(.+)Start$/;da.$$addBindingInfo=n?function(a,b){var c=a.data("$binding")||[];H(b)?c=c.concat(b):c.push(b);a.data("$binding",c)}:E;da.$$addBindingClass=n?function(a){sa(a,"ng-binding")}:E;da.$$addScopeInfo=n?function(a,b,c,d){a.data(c?d?"$isolateScopeNoTemplate":"$isolateScope":"$scope",b)}:E;da.$$addScopeClass=n?function(a,b){sa(a,b?"ng-isolate-scope":"ng-scope")}:E;da.$$createComment=function(a,b){var c="";n&&(c=" "+(a||"")+": ",b&&(c+=b+" "));return z.document.createComment(c)}; 99return da}]}function Kb(a,b){this.previousValue=a;this.currentValue=b}function xa(a){return a.replace(rd,"").replace(Jg,function(a,d,c){return c?d.toUpperCase():d})}function ud(a,b){var d="",c=a.split(/\s+/),e=b.split(/\s+/),f=0;a:for(;f<c.length;f++){for(var g=c[f],k=0;k<e.length;k++)if(g===e[k])continue a;d+=(0<d.length?" ":"")+g}return d}function td(a){a=x(a);var b=a.length;if(1>=b)return a;for(;b--;){var d=a[b];(8===d.nodeType||d.nodeType===Pa&&""===d.nodeValue.trim())&&Kg.call(a,b,1)}return a} 100function Gg(a,b){if(b&&C(b))return b;if(C(a)){var d=wd.exec(a);if(d)return d[3]}}function Kf(){var a={};this.has=function(b){return a.hasOwnProperty(b)};this.register=function(b,d){Ja(b,"controller");D(b)?S(a,b):a[b]=d};this.$get=["$injector",function(b){function d(a,b,d,g){if(!a||!D(a.$scope))throw F("$controller")("noscp",g,b);a.$scope[b]=d}return function(c,e,f,g){var k,h,l;f=!0===f;g&&C(g)&&(l=g);if(C(c)){g=c.match(wd);if(!g)throw xd("ctrlfmt",c);h=g[1];l=l||g[3];c=a.hasOwnProperty(h)?a[h]:Le(e.$scope, 101h,!0);if(!c)throw xd("ctrlreg",h);tb(c,h,!0)}if(f)return f=(H(c)?c[c.length-1]:c).prototype,k=Object.create(f||null),l&&d(e,l,k,h||c.name),S(function(){var a=b.invoke(c,k,e,h);a!==k&&(D(a)||B(a))&&(k=a,l&&d(e,l,k,h||c.name));return k},{instance:k,identifier:l});k=b.instantiate(c,e,h);l&&d(e,l,k,h||c.name);return k}}]}function Lf(){this.$get=["$window",function(a){return x(a.document)}]}function Mf(){this.$get=["$document","$rootScope",function(a,b){function d(){e=c.hidden}var c=a[0],e=c&&c.hidden; 102a.on("visibilitychange",d);b.$on("$destroy",function(){a.off("visibilitychange",d)});return function(){return e}}]}function Nf(){this.$get=["$log",function(a){return function(b,d){a.error.apply(a,arguments)}}]}function vc(a){return D(a)?ha(a)?a.toISOString():eb(a):a}function Tf(){this.$get=function(){return function(a){if(!a)return"";var b=[];Qc(a,function(a,c){null===a||A(a)||B(a)||(H(a)?r(a,function(a){b.push(ba(c)+"="+ba(vc(a)))}):b.push(ba(c)+"="+ba(vc(a))))});return b.join("&")}}}function Uf(){this.$get= 103function(){return function(a){function b(a,e,f){H(a)?r(a,function(a,c){b(a,e+"["+(D(a)?c:"")+"]")}):D(a)&&!ha(a)?Qc(a,function(a,c){b(a,e+(f?"":"[")+c+(f?"":"]"))}):(B(a)&&(a=a()),d.push(ba(e)+"="+(null==a?"":ba(vc(a)))))}if(!a)return"";var d=[];b(a,"",!0);return d.join("&")}}}function wc(a,b){if(C(a)){var d=a.replace(Lg,"").trim();if(d){var c=b("Content-Type"),c=c&&0===c.indexOf(yd),e;(e=c)||(e=(e=d.match(Mg))&&Ng[e[0]].test(d));if(e)try{a=Tc(d)}catch(f){if(!c)return a;throw Lb("baddata",a,f);}}}return a} 104function zd(a){var b=T(),d;C(a)?r(a.split("\n"),function(a){d=a.indexOf(":");var e=K(V(a.substr(0,d)));a=V(a.substr(d+1));e&&(b[e]=b[e]?b[e]+", "+a:a)}):D(a)&&r(a,function(a,d){var f=K(d),g=V(a);f&&(b[f]=b[f]?b[f]+", "+g:g)});return b}function Ad(a){var b;return function(d){b||(b=zd(a));return d?(d=b[K(d)],void 0===d&&(d=null),d):b}}function Bd(a,b,d,c){if(B(c))return c(a,b,d);r(c,function(c){a=c(a,b,d)});return a}function Sf(){var a=this.defaults={transformResponse:[wc],transformRequest:[function(a){return D(a)&& 105"[object File]"!==la.call(a)&&"[object Blob]"!==la.call(a)&&"[object FormData]"!==la.call(a)?eb(a):a}],headers:{common:{Accept:"application/json, text/plain, */*"},post:ja(xc),put:ja(xc),patch:ja(xc)},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",paramSerializer:"$httpParamSerializer",jsonpCallbackParam:"callback"},b=!1;this.useApplyAsync=function(a){return w(a)?(b=!!a,this):b};var d=this.interceptors=[],c=this.xsrfTrustedOrigins=[];Object.defineProperty(this,"xsrfWhitelistedOrigins", 106{get:function(){return this.xsrfTrustedOrigins},set:function(a){this.xsrfTrustedOrigins=a}});this.$get=["$browser","$httpBackend","$$cookieReader","$cacheFactory","$rootScope","$q","$injector","$sce",function(e,f,g,k,h,l,m,p){function n(b){function c(a,b){for(var d=0,e=b.length;d<e;){var f=b[d++],g=b[d++];a=a.then(f,g)}b.length=0;return a}function d(a,b){var c,e={};r(a,function(a,d){B(a)?(c=a(b),null!=c&&(e[d]=c)):e[d]=a});return e}function f(a){var b=S({},a);b.data=Bd(a.data,a.headers,a.status,g.transformResponse); 107a=a.status;return 200<=a&&300>a?b:l.reject(b)}if(!D(b))throw F("$http")("badreq",b);if(!C(p.valueOf(b.url)))throw F("$http")("badreq",b.url);var g=S({method:"get",transformRequest:a.transformRequest,transformResponse:a.transformResponse,paramSerializer:a.paramSerializer,jsonpCallbackParam:a.jsonpCallbackParam},b);g.headers=function(b){var c=a.headers,e=S({},b.headers),f,g,h,c=S({},c.common,c[K(b.method)]);a:for(f in c){g=K(f);for(h in e)if(K(h)===g)continue a;e[f]=c[f]}return d(e,ja(b))}(b);g.method= 108vb(g.method);g.paramSerializer=C(g.paramSerializer)?m.get(g.paramSerializer):g.paramSerializer;e.$$incOutstandingRequestCount("$http");var h=[],k=[];b=l.resolve(g);r(v,function(a){(a.request||a.requestError)&&h.unshift(a.request,a.requestError);(a.response||a.responseError)&&k.push(a.response,a.responseError)});b=c(b,h);b=b.then(function(b){var c=b.headers,d=Bd(b.data,Ad(c),void 0,b.transformRequest);A(d)&&r(c,function(a,b){"content-type"===K(b)&&delete c[b]});A(b.withCredentials)&&!A(a.withCredentials)&& 109(b.withCredentials=a.withCredentials);return s(b,d).then(f,f)});b=c(b,k);return b=b.finally(function(){e.$$completeOutstandingRequest(E,"$http")})}function s(c,d){function e(a){if(a){var c={};r(a,function(a,d){c[d]=function(c){function d(){a(c)}b?h.$applyAsync(d):h.$$phase?d():h.$apply(d)}});return c}}function k(a,c,d,e,f){function g(){m(c,a,d,e,f)}R&&(200<=a&&300>a?R.put(O,[a,c,zd(d),e,f]):R.remove(O));b?h.$applyAsync(g):(g(),h.$$phase||h.$apply())}function m(a,b,d,e,f){b=-1<=b?b:0;(200<=b&&300> 110b?L.resolve:L.reject)({data:a,status:b,headers:Ad(d),config:c,statusText:e,xhrStatus:f})}function s(a){m(a.data,a.status,ja(a.headers()),a.statusText,a.xhrStatus)}function v(){var a=n.pendingRequests.indexOf(c);-1!==a&&n.pendingRequests.splice(a,1)}var L=l.defer(),u=L.promise,R,q,ma=c.headers,x="jsonp"===K(c.method),O=c.url;x?O=p.getTrustedResourceUrl(O):C(O)||(O=p.valueOf(O));O=G(O,c.paramSerializer(c.params));x&&(O=t(O,c.jsonpCallbackParam));n.pendingRequests.push(c);u.then(v,v);!c.cache&&!a.cache|| 111!1===c.cache||"GET"!==c.method&&"JSONP"!==c.method||(R=D(c.cache)?c.cache:D(a.cache)?a.cache:N);R&&(q=R.get(O),w(q)?q&&B(q.then)?q.then(s,s):H(q)?m(q[1],q[0],ja(q[2]),q[3],q[4]):m(q,200,{},"OK","complete"):R.put(O,u));A(q)&&((q=kc(c.url)?g()[c.xsrfCookieName||a.xsrfCookieName]:void 0)&&(ma[c.xsrfHeaderName||a.xsrfHeaderName]=q),f(c.method,O,d,k,ma,c.timeout,c.withCredentials,c.responseType,e(c.eventHandlers),e(c.uploadEventHandlers)));return u}function G(a,b){0<b.length&&(a+=(-1===a.indexOf("?")? 112"?":"&")+b);return a}function t(a,b){var c=a.split("?");if(2<c.length)throw Lb("badjsonp",a);c=hc(c[1]);r(c,function(c,d){if("JSON_CALLBACK"===c)throw Lb("badjsonp",a);if(d===b)throw Lb("badjsonp",b,a);});return a+=(-1===a.indexOf("?")?"?":"&")+b+"=JSON_CALLBACK"}var N=k("$http");a.paramSerializer=C(a.paramSerializer)?m.get(a.paramSerializer):a.paramSerializer;var v=[];r(d,function(a){v.unshift(C(a)?m.get(a):m.invoke(a))});var kc=Og(c);n.pendingRequests=[];(function(a){r(arguments,function(a){n[a]= 113function(b,c){return n(S({},c||{},{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){r(arguments,function(a){n[a]=function(b,c,d){return n(S({},d||{},{method:a,url:b,data:c}))}})})("post","put","patch");n.defaults=a;return n}]}function Wf(){this.$get=function(){return function(){return new z.XMLHttpRequest}}}function Vf(){this.$get=["$browser","$jsonpCallbacks","$document","$xhrFactory",function(a,b,d,c){return Pg(a,c,a.defer,b,d[0])}]}function Pg(a,b,d,c,e){function f(a,b,d){a=a.replace("JSON_CALLBACK", 114b);var f=e.createElement("script"),m=null;f.type="text/javascript";f.src=a;f.async=!0;m=function(a){f.removeEventListener("load",m);f.removeEventListener("error",m);e.body.removeChild(f);f=null;var g=-1,s="unknown";a&&("load"!==a.type||c.wasCalled(b)||(a={type:"error"}),s=a.type,g="error"===a.type?404:200);d&&d(g,s)};f.addEventListener("load",m);f.addEventListener("error",m);e.body.appendChild(f);return m}return function(e,k,h,l,m,p,n,s,G,t){function N(a){J="timeout"===a;pa&&pa();y&&y.abort()}function v(a, 115b,c,e,f,g){w(P)&&d.cancel(P);pa=y=null;a(b,c,e,f,g)}k=k||a.url();if("jsonp"===K(e))var q=c.createCallback(k),pa=f(k,q,function(a,b){var d=200===a&&c.getResponse(q);v(l,a,d,"",b,"complete");c.removeCallback(q)});else{var y=b(e,k),J=!1;y.open(e,k,!0);r(m,function(a,b){w(a)&&y.setRequestHeader(b,a)});y.onload=function(){var a=y.statusText||"",b="response"in y?y.response:y.responseText,c=1223===y.status?204:y.status;0===c&&(c=b?200:"file"===ga(k).protocol?404:0);v(l,c,b,y.getAllResponseHeaders(),a,"complete")}; 116y.onerror=function(){v(l,-1,null,null,"","error")};y.ontimeout=function(){v(l,-1,null,null,"","timeout")};y.onabort=function(){v(l,-1,null,null,"",J?"timeout":"abort")};r(G,function(a,b){y.addEventListener(b,a)});r(t,function(a,b){y.upload.addEventListener(b,a)});n&&(y.withCredentials=!0);if(s)try{y.responseType=s}catch(I){if("json"!==s)throw I;}y.send(A(h)?null:h)}if(0<p)var P=d(function(){N("timeout")},p);else p&&B(p.then)&&p.then(function(){N(w(p.$$timeoutId)?"timeout":"abort")})}}function Pf(){var a= 117"{{",b="}}";this.startSymbol=function(b){return b?(a=b,this):a};this.endSymbol=function(a){return a?(b=a,this):b};this.$get=["$parse","$exceptionHandler","$sce",function(d,c,e){function f(a){return"\\\\\\"+a}function g(c){return c.replace(p,a).replace(n,b)}function k(a,b,c,d){var e=a.$watch(function(a){e();return d(a)},b,c);return e}function h(f,h,n,p){function v(a){try{return a=n&&!r?e.getTrusted(n,a):e.valueOf(a),p&&!w(a)?a:jc(a)}catch(b){c(Ma.interr(f,b))}}var r=n===e.URL||n===e.MEDIA_URL;if(!f.length|| 118-1===f.indexOf(a)){if(h)return;h=g(f);r&&(h=e.getTrusted(n,h));h=ia(h);h.exp=f;h.expressions=[];h.$$watchDelegate=k;return h}p=!!p;for(var q,y,J=0,I=[],P,Q=f.length,M=[],L=[],u;J<Q;)if(-1!==(q=f.indexOf(a,J))&&-1!==(y=f.indexOf(b,q+l)))J!==q&&M.push(g(f.substring(J,q))),J=f.substring(q+l,y),I.push(J),J=y+m,L.push(M.length),M.push("");else{J!==Q&&M.push(g(f.substring(J)));break}u=1===M.length&&1===L.length;var R=r&&u?void 0:v;P=I.map(function(a){return d(a,R)});if(!h||I.length){var x=function(a){for(var b= 1190,c=I.length;b<c;b++){if(p&&A(a[b]))return;M[L[b]]=a[b]}if(r)return e.getTrusted(n,u?M[0]:M.join(""));n&&1<M.length&&Ma.throwNoconcat(f);return M.join("")};return S(function(a){var b=0,d=I.length,e=Array(d);try{for(;b<d;b++)e[b]=P[b](a);return x(e)}catch(g){c(Ma.interr(f,g))}},{exp:f,expressions:I,$$watchDelegate:function(a,b){var c;return a.$watchGroup(P,function(d,e){var f=x(d);b.call(this,f,d!==e?c:f,a);c=f})}})}}var l=a.length,m=b.length,p=new RegExp(a.replace(/./g,f),"g"),n=new RegExp(b.replace(/./g, 120f),"g");h.startSymbol=function(){return a};h.endSymbol=function(){return b};return h}]}function Qf(){this.$get=["$$intervalFactory","$window",function(a,b){var d={},c=function(a){b.clearInterval(a);delete d[a]},e=a(function(a,c,e){a=b.setInterval(a,c);d[a]=e;return a},c);e.cancel=function(a){if(!a)return!1;if(!a.hasOwnProperty("$$intervalId"))throw Qg("badprom");if(!d.hasOwnProperty(a.$$intervalId))return!1;a=a.$$intervalId;var b=d[a],e=b.promise;e.$$state&&(e.$$state.pur=!0);b.reject("canceled"); 121c(a);return!0};return e}]}function Rf(){this.$get=["$browser","$q","$$q","$rootScope",function(a,b,d,c){return function(e,f){return function(g,k,h,l){function m(){p?g.apply(null,n):g(s)}var p=4<arguments.length,n=p?Ha.call(arguments,4):[],s=0,G=w(l)&&!l,t=(G?d:b).defer(),r=t.promise;h=w(h)?h:0;r.$$intervalId=e(function(){G?a.defer(m):c.$evalAsync(m);t.notify(s++);0<h&&s>=h&&(t.resolve(s),f(r.$$intervalId));G||c.$apply()},k,t,G);return r}}}]}function Cd(a,b){var d=ga(a);b.$$protocol=d.protocol;b.$$host= 122d.hostname;b.$$port=fa(d.port)||Rg[d.protocol]||null}function Dd(a,b,d){if(Sg.test(a))throw kb("badpath",a);var c="/"!==a.charAt(0);c&&(a="/"+a);a=ga(a);for(var c=(c&&"/"===a.pathname.charAt(0)?a.pathname.substring(1):a.pathname).split("/"),e=c.length;e--;)c[e]=decodeURIComponent(c[e]),d&&(c[e]=c[e].replace(/\//g,"%2F"));d=c.join("/");b.$$path=d;b.$$search=hc(a.search);b.$$hash=decodeURIComponent(a.hash);b.$$path&&"/"!==b.$$path.charAt(0)&&(b.$$path="/"+b.$$path)}function yc(a,b){return a.slice(0, 123b.length)===b}function ya(a,b){if(yc(b,a))return b.substr(a.length)}function Da(a){var b=a.indexOf("#");return-1===b?a:a.substr(0,b)}function zc(a,b,d){this.$$html5=!0;d=d||"";Cd(a,this);this.$$parse=function(a){var d=ya(b,a);if(!C(d))throw kb("ipthprfx",a,b);Dd(d,this,!0);this.$$path||(this.$$path="/");this.$$compose()};this.$$normalizeUrl=function(a){return b+a.substr(1)};this.$$parseLinkUrl=function(c,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;w(f=ya(a,c))?(g=f,g=d&&w(f=ya(d,f))? 124b+(ya("/",f)||f):a+g):w(f=ya(b,c))?g=b+f:b===c+"/"&&(g=b);g&&this.$$parse(g);return!!g}}function Ac(a,b,d){Cd(a,this);this.$$parse=function(c){var e=ya(a,c)||ya(b,c),f;A(e)||"#"!==e.charAt(0)?this.$$html5?f=e:(f="",A(e)&&(a=c,this.replace())):(f=ya(d,e),A(f)&&(f=e));Dd(f,this,!1);c=this.$$path;var e=a,g=/^\/[A-Z]:(\/.*)/;yc(f,e)&&(f=f.replace(e,""));g.exec(f)||(c=(f=g.exec(c))?f[1]:c);this.$$path=c;this.$$compose()};this.$$normalizeUrl=function(b){return a+(b?d+b:"")};this.$$parseLinkUrl=function(b, 125d){return Da(a)===Da(b)?(this.$$parse(b),!0):!1}}function Ed(a,b,d){this.$$html5=!0;Ac.apply(this,arguments);this.$$parseLinkUrl=function(c,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;a===Da(c)?f=c:(g=ya(b,c))?f=a+d+g:b===c+"/"&&(f=b);f&&this.$$parse(f);return!!f};this.$$normalizeUrl=function(b){return a+d+b}}function Mb(a){return function(){return this[a]}}function Fd(a,b){return function(d){if(A(d))return this[a];this[a]=b(d);this.$$compose();return this}}function Yf(){var a="!", 126b={enabled:!1,requireBase:!0,rewriteLinks:!0};this.hashPrefix=function(b){return w(b)?(a=b,this):a};this.html5Mode=function(a){if(Ga(a))return b.enabled=a,this;if(D(a)){Ga(a.enabled)&&(b.enabled=a.enabled);Ga(a.requireBase)&&(b.requireBase=a.requireBase);if(Ga(a.rewriteLinks)||C(a.rewriteLinks))b.rewriteLinks=a.rewriteLinks;return this}return b};this.$get=["$rootScope","$browser","$sniffer","$rootElement","$window",function(d,c,e,f,g){function k(a,b){return a===b||ga(a).href===ga(b).href}function h(a, 127b,d){var e=m.url(),f=m.$$state;try{c.url(a,b,d),m.$$state=c.state()}catch(g){throw m.url(e),m.$$state=f,g;}}function l(a,b){d.$broadcast("$locationChangeSuccess",m.absUrl(),a,m.$$state,b)}var m,p;p=c.baseHref();var n=c.url(),s;if(b.enabled){if(!p&&b.requireBase)throw kb("nobase");s=n.substring(0,n.indexOf("/",n.indexOf("//")+2))+(p||"/");p=e.history?zc:Ed}else s=Da(n),p=Ac;var r=s.substr(0,Da(s).lastIndexOf("/")+1);m=new p(s,r,"#"+a);m.$$parseLinkUrl(n,n);m.$$state=c.state();var t=/^\s*(javascript|mailto):/i; 128f.on("click",function(a){var e=b.rewriteLinks;if(e&&!a.ctrlKey&&!a.metaKey&&!a.shiftKey&&2!==a.which&&2!==a.button){for(var g=x(a.target);"a"!==ua(g[0]);)if(g[0]===f[0]||!(g=g.parent())[0])return;if(!C(e)||!A(g.attr(e))){var e=g.prop("href"),h=g.attr("href")||g.attr("xlink:href");D(e)&&"[object SVGAnimatedString]"===e.toString()&&(e=ga(e.animVal).href);t.test(e)||!e||g.attr("target")||a.isDefaultPrevented()||!m.$$parseLinkUrl(e,h)||(a.preventDefault(),m.absUrl()!==c.url()&&d.$apply())}}});m.absUrl()!== 129n&&c.url(m.absUrl(),!0);var N=!0;c.onUrlChange(function(a,b){yc(a,r)?(d.$evalAsync(function(){var c=m.absUrl(),e=m.$$state,f;m.$$parse(a);m.$$state=b;f=d.$broadcast("$locationChangeStart",a,c,b,e).defaultPrevented;m.absUrl()===a&&(f?(m.$$parse(c),m.$$state=e,h(c,!1,e)):(N=!1,l(c,e)))}),d.$$phase||d.$digest()):g.location.href=a});d.$watch(function(){if(N||m.$$urlUpdatedByLocation){m.$$urlUpdatedByLocation=!1;var a=c.url(),b=m.absUrl(),f=c.state(),g=m.$$replace,n=!k(a,b)||m.$$html5&&e.history&&f!== 130m.$$state;if(N||n)N=!1,d.$evalAsync(function(){var b=m.absUrl(),c=d.$broadcast("$locationChangeStart",b,a,m.$$state,f).defaultPrevented;m.absUrl()===b&&(c?(m.$$parse(a),m.$$state=f):(n&&h(b,g,f===m.$$state?null:m.$$state),l(a,f)))})}m.$$replace=!1});return m}]}function Zf(){var a=!0,b=this;this.debugEnabled=function(b){return w(b)?(a=b,this):a};this.$get=["$window",function(d){function c(a){dc(a)&&(a.stack&&f?a=a.message&&-1===a.stack.indexOf(a.message)?"Error: "+a.message+"\n"+a.stack:a.stack:a.sourceURL&& 131(a=a.message+"\n"+a.sourceURL+":"+a.line));return a}function e(a){var b=d.console||{},e=b[a]||b.log||E;return function(){var a=[];r(arguments,function(b){a.push(c(b))});return Function.prototype.apply.call(e,b,a)}}var f=wa||/\bEdge\//.test(d.navigator&&d.navigator.userAgent);return{log:e("log"),info:e("info"),warn:e("warn"),error:e("error"),debug:function(){var c=e("debug");return function(){a&&c.apply(b,arguments)}}()}}]}function Tg(a){return a+""}function Ug(a,b){return"undefined"!==typeof a?a: 132b}function Gd(a,b){return"undefined"===typeof a?b:"undefined"===typeof b?a:a+b}function Vg(a,b){switch(a.type){case q.MemberExpression:if(a.computed)return!1;break;case q.UnaryExpression:return 1;case q.BinaryExpression:return"+"!==a.operator?1:!1;case q.CallExpression:return!1}return void 0===b?Hd:b}function Z(a,b,d){var c,e,f=a.isPure=Vg(a,d);switch(a.type){case q.Program:c=!0;r(a.body,function(a){Z(a.expression,b,f);c=c&&a.expression.constant});a.constant=c;break;case q.Literal:a.constant=!0;a.toWatch= 133[];break;case q.UnaryExpression:Z(a.argument,b,f);a.constant=a.argument.constant;a.toWatch=a.argument.toWatch;break;case q.BinaryExpression:Z(a.left,b,f);Z(a.right,b,f);a.constant=a.left.constant&&a.right.constant;a.toWatch=a.left.toWatch.concat(a.right.toWatch);break;case q.LogicalExpression:Z(a.left,b,f);Z(a.right,b,f);a.constant=a.left.constant&&a.right.constant;a.toWatch=a.constant?[]:[a];break;case q.ConditionalExpression:Z(a.test,b,f);Z(a.alternate,b,f);Z(a.consequent,b,f);a.constant=a.test.constant&& 134a.alternate.constant&&a.consequent.constant;a.toWatch=a.constant?[]:[a];break;case q.Identifier:a.constant=!1;a.toWatch=[a];break;case q.MemberExpression:Z(a.object,b,f);a.computed&&Z(a.property,b,f);a.constant=a.object.constant&&(!a.computed||a.property.constant);a.toWatch=a.constant?[]:[a];break;case q.CallExpression:c=d=a.filter?!b(a.callee.name).$stateful:!1;e=[];r(a.arguments,function(a){Z(a,b,f);c=c&&a.constant;e.push.apply(e,a.toWatch)});a.constant=c;a.toWatch=d?e:[a];break;case q.AssignmentExpression:Z(a.left, 135b,f);Z(a.right,b,f);a.constant=a.left.constant&&a.right.constant;a.toWatch=[a];break;case q.ArrayExpression:c=!0;e=[];r(a.elements,function(a){Z(a,b,f);c=c&&a.constant;e.push.apply(e,a.toWatch)});a.constant=c;a.toWatch=e;break;case q.ObjectExpression:c=!0;e=[];r(a.properties,function(a){Z(a.value,b,f);c=c&&a.value.constant;e.push.apply(e,a.value.toWatch);a.computed&&(Z(a.key,b,!1),c=c&&a.key.constant,e.push.apply(e,a.key.toWatch))});a.constant=c;a.toWatch=e;break;case q.ThisExpression:a.constant= 136!1;a.toWatch=[];break;case q.LocalsExpression:a.constant=!1,a.toWatch=[]}}function Id(a){if(1===a.length){a=a[0].expression;var b=a.toWatch;return 1!==b.length?b:b[0]!==a?b:void 0}}function Jd(a){return a.type===q.Identifier||a.type===q.MemberExpression}function Kd(a){if(1===a.body.length&&Jd(a.body[0].expression))return{type:q.AssignmentExpression,left:a.body[0].expression,right:{type:q.NGValueParameter},operator:"="}}function Ld(a){this.$filter=a}function Md(a){this.$filter=a}function Nb(a,b,d){this.ast= 137new q(a,d);this.astCompiler=d.csp?new Md(b):new Ld(b)}function Bc(a){return B(a.valueOf)?a.valueOf():Wg.call(a)}function $f(){var a=T(),b={"true":!0,"false":!1,"null":null,undefined:void 0},d,c;this.addLiteral=function(a,c){b[a]=c};this.setIdentifierFns=function(a,b){d=a;c=b;return this};this.$get=["$filter",function(e){function f(b,c){var d,f;switch(typeof b){case "string":return f=b=b.trim(),d=a[f],d||(d=new Ob(G),d=(new Nb(d,e,G)).parse(b),a[f]=p(d)),s(d,c);case "function":return s(b,c);default:return s(E, 138c)}}function g(a,b,c){return null==a||null==b?a===b:"object"!==typeof a||(a=Bc(a),"object"!==typeof a||c)?a===b||a!==a&&b!==b:!1}function k(a,b,c,d,e){var f=d.inputs,h;if(1===f.length){var k=g,f=f[0];return a.$watch(function(a){var b=f(a);g(b,k,f.isPure)||(h=d(a,void 0,void 0,[b]),k=b&&Bc(b));return h},b,c,e)}for(var l=[],m=[],n=0,p=f.length;n<p;n++)l[n]=g,m[n]=null;return a.$watch(function(a){for(var b=!1,c=0,e=f.length;c<e;c++){var k=f[c](a);if(b||(b=!g(k,l[c],f[c].isPure)))m[c]=k,l[c]=k&&Bc(k)}b&& 139(h=d(a,void 0,void 0,m));return h},b,c,e)}function h(a,b,c,d,e){function f(){h(m)&&k()}function g(a,b,c,d){m=u&&d?d[0]:n(a,b,c,d);h(m)&&a.$$postDigest(f);return s(m)}var h=d.literal?l:w,k,m,n=d.$$intercepted||d,s=d.$$interceptor||Ta,u=d.inputs&&!n.inputs;g.literal=d.literal;g.constant=d.constant;g.inputs=d.inputs;p(g);return k=a.$watch(g,b,c,e)}function l(a){var b=!0;r(a,function(a){w(a)||(b=!1)});return b}function m(a,b,c,d){var e=a.$watch(function(a){e();return d(a)},b,c);return e}function p(a){a.constant? 140a.$$watchDelegate=m:a.oneTime?a.$$watchDelegate=h:a.inputs&&(a.$$watchDelegate=k);return a}function n(a,b){function c(d){return b(a(d))}c.$stateful=a.$stateful||b.$stateful;c.$$pure=a.$$pure&&b.$$pure;return c}function s(a,b){if(!b)return a;a.$$interceptor&&(b=n(a.$$interceptor,b),a=a.$$intercepted);var c=!1,d=function(d,e,f,g){d=c&&g?g[0]:a(d,e,f,g);return b(d)};d.$$intercepted=a;d.$$interceptor=b;d.literal=a.literal;d.oneTime=a.oneTime;d.constant=a.constant;b.$stateful||(c=!a.inputs,d.inputs=a.inputs? 141a.inputs:[a],b.$$pure||(d.inputs=d.inputs.map(function(a){return a.isPure===Hd?function(b){return a(b)}:a})));return p(d)}var G={csp:Ba().noUnsafeEval,literals:Ia(b),isIdentifierStart:B(d)&&d,isIdentifierContinue:B(c)&&c};f.$$getAst=function(a){var b=new Ob(G);return(new Nb(b,e,G)).getAst(a).ast};return f}]}function bg(){var a=!0;this.$get=["$rootScope","$exceptionHandler",function(b,d){return Nd(function(a){b.$evalAsync(a)},d,a)}];this.errorOnUnhandledRejections=function(b){return w(b)?(a=b,this): 142a}}function cg(){var a=!0;this.$get=["$browser","$exceptionHandler",function(b,d){return Nd(function(a){b.defer(a)},d,a)}];this.errorOnUnhandledRejections=function(b){return w(b)?(a=b,this):a}}function Nd(a,b,d){function c(){return new e}function e(){var a=this.promise=new f;this.resolve=function(b){h(a,b)};this.reject=function(b){m(a,b)};this.notify=function(b){n(a,b)}}function f(){this.$$state={status:0}}function g(){for(;!w&&x.length;){var a=x.shift();if(!a.pur){a.pur=!0;var c=a.value,c="Possibly unhandled rejection: "+ 143("function"===typeof c?c.toString().replace(/ \{[\s\S]*$/,""):A(c)?"undefined":"string"!==typeof c?Ne(c,void 0):c);dc(a.value)?b(a.value,c):b(c)}}}function k(c){!d||c.pending||2!==c.status||c.pur||(0===w&&0===x.length&&a(g),x.push(c));!c.processScheduled&&c.pending&&(c.processScheduled=!0,++w,a(function(){var e,f,k;k=c.pending;c.processScheduled=!1;c.pending=void 0;try{for(var l=0,n=k.length;l<n;++l){c.pur=!0;f=k[l][0];e=k[l][c.status];try{B(e)?h(f,e(c.value)):1===c.status?h(f,c.value):m(f,c.value)}catch(p){m(f, 144p),p&&!0===p.$$passToExceptionHandler&&b(p)}}}finally{--w,d&&0===w&&a(g)}}))}function h(a,b){a.$$state.status||(b===a?p(a,v("qcycle",b)):l(a,b))}function l(a,b){function c(b){g||(g=!0,l(a,b))}function d(b){g||(g=!0,p(a,b))}function e(b){n(a,b)}var f,g=!1;try{if(D(b)||B(b))f=b.then;B(f)?(a.$$state.status=-1,f.call(b,c,d,e)):(a.$$state.value=b,a.$$state.status=1,k(a.$$state))}catch(h){d(h)}}function m(a,b){a.$$state.status||p(a,b)}function p(a,b){a.$$state.value=b;a.$$state.status=2;k(a.$$state)}function n(c, 145d){var e=c.$$state.pending;0>=c.$$state.status&&e&&e.length&&a(function(){for(var a,c,f=0,g=e.length;f<g;f++){c=e[f][0];a=e[f][3];try{n(c,B(a)?a(d):d)}catch(h){b(h)}}})}function s(a){var b=new f;m(b,a);return b}function G(a,b,c){var d=null;try{B(c)&&(d=c())}catch(e){return s(e)}return d&&B(d.then)?d.then(function(){return b(a)},s):b(a)}function t(a,b,c,d){var e=new f;h(e,a);return e.then(b,c,d)}function q(a){if(!B(a))throw v("norslvr",a);var b=new f;a(function(a){h(b,a)},function(a){m(b,a)});return b} 146var v=F("$q",TypeError),w=0,x=[];S(f.prototype,{then:function(a,b,c){if(A(a)&&A(b)&&A(c))return this;var d=new f;this.$$state.pending=this.$$state.pending||[];this.$$state.pending.push([d,a,b,c]);0<this.$$state.status&&k(this.$$state);return d},"catch":function(a){return this.then(null,a)},"finally":function(a,b){return this.then(function(b){return G(b,y,a)},function(b){return G(b,s,a)},b)}});var y=t;q.prototype=f.prototype;q.defer=c;q.reject=s;q.when=t;q.resolve=y;q.all=function(a){var b=new f,c= 1470,d=H(a)?[]:{};r(a,function(a,e){c++;t(a).then(function(a){d[e]=a;--c||h(b,d)},function(a){m(b,a)})});0===c&&h(b,d);return b};q.race=function(a){var b=c();r(a,function(a){t(a).then(b.resolve,b.reject)});return b.promise};return q}function mg(){this.$get=["$window","$timeout",function(a,b){var d=a.requestAnimationFrame||a.webkitRequestAnimationFrame,c=a.cancelAnimationFrame||a.webkitCancelAnimationFrame||a.webkitCancelRequestAnimationFrame,e=!!d,f=e?function(a){var b=d(a);return function(){c(b)}}: 148function(a){var c=b(a,16.66,!1);return function(){b.cancel(c)}};f.supported=e;return f}]}function ag(){function a(a){function b(){this.$$watchers=this.$$nextSibling=this.$$childHead=this.$$childTail=null;this.$$listeners={};this.$$listenerCount={};this.$$watchersCount=0;this.$id=++qb;this.$$ChildScope=null;this.$$suspended=!1}b.prototype=a;return b}var b=10,d=F("$rootScope"),c=null,e=null;this.digestTtl=function(a){arguments.length&&(b=a);return b};this.$get=["$exceptionHandler","$parse","$browser", 149function(f,g,k){function h(a){a.currentScope.$$destroyed=!0}function l(a){9===wa&&(a.$$childHead&&l(a.$$childHead),a.$$nextSibling&&l(a.$$nextSibling));a.$parent=a.$$nextSibling=a.$$prevSibling=a.$$childHead=a.$$childTail=a.$root=a.$$watchers=null}function m(){this.$id=++qb;this.$$phase=this.$parent=this.$$watchers=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null;this.$root=this;this.$$suspended=this.$$destroyed=!1;this.$$listeners={};this.$$listenerCount={};this.$$watchersCount= 1500;this.$$isolateBindings=null}function p(a){if(v.$$phase)throw d("inprog",v.$$phase);v.$$phase=a}function n(a,b){do a.$$watchersCount+=b;while(a=a.$parent)}function s(a,b,c){do a.$$listenerCount[c]-=b,0===a.$$listenerCount[c]&&delete a.$$listenerCount[c];while(a=a.$parent)}function G(){}function t(){for(;y.length;)try{y.shift()()}catch(a){f(a)}e=null}function q(){null===e&&(e=k.defer(function(){v.$apply(t)},null,"$applyAsync"))}m.prototype={constructor:m,$new:function(b,c){var d;c=c||this;b?(d=new m, 151d.$root=this.$root):(this.$$ChildScope||(this.$$ChildScope=a(this)),d=new this.$$ChildScope);d.$parent=c;d.$$prevSibling=c.$$childTail;c.$$childHead?(c.$$childTail.$$nextSibling=d,c.$$childTail=d):c.$$childHead=c.$$childTail=d;(b||c!==this)&&d.$on("$destroy",h);return d},$watch:function(a,b,d,e){var f=g(a);b=B(b)?b:E;if(f.$$watchDelegate)return f.$$watchDelegate(this,b,d,f,a);var h=this,k=h.$$watchers,l={fn:b,last:G,get:f,exp:e||a,eq:!!d};c=null;k||(k=h.$$watchers=[],k.$$digestWatchIndex=-1);k.unshift(l); 152k.$$digestWatchIndex++;n(this,1);return function(){var a=cb(k,l);0<=a&&(n(h,-1),a<k.$$digestWatchIndex&&k.$$digestWatchIndex--);c=null}},$watchGroup:function(a,b){function c(){h=!1;try{k?(k=!1,b(e,e,g)):b(e,d,g)}finally{for(var f=0;f<a.length;f++)d[f]=e[f]}}var d=Array(a.length),e=Array(a.length),f=[],g=this,h=!1,k=!0;if(!a.length){var l=!0;g.$evalAsync(function(){l&&b(e,e,g)});return function(){l=!1}}if(1===a.length)return this.$watch(a[0],function(a,c,f){e[0]=a;d[0]=c;b(e,a===c?e:d,f)});r(a,function(a, 153b){var d=g.$watch(a,function(a){e[b]=a;h||(h=!0,g.$evalAsync(c))});f.push(d)});return function(){for(;f.length;)f.shift()()}},$watchCollection:function(a,b){function c(a){e=a;var b,d,g,h;if(!A(e)){if(D(e))if(za(e))for(f!==n&&(f=n,t=f.length=0,l++),a=e.length,t!==a&&(l++,f.length=t=a),b=0;b<a;b++)h=f[b],g=e[b],d=h!==h&&g!==g,d||h===g||(l++,f[b]=g);else{f!==p&&(f=p={},t=0,l++);a=0;for(b in e)ta.call(e,b)&&(a++,g=e[b],h=f[b],b in f?(d=h!==h&&g!==g,d||h===g||(l++,f[b]=g)):(t++,f[b]=g,l++));if(t>a)for(b in l++, 154f)ta.call(e,b)||(t--,delete f[b])}else f!==e&&(f=e,l++);return l}}c.$$pure=g(a).literal;c.$stateful=!c.$$pure;var d=this,e,f,h,k=1<b.length,l=0,m=g(a,c),n=[],p={},s=!0,t=0;return this.$watch(m,function(){s?(s=!1,b(e,e,d)):b(e,h,d);if(k)if(D(e))if(za(e)){h=Array(e.length);for(var a=0;a<e.length;a++)h[a]=e[a]}else for(a in h={},e)ta.call(e,a)&&(h[a]=e[a]);else h=e})},$digest:function(){var a,g,h,l,m,n,s,r=b,q,y=w.length?v:this,N=[],A,z;p("$digest");k.$$checkUrlChange();this===v&&null!==e&&(k.defer.cancel(e), 155t());c=null;do{s=!1;q=y;for(n=0;n<w.length;n++){try{z=w[n],l=z.fn,l(z.scope,z.locals)}catch(C){f(C)}c=null}w.length=0;a:do{if(n=!q.$$suspended&&q.$$watchers)for(n.$$digestWatchIndex=n.length;n.$$digestWatchIndex--;)try{if(a=n[n.$$digestWatchIndex])if(m=a.get,(g=m(q))!==(h=a.last)&&!(a.eq?va(g,h):Y(g)&&Y(h)))s=!0,c=a,a.last=a.eq?Ia(g,null):g,l=a.fn,l(g,h===G?g:h,q),5>r&&(A=4-r,N[A]||(N[A]=[]),N[A].push({msg:B(a.exp)?"fn: "+(a.exp.name||a.exp.toString()):a.exp,newVal:g,oldVal:h}));else if(a===c){s= 156!1;break a}}catch(E){f(E)}if(!(n=!q.$$suspended&&q.$$watchersCount&&q.$$childHead||q!==y&&q.$$nextSibling))for(;q!==y&&!(n=q.$$nextSibling);)q=q.$parent}while(q=n);if((s||w.length)&&!r--)throw v.$$phase=null,d("infdig",b,N);}while(s||w.length);for(v.$$phase=null;J<x.length;)try{x[J++]()}catch(D){f(D)}x.length=J=0;k.$$checkUrlChange()},$suspend:function(){this.$$suspended=!0},$isSuspended:function(){return this.$$suspended},$resume:function(){this.$$suspended=!1},$destroy:function(){if(!this.$$destroyed){var a= 157this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;this===v&&k.$$applicationDestroyed();n(this,-this.$$watchersCount);for(var b in this.$$listenerCount)s(this,this.$$listenerCount[b],b);a&&a.$$childHead===this&&(a.$$childHead=this.$$nextSibling);a&&a.$$childTail===this&&(a.$$childTail=this.$$prevSibling);this.$$prevSibling&&(this.$$prevSibling.$$nextSibling=this.$$nextSibling);this.$$nextSibling&&(this.$$nextSibling.$$prevSibling=this.$$prevSibling);this.$destroy=this.$digest=this.$apply= 158this.$evalAsync=this.$applyAsync=E;this.$on=this.$watch=this.$watchGroup=function(){return E};this.$$listeners={};this.$$nextSibling=null;l(this)}},$eval:function(a,b){return g(a)(this,b)},$evalAsync:function(a,b){v.$$phase||w.length||k.defer(function(){w.length&&v.$digest()},null,"$evalAsync");w.push({scope:this,fn:g(a),locals:b})},$$postDigest:function(a){x.push(a)},$apply:function(a){try{p("$apply");try{return this.$eval(a)}finally{v.$$phase=null}}catch(b){f(b)}finally{try{v.$digest()}catch(c){throw f(c), 159c;}}},$applyAsync:function(a){function b(){c.$eval(a)}var c=this;a&&y.push(b);a=g(a);q()},$on:function(a,b){var c=this.$$listeners[a];c||(this.$$listeners[a]=c=[]);c.push(b);var d=this;do d.$$listenerCount[a]||(d.$$listenerCount[a]=0),d.$$listenerCount[a]++;while(d=d.$parent);var e=this;return function(){var d=c.indexOf(b);-1!==d&&(delete c[d],s(e,1,a))}},$emit:function(a,b){var c=[],d,e=this,g=!1,h={name:a,targetScope:e,stopPropagation:function(){g=!0},preventDefault:function(){h.defaultPrevented= 160!0},defaultPrevented:!1},k=db([h],arguments,1),l,m;do{d=e.$$listeners[a]||c;h.currentScope=e;l=0;for(m=d.length;l<m;l++)if(d[l])try{d[l].apply(null,k)}catch(n){f(n)}else d.splice(l,1),l--,m--;if(g)break;e=e.$parent}while(e);h.currentScope=null;return h},$broadcast:function(a,b){var c=this,d=this,e={name:a,targetScope:this,preventDefault:function(){e.defaultPrevented=!0},defaultPrevented:!1};if(!this.$$listenerCount[a])return e;for(var g=db([e],arguments,1),h,k;c=d;){e.currentScope=c;d=c.$$listeners[a]|| 161[];h=0;for(k=d.length;h<k;h++)if(d[h])try{d[h].apply(null,g)}catch(l){f(l)}else d.splice(h,1),h--,k--;if(!(d=c.$$listenerCount[a]&&c.$$childHead||c!==this&&c.$$nextSibling))for(;c!==this&&!(d=c.$$nextSibling);)c=c.$parent}e.currentScope=null;return e}};var v=new m,w=v.$$asyncQueue=[],x=v.$$postDigestQueue=[],y=v.$$applyAsyncQueue=[],J=0;return v}]}function Qe(){var a=/^\s*(https?|s?ftp|mailto|tel|file):/,b=/^\s*((https?|ftp|file|blob):|data:image\/)/;this.aHrefSanitizationTrustedUrlList=function(b){return w(b)? 162(a=b,this):a};this.imgSrcSanitizationTrustedUrlList=function(a){return w(a)?(b=a,this):b};this.$get=function(){return function(d,c){var e=c?b:a,f=ga(d&&d.trim()).href;return""===f||f.match(e)?d:"unsafe:"+f}}}function Xg(a){if("self"===a)return a;if(C(a)){if(-1<a.indexOf("***"))throw Ea("iwcard",a);a=Od(a).replace(/\\\*\\\*/g,".*").replace(/\\\*/g,"[^:/.?&;]*");return new RegExp("^"+a+"$")}if(ab(a))return new RegExp("^"+a.source+"$");throw Ea("imatcher");}function Pd(a){var b=[];w(a)&&r(a,function(a){b.push(Xg(a))}); 163return b}function eg(){this.SCE_CONTEXTS=W;var a=["self"],b=[];this.trustedResourceUrlList=function(b){arguments.length&&(a=Pd(b));return a};Object.defineProperty(this,"resourceUrlWhitelist",{get:function(){return this.trustedResourceUrlList},set:function(a){this.trustedResourceUrlList=a}});this.bannedResourceUrlList=function(a){arguments.length&&(b=Pd(a));return b};Object.defineProperty(this,"resourceUrlBlacklist",{get:function(){return this.bannedResourceUrlList},set:function(a){this.bannedResourceUrlList= 164a}});this.$get=["$injector","$$sanitizeUri",function(d,c){function e(a,b){var c;"self"===a?(c=Cc(b,Qd))||(z.document.baseURI?c=z.document.baseURI:(Na||(Na=z.document.createElement("a"),Na.href=".",Na=Na.cloneNode(!1)),c=Na.href),c=Cc(b,c)):c=!!a.exec(b.href);return c}function f(a){var b=function(a){this.$$unwrapTrustedValue=function(){return a}};a&&(b.prototype=new a);b.prototype.valueOf=function(){return this.$$unwrapTrustedValue()};b.prototype.toString=function(){return this.$$unwrapTrustedValue().toString()}; 165return b}var g=function(a){throw Ea("unsafe");};d.has("$sanitize")&&(g=d.get("$sanitize"));var k=f(),h={};h[W.HTML]=f(k);h[W.CSS]=f(k);h[W.MEDIA_URL]=f(k);h[W.URL]=f(h[W.MEDIA_URL]);h[W.JS]=f(k);h[W.RESOURCE_URL]=f(h[W.URL]);return{trustAs:function(a,b){var c=h.hasOwnProperty(a)?h[a]:null;if(!c)throw Ea("icontext",a,b);if(null===b||A(b)||""===b)return b;if("string"!==typeof b)throw Ea("itype",a);return new c(b)},getTrusted:function(d,f){if(null===f||A(f)||""===f)return f;var k=h.hasOwnProperty(d)? 166h[d]:null;if(k&&f instanceof k)return f.$$unwrapTrustedValue();B(f.$$unwrapTrustedValue)&&(f=f.$$unwrapTrustedValue());if(d===W.MEDIA_URL||d===W.URL)return c(f.toString(),d===W.MEDIA_URL);if(d===W.RESOURCE_URL){var k=ga(f.toString()),n,s,r=!1;n=0;for(s=a.length;n<s;n++)if(e(a[n],k)){r=!0;break}if(r)for(n=0,s=b.length;n<s;n++)if(e(b[n],k)){r=!1;break}if(r)return f;throw Ea("insecurl",f.toString());}if(d===W.HTML)return g(f);throw Ea("unsafe");},valueOf:function(a){return a instanceof k?a.$$unwrapTrustedValue(): 167a}}}]}function dg(){var a=!0;this.enabled=function(b){arguments.length&&(a=!!b);return a};this.$get=["$parse","$sceDelegate",function(b,d){if(a&&8>wa)throw Ea("iequirks");var c=ja(W);c.isEnabled=function(){return a};c.trustAs=d.trustAs;c.getTrusted=d.getTrusted;c.valueOf=d.valueOf;a||(c.trustAs=c.getTrusted=function(a,b){return b},c.valueOf=Ta);c.parseAs=function(a,d){var e=b(d);return e.literal&&e.constant?e:b(d,function(b){return c.getTrusted(a,b)})};var e=c.parseAs,f=c.getTrusted,g=c.trustAs;r(W, 168function(a,b){var d=K(b);c[("parse_as_"+d).replace(Dc,xb)]=function(b){return e(a,b)};c[("get_trusted_"+d).replace(Dc,xb)]=function(b){return f(a,b)};c[("trust_as_"+d).replace(Dc,xb)]=function(b){return g(a,b)}});return c}]}function fg(){this.$get=["$window","$document",function(a,b){var d={},c=!((!a.nw||!a.nw.process)&&a.chrome&&(a.chrome.app&&a.chrome.app.runtime||!a.chrome.app&&a.chrome.runtime&&a.chrome.runtime.id))&&a.history&&a.history.pushState,e=fa((/android (\d+)/.exec(K((a.navigator||{}).userAgent))|| 169[])[1]),f=/Boxee/i.test((a.navigator||{}).userAgent),g=b[0]||{},k=g.body&&g.body.style,h=!1,l=!1;k&&(h=!!("transition"in k||"webkitTransition"in k),l=!!("animation"in k||"webkitAnimation"in k));return{history:!(!c||4>e||f),hasEvent:function(a){if("input"===a&&wa)return!1;if(A(d[a])){var b=g.createElement("div");d[a]="on"+a in b}return d[a]},csp:Ba(),transitions:h,animations:l,android:e}}]}function gg(){this.$get=ia(function(a){return new Yg(a)})}function Yg(a){function b(){var a=e.pop();return a&& 170a.cb}function d(a){for(var b=e.length-1;0<=b;--b){var c=e[b];if(c.type===a)return e.splice(b,1),c.cb}}var c={},e=[],f=this.ALL_TASKS_TYPE="$$all$$",g=this.DEFAULT_TASK_TYPE="$$default$$";this.completeTask=function(e,h){h=h||g;try{e()}finally{var l;l=h||g;c[l]&&(c[l]--,c[f]--);l=c[h];var m=c[f];if(!m||!l)for(l=m?d:b;m=l(h);)try{m()}catch(p){a.error(p)}}};this.incTaskCount=function(a){a=a||g;c[a]=(c[a]||0)+1;c[f]=(c[f]||0)+1};this.notifyWhenNoPendingTasks=function(a,b){b=b||f;c[b]?e.push({type:b,cb:a}): 171a()}}function ig(){var a;this.httpOptions=function(b){return b?(a=b,this):a};this.$get=["$exceptionHandler","$templateCache","$http","$q","$sce",function(b,d,c,e,f){function g(k,h){g.totalPendingRequests++;if(!C(k)||A(d.get(k)))k=f.getTrustedResourceUrl(k);var l=c.defaults&&c.defaults.transformResponse;H(l)?l=l.filter(function(a){return a!==wc}):l===wc&&(l=null);return c.get(k,S({cache:d,transformResponse:l},a)).finally(function(){g.totalPendingRequests--}).then(function(a){return d.put(k,a.data)}, 172function(a){h||(a=Zg("tpload",k,a.status,a.statusText),b(a));return e.reject(a)})}g.totalPendingRequests=0;return g}]}function jg(){this.$get=["$rootScope","$browser","$location",function(a,b,d){return{findBindings:function(a,b,d){a=a.getElementsByClassName("ng-binding");var g=[];r(a,function(a){var c=ca.element(a).data("$binding");c&&r(c,function(c){d?(new RegExp("(^|\\s)"+Od(b)+"(\\s|\\||$)")).test(c)&&g.push(a):-1!==c.indexOf(b)&&g.push(a)})});return g},findModels:function(a,b,d){for(var g=["ng-", 173"data-ng-","ng\\:"],k=0;k<g.length;++k){var h=a.querySelectorAll("["+g[k]+"model"+(d?"=":"*=")+'"'+b+'"]');if(h.length)return h}},getLocation:function(){return d.url()},setLocation:function(b){b!==d.url()&&(d.url(b),a.$digest())},whenStable:function(a){b.notifyWhenNoOutstandingRequests(a)}}}]}function kg(){this.$get=["$rootScope","$browser","$q","$$q","$exceptionHandler",function(a,b,d,c,e){function f(f,h,l){B(f)||(l=h,h=f,f=E);var m=Ha.call(arguments,3),p=w(l)&&!l,n=(p?c:d).defer(),s=n.promise,r; 174r=b.defer(function(){try{n.resolve(f.apply(null,m))}catch(b){n.reject(b),e(b)}finally{delete g[s.$$timeoutId]}p||a.$apply()},h,"$timeout");s.$$timeoutId=r;g[r]=n;return s}var g={};f.cancel=function(a){if(!a)return!1;if(!a.hasOwnProperty("$$timeoutId"))throw $g("badprom");if(!g.hasOwnProperty(a.$$timeoutId))return!1;a=a.$$timeoutId;var c=g[a],d=c.promise;d.$$state&&(d.$$state.pur=!0);c.reject("canceled");delete g[a];return b.defer.cancel(a)};return f}]}function ga(a){if(!C(a))return a;wa&&(aa.setAttribute("href", 175a),a=aa.href);aa.setAttribute("href",a);a=aa.hostname;!ah&&-1<a.indexOf(":")&&(a="["+a+"]");return{href:aa.href,protocol:aa.protocol?aa.protocol.replace(/:$/,""):"",host:aa.host,search:aa.search?aa.search.replace(/^\?/,""):"",hash:aa.hash?aa.hash.replace(/^#/,""):"",hostname:a,port:aa.port,pathname:"/"===aa.pathname.charAt(0)?aa.pathname:"/"+aa.pathname}}function Og(a){var b=[Qd].concat(a.map(ga));return function(a){a=ga(a);return b.some(Cc.bind(null,a))}}function Cc(a,b){a=ga(a);b=ga(b);return a.protocol=== 176b.protocol&&a.host===b.host}function lg(){this.$get=ia(z)}function Rd(a){function b(a){try{return decodeURIComponent(a)}catch(b){return a}}var d=a[0]||{},c={},e="";return function(){var a,g,k,h,l;try{a=d.cookie||""}catch(m){a=""}if(a!==e)for(e=a,a=e.split("; "),c={},k=0;k<a.length;k++)g=a[k],h=g.indexOf("="),0<h&&(l=b(g.substring(0,h)),A(c[l])&&(c[l]=b(g.substring(h+1))));return c}}function pg(){this.$get=Rd}function fd(a){function b(d,c){if(D(d)){var e={};r(d,function(a,c){e[c]=b(c,a)});return e}return a.factory(d+ 177"Filter",c)}this.register=b;this.$get=["$injector",function(a){return function(b){return a.get(b+"Filter")}}];b("currency",Sd);b("date",Td);b("filter",bh);b("json",ch);b("limitTo",dh);b("lowercase",eh);b("number",Ud);b("orderBy",Vd);b("uppercase",fh)}function bh(){return function(a,b,d,c){if(!za(a)){if(null==a)return a;throw F("filter")("notarray",a);}c=c||"$";var e;switch(Ec(b)){case "function":break;case "boolean":case "null":case "number":case "string":e=!0;case "object":b=gh(b,d,c,e);break;default:return a}return Array.prototype.filter.call(a, 178b)}}function gh(a,b,d,c){var e=D(a)&&d in a;!0===b?b=va:B(b)||(b=function(a,b){if(A(a))return!1;if(null===a||null===b)return a===b;if(D(b)||D(a)&&!cc(a))return!1;a=K(""+a);b=K(""+b);return-1!==a.indexOf(b)});return function(f){return e&&!D(f)?Fa(f,a[d],b,d,!1):Fa(f,a,b,d,c)}}function Fa(a,b,d,c,e,f){var g=Ec(a),k=Ec(b);if("string"===k&&"!"===b.charAt(0))return!Fa(a,b.substring(1),d,c,e);if(H(a))return a.some(function(a){return Fa(a,b,d,c,e)});switch(g){case "object":var h;if(e){for(h in a)if(h.charAt&& 179"$"!==h.charAt(0)&&Fa(a[h],b,d,c,!0))return!0;return f?!1:Fa(a,b,d,c,!1)}if("object"===k){for(h in b)if(f=b[h],!B(f)&&!A(f)&&(g=h===c,!Fa(g?a:a[h],f,d,c,g,g)))return!1;return!0}return d(a,b);case "function":return!1;default:return d(a,b)}}function Ec(a){return null===a?"null":typeof a}function Sd(a){var b=a.NUMBER_FORMATS;return function(a,c,e){A(c)&&(c=b.CURRENCY_SYM);A(e)&&(e=b.PATTERNS[1].maxFrac);var f=c?/\u00A4/g:/\s*\u00A4\s*/g;return null==a?a:Wd(a,b.PATTERNS[1],b.GROUP_SEP,b.DECIMAL_SEP,e).replace(f, 180c)}}function Ud(a){var b=a.NUMBER_FORMATS;return function(a,c){return null==a?a:Wd(a,b.PATTERNS[0],b.GROUP_SEP,b.DECIMAL_SEP,c)}}function hh(a){var b=0,d,c,e,f,g;-1<(c=a.indexOf(Xd))&&(a=a.replace(Xd,""));0<(e=a.search(/e/i))?(0>c&&(c=e),c+=+a.slice(e+1),a=a.substring(0,e)):0>c&&(c=a.length);for(e=0;a.charAt(e)===Fc;e++);if(e===(g=a.length))d=[0],c=1;else{for(g--;a.charAt(g)===Fc;)g--;c-=e;d=[];for(f=0;e<=g;e++,f++)d[f]=+a.charAt(e)}c>Yd&&(d=d.splice(0,Yd-1),b=c-1,c=1);return{d:d,e:b,i:c}}function ih(a, 181b,d,c){var e=a.d,f=e.length-a.i;b=A(b)?Math.min(Math.max(d,f),c):+b;d=b+a.i;c=e[d];if(0<d){e.splice(Math.max(a.i,d));for(var g=d;g<e.length;g++)e[g]=0}else for(f=Math.max(0,f),a.i=1,e.length=Math.max(1,d=b+1),e[0]=0,g=1;g<d;g++)e[g]=0;if(5<=c)if(0>d-1){for(c=0;c>d;c--)e.unshift(0),a.i++;e.unshift(1);a.i++}else e[d-1]++;for(;f<Math.max(0,b);f++)e.push(0);if(b=e.reduceRight(function(a,b,c,d){b+=a;d[c]=b%10;return Math.floor(b/10)},0))e.unshift(b),a.i++}function Wd(a,b,d,c,e){if(!C(a)&&!X(a)||isNaN(a))return""; 182var f=!isFinite(a),g=!1,k=Math.abs(a)+"",h="";if(f)h="\u221e";else{g=hh(k);ih(g,e,b.minFrac,b.maxFrac);h=g.d;k=g.i;e=g.e;f=[];for(g=h.reduce(function(a,b){return a&&!b},!0);0>k;)h.unshift(0),k++;0<k?f=h.splice(k,h.length):(f=h,h=[0]);k=[];for(h.length>=b.lgSize&&k.unshift(h.splice(-b.lgSize,h.length).join(""));h.length>b.gSize;)k.unshift(h.splice(-b.gSize,h.length).join(""));h.length&&k.unshift(h.join(""));h=k.join(d);f.length&&(h+=c+f.join(""));e&&(h+="e+"+e)}return 0>a&&!g?b.negPre+h+b.negSuf:b.posPre+ 183h+b.posSuf}function Pb(a,b,d,c){var e="";if(0>a||c&&0>=a)c?a=-a+1:(a=-a,e="-");for(a=""+a;a.length<b;)a=Fc+a;d&&(a=a.substr(a.length-b));return e+a}function ea(a,b,d,c,e){d=d||0;return function(f){f=f["get"+a]();if(0<d||f>-d)f+=d;0===f&&-12===d&&(f=12);return Pb(f,b,c,e)}}function lb(a,b,d){return function(c,e){var f=c["get"+a](),g=vb((d?"STANDALONE":"")+(b?"SHORT":"")+a);return e[g][f]}}function Zd(a){var b=(new Date(a,0,1)).getDay();return new Date(a,0,(4>=b?5:12)-b)}function $d(a){return function(b){var d= 184Zd(b.getFullYear());b=+new Date(b.getFullYear(),b.getMonth(),b.getDate()+(4-b.getDay()))-+d;b=1+Math.round(b/6048E5);return Pb(b,a)}}function Gc(a,b){return 0>=a.getFullYear()?b.ERAS[0]:b.ERAS[1]}function Td(a){function b(a){var b;if(b=a.match(d)){a=new Date(0);var f=0,g=0,k=b[8]?a.setUTCFullYear:a.setFullYear,h=b[8]?a.setUTCHours:a.setHours;b[9]&&(f=fa(b[9]+b[10]),g=fa(b[9]+b[11]));k.call(a,fa(b[1]),fa(b[2])-1,fa(b[3]));f=fa(b[4]||0)-f;g=fa(b[5]||0)-g;k=fa(b[6]||0);b=Math.round(1E3*parseFloat("0."+ 185(b[7]||0)));h.call(a,f,g,k,b)}return a}var d=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c,d,f){var g="",k=[],h,l;d=d||"mediumDate";d=a.DATETIME_FORMATS[d]||d;C(c)&&(c=jh.test(c)?fa(c):b(c));X(c)&&(c=new Date(c));if(!ha(c)||!isFinite(c.getTime()))return c;for(;d;)(l=kh.exec(d))?(k=db(k,l,1),d=k.pop()):(k.push(d),d=null);var m=c.getTimezoneOffset();f&&(m=fc(f,m),c=gc(c,f,!0));r(k,function(b){h=lh[b];g+=h?h(c,a.DATETIME_FORMATS, 186m):"''"===b?"'":b.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function ch(){return function(a,b){A(b)&&(b=2);return eb(a,b)}}function dh(){return function(a,b,d){b=Infinity===Math.abs(Number(b))?Number(b):fa(b);if(Y(b))return a;X(a)&&(a=a.toString());if(!za(a))return a;d=!d||isNaN(d)?0:fa(d);d=0>d?Math.max(0,a.length+d):d;return 0<=b?Hc(a,d,d+b):0===d?Hc(a,b,a.length):Hc(a,Math.max(0,d+b),d)}}function Hc(a,b,d){return C(a)?a.slice(b,d):Ha.call(a,b,d)}function Vd(a){function b(b){return b.map(function(b){var c= 1871,d=Ta;if(B(b))d=b;else if(C(b)){if("+"===b.charAt(0)||"-"===b.charAt(0))c="-"===b.charAt(0)?-1:1,b=b.substring(1);if(""!==b&&(d=a(b),d.constant))var e=d(),d=function(a){return a[e]}}return{get:d,descending:c}})}function d(a){switch(typeof a){case "number":case "boolean":case "string":return!0;default:return!1}}function c(a,b){var c=0,d=a.type,h=b.type;if(d===h){var h=a.value,l=b.value;"string"===d?(h=h.toLowerCase(),l=l.toLowerCase()):"object"===d&&(D(h)&&(h=a.index),D(l)&&(l=b.index));h!==l&&(c= 188h<l?-1:1)}else c="undefined"===d?1:"undefined"===h?-1:"null"===d?1:"null"===h?-1:d<h?-1:1;return c}return function(a,f,g,k){if(null==a)return a;if(!za(a))throw F("orderBy")("notarray",a);H(f)||(f=[f]);0===f.length&&(f=["+"]);var h=b(f),l=g?-1:1,m=B(k)?k:c;a=Array.prototype.map.call(a,function(a,b){return{value:a,tieBreaker:{value:b,type:"number",index:b},predicateValues:h.map(function(c){var e=c.get(a);c=typeof e;if(null===e)c="null";else if("object"===c)a:{if(B(e.valueOf)&&(e=e.valueOf(),d(e)))break a; 189cc(e)&&(e=e.toString(),d(e))}return{value:e,type:c,index:b}})}});a.sort(function(a,b){for(var d=0,e=h.length;d<e;d++){var f=m(a.predicateValues[d],b.predicateValues[d]);if(f)return f*h[d].descending*l}return(m(a.tieBreaker,b.tieBreaker)||c(a.tieBreaker,b.tieBreaker))*l});return a=a.map(function(a){return a.value})}}function Ra(a){B(a)&&(a={link:a});a.restrict=a.restrict||"AC";return ia(a)}function Qb(a,b,d,c,e){this.$$controls=[];this.$error={};this.$$success={};this.$pending=void 0;this.$name=e(b.name|| 190b.ngForm||"")(d);this.$dirty=!1;this.$valid=this.$pristine=!0;this.$submitted=this.$invalid=!1;this.$$parentForm=mb;this.$$element=a;this.$$animate=c;ae(this)}function ae(a){a.$$classCache={};a.$$classCache[be]=!(a.$$classCache[nb]=a.$$element.hasClass(nb))}function ce(a){function b(a,b,c){c&&!a.$$classCache[b]?(a.$$animate.addClass(a.$$element,b),a.$$classCache[b]=!0):!c&&a.$$classCache[b]&&(a.$$animate.removeClass(a.$$element,b),a.$$classCache[b]=!1)}function d(a,c,d){c=c?"-"+Xc(c,"-"):"";b(a,nb+ 191c,!0===d);b(a,be+c,!1===d)}var c=a.set,e=a.unset;a.clazz.prototype.$setValidity=function(a,g,k){A(g)?(this.$pending||(this.$pending={}),c(this.$pending,a,k)):(this.$pending&&e(this.$pending,a,k),de(this.$pending)&&(this.$pending=void 0));Ga(g)?g?(e(this.$error,a,k),c(this.$$success,a,k)):(c(this.$error,a,k),e(this.$$success,a,k)):(e(this.$error,a,k),e(this.$$success,a,k));this.$pending?(b(this,"ng-pending",!0),this.$valid=this.$invalid=void 0,d(this,"",null)):(b(this,"ng-pending",!1),this.$valid= 192de(this.$error),this.$invalid=!this.$valid,d(this,"",this.$valid));g=this.$pending&&this.$pending[a]?void 0:this.$error[a]?!1:this.$$success[a]?!0:null;d(this,a,g);this.$$parentForm.$setValidity(a,g,this)}}function de(a){if(a)for(var b in a)if(a.hasOwnProperty(b))return!1;return!0}function Ic(a){a.$formatters.push(function(b){return a.$isEmpty(b)?b:b.toString()})}function Sa(a,b,d,c,e,f){var g=K(b[0].type);if(!e.android){var k=!1;b.on("compositionstart",function(){k=!0});b.on("compositionupdate", 193function(a){if(A(a.data)||""===a.data)k=!1});b.on("compositionend",function(){k=!1;l()})}var h,l=function(a){h&&(f.defer.cancel(h),h=null);if(!k){var e=b.val();a=a&&a.type;"password"===g||d.ngTrim&&"false"===d.ngTrim||(e=V(e));(c.$viewValue!==e||""===e&&c.$$hasNativeValidators)&&c.$setViewValue(e,a)}};if(e.hasEvent("input"))b.on("input",l);else{var m=function(a,b,c){h||(h=f.defer(function(){h=null;b&&b.value===c||l(a)}))};b.on("keydown",function(a){var b=a.keyCode;91===b||15<b&&19>b||37<=b&&40>=b|| 194m(a,this,this.value)});if(e.hasEvent("paste"))b.on("paste cut drop",m)}b.on("change",l);if(ee[g]&&c.$$hasNativeValidators&&g===d.type)b.on("keydown wheel mousedown",function(a){if(!h){var b=this.validity,c=b.badInput,d=b.typeMismatch;h=f.defer(function(){h=null;b.badInput===c&&b.typeMismatch===d||l(a)})}});c.$render=function(){var a=c.$isEmpty(c.$viewValue)?"":c.$viewValue;b.val()!==a&&b.val(a)}}function Rb(a,b){return function(d,c){var e,f;if(ha(d))return d;if(C(d)){'"'===d.charAt(0)&&'"'===d.charAt(d.length- 1951)&&(d=d.substring(1,d.length-1));if(mh.test(d))return new Date(d);a.lastIndex=0;if(e=a.exec(d))return e.shift(),f=c?{yyyy:c.getFullYear(),MM:c.getMonth()+1,dd:c.getDate(),HH:c.getHours(),mm:c.getMinutes(),ss:c.getSeconds(),sss:c.getMilliseconds()/1E3}:{yyyy:1970,MM:1,dd:1,HH:0,mm:0,ss:0,sss:0},r(e,function(a,c){c<b.length&&(f[b[c]]=+a)}),e=new Date(f.yyyy,f.MM-1,f.dd,f.HH,f.mm,f.ss||0,1E3*f.sss||0),100>f.yyyy&&e.setFullYear(f.yyyy),e}return NaN}}function ob(a,b,d,c){return function(e,f,g,k,h,l,m, 196p){function n(a){return a&&!(a.getTime&&a.getTime()!==a.getTime())}function s(a){return w(a)&&!ha(a)?r(a)||void 0:a}function r(a,b){var c=k.$options.getOption("timezone");v&&v!==c&&(b=Uc(b,fc(v)));var e=d(a,b);!isNaN(e)&&c&&(e=gc(e,c));return e}Jc(e,f,g,k,a);Sa(e,f,g,k,h,l);var t="time"===a||"datetimelocal"===a,q,v;k.$parsers.push(function(c){if(k.$isEmpty(c))return null;if(b.test(c))return r(c,q);k.$$parserName=a});k.$formatters.push(function(a){if(a&&!ha(a))throw pb("datefmt",a);if(n(a)){q=a;var b= 197k.$options.getOption("timezone");b&&(v=b,q=gc(q,b,!0));var d=c;t&&C(k.$options.getOption("timeSecondsFormat"))&&(d=c.replace("ss.sss",k.$options.getOption("timeSecondsFormat")).replace(/:$/,""));a=m("date")(a,d,b);t&&k.$options.getOption("timeStripZeroSeconds")&&(a=a.replace(/(?::00)?(?:\.000)?$/,""));return a}v=q=null;return""});if(w(g.min)||g.ngMin){var x=g.min||p(g.ngMin)(e),z=s(x);k.$validators.min=function(a){return!n(a)||A(z)||d(a)>=z};g.$observe("min",function(a){a!==x&&(z=s(a),x=a,k.$validate())})}if(w(g.max)|| 198g.ngMax){var y=g.max||p(g.ngMax)(e),J=s(y);k.$validators.max=function(a){return!n(a)||A(J)||d(a)<=J};g.$observe("max",function(a){a!==y&&(J=s(a),y=a,k.$validate())})}}}function Jc(a,b,d,c,e){(c.$$hasNativeValidators=D(b[0].validity))&&c.$parsers.push(function(a){var d=b.prop("validity")||{};if(d.badInput||d.typeMismatch)c.$$parserName=e;else return a})}function fe(a){a.$parsers.push(function(b){if(a.$isEmpty(b))return null;if(nh.test(b))return parseFloat(b);a.$$parserName="number"});a.$formatters.push(function(b){if(!a.$isEmpty(b)){if(!X(b))throw pb("numfmt", 199b);b=b.toString()}return b})}function na(a){w(a)&&!X(a)&&(a=parseFloat(a));return Y(a)?void 0:a}function Kc(a){var b=a.toString(),d=b.indexOf(".");return-1===d?-1<a&&1>a&&(a=/e-(\d+)$/.exec(b))?Number(a[1]):0:b.length-d-1}function ge(a,b,d){a=Number(a);var c=(a|0)!==a,e=(b|0)!==b,f=(d|0)!==d;if(c||e||f){var g=c?Kc(a):0,k=e?Kc(b):0,h=f?Kc(d):0,g=Math.max(g,k,h),g=Math.pow(10,g);a*=g;b*=g;d*=g;c&&(a=Math.round(a));e&&(b=Math.round(b));f&&(d=Math.round(d))}return 0===(a-b)%d}function he(a,b,d,c,e){if(w(c)){a= 200a(c);if(!a.constant)throw pb("constexpr",d,c);return a(b)}return e}function Lc(a,b){function d(a,b){if(!a||!a.length)return[];if(!b||!b.length)return a;var c=[],d=0;a:for(;d<a.length;d++){for(var e=a[d],m=0;m<b.length;m++)if(e===b[m])continue a;c.push(e)}return c}function c(a){if(!a)return a;var b=a;H(a)?b=a.map(c).join(" "):D(a)?b=Object.keys(a).filter(function(b){return a[b]}).join(" "):C(a)||(b=a+"");return b}a="ngClass"+a;var e;return["$parse",function(f){return{restrict:"AC",link:function(g, 201k,h){function l(a,b){var c=[];r(a,function(a){if(0<b||p[a])p[a]=(p[a]||0)+b,p[a]===+(0<b)&&c.push(a)});return c.join(" ")}function m(a){if(a===b){var c=s,c=l(c&&c.split(" "),1);h.$addClass(c)}else c=s,c=l(c&&c.split(" "),-1),h.$removeClass(c);n=a}var p=k.data("$classCounts"),n=!0,s;p||(p=T(),k.data("$classCounts",p));"ngClass"!==a&&(e||(e=f("$index",function(a){return a&1})),g.$watch(e,m));g.$watch(f(h[a],c),function(a){if(n===b){var c=s&&s.split(" "),e=a&&a.split(" "),f=d(c,e),c=d(e,c),f=l(f,-1), 202c=l(c,1);h.$addClass(c);h.$removeClass(f)}s=a})}}}]}function sd(a,b,d,c,e,f){return{restrict:"A",compile:function(g,k){var h=a(k[c]);return function(a,c){c.on(e,function(c){var e=function(){h(a,{$event:c})};if(b.$$phase)if(f)a.$evalAsync(e);else try{e()}catch(g){d(g)}else a.$apply(e)})}}}}function Sb(a,b,d,c,e,f,g,k,h){this.$modelValue=this.$viewValue=Number.NaN;this.$$rawModelValue=void 0;this.$validators={};this.$asyncValidators={};this.$parsers=[];this.$formatters=[];this.$viewChangeListeners= 203[];this.$untouched=!0;this.$touched=!1;this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$error={};this.$$success={};this.$pending=void 0;this.$name=h(d.name||"",!1)(a);this.$$parentForm=mb;this.$options=Tb;this.$$updateEvents="";this.$$updateEventHandler=this.$$updateEventHandler.bind(this);this.$$parsedNgModel=e(d.ngModel);this.$$parsedNgModelAssign=this.$$parsedNgModel.assign;this.$$ngModelGet=this.$$parsedNgModel;this.$$ngModelSet=this.$$parsedNgModelAssign;this.$$pendingDebounce= 204null;this.$$parserValid=void 0;this.$$parserName="parse";this.$$currentValidationRunId=0;this.$$scope=a;this.$$rootScope=a.$root;this.$$attr=d;this.$$element=c;this.$$animate=f;this.$$timeout=g;this.$$parse=e;this.$$q=k;this.$$exceptionHandler=b;ae(this);oh(this)}function oh(a){a.$$scope.$watch(function(b){b=a.$$ngModelGet(b);b===a.$modelValue||a.$modelValue!==a.$modelValue&&b!==b||a.$$setModelValue(b);return b})}function Mc(a){this.$$options=a}function ie(a,b){r(b,function(b,c){w(a[c])||(a[c]=b)})} 205function Oa(a,b){a.prop("selected",b);a.attr("selected",b)}function je(a,b,d){if(a){C(a)&&(a=new RegExp("^"+a+"$"));if(!a.test)throw F("ngPattern")("noregexp",b,a,Aa(d));return a}}function Ub(a){a=fa(a);return Y(a)?-1:a}var Xb={objectMaxDepth:5,urlErrorParamsEnabled:!0},ke=/^\/(.+)\/([a-z]*)$/,ta=Object.prototype.hasOwnProperty,K=function(a){return C(a)?a.toLowerCase():a},vb=function(a){return C(a)?a.toUpperCase():a},wa,x,sb,Ha=[].slice,Kg=[].splice,ph=[].push,la=Object.prototype.toString,Rc=Object.getPrototypeOf, 206oa=F("ng"),ca=z.angular||(z.angular={}),lc,qb=0;wa=z.document.documentMode;var Y=Number.isNaN||function(a){return a!==a};E.$inject=[];Ta.$inject=[];var ze=/^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array]$/,V=function(a){return C(a)?a.trim():a},Od=function(a){return a.replace(/([-()[\]{}+?*.$^|,:#<!\\])/g,"\\$1").replace(/\x08/g,"\\x08")},Ba=function(){if(!w(Ba.rules)){var a=z.document.querySelector("[ng-csp]")||z.document.querySelector("[data-ng-csp]");if(a){var b= 207a.getAttribute("ng-csp")||a.getAttribute("data-ng-csp");Ba.rules={noUnsafeEval:!b||-1!==b.indexOf("no-unsafe-eval"),noInlineStyle:!b||-1!==b.indexOf("no-inline-style")}}else{a=Ba;try{new Function(""),b=!1}catch(d){b=!0}a.rules={noUnsafeEval:b,noInlineStyle:!1}}}return Ba.rules},rb=function(){if(w(rb.name_))return rb.name_;var a,b,d=Qa.length,c,e;for(b=0;b<d;++b)if(c=Qa[b],a=z.document.querySelector("["+c.replace(":","\\:")+"jq]")){e=a.getAttribute(c+"jq");break}return rb.name_=e},Be=/:/g,Qa=["ng-", 208"data-ng-","ng:","x-ng-"],Fe=function(a){var b=a.currentScript;if(!b)return!0;if(!(b instanceof z.HTMLScriptElement||b instanceof z.SVGScriptElement))return!1;b=b.attributes;return[b.getNamedItem("src"),b.getNamedItem("href"),b.getNamedItem("xlink:href")].every(function(b){if(!b)return!0;if(!b.value)return!1;var c=a.createElement("a");c.href=b.value;if(a.location.origin===c.origin)return!0;switch(c.protocol){case "http:":case "https:":case "ftp:":case "blob:":case "file:":case "data:":return!0;default:return!1}})}(z.document), 209Ie=/[A-Z]/g,Yc=!1,Pa=3,Pe={full:"1.8.
2092",major:1,minor:8,dot:2,codeName:"meteoric-mining
vendor: 74,250 bytes, lines 209-353
209"};U.expando="ng339";var Ka=U.cache={},ug=1;U._data=function(a){return this.cache[a[this.expando]]||{}};var qg=/-([a-z])/g,qh=/^-ms-/,Bb={mouseleave:"mouseout",mouseenter:"mouseover"},oc=F("jqLite"),tg=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,nc=/<|&#?\w+;/,rg=/<([\w:-]+)/,sg=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,qa={thead:["table"],col:["colgroup","table"],tr:["tbody","table"],td:["tr", 210"tbody","table"]};qa.tbody=qa.tfoot=qa.colgroup=qa.caption=qa.thead;qa.th=qa.td;var hb={option:[1,'<select multiple="multiple">',"</select>"],_default:[0,"",""]},Nc;for(Nc in qa){var le=qa[Nc],me=le.slice().reverse();hb[Nc]=[me.length,"<"+me.join("><")+">","</"+le.join("></")+">"]}hb.optgroup=hb.option;var zg=z.Node.prototype.contains||function(a){return!!(this.compareDocumentPosition(a)&16)},Wa=U.prototype={ready:hd,toString:function(){var a=[];r(this,function(b){a.push(""+b)});return"["+a.join(", ")+ 211"]"},eq:function(a){return 0<=a?x(this[a]):x(this[this.length+a])},length:0,push:ph,sort:[].sort,splice:[].splice},Hb={};r("multiple selected checked disabled readOnly required open".split(" "),function(a){Hb[K(a)]=a});var od={};r("input select option textarea button form details".split(" "),function(a){od[a]=!0});var vd={ngMinlength:"minlength",ngMaxlength:"maxlength",ngMin:"min",ngMax:"max",ngPattern:"pattern",ngStep:"step"};r({data:sc,removeData:rc,hasData:function(a){for(var b in Ka[a.ng339])return!0; 212return!1},cleanData:function(a){for(var b=0,d=a.length;b<d;b++)rc(a[b]),kd(a[b])}},function(a,b){U[b]=a});r({data:sc,inheritedData:Fb,scope:function(a){return x.data(a,"$scope")||Fb(a.parentNode||a,["$isolateScope","$scope"])},isolateScope:function(a){return x.data(a,"$isolateScope")||x.data(a,"$isolateScopeNoTemplate")},controller:ld,injector:function(a){return Fb(a,"$injector")},removeAttr:function(a,b){a.removeAttribute(b)},hasClass:Cb,css:function(a,b,d){b=yb(b.replace(qh,"ms-"));if(w(d))a.style[b]= 213d;else return a.style[b]},attr:function(a,b,d){var c=a.nodeType;if(c!==Pa&&2!==c&&8!==c&&a.getAttribute){var c=K(b),e=Hb[c];if(w(d))null===d||!1===d&&e?a.removeAttribute(b):a.setAttribute(b,e?c:d);else return a=a.getAttribute(b),e&&null!==a&&(a=c),null===a?void 0:a}},prop:function(a,b,d){if(w(d))a[b]=d;else return a[b]},text:function(){function a(a,d){if(A(d)){var c=a.nodeType;return 1===c||c===Pa?a.textContent:""}a.textContent=d}a.$dv="";return a}(),val:function(a,b){if(A(b)){if(a.multiple&&"select"=== 214ua(a)){var d=[];r(a.options,function(a){a.selected&&d.push(a.value||a.text)});return d}return a.value}a.value=b},html:function(a,b){if(A(b))return a.innerHTML;zb(a,!0);a.innerHTML=b},empty:md},function(a,b){U.prototype[b]=function(b,c){var e,f,g=this.length;if(a!==md&&A(2===a.length&&a!==Cb&&a!==ld?b:c)){if(D(b)){for(e=0;e<g;e++)if(a===sc)a(this[e],b);else for(f in b)a(this[e],f,b[f]);return this}e=a.$dv;g=A(e)?Math.min(g,1):g;for(f=0;f<g;f++){var k=a(this[f],b,c);e=e?e+k:k}return e}for(e=0;e<g;e++)a(this[e], 215b,c);return this}});r({removeData:rc,on:function(a,b,d,c){if(w(c))throw oc("onargs");if(mc(a)){c=Ab(a,!0);var e=c.events,f=c.handle;f||(f=c.handle=wg(a,e));c=0<=b.indexOf(" ")?b.split(" "):[b];for(var g=c.length,k=function(b,c,g){var k=e[b];k||(k=e[b]=[],k.specialHandlerWrapper=c,"$destroy"===b||g||a.addEventListener(b,f));k.push(d)};g--;)b=c[g],Bb[b]?(k(Bb[b],yg),k(b,void 0,!0)):k(b)}},off:kd,one:function(a,b,d){a=x(a);a.on(b,function e(){a.off(b,d);a.off(b,e)});a.on(b,d)},replaceWith:function(a, 216b){var d,c=a.parentNode;zb(a);r(new U(b),function(b){d?c.insertBefore(b,d.nextSibling):c.replaceChild(b,a);d=b})},children:function(a){var b=[];r(a.childNodes,function(a){1===a.nodeType&&b.push(a)});return b},contents:function(a){return a.contentDocument||a.childNodes||[]},append:function(a,b){var d=a.nodeType;if(1===d||11===d){b=new U(b);for(var d=0,c=b.length;d<c;d++)a.appendChild(b[d])}},prepend:function(a,b){if(1===a.nodeType){var d=a.firstChild;r(new U(b),function(b){a.insertBefore(b,d)})}}, 217wrap:function(a,b){var d=x(b).eq(0).clone()[0],c=a.parentNode;c&&c.replaceChild(d,a);d.appendChild(a)},remove:Gb,detach:function(a){Gb(a,!0)},after:function(a,b){var d=a,c=a.parentNode;if(c){b=new U(b);for(var e=0,f=b.length;e<f;e++){var g=b[e];c.insertBefore(g,d.nextSibling);d=g}}},addClass:Eb,removeClass:Db,toggleClass:function(a,b,d){b&&r(b.split(" "),function(b){var e=d;A(e)&&(e=!Cb(a,b));(e?Eb:Db)(a,b)})},parent:function(a){return(a=a.parentNode)&&11!==a.nodeType?a:null},next:function(a){return a.nextElementSibling}, 218find:function(a,b){return a.getElementsByTagName?a.getElementsByTagName(b):[]},clone:qc,triggerHandler:function(a,b,d){var c,e,f=b.type||b,g=Ab(a);if(g=(g=g&&g.events)&&g[f])c={preventDefault:function(){this.defaultPrevented=!0},isDefaultPrevented:function(){return!0===this.defaultPrevented},stopImmediatePropagation:function(){this.immediatePropagationStopped=!0},isImmediatePropagationStopped:function(){return!0===this.immediatePropagationStopped},stopPropagation:E,type:f,target:a},b.type&&(c=S(c, 219b)),b=ja(g),e=d?[c].concat(d):[c],r(b,function(b){c.isImmediatePropagationStopped()||b.apply(a,e)})}},function(a,b){U.prototype[b]=function(b,c,e){for(var f,g=0,k=this.length;g<k;g++)A(f)?(f=a(this[g],b,c,e),w(f)&&(f=x(f))):pc(f,a(this[g],b,c,e));return w(f)?f:this}});U.prototype.bind=U.prototype.on;U.prototype.unbind=U.prototype.off;var rh=Object.create(null);pd.prototype={_idx:function(a){a!==this._lastKey&&(this._lastKey=a,this._lastIndex=this._keys.indexOf(a));return this._lastIndex},_transformKey:function(a){return Y(a)? 220rh:a},get:function(a){a=this._transformKey(a);a=this._idx(a);if(-1!==a)return this._values[a]},has:function(a){a=this._transformKey(a);return-1!==this._idx(a)},set:function(a,b){a=this._transformKey(a);var d=this._idx(a);-1===d&&(d=this._lastIndex=this._keys.length);this._keys[d]=a;this._values[d]=b},delete:function(a){a=this._transformKey(a);a=this._idx(a);if(-1===a)return!1;this._keys.splice(a,1);this._values.splice(a,1);this._lastKey=NaN;this._lastIndex=-1;return!0}};var Ib=pd,og=[function(){this.$get= 221[function(){return Ib}]}],Bg=/^([^(]+?)=>/,Cg=/^[^(]*\(\s*([^)]*)\)/m,sh=/,/,th=/^\s*(_?)(\S+?)\1\s*$/,Ag=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg,Ca=F("$injector");fb.$$annotate=function(a,b,d){var c;if("function"===typeof a){if(!(c=a.$inject)){c=[];if(a.length){if(b)throw C(d)&&d||(d=a.name||Dg(a)),Ca("strictdi",d);b=qd(a);r(b[1].split(sh),function(a){a.replace(th,function(a,b,d){c.push(d)})})}a.$inject=c}}else H(a)?(b=a.length-1,tb(a[b],"fn"),c=a.slice(0,b)):tb(a,"fn",!0);return c};var ne=F("$animate"), 222Ef=function(){this.$get=E},Ff=function(){var a=new Ib,b=[];this.$get=["$$AnimateRunner","$rootScope",function(d,c){function e(a,b,c){var d=!1;b&&(b=C(b)?b.split(" "):H(b)?b:[],r(b,function(b){b&&(d=!0,a[b]=c)}));return d}function f(){r(b,function(b){var c=a.get(b);if(c){var d=Eg(b.attr("class")),e="",f="";r(c,function(a,b){a!==!!d[b]&&(a?e+=(e.length?" ":"")+b:f+=(f.length?" ":"")+b)});r(b,function(a){e&&Eb(a,e);f&&Db(a,f)});a.delete(b)}});b.length=0}return{enabled:E,on:E,off:E,pin:E,push:function(g, 223k,h,l){l&&l();h=h||{};h.from&&g.css(h.from);h.to&&g.css(h.to);if(h.addClass||h.removeClass)if(k=h.addClass,l=h.removeClass,h=a.get(g)||{},k=e(h,k,!0),l=e(h,l,!1),k||l)a.set(g,h),b.push(g),1===b.length&&c.$$postDigest(f);g=new d;g.complete();return g}}}]},Cf=["$provide",function(a){var b=this,d=null,c=null;this.$$registeredAnimations=Object.create(null);this.register=function(c,d){if(c&&"."!==c.charAt(0))throw ne("notcsel",c);var g=c+"-animation";b.$$registeredAnimations[c.substr(1)]=g;a.factory(g, 224d)};this.customFilter=function(a){1===arguments.length&&(c=B(a)?a:null);return c};this.classNameFilter=function(a){if(1===arguments.length&&(d=a instanceof RegExp?a:null)&&/[(\s|\/)]ng-animate[(\s|\/)]/.test(d.toString()))throw d=null,ne("nongcls","ng-animate");return d};this.$get=["$$animateQueue",function(a){function b(a,c,d){if(d){var e;a:{for(e=0;e<d.length;e++){var f=d[e];if(1===f.nodeType){e=f;break a}}e=void 0}!e||e.parentNode||e.previousElementSibling||(d=null)}d?d.after(a):c.prepend(a)}return{on:a.on, 225off:a.off,pin:a.pin,enabled:a.enabled,cancel:function(a){a.cancel&&a.cancel()},enter:function(c,d,h,l){d=d&&x(d);h=h&&x(h);d=d||h.parent();b(c,d,h);return a.push(c,"enter",ra(l))},move:function(c,d,h,l){d=d&&x(d);h=h&&x(h);d=d||h.parent();b(c,d,h);return a.push(c,"move",ra(l))},leave:function(b,c){return a.push(b,"leave",ra(c),function(){b.remove()})},addClass:function(b,c,d){d=ra(d);d.addClass=ib(d.addclass,c);return a.push(b,"addClass",d)},removeClass:function(b,c,d){d=ra(d);d.removeClass=ib(d.removeClass, 226c);return a.push(b,"removeClass",d)},setClass:function(b,c,d,f){f=ra(f);f.addClass=ib(f.addClass,c);f.removeClass=ib(f.removeClass,d);return a.push(b,"setClass",f)},animate:function(b,c,d,f,m){m=ra(m);m.from=m.from?S(m.from,c):c;m.to=m.to?S(m.to,d):d;m.tempClasses=ib(m.tempClasses,f||"ng-inline-animate");return a.push(b,"animate",m)}}}]}],Hf=function(){this.$get=["$$rAF",function(a){function b(b){d.push(b);1<d.length||a(function(){for(var a=0;a<d.length;a++)d[a]();d=[]})}var d=[];return function(){var a= 227!1;b(function(){a=!0});return function(d){a?d():b(d)}}}]},Gf=function(){this.$get=["$q","$sniffer","$$animateAsyncRun","$$isDocumentHidden","$timeout",function(a,b,d,c,e){function f(a){this.setHost(a);var b=d();this._doneCallbacks=[];this._tick=function(a){c()?e(a,0,!1):b(a)};this._state=0}f.chain=function(a,b){function c(){if(d===a.length)b(!0);else a[d](function(a){!1===a?b(!1):(d++,c())})}var d=0;c()};f.all=function(a,b){function c(f){e=e&&f;++d===a.length&&b(e)}var d=0,e=!0;r(a,function(a){a.done(c)})}; 228f.prototype={setHost:function(a){this.host=a||{}},done:function(a){2===this._state?a():this._doneCallbacks.push(a)},progress:E,getPromise:function(){if(!this.promise){var b=this;this.promise=a(function(a,c){b.done(function(b){!1===b?c():a()})})}return this.promise},then:function(a,b){return this.getPromise().then(a,b)},"catch":function(a){return this.getPromise()["catch"](a)},"finally":function(a){return this.getPromise()["finally"](a)},pause:function(){this.host.pause&&this.host.pause()},resume:function(){this.host.resume&& 229this.host.resume()},end:function(){this.host.end&&this.host.end();this._resolve(!0)},cancel:function(){this.host.cancel&&this.host.cancel();this._resolve(!1)},complete:function(a){var b=this;0===b._state&&(b._state=1,b._tick(function(){b._resolve(a)}))},_resolve:function(a){2!==this._state&&(r(this._doneCallbacks,function(b){b(a)}),this._doneCallbacks.length=0,this._state=2)}};return f}]},Df=function(){this.$get=["$$rAF","$q","$$AnimateRunner",function(a,b,d){return function(b,e){function f(){a(function(){g.addClass&& 230(b.addClass(g.addClass),g.addClass=null);g.removeClass&&(b.removeClass(g.removeClass),g.removeClass=null);g.to&&(b.css(g.to),g.to=null);k||h.complete();k=!0});return h}var g=e||{};g.$$prepared||(g=Ia(g));g.cleanupStyles&&(g.from=g.to=null);g.from&&(b.css(g.from),g.from=null);var k,h=new d;return{start:f,end:f}}}]},$=F("$compile"),uc=new function(){};Zc.$inject=["$provide","$$sanitizeUriProvider"];Kb.prototype.isFirstChange=function(){return this.previousValue===uc};var rd=/^((?:x|data)[:\-_])/i,Jg= 231/[:\-_]+(.)/g,xd=F("$controller"),wd=/^(\S+)(\s+as\s+([\w$]+))?$/,Of=function(){this.$get=["$document",function(a){return function(b){b?!b.nodeType&&b instanceof x&&(b=b[0]):b=a[0].body;return b.offsetWidth+1}}]},yd="application/json",xc={"Content-Type":yd+";charset=utf-8"},Mg=/^\[|^\{(?!\{)/,Ng={"[":/]$/,"{":/}$/},Lg=/^\)]\}',?\n/,Lb=F("$http"),Ma=ca.$interpolateMinErr=F("$interpolate");Ma.throwNoconcat=function(a){throw Ma("noconcat",a);};Ma.interr=function(a,b){return Ma("interr",a,b.toString())}; 232var Qg=F("$interval"),Xf=function(){this.$get=function(){function a(a){var b=function(a){b.data=a;b.called=!0};b.id=a;return b}var b=ca.callbacks,d={};return{createCallback:function(c){c="_"+(b.$$counter++).toString(36);var e="angular.callbacks."+c,f=a(c);d[e]=b[c]=f;return e},wasCalled:function(a){return d[a].called},getResponse:function(a){return d[a].data},removeCallback:function(a){delete b[d[a].id];delete d[a]}}}},uh=/^([^?#]*)(\?([^#]*))?(#(.*))?$/,Rg={http:80,https:443,ftp:21},kb=F("$location"), 233Sg=/^\s*[\\/]{2,}/,vh={$$absUrl:"",$$html5:!1,$$replace:!1,$$compose:function(){for(var a=this.$$path,b=this.$$hash,d=Ce(this.$$search),b=b?"#"+ic(b):"",a=a.split("/"),c=a.length;c--;)a[c]=ic(a[c].replace(/%2F/g,"/"));this.$$url=a.join("/")+(d?"?"+d:"")+b;this.$$absUrl=this.$$normalizeUrl(this.$$url);this.$$urlUpdatedByLocation=!0},absUrl:Mb("$$absUrl"),url:function(a){if(A(a))return this.$$url;var b=uh.exec(a);(b[1]||""===a)&&this.path(decodeURIComponent(b[1]));(b[2]||b[1]||""===a)&&this.search(b[3]|| 234"");this.hash(b[5]||"");return this},protocol:Mb("$$protocol"),host:Mb("$$host"),port:Mb("$$port"),path:Fd("$$path",function(a){a=null!==a?a.toString():"";return"/"===a.charAt(0)?a:"/"+a}),search:function(a,b){switch(arguments.length){case 0:return this.$$search;case 1:if(C(a)||X(a))a=a.toString(),this.$$search=hc(a);else if(D(a))a=Ia(a,{}),r(a,function(b,c){null==b&&delete a[c]}),this.$$search=a;else throw kb("isrcharg");break;default:A(b)||null===b?delete this.$$search[a]:this.$$search[a]=b}this.$$compose(); 235return this},hash:Fd("$$hash",function(a){return null!==a?a.toString():""}),replace:function(){this.$$replace=!0;return this}};r([Ed,Ac,zc],function(a){a.prototype=Object.create(vh);a.prototype.state=function(b){if(!arguments.length)return this.$$state;if(a!==zc||!this.$$html5)throw kb("nostate");this.$$state=A(b)?null:b;this.$$urlUpdatedByLocation=!0;return this}});var Ya=F("$parse"),Wg={}.constructor.prototype.valueOf,Vb=T();r("+ - * / % === !== == != < > <= >= && || ! = |".split(" "),function(a){Vb[a]= 236!0});var wh={n:"\n",f:"\f",r:"\r",t:"\t",v:"\v","'":"'",'"':'"'},Ob=function(a){this.options=a};Ob.prototype={constructor:Ob,lex:function(a){this.text=a;this.index=0;for(this.tokens=[];this.index<this.text.length;)if(a=this.text.charAt(this.index),'"'===a||"'"===a)this.readString(a);else if(this.isNumber(a)||"."===a&&this.isNumber(this.peek()))this.readNumber();else if(this.isIdentifierStart(this.peekMultichar()))this.readIdent();else if(this.is(a,"(){}[].,;:?"))this.tokens.push({index:this.index, 237text:a}),this.index++;else if(this.isWhitespace(a))this.index++;else{var b=a+this.peek(),d=b+this.peek(2),c=Vb[b],e=Vb[d];Vb[a]||c||e?(a=e?d:c?b:a,this.tokens.push({index:this.index,text:a,operator:!0}),this.index+=a.length):this.throwError("Unexpected next character ",this.index,this.index+1)}return this.tokens},is:function(a,b){return-1!==b.indexOf(a)},peek:function(a){a=a||1;return this.index+a<this.text.length?this.text.charAt(this.index+a):!1},isNumber:function(a){return"0"<=a&&"9">=a&&"string"=== 238typeof a},isWhitespace:function(a){return" "===a||"\r"===a||"\t"===a||"\n"===a||"\v"===a||"\u00a0"===a},isIdentifierStart:function(a){return this.options.isIdentifierStart?this.options.isIdentifierStart(a,this.codePointAt(a)):this.isValidIdentifierStart(a)},isValidIdentifierStart:function(a){return"a"<=a&&"z">=a||"A"<=a&&"Z">=a||"_"===a||"$"===a},isIdentifierContinue:function(a){return this.options.isIdentifierContinue?this.options.isIdentifierContinue(a,this.codePointAt(a)):this.isValidIdentifierContinue(a)}, 239isValidIdentifierContinue:function(a,b){return this.isValidIdentifierStart(a,b)||this.isNumber(a)},codePointAt:function(a){return 1===a.length?a.charCodeAt(0):(a.charCodeAt(0)<<10)+a.charCodeAt(1)-56613888},peekMultichar:function(){var a=this.text.charAt(this.index),b=this.peek();if(!b)return a;var d=a.charCodeAt(0),c=b.charCodeAt(0);return 55296<=d&&56319>=d&&56320<=c&&57343>=c?a+b:a},isExpOperator:function(a){return"-"===a||"+"===a||this.isNumber(a)},throwError:function(a,b,d){d=d||this.index;b= 240w(b)?"s "+b+"-"+this.index+" ["+this.text.substring(b,d)+"]":" "+d;throw Ya("lexerr",a,b,this.text);},readNumber:function(){for(var a="",b=this.index;this.index<this.text.length;){var d=K(this.text.charAt(this.index));if("."===d||this.isNumber(d))a+=d;else{var c=this.peek();if("e"===d&&this.isExpOperator(c))a+=d;else if(this.isExpOperator(d)&&c&&this.isNumber(c)&&"e"===a.charAt(a.length-1))a+=d;else if(!this.isExpOperator(d)||c&&this.isNumber(c)||"e"!==a.charAt(a.length-1))break;else this.throwError("Invalid exponent")}this.index++}this.tokens.push({index:b, 241text:a,constant:!0,value:Number(a)})},readIdent:function(){var a=this.index;for(this.index+=this.peekMultichar().length;this.index<this.text.length;){var b=this.peekMultichar();if(!this.isIdentifierContinue(b))break;this.index+=b.length}this.tokens.push({index:a,text:this.text.slice(a,this.index),identifier:!0})},readString:function(a){var b=this.index;this.index++;for(var d="",c=a,e=!1;this.index<this.text.length;){var f=this.text.charAt(this.index),c=c+f;if(e)"u"===f?(e=this.text.substring(this.index+ 2421,this.index+5),e.match(/[\da-f]{4}/i)||this.throwError("Invalid unicode escape [\\u"+e+"]"),this.index+=4,d+=String.fromCharCode(parseInt(e,16))):d+=wh[f]||f,e=!1;else if("\\"===f)e=!0;else{if(f===a){this.index++;this.tokens.push({index:b,text:c,constant:!0,value:d});return}d+=f}this.index++}this.throwError("Unterminated quote",b)}};var q=function(a,b){this.lexer=a;this.options=b};q.Program="Program";q.ExpressionStatement="ExpressionStatement";q.AssignmentExpression="AssignmentExpression";q.ConditionalExpression= 243"ConditionalExpression";q.LogicalExpression="LogicalExpression";q.BinaryExpression="BinaryExpression";q.UnaryExpression="UnaryExpression";q.CallExpression="CallExpression";q.MemberExpression="MemberExpression";q.Identifier="Identifier";q.Literal="Literal";q.ArrayExpression="ArrayExpression";q.Property="Property";q.ObjectExpression="ObjectExpression";q.ThisExpression="ThisExpression";q.LocalsExpression="LocalsExpression";q.NGValueParameter="NGValueParameter";q.prototype={ast:function(a){this.text= 244a;this.tokens=this.lexer.lex(a);a=this.program();0!==this.tokens.length&&this.throwError("is an unexpected token",this.tokens[0]);return a},program:function(){for(var a=[];;)if(0<this.tokens.length&&!this.peek("}",")",";","]")&&a.push(this.expressionStatement()),!this.expect(";"))return{type:q.Program,body:a}},expressionStatement:function(){return{type:q.ExpressionStatement,expression:this.filterChain()}},filterChain:function(){for(var a=this.expression();this.expect("|");)a=this.filter(a);return a}, 245expression:function(){return this.assignment()},assignment:function(){var a=this.ternary();if(this.expect("=")){if(!Jd(a))throw Ya("lval");a={type:q.AssignmentExpression,left:a,right:this.assignment(),operator:"="}}return a},ternary:function(){var a=this.logicalOR(),b,d;return this.expect("?")&&(b=this.expression(),this.consume(":"))?(d=this.expression(),{type:q.ConditionalExpression,test:a,alternate:b,consequent:d}):a},logicalOR:function(){for(var a=this.logicalAND();this.expect("||");)a={type:q.LogicalExpression, 246operator:"||",left:a,right:this.logicalAND()};return a},logicalAND:function(){for(var a=this.equality();this.expect("&&");)a={type:q.LogicalExpression,operator:"&&",left:a,right:this.equality()};return a},equality:function(){for(var a=this.relational(),b;b=this.expect("==","!=","===","!==");)a={type:q.BinaryExpression,operator:b.text,left:a,right:this.relational()};return a},relational:function(){for(var a=this.additive(),b;b=this.expect("<",">","<=",">=");)a={type:q.BinaryExpression,operator:b.text, 247left:a,right:this.additive()};return a},additive:function(){for(var a=this.multiplicative(),b;b=this.expect("+","-");)a={type:q.BinaryExpression,operator:b.text,left:a,right:this.multiplicative()};return a},multiplicative:function(){for(var a=this.unary(),b;b=this.expect("*","/","%");)a={type:q.BinaryExpression,operator:b.text,left:a,right:this.unary()};return a},unary:function(){var a;return(a=this.expect("+","-","!"))?{type:q.UnaryExpression,operator:a.text,prefix:!0,argument:this.unary()}:this.primary()}, 248primary:function(){var a;this.expect("(")?(a=this.filterChain(),this.consume(")")):this.expect("[")?a=this.arrayDeclaration():this.expect("{")?a=this.object():this.selfReferential.hasOwnProperty(this.peek().text)?a=Ia(this.selfReferential[this.consume().text]):this.options.literals.hasOwnProperty(this.peek().text)?a={type:q.Literal,value:this.options.literals[this.consume().text]}:this.peek().identifier?a=this.identifier():this.peek().constant?a=this.constant():this.throwError("not a primary expression", 249this.peek());for(var b;b=this.expect("(","[",".");)"("===b.text?(a={type:q.CallExpression,callee:a,arguments:this.parseArguments()},this.consume(")")):"["===b.text?(a={type:q.MemberExpression,object:a,property:this.expression(),computed:!0},this.consume("]")):"."===b.text?a={type:q.MemberExpression,object:a,property:this.identifier(),computed:!1}:this.throwError("IMPOSSIBLE");return a},filter:function(a){a=[a];for(var b={type:q.CallExpression,callee:this.identifier(),arguments:a,filter:!0};this.expect(":");)a.push(this.expression()); 250return b},parseArguments:function(){var a=[];if(")"!==this.peekToken().text){do a.push(this.filterChain());while(this.expect(","))}return a},identifier:function(){var a=this.consume();a.identifier||this.throwError("is not a valid identifier",a);return{type:q.Identifier,name:a.text}},constant:function(){return{type:q.Literal,value:this.consume().value}},arrayDeclaration:function(){var a=[];if("]"!==this.peekToken().text){do{if(this.peek("]"))break;a.push(this.expression())}while(this.expect(","))}this.consume("]"); 251return{type:q.ArrayExpression,elements:a}},object:function(){var a=[],b;if("}"!==this.peekToken().text){do{if(this.peek("}"))break;b={type:q.Property,kind:"init"};this.peek().constant?(b.key=this.constant(),b.computed=!1,this.consume(":"),b.value=this.expression()):this.peek().identifier?(b.key=this.identifier(),b.computed=!1,this.peek(":")?(this.consume(":"),b.value=this.expression()):b.value=b.key):this.peek("[")?(this.consume("["),b.key=this.expression(),this.consume("]"),b.computed=!0,this.consume(":"), 252b.value=this.expression()):this.throwError("invalid key",this.peek());a.push(b)}while(this.expect(","))}this.consume("}");return{type:q.ObjectExpression,properties:a}},throwError:function(a,b){throw Ya("syntax",b.text,a,b.index+1,this.text,this.text.substring(b.index));},consume:function(a){if(0===this.tokens.length)throw Ya("ueoe",this.text);var b=this.expect(a);b||this.throwError("is unexpected, expecting ["+a+"]",this.peek());return b},peekToken:function(){if(0===this.tokens.length)throw Ya("ueoe", 253this.text);return this.tokens[0]},peek:function(a,b,d,c){return this.peekAhead(0,a,b,d,c)},peekAhead:function(a,b,d,c,e){if(this.tokens.length>a){a=this.tokens[a];var f=a.text;if(f===b||f===d||f===c||f===e||!(b||d||c||e))return a}return!1},expect:function(a,b,d,c){return(a=this.peek(a,b,d,c))?(this.tokens.shift(),a):!1},selfReferential:{"this":{type:q.ThisExpression},$locals:{type:q.LocalsExpression}}};var Hd=2;Ld.prototype={compile:function(a){var b=this;this.state={nextId:0,filters:{},fn:{vars:[], 254body:[],own:{}},assign:{vars:[],body:[],own:{}},inputs:[]};Z(a,b.$filter);var d="",c;this.stage="assign";if(c=Kd(a))this.state.computing="assign",d=this.nextId(),this.recurse(c,d),this.return_(d),d="fn.assign="+this.generateFunction("assign","s,v,l");c=Id(a.body);b.stage="inputs";r(c,function(a,c){var d="fn"+c;b.state[d]={vars:[],body:[],own:{}};b.state.computing=d;var k=b.nextId();b.recurse(a,k);b.return_(k);b.state.inputs.push({name:d,isPure:a.isPure});a.watchId=c});this.state.computing="fn";this.stage= 255"main";this.recurse(a);a='"'+this.USE+" "+this.STRICT+'";\n'+this.filterPrefix()+"var fn="+this.generateFunction("fn","s,l,a,i")+d+this.watchFns()+"return fn;";a=(new Function("$filter","getStringValue","ifDefined","plus",a))(this.$filter,Tg,Ug,Gd);this.state=this.stage=void 0;return a},USE:"use",STRICT:"strict",watchFns:function(){var a=[],b=this.state.inputs,d=this;r(b,function(b){a.push("var "+b.name+"="+d.generateFunction(b.name,"s"));b.isPure&&a.push(b.name,".isPure="+JSON.stringify(b.isPure)+ 256";")});b.length&&a.push("fn.inputs=["+b.map(function(a){return a.name}).join(",")+"];");return a.join("")},generateFunction:function(a,b){return"function("+b+"){"+this.varsPrefix(a)+this.body(a)+"};"},filterPrefix:function(){var a=[],b=this;r(this.state.filters,function(d,c){a.push(d+"=$filter("+b.escape(c)+")")});return a.length?"var "+a.join(",")+";":""},varsPrefix:function(a){return this.state[a].vars.length?"var "+this.state[a].vars.join(",")+";":""},body:function(a){return this.state[a].body.join("")}, 257recurse:function(a,b,d,c,e,f){var g,k,h=this,l,m,p;c=c||E;if(!f&&w(a.watchId))b=b||this.nextId(),this.if_("i",this.lazyAssign(b,this.computedMember("i",a.watchId)),this.lazyRecurse(a,b,d,c,e,!0));else switch(a.type){case q.Program:r(a.body,function(b,c){h.recurse(b.expression,void 0,void 0,function(a){k=a});c!==a.body.length-1?h.current().body.push(k,";"):h.return_(k)});break;case q.Literal:m=this.escape(a.value);this.assign(b,m);c(b||m);break;case q.UnaryExpression:this.recurse(a.argument,void 0, 258void 0,function(a){k=a});m=a.operator+"("+this.ifDefined(k,0)+")";this.assign(b,m);c(m);break;case q.BinaryExpression:this.recurse(a.left,void 0,void 0,function(a){g=a});this.recurse(a.right,void 0,void 0,function(a){k=a});m="+"===a.operator?this.plus(g,k):"-"===a.operator?this.ifDefined(g,0)+a.operator+this.ifDefined(k,0):"("+g+")"+a.operator+"("+k+")";this.assign(b,m);c(m);break;case q.LogicalExpression:b=b||this.nextId();h.recurse(a.left,b);h.if_("&&"===a.operator?b:h.not(b),h.lazyRecurse(a.right, 259b));c(b);break;case q.ConditionalExpression:b=b||this.nextId();h.recurse(a.test,b);h.if_(b,h.lazyRecurse(a.alternate,b),h.lazyRecurse(a.consequent,b));c(b);break;case q.Identifier:b=b||this.nextId();d&&(d.context="inputs"===h.stage?"s":this.assign(this.nextId(),this.getHasOwnProperty("l",a.name)+"?l:s"),d.computed=!1,d.name=a.name);h.if_("inputs"===h.stage||h.not(h.getHasOwnProperty("l",a.name)),function(){h.if_("inputs"===h.stage||"s",function(){e&&1!==e&&h.if_(h.isNull(h.nonComputedMember("s",a.name)), 260h.lazyAssign(h.nonComputedMember("s",a.name),"{}"));h.assign(b,h.nonComputedMember("s",a.name))})},b&&h.lazyAssign(b,h.nonComputedMember("l",a.name)));c(b);break;case q.MemberExpression:g=d&&(d.context=this.nextId())||this.nextId();b=b||this.nextId();h.recurse(a.object,g,void 0,function(){h.if_(h.notNull(g),function(){a.computed?(k=h.nextId(),h.recurse(a.property,k),h.getStringValue(k),e&&1!==e&&h.if_(h.not(h.computedMember(g,k)),h.lazyAssign(h.computedMember(g,k),"{}")),m=h.computedMember(g,k),h.assign(b, 261m),d&&(d.computed=!0,d.name=k)):(e&&1!==e&&h.if_(h.isNull(h.nonComputedMember(g,a.property.name)),h.lazyAssign(h.nonComputedMember(g,a.property.name),"{}")),m=h.nonComputedMember(g,a.property.name),h.assign(b,m),d&&(d.computed=!1,d.name=a.property.name))},function(){h.assign(b,"undefined")});c(b)},!!e);break;case q.CallExpression:b=b||this.nextId();a.filter?(k=h.filter(a.callee.name),l=[],r(a.arguments,function(a){var b=h.nextId();h.recurse(a,b);l.push(b)}),m=k+"("+l.join(",")+")",h.assign(b,m),c(b)): 262(k=h.nextId(),g={},l=[],h.recurse(a.callee,k,g,function(){h.if_(h.notNull(k),function(){r(a.arguments,function(b){h.recurse(b,a.constant?void 0:h.nextId(),void 0,function(a){l.push(a)})});m=g.name?h.member(g.context,g.name,g.computed)+"("+l.join(",")+")":k+"("+l.join(",")+")";h.assign(b,m)},function(){h.assign(b,"undefined")});c(b)}));break;case q.AssignmentExpression:k=this.nextId();g={};this.recurse(a.left,void 0,g,function(){h.if_(h.notNull(g.context),function(){h.recurse(a.right,k);m=h.member(g.context, 263g.name,g.computed)+a.operator+k;h.assign(b,m);c(b||m)})},1);break;case q.ArrayExpression:l=[];r(a.elements,function(b){h.recurse(b,a.constant?void 0:h.nextId(),void 0,function(a){l.push(a)})});m="["+l.join(",")+"]";this.assign(b,m);c(b||m);break;case q.ObjectExpression:l=[];p=!1;r(a.properties,function(a){a.computed&&(p=!0)});p?(b=b||this.nextId(),this.assign(b,"{}"),r(a.properties,function(a){a.computed?(g=h.nextId(),h.recurse(a.key,g)):g=a.key.type===q.Identifier?a.key.name:""+a.key.value;k=h.nextId(); 264h.recurse(a.value,k);h.assign(h.member(b,g,a.computed),k)})):(r(a.properties,function(b){h.recurse(b.value,a.constant?void 0:h.nextId(),void 0,function(a){l.push(h.escape(b.key.type===q.Identifier?b.key.name:""+b.key.value)+":"+a)})}),m="{"+l.join(",")+"}",this.assign(b,m));c(b||m);break;case q.ThisExpression:this.assign(b,"s");c(b||"s");break;case q.LocalsExpression:this.assign(b,"l");c(b||"l");break;case q.NGValueParameter:this.assign(b,"v"),c(b||"v")}},getHasOwnProperty:function(a,b){var d=a+"."+ 265b,c=this.current().own;c.hasOwnProperty(d)||(c[d]=this.nextId(!1,a+"&&("+this.escape(b)+" in "+a+")"));return c[d]},assign:function(a,b){if(a)return this.current().body.push(a,"=",b,";"),a},filter:function(a){this.state.filters.hasOwnProperty(a)||(this.state.filters[a]=this.nextId(!0));return this.state.filters[a]},ifDefined:function(a,b){return"ifDefined("+a+","+this.escape(b)+")"},plus:function(a,b){return"plus("+a+","+b+")"},return_:function(a){this.current().body.push("return ",a,";")},if_:function(a, 266b,d){if(!0===a)b();else{var c=this.current().body;c.push("if(",a,"){");b();c.push("}");d&&(c.push("else{"),d(),c.push("}"))}},not:function(a){return"!("+a+")"},isNull:function(a){return a+"==null"},notNull:function(a){return a+"!=null"},nonComputedMember:function(a,b){var d=/[^$_a-zA-Z0-9]/g;return/^[$_a-zA-Z][$_a-zA-Z0-9]*$/.test(b)?a+"."+b:a+'["'+b.replace(d,this.stringEscapeFn)+'"]'},computedMember:function(a,b){return a+"["+b+"]"},member:function(a,b,d){return d?this.computedMember(a,b):this.nonComputedMember(a, 267b)},getStringValue:function(a){this.assign(a,"getStringValue("+a+")")},lazyRecurse:function(a,b,d,c,e,f){var g=this;return function(){g.recurse(a,b,d,c,e,f)}},lazyAssign:function(a,b){var d=this;return function(){d.assign(a,b)}},stringEscapeRegex:/[^ a-zA-Z0-9]/g,stringEscapeFn:function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)},escape:function(a){if(C(a))return"'"+a.replace(this.stringEscapeRegex,this.stringEscapeFn)+"'";if(X(a))return a.toString();if(!0===a)return"true";if(!1=== 268a)return"false";if(null===a)return"null";if("undefined"===typeof a)return"undefined";throw Ya("esc");},nextId:function(a,b){var d="v"+this.state.nextId++;a||this.current().vars.push(d+(b?"="+b:""));return d},current:function(){return this.state[this.state.computing]}};Md.prototype={compile:function(a){var b=this;Z(a,b.$filter);var d,c;if(d=Kd(a))c=this.recurse(d);d=Id(a.body);var e;d&&(e=[],r(d,function(a,c){var d=b.recurse(a);d.isPure=a.isPure;a.input=d;e.push(d);a.watchId=c}));var f=[];r(a.body, 269function(a){f.push(b.recurse(a.expression))});a=0===a.body.length?E:1===a.body.length?f[0]:function(a,b){var c;r(f,function(d){c=d(a,b)});return c};c&&(a.assign=function(a,b,d){return c(a,d,b)});e&&(a.inputs=e);return a},recurse:function(a,b,d){var c,e,f=this,g;if(a.input)return this.inputs(a.input,a.watchId);switch(a.type){case q.Literal:return this.value(a.value,b);case q.UnaryExpression:return e=this.recurse(a.argument),this["unary"+a.operator](e,b);case q.BinaryExpression:return c=this.recurse(a.left), 270e=this.recurse(a.right),this["binary"+a.operator](c,e,b);case q.LogicalExpression:return c=this.recurse(a.left),e=this.recurse(a.right),this["binary"+a.operator](c,e,b);case q.ConditionalExpression:return this["ternary?:"](this.recurse(a.test),this.recurse(a.alternate),this.recurse(a.consequent),b);case q.Identifier:return f.identifier(a.name,b,d);case q.MemberExpression:return c=this.recurse(a.object,!1,!!d),a.computed||(e=a.property.name),a.computed&&(e=this.recurse(a.property)),a.computed?this.computedMember(c, 271e,b,d):this.nonComputedMember(c,e,b,d);case q.CallExpression:return g=[],r(a.arguments,function(a){g.push(f.recurse(a))}),a.filter&&(e=this.$filter(a.callee.name)),a.filter||(e=this.recurse(a.callee,!0)),a.filter?function(a,c,d,f){for(var p=[],n=0;n<g.length;++n)p.push(g[n](a,c,d,f));a=e.apply(void 0,p,f);return b?{context:void 0,name:void 0,value:a}:a}:function(a,c,d,f){var p=e(a,c,d,f),n;if(null!=p.value){n=[];for(var s=0;s<g.length;++s)n.push(g[s](a,c,d,f));n=p.value.apply(p.context,n)}return b? 272{value:n}:n};case q.AssignmentExpression:return c=this.recurse(a.left,!0,1),e=this.recurse(a.right),function(a,d,f,g){var p=c(a,d,f,g);a=e(a,d,f,g);p.context[p.name]=a;return b?{value:a}:a};case q.ArrayExpression:return g=[],r(a.elements,function(a){g.push(f.recurse(a))}),function(a,c,d,e){for(var f=[],n=0;n<g.length;++n)f.push(g[n](a,c,d,e));return b?{value:f}:f};case q.ObjectExpression:return g=[],r(a.properties,function(a){a.computed?g.push({key:f.recurse(a.key),computed:!0,value:f.recurse(a.value)}): 273g.push({key:a.key.type===q.Identifier?a.key.name:""+a.key.value,computed:!1,value:f.recurse(a.value)})}),function(a,c,d,e){for(var f={},n=0;n<g.length;++n)g[n].computed?f[g[n].key(a,c,d,e)]=g[n].value(a,c,d,e):f[g[n].key]=g[n].value(a,c,d,e);return b?{value:f}:f};case q.ThisExpression:return function(a){return b?{value:a}:a};case q.LocalsExpression:return function(a,c){return b?{value:c}:c};case q.NGValueParameter:return function(a,c,d){return b?{value:d}:d}}},"unary+":function(a,b){return function(d, 274c,e,f){d=a(d,c,e,f);d=w(d)?+d:0;return b?{value:d}:d}},"unary-":function(a,b){return function(d,c,e,f){d=a(d,c,e,f);d=w(d)?-d:-0;return b?{value:d}:d}},"unary!":function(a,b){return function(d,c,e,f){d=!a(d,c,e,f);return b?{value:d}:d}},"binary+":function(a,b,d){return function(c,e,f,g){var k=a(c,e,f,g);c=b(c,e,f,g);k=Gd(k,c);return d?{value:k}:k}},"binary-":function(a,b,d){return function(c,e,f,g){var k=a(c,e,f,g);c=b(c,e,f,g);k=(w(k)?k:0)-(w(c)?c:0);return d?{value:k}:k}},"binary*":function(a,b, 275d){return function(c,e,f,g){c=a(c,e,f,g)*b(c,e,f,g);return d?{value:c}:c}},"binary/":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)/b(c,e,f,g);return d?{value:c}:c}},"binary%":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)%b(c,e,f,g);return d?{value:c}:c}},"binary===":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)===b(c,e,f,g);return d?{value:c}:c}},"binary!==":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)!==b(c,e,f,g);return d?{value:c}:c}},"binary==":function(a,b,d){return function(c, 276e,f,g){c=a(c,e,f,g)==b(c,e,f,g);return d?{value:c}:c}},"binary!=":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)!=b(c,e,f,g);return d?{value:c}:c}},"binary<":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)<b(c,e,f,g);return d?{value:c}:c}},"binary>":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)>b(c,e,f,g);return d?{value:c}:c}},"binary<=":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)<=b(c,e,f,g);return d?{value:c}:c}},"binary>=":function(a,b,d){return function(c,e,f,g){c= 277a(c,e,f,g)>=b(c,e,f,g);return d?{value:c}:c}},"binary&&":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)&&b(c,e,f,g);return d?{value:c}:c}},"binary||":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)||b(c,e,f,g);return d?{value:c}:c}},"ternary?:":function(a,b,d,c){return function(e,f,g,k){e=a(e,f,g,k)?b(e,f,g,k):d(e,f,g,k);return c?{value:e}:e}},value:function(a,b){return function(){return b?{context:void 0,name:void 0,value:a}:a}},identifier:function(a,b,d){return function(c,e,f,g){c= 278e&&a in e?e:c;d&&1!==d&&c&&null==c[a]&&(c[a]={});e=c?c[a]:void 0;return b?{context:c,name:a,value:e}:e}},computedMember:function(a,b,d,c){return function(e,f,g,k){var h=a(e,f,g,k),l,m;null!=h&&(l=b(e,f,g,k),l+="",c&&1!==c&&h&&!h[l]&&(h[l]={}),m=h[l]);return d?{context:h,name:l,value:m}:m}},nonComputedMember:function(a,b,d,c){return function(e,f,g,k){e=a(e,f,g,k);c&&1!==c&&e&&null==e[b]&&(e[b]={});f=null!=e?e[b]:void 0;return d?{context:e,name:b,value:f}:f}},inputs:function(a,b){return function(d, 279c,e,f){return f?f[b]:a(d,c,e)}}};Nb.prototype={constructor:Nb,parse:function(a){a=this.getAst(a);var b=this.astCompiler.compile(a.ast),d=a.ast;b.literal=0===d.body.length||1===d.body.length&&(d.body[0].expression.type===q.Literal||d.body[0].expression.type===q.ArrayExpression||d.body[0].expression.type===q.ObjectExpression);b.constant=a.ast.constant;b.oneTime=a.oneTime;return b},getAst:function(a){var b=!1;a=a.trim();":"===a.charAt(0)&&":"===a.charAt(1)&&(b=!0,a=a.substring(2));return{ast:this.ast.ast(a), 280oneTime:b}}};var Ea=F("$sce"),W={HTML:"html",CSS:"css",MEDIA_URL:"mediaUrl",URL:"url",RESOURCE_URL:"resourceUrl",JS:"js"},Dc=/_([a-z])/g,Zg=F("$templateRequest"),$g=F("$timeout"),aa=z.document.createElement("a"),Qd=ga(z.location.href),Na;aa.href="http://[::1]";var ah="[::1]"===aa.hostname;Rd.$inject=["$document"];fd.$inject=["$provide"];var Yd=22,Xd=".",Fc="0";Sd.$inject=["$locale"];Ud.$inject=["$locale"];var lh={yyyy:ea("FullYear",4,0,!1,!0),yy:ea("FullYear",2,0,!0,!0),y:ea("FullYear",1,0,!1,!0), 281MMMM:lb("Month"),MMM:lb("Month",!0),MM:ea("Month",2,1),M:ea("Month",1,1),LLLL:lb("Month",!1,!0),dd:ea("Date",2),d:ea("Date",1),HH:ea("Hours",2),H:ea("Hours",1),hh:ea("Hours",2,-12),h:ea("Hours",1,-12),mm:ea("Minutes",2),m:ea("Minutes",1),ss:ea("Seconds",2),s:ea("Seconds",1),sss:ea("Milliseconds",3),EEEE:lb("Day"),EEE:lb("Day",!0),a:function(a,b){return 12>a.getHours()?b.AMPMS[0]:b.AMPMS[1]},Z:function(a,b,d){a=-1*d;return a=(0<=a?"+":"")+(Pb(Math[0<a?"floor":"ceil"](a/60),2)+Pb(Math.abs(a%60),2))}, 282ww:$d(2),w:$d(1),G:Gc,GG:Gc,GGG:Gc,GGGG:function(a,b){return 0>=a.getFullYear()?b.ERANAMES[0]:b.ERANAMES[1]}},kh=/((?:[^yMLdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|m+|s+|a|Z|G+|w+))([\s\S]*)/,jh=/^-?\d+$/;Td.$inject=["$locale"];var eh=ia(K),fh=ia(vb);Vd.$inject=["$parse"];var Re=ia({restrict:"E",compile:function(a,b){if(!b.href&&!b.xlinkHref)return function(a,b){if("a"===b[0].nodeName.toLowerCase()){var e="[object SVGAnimatedString]"===la.call(b.prop("href"))?"xlink:href":"href"; 283b.on("click",function(a){b.attr(e)||a.preventDefault()})}}}}),wb={};r(Hb,function(a,b){function d(a,d,e){a.$watch(e[c],function(a){e.$set(b,!!a)})}if("multiple"!==a){var c=xa("ng-"+b),e=d;"checked"===a&&(e=function(a,b,e){e.ngModel!==e[c]&&d(a,b,e)});wb[c]=function(){return{restrict:"A",priority:100,link:e}}}});r(vd,function(a,b){wb[b]=function(){return{priority:100,link:function(a,c,e){if("ngPattern"===b&&"/"===e.ngPattern.charAt(0)&&(c=e.ngPattern.match(ke))){e.$set("ngPattern",new RegExp(c[1], 284c[2]));return}a.$watch(e[b],function(a){e.$set(b,a)})}}}});r(["src","srcset","href"],function(a){var b=xa("ng-"+a);wb[b]=["$sce",function(d){return{priority:99,link:function(c,e,f){var g=a,k=a;"href"===a&&"[object SVGAnimatedString]"===la.call(e.prop("href"))&&(k="xlinkHref",f.$attr[k]="xlink:href",g=null);f.$set(b,d.getTrustedMediaUrl(f[b]));f.$observe(b,function(b){b?(f.$set(k,b),wa&&g&&e.prop(g,f[k])):"href"===a&&f.$set(k,null)})}}}]});var mb={$addControl:E,$getControls:ia([]),$$renameControl:function(a, 285b){a.$name=b},$removeControl:E,$setValidity:E,$setDirty:E,$setPristine:E,$setSubmitted:E,$$setSubmitted:E};Qb.$inject=["$element","$attrs","$scope","$animate","$interpolate"];Qb.prototype={$rollbackViewValue:function(){r(this.$$controls,function(a){a.$rollbackViewValue()})},$commitViewValue:function(){r(this.$$controls,function(a){a.$commitViewValue()})},$addControl:function(a){Ja(a.$name,"input");this.$$controls.push(a);a.$name&&(this[a.$name]=a);a.$$parentForm=this},$getControls:function(){return ja(this.$$controls)}, 286$$renameControl:function(a,b){var d=a.$name;this[d]===a&&delete this[d];this[b]=a;a.$name=b},$removeControl:function(a){a.$name&&this[a.$name]===a&&delete this[a.$name];r(this.$pending,function(b,d){this.$setValidity(d,null,a)},this);r(this.$error,function(b,d){this.$setValidity(d,null,a)},this);r(this.$$success,function(b,d){this.$setValidity(d,null,a)},this);cb(this.$$controls,a);a.$$parentForm=mb},$setDirty:function(){this.$$animate.removeClass(this.$$element,Za);this.$$animate.addClass(this.$$element, 287Wb);this.$dirty=!0;this.$pristine=!1;this.$$parentForm.$setDirty()},$setPristine:function(){this.$$animate.setClass(this.$$element,Za,Wb+" ng-submitted");this.$dirty=!1;this.$pristine=!0;this.$submitted=!1;r(this.$$controls,function(a){a.$setPristine()})},$setUntouched:function(){r(this.$$controls,function(a){a.$setUntouched()})},$setSubmitted:function(){for(var a=this;a.$$parentForm&&a.$$parentForm!==mb;)a=a.$$parentForm;a.$$setSubmitted()},$$setSubmitted:function(){this.$$animate.addClass(this.$$element, 288"ng-submitted");this.$submitted=!0;r(this.$$controls,function(a){a.$$setSubmitted&&a.$$setSubmitted()})}};ce({clazz:Qb,set:function(a,b,d){var c=a[b];c?-1===c.indexOf(d)&&c.push(d):a[b]=[d]},unset:function(a,b,d){var c=a[b];c&&(cb(c,d),0===c.length&&delete a[b])}});var oe=function(a){return["$timeout","$parse",function(b,d){function c(a){return""===a?d('this[""]').assign:d(a).assign||E}return{name:"form",restrict:a?"EAC":"E",require:["form","^^?form"],controller:Qb,compile:function(d,f){d.addClass(Za).addClass(nb); 289var g=f.name?"name":a&&f.ngForm?"ngForm":!1;return{pre:function(a,d,e,f){var p=f[0];if(!("action"in e)){var n=function(b){a.$apply(function(){p.$commitViewValue();p.$setSubmitted()});b.preventDefault()};d[0].addEventListener("submit",n);d.on("$destroy",function(){b(function(){d[0].removeEventListener("submit",n)},0,!1)})}(f[1]||p.$$parentForm).$addControl(p);var s=g?c(p.$name):E;g&&(s(a,p),e.$observe(g,function(b){p.$name!==b&&(s(a,void 0),p.$$parentForm.$$renameControl(p,b),s=c(p.$name),s(a,p))})); 290d.on("$destroy",function(){p.$$parentForm.$removeControl(p);s(a,void 0);S(p,mb)})}}}}}]},Se=oe(),df=oe(!0),mh=/^\d{4,}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+(?:[+-][0-2]\d:[0-5]\d|Z)$/,xh=/^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i,yh=/^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+(\.[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/, 291nh=/^\s*(-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/,pe=/^(\d{4,})-(\d{2})-(\d{2})$/,qe=/^(\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,Oc=/^(\d{4,})-W(\d\d)$/,re=/^(\d{4,})-(\d\d)$/,se=/^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,ee=T();r(["date","datetime-local","month","time","week"],function(a){ee[a]=!0});var te={text:function(a,b,d,c,e,f){Sa(a,b,d,c,e,f);Ic(c)},date:ob("date",pe,Rb(pe,["yyyy","MM","dd"]),"yyyy-MM-dd"),"datetime-local":ob("datetimelocal",qe,Rb(qe,"yyyy MM dd HH mm ss sss".split(" ")), 292"yyyy-MM-ddTHH:mm:ss.sss"),time:ob("time",se,Rb(se,["HH","mm","ss","sss"]),"HH:mm:ss.sss"),week:ob("week",Oc,function(a,b){if(ha(a))return a;if(C(a)){Oc.lastIndex=0;var d=Oc.exec(a);if(d){var c=+d[1],e=+d[2],f=d=0,g=0,k=0,h=Zd(c),e=7*(e-1);b&&(d=b.getHours(),f=b.getMinutes(),g=b.getSeconds(),k=b.getMilliseconds());return new Date(c,0,h.getDate()+e,d,f,g,k)}}return NaN},"yyyy-Www"),month:ob("month",re,Rb(re,["yyyy","MM"]),"yyyy-MM"),number:function(a,b,d,c,e,f,g,k){Jc(a,b,d,c,"number");fe(c);Sa(a, 293b,d,c,e,f);var h;if(w(d.min)||d.ngMin){var l=d.min||k(d.ngMin)(a);h=na(l);c.$validators.min=function(a,b){return c.$isEmpty(b)||A(h)||b>=h};d.$observe("min",function(a){a!==l&&(h=na(a),l=a,c.$validate())})}if(w(d.max)||d.ngMax){var m=d.max||k(d.ngMax)(a),p=na(m);c.$validators.max=function(a,b){return c.$isEmpty(b)||A(p)||b<=p};d.$observe("max",function(a){a!==m&&(p=na(a),m=a,c.$validate())})}if(w(d.step)||d.ngStep){var n=d.step||k(d.ngStep)(a),s=na(n);c.$validators.step=function(a,b){return c.$isEmpty(b)|| 294A(s)||ge(b,h||0,s)};d.$observe("step",function(a){a!==n&&(s=na(a),n=a,c.$validate())})}},url:function(a,b,d,c,e,f){Sa(a,b,d,c,e,f);Ic(c);c.$validators.url=function(a,b){var d=a||b;return c.$isEmpty(d)||xh.test(d)}},email:function(a,b,d,c,e,f){Sa(a,b,d,c,e,f);Ic(c);c.$validators.email=function(a,b){var d=a||b;return c.$isEmpty(d)||yh.test(d)}},radio:function(a,b,d,c){var e=!d.ngTrim||"false"!==V(d.ngTrim);A(d.name)&&b.attr("name",++qb);b.on("change",function(a){var g;b[0].checked&&(g=d.value,e&&(g= 295V(g)),c.$setViewValue(g,a&&a.type))});c.$render=function(){var a=d.value;e&&(a=V(a));b[0].checked=a===c.$viewValue};d.$observe("value",c.$render)},range:function(a,b,d,c,e,f){function g(a,c){b.attr(a,d[a]);var e=d[a];d.$observe(a,function(a){a!==e&&(e=a,c(a))})}function k(a){p=na(a);Y(c.$modelValue)||(m?(a=b.val(),p>a&&(a=p,b.val(a)),c.$setViewValue(a)):c.$validate())}function h(a){n=na(a);Y(c.$modelValue)||(m?(a=b.val(),n<a&&(b.val(n),a=n<p?p:n),c.$setViewValue(a)):c.$validate())}function l(a){s= 296na(a);Y(c.$modelValue)||(m?c.$viewValue!==b.val()&&c.$setViewValue(b.val()):c.$validate())}Jc(a,b,d,c,"range");fe(c);Sa(a,b,d,c,e,f);var m=c.$$hasNativeValidators&&"range"===b[0].type,p=m?0:void 0,n=m?100:void 0,s=m?1:void 0,r=b[0].validity;a=w(d.min);e=w(d.max);f=w(d.step);var q=c.$render;c.$render=m&&w(r.rangeUnderflow)&&w(r.rangeOverflow)?function(){q();c.$setViewValue(b.val())}:q;a&&(p=na(d.min),c.$validators.min=m?function(){return!0}:function(a,b){return c.$isEmpty(b)||A(p)||b>=p},g("min",k)); 297e&&(n=na(d.max),c.$validators.max=m?function(){return!0}:function(a,b){return c.$isEmpty(b)||A(n)||b<=n},g("max",h));f&&(s=na(d.step),c.$validators.step=m?function(){return!r.stepMismatch}:function(a,b){return c.$isEmpty(b)||A(s)||ge(b,p||0,s)},g("step",l))},checkbox:function(a,b,d,c,e,f,g,k){var h=he(k,a,"ngTrueValue",d.ngTrueValue,!0),l=he(k,a,"ngFalseValue",d.ngFalseValue,!1);b.on("change",function(a){c.$setViewValue(b[0].checked,a&&a.type)});c.$render=function(){b[0].checked=c.$viewValue};c.$isEmpty= 298function(a){return!1===a};c.$formatters.push(function(a){return va(a,h)});c.$parsers.push(function(a){return a?h:l})},hidden:E,button:E,submit:E,reset:E,file:E},$c=["$browser","$sniffer","$filter","$parse",function(a,b,d,c){return{restrict:"E",require:["?ngModel"],link:{pre:function(e,f,g,k){k[0]&&(te[K(g.type)]||te.text)(e,f,g,k[0],b,a,d,c)}}}}],Af=function(){var a={configurable:!0,enumerable:!1,get:function(){return this.getAttribute("value")||""},set:function(a){this.setAttribute("value",a)}}; 299return{restrict:"E",priority:200,compile:function(b,d){if("hidden"===K(d.type))return{pre:function(b,d,f,g){b=d[0];b.parentNode&&b.parentNode.insertBefore(b,b.nextSibling);Object.defineProperty&&Object.defineProperty(b,"value",a)}}}}},zh=/^(true|false|\d+)$/,xf=function(){function a(a,d,c){var e=w(c)?c:9===wa?"":null;a.prop("value",e);d.$set("value",c)}return{restrict:"A",priority:100,compile:function(b,d){return zh.test(d.ngValue)?function(b,d,f){b=b.$eval(f.ngValue);a(d,f,b)}:function(b,d,f){b.$watch(f.ngValue, 300function(b){a(d,f,b)})}}}},We=["$compile",function(a){return{restrict:"AC",compile:function(b){a.$$addBindingClass(b);return function(b,c,e){a.$$addBindingInfo(c,e.ngBind);c=c[0];b.$watch(e.ngBind,function(a){c.textContent=jc(a)})}}}}],Ye=["$interpolate","$compile",function(a,b){return{compile:function(d){b.$$addBindingClass(d);return function(c,d,f){c=a(d.attr(f.$attr.ngBindTemplate));b.$$addBindingInfo(d,c.expressions);d=d[0];f.$observe("ngBindTemplate",function(a){d.textContent=A(a)?"":a})}}}}], 301Xe=["$sce","$parse","$compile",function(a,b,d){return{restrict:"A",compile:function(c,e){var f=b(e.ngBindHtml),g=b(e.ngBindHtml,function(b){return a.valueOf(b)});d.$$addBindingClass(c);return function(b,c,e){d.$$addBindingInfo(c,e.ngBindHtml);b.$watch(g,function(){var d=f(b);c.html(a.getTrustedHtml(d)||"")})}}}}],wf=ia({restrict:"A",require:"ngModel",link:function(a,b,d,c){c.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),Ze=Lc("",!0),af=Lc("Odd",0),$e=Lc("Even",1),bf=Ra({compile:function(a, 302b){b.$set("ngCloak",void 0);a.removeClass("ng-cloak")}}),cf=[function(){return{restrict:"A",scope:!0,controller:"@",priority:500}}],ed={},Ah={blur:!0,focus:!0};r("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "),function(a){var b=xa("ng-"+a);ed[b]=["$parse","$rootScope","$exceptionHandler",function(d,c,e){return sd(d,c,e,b,a,Ah[a])}]});var ff=["$animate","$compile",function(a,b){return{multiElement:!0, 303transclude:"element",priority:600,terminal:!0,restrict:"A",$$tlb:!0,link:function(d,c,e,f,g){var k,h,l;d.$watch(e.ngIf,function(d){d?h||g(function(d,f){h=f;d[d.length++]=b.$$createComment("end ngIf",e.ngIf);k={clone:d};a.enter(d,c.parent(),c)}):(l&&(l.remove(),l=null),h&&(h.$destroy(),h=null),k&&(l=ub(k.clone),a.leave(l).done(function(a){!1!==a&&(l=null)}),k=null))})}}}],gf=["$templateRequest","$anchorScroll","$animate",function(a,b,d){return{restrict:"ECA",priority:400,terminal:!0,transclude:"element", 304controller:ca.noop,compile:function(c,e){var f=e.ngInclude||e.src,g=e.onload||"",k=e.autoscroll;return function(c,e,m,p,n){var r=0,q,t,x,v=function(){t&&(t.remove(),t=null);q&&(q.$destroy(),q=null);x&&(d.leave(x).done(function(a){!1!==a&&(t=null)}),t=x,x=null)};c.$watch(f,function(f){var m=function(a){!1===a||!w(k)||k&&!c.$eval(k)||b()},t=++r;f?(a(f,!0).then(function(a){if(!c.$$destroyed&&t===r){var b=c.$new();p.template=a;a=n(b,function(a){v();d.enter(a,null,e).done(m)});q=b;x=a;q.$emit("$includeContentLoaded", 305f);c.$eval(g)}},function(){c.$$destroyed||t!==r||(v(),c.$emit("$includeContentError",f))}),c.$emit("$includeContentRequested",f)):(v(),p.template=null)})}}}}],zf=["$compile",function(a){return{restrict:"ECA",priority:-400,require:"ngInclude",link:function(b,d,c,e){la.call(d[0]).match(/SVG/)?(d.empty(),a(gd(e.template,z.document).childNodes)(b,function(a){d.append(a)},{futureParentElement:d})):(d.html(e.template),a(d.contents())(b))}}}],hf=Ra({priority:450,compile:function(){return{pre:function(a, 306b,d){a.$eval(d.ngInit)}}}}),vf=function(){return{restrict:"A",priority:100,require:"ngModel",link:function(a,b,d,c){var e=d.ngList||", ",f="false"!==d.ngTrim,g=f?V(e):e;c.$parsers.push(function(a){if(!A(a)){var b=[];a&&r(a.split(g),function(a){a&&b.push(f?V(a):a)});return b}});c.$formatters.push(function(a){if(H(a))return a.join(e)});c.$isEmpty=function(a){return!a||!a.length}}}},nb="ng-valid",be="ng-invalid",Za="ng-pristine",Wb="ng-dirty",pb=F("ngModel");Sb.$inject="$scope $exceptionHandler $attrs $element $parse $animate $timeout $q $interpolate".split(" "); 307Sb.prototype={$$initGetterSetters:function(){if(this.$options.getOption("getterSetter")){var a=this.$$parse(this.$$attr.ngModel+"()"),b=this.$$parse(this.$$attr.ngModel+"($$$p)");this.$$ngModelGet=function(b){var c=this.$$parsedNgModel(b);B(c)&&(c=a(b));return c};this.$$ngModelSet=function(a,c){B(this.$$parsedNgModel(a))?b(a,{$$$p:c}):this.$$parsedNgModelAssign(a,c)}}else if(!this.$$parsedNgModel.assign)throw pb("nonassign",this.$$attr.ngModel,Aa(this.$$element));},$render:E,$isEmpty:function(a){return A(a)|| 308""===a||null===a||a!==a},$$updateEmptyClasses:function(a){this.$isEmpty(a)?(this.$$animate.removeClass(this.$$element,"ng-not-empty"),this.$$animate.addClass(this.$$element,"ng-empty")):(this.$$animate.removeClass(this.$$element,"ng-empty"),this.$$animate.addClass(this.$$element,"ng-not-empty"))},$setPristine:function(){this.$dirty=!1;this.$pristine=!0;this.$$animate.removeClass(this.$$element,Wb);this.$$animate.addClass(this.$$element,Za)},$setDirty:function(){this.$dirty=!0;this.$pristine=!1;this.$$animate.removeClass(this.$$element, 309Za);this.$$animate.addClass(this.$$element,Wb);this.$$parentForm.$setDirty()},$setUntouched:function(){this.$touched=!1;this.$untouched=!0;this.$$animate.setClass(this.$$element,"ng-untouched","ng-touched")},$setTouched:function(){this.$touched=!0;this.$untouched=!1;this.$$animate.setClass(this.$$element,"ng-touched","ng-untouched")},$rollbackViewValue:function(){this.$$timeout.cancel(this.$$pendingDebounce);this.$viewValue=this.$$lastCommittedViewValue;this.$render()},$validate:function(){if(!Y(this.$modelValue)){var a= 310this.$$lastCommittedViewValue,b=this.$$rawModelValue,d=this.$valid,c=this.$modelValue,e=this.$options.getOption("allowInvalid"),f=this;this.$$runValidators(b,a,function(a){e||d===a||(f.$modelValue=a?b:void 0,f.$modelValue!==c&&f.$$writeModelToScope())})}},$$runValidators:function(a,b,d){function c(){var c=!0;r(h.$validators,function(d,e){var g=Boolean(d(a,b));c=c&&g;f(e,g)});return c?!0:(r(h.$asyncValidators,function(a,b){f(b,null)}),!1)}function e(){var c=[],d=!0;r(h.$asyncValidators,function(e, 311g){var h=e(a,b);if(!h||!B(h.then))throw pb("nopromise",h);f(g,void 0);c.push(h.then(function(){f(g,!0)},function(){d=!1;f(g,!1)}))});c.length?h.$$q.all(c).then(function(){g(d)},E):g(!0)}function f(a,b){k===h.$$currentValidationRunId&&h.$setValidity(a,b)}function g(a){k===h.$$currentValidationRunId&&d(a)}this.$$currentValidationRunId++;var k=this.$$currentValidationRunId,h=this;(function(){var a=h.$$parserName;if(A(h.$$parserValid))f(a,null);else return h.$$parserValid||(r(h.$validators,function(a, 312b){f(b,null)}),r(h.$asyncValidators,function(a,b){f(b,null)})),f(a,h.$$parserValid),h.$$parserValid;return!0})()?c()?e():g(!1):g(!1)},$commitViewValue:function(){var a=this.$viewValue;this.$$timeout.cancel(this.$$pendingDebounce);if(this.$$lastCommittedViewValue!==a||""===a&&this.$$hasNativeValidators)this.$$updateEmptyClasses(a),this.$$lastCommittedViewValue=a,this.$pristine&&this.$setDirty(),this.$$parseAndValidate()},$$parseAndValidate:function(){var a=this.$$lastCommittedViewValue,b=this;this.$$parserValid= 313A(a)?void 0:!0;this.$setValidity(this.$$parserName,null);this.$$parserName="parse";if(this.$$parserValid)for(var d=0;d<this.$parsers.length;d++)if(a=this.$parsers[d](a),A(a)){this.$$parserValid=!1;break}Y(this.$modelValue)&&(this.$modelValue=this.$$ngModelGet(this.$$scope));var c=this.$modelValue,e=this.$options.getOption("allowInvalid");this.$$rawModelValue=a;e&&(this.$modelValue=a,b.$modelValue!==c&&b.$$writeModelToScope());this.$$runValidators(a,this.$$lastCommittedViewValue,function(d){e||(b.$modelValue= 314d?a:void 0,b.$modelValue!==c&&b.$$writeModelToScope())})},$$writeModelToScope:function(){this.$$ngModelSet(this.$$scope,this.$modelValue);r(this.$viewChangeListeners,function(a){try{a()}catch(b){this.$$exceptionHandler(b)}},this)},$setViewValue:function(a,b){this.$viewValue=a;this.$options.getOption("updateOnDefault")&&this.$$debounceViewValueCommit(b)},$$debounceViewValueCommit:function(a){var b=this.$options.getOption("debounce");X(b[a])?b=b[a]:X(b["default"])&&-1===this.$options.getOption("updateOn").indexOf(a)? 315b=b["default"]:X(b["*"])&&(b=b["*"]);this.$$timeout.cancel(this.$$pendingDebounce);var d=this;0<b?this.$$pendingDebounce=this.$$timeout(function(){d.$commitViewValue()},b):this.$$rootScope.$$phase?this.$commitViewValue():this.$$scope.$apply(function(){d.$commitViewValue()})},$overrideModelOptions:function(a){this.$options=this.$options.createChild(a);this.$$setUpdateOnEvents()},$processModelValue:function(){var a=this.$$format();this.$viewValue!==a&&(this.$$updateEmptyClasses(a),this.$viewValue=this.$$lastCommittedViewValue= 316a,this.$render(),this.$$runValidators(this.$modelValue,this.$viewValue,E))},$$format:function(){for(var a=this.$formatters,b=a.length,d=this.$modelValue;b--;)d=a[b](d);return d},$$setModelValue:function(a){this.$modelValue=this.$$rawModelValue=a;this.$$parserValid=void 0;this.$processModelValue()},$$setUpdateOnEvents:function(){this.$$updateEvents&&this.$$element.off(this.$$updateEvents,this.$$updateEventHandler);if(this.$$updateEvents=this.$options.getOption("updateOn"))this.$$element.on(this.$$updateEvents, 317this.$$updateEventHandler)},$$updateEventHandler:function(a){this.$$debounceViewValueCommit(a&&a.type)}};ce({clazz:Sb,set:function(a,b){a[b]=!0},unset:function(a,b){delete a[b]}});var uf=["$rootScope",function(a){return{restrict:"A",require:["ngModel","^?form","^?ngModelOptions"],controller:Sb,priority:1,compile:function(b){b.addClass(Za).addClass("ng-untouched").addClass(nb);return{pre:function(a,b,e,f){var g=f[0];b=f[1]||g.$$parentForm;if(f=f[2])g.$options=f.$options;g.$$initGetterSetters();b.$addControl(g); 318e.$observe("name",function(a){g.$name!==a&&g.$$parentForm.$$renameControl(g,a)});a.$on("$destroy",function(){g.$$parentForm.$removeControl(g)})},post:function(b,c,e,f){function g(){k.$setTouched()}var k=f[0];k.$$setUpdateOnEvents();c.on("blur",function(){k.$touched||(a.$$phase?b.$evalAsync(g):b.$apply(g))})}}}}}],Tb,Bh=/(\s+|^)default(\s+|$)/;Mc.prototype={getOption:function(a){return this.$$options[a]},createChild:function(a){var b=!1;a=S({},a);r(a,function(d,c){"$inherit"===d?"*"===c?b=!0:(a[c]= 319this.$$options[c],"updateOn"===c&&(a.updateOnDefault=this.$$options.updateOnDefault)):"updateOn"===c&&(a.updateOnDefault=!1,a[c]=V(d.replace(Bh,function(){a.updateOnDefault=!0;return" "})))},this);b&&(delete a["*"],ie(a,this.$$options));ie(a,Tb.$$options);return new Mc(a)}};Tb=new Mc({updateOn:"",updateOnDefault:!0,debounce:0,getterSetter:!1,allowInvalid:!1,timezone:null});var yf=function(){function a(a,d){this.$$attrs=a;this.$$scope=d}a.$inject=["$attrs","$scope"];a.prototype={$onInit:function(){var a= 320this.parentCtrl?this.parentCtrl.$options:Tb,d=this.$$scope.$eval(this.$$attrs.ngModelOptions);this.$options=a.createChild(d)}};return{restrict:"A",priority:10,require:{parentCtrl:"?^^ngModelOptions"},bindToController:!0,controller:a}},jf=Ra({terminal:!0,priority:1E3}),Ch=F("ngOptions"),Dh=/^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?(?:\s+disable\s+when\s+([\s\S]+?))?\s+for\s+(?:([$\w][$\w]*)|(?:\(\s*([$\w][$\w]*)\s*,\s*([$\w][$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/, 321sf=["$compile","$document","$parse",function(a,b,d){function c(a,b,c){function e(a,b,c,d,f){this.selectValue=a;this.viewValue=b;this.label=c;this.group=d;this.disabled=f}function f(a){var b;if(!r&&za(a))b=a;else{b=[];for(var c in a)a.hasOwnProperty(c)&&"$"!==c.charAt(0)&&b.push(c)}return b}var p=a.match(Dh);if(!p)throw Ch("iexp",a,Aa(b));var n=p[5]||p[7],r=p[6];a=/ as /.test(p[0])&&p[1];var q=p[9];b=d(p[2]?p[1]:n);var t=a&&d(a)||b,w=q&&d(q),v=q?function(a,b){return w(c,b)}:function(a){return La(a)}, 322x=function(a,b){return v(a,B(a,b))},A=d(p[2]||p[1]),y=d(p[3]||""),J=d(p[4]||""),I=d(p[8]),z={},B=r?function(a,b){z[r]=b;z[n]=a;return z}:function(a){z[n]=a;return z};return{trackBy:q,getTrackByValue:x,getWatchables:d(I,function(a){var b=[];a=a||[];for(var d=f(a),e=d.length,g=0;g<e;g++){var k=a===d?g:d[g],l=a[k],k=B(l,k),l=v(l,k);b.push(l);if(p[2]||p[1])l=A(c,k),b.push(l);p[4]&&(k=J(c,k),b.push(k))}return b}),getOptions:function(){for(var a=[],b={},d=I(c)||[],g=f(d),k=g.length,n=0;n<k;n++){var p=d=== 323g?n:g[n],r=B(d[p],p),s=t(c,r),p=v(s,r),w=A(c,r),z=y(c,r),r=J(c,r),s=new e(p,s,w,z,r);a.push(s);b[p]=s}return{items:a,selectValueMap:b,getOptionFromViewValue:function(a){return b[x(a)]},getViewValueFromOption:function(a){return q?Ia(a.viewValue):a.viewValue}}}}}var e=z.document.createElement("option"),f=z.document.createElement("optgroup");return{restrict:"A",terminal:!0,require:["select","ngModel"],link:{pre:function(a,b,c,d){d[0].registerOption=E},post:function(d,k,h,l){function m(a){var b=(a=v.getOptionFromViewValue(a))&& 324a.element;b&&!b.selected&&(b.selected=!0);return a}function p(a,b){a.element=b;b.disabled=a.disabled;a.label!==b.label&&(b.label=a.label,b.textContent=a.label);b.value=a.selectValue}var n=l[0],q=l[1],A=h.multiple;l=0;for(var t=k.children(),z=t.length;l<z;l++)if(""===t[l].value){n.hasEmptyOption=!0;n.emptyOption=t.eq(l);break}k.empty();l=!!n.emptyOption;x(e.cloneNode(!1)).val("?");var v,B=c(h.ngOptions,k,d),C=b[0].createDocumentFragment();n.generateUnknownOptionValue=function(a){return"?"};A?(n.writeValue= 325function(a){if(v){var b=a&&a.map(m)||[];v.items.forEach(function(a){a.element.selected&&-1===Array.prototype.indexOf.call(b,a)&&(a.element.selected=!1)})}},n.readValue=function(){var a=k.val()||[],b=[];r(a,function(a){(a=v.selectValueMap[a])&&!a.disabled&&b.push(v.getViewValueFromOption(a))});return b},B.trackBy&&d.$watchCollection(function(){if(H(q.$viewValue))return q.$viewValue.map(function(a){return B.getTrackByValue(a)})},function(){q.$render()})):(n.writeValue=function(a){if(v){var b=k[0].options[k[0].selectedIndex], 326c=v.getOptionFromViewValue(a);b&&b.removeAttribute("selected");c?(k[0].value!==c.selectValue&&(n.removeUnknownOption(),k[0].value=c.selectValue,c.element.selected=!0),c.element.setAttribute("selected","selected")):n.selectUnknownOrEmptyOption(a)}},n.readValue=function(){var a=v.selectValueMap[k.val()];return a&&!a.disabled?(n.unselectEmptyOption(),n.removeUnknownOption(),v.getViewValueFromOption(a)):null},B.trackBy&&d.$watch(function(){return B.getTrackByValue(q.$viewValue)},function(){q.$render()})); 327l&&(a(n.emptyOption)(d),k.prepend(n.emptyOption),8===n.emptyOption[0].nodeType?(n.hasEmptyOption=!1,n.registerOption=function(a,b){""===b.val()&&(n.hasEmptyOption=!0,n.emptyOption=b,n.emptyOption.removeClass("ng-scope"),q.$render(),b.on("$destroy",function(){var a=n.$isEmptyOptionSelected();n.hasEmptyOption=!1;n.emptyOption=void 0;a&&q.$render()}))}):n.emptyOption.removeClass("ng-scope"));d.$watchCollection(B.getWatchables,function(){var a=v&&n.readValue();if(v)for(var b=v.items.length-1;0<=b;b--){var c= 328v.items[b];w(c.group)?Gb(c.element.parentNode):Gb(c.element)}v=B.getOptions();var d={};v.items.forEach(function(a){var b;if(w(a.group)){b=d[a.group];b||(b=f.cloneNode(!1),C.appendChild(b),b.label=null===a.group?"null":a.group,d[a.group]=b);var c=e.cloneNode(!1);b.appendChild(c);p(a,c)}else b=e.cloneNode(!1),C.appendChild(b),p(a,b)});k[0].appendChild(C);q.$render();q.$isEmpty(a)||(b=n.readValue(),(B.trackBy||A?va(a,b):a===b)||(q.$setViewValue(b),q.$render()))})}}}}],kf=["$locale","$interpolate","$log", 329function(a,b,d){var c=/{}/g,e=/^when(Minus)?(.+)$/;return{link:function(f,g,k){function h(a){g.text(a||"")}var l=k.count,m=k.$attr.when&&g.attr(k.$attr.when),p=k.offset||0,n=f.$eval(m)||{},q={},w=b.startSymbol(),t=b.endSymbol(),x=w+l+"-"+p+t,v=ca.noop,z;r(k,function(a,b){var c=e.exec(b);c&&(c=(c[1]?"-":"")+K(c[2]),n[c]=g.attr(k.$attr[b]))});r(n,function(a,d){q[d]=b(a.replace(c,x))});f.$watch(l,function(b){var c=parseFloat(b),e=Y(c);e||c in n||(c=a.pluralCat(c-p));c===z||e&&Y(z)||(v(),e=q[c],A(e)? 330(null!=b&&d.debug("ngPluralize: no rule defined for '"+c+"' in "+m),v=E,h()):v=f.$watch(e,h),z=c)})}}}],ue=F("ngRef"),lf=["$parse",function(a){return{priority:-1,restrict:"A",compile:function(b,d){var c=xa(ua(b)),e=a(d.ngRef),f=e.assign||function(){throw ue("nonassign",d.ngRef);};return function(a,b,h){var l;if(h.hasOwnProperty("ngRefRead"))if("$element"===h.ngRefRead)l=b;else{if(l=b.data("$"+h.ngRefRead+"Controller"),!l)throw ue("noctrl",h.ngRefRead,d.ngRef);}else l=b.data("$"+c+"Controller");l= 331l||b;f(a,l);b.on("$destroy",function(){e(a)===l&&f(a,null)})}}}}],mf=["$parse","$animate","$compile",function(a,b,d){var c=F("ngRepeat"),e=function(a,b,c,d,e,f,g){a[c]=d;e&&(a[e]=f);a.$index=b;a.$first=0===b;a.$last=b===g-1;a.$middle=!(a.$first||a.$last);a.$odd=!(a.$even=0===(b&1))},f=function(a,b,c){return La(c)},g=function(a,b){return b};return{restrict:"A",multiElement:!0,transclude:"element",priority:1E3,terminal:!0,$$tlb:!0,compile:function(k,h){var l=h.ngRepeat,m=d.$$createComment("end ngRepeat", 332l),p=l.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);if(!p)throw c("iexp",l);var n=p[1],q=p[2],w=p[3],t=p[4],p=n.match(/^(?:(\s*[$\w]+)|\(\s*([$\w]+)\s*,\s*([$\w]+)\s*\))$/);if(!p)throw c("iidexp",n);var x=p[3]||p[1],v=p[2];if(w&&(!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(w)||/^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(w)))throw c("badident",w);var A;if(t){var z={$id:La},y=a(t);A=function(a,b,c,d){v&& 333(z[v]=b);z[x]=c;z.$index=d;return y(a,z)}}return function(a,d,h,k,n){var p=T();a.$watchCollection(q,function(h){var k,q,t=d[0],s,y=T(),B,C,E,D,H,F,K;w&&(a[w]=h);if(za(h))H=h,q=A||f;else for(K in q=A||g,H=[],h)ta.call(h,K)&&"$"!==K.charAt(0)&&H.push(K);B=H.length;K=Array(B);for(k=0;k<B;k++)if(C=h===H?k:H[k],E=h[C],D=q(a,C,E,k),p[D])F=p[D],delete p[D],y[D]=F,K[k]=F;else{if(y[D])throw r(K,function(a){a&&a.scope&&(p[a.id]=a)}),c("dupes",l,D,E);K[k]={id:D,scope:void 0,clone:void 0};y[D]=!0}z&&(z[x]=void 0); 334for(s in p){F=p[s];D=ub(F.clone);b.leave(D);if(D[0].parentNode)for(k=0,q=D.length;k<q;k++)D[k].$$NG_REMOVED=!0;F.scope.$destroy()}for(k=0;k<B;k++)if(C=h===H?k:H[k],E=h[C],F=K[k],F.scope){s=t;do s=s.nextSibling;while(s&&s.$$NG_REMOVED);F.clone[0]!==s&&b.move(ub(F.clone),null,t);t=F.clone[F.clone.length-1];e(F.scope,k,x,E,v,C,B)}else n(function(a,c){F.scope=c;var d=m.cloneNode(!1);a[a.length++]=d;b.enter(a,null,t);t=d;F.clone=a;y[F.id]=F;e(F.scope,k,x,E,v,C,B)});p=y})}}}}],nf=["$animate",function(a){return{restrict:"A", 335multiElement:!0,link:function(b,d,c){b.$watch(c.ngShow,function(b){a[b?"removeClass":"addClass"](d,"ng-hide",{tempClasses:"ng-hide-animate"})})}}}],ef=["$animate",function(a){return{restrict:"A",multiElement:!0,link:function(b,d,c){b.$watch(c.ngHide,function(b){a[b?"addClass":"removeClass"](d,"ng-hide",{tempClasses:"ng-hide-animate"})})}}}],of=Ra(function(a,b,d){a.$watchCollection(d.ngStyle,function(a,d){d&&a!==d&&r(d,function(a,c){b.css(c,"")});a&&b.css(a)})}),pf=["$animate","$compile",function(a, 336b){return{require:"ngSwitch",controller:["$scope",function(){this.cases={}}],link:function(d,c,e,f){var g=[],k=[],h=[],l=[],m=function(a,b){return function(c){!1!==c&&a.splice(b,1)}};d.$watch(e.ngSwitch||e.on,function(c){for(var d,e;h.length;)a.cancel(h.pop());d=0;for(e=l.length;d<e;++d){var q=ub(k[d].clone);l[d].$destroy();(h[d]=a.leave(q)).done(m(h,d))}k.length=0;l.length=0;(g=f.cases["!"+c]||f.cases["?"])&&r(g,function(c){c.transclude(function(d,e){l.push(e);var f=c.element;d[d.length++]=b.$$createComment("end ngSwitchWhen"); 337k.push({clone:d});a.enter(d,f.parent(),f)})})})}}}],qf=Ra({transclude:"element",priority:1200,require:"^ngSwitch",multiElement:!0,link:function(a,b,d,c,e){a=d.ngSwitchWhen.split(d.ngSwitchWhenSeparator).sort().filter(function(a,b,c){return c[b-1]!==a});r(a,function(a){c.cases["!"+a]=c.cases["!"+a]||[];c.cases["!"+a].push({transclude:e,element:b})})}}),rf=Ra({transclude:"element",priority:1200,require:"^ngSwitch",multiElement:!0,link:function(a,b,d,c,e){c.cases["?"]=c.cases["?"]||[];c.cases["?"].push({transclude:e, 338element:b})}}),Eh=F("ngTransclude"),tf=["$compile",function(a){return{restrict:"EAC",compile:function(b){var d=a(b.contents());b.empty();return function(a,b,f,g,k){function h(){d(a,function(a){b.append(a)})}if(!k)throw Eh("orphan",Aa(b));f.ngTransclude===f.$attr.ngTransclude&&(f.ngTransclude="");f=f.ngTransclude||f.ngTranscludeSlot;k(function(a,c){var d;if(d=a.length)a:{d=0;for(var f=a.length;d<f;d++){var g=a[d];if(g.nodeType!==Pa||g.nodeValue.trim()){d=!0;break a}}d=void 0}d?b.append(a):(h(),c.$destroy())}, 339null,f);f&&!k.isSlotFilled(f)&&h()}}}}],Te=["$templateCache",function(a){return{restrict:"E",terminal:!0,compile:function(b,d){"text/ng-template"===d.type&&a.put(d.id,b[0].text)}}}],Fh={$setViewValue:E,$render:E},Gh=["$element","$scope",function(a,b){function d(){g||(g=!0,b.$$postDigest(function(){g=!1;e.ngModelCtrl.$render()}))}function c(a){k||(k=!0,b.$$postDigest(function(){b.$$destroyed||(k=!1,e.ngModelCtrl.$setViewValue(e.readValue()),a&&e.ngModelCtrl.$render())}))}var e=this,f=new Ib;e.selectValueMap= 340{};e.ngModelCtrl=Fh;e.multiple=!1;e.unknownOption=x(z.document.createElement("option"));e.hasEmptyOption=!1;e.emptyOption=void 0;e.renderUnknownOption=function(b){b=e.generateUnknownOptionValue(b);e.unknownOption.val(b);a.prepend(e.unknownOption);Oa(e.unknownOption,!0);a.val(b)};e.updateUnknownOption=function(b){b=e.generateUnknownOptionValue(b);e.unknownOption.val(b);Oa(e.unknownOption,!0);a.val(b)};e.generateUnknownOptionValue=function(a){return"? "+La(a)+" ?"};e.removeUnknownOption=function(){e.unknownOption.parent()&& 341e.unknownOption.remove()};e.selectEmptyOption=function(){e.emptyOption&&(a.val(""),Oa(e.emptyOption,!0))};e.unselectEmptyOption=function(){e.hasEmptyOption&&Oa(e.emptyOption,!1)};b.$on("$destroy",function(){e.renderUnknownOption=E});e.readValue=function(){var b=a.val(),b=b in e.selectValueMap?e.selectValueMap[b]:b;return e.hasOption(b)?b:null};e.writeValue=function(b){var c=a[0].options[a[0].selectedIndex];c&&Oa(x(c),!1);e.hasOption(b)?(e.removeUnknownOption(),c=La(b),a.val(c in e.selectValueMap? 342c:b),Oa(x(a[0].options[a[0].selectedIndex]),!0)):e.selectUnknownOrEmptyOption(b)};e.addOption=function(a,b){if(8!==b[0].nodeType){Ja(a,'"option value"');""===a&&(e.hasEmptyOption=!0,e.emptyOption=b);var c=f.get(a)||0;f.set(a,c+1);d()}};e.removeOption=function(a){var b=f.get(a);b&&(1===b?(f.delete(a),""===a&&(e.hasEmptyOption=!1,e.emptyOption=void 0)):f.set(a,b-1))};e.hasOption=function(a){return!!f.get(a)};e.$hasEmptyOption=function(){return e.hasEmptyOption};e.$isUnknownOptionSelected=function(){return a[0].options[0]=== 343e.unknownOption[0]};e.$isEmptyOptionSelected=function(){return e.hasEmptyOption&&a[0].options[a[0].selectedIndex]===e.emptyOption[0]};e.selectUnknownOrEmptyOption=function(a){null==a&&e.emptyOption?(e.removeUnknownOption(),e.selectEmptyOption()):e.unknownOption.parent().length?e.updateUnknownOption(a):e.renderUnknownOption(a)};var g=!1,k=!1;e.registerOption=function(a,b,f,g,k){if(f.$attr.ngValue){var q,r;f.$observe("value",function(a){var d,f=b.prop("selected");w(r)&&(e.removeOption(q),delete e.selectValueMap[r], 344d=!0);r=La(a);q=a;e.selectValueMap[r]=a;e.addOption(a,b);b.attr("value",r);d&&f&&c()})}else g?f.$observe("value",function(a){e.readValue();var d,f=b.prop("selected");w(q)&&(e.removeOption(q),d=!0);q=a;e.addOption(a,b);d&&f&&c()}):k?a.$watch(k,function(a,d){f.$set("value",a);var g=b.prop("selected");d!==a&&e.removeOption(d);e.addOption(a,b);d&&g&&c()}):e.addOption(f.value,b);f.$observe("disabled",function(a){if("true"===a||a&&b.prop("selected"))e.multiple?c(!0):(e.ngModelCtrl.$setViewValue(null),e.ngModelCtrl.$render())}); 345b.on("$destroy",function(){var a=e.readValue(),b=f.value;e.removeOption(b);d();(e.multiple&&a&&-1!==a.indexOf(b)||a===b)&&c(!0)})}}],Ue=function(){return{restrict:"E",require:["select","?ngModel"],controller:Gh,priority:1,link:{pre:function(a,b,d,c){var e=c[0],f=c[1];if(f){if(e.ngModelCtrl=f,b.on("change",function(){e.removeUnknownOption();a.$apply(function(){f.$setViewValue(e.readValue())})}),d.multiple){e.multiple=!0;e.readValue=function(){var a=[];r(b.find("option"),function(b){b.selected&&!b.disabled&& 346(b=b.value,a.push(b in e.selectValueMap?e.selectValueMap[b]:b))});return a};e.writeValue=function(a){r(b.find("option"),function(b){var c=!!a&&(-1!==Array.prototype.indexOf.call(a,b.value)||-1!==Array.prototype.indexOf.call(a,e.selectValueMap[b.value]));c!==b.selected&&Oa(x(b),c)})};var g,k=NaN;a.$watch(function(){k!==f.$viewValue||va(g,f.$viewValue)||(g=ja(f.$viewValue),f.$render());k=f.$viewValue});f.$isEmpty=function(a){return!a||0===a.length}}}else e.registerOption=E},post:function(a,b,d,c){var e= 347c[1];if(e){var f=c[0];e.$render=function(){f.writeValue(e.$viewValue)}}}}}},Ve=["$interpolate",function(a){return{restrict:"E",priority:100,compile:function(b,d){var c,e;w(d.ngValue)||(w(d.value)?c=a(d.value,!0):(e=a(b.text(),!0))||d.$set("value",b.text()));return function(a,b,d){var h=b.parent();(h=h.data("$selectController")||h.parent().data("$selectController"))&&h.registerOption(a,b,d,c,e)}}}}],bd=["$parse",function(a){return{restrict:"A",require:"?ngModel",link:function(b,d,c,e){if(e){var f= 348c.hasOwnProperty("required")||a(c.ngRequired)(b);c.ngRequired||(c.required=!0);e.$validators.required=function(a,b){return!f||!e.$isEmpty(b)};c.$observe("required",function(a){f!==a&&(f=a,e.$validate())})}}}}],ad=["$parse",function(a){return{restrict:"A",require:"?ngModel",compile:function(b,d){var c,e;d.ngPattern&&(c=d.ngPattern,e="/"===d.ngPattern.charAt(0)&&ke.test(d.ngPattern)?function(){return d.ngPattern}:a(d.ngPattern));return function(a,b,d,h){if(h){var l=d.pattern;d.ngPattern?l=e(a):c=d.pattern; 349var m=je(l,c,b);d.$observe("pattern",function(a){var d=m;m=je(a,c,b);(d&&d.toString())!==(m&&m.toString())&&h.$validate()});h.$validators.pattern=function(a,b){return h.$isEmpty(b)||A(m)||m.test(b)}}}}}}],dd=["$parse",function(a){return{restrict:"A",require:"?ngModel",link:function(b,d,c,e){if(e){var f=c.maxlength||a(c.ngMaxlength)(b),g=Ub(f);c.$observe("maxlength",function(a){f!==a&&(g=Ub(a),f=a,e.$validate())});e.$validators.maxlength=function(a,b){return 0>g||e.$isEmpty(b)||b.length<=g}}}}}],cd= 350["$parse",function(a){return{restrict:"A",require:"?ngModel",link:function(b,d,c,e){if(e){var f=c.minlength||a(c.ngMinlength)(b),g=Ub(f)||-1;c.$observe("minlength",function(a){f!==a&&(g=Ub(a)||-1,f=a,e.$validate())});e.$validators.minlength=function(a,b){return e.$isEmpty(b)||b.length>=g}}}}}];z.angular.bootstrap?z.console&&console.log("WARNING: Tried to load AngularJS more than once."):(Je(),Oe(ca),ca.module("ngLocale",[],["$provide",function(a){function b(a){a+="";var b=a.indexOf(".");return-1== 351b?0:a.length-b-1}a.value("$locale",{DATETIME_FORMATS:{AMPMS:["AM","PM"],DAY:"Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),ERANAMES:["Before Christ","Anno Domini"],ERAS:["BC","AD"],FIRSTDAYOFWEEK:6,MONTH:"January February March April May June July August September October November December".split(" "),SHORTDAY:"Sun Mon Tue Wed Thu Fri Sat".split(" "),SHORTMONTH:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),STANDALONEMONTH:"January February March April May June July August September October November December".split(" "), 352WEEKENDRANGE:[5,6],fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",medium:"MMM d, y h:mm:ss a",mediumDate:"MMM d, y",mediumTime:"h:mm:ss a","short":"M/d/yy h:mm a",shortDate:"M/d/yy",shortTime:"h:mm a"},NUMBER_FORMATS:{CURRENCY_SYM:"$",DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{gSize:3,lgSize:3,maxFrac:3,minFrac:0,minInt:1,negPre:"-",negSuf:"",posPre:"",posSuf:""},{gSize:3,lgSize:3,maxFrac:2,minFrac:2,minInt:1,negPre:"-\u00a4",negSuf:"",posPre:"\u00a4",posSuf:""}]},id:"en-us",localeID:"en_US",pluralCat:function(a, 353c){var e=a|0,f=c;void 0===f&&(f=Math.min(b(a),3));Math.pow(10,f);return 1==e&&0==f?"one":"other"}})}]),x(function(){Ee(z.document,Wc)}))})(window);!window.angular.$$csp().noInlineStyle&&window.angular.element(document.head).prepend(window.angular.element("<style>").text('@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate){display:none !important;}ng\\:form{display:block;}.ng-animate-shim{visibility:hidden;}.ng-anchor{position:absolute;}'));
354 355 356; 357/** include: angular.patch.js **/ 358window.core&&core.registerScript&&core.registerScript('/public/angular/javascripts/angular.patch.js'); 359!function(n){var a=[],r={},u=!1;$("body[ng-app]").length||($(document).ready(function(){angular.element(document).ready(function(){var n;if(r.core){for(;n=a.shift();)r.core.requires.push(n);angular.bootstrap($("body#admin-body")[0],["core"])}u=!0})}),angular.module=function(o,e){return e&&a.push(o),u?angular:r[o]=n.apply(null,arguments)})}(angular.module); 360; 361/** include: angular-cookies.min.js **/ 362window.core&&core.registerScript&&core.registerScript('/public/javascripts/angular-1.8.2/angular-cookies.min.js'); 363/* 364 AngularJS v1.8.2 365 (c) 2010-2020 Google LLC. http://angularjs.org 366 License: MIT 367*/ 368(function(n,e){'use strict';function m(d,k,l){var a=l.baseHref(),h=d[0];return function(f,b,c){var d,g;c=c||{};g=c.expires;d=e.isDefined(c.path)?c.path:a;e.isUndefined(b)&&(g="Thu, 01 Jan 1970 00:00:00 GMT",b="");e.isString(g)&&(g=new Date(g));b=encodeURIComponent(f)+"="+encodeURIComponent(b);b=b+(d?";path="+d:"")+(c.domain?";domain="+c.domain:"");b+=g?";expires="+g.toUTCString():"";b+=c.secure?";secure":"";b+=c.samesite?";samesite="+c.samesite:"";c=b.length+1;4096<c&&k.warn("Cookie '"+f+"' possibly not set or overflowed because it was too large ("+ 369c+" > 4096 bytes)!");h.cookie=b}}e.module("ngCookies",["ng"]).info({angularVersion:"1.8.2"}).provider("$cookies",[function(){var d=this.defaults={};this.$get=["$$cookieReader","$$cookieWriter",function(k,l){return{get:function(a){return k()[a]},getObject:function(a){return(a=this.get(a))?e.fromJson(a):a},getAll:function(){return k()},put:function(a,h,f){l(a,h,f?e.extend({},d,f):d)},putObject:function(a,d,f){this.put(a,e.toJson(d),f)},remove:function(a,h){l(a,void 0,h?e.extend({},d,h):d)}}}]}]);m.$inject= 370["$document","$log","$browser"];e.module("ngCookies").provider("$$cookieWriter",function(){this.$get=m})})(window,window.angular); 371 372 373; 374/** include: angular.page.js **/ 375window.core&&core.registerScript&&core.registerScript('/public/javascripts/angular.page.js'); 376angular.module('page', []) 377.factory('$zIndex', function() { 378 return function(selector) { 379 Math.max(0, Math.max.apply(null, $.map(((selector || "*") === "*")? $.makeArray(document.getElementsByTagName("*")) : $(selector), 380 function (v) { 381 return parseFloat($(v).css("z-index")) || null; 382 } 383 )))+1; 384 } 385}) 386.factory('$at', ['$http', function($http) { 387 var started,queue = []; 388 var $at = function(fn) { 389 if (CORE.AT) { return fn(CORE.AT) } 390 391 queue.push(fn); 392 if (CORE.ATF && !started) { 393 var fn; 394 started = true; 395 $http.post(CORE.ATF).then(function(response) { 396 CORE.AT = response.headers('X-XSRF-TOKEN'); 397 $('input[name="authenticityToken"]').val(CORE.AT); 398 while (fn = queue.shift()) { fn(CORE.AT); } 399 }, function() {}); 400 } 401 } 402 403 CORE.onTokenLoad = function() { 404 for (var fn,i=0;(fn=arguments[i]);i++) $at(fn); 405 } 406 407 return $at; 408}]) 409.factory('$coreConfig', [ '$requester', '$q', function($requester, $q) { 410 let $json = $requester({ get: 'PageFront.getCoreConfig_JSON' }); 411 412 return function() { 413 let deferred = $q.defer(); 414 let keys = []; 415 416 Object.values(arguments).forEach(key => keys.push(key)); 417 418 $json.get({ keys }, function(response) { 419 deferred.resolve(response); 420 }); 421 422 return deferred.promise; 423 } 424}]) 425.factory('$navigation', [ '$requester', '$location', '$debounce', '$rootScope', function($requester, $location, $debounce, $rootScope) { 426 var scopes = { } 427 var byHandle = { } 428 var $json = $requester({ trail: 'PageFront.getTrail_JSON' }); 429 var loaded = true; 430 var cache = {} 431 var install = function(handle, trail) { 432 byHandle[handle] && $.each(byHandle[handle], function(i, result) { 433 result.trail = trail.slice(); 434 result.selected = trail.length && trail[trail.length - 1]; 435 }) 436 } 437 var fetch = function() { 438 var allHandles = []; 439 var fetchHandles = []; 440 var path = $location.path() || $location.absUrl().replace(/^https?:\/\/[^\/]+\//, '/'); 441 442 if (path) { 443 $.each(scopes, function(k, s) { allHandles = allHandles.concat(s.refHandles) }) 444 445 $.each(allHandles, function(i, h) { 446 if ((cache[path] = cache[path] || {})[h]) { 447 install(h, cache[path][h]) 448 } else if (fetchHandles.indexOf(h) < 0) { 449 fetchHandles.push(h); 450 } 451 }) 452 453 loaded && fetchHandles.length && $json.trail({ handles: fetchHandles, currentUrl: path }, function(result){ 454 $.each(result, function(handle, res) { 455 (cache[path] = cache[path] || {})[handle] = res; 456 install(handle, res); 457 }) 458 }) 459 } 460 }.$debounce(100); 461 462 var removeWatcher = $rootScope.$watch(function() { return $location.path(); }, fetch); 463 var watcher = function(key) { return this[key] } 464 var service = function($scope, refHandles) { 465 var result = { } 466 467 if (typeof(refHandles) == 'string') { 468 refHandles = [refHandles]; 469 } 470 scopes[$scope.$id] = scopes[$scope.$id] || { scope: $scope, refHandles: refHandles, result: result } 471 $.each(refHandles, function(i, h) { 472 (byHandle[h] = byHandle[h] || []).push(result); 473 }) 474 $scope.$on('$destroy', function() { delete scopes[$scope.$id]; }) 475 fetch(); 476 477 result.$watch = function(key, fn) { return $scope.$watch(watcher.bind(result, key), fn); } 478 479 return result; 480 } 481 var oldBeforeUnload = window.onbeforeunload; 482 483 window.onbeforeunload = function() { loaded = false; removeWatcher && removeWatcher(); removeWatcher = null; oldBeforeUnload && oldBeforeUnload(); } 484 485 return service; 486}]) 487.factory('$i18n', [ '$requester', '$timeout', '$q', function($requester, $timeout, $q) { 488 var queued = [], i18n = {}; 489 var $json = $requester({ get: 'PageFront.i18n_JSON'}); 490 491 var service = function(str, p) { 492 var i=1,a,result = i18n[str]; 493 494 if (!result) { 495 service.get(str); 496 result = i18n[str] = str; 497 } 498 for (i=1; i<arguments.length; i++) { 499 var undef; 500 if (undef != arguments[i]) { 501 result = result.replace('%s', arguments[i]); 502 } 503 } 504 505 return result; 506 } 507 508 service.promise = function(str) { 509 var q = $q.defer(), originals = {}, original = str; 510 511 str = str.replace(/ +/g, '-') 512 513 if (i18n && i18n[str]) { 514 q.resolve(i18n[str]); 515 } else { 516 if (queued.indexOf(str) < 0) { 517 service.get(str).then(function(result) { 518 var value = result && result[0] && result[0].value || str; 519 520 value = (value!=str)?value:original; 521 522 i18n[str] = value || i18n[str] || str; 523 524 q.resolve(value); 525 }) 526 } 527 } 528 529 return q.promise; 530 } 531 532 var requestTimeoutId, qMap = {}, keyMap = {}, nextMapKey =0; 533 service.get = function() { 534 var q = $q.defer(), keys = [], mapKey = ++nextMapKey; 535 536 qMap[mapKey] = q; 537 538 $.each(arguments, function(i, e) { 539 if (typeof(e) == 'string' && queued.indexOf(e) == -1) { 540 e = (e[e.length-1]=='.') ? e + '*' : e; 541 542 queued.push(e); 543 keyMap[e] = mapKey; 544 } 545 }) 546 547 requestTimeoutId && $timeout.cancel(requestTimeoutId); 548 549 requestTimeoutId = $timeout(function() { 550 var fnIndex, items = queued,fn, key; 551 552 queued = []; 553 var qKeys = Object.keys(qMap); 554 555 if (items.length) $json.get({ i18n: items }, function(response) { 556 var qResults = {} 557 558 $.each(response.i18n, function(k, v) { 559 var qKey = keyMap[k]; 560 561 (qResults[qKey] = qResults[qKey] || []).push({ key: k, value: v }) 562 i18n[k] = v; 563 }) 564 565 while (key = qKeys.pop()) { 566 if (qMap[key]) { 567 qMap[key].resolve(qResults[key] || { key: key, value: key }); 568 569 delete qMap[key]; 570 } 571 } 572 }, null, true); 573 574 }, 50); 575 576 return q.promise; 577 } 578 579 service.set = function(defs) { 580 i18n = i18n||{} 581 $.each(defs, function(key, value) { 582 i18n[key] = value; 583 }) 584 } 585 586 return service; 587}]) 588 589 590.factory('$ajax', ['$rootScope', '$loading', '$at', '$timeout', function($rootScope, $loading, $at, $timeout) { 591 return function(url, data, success, error, background, loader) { 592 var on = { 593 successFN: success, 594 errorFN: error, 595 success: function(fn) { this.successFN = fn; return this; }, 596 error: function(fn) { this.errorFN = fn; return this; } 597 } 598 599 var method = 'POST'; 600 var headers = { 'Cache-Control': 'no-cache' } 601 var cache = false;
602 var installMenu = function(html) { 603 $('#admin-menu-holder').replaceWith($(html)); 604 } 605 606 if ($.isPlainObject(url)) { 607 method = url.method || 'POST'; 608 if (method == 'GET') { 609 delete headers['Cache-Control']; 610 cache = true; 611 } 612 613 url = url.url; 614 } 615 if (CORE && CORE.getters && CORE.getters[url]) url = CORE.getters[url]; 616 617 var ajaxCall = function() { 618 var successFn = function(data, textStatus, jqXHR) { 619 if (!background) $loading.clear(loader); 620 if (jqXHR.getResponseHeader('REQUIRES_AUTH')==='1') return doLogin(); 621 else on.successFN&&on.successFN(data); 622 $rootScope.$digest(); 623 } 624 625 if (!background) $loading(loader); 626 if (method != 'GET'||(data&&CORE&&CORE.requesterLegacyMode)) data.authenticityToken = CORE.AT; 627 628 var sendData = (method == 'GET') ? { body: JSON.stringify(data) } : JSON.stringify(data); 629 630 $.ajax({ cache: cache, headers:headers, contentType: "application/json", type: method, url: url, data: sendData, dataType: 'JSON', 631 success: successFn, 632 error: function(jqXHR, textStatus, errorThrown) { 633 if (textStatus == 'parsererror' && jqXHR.responseText[0] == '{') { 634 try { 635 var data = JSON.parse(jqXHR.responseText); 636 successFn(data, "", jqXHR); 637 return; 638 } catch(e) { } 639 } 640 if (!background) { 641 $loading.clear(); 642 } 643 644 on.errorFN&&on.errorFN(jqXHR, textStatus, errorThrown); 645 if (!on.errorFN) { 646 console.log('XHR Error', textStatus, errorThrown) 647 } 648 $rootScope.$digest(); 649 } 650 }) 651 } 652 653 $at(ajaxCall); 654 655 return on; 656 } 657}]) 658 659.factory('$loading', ['$timeout', '$zIndex', function($timeout, $zIndex) { 660 var depth = 0, stage=0, 661 ele= $('<div class="admin-loading"><img src="/public/images/spinner2.gif" /><div>').appendTo($('body')), 662 cloud = $('<div class="admin-loading-cloud"></div>'), 663 timeout, 664 clearAnimations = function() { ele.stop(true, false); cloud.stop(true, false); timeout = 0; }, 665 defaultLoader = { 666 load: function() { 667 clearAnimations(); 668 cloud.css({ opacity: 0 }); 669 cloud.appendTo($('body')); 670 cloud.css('z-index', $zIndex()); 671 ele.css('z-index', $zIndex()); 672 ele.show(); 673 stage = 0; 674 675 cloud.delay(500).animate({ opacity: .2 }, 500, 'swing'); 676 ele.delay(500).animate({ left: -10 }, 500); 677 }, 678 clear: function() { 679 clearAnimations(); 680 ele.animate( { left: -50 }, 500, 'swing'); 681 cloud.animate({ opacity: 0 }, 300, 'swing', function() { cloud.remove(); ele.css('z-index', 0); } ) 682 }, 683 depth: 0 684 } 685 686 687 688 var result = function(loader) { 689 loader = loader || defaultLoader; 690 loader.depth = loader.depth||0; 691 692 if (loader.depth++) return; 693 694 if (loader && loader.load) loader.load(); 695 } 696 697 var clear = function(loader) { 698 loader = loader || defaultLoader; 699 loader.depth = loader.depth||0; 700 701 if (!loader.depth || (loader.depth = Math.max(loader.depth-1, 0))) return; 702 703 if (loader && loader.clear) loader.clear(); 704 } 705 706 result.setDefaultLoader = function(loader) { 707 defaultLoader = loader || { } 708 709 defaultLoader.load = defaultLoader.load || function() {} 710 defaultLoader.clear = defaultLoader.clear || function() {} 711 defaultLoader.depth = 0; 712 } 713 714 result.clear = function(loader) { $timeout(function() { clear(loader); }); } 715 716 return result; 717}]) 718 719 720.factory('$requester', ['$http', '$rootScope', '$timeout', '$ajax', '$q', function($http, $rootScope, $timeout, $ajax, $q) { 721 var timeoutId, allRoutes = { }, 722 pending = { routes: {}, i18n: [] }, callbacks=[], 723 getRoutes = function(routes, i18n, callback) { 724 var cbKey = callbacks.length; 725 726 callbacks.push(callback); 727 728 for (var i,n;(n=(i18n||[])[i]);
728i++) pending.i18n.push(n); 729 730 routes && $.each(routes, function(key, r) { 731 (pending.routes[r] = pending.routes[r] || []).push({ cbKey: cbKey, key: key }); 732 }) 733 734 timeoutId && $timeout.cancel(timeoutId); 735 timeoutId = $timeout(function() { 736 var routes = {}, params = {}, i18n = pending.i18n, i=0, storedCallbacks = callbacks; 737 738 $.each(pending.routes, function(r, cb) { 739 routes[i] = r; 740 params[i] = cb; 741 i++; 742 }) 743 744 callbacks = []; 745 pending = { routes: {}, i18n: [] }; 746 747 $ajax(CORE.JI, { routes: routes, i18n: i18n }, function(response) { 748 var index=0,i,p,calls = $.map(storedCallbacks, function(fn) { return { fn: fn, routes: { }, methods: { } } }); 749 750 response.i18n && $.each(response.i18n, function(k,v) { CORE.i18n[k] = v; } ); 751 752 $.each(response.routes, function(key, r) { 753 allRoutes[routes[index]] = r; 754 if (CORE.lang && CORE.lang != '') r = '/' + CORE.lang + r; 755 if (params[key]) { 756 for (i=0;(p=params[key][i]);i++) { 757 calls[p.cbKey].routes[p.key] = r; 758 calls[p.cbKey].methods[p.key] = (response.methods) ? response.methods[key] : 'POST'; 759 } 760 } 761 }) 762 763 for (i=0;(p=calls[i]);i++) p.fn({ routes: p.routes, methods: p.methods }); 764 765 }, null, true); 766 767 }) 768 } 769 var counter = 0; 770 var premade = {}, 771 create = function(routes, i18n, onReady) { 772 var service = { }, 773 ready = false, 774 queue = [], 775 readyQueue = [], 776 routeRequest = {}, 777 thisId = ++counter, 778 key, 779 loader, 780 methods = { }, 781 inited; 782 783 onReady && readyQueue.push(onReady); 784 785 var initServices = function(response) { 786 var rq, fn; 787 788 routes = response.routes||[]; 789 methods = response.methods||[] 790 ready = true; 791 792 for (key in response.i18n) { 793 CORE.i18n[key] = response.i18n[key]; 794 } 795 796 while (fn = readyQueue.shift()) { 797 fn(); 798 } 799 800 while (rq = queue.shift()) { 801 callService.apply(this, rq); 802 } 803 804 inited = true; 805 } 806 807 var callService = function(key, params, response, background) { 808 if (!ready) { 809 return queue.push([key, params, response, background]); 810 } 811 812 params = params || {}; 813 814 var route = routes[key]&&$ajax({ url: routes[key], method: methods[key]||'POST'}, params, 0, 0, background, response.loader||loader); 815 816 route && route.success(function() { 817 response.success&&response.success.apply(this, arguments); 818 $rootScope.$digest(); 819 }) 820 821 if (response.failure) 822 route.error(function() { 823 response.failure.apply(this, arguments); 824 $rootScope.$digest(); 825 }); 826 } 827 828 var createService = function(key) { 829 return function(params, callback, background) { 830 var response = { success: callback } 831 832 $timeout(function() { callService(key, params, response, background); }); 833 834 return { 835 onSuccess: function(fn) { response.success = fn; }, 836 onFailure: function(fn) { response.failure = fn; },
837 setLoader: function(loader) { response.loader = loader; } 838 } 839 } 840 } 841 842 var getRoute = function(key) { 843 return routes[key]; 844 } 845 846 var setLoader = function(ldr) { 847 loader = ldr; 848 return this; 849 } 850 851 service.$getRoute = getRoute; 852 service.setLoader = setLoader; 853 854 service.$onReady = service.onReady = function(fn) { if (ready) $timeout(fn); else readyQueue.push(fn); return service; }; 855 856 for (key in routes) { 857 service[key] = createService(key); 858 if (routes[key].indexOf('_JSON') < 0) 859 routes[key] = routes[key]+'_JSON'; 860 } 861 if (!inited) { 862 getRoutes(routes, i18n, initServices); 863 } 864 865 return service; 866 } 867 868 var requesterService = function(routes, i18n, onReady, handle) { 869 if (handle && premade[handle]) { 870 return premade[handle]; 871 } 872 873 var service = create(routes, i18n, onReady, handle); 874 875 if (handle) premade[handle] = service; 876 877 return service; 878 } 879 880 /** 881 * Determines a Play! route given the controller.method pair (e.g 'CorePages.list_JSON') as a jet 882 * It returns a promise, which will deliver the key / route pair in the fullness of time. 883 * 884 * Unlike $json.$getRoute, if the route hasn't already been asked for, this will prompt the router 885 * to go and get it. If the call fails, it will reject the promise, and supply the key in the rejection 886 * @param {type} key 887 * @returns {.$q@call;defer.promise} 888 */ 889 requesterService.getRoute = function(key) { 890 var deferred = $q.defer(); 891 892 if (allRoutes[key]) { 893 var result = {} 894 895 result[key] = allRoutes[key]; 896 897 deferred.resolve(result); 898 } else { 899 var query = {} 900 901 query[key] = key; 902 getRoutes(query, {}, function(routes) { 903 if (routes.routes && routes.routes[key]) { 904 deferred.resolve(routes.routes); 905 } else { 906 deferred.reject(key); 907 } 908 }); 909 } 910 911 return deferred.promise; 912 } 913 914 return requesterService; 915 916}]) 917 918 919/** 920 * @param {type} fn 921 * @returns {undefined} 922 * 923 * Debouncer service leveraging $timeout. Will call the supplied function on a timeout, but if it's used again 924 * before the end of the timeout, cancels the first call and replaces it with the second. 925 * 926 * Usage: 927 * 928 * $debounce(myFn, 1000); // Use with a one second delay. 929 * $debounce(myFn, 'my-timeout-id', 500); // Use with a half second delay, and a custom timeout ID. 930 * $debounce(function() { myStuff(); }, 'my-timeout-id', 500); 931 * 932 * Never do this: 933 * $debounce(function() { myStuff(); }, 500); - as the function is freshly created on each call, $debounce has no way of matching it with previous calls, and will fail to cancel earlier timeouts 934 * 935 */ 936.factory('$debounce', [ '$timeout', function($timeout) { 937 var timeouts = { }, nextId = 0; 938 939 return function(fn) { 940 var timeoutPeriod = arguments[(arguments.length > 2) ? 2 : 1]; 941 var id = (arguments.length == 3) ? arguments[1] : (fn._debounce_id=(fn._debounce_id || ('_db.auto.'+(++nextId)))); 942 943 timeouts[id] && $timeout.cancel(timeouts[id]); 944 timeouts[id] = $timeout(function() { fn(); delete timeouts[id]; }, timeoutPeriod); 945 } 946}]) 947 948.factory('$session', function() { 949 var undef,fn = function(key, value) { 950 if (!window.sessionStorage) return null; 951 var result = window.sessionStorage.getItem(key); 952 953 if (value !== undef) { 954 window.sessionStorage.setItem(key, JSON.stringify({ value: value })); 955 } 956 957 return result && JSON.parse(result).value; 958 } 959 960 fn.remove = function(key) { 961 window.sessionStorage && window.sessionStorage.removeItem(key); 962 } 963 return fn; 964}) 965 966.factory('$data', function() { 967 var values = {}; 968 969 return { 970 set: function(handle, value) { 971 values[handle] = value; 972 }, 973 get: function(handle, defaultValue) { 974 if (typeof(values[handle])=='undefined') return defaultValue; 975 return values[handle]; 976 } 977 } 978}) 979.filter('html', ['$sce', function ($sce) { 980 return function (val) { 981 return $sce.trustAsHtml(val); 982 }; 983}]) 984.directive('filterFn', ['$parse', function($parse) { 985 return { 986 restrict: 'A', 987 required: 'ng-model', 988 link: function($scope, ele, attrs) { 989 var modelGetter = $parse(attrs.ngModel), 990 fnGetter = $parse(attrs.filterFn); 991 992 $scope.$watch(attrs.ngModel, function(val, old) { 993 var undef; 994 995 if (val != undef && val != old) { 996 modelGetter.assign($scope, fnGetter($scope)(val, old)); 997 } 998 }) 999 } 1000 } 1001 1002}]) 1003 1004.directive('ngBindHtmlUnsafe', function() { 1005 return function(scope, element, attr) { 1006 element.addClass('ng-binding').data('$binding', attr.ngBindHtmlUnsafe); 1007 scope.$watch(attr.ngBindHtmlUnsafe, function ngBindHtmlUnsafeWatchAction(value) { 1008 element.html(value || ''); 1009 }); 1010 }; 1011}) 1012 1013/* 1014This directive allows us to pass a function in on an enter key to do what we want. 1015 */ 1016.directive('ngEnter', function () { 1017 return function (scope, element, attrs) { 1018 element.bind("keydown keypress", function (event) { 1019 if(event.which === 13) { 1020 scope.$apply(function (){ 1021 scope.$eval(attrs.ngEnter); 1022 }); 1023 1024 event.preventDefault(); 1025 } 1026 }); 1027 }; 1028}) 1029 1030.directive('simpleTemplate', [ '$compile', '$parse', '$templateCache', function($compile, $parse, $templateCache) { 1031 return { 1032 link: function($scope, ele, attrs) { 1033 var id = $parse(attrs.rugsMenuTemplate)($scope); 1034 var t = $templateCache.get(id); 1035 1036 $compile(t)($scope, function(clone, scope) { ele.append(clone); }); 1037 } 1038 } 1039}]) 1040 1041.directive('ngBindI18n', [ '$i18n', function($i18n) { 1042 return { 1043 link: function($scope, $ele, $attrs) { 1044 $i18n.promise($attrs.ngBindI18n).then(function(str) { 1045 $ele.html(str); 1046 }) 1047 } 1048 } 1049}]) 1050/** 1051 * This is a slightly dirty hack, to get around the fact that a base href of 1052 * /products/ 1053 * will not be happy with a url of
1054 * /products 1055 * 1056 * The Angular guys have marked this bug as "won't fix" - so we're tapping into Things With Which We Shouldn't Mess With. 1057 * To wit: $browser 1058 * 1059 * Watch out for this one - it could easily break in a future Angular release. 1060 */ 1061.config(['$provide', function($provide) { 1062 $provide.decorator('$browser', [ '$delegate', function($delegate) { 1063 var baseHrefFn = $delegate.baseHref; 1064 1065 $delegate.baseHref = function() { 1066 var result = baseHrefFn.apply($delegate, arguments); 1067 var url = $delegate.url().replace(/^(https?:)?\/\/[^/]*(.*)\?.*/, '$2'); 1068 1069 return (url + '/' == result) ? url : result; 1070 } 1071 1072 return $delegate; 1073 }]) 1074}]) 1075 1076/** 1077 * @param {type} fn 1078 * @returns {undefined} 1079 * 1080 * Debouncer service leveraging $timeout. Will call the supplied function on a timeout, but if it's used again 1081 * before the end of the timeout, cancels the first call and replaces it with the second. 1082 * 1083 * Usage: 1084 * 1085 * $debounce(myFn, 1000); // Use with a one second delay. 1086 * $debounce(myFn, 'my-timeout-id', 500); // Use with a half second delay, and a custom timeout ID. 1087 * $debounce(function() { myStuff(); }, 'my-timeout-id', 500); 1088 * $debounce(true, myFn, 1000) // return a debounced version of myFn. Call the result with whatever args you like, and they will be passed to myFn when it runs. 1089 * 1090 * and for the piece de resistence - if you've included the service in a controller or some such, you have this: 1091 * myFunc.$debounce(1000) 1092 * this one applies to *all* functions. 1093 * 1094 * Never do this: 1095 * $debounce(function() { myStuff(); }, 500); - as the function is freshly created on each call, $debounce has no way of matching it with previous calls, and will fail to cancel earlier timeouts 1096 * 1097 */ 1098.factory('$debounce', [ '$timeout', function($timeout) { 1099 var timeouts = { }, nextId = 0; 1100 1101 var makeDebounceId = function(fn) { 1102 return fn._debounce_id = fn._debounce_id || ('_db.auto.'+(++nextId)); 1103 } 1104 1105 var createFunction = function(fn, timeout) { 1106 makeDebounceId(fn); 1107 1108 return function() { 1109 var i,a = []; 1110 1111 for (i=0;i<arguments.length;i++) { 1112 a.push(arguments[i]); 1113 } 1114 1115 service(function() { fn.apply(this, a); }, fn._debounce_id, timeout) 1116 } 1117 } 1118 var service = function(fn) { 1119 if (fn === true) { 1120 var i,args = []; 1121 1122 for (i=1;i<arguments.length;i++) { 1123 args.push(arguments[i]) 1124 } 1125 return createFunction.apply(this, args) 1126 } 1127 var timeoutPeriod = arguments[(arguments.length > 2) ? 2 : 1]; 1128 var id = (typeof(arguments[1]) != 'number') ? arguments[1] : makeDebounceId(fn); 1129 1130 timeouts[id] && $timeout.cancel(timeouts[id]); 1131 timeouts[id] = $timeout(function() { fn(); delete timeouts[id]; }, timeoutPeriod); 1132 } 1133 1134 service.fn = function(fn, a, b) { 1135 return service.bind(service, fn, a, b); 1136 } 1137 1138 Function.prototype.$debounce = function(a) { 1139 return service(true, this, a); 1140 } 1141 1142 return service; 1143}]) 1144$(document).ready((function(a){a.topZIndex=function(b){return Math.max(0,Math.max.apply(null,a.map((b||"*")==="*"?a.makeArray(document.getElementsByTagName("*")):a(b),function(b){return parseFloat(a(b).css("z-index"))||null})))};a.fn.topZIndex=function(b){if(this.length===0)return this;b=a.extend({increment:1},b);var c=a.topZIndex(b.selector),d=b.increment;return this.each(function(){this.style.zIndex=c+=d})}})(jQuery)); 1145 1146; 1147/** include: angular.menus.js **/ 1148window.core&&core.registerScript&&core.registerScript('/public/javascripts/angular.menus.js'); 1149var menus = angular.module('menus', []); 1150 1151menus.service('siteNavigation', function () { 1152 var linkIds = null; 1153 var selected = null; 1154 var selectedWatcher = null; 1155 1156 this.setLinkIds = function (val) { 1157 linkIds = val; 1158 if (angular.isArray(linkIds) && linkIds.length) { 1159 this.setSelected(linkIds[linkIds.length - 1]); 1160 } 1161 }; 1162 1163 this.getLinkIds = function () { 1164 return linkIds; 1165 }; 1166 1167 this.setSelected = function (val) { 1168 selected = val; 1169 (selectedWatcher || angular.noop)(val); 1170 }; 1171
1172 this.getSelected = function () { 1173 return selected; 1174 }; 1175 1176 this.watchSelected = function (fn) { 1177 selectedWatcher = fn; 1178 }; 1179}); 1180 1181menus.controller('mainMenuCTRL', [ 1182 '$timeout', 1183 function ($timeout) { 1184 var vm = this; 1185 1186 var linkIds = []; 1187 1188 vm.getLinkClass = function (id) { 1189 return linkIds.indexOf(id) >= 0 ? 'selected' : ''; 1190 }; 1191 1192 vm.init = function (handle, trail, refHandles, linkCount) { 1193 linkIds = trail.map(function (t) { 1194 return t.id; 1195 }); 1196 1197 //initialise the array to keep track of what submenu's are open 1198 vm.showSubmenu = new Array(linkCount); 1199 }; 1200 1201 vm.toggleBurger = function (show) { 1202 vm.burgerOpen = show || !vm.burgerOpen; 1203 if (vm.burgerOpen) { 1204 console.log(angular.element('nav#wpd-main-navigation-wrapper')); 1205 $timeout(function () { 1206 angular 1207 .element('nav#wpd-main-navigation-wrapper') 1208 .eq(0) 1209 .focus(); 1210 }); 1211 } 1212 if (!show) { 1213 vm.closeSearch(); 1214 } 1215 }; 1216 1217 vm.toggleSearch = function (show) { 1218 vm.searchOpen = show || !vm.searchOpen; 1219 if (vm.searchOpen) { 1220 $timeout(function () { 1221 angular 1222 .element('input#search') 1223 .eq(0) 1224 .focus(); 1225 }); 1226 } 1227 if (!show) { 1228 vm.closeBurger(); 1229 } 1230 }; 1231 1232 vm.closeBurger = function () { 1233 vm.burgerOpen = false; 1234 }; 1235 1236 vm.closeSearch = function () { 1237 vm.searchOpen = false; 1238 }; 1239 1240 vm.burgerOpen = false; 1241 vm.searchOpen = false; 1242 1243 //On mouseover/mouseleave close any toggled submenus 1244 //The toggle is primarily for keyboard users 1245 //mask this functionality for mouse users 1246 //This does not interfere with the css based hover functionality of the submenus 1247 vm.closeAllToggledMenus = function() 1248 { 1249 untoggleSubmenus(-1); 1250 }; 1251 1252 //Boolean array to control toggled visibility of the megamenu submenus 1253 //Additional control for use with the chevrons (down triangles) - default hover behaviour untouched 1254 vm.showSubmenu = []; 1255 1256 //Method for toggling the submenu on the main megamenu 1257 vm.toggleSubmenu = function(linkIndex) 1258 { 1259 //Toggle the boolean 1260 vm.showSubmenu[linkIndex] = !vm.showSubmenu[linkIndex]; 1261 1262 //Belt and braces 1263 //Set all other values to false 1264 //Incase we're jumping to other menu items without nicely untoggling the existing one 1265 untoggleSubmenus(linkIndex); 1266 }; 1267 1268 //Boolean array to control toggled visibility of the megamenu submenus 1269 //Additional control for use with the chevrons (down triangles) - default hover behaviour untouched 1270 vm.hoverSubmenu = function(linkIndex) 1271 { 1272 //This is a hover event - which we do not want to happen on mobile devices 1273 //Problem occurs because IOS translates mouseover events into clicks 1274 //So the user clicks and gets a mouseover event - which is undesireable 1275 if (!iOS()) 1276 { 1277 //Toggle the boolean 1278 vm.showSubmenu[linkIndex] = true; 1279 1280 //Belt and braces 1281 //Set all other values to false 1282 //Incase we're jumping to other menu items without nicely untoggling the existing one 1283 untoggleSubmenus(linkIndex); 1284 } 1285 }; 1286 1287 //Is this an IOS device? 1288 function iOS() { 1289 return [ 1290 'iPad Simulator', 1291 'iPhone Simulator', 1292 'iPod Simulator', 1293 'iPad', 1294 'iPhone', 1295 'iPod' 1296 ].includes(navigator.platform) 1297 // iPad on iOS 13 detection 1298 || (navigator.userAgent.includes("Mac") && "ontouchend" in document) 1299 } 1300 1301 1302 //Untoggle all submenu items except "skipindex". 1303 var untoggleSubmenus = function(skipIndex) 1304 { 1305 var i; 1306 for (i = 0; i < vm.showSubmenu.length; i++) { 1307 if (i !== skipIndex) 1308 { 1309 vm.showSubmenu[i] = false;
1310 } 1311 } 1312 }; 1313 } 1314]); 1315 1316/** 1317 * Sidebar controller that is displayed on each child page 1318 * responsible for setting active item and controlling menu 1319 * visibility 1320 * @author keiranc, lewisl 1321 */ 1322menus.controller('sidebarCTRL', [ 1323 'siteNavigation', 1324 function (siteNavigation) { 1325 var vm = this; 1326 1327 vm.open = false; 1328 1329 /** 1330 * Toggles menu open or closed 1331 */ 1332 vm.toggleOpen = function () { 1333 vm.open = !vm.open; 1334 }; 1335 1336 /** 1337 * Initialiser function called in the view 1338 * @param {String} handle the handle of the navigation menu 1339 * @param {Array} trail list of navigation objects in the trail 1340 */ 1341 vm.init = function (handle, trail) { 1342 siteNavigation.setLinkIds( 1343 trail.map(function (t) { 1344 return t.id; 1345 }) 1346 ); 1347 }; 1348 1349 /** 1350 * Used in view to determine selected link 1351 * @param {*} id the id of the link 1352 */ 1353 vm.getLinkClass = function (id) { 1354 var linkIds = siteNavigation.getLinkIds(); 1355 if (!linkIds.length) return; 1356 if (linkIds[linkIds.length - 1] === id) { 1357 return 'current'; 1358 } 1359 return ''; 1360 }; 1361 } 1362]); 1363 1364/** 1365 * Used to control the 'section hints' at the bottom of content section pages 1366 * @author lewisl 1367 */ 1368menus.controller('sectionHintCTRL', [ 1369 '$timeout', 1370 'siteNavigation', 1371 function ($timeout, siteNavigation) { 1372 var vm = this; 1373 1374 var prevLink, nextLink; 1375 var $ = angular.element; 1376 var sidebarLinks = $('.sidebar-menu li'); 1377 var heading = $('.content-sidebar h2'); 1378 1379 vm.initLinks = function (data, headingData) { 1380 // If we get supplied some data relating to the nav structure 1381 // (i.e. being used as a page.navigation item) then we can 1382 // reconstruct the DOM structure this page hints functionality 1383 // relies on to save us rewriting it (for now...) 1384 if (data && data.length) { 1385 var sidebarEl = $('<ul class="sidebar-menu" />'); 1386 sidebarEl.css({"display": "none"}); 1387 $('body').append(sidebarEl); 1388 data.forEach(function (link) { 1389 var currentLink = $('<li />'); 1390 currentLink.attr('id', 'sidebar-link-' + link.targetId); 1391 currentLink.append('<a href="' + link.fullURL + '">' + link.linkTitle + '</a>'); 1392 sidebarEl.append(currentLink); 1393 1394 if (link.isSelected) { 1395 siteNavigation.setSelected(link.targetId); 1396 } 1397 }); 1398 sidebarLinks = sidebarEl.find('li'); 1399 } 1400 if (headingData) { 1401 var headingEl = $("<h2 />"); 1402 headingEl.css({"display": "none"}); 1403 headingEl.attr('id', 'section-heading-' + headingData.targetId); 1404 if (headingData.isSelected) { 1405 headingEl.addClass('selected'); 1406 } 1407 headingEl.append('<a href="' + headingData.fullURL + '">' + headingData.linkTitle + '</a>'); 1408 $('body').append(headingEl); 1409 heading = headingEl; 1410 } 1411 }; 1412 1413 var getPrevTerm = function (selected) { 1414 var prev = $('#sidebar-link-' + selected).prev(); 1415 if (prev.length === 0) { 1416 return 'Overview'; 1417 } 1418 if (prev.index() === 0) { 1419 return 'First'; 1420 } 1421 return 'Previous'; 1422 }; 1423 1424 var getNextTerm = function (selected) { 1425 if (heading.filter('.selected').length) { 1426 return 'First'; 1427 } 1428 var next = $('#sidebar-link-' + selected).next(); 1429 if (next.index() === sidebarLinks.length - 1) { 1430 return 'Final'; 1431 } 1432 return 'Next'; 1433 }; 1434 1435 var getPrevLink = function (selected) { 1436 if (prevLink) { 1437 return prevLink; 1438 } 1439 1440 var prev = $('#sidebar-link-' + selected).prev(); 1441 1442 if (!prev.length) { 1443 prev = $('.sidebar-menu li.current').prev(); 1444 } 1445 1446 if (prev && prev.length) { 1447 prevLink = { 1448 text: prev.text(), 1449 href: prev.find('a').attr('href') 1450 }; 1451 return prevLink; 1452 } 1453 1454 if (heading.length && !heading.hasClass('selected')) { 1455 var prevElement = heading.eq(0); 1456 prevLink = { 1457 text: prevElement.text(), 1458 href: prevElement.find('a').attr('href') 1459 }; 1460 return prevLink; 1461 } 1462 return false;
1463 }; 1464 1465 var getNextLink = function (selected) { 1466 if (nextLink) { 1467 return nextLink; 1468 } 1469 1470 if (heading.filter('.selected').length) { 1471 var next = sidebarLinks.eq(0); 1472 nextLink = { 1473 text: next.text(), 1474 href: next.find('a').attr('href') 1475 }; 1476 return nextLink; 1477 } 1478 1479 var nextElement = $('#sidebar-link-' + selected).next(); 1480 if (nextElement && nextElement.length) { 1481 nextLink = { 1482 text: nextElement.text(), 1483 href: nextElement.find('a').attr('href') 1484 }; 1485 return nextLink; 1486 } 1487 return false; 1488 }; 1489 1490 siteNavigation.watchSelected(function (selected) { 1491 vm.sectionHeading = 1492 $('[id^=section-heading-]') 1493 .first() 1494 .text() 1495 .trim() || 'this section'; 1496 1497 $timeout(function () { 1498 vm.prevTerm = getPrevTerm(selected); 1499 vm.nextTerm = getNextTerm(selected); 1500 vm.prevLink = getPrevLink(selected); 1501 vm.nextLink = getNextLink(selected); 1502 }, 0); 1503 1504 }); 1505 } 1506]); 1507 1508menus.controller('socialIconsCtrl', [ 1509 '$scope', '$templateCache', function ($scope, $templateCache) { 1510 $scope.openDialog = function ($event) { 1511 $event.preventDefault(); 1512 1513 // Gets the template from the templateCache. 1514 var template = $templateCache.get('whatsapp-dialog-template.html'); 1515 1516 // Checks if the template is legit and no broken, or empty, or null, or undefined, 1517 // I think you got the point :D Leave now, have a great day. 1518 if (!template) { 1519 console.error('Something got wrong with the template "whatsapp-dialog-template.html". Keep calm and... exterminate, exterminate...'); 1520 1521 return; 1522 } 1523 1524 // Opens the dialog, it's that simple. 1525 vex.dialog.open({ 1526 input: template, 1527 contentClassName: 'whatsapp-dialog', 1528 buttons: [], 1529 showCloseButton: true 1530 }); 1531 }; 1532 } 1533]); 1534 1535; 1536/** include: angular.wpd-cookie-notice.js **/ 1537window.core&&core.registerScript&&core.registerScript('/public/javascripts/angular.wpd-cookie-notice.js'); 1538/** 1539 * AngularJS Cookie notice module 1540 * @author keiranc 1541 */ 1542var wpdCookieNotice = angular.module('wpd-cookie-notice', ['ngCookies']); 1543 1544wpdCookieNotice.service('CookieNotice', [ 1545 '$cookies', 1546 '$rootScope', 1547 function($cookies, $rootScope) { 1548 /** 1549 * Gets the current cookie level cookie 1550 * @param {String} cookieName the name of the cookie 1551 * @returns {Object} the current cookie 1552 */ 1553 this.getCurrentCookie = function(cookieName) { 1554 return $cookies.getObject(cookieName); 1555 }; 1556 1557 /** 1558 * Gets the allowed cookie level 1559 * @param {String} cookieName the name of the cookie 1560 * @returns {Number} the current cookie level 1561 */ 1562 this.getAllowedCookieLevel = function(cookieName) { 1563 var currentCookie = this.getCurrentCookie(cookieName); 1564 return currentCookie ? currentCookie : 0; 1565 }; 1566 1567 /** 1568 * Checks whether a certain level is allowed 1569 * @param {String} cookieName the name of the cookie 1570 * @param {Number} level the level to match 1571 * @returns {Boolean} whether that level is allowed 1572 */ 1573 this.isAllowedAt = function(cookieName, level) { 1574 var cookie = this.getCurrentCookie(cookieName); 1575 if (cookie === null || !angular.isDefined(cookie)) return false; 1576 return parseInt(cookie) >= level; 1577 }; 1578 1579 /** 1580 * Accepts a certain level of cookies 1581 * @param {String} cookieName the name of the cookie 1582 * @param {Number} level level to accept 1583 * @param {String} domain the domain to set the cookie for 1584 * @param {Date|undefined} expires 1585 */ 1586 this.accept = function(cookieName, level, domain, expires) { 1587 if (expires === undefined) { 1588 expires = new Date(); 1589 expires.setFullYear(expires.getFullYear() + 1); 1590 } 1591 1592 $cookies.putObject(cookieName, level, { 1593 path: '/', 1594 domain: domain, 1595 expires: expires 1596 }); 1597 $rootScope.$broadcast('cookie.notice.accepted'); 1598 }; 1599 } 1600]); 1601 1602wpdCookieNotice.controller('CookieNoticeController', [ 1603 'CookieNotice', 1604 '$scope', 1605 function(CookieNotice, $scope) { 1606 var vm = this; 1607 1608 vm.visible = false; 1609 1610 /** 1611 * Called by angular button click 1612 * uses CookieNotice service to accept cookie notice 1613 * @param {Number} level the level to accept 1614 */ 1615 vm.accept = function() { 1616 vm.visible = false; 1617 1618 if (vm.disableautohide) { 1619 CookieNotice.accept(vm.key, vm.level, vm.domain, vm.expires); 1620 } 1621 }; 1622 1623 vm.checkAll = function() { 1624 for(key in vm.ticked) { 1625 1626 vm.ticked[key] = true; 1627 } 1628 1629 this.accept(); 1630 1631 }; 1632 1633 // accept default level 1634 vm.init = function() { 1635 if (!vm.cmsMode && !CookieNotice.isAllowedAt(vm.key, vm.level)) { 1636 vm.visible = true; 1637 } 1638 1639 if (isNaN(vm.expires)) { 1640 vm.expires = undefined; 1641 } else { 1642 var expiresDate = new Date(); 1643 expiresDate.setHours(expiresDate.getHours() + vm.expires); 1644 1645 vm.expires = expiresDate; 1646 } 1647 1648 if (!vm.disableautohide) { 1649 CookieNotice.accept(vm.key, vm.level, vm.domain, vm.expires); 1650 } 1651 }; 1652 1653 // broadcast event when visibility changes 1654 $scope.$watch('vm.visible', function () { 1655 $scope.$emit('header.height.changed'); 1656 }); 1657 } 1658]); 1659 1660wpdCookieNotice.directive('wpdCookieNotice', [ 1661 function() { 1662 return { 1663 scope: { 1664 domain: '@', 1665 level: '<', 1666 key: '@', 1667 expires: '<?', 1668 disableautohide: '<?', 1669 cmsMode: '=mode' 1670 }, 1671 controllerAs: 'vm', 1672 bindToController: true, 1673 controller: 'CookieNoticeController', 1674 transclude: true, 1675 template: 1676 '<div data-ng-show="vm.visible" data-ng-init="vm.init(
1676)">\ 1677 <ng-transclude></ng-transclude> \ 1678 </div>' 1679 }; 1680 } 1681]); 1682; 1683/** include: angular.wpdrecitemeReciteMe.js **/ 1684window.core&&core.registerScript&&core.registerScript('/public/javascripts/angular.wpdrecitemeReciteMe.js'); 1685/** 1686 * There's still some cur 1687 * 1688 * AngularJS recite-me module 1689 * @author keiranc, noahm 1690 */ 1691var wpdrecitemeReciteMeModule = angular.module('wpdrecitemeReciteMe', ['ngCookies']); 1692 1693// this is a provider and not a config as when 1694// this file is executed, Recite js has not loaded 1695// but when this provider is initialized, Recite has 1696wpdrecitemeReciteMeModule.provider('wpdrecitemeReciteMeConfig', [ 1697 function() { 1698 var storageEngine = window.Recite ? window.Recite.Storage.Api : {}; // cannot inject $window, hence use of window 1699 return { 1700 $get: function() { 1701 return { 1702 autoEnable: false, 1703 Tracker: { 1704 Ga: { 1705 enabled: true 1706 } 1707 }, 1708 Preferences: { 1709 storageEngine: storageEngine 1710 }, 1711 Parser: { 1712 Html: { 1713 skipElements: [ 1714 'INPUT', 1715 'SCRIPT', 1716 'STYLE', 1717 'SELECT', 1718 'OPTION', 1719 'IMG', 1720 'FRAMESET', 1721 'FRAME', 1722 'IFRAME', 1723 'NOSCRIPT', 1724 'TEXTAREA' 1725 ] 1726 } 1727 }, 1728 Language: { 1729 Translate: { 1730 disallowedTags: [ 1731 'SCRIPT', 1732 'STYLE', 1733 'INPUT', 1734 'TEXTAREA', 1735 'SELECT' 1736 ] 1737 } 1738 } 1739 }; 1740 } 1741 }; 1742 } 1743]); 1744 1745// directive for any object that wants access to ReciteMeLangController 1746wpdrecitemeReciteMeModule.directive('wpdrecitemeReciteMeToolbarButton', [ 1747 function() { 1748 return { 1749 restrict: 'A', 1750 scope: true, // good to know: you cannot have an isolate scope bound to a directive which does not have a template or templateUrl! 1751 controllerAs: 'vm', 1752 controller: [ 1753 'wpdrecitemeReciteMeInitService', 1754 '$window', 1755 '$cookies', 1756 function(ReciteMeInitService, $window, $cookies) { 1757 var vm = this; 1758 1759 /** 1760 * @return {undefined} 1761 */ 1762 vm.enableToolbar = function() { 1763 ReciteMeInitService.init().then(() => { 1764 if ($window.Recite) { 1765 // FYI this calls Recite.Language.Translate.init() too 1766 $window.Recite.enable(); 1767 } 1768 }); 1769 }; 1770 1771 // do toolbar enable straight away if our cookies say so 1772 var recitePersist = $cookies.get("Recite.Persist"); 1773 if (recitePersist == "true"){ 1774 vm.enableToolbar(); 1775 } 1776 } 1777 ] 1778 }; 1779 } 1780]); 1781 1782// directive for a language changer button 1783wpdrecitemeReciteMeModule.directive('wpdrecitemeReciteMeLanguageButton', [ 1784 function() { 1785 return { 1786 restrict: 'A', 1787 scope: true, 1788 controllerAs: 'vm', 1789 controller: [ 1790 'wpdrecitemeReciteMe_RECITE_ME_LANGUAGES', 1791 'wpdrecitemeReciteMeInitService', 1792 'wpdrecitemeReciteMeLanguageService', 1793 '$timeout', 1794 '$cookies', 1795 function(RECITE_ME_LANGUAGES, ReciteMeInitService, ReciteMeLanguageService, $timeout, $cookies) { 1796 var vm = this; 1797 1798 /** 1799 * Previously `vm.getNextLanguage` 1800 * 1801 * @return {string} - the name of the next language to be switched to 1802 */ 1803 vm.getNextLanguageName = function() { 1804 if (!ReciteMeLanguageService.currentLanguageIsEnglish()) { 1805 return RECITE_ME_LANGUAGES['en'].name; 1806 } else { 1807 return RECITE_ME_LANGUAGES.cy.name; 1808 } 1809 }; 1810 1811 /** 1812 * @return {undefined} 1813 */ 1814 vm.switchLanguage = function() { 1815 ReciteMeInitService.init().then(() => { 1816 $timeout(function() { 1817 ReciteMeLanguageService.switchLanguage(); 1818 }); 1819 }); 1820 }; 1821 1822 // do change to Welsh straight away if our cookies say so 1823 var welshPersist = $cookies.get("Recite.Welsh"); 1824 if (welshPersist == "true"){ 1825 ReciteMeInitService.init(); 1826 } 1827 } 1828 ] 1829 }; 1830 } 1831]); 1832 1833/** 1834* @constant 1835*/ 1836wpdrecitemeReciteMeModule.constant('wpdrecitemeReciteMe_RECITE_ME_LANGUAGES', { 1837 'en': { 1838 code: 'en', 1839 name: 'English' 1840 }, 1841 'cy': { 1842 code: 'cy', 1843 name: 'Cymraeg' 1844 } 1845}); 1846 1847/** 1848* @constant 1849*/ 1850wpdrecitemeReciteMeModule.constant('wpdrecitemeReciteMe_RECITE_ME_ENDPOINT', "https://api.reciteme.com/asset/js?key="); 1851 1852/** 1853* @service 1854* 1855* A service concerned with managing the current language being used in the page 1856* @author kieranc, noahm 1857*/ 1858wpdrecitemeReciteMeModule.service('wpdrecitemeReciteMeLanguageService', [ 1859 'wpdrecitemeReciteMeConfig', 1860 'wpdrecitemeReciteMe_RECITE_ME_LANGUAGES', 1861 'wpdrecitemeReciteMe_RECITE_ME_ENDPOINT', 1862 '$rootScope', 1863 '$timeout', 1864 '$window', 1865 '$cookies', 1866 function(RECITE_ME_CONFIG, RECITE_ME_LANGUAGES, RECITE_ME_ENDPOINT, $rootScope, $timeout, $window, $cookies) { 1867 var service = this; 1868 1869 // @private 1870 var _currentLanguage = RECITE_ME_LANGUAGES['en']; 1871 1872 /** 1873 * @param {object} o 1874 * @param {boolean} andApply - if true will also apply this language setting to the ReciteMe preferences 1875 * @return {undefined} 1876 */ 1877 service.setCurrentLanguage = function(o, andApply) { 1878 _currentLanguage = o && typeof o === 'object' && o.code ? o : RECITE_ME_LANGUAGES['en']; 1879 if (andApply) { 1880 if ($window.Recite) { 1881 var newCode = _currentLanguage.code || 'en'; 1882 $window.Recite.Preferences.set('language', newCode); 1883 } 1884 else { 1885 console.warn("Could not apply the language preference because window.Recite is unavailable"); 1886 } 1887 } 1888 }; 1889 1890 /** 1891 * @return {object} 1892 */ 1893 service.getCurrentLanguage = function() { 1894 return _currentLanguage; 1895 }; 1896 1897 /** 1898 * @return {boolean} - true if the `_currentLanguage` is an English variant (en, en-gb, en-us, etc.) 1899 */ 1900 service.currentLanguageIsEnglish = function() { 1901 return _currentLanguage.code && _currentLanguage.code.startsWith('en'); 1902 }; 1903 1904 /** 1905 * Triggers a translation of all the text in the page to the specified language, also setting a cookie indicating if the 1906 * new language is Welsh (so that this may trigger an automatic translate on page navigation/refresh) 1907 * 1908 * @author kieranc, noahm 1909 * 1910 * @param {string} language - the language code for the language you wish to translate the page to 1911 * @return {undefined} 1912 */ 1913 service.translate = function(language) { 1914 if ($window.Recite) { 1915 if (_currentLanguage.code === language) return; 1916 1917 $window.Recite.Storage.Cookie.save("Recite.Welsh", language === 'cy', 365); 1918 1919 service.setCurrentLanguage({ code: language }, true); 1920 $window.Recite.Language.Translate.translate(document.body, language); 1921 } 1922 else { 1923 console.warn('window.Recite is not loaded!'); 1924 } 1925 }; 1926 1927 /** 1928 * Changes over the `_currentLanguage` from English to Welsh, and then triggers a translation of the page 1929 * 1930 * @author kieranc 1931 * 1932 * @return {undefined} 1933 */ 1934 service.switchLanguage = function() { 1935 if ($window.Recite) { 1936 var newCode = RECITE_ME_LANGUAGES['en'].code; 1937 1938 if (service.currentLanguageIsEnglish()) { 1939 newCode = RECITE_ME_LANGUAGES['cy'].code; 1940 } 1941 1942 // translate will update _currentLanguage and the ReciteMe preferences 1943 service.translate(newCode); 1944 } 1945 else { 1946 console.warn('window.Recite is not loaded!'); 1947 } 1948 } 1949 } 1950]); 1951 1952/** 1953* @service 1954* 1955* A service concerned with the initial one-time load and setup of the ReciteMe script 1956* @author kieranc, noahm 1957*/ 1958wpdrecitemeReciteMeModule.service('wpdrecitemeReciteMeInitService', [ 1959 'wpdrecitemeReciteMeConfig', 1960 'wpdrecitemeReciteMe_RECITE_ME_LANGUAGES', 1961 'wpdrecitemeReciteMe_RECITE_ME_ENDPOINT', 1962 'wpdrecitemeReciteMeLanguageService', 1963 '$rootScope', 1964 '$timeout', 1965 '$window', 1966 '$cookies', 1967 function(RECITE_ME_CONFIG, RECITE_ME_LANGUAGES, RECITE_ME_ENDPOINT, ReciteMeLanguageService, $rootScope, $timeout, $window, $cookies) { 1968 var service = this; 1969 1970 // @private 1971 var _inited = false; 1972 1973 const RECITE_ME_INITED = 1; 1974 const RECITE_ME_INITING = 2; // purpose of this is to prevent any other init calls from occurring once the first init call has started. 1975 1976 /** 1977 * Handy: https://www.educative.io/answers/how-to-dynamically-load-a-js-file-in-javascript 1978 * 1979 * @private 1980 * @param {String} key - the ReciteMe API key 1981 * @param {String} version - the particular version of the ReciteMe script to try and load 1982 * @return {Promise} - a Promise in which the resolve will occur when the ReciteMe script has 1983 * loaded successfully. Reject will occur if there is an error in loading 1984 * the script. 1985 */ 1986 function _addReciteMeScript(key, version) { 1987 return new Promise((resolve, reject) => { 1988 let reciteEndpoint = RECITE_ME_ENDPOINT + key; 1989 1990 // do not attempt to load multiple ReciteMe scripts into the page, even if they differ in version 1991 if (document.querySelector(`[src*="${reciteEndpoint}"]`)) { 1992 console.warn("Will not load multiple ReciteMe scripts into the page"); 1993 resolve(); 1994 } 1995 1996 if (version) reciteEndpoint += ('&v=' + version); 1997 1998 let scriptElem = document.createElement('script'); 1999 scriptElem.setAttribute("async", true); 2000 scriptElem.setAttribute("type", "text/javascript"); 2001 scriptElem.setAttribute("src", reciteEndpoint); 2002 2003 scriptElem.addEventListener("load", (e) => resolve({event: e})); 2004 scriptElem.addEventListener("error", (e) => reject({event: e})); 2005 2006 document.body.appendChild(scriptElem); 2007 }); 2008 }; 2009 2010 /** 2011 * Main loading/initialization function in which it will asynchronously load 2012 * the ReciteMe script into the page from the ReciteMe API, and then perform 2013 * some one-time setup stuff. 2014 * 2015 * @private 2016 * @async 2017 * @return {Promise} 2018 */ 2019 async function _init() { 2020 if (_inited) return; 2021 2022 if (!$window.WPDReciteMe) { 2023 console.warn("window.WPDReciteMe is not available. Are you sure it's been loaded?"); 2024 return; 2025 } 2026 2027 _inited = RECITE_ME_INITING; 2028 2029 // technically I think this should be part of the module configuration via a service, but with Alpine 2030 // now being the successor in 2.4, I don't think we care too much...
2031 const key = $window.WPDReciteMe.key; 2032 const version = $window.WPDReciteMe.version; 2033 2034 try { 2035 await _addReciteMeScript(key, version); 2036 2037 if (!$window.Recite) { 2038 console.warn('ReciteMe is not loaded!'); 2039 return; 2040 } 2041 2042 // let us use the proper storageEngine 2043 RECITE_ME_CONFIG.Preferences.storageEngine = $window.Recite.Storage.Api; 2044 $window.Recite.load(RECITE_ME_CONFIG); 2045 2046 $window.Recite.Event.subscribe('Recite:ModuleEvent', function() { 2047 angular.element($window).trigger('wpd.resize'); 2048 $rootScope.$broadcast('header.height.changed'); 2049 }); 2050 2051 $window.Recite.Event.subscribe('Recite:enable', function() { 2052 $window.Recite.Preferences.load(); 2053 $rootScope.$broadcast('header.height.changed'); 2054 }); 2055 2056 $window.Recite.Event.subscribe('Preferences:load', function() { 2057 $rootScope.$broadcast('header.height.changed'); 2058 }); 2059 2060 angular 2061 .element(document.body) 2062 .on('click', '.recite-close', function(e) { 2063 e.preventDefault(); 2064 if ($window.Recite && $window.Recite.disable) { 2065 $window.Recite.disable(); 2066 } 2067 }) 2068 .on('click', '.recite-sprite-button-back', function(e) { 2069 e.preventDefault(); 2070 if ( 2071 $window.Recite && 2072 $window.Recite.PlayerControls && 2073 $window.Recite.PlayerControls.previous 2074 ) { 2075 $window.Recite.PlayerControls.previous(); 2076 } 2077 }) 2078 .on('click', '.recite', function(e) { 2079 e.preventDefault(); 2080 2081 }) 2082 .on('click', '.recite-sprite-button-reset', function(e) { 2083 e.preventDefault(); 2084 $window.Recite.Storage.Cookie.save("Recite.Welsh",!1,365) 2085 }) 2086 .on('click', '.recite-sprite-button-forward', function(e) { 2087 e.preventDefault(); 2088 if ( 2089 $window.Recite && 2090 $window.Recite.PlayerControls && 2091 $window.Recite.PlayerControls.next 2092 ) { 2093 $window.Recite.PlayerControls.next(); 2094 } 2095 }); 2096 2097 _inited = RECITE_ME_INITED; 2098 } 2099 catch (e) { 2100 console.error("Couldn't load the ReciteMe script..."); 2101 _inited = false; 2102 } 2103 } 2104 2105 /** 2106 * @return {boolean|int} 2107 */ 2108 service.isInited = function() { 2109 return _inited; 2110 }; 2111 2112 /** 2113 * @see _init 2114 */ 2115 service.init = _init; 2116 } 2117]); 2118 2119/** 2120* @run 2121* 2122*/ 2123wpdrecitemeReciteMeModule.run([ 2124 '$window', 2125 '$cookies', 2126 '$timeout', 2127 'wpdrecitemeReciteMeLanguageService', 2128 'wpdrecitemeReciteMeInitService', 2129 'wpdrecitemeReciteMe_RECITE_ME_LANGUAGES', 2130 function($window, $cookies, $timeout, ReciteMeLanguageService, ReciteMeInitService, RECITE_ME_LANGUAGES) { 2131 2132 // If redirected from welsh domain or Recite.Welsh cookie is set to true. 2133 var currentUrl = $window.location.href; 2134 var langParam = /lang=([^&]+)/.exec(currentUrl); 2135 var welshPersist = $cookies.get("Recite.Welsh"); 2136 if (welshPersist == "true"){ 2137 langParam = ["0","cy"]; 2138 } 2139 if ((langParam != null && langParam.length === 2) || welshPersist == "true") { 2140 langParamValue = langParam[1]; 2141 if (langParamValue == RECITE_ME_LANGUAGES['cy'].code) { 2142 ReciteMeInitService.init().then(() => { 2143 $timeout(function() { 2144 ReciteMeLanguageService.translate(langParamValue); 2145 }); 2146 }); 2147 } 2148 } 2149 } 2150]); 2151; 2152/** include: angular.wpd.js **/ 2153window.core&&core.registerScript&&core.registerScript('/public/javascripts/angular.wpd.js'); 2154/** 2155 * @author keiranc 2156 * global angularjs wpd module 2157 */ 2158var wpd = angular.module('wpd', []); 2159 2160wpd.config([ 2161 '$locationProvider', 2162 function($locationProvider) { 2163 $locationProvider.html5Mode(true); 2164 $locationProvider.hashPrefix('!'); 2165 } 2166]); 2167 2168 2169/** 2170 * Angular service that **should** be used for reporting any metrics stuff 2171 * @author keiranc 2172 * 2173 * GB - The google analytics element of this has never worked - ! DO NOT USE TO LOG GOOGLE ANALYTICS EVENTS ! 2174 * Only use for "internal" metrics logging. 2175 */ 2176wpd.service('Analytics', [ 2177 '$http', 2178 '$log', 2179 '$window', 2180 function($http, $log, $window) { 2181 // Google Analytics may not be loaded, so use angular.noop in this case (no-ga mode) 2182 this.ga = $window.ga || angular.noop; 2183 2184 this.metricsUrl = '/__metrics/log'; 2185 2186 // Utility method to ensure the GA event is sent - see https://developers.google.com/analytics/devguides/collection/analyticsjs/sending-hits#hitcallback 2187 function createFunctionWithTimeout(callback, opt_timeout) { 2188 var called = false; 2189 function fn() { 2190 if (!called) { 2191 called = true; 2192 callback(); 2193 } 2194 } 2195 setTimeout(fn, opt_timeout || 1000); 2196 return fn; 2197 } 2198 2199 /** 2200 * Sends a custom event to google analytics 2201 * @param {String} category typically the object that was interacted with 2202 * @param {String} action the type of interaction 2203 * @param {String} label useful for categorising events 2204 * @param {Number} value a numeric value associated with the event (integer) 2205 */ 2206 this.logEvent = function(category, action, label, value) { 2207 if (!category || !action) { 2208 $log.error( 2209 'Analytics.logEvent must have at least a valid category and action parameter' 2210 ); 2211 return; 2212 } 2213 2214 var _this = this; 2215 2216 //!!!GB - As far as I can tell this has never worked 2217 //We will just treat this module as an "internal" logger and deal with google analytics seperately. 2218 //this.ga('send', category, action, label, value); 2219 2220 // send to metrics 2221 var url = $window.location.href; 2222 2223 var payload = { 2224 category: category, 2225 action: action, 2226 URL: url, 2227 authenticityToken: CORE.AT 2228 }; 2229 2230 if (angular.isDefined(label) && label != null) { 2231 payload.label = label; 2232 } 2233 2234 if (angular.isDefined(value) && value != null) { 2235 payload.value = value; 2236 } 2237 2238 if (navigator.sendBeacon) { 2239 var payloadFormData = new FormData(); 2240 for (var key in payload) { 2241 if (payload.hasOwnProperty(key)) { 2242 payloadFormData.append(key, payload[key]); 2243 } 2244 } 2245 navigator.sendBeacon(_this.metricsUrl, payloadFormData); 2246 } else { 2247 $http.post(_this.metricsUrl, payload); 2248 } 2249 return this; 2250 }; 2251 2252 /** 2253 * Sends a page view event to google analytics for the url 2254 * @param {String} url url of the page 2255 * @returns {Analytics} this 2256 */ 2257 this.logPageView = function(url) { 2258 this.ga('send', { 2259 hitType: 'pageview', 2260 page: url 2261 }); 2262 return this; 2263 }; 2264 2265 /** 2266 * Sends a page view event to google analytics for the current page 2267 * @returns {Analytics} this 2268 */ 2269 this.logCurrentPageView = function() { 2270 var currentUrl = $window.location.href; 2271 this.logPageView(currentUrl); 2272 return this; 2273 }; 2274 } 2275]); 2276 2277wpd.controller('alertDismissableCTRL', [ 2278 '$element', 2279 '$scope', 2280 function($element, $scope) { 2281 $scope.close = function() { 2282 angular.element($element).fadeOut(); 2283 }; 2284 } 2285]); 2286 2287wpd.controller('youtubeCTRL', [ 2288 '$scope', 2289 '$window', 2290 function($scope, $window) { 2291 // DEBUG && console.log(angular.element('.yt_holder')); 2292 } 2293]); 2294 2295 2296 //GBDEVELOPMENT - my new directive to check dates 2297/** 2298 * check if the date is valid 2299 */ 2300wpd.directive('invalidDate', [ 2301 function() { 2302 return { 2303 require: 'ngModel', 2304 restrict: 'A', 2305 link: function(scope, element, attrs, ngModel) { 2306 ngModel.$validators.invalidDate = function(value) { 2307 if(value != undefined){ 2308 return (true); //all dates are invalid 2309 }else{ 2310 return false; 2311 } 2312 }; 2313 } 2314 }; 2315 } 2316]); 2317 2318// For Ability Net refinement of [WSME-586] 2319/** 2320* Use on a structure in which role=tablist, and it has role=tab children. 2321* Will set things up so that an Enter or Space press will trigger click() for each tab child. 2322* Will set up movement between tabs using left and right arrow keys. 2323* TODO: should be namespaced 2324* 2325* @author noahm 2326*/ 2327// For Ability Net refinement of [WSME-586] 2328/* Use on a structure in which role=tablist, and it has role=tab children. 2329 * Will set things up so that an Enter or Space press will trigger click() for each tab child. 2330 * Will set up movement between tabs using left and right arrow keys. 2331*/ 2332wpd.directive('accessibleTabs', ['accessibilityKeyCodes', '$timeout', 2333 function(accessibilityKeyCodes, $timeout) { 2334 return { 2335 restrict: 'AE', 2336// scope: { 2337// initialSelectedTabIndex: '<?' 2338// }, 2339 /** 2340 * Note that this directive has no explicit template (TODO - but is this worth it?) 2341 * Interesting: https://stackoverflow.com/questions/20878830/angularjs-isolated-scope-for-directives-without-own-template 2342 * and: https://stackoverflow.com/questions/36211733/angular-directive-with-no-template-assign-ng-click-function-from-directive 2343 */ 2344 link: function($scope, $element, $attrs, $ctrl, $transcludeFn) { 2345 2346 $scope.selectedTabIndex = parseInt($attrs.initialSelectedTabIndex) || 0; 2347 2348 /** 2349 * Given as a function, despite its simplicity, to allow people overriding the ng-click to have a slightly easier time setting the tab 2350 * 2351 * @param {number|string} tabIndex - the tab to set as the currently showing tab 2352 * @return {undefined} 2353 */ 2354 $scope.selectTab = function(tabIndex) { 2355 $scope.selectedTabIndex = parseInt(tabIndex) || 0; 2356 }; 2357 2358 /** 2359 * @param {number|string} tabIndex - the tab to check whether we should be able to show or not 2360 * @return {boolean} 2361 */ 2362 $scope.showTabPanel = function(tabIndex) { 2363 return $scope.selectedTabIndex === parseInt(tabIndex); 2364 }; 2365 2366 $timeout(function() { 2367 var $tabChildren = $element.find('[role="tab"]'); 2368 2369 $tabChildren.each(function(idx, tabChild) { 2370 tabChild = angular.element(tabChild); 2371 2372 /** 2373 * So this is how it should work: you can focus on each tab sequentially by navigating through them with the left and right arrow keys. 2374 * Changing focus does not actually 'click' the tab to change the tab panel, pressing enter or space will though. 2375 */ 2376 tabChild.on('keyup', function(event) { 2377 if (event.keyCode === accessibilityKeyCodes.ENTE
2377R || event.keyCode === accessibilityKeyCodes.SPACE) { 2378 tabChild.click(); 2379 } 2380 else if (event.keyCode === accessibilityKeyCodes.LEFT_ARROW) { 2381 if (idx === 0) { //wrap to last 2382 $tabChildren.last().focus(); 2383 } 2384 else { 2385 $tabChildren[idx-1].focus(); 2386 } 2387 } 2388 else if (event.keyCode === accessibilityKeyCodes.RIGHT_ARROW) { 2389 if (idx === $tabChildren.length - 1) { //wrap to first 2390 $tabChildren.first().focus(); 2391 } 2392 else { 2393 $tabChildren[idx+1].focus(); 2394 } 2395 } 2396 }); 2397 }); 2398 }); 2399 } 2400 } 2401 } 2402]); 2403 2404/** 2405* TODO: make this the only version of accessibleTabs and ditch the other one. 2406* Can't address this now because short on time and the change to use an isolate scope means that HTML bits will need 2407* to be updated! 2408* 2409* @author noahm 2410*/ 2411wpd.directive('wpdutilsAccessibleTabs', ['accessibilityKeyCodes', '$timeout', 2412 function(accessibilityKeyCodes, $timeout) { 2413 return { 2414 restrict: 'AE', 2415 scope: { 2416 initialSelectedTabIndex: '<?wpdutilsInitialSelectedTabIndex', 2417 deferSetup: '<?wpdutilsAccessibleTabsDeferSetup', 2418 tabChildren: '=?wpdutilsAccessibleTabsTabChildren', 2419 watchTabChildren: '<?wpdutilsAccessibleTabsWatchTabChildren', 2420 onSetupFn: '&?wpdutilsAccessibleTabsOnSetupFn', 2421 onTearDownFn: '&?wpdutilsAccessibleTabsOnTearDownFn', 2422 disableDirective: '<?wpdutilsAccessibleTabsDisableDirective' 2423 }, 2424 /** 2425 * Note that this directive has no explicit template (TODO - but is this worth it?) 2426 * Interesting: https://stackoverflow.com/questions/20878830/angularjs-isolated-scope-for-directives-without-own-template 2427 * and: https://stackoverflow.com/questions/36211733/angular-directive-with-no-template-assign-ng-click-function-from-directive 2428 */ 2429 link: function($scope, $element, $attrs, $ctrl, $transcludeFn) { 2430 const DEBUG = false; 2431 const EVENT_NAMESPACE = 'wpdutilsAccessibleTabs'; 2432 2433 $scope.selectedTabIndex = $scope.initialSelectedTabIndex || parseInt($attrs.initialSelectedTab) || 0; 2434 2435 /** 2436 * Given as a function, despite its simplicity, to allow people overriding the ng-click to have a slightly easier time setting the tab 2437 * 2438 * @param {number|string} tabIndex - the tab to set as the currently showing tab 2439 * @return {undefined} 2440 */ 2441 $scope.selectTab = function(tabIndex) { 2442 $scope.selectedTabIndex = parseInt(tabIndex) || 0; 2443 }; 2444 2445 /** 2446 * @param {number|string} tabIndex - the tab to check whether we should be able to show or not 2447 * @return {boolean} 2448 */ 2449 $scope.showTabPanel = function(tabIndex) { 2450 return $scope.selectedTabIndex === parseInt(tabIndex); 2451 }; 2452 2453 /** 2454 * As the name implies, will only attach a particular handler for an event once, no matter how many times 2455 * it's told to attach that handler to the specified event. 2456 * 2457 * @param {jQuery} $targetElem - the element in the DOM triggering the named event 2458 * @param {string} eventName - the name of the event being triggered 2459 * @param {string} eventNamespace - the namespace of the event being triggered 2460 * @param {function} handler - the handler function to provide as a callback 2461 * @returns {undefined} 2462 */ 2463 var attachHandlerOnce = function($targetElem, eventName, eventNamespace, handler) { 2464 var han = $._data($targetElem.get(0), "events"); 2465 if (!han || !han[eventName] || han[eventName].map(function(a) {return a.handler.name + '.' + a.namespace}).indexOf(handler.name + '.' + eventNamespace) < 0) { 2466 let finalEventName = eventName + (eventNamespace ? '.' + eventNamespace : ''); 2467 $targetElem.on(finalEventName, handler); 2468 } 2469 }; 2470 2471 /** 2472 * So this is how it should work: you can focus on each tab sequentially by navigating through them with the left and right arrow keys. 2473 * Changing focus does not actually 'click' the tab to change the tab panel, pressing enter or space will though. 2474 */ 2475 var _tabKeyupEvent = function(event, tabChild, idx, $tabChildren) { 2476 if (event.keyCode === accessibilityKeyCodes.ENTE
2476R || event.keyCode === accessibilityKeyCodes.SPACE) { 2477 // `event.currentTarget` should == tabChild anyway 2478 tabChild.click(); 2479 } 2480 else if (event.keyCode === accessibilityKeyCodes.LEFT_ARROW) { 2481 if (idx === 0) { // wrap to last 2482 $tabChildren.last().focus(); 2483 } 2484 else { 2485 $tabChildren[idx-1].focus(); 2486 } 2487 } 2488 else if (event.keyCode === accessibilityKeyCodes.RIGHT_ARROW) { 2489 if (idx === $tabChildren.length - 1) { // wrap to first 2490 $tabChildren.first().focus(); 2491 } 2492 else { 2493 $tabChildren[idx+1].focus(); 2494 } 2495 } 2496 }; 2497 2498 $scope.setupTabBehaviour = function(callbackFn, ignoreDefaultCallbackFn) { 2499 DEBUG && console.log($scope.tabChildren); 2500 2501 if ($scope.tabChildren) { 2502 $scope.tabChildren.each(function(idx, tabChild) { 2503 tabChild = angular.element(tabChild); 2504 attachHandlerOnce( 2505 tabChild, 2506 'keyup', 2507 `${EVENT_NAMESPACE}__tabKeyupEvent`, 2508 (event) => _tabKeyupEvent(event, tabChild, idx, $scope.tabChildren) 2509 ); 2510 }); 2511 } 2512 2513 callbackFn && callbackFn(); 2514 2515 if (!ignoreDefaultCallbackFn) { 2516 $scope.onSetupFn && $scope.onSetupFn($scope, context); 2517 } 2518 }; 2519 2520 $scope.tearDownTabBehaviour = function(callbackFn, ignoreDefaultCallbackFn) { 2521 DEBUG && console.log($scope.tabChildren); 2522 2523 if ($scope.tabChildren) { 2524 $scope.tabChildren.each(function(idx, tabChild) { 2525 tabChild = angular.element(tabChild); 2526 tabChild.off(`keyup.${EVENT_NAMESPACE}__tabKeyupEvent`); 2527 }); 2528 } 2529 2530 callbackFn && callbackFn(); 2531 2532 if (!ignoreDefaultCallbackFn) { 2533 $scope.onTearDownFn && $scope.onTearDownFn($scope, context); 2534 } 2535 }; 2536 2537 $scope.initUndefinedTabChildren = function() { 2538 if ($scope.tabChildren === undefined) { 2539 $scope.tabChildren = $element.find('[role="tab"]'); 2540 } 2541 }; 2542 2543 $scope.$on('wpdutilsAccessibleTabs__setup', (event, data) => { 2544 if ($scope.disableDirective) return; 2545 $scope.setupTabBehaviour(data?.callbackFn, data?.ignoreDefaultCallbackFn); 2546 }); 2547 2548 $scope.$on('wpdutilsAccessibleTabs__tearDown', (event, data) => { 2549 $scope.tearDownTabBehaviour(data?.callbackFn, data?.ignoreDefaultCallbackFn); 2550 }); 2551// 2552// if ($scope.watchDisableDirective) { 2553// $scope.$watch('disableDirective', function(newVal, oldVal) { 2554// if (newVal) $scope.tearDownTabBehaviour(); 2555// else $scope.setupTabBehaviour(); 2556// }); 2557// } 2558
2559 // only set this watcher up if we were told to 2560 if ($scope.watchTabChildren) { 2561 $scope.$watchCollection('tabChildren', function(newVal, oldVal) { 2562 // both newVal and oldVal will be null on initialization for watchCollection. 2563 if (newVal !== oldVal) { 2564 if ($scope.disableDirective) $scope.tearDownTabBehaviour(); 2565 else $scope.setupTabBehaviour(); 2566 } 2567 }); 2568 } 2569 2570 // initialize 2571 if (!($scope.deferSetup || $scope.disableDirective)) { 2572 $timeout(() => { 2573 $scope.initUndefinedTabChildren(); 2574 $scope.setupTabBehaviour(); 2575 }); 2576 } 2577 } 2578 } 2579 } 2580]); 2581 2582/** 2583 * WPD site wide datePicker directive 2584 * for options see https://xdsoft.net/jqplugins/datetimepicker/ 2585 * @author keiranc 2586 */ 2587wpd.directive('datePicker', [ 2588 function() { 2589 return { 2590 require: 'ngModel', 2591 scope: { 2592 formatFn: '&', // custom formatter 2593 parseFn: '&', // custom parser 2594 options: '=' // xdsoft datetimepicker options 2595 }, 2596 link: function(scope, element, attrs, ngModelController) { 2597 const DEBUG = false; 2598 // Object.values polyfill for IE 9+ 2599 if (!Object.values) { 2600 Object.values = function(o) { 2601 return Object.keys(o).map(function(k) { 2602 return o[k]; 2603 }); 2604 }; 2605 } 2606 2607 2608 // NOTE: this application of datetimepicker on the jQuery object is different to $.fn.datetimepicker! 2609 // This only appears in jquery.datetimepicker.full.js, not the standard jquery.datetimepicker.js that comes with CORE, 2610 // so make sure you have used #{wpd.utils.loadDatePicker /} in your template somewhere. 2611 $.datetimepicker.setDateFormatter('moment'); 2612 2613 var format = 'DD/MM/YYYY'; 2614 2615 if (scope.options && scope.options.format) { 2616 format = scope.options.format; 2617 } 2618 2619 var hasParseFn = !!attrs.parseFn; 2620 var hasFormatFn = !!attrs.formatFn; 2621 2622 // format the date from the model value to a value to display in the input box 2623 ngModelController.$formatters.push(function(modelValue) { 2624 if (modelValue == null) { 2625 return null; 2626 } 2627 if (hasFormatFn) { 2628 return scope.formatFn({ 2629 modelValue: modelValue, 2630 format: format 2631 }); 2632 } 2633 var date = moment(modelValue); 2634 2635 return date.isValid() ? date.format(format) : ''; 2636 }); 2637 2638 // format the date from the input box to a value to store in the model 2639 ngModelController.$parsers.push(function(viewValue) { 2640 if (viewValue == null) { 2641 return null; 2642 } 2643 if (hasParseFn) { 2644 return scope.parseFn({ 2645 viewValue: viewValue, 2646 format: format 2647 }); 2648 } 2649 var date = moment(viewValue, format); 2650 return date.isValid() ? date.toDate() : null; 2651 }); 2652 2653//GBDEVELOPMENT - Switched off validateOnBlur as that was the built in validation changing the invalid date to "today" 2654 angular.element(element).datetimepicker( 2655 angular.extend( 2656 { 2657 format: format, 2658 timepicker: false, 2659 autoHide: true, 2660 validateOnBlur: false, 2661 2662 // remove scroll date change 2663 scrollInput: false, 2664 }, 2665 scope.options || {} 2666 ) 2667 ); 2668 2669//GBDEVELOPMENT - My new onblur that I don't think is a great solution. 2670// angular.element(element).on('blur', function() { 2671// DEBUG && console.log(element); 2672// var dateToCheck = angular.element(element).val();
2673// var valid = true; 2674// if (dateToCheck == 'f') valid = false; 2675// if (!valid) { 2676// element.after('<span class="error" id="' + element.ID + 'clientError' + '">You have entered an invalid date</span>'); 2677// } 2678// else 2679// { 2680// var toDel = document.getElementById(element.ID + 'clientError'); 2681// toDel.parentNode.removeChild(toDel); 2682// } 2683// }); 2684 } 2685 }; 2686 } 2687]); 2688 2689wpd.directive('trackEvent', ['Analytics', function(Analytics) { 2690 return { 2691 scope: { 2692 trackEvent: '@?', 2693 category: '@', 2694 action: '@', 2695 label: '@?', 2696 value: '@?' 2697 }, 2698 link: function(scope, element, attrs) { 2699 if (scope.category && scope.action) { 2700 angular.element(element).on(scope.trackEvent || 'click', function() { 2701 Analytics.logEvent(scope.category, scope.action, scope.label || attrs.href || '', parseInt(scope.value, 10) || null); 2702 }); 2703 } 2704 } 2705 } 2706}]); 2707 2708wpd.factory('scrollTo', [ 2709 '$q', 2710 '$timeout', 2711 function($q, $timeout) { 2712 return function(selector, offset, duration) { 2713 return $q(function(resolve) { 2714 $timeout(function() { 2715 var element = angular.element(selector); 2716 if (element && element.offset()) { 2717 angular.element('html, body').animate( 2718 { 2719 scrollTop: 2720 element.offset().top - 2721 (offset ? offset : 50) - 2722 (window.fixedHeaderOffset || 0) 2723 }, 2724 duration, 2725 'swing', 2726 resolve 2727 ); 2728 } 2729 }); 2730 }); 2731 }; 2732 } 2733]); 2734 2735wpd.controller('BlocksCtrl', [ 2736 '$scope', 2737 function($scope) { 2738 $scope.selected = 1; 2739 2740 $scope.select = function(index) { 2741 $scope.selected = parseInt(index, 10); 2742 }; 2743 } 2744]); 2745 2746/** 2747 * Angular service for adding accessibility functionality when opening a pop up with vex 2748 * @author petert 2749 */ 2750wpd.service('vexAccessibility', [ 2751 '$timeout', 2752 function($timeout) { 2753 2754 2755 /** 2756 * Hides elements from screen readers that are in the background and indexes elements created by vex so screen readers can access them. 2757 * @param {List} elements, classes, ids to hide from the screen reader 2758 * @param {List} classes and ids to index 2759 * @param {String} element to give focus to 2760 */ 2761 this.openVex = function(elementsToHide, elementsToIndex, elementToFocus) { 2762 var e; 2763 var tag; 2764 for(e = 0; e < elementsToHide.length; e++){ 2765 tag = angular.element(document).find(elementsToHide[e]); 2766 tag.attr('aria-hidden',true); 2767 tag.attr('tabindex',-1); 2768 } 2769 $timeout(function() { 2770 var e; 2771 var tag; 2772 2773 // make the vex-close button accessible and respond to enter keypresses. 2774 $('.vex-content').each(function(idx, elem) { 2775 if ($(this).find('.vex-close').length) { 2776 $(this).prepend( 2777 $(this).find('.vex-close') 2778 .attr('tabindex',0).attr('role','button').attr('aria-label','Close dialog') 2779 .addClass('focusable-element') 2780 .on('keyup', 2781 function(event) { 2782 if (event.keyCode === 13) { 2783 event.preventDefault(); 2784 this.click(); 2785 } 2786 } 2787 ) 2788 ); 2789 } 2790 2791 }); 2792 2793 for(e = 0; e < elementsToIndex.length; e++){ 2794 tag = angular.element(document).find(elementsToIndex[e]); 2795 tag.attr('tabindex',0); 2796 } 2797 angular.element(document).find(elementToFocus).focus(); 2798 }); 2799 }; 2800 2801 /** 2802 * Unhides elements that were hidden with openVex 2803 * @param {List} elements, classes, ids to unhide from the screen reader 2804 * @param {String} element to give focus to 2805 */ 2806 this.closeVex = function(elementsToAdd, elementToFocus) { 2807 var e; 2808 var tag; 2809 $timeout(function() { 2810 for(e = 0; e < elementsToAdd.length; e++){ 2811 tag = angular.element(document).find(elementsToAdd[e]); 2812 tag.attr('aria-hidden',false); 2813 tag.attr('tabindex',0); 2814 }
2815 angular.element(document)?.find(elementToFocus)?.focus()?.get(0)?.scrollIntoView( 2816 { 2817 behavior: "auto", 2818 block: "center", 2819 inline: "nearest" 2820 }); 2821 }); 2822 }; 2823 } 2824]); 2825 2826/** 2827* A service that will configure a vex dialog object's settings such that it can render an ngTemplate as its view. 2828* 2829* @author noahm 2830*/ 2831wpd.service('vexNgTemplate', [ 2832 '$templateRequest', '$templateCache', '$timeout', '$compile', 2833 function($templateRequest, $templateCache, $timeout, $compile) { 2834 2835 var service = this; 2836 2837 // Stolen from https://www.w3resource.com/javascript-exercises/javascript-math-exercise-23.php 2838 var _createUUID = function() { 2839 var dt = new Date().getTime(); 2840 var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { 2841 var r = (dt + Math.random()*16)%16 | 0; 2842 dt = Math.floor(dt/16); 2843 return (c=='x' ? r :(r&0x3|0x8)).toString(16); 2844 }); 2845 return uuid; 2846 }; 2847 2848 /** 2849 * @param {String} rawHTML 2850 * @param {Object} existingSettings 2851 * @param {Object} templateScope 2852 * @param {boolean} removeVexForm 2853 * @param {String|null} compileTargetId 2854 * 2855 * @return {boolean|Object} 2856 */ 2857 service.setRawTemplate = function(rawHTML, existingSettings, templateScope, removeVexForm, compileTargetId) { 2858 var vexSettings = existingSettings || {}; 2859 2860 if (rawHTML) { 2861 var targetId = compileTargetId || _createUUID(); 2862 vexSettings.input = '<div id="' + targetId + '">' + rawHTML + '</div>'; 2863 var currentAfterOpenFn = vexSettings.afterOpen || angular.noop; 2864 2865 vexSettings.afterOpen = function() { 2866 // find the DOM node of the content we added into vex (our vexSettings.input). You want this to have a unique ID so if you supply the compileTargetId make sure it is. 2867 var $vexHtml = angular.element('#' + targetId); 2868 2869 // Vex popups wrap their content in a form element, which means any form element within your template will not function. 2870 // If you choose to remove the vex form, then think about the repercussions on the vex buttons (if you're using them) 2871 if (removeVexForm) { 2872 var $vexForm = $vexHtml.closest('form.vex-dialog-form'); 2873 2874 if ($vexForm) { 2875 $vexForm.parent().append($vexForm.children()); 2876 $vexForm.remove(); 2877 } 2878 } 2879 2880 // compile the template using the supplied templateScope, so it can access vm or whatever, despite no longer being within vm's root DOM node 2881 $compile($vexHtml)(templateScope); 2882 2883 // execute the original afterOpen function 2884 currentAfterOpenFn(); 2885 }; 2886 } 2887 else { 2888 console.error('Could not apply the HTML template'); 2889 return false; 2890 } 2891 2892 // return the new augmented settings 2893 return existingSettings; 2894 }; 2895 2896 /** 2897 * @asynchronous 2898 * 2899 * @param {String} ngTemplateName 2900 * @param {Object} existingSettings 2901 * @param {Object} templateScope 2902 * @param {boolean} removeVexForm 2903 * @param {String|null} compileTargetId 2904 * 2905 * @return {Promise} - a Promise that will resolve to an object of Vex settings ready to do with as you wish 2906 */ 2907 service.setNgTemplate = function(ngTemplateName, existingSettings, templateScope, removeVexForm, compileTargetId) { 2908 return new Promise(function(resolve, reject) { 2909 // look in templateCache first 2910 var existingTemplate = $templateCache.get(ngTemplateName); 2911 2912 if (existingTemplate) { 2913 var setTemplateResult = service.setRawTemplate(existingTemplate, existingSettings, templateScope, removeVexForm, compileTargetId); 2914 2915 if (setTemplateResult) { 2916 // return the augmented settings 2917 resolve(setTemplateResult); 2918 } 2919 else { 2920 // return the original settings 2921 console.error('Could not fetch template for ' + ngTemplateName); 2922 reject(existingSettings); 2923 } 2924 } 2925 else { 2926 $templateRequest(ngTemplateName).then( 2927 function(html) { 2928 var setTemplateResult = service.setRawTemplate(html, existingSettings, templateScope, removeVexForm, compileTargetId); 2929 2930 if (setTemplateResult) { 2931 // return the augmented settings 2932 resolve(setTemplateResult); 2933 } 2934 else { 2935 // return the original settings 2936 console.error('Could not fetch template for ' + ngTemplateName); 2937 reject(existingSettings); 2938 } 2939 } 2940 ); 2941 } 2942 }); 2943 }; 2944 } 2945]); 2946 2947 2948/** 2949* Little directive to help CMS users discern areas of a template which on the frontend would normally be visually mutually exclusive, but have been 2950* made visible all the time in the CMS in order to edit stuff within them like slots. 2951* Will outline the directive annotated element with randomly coloured dashed border, and supply a label for the area at the top. 2952* 2953* @author noahm 2954*/ 2955wpd.directive('cmsShowBounds', function() { 2956 return { 2957 restrict: 'A', 2958 scope: { 2959 cmsBoundLabel: '@' 2960 }, 2961 link: function($scope, $elem, $attrs) { 2962 if ($attrs.cmsShowBounds === 'true') { 2963 var randomColor = 'hsl(' + Math.floor(Math.random() * 360) + ', 70%, 50%)'; // percentages are for saturation and lightness 2964 2965 $($elem).css({outline: ('2px dashed ' + randomColor), borderRadius: '3px', outlineOffset: '6px'}); 2966 2967 if ($scope.cmsBoundLabel) { 2968 var $label = $('<span>' + $scope.cmsBoundLabel + '</span>'); 2969 $label.css({fontSize: '0.8em', color: randomColor, display: 'inline-block', margin: '18px 0px 12px'}); 2970 $($elem).before($label); 2971 } 2972 } 2973 } 2974 } 2975}); 2976 2977/** 2978* TODO: think about creating a super class rather than mixin? 2979*/ 2980wpd.service('wpdutilsCustomInputMixinService', ['$compile', function($compile) { 2981 /** 2982 * A quick setup function that instantiates watches between a custom input directive's inner FormController 2983 * and syncs the status of that FormController to the status of the NgModelController of the input's ngModel 2984 * 2985 * @param {Scope} $scope - the $scope of the directive this mixin is being applied to 2986 * @param {string} formCtrlName - the name of the inner form of the directive that this mixin is being applied to 2987 * @param {NgModelController} ngModelCtrl - the ngModel controller attached to the directive that this mixin is being applied to 2988 * @param {string} directiveKey - the name of the directive so that uit may be identifiable within ngModelCtrl's $error object 2989 * 2990 * @return {object} - an object possessing the watcher cancel functions 2991 */ 2992 this.initStatusSyncers = function($scope, formCtrlName, ngModelCtrl, directiveKey) { 2993 // sync touch/dirty/valid states of our inner form with the outer ngControl 2994 var cancelFormValidWatch = $scope.$watch(() => $scope[formCtrlName].$valid, function() { 2995 ngModelCtrl.$setValidity(directiveKey, $scope[formCtrlName].$valid); 2996 }); 2997 2998 var cancelFormDirtyWatch = $scope.$watch(() => $scope[formCtrlName].$dirty, function() { 2999 $scope[formCtrlName].$dirty ? ngModelCtrl.$setDirty() : ngModelCtrl.$setPristine(); 3000 }); 3001 3002 // a form doesn't have a $touched field (though feels like it should do). we need to check each of our controls 3003 var cancelFormDirtyWatch = $scope.$watch(() =>
3003 $scope[formCtrlName].$$controls.find((control) => control.$touched), function(newVal, oldVal) { 3004 newVal ? ngModelCtrl.$setTouched() : ngModelCtrl.$setUntouched(); 3005 }); 3006 3007 return { 3008 cancelFormValidWatch: cancelFormValidWatch, 3009 cancelFormDirtyWatch: cancelFormDirtyWatch, 3010 cancelFormDirtyWatch: cancelFormDirtyWatch 3011 } 3012 }; 3013 3014 /** 3015 * Inject a loading spinner into the given parent element, that activates on the given Angular condition 3016 * @param {object} $scope - the scope for the condition, typically $scope 3017 * @param {element} parent - the element to add the wrapper class to, and to add the spinner overlay to, typically $elem.parent() 3018 * @param {string} condition - the condition to use for showing the spinner, e.g. 'loading' or 'vm.loading' 3019 */ 3020 this.injectLoadingSpinner = function($scope, parent, condition = 'loading') { 3021 // Bail if the parent already has a matching spinner 3022 if (parent.find('.universal-loading-spinner[data-ng-show="' + condition + '"]').length > 0) { 3023 return; 3024 } 3025 // Add the spinner to the parent and give it the wrapper class 3026 const spinner = $compile('<div class="universal-loading-spinner" data-ng-show="' + condition + '"></div>')($scope); 3027 parent.prepend(spinner); 3028 parent.addClass('universal-loading-spinner-wrapper'); 3029 } 3030}]); 3031 3032// TODO: these parsers need a complement "unparser" in order to take an existing value from an external model and return it to a format that the input can respond to 3033// not sure if a formatter will cut it? 3034 3035/** 3036* A simple little parser that returns the boolean interpretation of a value. 3037* In the case of a string, all values except 'true', 'y' or 'yes' will count as false. 3038* 3039* @author noahm 3040*/ 3041wpd.directive('wpdutilsAsBoolean', [function() { 3042 return { 3043 require: 'ngModel', 3044 restrict: 'A', 3045 link: function($scope, $elem, $attrs, ctrl) { 3046 var parse = function(value) { 3047 if (typeof value === 'string') { 3048 let v = value.toLowerCase(); 3049 return (v === 'true' || v === 'y' || v === 'yes'); 3050 } 3051 3052 return !!value; 3053 }; 3054 3055 ctrl.$parsers.push(parse); 3056 } 3057 } 3058}]); 3059 3060/** 3061* Might seem unnecessary, but certain inputs (like checkboxes) don't necessarily return what's in their value attribute 3062* 3063* @author noahm 3064*/ 3065wpd.directive('wpdutilsAsValueAttr', [function() { 3066 return { 3067 require: 'ngModel', 3068 restrict: 'A', 3069 link: function($scope, $elem, $attrs, ctrl) { 3070 // this is of no use if we are on an element that has no value attribute. 3071 if (!($elem[0] instanceof HTMLInputElement)) return; 3072 3073 var parse = function(value) { 3074 if (value) { 3075 return $elem.attr('value'); 3076 } 3077 3078 return null; 3079 }; 3080 3081 ctrl.$parsers.push(parse); 3082 } 3083 } 3084}]); 3085 3086/** 3087* Since angularJS-eqsue '{{}}' strings get sanitized out of WYSIWYG content in the CMS, use '#{}' instead. 3088* If an element has this directive attached to it, any instances of '#{}' will be changed to '{{}}' bindings 3089* 3090* @directive 3091* @author noahm 3092*/ 3093wpd.directive('wpdutilsCompilable', [function() { 3094 return { 3095 restrict: 'AC', 3096 scope: false, 3097 compile: function(tElem, tAttrs) { 3098 const SEARCH_REGEX = /#\{(.*?)\}/gi; 3099 3100 tElem.html(function(idx, oldHTML) { 3101 return oldHTML.replaceAll(SEARCH_REGEX, '{{ $1 }}'); 3102 }); 3103 } 3104 } 3105}]); 3106 3107wpd.directive('wpdutilsHref', ['$window','$location', function($window, $location) { 3108 return { 3109 scope: { 3110 preserveQueryString: '<?', 3111 href: '@?wpdutilsHref' 3112 }, 3113 link: function(scope, iElem, iAttrs) { 3114 const MATCH_REGEX = /^([^?#]*)?(?<query>\?.*)?(?<hash>#.*)?$/gmi; 3115 scope.preserveQueryString = scope.preserveQueryString !== undefined ? scope.preserveQueryString : true; 3116 3117 // preference for an existing href attribute over one given to us, as this may be an href 3118 // created by something like ngHref 3119 let href = iAttrs.href || scope.href; 3120 3121 const replaceFn = function() { 3122 let href = iAttrs.href || scope.href; 3123 let curSearchParams = $window.location.search; 3124 let newHref = href.replace(MATCH_REGEX, '$1' + curSearchParams + '$<hash>'); 3125 3126 if (newHref.startsWith('?')) { 3127 newHref = $window.location.pathname + newHref; 3128 } 3129 iElem.attr('href', newHref); 3130 }; 3131 3132 if (!scope.preserveQueryString) { 3133 iElem.attr('href', href); 3134 } 3135 else { 3136 replaceFn(); 3137 3138 // TODO: watch query string and update href automatically? 3139 } 3140 } 3141 } 3142}]) 3143 3144/** 3145* TODO: Map option will not behave as you expect with the ng-model definition on the checkboxes! map['key'] is wrong, you need to use map.set('key') 3146* 3147* 3148*/ 3149wpd.directive('wpdutilsCheckboxNgCollectionField', ['wpdutilsCustomInputMixinService', function(customInputMixin) { 3150 return { 3151 require: ['ngModel'], 3152 restrict: 'AE', 3153 scope: { 3154 readonly: '<', 3155 asType: '<', // ('array'|'map'|undefined|null) 3156 fieldId: '<', 3157 disabled: '<', 3158 required: '<' 3159 }, 3160 templateUrl: 'wpdutilsCheckboxNgCollectionField.html',
3161 link: function($scope, $elem, $attrs, ctrl) { 3162 const DIRECTIVE_KEY = 'wpdutilsCheckboxNgCollectionField'; 3163 const INNER_FORM_NAME = 'checkboxCollectionForm'; 3164 3165 // TODO: not sure why in this instance ctrl is an array, even though it has just one member 3166 var ngModelCtrl = Array.isArray(ctrl) ? ctrl[0] : ctrl; 3167 3168 var updatedViaNgChange = false; 3169 3170 var unsetFormData = function() { 3171 $scope.formData = { 3172 checkboxCollection: ($scope.asType === 'array' ? [] : $scope.asType === 'map' ? new Map() : {}) 3173 }; 3174 }; 3175 unsetFormData(); 3176 3177 /** 3178 * Updates the passed in ngModel with the checkboxCollectionForm formData. 3179 * Will only perform the update if all of checkboxCollectionForm's fields are valid. 3180 * 3181 * @return {undefined} 3182 */ 3183 $scope.updateNgModel = function() { 3184 updatedViaNgChange = true; 3185 3186 if ($scope[INNER_FORM_NAME].$valid) { 3187 ngModelCtrl.$setViewValue($scope.formData.checkboxCollection); 3188 } 3189 3190 if ($scope.required) checkValidity(); 3191 }; 3192 3193 3194 // TODO: This should be its own validator directive 3195 function checkValidity() { 3196 var validateArray = []; 3197 if ($scope?.formData?.checkboxCollection?.length > 0) { 3198 $scope.formData.checkboxCollection.forEach(item => { 3199 if (item) { 3200 validateArray.push(item); 3201 } 3202 }); 3203 } 3204 if (validateArray.length <= 0) { 3205 ngModelCtrl.$setValidity("required-empty", false); 3206 } 3207 else { 3208 ngModelCtrl.$setValidity("required-empty", true); 3209 } 3210 } 3211 3212 3213 var updateCheckedProperties = function() { 3214 // TODO: you need to "recheck" the checkboxes which have values that are found in our internal model 3215 }; 3216 3217 // TODO: needs more thorough testing 3218 var updateInternalModel = function(value) { 3219 if (value === undefined || value === null) { 3220 unsetFormData(); 3221 } 3222 else if (Array.isArray(value)) { 3223 switch ($scope.asType) { 3224 case 'map': 3225 let m = new Map(); 3226 value.forEach((elem) => m.set(elem, elem)); // TODO: think about how a user would actually like a map/object to be utilised 3227 $scope.formData.checkboxCollection = m; 3228 break; 3229 case 'array': 3230 // NOTE: this is a shallow copy! 3231 $scope.formData.checkboxCollection = angular.copy(value); 3232 break; 3233 default: 3234 let o = {}; 3235 value.forEach((elem) => o[elem] = elem); 3236 $scope.formData.checkboxCollection = o; 3237 break; 3238 } 3239 } 3240 else if (value instanceof Map) { 3241 switch ($scope.asType) { 3242 case 'array': 3243 let a = []; 3244 for (const [k,v] of value) a.push(v); 3245 $scope.formData.checkboxCollection = a; 3246 break; 3247 case 'map': 3248 // NOTE: this is a shallow copy! 3249 $scope.formData.checkboxCollection = angular.copy(value); 3250 break; 3251 default: 3252 let o = {}; 3253 for (const [k,v] of value) o[k] = v; 3254 $scope.formData.checkboxCollection = o; 3255 break; 3256 } 3257 } 3258 else if (typeof value === 'object') { 3259 switch ($scope.asType) { 3260 case 'array': 3261 let a = []; 3262 Object.entries(value).forEach(([k,v]) => a.push(v)); 3263 $scope.formData.checkboxCollection = a; 3264 break; 3265 case 'map': 3266 let m = new Map(); 3267 Object.entries(value).forEach(([k,v]) =>
3267 m.set(k, v)); 3268 break; 3269 default: 3270 // NOTE: this is a shallow copy! 3271 $scope.formData.checkboxCollection = angular.copy(value); 3272 break; 3273 } 3274 } 3275 }; 3276 3277 var cancelWatch = $scope.$watch(() => ngModelCtrl.$modelValue, function(newVal, oldVal) { 3278 if (!updatedViaNgChange) updateInternalModel(newVal); 3279 updatedViaNgChange = false; 3280 if ($scope.required) checkValidity(); 3281 }); 3282 3283 var statusSyncerCancelFns = customInputMixin.initStatusSyncers($scope, INNER_FORM_NAME, ngModelCtrl, DIRECTIVE_KEY); 3284 3285 } 3286 } 3287}]); 3288 3289/** 3290* Hooks together a "button" functional element with a checkbox or radio input and passes on a click on the button to a click on the input 3291* 3292* @param {string} inputId - the id attribute of the input to link to 3293* @param {string|function} checkedClass - a string or string output expression which represents a CSS class to apply to the "button" when the input is in a checked state 3294* @param {function} extraClickFn - a callback to execute when the input is toggled. It will be supplied the input and the "button" element 3295* 3296* @author noahm 3297*/ 3298wpd.directive('wpdutilsInputToggleButton', [function() { 3299 return { 3300 restrict: 'A', 3301 scope: { 3302 inputId: '<', 3303 checkedClass: '@', 3304 extraClickFn: '&' 3305 }, 3306 link: function($scope, $elem, $attrs) { 3307 if (!$scope.inputId) return; 3308 3309 const $input = angular.element('#' + $scope.inputId.replace('#', '')); 3310 3311 if ($input && $input.length) { 3312 let t = $input.attr('type'); 3313 3314 if (t === 'checkbox' || t === 'radio') { 3315 $scope.toggleInput = function() { 3316 $input.click(); 3317 $scope.extraClickFn && $scope.extraClickFn($input, $elem); 3318 }; 3319 3320 // apply/remove the checkedClass from the button based on the checked property of its input 3321 $scope.$watch(() => $input.prop('checked'), function() { 3322 $input.prop('checked') ? $elem.addClass($scope.checkedClass) : $elem.removeClass($scope.checkedClass); 3323 }); 3324 3325 $elem.on('click', $scope.toggleInput); 3326 } 3327 } 3328 else { 3329 console.error('A checkbox or radio input with ID ' + $scope.inputId + ' couldn\'t be found'); 3330 } 3331 } 3332 } 3333}]); 3334 3335/** 3336* Template: /app/views/ng_templates/wpd/utils/mpan-field.html 3337* Style: ??? 3338* 3339* @author noahm 3340*/ 3341wpd.directive('wpdutilsMpanField', ['$timeout', 'wpdutilsCustomInputMixinService', function($timeout, customInputMixin) { 3342 return { 3343 require: 'ngModel', 3344 restrict: 'AE', 3345 scope: { 3346 isReadonly: '<', 3347 asType: '<', // ('string'|'number'|undefined|null) 3348 fieldId: '<', 3349 isDisabled: '<', 3350 isRequired: '<' 3351 }, 3352 templateUrl: 'wpdutilsMpanField.html', 3353 link: function($scope, $elem, $attrs, ctrl) { 3354 const DIRECTIVE_KEY = 'wpdutilsMpanField'; 3355 const INNER_FORM_NAME = 'mpanForm'; 3356 3357 var updatedViaNgChange = false; 3358 3359 if ($scope.asType !== 'string' && $scope.asType !== 'number') { 3360 $scope.asType = null; 3361 } 3362 3363 // we store the mpan value internally as an object, regardless of the asString setting 3364 var unsetFormData = function() { 3365 $scope.formData = { 3366 distributorId: null, 3367 meterPointId: null, 3368 checkDigit: null 3369 }; 3370 } 3371 unsetFormData(); 3372 3373 /** 3374 * @return {String} 3375 */ 3376 var formDataToString = function() { 3377 return $scope.formData.distributorId + $scope.formData.meterPointId + $scope.formData.checkDigit; 3378 }; 3379 3380 /** 3381 * Updates the passed in ngModel with the mpanForm formData. 3382 * Will only perform the update if all of mpanForm's fields are valid. 3383 * 3384 * @return {undefined} 3385 */ 3386 $scope.updateNgModel = function() { 3387 updatedViaNgChange = true; 3388 3389 if ($scope[INNER_FORM_NAME].$valid) { 3390 switch ($scope.asType) { 3391 case 'string': 3392 ctrl.$setViewValue(formDataToString()); 3393 break; 3394 case 'number': 3395 // NOTE: if casting to a number, no leading 0s will be preserved! 3396 ctrl.$setViewValue(parseInt(formDataToString())); 3397 break; 3398 default: 3399 ctrl.$setViewValue($scope.formData); 3400 } 3401 } 3402 else { 3403 ctrl.$setViewValue(undefined); 3404 } 3405 }; 3406 3407 var updateInternalModel = function(value) { 3408 if (value === undefined || value === null) { 3409 unsetFormData(); 3410 } 3411 else if (typeof value === 'object') { 3412 $scope.formData = { 3413 distributorId: value.distributorId, 3414 meterPointId: value.meterPointId, 3415 checkDigit: value.checkDigit 3416 }; 3417 } 3418 else if (typeof value === 'number' || typeof value === 'string') { 3419 var x = ''; 3420 3421 // transform into string 3422 if (typeof value === 'number') x += value; 3423 else x = value; 3424
3425 // try to split string into the individual digits 3426 try { 3427 $scope.formData = { 3428 distributorId: x.substring(0,2), 3429 meterPointId: x.substring(2,10), 3430 checkDigit: x.substring(10,13), 3431 }; 3432 } 3433 catch (e) { 3434 console.error(e); 3435 } 3436 } 3437 }; 3438 3439 /** 3440 * Interesting in that watching 'ctrl.$modelValue' doesn't seem to fire when you change the underlying model, but watching () => ctrl.$modelValue does. 3441 * You can also watch $attrs['ngModel'] which is a reference to the object being written to, and not the ngModelController itself (ctrl). 3442 * 3443 * In this case, we're wanting to look for a programmatic external update to the ngModel 3444 */ 3445 var cancelWatch = $scope.$watch(() => ctrl.$modelValue, function(newVal, oldVal) { 3446 // an ngChange call that we've internally set up will execute before this watch fires. We don't want to waste time writing back an identical value 3447 // to our internal model if the change to $modelValue came from us. 3448 if (!updatedViaNgChange) { 3449 updateInternalModel(newVal); 3450 } 3451 3452 updatedViaNgChange = false; 3453 }); 3454 3455 var statusSyncerCancelFns = customInputMixin.initStatusSyncers($scope, INNER_FORM_NAME, ctrl, DIRECTIVE_KEY); 3456 } 3457 } 3458}]); 3459 3460/** 3461* Template: /app/views/ng_templates/wpd/utils/numeric-unit-field.html 3462* Style: ??? 3463* 3464* Motivation behind this directive is introducing built-in automatic conversion of the value when units change (set up a standalone angular service for this) 3465* 3466* @author noahm 3467*/ 3468wpd.directive('wpdutilsNumericUnitField', ['wpdutilsCustomInputMixinService', function(customInputMixin) { 3469 return { 3470 require: 'ngModel', 3471 restrict: 'AE', 3472 transclude: true, 3473 scope: { 3474 isReadonly: '<', // {boolean} 3475 asType: '<', // {string} ('string'|undefined|null) 3476 fieldId: '<', // {string} 3477 isDisabled: '<', // {boolean} 3478 isRequired: '<', // {boolean} 3479 units: '<', // {(string|object)[]} 3480 minValue: '<', // {number} 3481 maxValue: '<', // {number} 3482 defaultUnit: '<' // {string} 3483 }, 3484 templateUrl: 'wpdutilsNumericUnitField.html', 3485 link: function($scope, $elem, $attrs, ctrl) { 3486 const DIRECTIVE_KEY = 'wpdutilsNumericUnitField'; 3487 const INNER_FORM_NAME = 'numericUnitForm'; 3488 3489 var updatedViaNgChange = false; 3490 3491 if ($scope.asType !== 'string') { 3492 $scope.asType = null; 3493 } 3494 3495 // if the supplied defaultUnit isn't in the units array, take the first element of the units array instead. 3496 var defaultUnit = null; // $scope.units?.includes($scope.defaultUnit) ? $scope.defaultUnit : ($scope.units?.length ? $scope.units[0] : null); 3497 3498 $scope.unitStrings = []; 3499 $scope.unitValidators = {}; 3500 3501 var _extractUnitValidator = function(validator, unitKey, validatorKey) { 3502 if (!$scope.unitValidators[unitKey]) $scope.unitValidators[unitKey] = {}; 3503 // treat all validation objects as functions to be executed. 3504 $scope.unitValidators[unitKey][validatorKey] = typeof validator === 'function' ? validator : () => { return validator; }; 3505 }; 3506 3507 var _extractUnitData = function(unitObj, unitKey) { 3508 if (typeof unitObj === 'string') { 3509 $scope.unitStrings.push(unitObj); 3510 if (!defaultUnit) defaultUnit = unitObj; 3511 } 3512 else if (typeof unitObj === 'object') { 3513 $scope.unitStrings.push(unitKey); 3514 $scope.unitValidators[unitKey] = {}; 3515 if (!defaultUnit) defaultUnit = unitKey; 3516 3517 if (unitObj['validators']) { 3518 if (Array.isArray(unitObj['validators'])) { 3519 unitObj['validators'].forEach(function(elem, validatorKey) { 3520 _extractUnitValidator(elem, unitKey, validatorKey); 3521 }); 3522 } 3523 else { 3524 for (const validatorKey in unitObj['validators']) { 3525 _extractUnitValidator(unitObj['validators'][validatorKey], unitKey, validatorKey); 3526 } 3527 } 3528 } 3529 } 3530 }; 3531 3532 if ($scope.units) { 3533 if (Array.isArray($scope.units)) {
3534 $scope.units.forEach(_extractUnitData); 3535 } 3536 else { // assume to be an object 3537 for (const unitKey in $scope.units) { 3538 _extractUnitData($scope.units[unitKey], unitKey) 3539 } 3540 } 3541 } 3542 3543 // we store the mpan value internally as an object, regardless of the asString setting 3544 var unsetFormData = function() { 3545 $scope.formData = { 3546 value: null, 3547 unit: defaultUnit 3548 }; 3549 } 3550 unsetFormData(); 3551 3552 /** 3553 * @return {String} 3554 */ 3555 var formDataToString = function() { 3556 return $scope.formData.value + $scope.formData.unit; 3557 }; 3558 3559 /** 3560 * Updates the passed in ngModel with the numericUnitForm formData. 3561 * Will only perform the update if all of numericUnitForm's fields are valid. 3562 * 3563 * @return {undefined} 3564 */ 3565 $scope.updateNgModel = function() { 3566 updatedViaNgChange = true; 3567 3568 if ($scope[INNER_FORM_NAME].$valid) { 3569 switch ($scope.asType) { 3570 case 'string': 3571 ctrl.$setViewValue(formDataToString()); 3572 break; 3573 default: 3574 ctrl.$setViewValue($scope.formData); 3575 } 3576 } 3577 else { 3578 ctrl.$setViewValue(undefined); 3579 } 3580 }; 3581 3582 var updateInternalModel = function(value) { 3583 if (value === undefined || value === null) { 3584 unsetFormData(); 3585 } 3586 else if (typeof value === 'object') { 3587 $scope.formData = { 3588 value: ctrl.$modelValue.value, 3589 unit: ctrl.$modelValue.unit 3590 }; 3591 } 3592 else if (typeof value === 'number') { 3593 $scope.formData.value = ctrl.$modelValue; 3594 } 3595 else if (typeof value === 'string') { 3596 // given a string that appears to be a representation of a numerical value plus a unit, try to split them 3597 // e.g. 5.65ppm => 5.65 and 'ppm' 3598 3599 const regex = /(?<value>\d*\.?\d*)(?<unit>.*)/gm; 3600 const results = regex.exec(ctrl.$modelValue); 3601 3602 $scope.formData = { 3603 value: results.groups.value, 3604 unit: results.groups.unit?.trim() 3605 }; 3606 } 3607 }; 3608 3609 var cancelWatch = $scope.$watch(() => ctrl.$modelValue, function(newVal, oldVal) { 3610 // an ngChange call that we've internally set up will execute before this watch fires. We don't want to waste time writing back an identical value 3611 // to our internal model if the change to $modelValue came from us. 3612 if (!updatedViaNgChange) { 3613 updateInternalModel(newVal); 3614 } 3615 3616 updatedViaNgChange = false; 3617 }); 3618 3619 var statusSyncerCancelFns = customInputMixin.initStatusSyncers($scope, INNER_FORM_NAME, ctrl, DIRECTIVE_KEY); 3620 } 3621 } 3622}]); 3623 3624/** 3625* Template: /app/views/ng_templates/wpd/utils/telephone-with-type-field.html 3626* Style: ??? 3627* 3628* @author noahm 3629*/ 3630wpd.directive('wpdutilsTelephoneWithTypeField', ['wpdutilsCustomInputMixinService', '$compile', '$parse', function(customInputMixin, $compile, $parse) { 3631 return { 3632 require: 'ngModel', 3633 restrict: 'AE', 3634 scope: { 3635 isReadonly: '<', // {boolean} 3636 asType: '<', // {string} ('string'|undefined|null) 3637 fieldId: '<', // {string} 3638 fieldName: '<', // {string} 3639 isDisabled: '<', // {boolean} 3640 isRequired: '<', // {boolean} 3641 telephoneTypes: '<', // {Object[]} 3642 placeholder: '<', // {string} 3643 }, 3644 templateUrl: 'wpdutilsTelephoneWithTypeField.html',
3645 compile: function($elem, $attrs) { 3646 // Add the validator attribute prior to compile 3647 3648 // This function modifies the template itself, not the instance, 3649 // so if this isn't the first use of this directive, we need to clean it up 3650 const telephoneField = $elem.find('input[type="tel"]'); 3651 telephoneField.removeAttr('wpdutils-is-telephone-mobile wpdutils-is-telephone-tight wpdutils-is-telephone'); 3652 3653 // Which validator to use (telephone-number-validator attribute)? 3654 // Because we're pre-compile, the $attrs haven't been parsed yet :( 3655 let validatorAttr = $parse($attrs.telephoneNumberValidator)(); 3656 if (validatorAttr) { 3657 // Add the data- prefix if necessary 3658 if (!validatorAttr.startsWith('data-')) { 3659 validatorAttr = 'data-' + validatorAttr; 3660 } 3661 } 3662 else { 3663 // Default to the standard telephone validator 3664 validatorAttr = 'wpdutils-is-telephone'; 3665 } 3666 3667 // Add the selected validator to the telephone number field 3668 telephoneField.attr(validatorAttr, ''); 3669 3670 3671 // Standard post-link function 3672 return function($scope, $elem, $attrs, ctrl) { 3673 const DIRECTIVE_KEY = 'wpdutilsTelephoneWithTypeField'; 3674 const INNER_FORM_NAME = 'telephoneWithTypeForm'; 3675 3676 var updatedViaNgChange = false; 3677 3678 const defaultType = $scope.telephoneTypes?.length ? $scope.telephoneTypes[0]?.value : null; 3679 3680 // we store the value internally as an object, regardless of the asString setting 3681 var unsetFormData = function() { 3682 $scope.formData = { 3683 telephone: null, 3684 type: defaultType 3685 }; 3686 } 3687 unsetFormData(); 3688 3689 if ($scope?.$parent?.vm?.formData?.hasOwnProperty($scope.fieldName)) { 3690 $scope.formData = $scope?.$parent?.vm?.formData[$scope.fieldName]; 3691 } 3692 3693 /** 3694 * Updates the passed in ngModel with the telephoneWithTypeForm formData. 3695 * Will only perform the update if all of telephoneWithTypeForm's fields are valid. 3696 * 3697 * @return {undefined} 3698 */ 3699 $scope.updateNgModel = function() { 3700 updatedViaNgChange = true; 3701 3702 if ($scope[INNER_FORM_NAME].$valid) { 3703 ctrl.$setViewValue($scope.formData); 3704 } 3705 else { 3706 ctrl.$setViewValue(undefined); 3707 } 3708 }; 3709 3710 var updateInternalModel = function(value) { 3711 if (value === undefined || value === null) { 3712 unsetFormData(); 3713 } 3714 else if (typeof value === 'object') { 3715 $scope.formData = { 3716 telephone: value.telephone, 3717 type: value.type 3718 }; 3719 } 3720 else if (typeof value === 'number' || typeof value === 'string') { 3721 $scope.formData.telephone = value + ''; 3722 } 3723 } 3724 3725 var cancelWatch = $scope.$watch(() => ctrl.$modelValue, function(newVal, oldVal) { 3726 if (!updatedViaNgChange) updateInternalModel(newVal); 3727 updatedViaNgChange = false; 3728 }); 3729 3730 var statusSyncerCancelFns = customInputMixin.initStatusSyncers($scope, INNER_FORM_NAME, ctrl, DIRECTIVE_KEY); 3731 }; 3732 } 3733 }; 3734}]); 3735 3736 3737// =========================================================================================== 3738// ------------------------------ Form validation directives --------------------------------- 3739// =========================================================================================== 3740 3741/** 3742 * @return {boolean} - true if viewValue is a postcode that exists within the shared data service (has a dno). 3743 */ 3744wpd.directive('registeredPostcodeCheck', ['$requester', '$q', function($requester, $q) { 3745 var json = $requester({ 3746 registeredPostcodeCheck: 'WPDUtilsFront.registeredPostcode_JSON' 3747 });
3748 3749 return { 3750 require: 'ngModel', 3751 link: function($scope, $elem, $attrs, ctrl) { 3752 const DIRECTIVE_KEY = 'registeredPostcodeCheck'; 3753 3754 if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {}; 3755 3756 // Set up the custom error message for this ngModel on failure of this validator 3757 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 3758 3759 ctrl.$asyncValidators[DIRECTIVE_KEY] = function(modelValue, viewValue) { 3760 return $q(function(resolve, reject) { 3761 if (ctrl.$isEmpty(modelValue)) { 3762 // consider empty models to be valid 3763 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 3764 resolve(); 3765 } 3766 3767 if (viewValue) { 3768 json.registeredPostcodeCheck({postcode: viewValue}, function(response) { 3769 if (response && response.areaStatus) { 3770 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 3771 resolve(); 3772 } 3773 else { 3774 ctrl.customErrorMessages[DIRECTIVE_KEY] = "Sorry, this postcode isn't registered."; 3775 reject(); 3776 } 3777 }); 3778 } 3779 else { 3780 ctrl.customErrorMessages[DIRECTIVE_KEY] = "Please enter a valid postcode"; 3781 reject(); 3782 } 3783 }); 3784 }; 3785 } 3786 }; 3787}]); 3788 3789 3790 3791// Author: @ianbe 3792wpd.directive('wpdutilsCheckboxGroupField', 3793 ['wpdutilsCustomInputMixinService', '$timeout', function(customInputMixin, $timeout) { 3794 return { 3795 require: 'ngModel', 3796 restrict: 'AE', 3797 scope: { 3798 isReadonly: '<', // {boolean} 3799 fieldId: '<', // {string} 3800 fieldName: '<', // {string} 3801 disabled: '<', // {boolean} 3802 required: '<', // {boolean} 3803 placeholder: '<', // {string} 3804 wrapperCssClasses: '@', // {string} 3805 3806 options: '<', // {Object[]} 3807 3808 type: '<', // {string} 3809 3810 // Indexed types only 3811 trueForChecked: '<', // {boolean} 3812 3813 // Ignored if type is 'array' 3814 falseForUnchecked: '<', // {boolean} // Takes priority over nullForUnchecked 3815 nullForUnchecked: '<', // {boolean} 3816 }, 3817 templateUrl: 'wpdutilsCheckboxGroupField.html', 3818 link: function($scope, $elem, $attrs, ctrl) { 3819 var i; 3820 3821 const DIRECTIVE_KEY = 'wpdUtilsCheckboxField'; 3822 const INNER_FORM_NAME = 'checkboxCollectionForm'; 3823 3824 var COLLECTION_TYPE = 'indexObject'; 3825 var TRUE_FOR_CHECKED = false; 3826 var FALSE_VALUE = undefined; 3827 3828 const ngModelCtrl = Array.isArray(ctrl) ? ctrl[0] : ctrl; 3829 3830 // this version of the internal model is just going to be the object as built by the vanilla behaviour (similar to indexObject, but with undefined/null) 3831 // the ngmodel with our desired structure will be built separately from it 3832 $scope.formData = { 3833 checkboxCollection: {} 3834 }; 3835 3836 switch ($scope.type) { 3837 case 'array': 3838 // Array type only contains checked values, from top to bottom. Indexes are lost but there is no empty space. 3839 // Good for storing a simple list of selected options. 3840 // Example: [ "VALUE2", "VALUE4" ] 3841 COLLECTION_TYPE = 'array'; 3842 break; 3843 case 'object': 3844 // Object type uses the option value as the key and the checked state (true/false) as the value. 3845 // This allows you to treat each option almost as its own separate boolean variable. 3846 // If falseForUnchecked or nullForUnchecked are set, all boxes will be present, otherwise only checked boxes will be present. 3847 // Example: { "VALUE1": FALSE_VALUE, "VALUE2": true, "VALUE3": FALSE_VALUE, "VALUE4": true } 3848 COLLECTION_TYPE = 'object'; 3849 break; 3850 case 'indexArray': 3851 // IndexArray type is almost identical to IndexObject type, but it uses an array instead of an object. 3852 // Example: [ 0: undefined, 1: FALSE_VALUE, 2: "VALUE2", 3: FALSE_VALUE, 4: "VALUE4" ] 3853 COLLECTION_TYPE = 'indexArray'; 3854 break; 3855 default: 3856 // indexObject (default): 3857 // IndexObject is similar to vanilla checkbox behaviour. The index is the key, and checked boxes will have their option value as the value. 3858 // falseForUnchecked or nullForUnchecked are available as de
3858scribed above. 3859 // trueForChecked will set the value to true instead of the option value. 3860 // Example: { 1: FALSE_VALUE, 2: "VALUE2", 3: FALSE_VALUE, 4: "VALUE4" } 3861 break; 3862 } 3863 3864 if (COLLECTION_TYPE !== 'array') { 3865 if ($scope.falseForUnchecked) { 3866 FALSE_VALUE = false; 3867 } 3868 else if ($scope.nullForUnchecked) { 3869 FALSE_VALUE = null; 3870 } 3871 3872 if (COLLECTION_TYPE !== 'object') { 3873 TRUE_FOR_CHECKED = ($scope.trueForChecked) ? true : false; 3874 } 3875 } 3876 3877 3878 3879 function checkValidity() { 3880 const validCount = Object.values($scope?.formData?.checkboxCollection).filter(o => typeof o === 'string').length; 3881 ngModelCtrl.$setValidity("required-empty", validCount > 0); 3882 } 3883 3884 3885 var directiveCausedUpdate = false; 3886 $scope.updateNgModel = function() { 3887 if ($scope[INNER_FORM_NAME].$valid) { 3888 directiveCausedUpdate = true; 3889 let viewValue = generateViewModel($scope.formData.checkboxCollection); 3890 ngModelCtrl.$setViewValue(viewValue); 3891 } 3892 3893 if ($scope.required) checkValidity(); 3894 }; 3895 3896 var generateViewModel = function(input) { 3897 let output; 3898 if (COLLECTION_TYPE === 'array') { 3899 output = Object.values(input).filter(val => typeof val === 'string'); 3900 } 3901 else if (COLLECTION_TYPE === 'object') { 3902 output = {}; 3903 if (FALSE_VALUE !== undefined) { 3904 for (const k of $scope.options) { 3905 output[k.value] = FALSE_VALUE; 3906 } 3907 } 3908 Object.entries(input).forEach(([k, v]) => { 3909 if (typeof v === 'string') { 3910 output[v] = true; 3911 } 3912 }); 3913 } 3914 else if (['indexArray', 'indexObject'].includes(COLLECTION_TYPE)) { 3915 output = (COLLECTION_TYPE === 'indexArray') ? [] : {}; 3916 if (FALSE_VALUE !== undefined) { 3917 for (i = 1; i <= $scope.options.length; i++) { 3918 output[i] = FALSE_VALUE; 3919 } 3920 } 3921 Object.entries(input).forEach(([k, v]) => { 3922 let value; 3923 if (typeof v === 'string') { 3924 value = TRUE_FOR_CHECKED ? true : v; 3925 } 3926 else value = FALSE_VALUE; 3927 output[k] = value; 3928 }); 3929 } 3930 return output; 3931 } 3932 3933 3934 var fieldsByIndex = []; 3935 var fieldsByValue = {}; 3936 i = 1; 3937 for (let o of $scope.options) { 3938 fieldsByIndex[i] = o.value; 3939 fieldsByValue[o.value] = i; 3940 i++; 3941 } 3942 3943 3944 var generateInternalModel = function(input) { 3945 let output = {}; 3946 3947 if (COLLECTION_TYPE === 'indexObject') { 3948 Object.entries(input).forEach(([k, v]) => { 3949 if (TRUE_FOR_CHECKED) { 3950 if (v === true) { 3951 output[k] = fieldsByIndex[k]; 3952 }; 3953 } 3954 else { 3955 output[k] = v; 3956 } 3957 }); 3958 } 3959 else if (COLLECTION_TYPE === 'indexArray') { 3960 input.forEach((v, k) => { 3961 if (TRUE_FOR_CHECKED) { 3962 if (v === true) { 3963 output[k] = fieldsByIndex[k]; 3964 } 3965 } 3966 else { 3967 output[k] = v; 3968 } 3969 }); 3970 } 3971 else if (COLLECTION_TYPE === 'object') { 3972 Object.entries(input).forEach(([k, v]) => { 3973 if (v === true) { 3974 output[fieldsByValue[k]] = k; 3975 } 3976 }); 3977 } 3978 else if (COLLECTION_TYPE === 'array') { 3979 input.forEach((v, k) => { 3980 output[fieldsByValue[v]] = v; 3981 }); 3982 } 3983 return output; 3984 } 3985 3986 var statusSyncerCancelFns = customInputMixin.initStatusSyncers($scope, INNER_FORM_NAME, ngModelCtrl, DIRECTIVE_KEY); 3987 3988 3989 // Wait a digest cycle to link to existing form data if present, and then rebuild the internal model from it 3990 $timeout(function() { 3991 var existingData = ngModelCtrl.$viewValue; 3992 if (existingData) { 3993 $scope.formData.checkboxCollection = generateInternalModel(existingData); 3994 } 3995 }); 3996 3997 } 3998 } 3999 }] 4000) 4001 4002/** 4003 * Uses the WIMS API's postcodeLookup endpoint to get address data for a given postcode, and populates the given fields 4004 * 4005 * @author ianbe 4006 */ 4007wpd.directive('wpdutilsAddressLookup', 4008 ['wpdutilsCustomInputMixinService', 'wpdutilsAddressLookupService', '$timeout', '$interpolate', function(customInputMixin, AddressLookupService, $timeout, $interpolate) { 4009 return { 4010 require: 'ngModel', 4011 restrict: 'AE', 4012 scope: { 4013 fieldId: '<', // {string} - the ID of the address lookup field as a whole 4014 fieldName: '<', // {string:null} - if provided, the field name within the parent form to use as the object c
4014ontaining the final address data 4015 // if not provided, the address data will be bound to the parent directly 4016 formName: '<', // {string:null} - if provided, the name of the inner form containing the search section 4017 // if not provided, "wpdutilsAddressLookup_" followed by a UUID will be used instead 4018 4019 required: '<', // {boolean:false} - whether the address is required 4020 // in practice, the main address fields are outside of this form and have their own validation, 4021 // so this is already satisfied if the address section is shown 4022 inArea: '<', // {boolean:false} - whether the postcode needs to satisfy postcodeAreaCheck validation 4023 label: '<', // {string} - the label to display above the postcode search field 4024 loqate: '<', // {string} - the handle of the API key preference to use for the address lookup service 4025 maxLengthLine1: '<', // {number} - the maximum length of the address line 1 field, only used if the address line 1 field is not provided by the API 4026 maxLengthLine2: '<', // {number} - the maximum length of the address line 2 field, only used if the address line 1 field is not provided by the API 4027 }, 4028 templateUrl: 'wpdutilsAddressLookup.html', 4029 transclude: true, 4030 link: function($scope, $elem, $attrs, ctrl, transclude) { 4031 4032 transclude($scope.$parent, function(clone) { 4033 $elem.find('[data-wpdutilsaddressfield-address-fields]').prepend(clone); 4034 }); 4035 4036 const DIRECTIVE_KEY = 'wpdutilsAddressLookup'; 4037 4038 //const INNER_FORM_NAME = $scope.formName ?? DIRECTIVE_KEY + '_' + crypto.randomUUID().replace('-', ''); 4039 const INNER_FORM_NAME = DIRECTIVE_KEY; 4040 // $scope.innerFormName = INNER_FORM_NAME; 4041 4042 const FIELDS = ['addressLine1', 'addressLine2', 'town', 'county', 'postcode']; 4043 4044 $scope.required = ($scope.required == true); 4045 $scope.inArea = ($scope.inArea == true); 4046 $scope.label = $scope.label ?? 'Address'; 4047 4048 // Interpolate the fieldId in case it contains variables 4049 $scope.fieldId = $interpolate($scope.fieldId)($scope.$parent); 4050 4051 var updatedViaNgChange = false; 4052 4053 4054 const getAddressField = function(handle) { 4055 const field = $elem.find('[data-wpdutilsaddresslookup-address=' + handle + ']'); 4056 return field ? field.controller('ngModel') : null; 4057 } 4058 4059 // Setup the loquate address lookup service 4060 const ADDRESS_LOOKUP_SERVICE = new AddressLookupService($scope.loqate); 4061 4062 // Populate the select list from a postcode search 4063 $scope.addressLookupList = null; 4064 $scope.currentlySearching = false; 4065 $scope.searchPostcode = function() { 4066 let lookupPostcode; 4067 var postcodeValid = false; 4068 4069 var postcodeSearchField = $scope[INNER_FORM_NAME].postcodeSearch; 4070 4071 if (postcodeSearchField?.$valid) 4072 { 4073 lookupPostcode = postcodeSearchField.$viewValue; 4074 postcodeValid = Object.keys(postcodeSearchField.$error)?.length === 0; 4075 } 4076 4077 if (postcodeValid && (lookupPostcode?.length >= 4)) { 4078 $scope.currentlySearching = true; 4079 ADDRESS_LOOKUP_SERVICE.addressFind(lookupPostcode).then( 4080 function(response) { 4081 $scope.currentlySearching = false; 4082 if (response?.result?.items) { 4083 $scope.addressLookupList = response.result.items; 4084 for (let item of $scope.addressLookupList) { 4085 item.label = `${item.text}, ${item.description}`; 4086 } 4087 } 4088 else { 4089 _noResultFromAddressLookup(); 4090 } 4091 }, 4092 _noResultFromAddressLookup.bind(null) 4093 ); 4094 4095 } 4096 else { 4097 $scope.addressLookupList = []; 4098 } 4099 4100 checkValidity(); 4101 }; 4102 4103 const _noResultFromAddressLookup = function() { 4104 $scope.currentlySearching = false; 4105 $scope.addressLookupList = []; 4106 } 4107 4108 // Control the visibility of the manual site address fields 4109 $scope.showAddressSection = false; 4110 $scope.setShowAddressSection = function(bool) 4111 { 4112 $timeout(function() { 4113 $scope.showAddressSection = bool; 4114 checkValidity(); 4115 4116 // If the address section is shown, don't care about validation on the (invisible) postcode search 4117 if (bool) { 4118 $scope[INNER_FORM_NAME].postcodeSearch.$setValidity('isPostcode', true); 4119 } 4120 }); 4121 } 4122 4123 $scope.currentlyGettingAddress = false; 4124 $scope.setAddress = function() { 4125 const barcode = $scope[INNER_FORM_NAME].selectedAddress.$modelValue?.id; 4126 4127 if (barcode !== null) { 4128 $scope.currentlyGettingAddress = true; 4129 ADDRESS_LOOKUP_SERVICE.addressRetrieve(barcode).then( 4130 function(response) { 4131 $scope.currentlyGettingAddress = false; 4132 if (response?.result?.address) { 4133 const address = response.result.address; 4134 4135 // If the address line 1 (and 2) is not provided by the API, we need to construct it ourselves 4136 // Adapted from Lloyd's PCR lookup 4137 if (!address.line1) { 4138 // Filter out any empty fields 4139 const fieldList = [address.company, address.subBuilding, address.buildingName, address.buildingNumber, address.street] 4140 .filter((field) => field != null && field != ''); 4141 // If exactly two fields are present, just use them as line 1 and line 2 4142 if (fieldList.length == 2) { 4143 [address.line1, address.line2] = fieldList; 4144 } 4145 // Otherwise, join the non-empty fields with a comma 4146 else { 4147 const separator = ', '; 4148 let line1 = fieldList.join(separator); 4149 // If too long for the field, trim down to last comma and move the rest to line 2 4150 const line1MaxLength = $scope.maxLengthLine1 || 255; 4151 const line2MaxLength = $scope.maxLengthLine2 || 255; 4152 if (line1.length > line1MaxLength) { 4153 const lastCommaAt = line1.lastIndexOf(separator, line1MaxLength); 4154 let line2 = line1.substring(lastCommaAt + separator.length); 4155 line1 = line1.substring(0, lastCommaAt); 4156 if (line2.length > line2MaxLength) { 4157 line2 = line2.substring(0, line2MaxLength); 4158 } 4159 address.line2 = line2; 4160 } 4161 address.line1 = (line1.length > 0) ? line1 : 'No first line of address found.'; 4162 } 4163 } 4164 4165 // sometimes county is not provided by the API (e.g. London addresses), so add a placeholder if required 4166 if ((address?.county === "") && (getAddressField('county').$$attr.required === true)) { 4167 address.county = '-'; 4168 } 4169 4170 $scope.setShowAddressSection(true); 4171 4172 const fields = { 4173 addressLine1: address.line1, 4174 addressLine2: address.line2, 4175 town: address.city, 4176 county: address.county, 4177 postcode: address.postcode, 4178 }; 4179 for (const [field, value] of Object.entries(fields)) { 4180 const addressField = getAddressField(field); 4181 if (addressField) { 4182 addressField.$setViewValue(value); 4183 addressField.$setTouched(); 4184 addressField.$render(); 4185 } 4186 } 4187 4188 // Set the lookup field in case we come back 4189 const postcodeSearchField = $scope[INNER_FORM_NAME].postcodeSearch; 4190 const previousPostcodeValue = postcodeSearchField.$viewValue; 4191 postcodeSearchField.$setViewValue(address.postcode); 4192 if (postcodeSearchField.$invalid) { 4193 postcodeSearchField.$setViewValue(previousPostcodeValue); 4194 } 4195 postcodeSearchField.$render(); 4196 4197 } 4198 }, 4199 function() { 4200 $scope.currentlyGettingAddress = false; 4201 $scope.setShowAddressSection(true); 4202 } 4203 ); 4204 } 4205 4206 checkValidity(); 4207 }; 4208 4209 // Repopulate the internal model with the existing data if it exists 4210 if ($scope?.$parent?.vm?.formData) { 4211 // If the fieldName is a dot notation path or array notation path, split it and reduce to get the value 4212 // - filter(Boolean) is just a truthiness test equivalent to !! to get rid of empty strings from the split 4213 let repopulated = false; 4214 let existingData = $scope.$parent.vm.formData; 4215 if ($scope.fieldName) { 4216 existingData = $scope.fieldName.split(/\.|\[|\]/).filter(
4216Boolean).reduce((acc, key) => acc && acc[key], existingData); 4217 } 4218 for (const field of FIELDS) { 4219 const addressField = getAddressField(field); 4220 if (addressField) { 4221 // Get the last part of the field name 4222 const existingName = addressField.$name.split('.').pop(); 4223 if (existingData && existingData.hasOwnProperty(existingName)) { 4224 addressField.$setViewValue(existingData[existingName]); 4225 addressField.$render(); 4226 repopulated = true; 4227 } 4228 } 4229 } 4230 if (repopulated) { 4231 $scope.setShowAddressSection(true); 4232 } 4233 } 4234 4235 const checkValidity = function() { 4236 if ($scope.required) { 4237 let ok = true; 4238 4239 // The lookup is valid if the address section is shown 4240 if (!$scope.showAddressSection) { 4241 ok = false; 4242 } 4243 4244 $scope[INNER_FORM_NAME].$setValidity('required', ok); 4245 } 4246 } 4247 4248 $scope[INNER_FORM_NAME].customErrorMessages = { 4249 required: "Required", 4250 }; 4251 4252 // Set the initial valid state 4253 $timeout(() => { 4254 const statusSyncerCancelFns = customInputMixin.initStatusSyncers($scope, INNER_FORM_NAME, ctrl, DIRECTIVE_KEY); 4255 checkValidity(); 4256 }); 4257 } 4258 }; 4259}]); 4260 4261 4262/** 4263 * Uses the WIMS API's postcodeLookup endpoint to get address data for a given postcode, and populates the given fields 4264 * Special version :) 4265 * 4266 * @author ianbe, michals 4267 */ 4268wpd.directive('wpdutilsAddressLookupWims', 4269 ['wpdutilsCustomInputMixinService', 'wpdutilsAddressLookupService', '$timeout', '$interpolate',
4270 function(customInputMixin, AddressLookupService, $timeout, $interpolate) { 4271 return { 4272 require: 'ngModel', 4273 restrict: 'AE', 4274 scope: { 4275 fieldId: '<', // {string} - ID of the address lookup field as a whole 4276 fieldName: '<', // {string|null} - field name within the parent form for the address data object; 4277 // if omitted, address data binds to the parent directly 4278 formName: '<', // {string|null} - name of the inner form containing the search section; 4279 // if omitted, defaults to "wpdutilsAddressLookup_<UUID>" 4280 required: '<', // {boolean:false} - whether an address is required 4281 inArea: '<', // {boolean:false} - whether the postcode must pass postcodeAreaCheck validation 4282 label: '<', // {string} - label displayed above the postcode search field 4283 loqate: '<', // {string} - API key preference handle for the address lookup service 4284 maxLengthLine1: '<', // {number} - max length of address line 1 (used when the API omits it) 4285 maxLengthLine2: '<', // {number} - max length of address line 2 (used when the API omits it) 4286 lockAddressFields: '<', // {boolean:false} - lock address fields after a successful WIMS lookup 4287 allowWimsLookup: '<', 4288 allowLoqateLookup: '<', 4289 allowIdnoLookup: '<' 4290 }, 4291 templateUrl: 'wpdutilsAddressLookupWIMS.html', 4292 transclude: true, 4293 link: function($scope, $elem, $attrs, ctrl, transclude) { 4294 4295 // ─── Constants ──────────────────────────────────────────────────────────── 4296 4297 const DEBUG = false; 4298 const DIRECTIVE_KEY = 'wpdutilsAddressLookupWIMS'; 4299 const INNER_FORM = DIRECTIVE_KEY; // alias for $scope[INNER_FORM] 4300 const ADDRESS_FIELDS = ['addressLine1', 'addressLine2', 'town', 'county', 'postcode']; 4301 4302 const ADDRESS_SOURCE = { 4303 MANUAL: 'MANUAL', // Manual entry or Loqate fallback 4304 LOQATE: 'LOQATE', // Loqate lookup (editable, same as MANUAL in practice) 4305 WIMS: 'WIMS', // WIMS lookup - allows MPAN retrieval later 4306 }; 4307 4308 // ─── Scope defaults ─────────────────────────────────────────────────────── 4309 4310 $scope.addressSourceEnum = ADDRESS_SOURCE; 4311 $scope.label = $scope.label ?? 'Address'; 4312 $scope.required = $scope.required == true; 4313 $scope.lockAddressFields = $scope.lockAddressFields == true; 4314 $scope.allowWimsLookup = $scope.allowWimsLookup != false; 4315 $scope.allowLoqateLookup = $scope.allowLoqateLookup != false; 4316 $scope.inArea = $scope.inArea == true; 4317 $scope.fieldId = $interpolate($scope.fieldId)($scope.$parent); 4318 $scope.allowIdnoLookup = $scope.allowIdnoLookup != false; 4319 4320 $scope.addressSource = null; 4321 $scope.addressLookupList = null; 4322 $scope.showAddressSection = false; 4323 $scope.currentlySearching = false; 4324 $scope.currentlyGettingAddress = false; 4325 $scope.postcodeAreaCheckResult = null; 4326 4327 const ADDRESS_LOOKUP_SERVICE = new AddressLookupService($scope.loqate); 4328 4329 transclude($scope.$parent, function(clone) { 4330 $elem.find('[data-wpdutilsaddressfield-address-fields]').prepend(clone); 4331 }); 4332 4333 // ─── Helpers ────────────────────────────────────────────────────────────── 4334 4335 const resetData = function(keepPostcode = false) { 4336 const savedValue = keepPostcode 4337 ? $scope[INNER_FORM]?.postcodeSearch?.$viewValue 4338 : null; 4339 // Clear address fields, unlock, and reset validation state 4340 ADDRESS_FIELDS.forEach(function(field) { 4341 const addressField = getAddressField(field); 4342 if (!addressField) return; 4343 addressField.$setViewValue(null); 4344 addressField.$setPristine(); 4345 addressField.$setUntouched(); 4346 addressField.$render(); 4347 addressField.$$element.prop('disabled', false); 4348 addressField.$$element.removeAttr('disabled'); 4349 }); 4350
4351 // Clear postcode search field 4352 const postcodeSearch = $scope[INNER_FORM]?.postcodeSearch; 4353 if (postcodeSearch) { 4354 postcodeSearch.$setViewValue(null); 4355 postcodeSearch.$setPristine(); 4356 postcodeSearch.$setUntouched(); 4357 postcodeSearch.$render(); 4358 } 4359 4360 // Wipe parent form data for this field 4361 const formData = $scope?.$parent?.vm?.formData; 4362 if (formData && $scope.fieldName) { 4363 formData[$scope.fieldName] = null; 4364 formData[$scope.fieldName + 'AddressSource'] = null; 4365 formData[$scope.fieldName + 'MPAN'] = null; 4366 } 4367 4368 // Reset all directive state 4369 $scope.addressSource = null; 4370 $scope.addressLookupList = null; 4371 $scope.showAddressSection = false; 4372 $scope.currentlySearching = false; 4373 $scope.currentlyGettingAddress = false; 4374 $scope.postcodeAreaCheckResult = null; 4375 4376 checkValidity(); 4377 4378 if (savedValue) { 4379 const postcodeSearch = $scope[INNER_FORM]?.postcodeSearch; 4380 postcodeSearch.$setViewValue(savedValue); 4381 postcodeSearch.$render(); 4382 } 4383 }; 4384 4385 $scope.resetData = resetData; 4386 4387 /** Returns the ngModel controller for a named address field, or null. */ 4388 const getAddressField = function(handle) { 4389 const field = $elem.find('[data-wpdutilsaddresslookupwims-address=' + handle + ']'); 4390 return field ? field.controller('ngModel') : null; 4391 }; 4392 4393 /** Returns the current postcode search value. */ 4394 const getPostcodeValue = function() { 4395 return cleanPostcode($scope[INNER_FORM]?.postcodeSearch?.$viewValue); 4396 }; 4397 4398 /** Returns true only if the postcode field is valid and long enough to search. */ 4399 const isPostcodeValid = function(postcode) { 4400 const field = $scope[INNER_FORM]?.postcodeSearch; 4401 return field?.$valid 4402 && Object.keys(field.$error ?? {}).length === 0 4403 && postcode?.length >= 4; 4404 }; 4405 4406 /** Re-runs form validity so that the required error reflects the current state. */ 4407 const checkValidity = function() { 4408 if ($scope.required) { 4409 let ok = true; 4410 if(!$scope.showAddressSection) { 4411 ok = false; 4412 } 4413 $scope[INNER_FORM]?.$setValidity('required', ok); 4414 } 4415 }; 4416 4417 // ─── Field locking ──────────────────────────────────────────────────────── 4418 4419 /** 4420 * Returns true when address fields should be disabled. 4421 */ 4422 $scope.addressFieldsLocked = function() { 4423 return $scope.showAddressSection 4424 && $scope.lockAddressFields 4425 && $scope.addressSource === ADDRESS_SOURCE.WIMS; 4426 }; 4427 4428 /** Applies or removes the disabled attribute on all address fields to match the lock state. */ 4429 const applyFieldLocking = function() { 4430 const locked = $scope.addressFieldsLocked(); 4431 ADDRESS_FIELDS.forEach(function(field) { 4432 const addressField = getAddressField(field); 4433 if (!addressField) return; 4434 addressField.$$element.prop('disabled', locked); 4435 if (!locked) addressField.$$element.removeAttr('disabled'); 4436 }); 4437 }; 4438 4439 // ─── Address section visibility ─────────────────────────────────────────── 4440 4441 /** Programmatic show/hide - also broadcast via $on('showAddressSection') below. */ 4442 $scope.setShowAddressSection = function(show) { 4443 $timeout(function() { 4444 $scope.showAddressSection = show; 4445 checkValidity(); 4446 4447 if (show) { 4448 // Suppress postcode-search validation while address fields are visible 4449 $scope[INNER_FORM].postcodeSearch.$setValidity('isPostcode', true); 4450 applyFieldLocking(); 4451 return; 4452 } 4453 4454 // ── Hiding: clear fields, reset state ──────────────────────────── 4455 $scope.$parent.vm.formData[$scope.fieldName + 'AddressSource'] = null; 4456 $scope.$parent.vm.formData[$scope.fieldName + 'MPAN'] = null; 4457 4458 ADDRESS_FIELDS.forEach(function(field) { 4459 const addressField = getAddressField(field); 4460 if (!addressField) return; 4461 addressField.$setViewValue(null); 4462 addressField.$setPristine(); 4463 addressField.$setUntouched(); 4464 addressField.$render(); 4465 // Always unlock when hiding 4466 addressField.$$element.prop('disabled', false); 4467 addressField.$$element.removeAttr('disabled'); 4468 }); 4469 4470 // Restore postcode into both the address field and the search f
4470ield 4471 if ($scope[INNER_FORM]?.postcodeSearch?.$viewValue) { 4472 const addressPostcodeField = getAddressField('postcode'); 4473 if (addressPostcodeField) { 4474 addressPostcodeField.$setViewValue(getPostcodeValue()); 4475 addressPostcodeField.$render(); 4476 } 4477 const postcodeSearch = $scope[INNER_FORM]?.postcodeSearch; 4478 if (postcodeSearch) { 4479 postcodeSearch.$setViewValue(getPostcodeValue()); 4480 postcodeSearch.$render(); 4481 } 4482 } 4483 4484 $scope.addressLookupList = null; 4485 $scope.postcodeAreaCheckResult = null; 4486 }); 4487 }; 4488 4489 $scope.$on('showAddressSection', function(event, bool) { 4490 if (!bool) { 4491 resetData(); 4492 return; 4493 } 4494 $scope.setShowAddressSection(bool); 4495 }); 4496 4497 $scope.setShowAddressSectionManual = function() { 4498 $scope.addressSource = ADDRESS_SOURCE.MANUAL; 4499 _persistAddressMeta(); 4500 $scope.setShowAddressSection(true); 4501 }; 4502 4503 // ─── Postcode search ────────────────────────────────────────────────────── 4504 4505 $scope.searchPostcode = function() { 4506 resetData(true); 4507 $scope.currentlySearching = true; 4508 4509 const lookupPostcode = getPostcodeValue(); 4510 if (!isPostcodeValid(lookupPostcode)) { 4511 DEBUG && console.log(DIRECTIVE_KEY + ': searchPostcode: postcode invalid, not searching'); 4512 $scope.addressLookupList = []; 4513 $scope.currentlySearching = false; 4514 checkValidity(); 4515 return; 4516 } 4517 4518 ADDRESS_LOOKUP_SERVICE.postcodeLookup(lookupPostcode) 4519 .then(response => _handleWimsResponse(response, lookupPostcode, $scope.allowWimsLookup)) 4520 .then(response => _handleLoqateResponse(response, lookupPostcode, $scope.allowLoqateLookup)) 4521 .catch(err => _handleLookupError(err)) 4522 .finally(() => _finaliseLookup()); 4523 }; 4524 4525 // ─── Lookup response handlers ───────────────────────────────────────────── 4526 4527 function cleanPostcode(value) { 4528 if(!value) return value; 4529 const formatted = (value + '') 4530 .replace(/\s/g, '') 4531 .toUpperCase(); 4532 return formatted.substring(0,formatted.length - 3) + ' ' + formatted.substring(formatted.length - 3); 4533 } 4534 4535 function _handleWimsResponse(response, lookupPostcode, allowLookup) { 4536 if (!allowLookup) { 4537 DEBUG && console.log(DIRECTIVE_KEY + ': skipping WIMS, falling through to Loqate'); 4538 return ADDRESS_LOOKUP_SERVICE.addressFind(lookupPostcode); 4539 } 4540 4541 DEBUG && console.log(DIRECTIVE_KEY + ': WIMS response:', response); 4542 4543 if ((response.inArea === false && response.success) && $scope.inArea) { 4544 DEBUG && console.log(DIRECTIVE_KEY + ': WIMS out of area'); 4545 _outOfAreaResult(response); 4546 return; 4547 } 4548 4549 if (response?.results) { 4550 $scope.postcodeAreaCheckResult = null; 4551 $scope.addressLookupList = response.results.map(_mapWimsResult); 4552 $scope.addressSource = ADDRESS_SOURCE.WIMS; 4553 DEBUG && console.log(DIRECTIVE_KEY + ': WIMS mapped results:', $scope.addressLookupList); 4554 } else { 4555 $scope.addressSource = ADDRESS_SOURCE.LOQATE; 4556 DEBUG && console.log(DIRECTIVE_KEY + ': no WIMS results, falling through to Loqate'); 4557 } 4558 4559 if (!$scope.addressLookupList?.length) { 4560 DEBUG && console.log(DIRECTIVE_KEY + ': starting Loqate lookup'); 4561 return ADDRESS_LOOKUP_SERVICE.addressFind(lookupPostcode); 4562 } 4563 } 4564 4565 function _handleLoqateResponse(response,lookupPostcode, allowLookup) { 4566 if (!allowLookup) { 4567 DEBUG && console.log(DIRECTIVE_KEY + ': skipping Loqate'); 4568 return; 4569 } 4570 if (!response) { 4571 DEBUG && console.log(DIRECTIVE_KEY + ': no Loqate response, ending'); 4572 return; 4573 } 4574 4575 DEBUG && console.log(DIRECTIVE_KEY + ': Loqate response:', response); 4576 4577 if (response?.result?.items) { 4578 $scope.postcodeAreaCheckResult = null; 4579 $scope.addressLookupList = response.result.items 4580 .map(_mapLoqateResult) 4581 .filter(item => { 4582 const label = item.label.replace(/\s/g, '').toUpperCase(); 4583 return label.includes(lookupPostcode.replace(/\s/g, '').toUpperCase()); 4584 }); 4585 if($scope.addressLookupList.length > 0) { 4586 $scope.addressSource = ADDRESS_SOURCE.LOQATE; 4587 DEBUG && console.log(DIRECTIVE_KEY + ': Loqate mapped results:', $scope.addressLookupList); 4588 } else { 4589 $scope.addressSource = ADDRESS_SOURCE.MANUAL; 4590 DEBUG && console.log(DIRECTIVE_KEY + ': Loqate mapped no matching results, falling through to manaul'); 4591 _noResults(); 4592 } 4593 } else { 4594 $scope.addressSource = ADDRESS_SOURCE.MANUAL; 4595 DEBUG && console.log(DIRECTIVE_KEY + ': Loqate no results, falling through to manual'); 4596 _noResults(); 4597 } 4598 } 4599 4600 function _handleLookupError(err) { 4601 $scope.addressSource = ADDRESS_SOURCE.MANUAL; 4602 console.error(DIRECTIVE_KEY + ':' + getPostcodeValue() + ':address lookup error:', err); 4603 _noResults(); 4604 } 4605 4606 function _finaliseLookup() { 4607 DEBUG && console.log(DIRECTIVE_KEY + ':' + getPostcodeValue() + ':lookup done - list:', $scope.addressLookupList, '| source:', $scope.addressSource); 4608 4609 $scope.addressLookupList = $scope.addressLookupList.slice().sort(function(a, b) { 4610 var strA = a.addressLine2 || a.text || a.addressLine1 || a.label || ''; 4611 var strB = b.addressLine2 || b.text || b.addressLine1 || b.label || ''; 4612 4613 // Pull out the last number in the string to use as house number 4614 function houseNum(str) { 4615 if (!str) return Infinity; 4616 if (/^\d+$/.test(str)) return parseInt(str, 10); 4617 var nums = str.match(/\b(\d+)(?:-\d+)?\b/g); 4618 return nums ? parseInt(nums[nums.length - 1], 10) : Infinity; 4619 } 4620 4621 // Pull out 'street name' 4622 // FLAT 1 COOL HOUSE STREET -> COOL HOUSE STREET 4623 // HOUSE COOL HOUSE STREET - > HOUSE COOL HOUSE STREET 4624 function street(str) { 4625 if (!str) return ''; 4626 var m = str.match(/^(.*?\b\d+[\w-]*\b)\s+(.+
4626)$/); 4627 return m ? m[2].trim().toUpperCase() : str.trim().toUpperCase(); 4628 } 4629 4630 return street(strA).localeCompare(street(strB)) || 4631 houseNum(a.addressLine1 || strA) - houseNum(b.addressLine1 || strB); 4632 }); 4633 4634 $scope.currentlySearching = false; 4635 checkValidity(); 4636 } 4637 4638 function _outOfAreaResult(data) { 4639 DEBUG && console.log(DIRECTIVE_KEY + ':' + getPostcodeValue() + ':out of area result:', data); 4640 _noResults(); 4641 } 4642 4643 function _noResults() { 4644 $scope.currentlySearching = false; 4645 $scope.addressLookupList = []; 4646 _persistAddressMeta(); 4647 $scope.setShowAddressSectionManual(); 4648 } 4649 4650 // ─── Result mapping ─────────────────────────────────────────────────────── 4651 4652 function _mapWimsResult(item) { 4653 const lines = Object.keys(item) 4654 .filter(k => k.indexOf('addressline') === 0) 4655 .sort() 4656 .map(k => item[k]); 4657 const county = ('addressline9' in item) ? lines.pop() : ''; 4658 const town = lines.pop(); 4659 const addressLine1 = lines.shift(); 4660 const addressLine2 = lines.length ? lines.join(', ') : ''; 4661 return { 4662 id: item.TempID, 4663 county, 4664 town, 4665 addressLine1, 4666 addressLine2, 4667 label: [addressLine1, addressLine2, town, county].filter(Boolean).join(', '), 4668 }; 4669 } 4670 4671 function _mapLoqateResult(item) { 4672 return { ...item, label: [item.text, item.description].filter(Boolean).join(', ') }; 4673 } 4674 4675 // ─── Set address fields from selection ──────────────────────────────────── 4676 4677 /** 4678 * Writes values into address fields and syncs the postcode search field. 4679 * If the postcode would make the search field invalid, the previous value is restored. 4680 */ 4681 const _applyFieldValues = function(fields) { 4682 for (const [field, value] of Object.entries(fields)) { 4683 const addressField = getAddressField(field); 4684 const maxLength = parseInt(addressField.$$element.attr('maxlength'), 10) || 255; 4685 if (addressField) { 4686 addressField.$setViewValue(value.substring(0,maxLength)); 4687 addressField.$setTouched(); 4688 addressField.$render(); 4689 } 4690 } 4691 4692 const postcodeSearch = $scope[INNER_FORM].postcodeSearch; 4693 const previousPostcode = postcodeSearch.$viewValue; 4694 postcodeSearch.$setViewValue(fields.postcode); 4695 if (postcodeSearch.$invalid) { 4696 DEBUG && console.log(DIRECTIVE_KEY + ': postcode search invalid after update, reverting'); 4697 postcodeSearch.$setViewValue(previousPostcode); 4698 } 4699 postcodeSearch.$render(); 4700 }; 4701 4702 /** Persists address metadata (source, id) to the parent form data. */ 4703 const _persistAddressMeta = function(extraData) { 4704 const formData = $scope?.$parent?.vm?.formData; 4705 const fieldName = $scope.fieldName; 4706 if (formData && fieldName) { 4707 if (!formData[fieldName]) { 4708 formData[fieldName] = {}; 4709 } 4710 formData[fieldName].addressSource = $scope.addressSource; 4711 if (extraData) { 4712 Object.assign(formData[fieldName], extraData); 4713 } 4714 } else { 4715 console.error(DIRECTIVE_KEY + ': failed - missing formData or fieldName'); 4716 } 4717 }; 4718 4719 $scope.setAddress = function() { 4720 DEBUG && console.log(DIRECTIVE_KEY + ': setAddress, source =', $scope.addressSource); 4721 4722 if ($scope.addressSource === ADDRESS_SOURCE.WIMS) { 4723 _setAddressFromWims(); 4724 } else if ($scope.addressSource === ADDRESS_SOURCE.LOQATE) { 4725 _setAddressFromLoqate(); 4726 } 4727 4728 // Always persist the source, even for LOQATE (meta only - fields set async above) 4729 _persistAddressMeta(); 4730 checkValidity(); 4731 }; 4732 4733 function _setAddressFromWims() { 4734 const selected = $scope.addressLookupList.find( 4735 item => item.id === $scope[INNER_FORM].selectedAddress.$modelValue?.id 4736 ); 4737 if (!selected) return; 4738 DEBUG && console.log(DIRECTIVE_KEY + ': WIMS selected:', selected); 4739 4740 $scope.currentlyGettingAddress = true; 4741 $scope.postcodeAreaCheckResult = null; 4742 4743 if (!$scope.allowIdnoLookup) { 4744 // No IDNO check needed (e.g. correspondence address) - skip straight to populating 4745 $scope.currentlyGettingAddress = false; 4746 _populateWimsFields(selected); 4747 return; 4748 } 4749 4750 ADDRESS_LOOKUP_SERVICE.checkMPANForIDNO(selected.id, getPostcodeValue()) 4751 .then(response => { 4752 $scope.currentlyGettingAddress = false; 4753 console.log(response); 4754 if (!response?.wpd && response.success) { 4755 // Address is outside WPD area - show DNO info and block progression 4756 $scope.addressLookupList = null; 4757 $scope.postcodeAreaCheckResult = { 4758 manual: true, 4759 message: "Your postcode is maintained by an Independent Network Operator (iDNO)", 4760 dnos: response?.success ? [{ 4761 name: (response.companyName || response.dno || '').trim(), 4762 dno: (response.dno || '').trim(), 4763 telephone: (response.companyPhoneNumber || '').trim(), 4764 isWPD: '0', 4765 url: null, 4766 prefixNo: response.prefixNo 4767 }] : [] 4768 }; 4769 const postcodeEl = $elem.find('[data-postcode-area-check]'); 4770 if (postcodeEl.length) { 4771 const postcodeNgModelCtrl = postcodeEl.controller('ngModel'); 4772 if (postcodeNgModelCtrl) { 4773 postcodeNgModelCtrl.customErrorMessages['postcodeAreaCheck'] = $scope.postcodeAreaCheckResult.message; 4774 postcodeNgModelCtrl.$setValidity('postcodeAreaCheck', false); 4775 postcodeNgModelCtrl.$setTouched(); 4776 } 4777 } 4778 return; // stop here, don't populate fields 4779 } 4780 // In area - proceed to populate 4781 _populateWimsFields(selected); 4782 }) 4783 // If the lookup for iDNO fails, just populate the fields as if it succeeded (we are permissive here) 4784 .catch(function(err) {console.error("iDNO check error", err); _populateWimsFields(selected)}); 4785 } 4786 4787 function _populateWimsFields(selected) { 4788 const fields = { 4789 addressLine1: selected.addressLine1, 4790 addressLine2: selected.addressLine2, 4791 town: selected.town, 4792 county: selected.county, 4793 postcode: selected.postcode, 4794 }; 4795 if (!fields.county && getAddressField('county')?.$$attr.required === true) fields.county = '-'; 4796 if (!fields.postcode && getAddressField('postcode')?.$$attr.required === true) { 4797 fields.postcode = $scope[INNER_FORM].postcodeSearch.$viewValue.toUpperCase(); 4798 } 4799 // Clear any previous out-of-area error state on the postcode field 4800 const postcodeEl = $elem.find('[data-postcode-area-check]'); 4801 if (postcodeEl.length) { 4802 const postcodeNgModelCtrl = postcodeEl.controller('ngModel'); 4803 if (postcodeNgModelCtrl) { 4804 postcodeNgModelCtrl.customErrorMessages['postcodeAreaCheck'] = null; 4805 postcodeNgModelCtrl.$setValidity('postcodeAreaCheck', true); 4806 } 4807 } 4808 $scope.setShowAddressSection(true); 4809 $scope.postcodeAreaCheckResult = null; 4810 _applyFieldValues(fields); 4811 _persistAddressMeta(selected.id ? { id: selected.id } : { id: null });
4812 $timeout(applyFieldLocking); 4813 } 4814 4815 $scope.$watch( 4816 function() { return ctrl.$modelValue; }, 4817 function(newVal, oldVal) { 4818 if (newVal !== oldVal && $scope.postcodeAreaCheckResult?.manual) { 4819 $scope.postcodeAreaCheckResult = null; 4820 } 4821 } 4822 ); 4823 4824 function _setAddressFromLoqate() { 4825 const barcode = $scope[INNER_FORM].selectedAddress.$modelValue?.id; 4826 if (barcode === null) return; 4827 4828 $scope.currentlyGettingAddress = true; 4829 DEBUG && console.log(DIRECTIVE_KEY + ': doing Loqate retrieve for', barcode); 4830 4831 ADDRESS_LOOKUP_SERVICE.addressRetrieve(barcode).then( 4832 function(response) { 4833 $scope.currentlyGettingAddress = false; 4834 DEBUG && console.log(DIRECTIVE_KEY + ': Loqate retrieve response:', response); 4835 4836 if (!response?.result?.address) return; 4837 const address = response.result.address; 4838 4839 // Build line1 (and optionally line2) when the API doesn't provide them 4840 if (!address.line1) { 4841 _buildLoqateAddressLines(address); 4842 } 4843 4844 // Fill placeholder for required county when the API returns empty string 4845 if (address?.county === '' && getAddressField('county').$$attr.required === true) { 4846 address.county = '-'; 4847 } 4848 4849 $scope.setShowAddressSection(true); 4850 $scope.postcodeAreaCheckResult = null; 4851 4852 _applyFieldValues({ 4853 addressLine1: address.line1, 4854 addressLine2: address.line2, 4855 town: address.city, 4856 county: address.county, 4857 postcode: address.postcode, 4858 }); 4859 4860 _persistAddressMeta(); 4861 4862 // Loqate addresses are never locked - no applyFieldLocking call here 4863 }, 4864 function() { 4865 $scope.currentlyGettingAddress = false; 4866 $scope.setShowAddressSection(true); 4867 } 4868 ); 4869 } 4870 4871 /** 4872 * Mutates `address` to populate line1/line2 from sub-fields when the API omits them. 4873 * Adapted from Lloyd's PCR lookup. 4874 */ 4875 function _buildLoqateAddressLines(address) { 4876 const line1MaxLength = $scope.maxLengthLine1 || 255; 4877 const line2MaxLength = $scope.maxLengthLine2 || 255; 4878 const separator = ', '; 4879 4880 const parts = [address.company, address.subBuilding, address.buildingName, address.buildingNumber, address.street] 4881 .filter(f => f != null && f !== ''); 4882 4883 if (parts.length === 2) { 4884 [address.line1, address.line2] = parts; 4885 return; 4886 } 4887 4888 let line1 = parts.join(separator); 4889 4890 if (line1.length > line1MaxLength) { 4891 const splitAt = line1.lastIndexOf(separator, line1MaxLength); 4892 let line2 = line1.substring(splitAt + separator.length); 4893 line1 = line1.substring(0, splitAt); 4894 if (line2.length > line2MaxLength) line2 = line2.substring(0, line2MaxLength); 4895 address.line2 = line2; 4896 } 4897 4898 address.line1 = line1.length > 0 ? line1 : 'No first line of address found.'; 4899 } 4900 4901 // ─── Pre-populate from existing form data ───────────────────────────────── 4902 4903 (function repopulateFromExistingData() { 4904 if (!$scope?.$parent?.vm?.formData) return; 4905 4906 DEBUG && console.log(DIRECTIVE_KEY + ': repopulating address fields from existing form data'); 4907 4908 let existingData = $scope.$parent.vm.formData; 4909 if ($scope.fieldName) { 4910 existingData = $scope.fieldName 4911 .split(/\.|\[|\]/) 4912 .filter(Boolean) 4913 .reduce((acc, key) => acc && acc[key], existingData); 4914 } 4915 4916 let repopulated = false; 4917 ADDRESS_FIELDS.forEach(function(field) { 4918 const addressField = getAddressField(field); 4919 if (!addressField) return; 4920 const existingKey = addressField.$name.split('.').pop(); 4921 if (existingData?.hasOwnProperty(existingKey)) { 4922 addressField.$setViewValue(existingData[existingKey]); 4923 addressField.$render(); 4924 repopulated = true; 4925 } 4926 }); 4927 4928 if (repopulated) $scope.setShowAddressSection(true); 4929 })(); 4930 4931 // ─── Initialisation ─────────────────────────────────────────────────────── 4932 4933 $scope[INNER_FORM].customErrorMessages = { required: 'Required' }; 4934 4935 $timeout(function() { 4936 customInputMixin.initStatusSyncers($scope, INNER_FORM, ctrl, DIRECTIVE_KEY); 4937 // Repopulate address source 4938 if ($scope?.$parent?.vm?.formData) { 4939 const data = $scope.fieldName 4940 ? $scope.$parent.vm.formData[$scope.fieldName] 4941 : $scope.$parent.vm.formData; 4942 if (data?.addressSource) { 4943 $scope.addressSource = data.addressSource; 4944 } 4945 } 4946 $scope.$watch( 4947 () => $scope[INNER_FORM]?.postcodeSearch?.postcodeAreaCheckResult, 4948 (newVal) => { $scope.postcodeAreaCheckResult = newVal; }, 4949 true 4950 ); 4951 checkValidity(); 4952 applyFieldLocking(); 4953 }); 4954 } 4955 }; 4956}]); 4957 4958 4959 4960/** 4961 * Template: /app/views/ng_templates/wpd/utils/upload-field.html 4962 * Style: ??? 4963 * 4964 * Bringing the uploadField into 2025, so the controller doesn't need to handle the actual upload behaviour 4965 * 4966 * @author ianbe 4967 */ 4968wpd.directive('wpdutilsUploadField', 4969 [ 'wpdutilsCustomInputMixinService', '$asset_upload', '$q', function(customInputMixin, $asset_upload, $q) { 4970 return { 4971 require: 'ngModel', 4972 restrict: 'AE', 4973 scope: { 4974 isReadonly: '<', // {boolean} 4975 asType: '<', // {string} ('string'|undefined|null) 4976 fieldId: '<', // {string} 4977 fieldName: '<', // {string} 4978 isDisabled: '<', // {boolean} 4979 isRequired: '<', // {boolean} 4980 placeholder: '<', // {string} 4981 allowedTypes: '<', // {Object[]} 4982 singleFile: '<', // {boolean} 4983 minFiles: '<', 4984 maxFiles: '<', 4985 multiSelection: '<', // {boolean} 4986 privateAsset: '<', // {boolean} 4987 hideMaxFiles: '<', 4988 4989 onStart: '=', // {function} 4990 onProgress: '=', // {function} 4991 onSuccess: '=', // {function} 4992 onInvalid: '=', // {function} 4993 4994 buttonLabel: '<', // {string} 4995 deleteButtonLabel: '<', // {string|boolean} 4996 // TODO unimplemented 4997 deleteButtonClass: '<', 4998 browseButtonClass: '<', 4999 }, 5000 templateUrl: 'wpdutilsUploadField.html',
5001 link: function($scope, $elem, $attrs, ctrl) { 5002 const DIRECTIVE_KEY = 'wpdutilsUploadField'; 5003 const INNER_FORM_NAME = $scope.fieldName.replace('.', '_') + '_' + DIRECTIVE_KEY; 5004 5005 $scope.singleFile = ($scope.singleFile == true); 5006 $scope.maxFiles = $scope.singleFile ? 1 : ($scope.maxFiles ?? 5); 5007 $scope.multiSelection = $scope.singleFile ? false : ($scope.multiSelection == true); 5008 $scope.isRequired = ($scope.isRequired == true); 5009 5010 $scope.browseButtonId = $scope.fieldId + "__browse-button"; 5011 $scope.deleteSectionId = $scope.fieldId + "__delete"; 5012 5013 if ($scope.deleteButtonLabel === true) { 5014 $scope.deleteButtonLabel = 'Remove'; 5015 } 5016 5017 const filesOverMaximum = []; 5018 5019 var unsetFormData = function() { 5020 $scope.formData = []; 5021 filesOverMaximum.length = 0; 5022 }; 5023 unsetFormData(); 5024 5025 // Repopulate the internal model with the existing data if it exists 5026 if ($scope?.$parent?.vm?.formData) { 5027 // If the fieldName is a dot notation path or array notation path, split it and reduce to get the value 5028 // - filter(Boolean) is just a truthiness test equivalent to !! to get rid of empty strings from the split 5029 // - not sure how this works if the target value is falsish, so be careful if you want to apply this to another field type 5030 const existingData = $scope.fieldName.split(/\.|\[|\]/).filter(Boolean).reduce((acc, key) => acc && acc[key], $scope.$parent.vm.formData); 5031 if (existingData !== undefined) { 5032 if ($scope.singleFile) { 5033 $scope.formData.push(existingData); 5034 } else { 5035 $scope.formData.push(...existingData); 5036 } 5037 } 5038 } 5039 5040 $scope.uploading = false; 5041 5042 var uploadOnSuccess = function(files) { 5043 if ($scope.onSuccess) { 5044 $scope.onSuccess.call(this, files, _uploadOnSuccess); 5045 } 5046 else { 5047 _uploadOnSuccess(files); 5048 } 5049 }; 5050 5051 var _uploadOnSuccess = function(files) { 5052 filesOverMaximum.length = 0; 5053 5054 if (!files) return; 5055 5056 for (var i in files) { 5057 if ($scope.formData.length < $scope.maxFiles) { 5058 filesOverMaximum.push(files[i]); 5059 } 5060 else { 5061 $scope.filesOverMaximum.push(files[i]); 5062 } 5063 } 5064 5065 if (filesOverMaximum.length) { 5066 // TODO trigger the over-max message 5067 } 5068 5069 $scope.updateNgModel(); 5070 ctrl.$validate(); 5071 5072 uploadOnFinally(); 5073 } 5074 5075 var _uploadOnProgress = function(progress) { 5076 }; 5077 5078 $scope.previousUploadPercentage = 0; 5079 $scope.uploadPercentage = 0; 5080 var uploadOnProgress = function(progress) { 5081 if (progress?.total) { 5082 $scope.previousUploadPercentage = $scope.uploadPercentage; 5083 $scope.uploadPercentage = progress.total.percent; 5084 } 5085 5086 if ($scope.onProgress) { 5087 $scope.onProgress.call(this, progress, _uploadOnProgress); 5088 } 5089 else { 5090 _uploadOnProgress(progress); 5091 } 5092 }; 5093 5094 var _uploadOnInvalid = function(data, error) { 5095 let message = "There was a problem uploading your file"; 5096 const messageExtra = error.response ?? error.message; 5097 message += (messageExtra) ? ":\n\n" + messageExtra : "."; 5098 window.alert(message); 5099 }; 5100
5101 var uploadOnInvalid = function(data, error) { 5102 if ($scope.onInvalid) { 5103 $scope.onInvalid.call(this, data, error, _uploadOnInvalid); 5104 } 5105 else { 5106 _uploadOnInvalid(data, error); 5107 } 5108 5109 uploadOnFinally(); 5110 } 5111 5112 var _uploadOnStart = function(files) { 5113 5114 }; 5115 5116 var uploadOnStart = function(files) { 5117 $scope.uploading = true; 5118 $scope.previousUploadPercentage = 0; 5119 $scope.uploadPercentage = 0; 5120 5121 if ($scope.onStart) { 5122 $scope.onStart.call(this, files, _uploadOnStart); 5123 } 5124 else { 5125 _uploadOnStart(files); 5126 } 5127 } 5128 5129 var uploadOnFinally = function() { 5130 $scope.uploading = false; 5131 } 5132 5133 $scope.removeFile = function(index) { 5134 $scope.formData.splice(index, 1); 5135 $scope.updateNgModel(); 5136 ctrl.$validate(); 5137 }; 5138 5139 var numberOfValidFiles = function() { 5140 const value = $scope.formData; 5141 if (typeof value != 'object') { 5142 return false; 5143 } 5144 return value.filter(o => o.result == 'success').length; 5145 } 5146 5147 ctrl.$validators.required = function(modelValue, viewValue) { 5148 if (!$scope.isRequired) return true; 5149 5150 if ((ctrl.$isEmpty(viewValue)) || (typeof viewValue != 'object')) { 5151 return false; 5152 } 5153 5154 if ($scope.singleFile) { 5155 if (viewValue.result != 'success') { 5156 return false; 5157 } 5158 } 5159 else if (!viewValue.some(o => o.result == 'success')) { 5160 return false; 5161 } 5162 5163 return true; 5164 }; 5165 5166 ctrl.$validators.min = function(modelValue, viewValue) { 5167 if (($scope.minFiles == null) || ($scope.minFiles < 1)) return true; 5168 return (numberOfValidFiles() >= $scope.minFiles); 5169 } 5170 5171 ctrl.$validators.max = function(modelValue, viewValue) { 5172 if (($scope.maxFiles == null) || ($scope.maxFiles < 1)) return true; 5173 return (numberOfValidFiles() <= $scope.maxFiles); 5174 } 5175 5176 var updatedViaNgChange = false; 5177 $scope.updateNgModel = function() { 5178 updatedViaNgChange = true; 5179 5180 if ($scope[INNER_FORM_NAME].$valid) { 5181 ctrl.$setViewValue($scope.singleFile ? ($scope.formData[0] ?? null) : $scope.formData); 5182 } 5183 else { 5184 ctrl.$setViewValue(undefined); 5185 } 5186 }; 5187 5188 var cancelWatch = $scope.$watch(() => ctrl.$modelValue, function(newVal, oldVal) { 5189 updatedViaNgChange = false; 5190 }); 5191 5192 var statusSyncerCancelFns = customInputMixin.initStatusSyncers($scope, INNER_FORM_NAME, ctrl, DIRECTIVE_KEY); 5193 5194 var options = { 5195 browse_button: $scope.browseButtonId, 5196 drop_element: $scope.browseButtonId, 5197 types: $scope.allowedTypes ?? ['image', 'document'], 5198 action: 'CoreAssetFrontUploader.upload_JSON', 5199 multi_selection: $scope.multiSelection ?? false, 5200 paramsFn: function () { 5201 return { 5202 privateAsset: $scope.privateAsset ?? false 5203 }; 5204 } 5205 }; 5206 $scope.asset_upload = $asset_upload(options, $scope); 5207 $scope.asset_upload.onOK(uploadOnSuccess); 5208 $scope.asset_upload.onProgress(uploadOnProgress);
5209 $scope.asset_upload.onInvalid(uploadOnInvalid); 5210 $scope.asset_upload.onStart(uploadOnStart); 5211 5212 } 5213 5214 }; 5215 }]); 5216 5217 5218 5219// =========================================================================================== 5220// ------------------------------ Form validation directives --------------------------------- 5221// =========================================================================================== 5222 5223wpd.service('wpdValidationService', ['$requester', '$q', function($requester, $q) { 5224 5225 this.isPostcode = function(value) { 5226 const PATTERN = /^\s*([a-z]{1,2}\d(\d|[a-z])?)\s*(\d[a-z]{2})\s*$/gi; 5227 return PATTERN.test(value); 5228 }; 5229}]); 5230 5231/** 5232 * @return {boolean} - true if the supplied {string} viewValue is a postcode within WPD's area of operation. This validator is asynchronous. 5233 * 5234 * @author noahm 5235 */ 5236wpd.directive('postcodeAreaCheck', ['wpdutilsRequester', '$q', 'wpdutilsCustomInputMixinService', function(wpdutilsRequester, $q, customInputMixin) { 5237 return { 5238 require: 'ngModel', 5239 link: function($scope, $elem, $attrs, ctrl) { 5240 const json = wpdutilsRequester({ 5241 areaCheck: 'WPDUtilsFront.wimsPostcodeLookup_JSON' 5242 }); 5243 const DEBUG = false; 5244 const DIRECTIVE_KEY = 'postcodeAreaCheck'; 5245 5246 const loadingSpinnerProperty = setupLoadingSpinner($scope, $elem, DIRECTIVE_KEY, customInputMixin); 5247 5248 if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {}; 5249 if (!ctrl.postcodeAreaCheckResult) ctrl.postcodeAreaCheckResult = {}; 5250 5251 $scope[loadingSpinnerProperty] = false; 5252 5253 // Set up the custom error message for this ngModel on failure of this validator 5254 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 5255 ctrl.postcodeAreaCheckResult = null; 5256 5257 ctrl.$asyncValidators[DIRECTIVE_KEY] = function(modelValue, viewValue) { 5258 return $q(function(resolve, reject) { 5259 5260 if ($scope.postcodeAreaCheckResult?.manual == true) { 5261 console.log("postcode area check not triggering as we're going manual mode"); 5262 resolve(); 5263 return; 5264 } 5265 5266 if (ctrl.$isEmpty(modelValue) || $scope.$eval($attrs.ngDisabled)) { 5267 // consider empty models to be valid 5268 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 5269 ctrl.postcodeAreaCheckResult = null; 5270 resolve(); 5271 } 5272 5273 if (viewValue) { 5274 const request = json.areaCheck({postcode: viewValue}, function(response) { 5275 DEBUG && console.log("postcodeAreaCheck response:", response); 5276 // Success here means 'is this a postcode that we successfully found' during lookup 5277 const isSuccess = response?.success === true; 5278 const isValid = response?.postcodeValid === true; 5279 const inArea = response?.inArea === true; 5280 if (isSuccess && isValid && inArea) { 5281 // valid + in area 5282 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 5283 ctrl.postcodeAreaCheckResult = null; 5284 $scope.postcodeAreaCheckResult = null; 5285 resolve(); 5286 } 5287 else if (isSuccess && isValid && !inArea) { 5288 // valid postcode, but outside area 5289 ctrl.customErrorMessages[DIRECTIVE_KEY] = "Postcode " + viewValue + " is out of area"; 5290 ctrl.postcodeAreaCheckResult = { 5291 message: ctrl.customErrorMessages[DIRECTIVE_KEY], 5292 dnos: response?.dnos 5293 }; 5294 $scope.postcodeAreaCheckResult = ctrl.postcodeAreaCheckResult; 5295 ctrl.$setTouched(); 5296 reject(); 5297 } 5298 else { 5299 // invalid / unrecognised postcode 5300 ctrl.customErrorMessages[DIRECTIVE_KEY] = "Please enter a valid postcode"; 5301 ctrl.postcodeAreaCheckResult = { 5302 message: ctrl.customErrorMessages[DIRECTIVE_KEY], 5303 dnos: response?.dnos 5304 }; 5305 $scope.postcodeAreaCheckResult = ctrl.postcodeAreaCheckResult; 5306 ctrl.$setTouched(); 5307 reject(); 5308 } 5309 return { cache: true }; 5310 }); 5311 request.setLoader({ 5312 load: () => { $scope[loadingSpinnerProperty] = true; }, 5313 clear: () => { $scope[loadingSpinnerProperty] = false; } 5314 }) 5315 } 5316 else { 5317 ctrl.customErrorMessages[DIRECTIVE_KEY] = "Please enter a valid postcode"; 5318 reject(); 5319 } 5320 }); 5321 }; 5322 } 5323 }; 5324}]); 5325 5326/** 5327* @return {boolean} - true if the supplied {number} viewValue is greater than the supplied 'greaterThan' attribute value, or if the underlying model value is unassigned 5328* 5329* @author noahm 5330*/ 5331wpd.directive('greaterThan', function() { 5332 return { 5333 require: 'ngModel', 5334 restrict: 'A', 5335 link: function($scope, $elem, $attrs, ctrl) { 5336 var DIRECTIVE_KEY = 'greaterThan'; 5337 5338 if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {}; 5339 5340 // Set up the custom error message for this ngModel on failure of this validator 5341 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 5342 5343 ctrl.$validators[DIRECTIVE_KEY] = function(modelValue, viewValue) { 5344 if (ctrl.$isEmpty(modelValue)) { 5345 // consider empty models to be valid 5346 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 5347 return true; 5348 } 5349 5350 // grab value supplied to attribute to compare. 5351 var greaterThanWhat = Number.parseFloat($attrs[DIRECTIVE_KEY]); 5352 5353 if (viewValue > greaterThanWhat) { 5354 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 5355 return true; 5356 } 5357 5358 ctrl.customErrorMessages[DIRECTIVE_KEY] = "Must be greater than " + greaterThanWhat; 5359 return false; 5360 }; 5361 } 5362 }; 5363}); 5364 5365/** 5366 * @return {boolean} - true if the supplied {number} viewValue is less than the supplied 'lessThan' attribute value, or if the underlying model value is unassigned 5367 * 5368 * @author noahm 5369 */ 5370wpd.directive('lessThan', function() { 5371 return { 5372 require: 'ngModel', 5373 restrict: 'A', 5374 link: function($scope, $elem, $attrs, ctrl) { 5375 var DIRECTIVE_KEY = 'lessThan'; 5376 5377 if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {}; 5378 5379 // Set up the custom error message for this ngModel on failure of this validator 5380 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 5381 5382 ctrl.$validators[DIRECTIVE_KEY] = function(modelValue, viewValue) { 5383 if (ctrl.$isEmpty(modelValue)) { 5384 // consider empty models to be valid 5385 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 5386 return true; 5387 } 5388 5389 // grab value supplied to attribute to compare. 5390 var lessThanWhat = Number.parseFloat($attrs[DIRECTIVE_KEY]); 5391 5392 if (viewValue < lessThanWhat) { 5393 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 5394 return true; 5395 } 5396 5397 ctrl.customErrorMessages[DIRECTIVE_KEY] = "Must be less than " + lessThanWhat; 5398 return false; 5399 }; 5400 } 5401 }; 5402}); 5403 5404/** 5405 * @return {boolean} - true if the supplied {number} viewValue is an integer 5406 * 5407 * @author noahm 5408 */ 5409wpd.directive('isInteger', function() { 5410 return { 5411 require: 'ngModel', 5412 restrict: 'A', 5413 link: function($scope, $elem, $attrs, ctrl) { 5414 var DIRECTIVE_KEY = 'isInteger';
5415 //var NUMBER_REGEX = /^-?[0-9]+\.?[0-9]*$/g; // general number 5416 var NUMBER_REGEX = /^-?[0-9]+$/g; 5417 5418 if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {}; 5419 5420 // Set up the custom error message for this ngModel on failure of this validator 5421 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 5422 5423 ctrl.$validators[DIRECTIVE_KEY] = function(modelValue, viewValue) { 5424 // consider empty models to be valid 5425 if (ctrl.$isEmpty(modelValue) || viewValue.match(NUMBER_REGEX)) { 5426 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 5427 return true; 5428 } 5429 5430 ctrl.customErrorMessages[DIRECTIVE_KEY] = "Value must be an integer"; 5431 return false; 5432 }; 5433 } 5434 }; 5435}); 5436 5437 5438wpd.directive('isValidDate', function() { 5439 return { 5440 require: 'ngModel', 5441 restrict: 'A', 5442 link: function($scope, $elem, $attrs, ctrl) { 5443 var DIRECTIVE_KEY = 'isValidDate'; 5444 var format = "DD/MM/YYYY"; 5445 5446 ctrl.$validators[DIRECTIVE_KEY] = function(modelValue, viewValue) { 5447 5448 // EMPTY MODEL - consider empty models to be valid - drop out of the check 5449 if (ctrl.$isEmpty(modelValue)) { 5450 return true; 5451 } 5452 5453 // ELSE - Continue with rest of valid date checks 5454 //Check if the date is in a valid format 5455 //Do we meet the correct pattern? 5456 var datePatternRegex = RegExp('\\d{2}\\/\\d{2}\\/\\d{4}'); //Only tests for integers and slashes (xx/xx/xxxx) - not date format 5457 valid = datePatternRegex.test(viewValue) && viewValue.length <= 10; 5458 if (valid) 5459 { 5460 //Now we know it's the correct format, is it a valid date? 5461 var date = moment(viewValue, format); 5462 returnVal = date.isValid() ? date.valueOf() : viewValue; //NOTE: [WSME-577] - changed ternary false bit from null to viewValue to stop it totally clearing. 5463 valid = date.isValid(); 5464 } 5465 return valid; 5466 } 5467 } 5468 } 5469}) 5470 5471wpd.directive('isValidPastDate', function() { 5472 return { 5473 require: 'ngModel', 5474 restrict: 'A', 5475 link: function($scope, $elem, $attrs, ctrl) { 5476 var DIRECTIVE_KEY = 'isValidPastDate'; 5477 var format = "DD/MM/YYYY"; 5478 5479 ctrl.$validators[DIRECTIVE_KEY] = function(modelValue, viewValue) { 5480 5481 // EMPTY MODEL - consider empty models to be valid - drop out of the check 5482 if (ctrl.$isEmpty(modelValue)) { 5483 return true; 5484 } 5485 5486 // ELSE - Continue with rest of valid date checks 5487 //Check if the date is in a valid format 5488 //Do we meet the correct pattern? 5489 var datePatternRegex = RegExp('\\d{2}\\/\\d{2}\\/\\d{4}'); //Only tests for integers and slashes (xx/xx/xxxx) - not date format 5490 valid = datePatternRegex.test(viewValue) && viewValue.length <= 10; 5491 if (valid) 5492 { 5493 //Now we know it's the correct format, is it a valid date? 5494 var date = moment(viewValue, format); 5495 returnVal = date.isValid() ? date.valueOf() : viewValue; //NOTE: [WSME-577] - changed ternary false bit from null to viewValue to stop it totally clearing. 5496 valid = date.isValid(); 5497 } 5498 5499 if (valid) 5500 { 5501 var now = new Date(); 5502 var viewDate = moment(viewValue, format).toDate(); 5503 5504 //If date is in past we're okay 5505 valid = (viewDate <= now); 5506 } 5507 5508 return valid; 5509 } 5510 } 5511 } 5512}) 5513 5514/** 5515* @return {boolean} - true if the supplied {date} is a valid date in the correct format and in the future 5516* 5517* @author kerianc, noahm 5518*/ 5519wpd.directive('isValidFutureDate', function() { 5520 return { 5521 require: 'ngModel', 5522 restrict: 'A', 5523 link: function($scope, $elem, $attrs, ctrl) { 5524 var DIRECTIVE_KEY = 'isValidFutureDate'; 5525 var format = "DD/MM/YYYY"; 5526 5527 if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {}; 5528 5529 // Set up the custom error message for this ngModel on failure of this validator 5530 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 5531 5532 ctrl.$validators[DIRECTIVE_KEY] = function(modelValue, viewValue) { 5533 5534 // EMPTY MODEL - consider empty models to be valid - drop out of the check 5535 if (ctrl.$isEmpty(modelValue)) { 5536 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 5537 return true; 5538 } 5539 5540 // ELSE - Continue with rest of valid date checks 5541 5542 var errMessage = null;
5543 var valid = false; 5544 5545 //Check if the date is in a valid format 5546 //Do we meet the correct pattern? 5547 var datePatternRegex = RegExp('\\d{2}\\/\\d{2}\\/\\d{4}'); //Only tests for integers and slashes (xx/xx/xxxx) - not date format 5548 valid = datePatternRegex.test(viewValue); 5549 if (valid) 5550 { 5551 //Now we know it's the correct format, is it a valid date? 5552 var date = moment(viewValue, format); 5553 returnVal = date.isValid() ? date.valueOf() : viewValue; //NOTE: [WSME-577] - changed ternary false bit from null to viewValue to stop it totally clearing. 5554 valid = date.isValid(); 5555 } 5556 if (!valid) 5557 { 5558 errMessage = "Date must be in format DD/MM/YYYY"; 5559 } 5560 5561 //Now we have a valid date - is it in the future? 5562 if (valid) 5563 { 5564 var now = new Date(); 5565 var viewDate = moment(viewValue, format).toDate(); 5566 5567 //If date is in future we're okay 5568 valid = (viewDate > now); 5569 5570 if (!valid) 5571 { 5572 errMessage = "Date must be in the future"; 5573 } 5574 } 5575 5576 ctrl.customErrorMessages[DIRECTIVE_KEY] = errMessage; 5577 return valid; 5578 }; 5579 5580 } 5581 }; 5582}); 5583 5584/** 5585 * @return {boolean} - true if the field's date is before the supplied date-before value 5586 * 5587 * @author ianbe (adapted from noahm) 5588 */ 5589wpd.directive('dateBefore', function() { 5590 return { 5591 require: 'ngModel', 5592 restrict: 'A', 5593 link: function($scope, $elem, $attrs, ctrl) { 5594 if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {}; 5595 5596 // Set up the custom error message for this ngModel on failure of this validator 5597 ctrl.customErrorMessages.dateBefore = null; 5598 5599 var errMsgFormat = "Must be before $1"; // IE doesn't support template literals so this will have to do 5600 5601 ctrl.$validators.dateBefore = function(modelValue, viewValue) { 5602 5603 if (ctrl.$isEmpty(modelValue)) { 5604 // consider empty models to be valid 5605 ctrl.customErrorMessages.dateBefore = null; 5606 return true; 5607 } 5608 5609 // grab value supplied to attribute to compare. 5610 var comparisonDate = parseFloat($attrs.dateBefore); 5611 var date, result; 5612 5613 // unset any unit smaller than day, since that's only as granular as we're getting with our comparison 5614 date = new Date(modelValue); 5615 date.setHours(0); 5616 date.setMinutes(0); 5617 date.setSeconds(0); 5618 date.setMilliseconds(0); 5619 5620 compare = new Date(comparisonDate); 5621 compare.setHours(0); 5622 compare.setMinutes(0); 5623 compare.setSeconds(0); 5624 compare.setMilliseconds(0); 5625 5626 result = date.valueOf() < compare.valueOf(); 5627 5628 // Set error message if necessary 5629 ctrl.customErrorMessages.dateBefore = !result ? 5630 errMsgFormat.replace('$1', compare.toLocaleDateString('en-GB')) : 5631 null; 5632 return result; 5633 5634 }; 5635 } 5636 } 5637}); 5638 5639/** 5640 * @return {boolean} - true if the field's date is after the supplied date-before value 5641 * 5642 * @author ianbe (adapted from noahm) 5643 */ 5644wpd.directive('dateAfter', function() { 5645 return { 5646 require: 'ngModel', 5647 restrict: 'A', 5648 link: function($scope, $elem, $attrs, ctrl) { 5649 if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {}; 5650 5651 // Set up the custom error message for this ngModel on failure of this validator 5652 ctrl.customErrorMessages.dateAfter = null; 5653 5654 var errMsgFormat = "Must be after $1"; // IE doesn't support template literals so this will have to do 5655 5656 ctrl.$validators.dateAfter = function(modelValue, viewValue) { 5657 5658 if (ctrl.$isEmpty(modelValue)) { 5659 // consider empty models to be valid 5660 ctrl.customErrorMessages.dateAfter = null; 5661 return true; 5662 } 5663 5664 // grab value supplied to attribute to compare. 5665 var comparisonDate = parseFloat($attrs.dateAfter); 5666 var date, result; 5667 5668 // unset any unit smaller than day, since that's only as granular as we're getting with our comparison 5669 date = new Date(modelValue); 5670 date.setHours(0); 5671 date.setMinutes(0); 5672 date.setSeconds(0); 5673 date.setMilliseconds(0); 5674 5675 compare = new Date(comparisonDate); 5676 compare.setHours(0); 5677 compare.setMinutes(0); 5678 compare.setSeconds(0); 5679 compare.setMilliseconds(0); 5680 5681 result = date.valueOf() > compare.valueOf(); 5682 5683 // Set error message if necessary 5684 ctrl.customErrorMessages.dateAfter = !result ? 5685 errMsgFormat.replace('$1', compare.toLocaleDateString('en-GB')) : 5686 null; 5687 return result; 5688 5689 }; 5690 } 5691 } 5692}); 5693 5694/** 5695 * @return {boolean} - true if the supplied {Date} viewValue is between now and now + X years (both inclusive, to the granularity of day), or if the underlying model is unassigned 5696 * 5697 * @author noahm 5698 */ 5699wpd.directive('dateWithinXYearsOfNow', function() { 5700 return { 5701 require: 'ngModel', 5702 restrict: 'A', 5703 link: function($scope, $elem, $attrs, ctrl) { 5704 if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {}; 5705 5706 // Set up the custom error message for this ngModel on failure of this validator 5707 ctrl.customErrorMessages.dateWithinXYearsOfNow = null; 5708 5709 var errMsgFormat = "Must be between today's date ($1) and $2 years from now ($3)"; // IE doesn't support template literals so this will have to do 5710 5711 ctrl.$validators.dateWithinXYearsOfNow = function(modelValue, viewValue) { 5712 5713 if (ctrl.$isEmpty(modelValue)) { 5714 // consider empty models to be valid 5715 ctrl.customErrorMessages.dateWithinXYearsOfNow = null; 5716 return true; 5717 } 5718 5719 // grab value supplied to attribute to compare. 5720 var xYears = parseFloat($attrs.dateWithinXYearsOfNow);
5721 var date, now, xYearsFromNow, result; 5722 5723 // if we have access to moment.js, use that. 5724 if (typeof moment === 'function') { 5725 date = moment(modelValue); 5726 now = moment(); 5727 xYearsFromNow = now.clone().add(xYears, 'years'); 5728 5729 // https://momentjs.com/docs/#/query/is-between/ 5730 // Note the 'day' granularity, and the inclusivity string '[]' 5731 result = date.isBetween(now, xYearsFromNow, 'day', '[]'); 5732 5733 // Set error message if necessary 5734 ctrl.customErrorMessages.dateWithinXYearsOfNow = !result ? 5735 errMsgFormat.replace('$1', now.format('DD/MM/YYYY')).replace('$2', xYears).replace('$3', xYearsFromNow.format('DD/MM/YYYY')) : 5736 null; 5737 return result; 5738 } 5739 else { 5740 // unset any unit smaller than day, since that's only as granular as we're getting with our comparison 5741 date = new Date(modelValue); 5742 date.setHours(0); 5743 date.setMinutes(0); 5744 date.setSeconds(0); 5745 date.setMilliseconds(0); 5746 5747 now = new Date(Date.now()); 5748 now.setHours(0); 5749 now.setMinutes(0); 5750 now.setSeconds(0); 5751 now.setMilliseconds(0); 5752 5753 xYearsFromNow = new Date(new Date(now).setFullYear(now.getFullYear() + xYears)); 5754 5755 result = date.valueOf() >= now.valueOf() && date.valueOf() <= xYearsFromNow.valueOf(); 5756 5757 // Set error message if necessary 5758 ctrl.customErrorMessages.dateWithinXYearsOfNow = !result ? 5759 errMsgFormat.replace('$1', now.toLocaleDateString('en-GB')).replace('$2', xYears).replace('$3', xYearsFromNow.toLocaleDateString('en-GB')) : 5760 null; 5761 return result; 5762 } 5763 }; 5764 } 5765 }; 5766}); 5767 5768/** 5769 * @return {boolean} - Do as is required check only if the passed in value is true 5770 * 5771 * @author grahamb 5772 */ 5773wpd.directive('conditionallyRequired', function() { 5774 return { 5775 require: 'ngModel', 5776 restrict: 'A', 5777 link: function($scope, $elem, $attrs, ctrl) { 5778 var DIRECTIVE_KEY = 'conditionallyRequired'; 5779 5780 if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {}; 5781 5782 // Set up the custom error message for this ngModel on failure of this validator 5783 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 5784 5785 ctrl.$validators[DIRECTIVE_KEY] = function(modelValue, viewValue) { 5786 5787 var isRequired = ($attrs[DIRECTIVE_KEY]); 5788 5789 if (isRequired) 5790 { 5791 //IF empty then fail 5792 if (ctrl.$isEmpty(modelValue)) { 5793 ctrl.customErrorMessages[DIRECTIVE_KEY] = "Required"; 5794 return false; 5795 } 5796 else 5797 { 5798 //else Okay 5799 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 5800 return true; 5801 } 5802 } 5803 else 5804 { 5805 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 5806 return true; 5807 } 5808 }; 5809 } 5810 }; 5811}); 5812 5813/** 5814* TODO: wpdUtils service has isValidPostcode... 5815* @return {boolean} - true if the supplied {String} viewValue matches the format for a UK postcode 5816* 5817* @author noahm 5818*/ 5819wpd.directive('isPostcode', ['wpdValidationService', '$timeout', function(wpdValidationService, $timeout) { 5820 return { 5821 require: 'ngModel', 5822 restrict: 'A', 5823 link: function($scope, $elem, $attrs, ctrl) { 5824 var DIRECTIVE_KEY = 'isPostcode'; 5825 5826 if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {}; 5827 5828 // Set up the custom error message for this ngModel on failure of this validator 5829 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 5830 5831 function cleanPostcode(value) { 5832 if (!value) return value; 5833 const stripped = (value + '').replace(/\s/g, '').toUpperCase(); 5834 if (stripped.length <= 3) return stripped; 5835 return stripped.slice(0, -3) + ' ' + stripped.slice(-3); 5836 } 5837 5838 ctrl.$parsers.push(function(viewValue) { 5839 return cleanPostcode(viewValue); 5840 }); 5841 5842 $scope.$watch( 5843 () => ctrl.$modelValue, 5844 function(newVal, oldVal) { 5845 if (newVal && newVal !== oldVal) { 5846 const formatted = cleanPostcode(newVal); 5847 if (formatted !== ctrl.$viewValue) { 5848 ctrl.$viewValue = formatted; 5849 ctrl.$render(); 5850 } 5851 } 5852 } 5853 ); 5854 5855 ctrl.$validators[DIRECTIVE_KEY] = function(modelValue, viewValue) { 5856 if (ctrl.$isEmpty(modelValue) || $scope.$eval($attrs.ngDisabled)) { 5857 // consider empty models to be valid 5858 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 5859 ctrl.postcodeAreaCheckResult = null; 5860 return true; 5861 } 5862 5863 let result = wpdValidationService.isPostcode(viewValue); 5864 ctrl.customErrorMessages[DIRECTIVE_KEY] = result ? null : "Must be a valid UK postcode"; 5865 ctrl.postcodeAreaCheckResult = null; 5866 return result; 5867 }; 5868 } 5869 }; 5870}]); 5871 5872/** 5873* @return {boolean} - true if the supplied {String|number} viewValue is in a telephone format 5874* Treat the following sample variants as valid: 5875* 123 456 789 5876* 123456789 5877* +44 123456789 5878* (+44) 123456789 5879* (0)123 456 789 5880* 5881* @author noahm 5882*/ 5883wpd.directive('wpdutilsIsTelephone', ['wpdValidationService', function (wpdValidationService) { 5884 return { 5885 require: 'ngModel', 5886 restrict: 'A', 5887 link: function ($scope, $elem, $attrs, ctrl) { 5888 5889 const DEBUG = false; 5890 const DIRECTIVE_KEY = 'wpdutilsIsTelephone'; 5891 const TEL_REGEX = /^(\+|\(\+?\d+\))?\d{3,15}$/; 5892 if (!ctrl.customErrorMessages) { ctrl.customErrorMessages = {}; } 5893 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 5894 5895 ctrl.$parsers.push(function (viewValue) { 5896 DEBUG && console.log(DIRECTIVE_KEY + ":parsing '" + viewValue + "'"); 5897 if (!viewValue) return viewValue; 5898 5899 let cleaned = (viewValue + '').replace(/\s/g, ''); 5900 return cleaned; 5901 }); 5902 5903 ctrl.$validators[DIRECTIVE_KEY] = function (modelValue) { 5904 DEBUG && console.log(DIRECTIVE_KEY + ":validating '" + modelValue + "'"); 5905 if (ctrl.$isEmpty(modelValue)) { 5906 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 5907 return true; 5908 } 5909 let isValid = TEL_REGEX.test(modelValue); 5910 ctrl.customErrorMessages[DIRECTIVE_KEY] = isValid ? null : "Must be a valid telephone number"; 5911 return isValid; 5912 }; 5913 5914 ctrl.$formatters.push(function (modelValue) { 5915 return modelValue; 5916 }); 5917 } 5918 }; 5919}]); 5920 5921/** 5922* @return {boolean} - true if the supplied {String|number} viewValue is in a telephone format 5923* Treat the following sample variants as valid: 5924* 01234567890 5925* 07712345678 5926* 02081234567 5927* 01908765432 5928* 01619876543
5929* Treat the following sample variants as invalid: 5930* 1234567890 (missing leading 0) 5931* 012345678901 (too many characters) 5932* 0123a567890 (contains non-numeric characters) 5933* 0123-567-890 (contains dashes) 5934* +441234567890 (contains country code) 5935* 5936* Nged requested as it matches their internal validation rules. 5937* Ours might be better but it would let through phone numbers their system would reject 5938* This validation differs from the telephone annotation used on the back end 5939 * @author noahm, konstanting, grahamb 5940 * -KG tightened up NMs original directive 5941 * -Moved to utils by GB 5942*/ 5943wpd.directive('wpdutilsIsTelephoneTight', ['wpdValidationService', function(wpdValidationService) { 5944 return { 5945 require: 'ngModel', 5946 restrict: 'A', 5947 link: function($scope, $elem, $attrs, ctrl) { 5948 const DIRECTIVE_KEY = 'wpdutilsIsTelephoneTight'; 5949 const TEL_REGEX = /^0\d{10}$/g; 5950 5951 if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {}; 5952 5953 // Set up the custom error message for this ngModel on failure of this validator 5954 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 5955 5956 ctrl.$validators[DIRECTIVE_KEY] = function(modelValue, viewValue) { 5957 if (ctrl.$isEmpty(modelValue)) { 5958 // consider empty models to be valid 5959 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 5960 return true; 5961 } 5962 5963 let result = false; 5964 5965 if (viewValue && (typeof viewValue === 'string' || typeof viewValue === 'number')) { 5966 let testValue = viewValue + ''; // convert to string 5967 testValue = testValue.replaceAll(/\s/g, ''); // remove any whitespace-like characters for easier testing 5968 result = testValue.match(TEL_REGEX); 5969 } 5970 ctrl.customErrorMessages[DIRECTIVE_KEY] = result ? null : "Must be a valid telephone number"; 5971 return result; 5972 }; 5973 } 5974 }; 5975}]); 5976 5977/** 5978 * @return {boolean} - true if the supplied {String|number} viewValue is in a telephone format 5979 * 5980 * This is a more specific yet accomodating version of wpdutilsIsTelephoneTight. 5981 * wpdutilsIsTelephoneTight doesn't make allowances for some landlines that can have 10 digits. 5982 * Built for more stringent validation rules for the PCR Form Contact Details step, rules based off feedback in NGEDSME-728 5983 * Any mobile number (assuming UK) must start with 07, and be 11 digits long. 5984 * Any other number must be either 10 or 11 digits long, and start with 0. 5985 * Non-UK phone numbers may not be valid in this format, and any international numbers with country codes will not be valid at all. 5986 * 5987 * Treat the following sample variants as valid: 5988 * 07855548895 5989 * 02147825748 5990 * 01915887744 5991 * 0169772584 (one of the valid 10-digit landline area codes '016977' - there are 12 in total, but we will only validate them length-wise, not checking explicity for the correct area codes) 5992 * Treat the following sample variants as invalid: 5993 * 1234567890 (missing leading 0) 5994 * 012345678901 (too many characters) 5995 * 0123a567890 (contains non-numeric characters) 5996 * 0123-567-890 (contains dashes) 5997 * 0712345678 (not enough digits for starting with 07) 5998 * 012345678 (not enough digits for any number) 5999 * +447851122114 (contains country code, too long) 6000 * 6001 * @author lloydc 6002 */ 6003wpd.directive('wpdutilsIsTelephoneMobile', ['wpdValidationService', function(wpdValidationService) { 6004 return { 6005 require: 'ngModel', 6006 restrict: 'A', 6007 link: function($scope, $elem, $attrs, ctrl) { 6008 const DIRECTIVE_KEY = 'wpdutilsIsTelephoneMobile'; 6009 const TEL_REGEX = /^0(7\d{9}|[1-689]\d{8,9})$/g; // Allows 10 or 11 digit numbers starting with 0 6010 6011 if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {}; 6012 6013 // Set up the custom error message for this ngModel on failure of this validator 6014 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 6015 6016 ctrl.$validators[DIRECTIVE_KEY] = function(modelValue, viewValue) { 6017 if (ctrl.$isEmpty(modelValue)) { 6018 // consider empty models to be valid 6019 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 6020 return true; 6021 } 6022 6023 let result = false; 6024 6025 if (viewValue && (typeof viewValue === 'string' || typeof viewValue === 'number')) { 6026 let testValue = viewValue + ''; // convert to string 6027 testValue = testValue.replaceAll(/\s/g, ''); // remove any whitespace-like characters for easier testing 6028 result = testValue.match(TEL_REGEX); 6029 } 6030 ctrl.customErrorMessages[DIRECTIVE_KEY] = result ? null : "Must be a valid UK telephone number (11 digits for mobile, 10 or 11 for landline)"; 6031 return result; 6032 }; 6033 } 6034 }; 6035}]); 6036 6037/** 6038 * Validates that the input is a valid UK telephone number using Loqate's telephone validation API. 6039 * 6040 * Performs an initial regex check before calling the API - the number must start with 0 and 6041 * be either 10 or 11 digits long. 6042 * 6043 * If the API response is credible and the number is valid, the cleaned value is written back to the model. 6044 * 6045 * @author GrahamB 6046 * @author BradleyM 6047 */ 6048wpd.directive('wpdutilsIsTelUkLoqate', [ 6049 '$requester', 6050 'wpdutilsCustomInputMixinService', 6051 'wpdutilsLoqateValidationService', 6052 function($requester, customInputMixin, loqateValidation) { 6053 6054 const DEBUG = false; 6055 const DIRECTIVE_KEY = 'wpdutilsIsTelUkLoqate'; 6056 const VALIDATION_TIMEOUT_MS = 10000; 6057 const TEL_REGEX = /^0(7\d{9}|[1-689]\d{8,9})$/; // Allows 10 or 11 digit numbers starting with 0 6058 6059 return { 6060 require: 'ngModel', 6061 restrict: 'A', 6062 link: function($scope, $elem, $attrs, ctrl) { 6063 const json = $requester({ 6064 telCheck: 'WPDUtilsFront.validateUKTelephoneLoqate_JSON' 6065 }); 6066 6067 DEBUG && console.log(`[${DIRECTIVE_KEY}] Initialised for element: ${$elem.attr('id')}`); 6068 6069 // Coerce to string and strip whitespace 6070 ctrl.$parsers.push(function(viewValue) { 6071 if (!viewValue) return viewValue; 6072 return (viewValue + '').replace(/\s/g, ''); 6073 }); 6074 6075 function telRegexCheck(val) { 6076 return !!val.match(TEL_REGEX); 6077 } 6078 6079 const loadingSpinnerProperty = setupLoadingSpinner($scope, $elem, DIRECTIVE_KEY, customInputMixin); 6080 6081 function buildTelApiCall(json, apiKeyHandle) { 6082 return function({ viewValue, resolve, reject, setError, markDefinitive, onNetworkFailure }) { 6083 json.telCheck({ telephoneNumber: viewValue, apiKeyHandle }, function(response) { 6084 const item = response?.result?.items?.[0]; 6085 6086 if (!response?.success || !item) { 6087 setError(null); 6088 resolve(); 6089 return; 6090 } 6091 6092 markDefinitive(); 6093 6094 if (item.IsCredible) { 6095 if (item.IsValid?.toUpperCase() === "NO") { 6096 setError(`Telephone number ${viewValue} is not a valid UK telephone number`); 6097 reject(); 6098 } else { 6099 ctrl.$setViewValue(viewValue); 6100 ctrl.$render(); 6101 setError(null); 6102 resolve(); 6103 } 6104 } else { 6105 // Response not credible - default to accepting the number 6106 setError(null); 6107 resolve(); 6108 } 6109 }
6109).onFailure(onNetworkFailure); 6110 }; 6111 } 6112 6113 ctrl.$asyncValidators[DIRECTIVE_KEY] = loqateValidation.createValidator({ 6114 validatorType: DIRECTIVE_KEY, 6115 ctrl, $elem, $scope, 6116 customInputMixin, json, 6117 apiKeyHandle: $attrs[DIRECTIVE_KEY], 6118 regexCheck: telRegexCheck, 6119 regexFailMessage: "Must be a valid UK telephone number", 6120 emptyMessage: "Please enter a valid UK telephone number", 6121 timeoutMs: VALIDATION_TIMEOUT_MS, 6122 debug: DEBUG, 6123 buildApiCall: buildTelApiCall, 6124 onLoadStart: function() { $scope[loadingSpinnerProperty] = true; }, 6125 onLoadEnd: function() { $scope[loadingSpinnerProperty] = false; }, 6126 onDestroy: function(cleanupFn) { $scope.$on('$destroy', cleanupFn); } 6127 }); 6128 } 6129 }; 6130}]); 6131 6132/** 6133 * Validates that the input is a valid email address using Loqate's email validation API. 6134 * 6135 * Performs an initial regex check before calling the API - the email must have a valid 6136 * format including a TLD after the @ symbol. If the API response marks the address as 6137 * invalid, the error is written back to the ngModel controller. 6138 * 6139 * @author GrahamB 6140 * @author BradleyM 6141 */ 6142wpd.directive('wpdutilsIsEmailLoqate', [ 6143 '$requester', 6144 'wpdutilsCustomInputMixinService', 6145 'wpdutilsLoqateValidationService', 6146 function($requester, customInputMixin, loqateValidation) { 6147 6148 const DEBUG = false; 6149 const DIRECTIVE_KEY = 'wpdutilsIsEmailLoqate'; 6150 const VALIDATION_TIMEOUT_MS = 10000; 6151 const EMAIL_REGEX = /^[\w.!#$%&'*+/=?^`{|}~-]+@[a-z\d](?:[a-z\d-]{0,61}[a-z\d])?(?:\.[a-z\d](?:[a-z\d-]{0,61}[a-z\d])?)+$/i; 6152 6153 return { 6154 require: 'ngModel', 6155 restrict: 'A', 6156 link: function($scope, $elem, $attrs, ctrl) { 6157 const json = $requester({ 6158 emailCheck: 'WPDUtilsFront.validateEmailLoqate_JSON' 6159 }); 6160 6161 DEBUG && console.log(`[${DIRECTIVE_KEY}] Initialised for element: ${$elem.attr('id')}`); 6162 6163 // Angular adds its own email validator on type="email" inputs. 6164 // Remove it so Loqate is the sole email validity authority. 6165 // Safe on re-link: delete on a non-existent property is a no-op. 6166 if ($attrs.type === 'email' && ctrl && ctrl.$validators) { 6167 delete ctrl.$validators.email; 6168 } 6169 6170 // Coerce to string and strip whitespace 6171 ctrl.$parsers.push(function(viewValue) { 6172 if (!viewValue) return viewValue; 6173 return (viewValue + '').trim(); 6174 }); 6175 6176 const loadingSpinnerProperty = setupLoadingSpinner($scope, $elem, DIRECTIVE_KEY, customInputMixin); 6177 6178 function buildEmailApiCall(json, apiKeyHandle) { 6179 return function({ viewValue, resolve, reject, setError, markDefinitive, onNetworkFailure }) { 6180 json.emailCheck({ emailAddress: viewValue, apiKeyHandle }, function(response) { 6181 const item = response?.result?.items?.[0]; 6182 6183 if (!response?.success || !item) { 6184 setError(null); 6185 resolve(); 6186 return; 6187 } 6188 6189 markDefinitive(); 6190 6191 if (item.ResponseCode?.toUpperCase() === "INVALID") { 6192 setError(`Email address ${viewValue} is not a valid email address`); 6193 reject(); 6194 } else { 6195 setError(null); 6196 resolve(); 6197 } 6198 }).onFailure(onNetworkFailure); 6199 }; 6200 } 6201 6202 function emailRegexCheck(val) { 6203 return typeof val === 'string' && !!val.match(EMAIL_REGEX); 6204 } 6205 6206 ctrl.$asyncValidators[DIRECTIVE_KEY] = loqateValidation.createValidator({ 6207 validatorType: DIRECTIVE_KEY, 6208 ctrl, 6209 $elem, 6210 $scope, 6211 customInputMixin, 6212 json, 6213 apiKeyHandle: $attrs[DIRECTIVE_KEY], 6214 regexCheck: emailRegexCheck, 6215 regexFailMessage: "Must be a valid email address", 6216 emptyMessage: "Please enter a valid email address", 6217 timeoutMs: VALIDATION_TIMEOUT_MS, 6218 debug: DEBUG, 6219 buildApiCall: buildEmailApiCall, 6220 onLoadStart: function() { $scope[loadingSpinnerProperty] = true; }, 6221 onLoadEnd: function() { $scope[loadingSpinnerProperty] = false; }, 6222 onDestroy: function(cleanupFn) { $scope.$on('$destroy', cleanupFn); } 6223 }); 6224 } 6225 }; 6226}]); 6227 6228/** 6229* @return {boolean} - true if the supplied String viewValue is in an email format
6230* Ensures that an email has a TLD after the @ symbol 6231* @author lloydc 6232*/ 6233wpd.directive('wpdutilsIsEmail', ['wpdValidationService', function(wpdValidationService) { 6234 return { 6235 require: 'ngModel', 6236 restrict: 'A', 6237 link: function($scope, $elem, $attrs, ctrl) { 6238 const DIRECTIVE_KEY = 'wpdutilsIsEmail'; 6239 const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/g; // Accepts "[email protected]" but not "test@test" 6240 6241 if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {}; 6242 6243 // Set up the custom error message for this ngModel on failure of this validator 6244 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 6245 6246 ctrl.$validators[DIRECTIVE_KEY] = function(modelValue, viewValue) { 6247 if (ctrl.$isEmpty(modelValue)) { 6248 // consider empty models to be valid 6249 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 6250 return true; 6251 } 6252 6253 let result = false; 6254 6255 if (viewValue && typeof viewValue === 'string') { 6256 result = viewValue.match(EMAIL_REGEX); 6257 } 6258 6259 ctrl.customErrorMessages[DIRECTIVE_KEY] = result ? null : "Must be a valid email in the format '[email protected]'"; 6260 return result; 6261 }; 6262 } 6263 }; 6264}]); 6265 6266/** 6267 * Adaptation of wpdutilsIsTelephone directive, using a JavaScript compatible 6268 * version of the regex in CoreTelephone's isValid method. 6269 * 6270 * This should be used in conjunction with the @Telephone annotation, as 6271 * CoreTelephone's isValid method is used when validating attributes with this 6272 * annotation on form submission. 6273 * 6274 * @return {boolean} - true if the supplied {String|number} viewValue is in a 6275 * telephone format 6276 * 6277 * Treat the following sample variants as valid: 6278 * 123 456 789 6279 * 123456789 6280 * +44 123456789 6281 * +44123456789 6282 * (020) 1234 1234 6283 * 6284 * @author bradleym, noahm (wpdutilsIsTelephone) 6285 */ 6286wpd.directive('wpdutilsIsCoreTelephone', ['wpdValidationService', function(wpdValidationService) { 6287 return { 6288 require: 'ngModel', 6289 restrict: 'A', 6290 link: function($scope, $elem, $attrs, ctrl) { 6291 const DIRECTIVE_KEY = 'wpdutilsIsCoreTelephone'; 6292 const TEL_REGEX = /^([\\+][0-9]{1,3})?([ \\.\\-])?([\\(]{1}[0-9]{2,6}[\\)])?([0-9 \\.\\\\-\\\/]{3,20})((x|ext|extension)[ ]?[0-9]{1,4})?$/g; 6293 // Adapted from CoreTelephone's isValid method 6294 // Original: ^([\\+][0-9]{1,3})?([ \\.\\-])?([\\(]{1}[0-9]{2,6}[\\)])?([0-9 \\.\\-/]{3,20})((x|ext|extension)[ ]?[0-9]{1,4})?$ 6295 6296 if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {}; 6297 6298 // Set up the custom error message for this ngModel on failure of this validator 6299 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 6300 6301 ctrl.$validators[DIRECTIVE_KEY] = function(modelValue, viewValue) { 6302 if (ctrl.$isEmpty(modelValue)) { 6303 // consider empty models to be valid 6304 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 6305 return true; 6306 } 6307 6308 let result = false; 6309 6310 if (viewValue && (typeof viewValue === 'string' || typeof viewValue === 'number')) { 6311 let testValue = viewValue + ''; // convert to string 6312 testValue = testValue.replaceAll(/\s/g, ''); // remove any whitespace-like characters for easier testing 6313 result = testValue.match(TEL_REGEX); 6314 } 6315 6316 ctrl.customErrorMessages[DIRECTIVE_KEY] = result ? null : "Must be a valid telephone number"; 6317 return result; 6318 }; 6319 } 6320 }; 6321}]); 6322 6323 6324wpd.directive('wpdutilsIsPowercutsTelephone', ['wpdValidationService', function(wpdValidationService) { 6325 return { 6326 require: 'ngModel', 6327 restrict: 'A', 6328 link: function($scope, $elem, $attrs, ctrl) { 6329 const DIRECTIVE_KEY = 'wpdutilsIsCoreTelephone'; 6330 const TEL_REGEX = /^([\\+][0-9]{1,3})?([ \\.\\-])?([\\(]{1}[0-9]{2,6}[\\)])?([0-9 \\.\\\\-\\\/]{3,20})((x|ext|extension)[ ]?[0-9]{1,4})?$/g; 6331 // Adapted from CoreTelephone's isValid method 6332 // Original: ^([\\+][0-9]{1,3})?([ \\.\\-])?([\\(]{1}[0-9]{2,6}[\\)])?([0-9 \\.\\-/]{3,20})((x|ext|extension)[ ]?[0-9]{1,4})?$ 6333 6334 if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {}; 6335 6336 // Set up the custom error message for this ngModel on failure of this validator 6337 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 6338 6339 ctrl.$validators[DIRECTIVE_KEY] = function(modelValue, viewValue) { 6340 if (ctrl.$isEmpty(modelValue)) { 6341 // consider empty models to be valid 6342 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 6343 return true; 6344 } 6345 6346 let result = false; 6347 6348 if (viewValue && (typeof viewValue === 'string' || typeof viewValue === 'number')) { 6349 let testValue = viewValue + ''; // convert to string 6350 testValue = testValue.replaceAll(/\s/g, ''); // remove any whitespace-like characters for easier testing 6351 result = testValue.match(TEL_REGEX); 6352 } 6353 6354 ctrl.customErrorMessages[DIRECTIVE_KEY] = result ? null : "Must be a valid telephone number"; 6355 return result; 6356 }; 6357 } 6358 }; 6359}]); 6360 6361/** 6362* @return {boolean} - true if the supplied {String} is prefixed with NGED area codes
6363* Does not check if the MPAN number if valid, only if it is in area. 6364* 6365* @author grahamb 6366*/ 6367wpd.directive('wpdutilsIsNgedMpan', ['wpdValidationService', function(wpdValidationService) { 6368 return { 6369 require: 'ngModel', 6370 restrict: 'A', 6371 link: function($scope, $elem, $attrs, ctrl) { 6372 const DIRECTIVE_KEY = 'wpdutilsIsNgedMpan'; 6373 6374 if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {}; 6375 6376 // Set up the custom error message for this ngModel on failure of this validator 6377 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 6378 6379 ctrl.$validators[DIRECTIVE_KEY] = function(modelValue, viewValue) { 6380 if (ctrl.$isEmpty(modelValue) || modelValue.toString().length < 2) { 6381 // consider empty models to be valid 6382 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 6383 return true; 6384 } 6385 6386 if (!Number.isInteger(Number(modelValue))) { 6387 ctrl.customErrorMessages[DIRECTIVE_KEY] = "Must contain only numeric digits"; 6388 return false; 6389 } 6390 6391 var checkValue = modelValue.toString().substr(0, 2); 6392 var hasRightStart = (checkValue == "21" || checkValue == "22" || checkValue == "11" || checkValue == "14"); 6393 6394 ctrl.customErrorMessages[DIRECTIVE_KEY] = (hasRightStart) ? null : "This MPAN does not belong to National Grid. Cannot proceed with application."; 6395 return hasRightStart; 6396 }; 6397 } 6398 }; 6399 } 6400]); 6401 6402 6403/** 6404* Form directive that passes through the value of the parent form's stepSubmitted property 6405* TODO maybe consider making this part of wpdutilsCustomInputMixinService instead (or as well?) 6406* 6407* @author ianbe 6408* @since 2025-06-27 6409*/ 6410wpd.directive('wpdutilsParentFormStatus', [function() { 6411 return { 6412 restrict: 'A', 6413 require: 'form', 6414 link: function($scope, $elem, $attrs, $ctrls) { 6415 const thisForm = $scope[$attrs.ngForm]; 6416 if (thisForm && thisForm.$$parentForm) { 6417 Object.defineProperty(thisForm, 'stepSubmitted', { 6418 get: function() { 6419 return thisForm.$$parentForm?.stepSubmitted; 6420 } 6421 }); 6422 } 6423 } 6424 }; 6425}]); 6426 6427 6428/** 6429 * Directive to validate the maximum length of a string, after encoding it in the manner of the backend 6430 * 6431 * @author ianbe 6432 * @since 2025-07-02 6433 */ 6434wpd.directive('wpdutilsMaxLength', ['wpdutilsRequester', function(wpdutilsRequester) { 6435 6436 // static object to hold the aliases for emojis, so that they can be shared across multiple instances of this directive 6437 const ALIASES = { state: 'uninitialised', aliases: {} }; 6438 6439 return { 6440 restrict: 'A', 6441 require: 'ngModel', 6442 link: function($scope, $elem, $attrs, $ctrl) { 6443 const DIRECTIVE_KEY = 'wpdutilsMaxLength'; 6444 6445 const FITZPATRICK_TYPES = { 6446 "\uD83C\uDFFB": "type_1_2", 6447 "\uD83C\uDFFC": "type_3", 6448 "\uD83C\uDFFD": "type_4", 6449 "\uD83C\uDFFE": "type_5", 6450 "\uD83C\uDFFF": "type_6" 6451 }; 6452 6453 // Modes - these match the CoreEmojiSanitiser behaviours for the corresponding modes 6454 // - parse (default): Create HTML entities for emojis, strip Fitzpatrick modifiers (e.g. 👧) 6455 // - alias: Create aliases for emojis, use aliases for Fitzpatrick modifiers if provided (e.g. :person: or :person|type_3:) 6456 // TODO: Not fully implemented yet - emoji aliases are currently mapped to their code points, not their aliases 6457 // - strip: Strip all emojis and Fitzpatrick modifiers, leaving no content 6458 const max = parseInt($attrs[DIRECTIVE_KEY], 10); 6459 let emojiMode = $attrs[DIRECTIVE_KEY + 'EmojiMode'] || 'parse'; // default to parse mode if not specified 6460 6461 if (!['parse', 'alias', 'strip'].includes(emojiMode)) { 6462 emojiMode = 'parse'; // default to parse mode 6463 } 6464 6465 if (emojiMode === 'alias') { 6466 // Set the shared aliases asynchronously if this is the first directive to request them 6467 if (['uninitialised', 'failed'].includes(ALIASES.state)) { 6468 ALIASES.state = 'loading'; 6469 const json = wpdutilsRequester({ 6470 getEmojiAliases: 'WPDUtilsFront.getEmojiAliases_JSON' 6471 }); 6472 const request = json.getEmojiAliases({}, function(response) { 6473 if (response && response.success) { 6474 ALIASES.state = 'success'; 6475 ALIASES.aliases = response.aliases; 6476 } 6477 }); 6478 request.onFailure(function() { 6479 // Mark aliases as failed so a later directive can try again 6480 ALIASES.state = 'failed'; 6481 }) 6482 } 6483 } 6484 6485 6486 $ctrl.customErrorMessages = $ctrl.customErrorMessages || {}; 6487 6488 6489 function stripFitzpatrick(str) { 6490 return str.replace(/\p{Emoji_Modifier}/ug, ''); 6491 } 6492 6493 function encodeEmoji(str) { 6494 switch (emojiMode) { 6495 case 'alias': 6496 str = str.replace(/(\p{Extended_Pictographic})(\p{Emoji_Modifier})?/ug, (matches, emoji, modifier) => { 6497 const emojiStr = ALIASES.aliases[emoji] || 'unknown'; 6498 if (modifier === undefined) { 6499 return `:${emojiStr}:`; 6500 } 6501 else { 6502 const modifierStr = FITZPATRICK_TYPES[modifier]; 6503 return `:${emojiStr}|${modifierStr}:`; 6504 } 6505 }); 6506 break; 6507 case 'strip': 6508 str = stripFitzpatrick(str); 6509 str = str.replace(/\p{Extended_Pictographic}/ug, ''); 6510 break; 6511 case 'parse': 6512 default: 6513 str = stripFitzpatrick(str); 6514 str = str.replace(/\p{Extended_Pictographic}/ug, emoji => '&#' + emoji.codePointAt(0) + ';'); 6515 break; 6516 } 6517 6518 return str; 6519 } 6520 6521 6522 $ctrl.$validators[DIRECTIVE_KEY] = function(modelValue) { 6523 if (!modelValue) return true; 6524 6525 const encoded = encodeEmoji(modelValue); 6526 const charCount = encoded.length; 6527 6528 // DEBUG && console.log(`"${modelValue}" has ${charCount} characters after encoding - ${encoded}`); 6529 6530 if (charCount <= max) { 6531 return true; 6532 } 6533 else { 6534 $ctrl.customErrorMessages[DIRECTIVE_KEY] = "Must not be longer than " + max + " characters"; 6535 if (modelValue < encoded) { 6536 $ctrl.customErrorMessages[DIRECTIVE_KEY] += ". Note that emoji use more than one character."; 6537 } 6538 return false; 6539 } 6540 }; 6541 } 6542 }; 6543}]); 6544 6545/** 6546 * Use this when you have a fieldset of checkboxes that are grouped together 6547 * and you want to make sure at least one checkbox in the group is checked. 6548 * 6549 * Add the directive to the fieldset element, and supply it with the "group name" 6550 * checkbox-group-required="claimReasons" 6551 * 6552 * All the input boxes need to have the data-group attribute set to the same group name: 6553 * <input type="checkbox" data-group="claimReasons" ... /> 6554 * 6555 * @return {boolean} - true if at least one checkbox in the group is checked 6556 * 6557 * @author grahamb 6558 */ 6559wpd.directive('checkboxGroupRequired', function() { 6560 return { 6561 restrict: 'A', 6562 require: '^form', 6563 link: function($scope, $elem, $attrs, ctrl) { 6564 const groupName = $attrs.checkboxGroupRequired; 6565 const DIRECTIVE_KEY = 'checkboxGroupRequired_' + groupName; // Unique per group 6566 6567 if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {}; 6568 6569 // Set up the custom error message for this form on failure of this validator 6570 ctrl.customErrorMessages[DIRECTIVE_KEY] = null; 6571 6572 ctrl.$validators[DIRECTIVE_KEY] = function() { 6573 const checkboxes = $elem.querySelectorAll('input[type="checkbox"][data-group="' + groupName + '"]'); 6574 return Array.from(checkboxes).some(cb => cb.checked); 6575 }; 6576 6577 // Watch for changes 6578 $scope.$watch(function() { 6579 const checkboxes = $elem.querySelectorAll('input[type="checkbox"][data-group="' + groupName + '"]'); 6580 return Array.from(checkboxes).map(cb => cb.checked).join(','); 6581 }, function() { 6582 ctrl.$validate(); 6583 }); 6584 } 6585 }; 6586}); 6587 6588 6589 6590// =========================================================================================== 6591// ----------------------------------- Helpful filters --------------------------------------- 6592// =========================================================================================== 6593 6594/** 6595* Return a string representation of a number, formatted to a particular locale's rules 6596* See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleStr
6596ing 6597* 6598* For the Java version, see {module:Utils} /app/wpd/utils/NumberHelper#numberLocaleString 6599* 6600* @param {number|string} input - the number to format, or a string parsable to a number 6601* @param {string} locale - the locale code to use, i.e. 'en-ie'. If left blank, will use the system locale. 6602* @param {object} options - a map of optional arguments to pass in which influence the returned string. See the Mozilla link for examples. 6603* @return {string} 6604* 6605* @author noahm 6606*/ 6607wpd.filter('numberLocaleString', function() { 6608 const NAME = 'numberLocaleString'; 6609 6610 return function(input, locale, options) { 6611 let workingInput = input; 6612 6613 if (typeof workingInput === 'string') { 6614 try { 6615 workingInput = parseFloat(workingInput); 6616 } 6617 catch (e) { 6618 console.warn(`${NAME} could not parse a Number from the supplied string value "${input}"`); 6619 } 6620 } 6621 6622 if (typeof workingInput !== 'number') { 6623 console.warn(`${NAME} cannot work on anything other than a Number type`); 6624 } 6625 else { 6626 workingInput = workingInput.toLocaleString(locale, options); 6627 } 6628 6629 return workingInput; 6630 } 6631}); 6632 6633/** 6634* Return a string representation of a number padded with some amount of leading 0s. 6635* 6636* @param {number} input 6637* @param {number} padCount 6638* @return {string} 6639* 6640* @author noahm 6641*/ 6642wpd.filter('zeroPad', function() { 6643 return function(input, padCount) { 6644 input += ''; 6645 if (!input || !padCount || typeof padCount !== 'number') return input; 6646 return input.padStart(padCount, '0'); 6647 }; 6648}); 6649 6650// Stolen from angular.stormmode.js/angular.powercuts-reporting.js 6651// It's okay for this to take precedence over the other two since they're all exactly the same function 6652// TODO: remove the ordinal filters in those two files - let them use this one. 6653wpd.filter('ordinal', function() { 6654 return function(input) { 6655 if (!input && input !== 0) return ''; 6656 var s = ['th', 'st', 'nd', 'rd'], 6657 v = input % 100; 6658 return input + (s[(v - 20) % 10] || s[v] || s[0]); 6659 }; 6660}); 6661 6662/** 6663* An augmentation of the normal angularjs date filter 6664* 6665* @return {string} - the given date in the specified angularjs date filter format, but with the ability to have an ordinal applied 6666* to any of the returned numerical portions, using '$ORD'. 6667* e.g. 'EEEE d$ORD MMMM, yyyy' => 'Thursday 13th May, 2022' 6668* 6669* @author noahm 6670*/ 6671wpd.filter('datePlus', ['$filter', function($filter) { 6672 var ordinalMarker = '$ORD'; 6673 6674 return function(date, format, timezone) { 6675 var ret = $filter('date')(date, format, timezone); 6676 6677 if (ret && ret.indexOf(ordinalMarker) >= 0) { 6678 ret = ret.replaceAll(new RegExp('(\\d{1,2})\\'+ordinalMarker, 'ig'), function(match, p1) { 6679 return $filter('ordinal')(Number.parseInt(p1)); 6680 }); 6681 } 6682 6683 return ret; 6684 }; 6685}]); 6686 6687/** 6688* A filter that attempts to capitalize words in a string in a title-like format, such as: 6689* "Structural evaluations of houses in Gateshead" => "Structural Evaluations of Houses in Gateshead" 6690* 6691* @author noahm 6692*/ 6693wpd.filter('smartCapitalize', function() { 6694 // Feel free to add to this list if you spot more examples 6695 var noCapitalize = ['of', 'the', 'do', 'with', 'to', 'from', 'by', 'upon', 'under', 'a', 'in', 'out', 'and', 'at', 'above', 'below']; 6696 6697 // TODO: give option to provide noCapitalize works that _should_ still be capitalized. 6698 return function(input) { 6699 if (!input) return ''; 6700 6701 var output = input.toLowerCase().replaceAll(/\b([^\s\r\n\t]*)\b/gi, function(match) { 6702 if (noCapitalize.indexOf(match) < 0) { 6703 match = match.charAt(0).toUpperCase() + match.slice(1); 6704 } 6705 6706 // TODO: you still need to capitalize noCapitalize words if they are the start of a sentence! 6707 6708 return match; 6709 }); 6710 6711 // we still want to capitalize the very first letter, no matter what it is 6712 return output.charAt(0).toUpperCase() + output.slice(1); 6713 }; 6714}); 6715 6716/** 6717* A filter that takes in a string containing substitution tokens, and swaps those tokens out with the relevant string which maps to it 6718* in the replaceMap. The input string may look like this: 6719* "My $vehicle is $color" 6720* and the map may look like this: 6721* { 6722* vehicle: 'motorbike', 6723* color: 'red' 6724* } 6725* 6726* @author noahm 6727*/ 6728wpd.filter('stringSubstitute', function() { 6729 return function(inputString, replaceMap) { 6730 if (!inputString) return ''; 6731 if (!replaceMap) return inputString; 6732 6733 return inputString.replaceAll(/\$[a-bA-B0-9_]+/gi, function(match) { 6734 match = match.slice(1); // remove the $ at the start 6735 return replaceMap[match] !== undefined ? replaceMap[match] : match; 6736 }); 6737 }; 6738}); 6739 6740/** 6741* A filter which returns the given input if it is neither null or undefined, else returns the supplied `alternativeValue` 6742* 6743* @author noahm 6744*/ 6745wpd.filter('wpdutilsIfExistsOr', function() { 6746 return function(input, alternativeValue) { 6747 return input !== null && input !== undefined ? input : alternativeValue; 6748 } 6749}); 6750 6751 6752// =========================================================================================== 6753// ------------------------------ General Utility Services ----------------------------------- 6754// =========================================================================================== 6755 6756/** 6757* Dumping ground of helpful math functions 6758* 6759* @author noahm 6760*/ 6761wpd.service('wpdutilsMathUtilities', [ 6762 function() { 6763 /** 6764 * Stolen from Powercuts 6765 * Rounds a number to a precision 6766 * @author keiranc 6767 * @param {Number} value the value to round 6768 * @param {Number} precision the number of decimal places 6769 * @returns {Number} the rounded value 6770 */ 6771 this.roundWithPrecision = function(value, precision) { 6772 var multiplier = Math.pow(10, precision || 0); 6773 return Math.round(value * multiplier) / multiplier; 6774 }; 6775 6776 /** 6777 * Stolen from Powercuts 6778 * Converts latitude to radians 6779 * @author keiranc 6780 * @param {Number} lat latitude 6781 * @returns {Number} the amount in radians 6782 */ 6783 this.lat2rad = function(lat) { 6784 var sin = Math.sin((lat * Math.PI) / 180); 6785 var radX2 = Math.log((1 + sin) / (1 - sin)) / 2; 6786 return Math.max(Math.min(radX2, Math.PI), -Math.PI) / 2; 6787 }; 6788 } 6789]); 6790 6791/** 6792* Dumping ground of helpful miscellaneous functions 6793* 6794* @author noahm 6795*/ 6796wpd.service('wpdutilsMiscUtilities', [ 6797 function() { 6798 /** 6799 * Stolen from the Powercuts site 6800 * 6801 * finds an object by property value and returns said object, 6802 * or null if there is no object, or if initial objects was null 6803 * 6804 * @author keiranc 6805 * @param {String} property the property to filter on 6806 * @param {Object} value the value to filter on 6807 * @param {Array} objects the objects to filter 6808 * @returns object with property matching value in objects 6809 */ 6810 this.getBy = function(property, value, objects) { 6811 if (!objects) { 6812 return null; 6813 } 6814 var _object = objects.filter(function(object) { 6815 return object[property] === value; 6816 }); 6817 return _object.length ? _object[0] : null; 6818 }; 6819 6820 6821 /** 6822 * Returns a simple key-value store (mostly for factories to use to share data between instances) 6823 * @author ianbe 6824 * @since 2025-07-30 6825 */ 6826 this.newKeyValueStore = () => function() { 6827 const store = {}; 6828 6829 /** 6830 * Check whether a value exists in the store. 6831 * @param {string} key Cache key 6832 * @returns {boolean} True if the store has a value at that key, false otherwise 6833 */ 6834 this.has = function(key) { 6835 return store.hasOwnProperty(key); 6836 }; 6837 6838 /** 6839 * Get a value from the store, or if not found, set it to the return value of a callback function and return it. 6840 * @param {string} key Cache key 6841 * @param {boolean|function} setDefault Determines the default value to set if the key is not found in the store. 6842 * If true, an empty object will be set at the key. 6843 * If a function, its return value will be set at the key. 6844 * Functions are highly recommended to return a reference type such as an object, if you want to share data without having to re-fetch it. 6845 * 6846 * @returns {*} The value from the store or the result of the callback function 6847 */ 6848 this.get = function(key, setDefault = null) { 6849 if (setDefault && !this.has(key)) { 6850 if (setDefault === true) { 6851 store[key] = {}; 6852 } 6853 else if (typeof setDefault === 'function') { 6854 store[key] = setDefault(); 6855 } 6856 } 6857 return store[key]; 6858 }; 6859 6860 /** 6861 * Set a value in the store. 6862 * Note that if the key already contains a reference type, any existing references will not be updated to the new value. 6863 * @param {string} key Cache key 6864 * @param {*} value The value to store at the key 6865 */ 6866 this.set = function(key, value) { 6867 store[key] = value; 6868 } 6869 }; 6870 6871 } 6872]) 6873 6874 6875 6876/** 6877 * Wrapper for the $requester service to add extra functionality: 6878 * 6879 * Check if a request in the requester - isLoading() - or a specific route - isLoading(route) - is currently in progress. 6880 * 6881 * Caching. If the success callback returns an object with a truthy `cache` property, the response will be cached 6882 * and returned on subsequent calls with the same parameters. Use for read-only requests where the response is expected to be static. 6883 * 6884 * Should be interchangeable with the $requester syntax, so, sorry, you still gotta call onFailure separately! 6885 * Also $requester doesn't use promises, so we won't either. 6886 * 6887 * @author ianbe 6888 * @created 2025-06-24 6889 */ 6890wpd.factory('wpdutilsRequester', ['$requester', function($requester) { 6891 6892 /* Store for the optional request cache */ 6893 const requestCache = {}; 6894 6895 /** 6896 * Looks in the request cache for a stored response to the given route and parameters. 6897 * @param {string} route The $requester route, for the cache key 6898 * @param {object} params The request parameters, for the cache key 6899 * @returns the response object if found, otherwise undefined 6900 */ 6901 const getCache = function(route, params) { 6902 const paramsKey = JSON.stringify(params); 6903 if (!requestCache[route]) { 6904 return undefined; 6905 } 6906 return requestCache[route][paramsKey]; 6907 } 6908 6909 /** 6910 * Stores a response in the request cache for the given route and parameters. 6911 * @param {string} route The $requester route, for the cache key 6912 * @param {object} params The request parameters, for the cache key 6913 * @param {object} value The response object to store 6914 */ 6915 const setCache = function(route, params, value) { 6916 const paramsKey = JSON.stringify(params); 6917 if (!requestCache[route]) { 6918 requestCache[route] = {}; 6919 } 6920 requestCache[route][paramsKey] = value; 6921 } 6922 6923 /** 6924 * Clears the request cache, optionally only for the given route and/or parameters. 6925 * @param {string|null} route The $requester route to clear from the cache, or null to clear the whole cache 6926 * @param {object|null} params The request parameters within the (required) route to clear from the cache, or null to clear the whole route 6927 */ 6928 const clearCache = function(route = null, params = null) { 6929 if (!route) { 6930 requestCache = {}; 6931 return; 6932 } 6933 if (params) { 6934 const paramsKey = JSON.stringify(params); 6935 delete requestCache[route][paramsKey]; 6936 } 6937 else { 6938 delete requestCache[route]; 6939 } 6940 } 6941 6942 return function(routes, ...args) { 6943 // Original requester object 6944 var requester = $requester(routes, ...args); 6945 6946 // Tracker for loading state 6947 var loading = 0; 6948 var loadingRoutes = {}; 6949 6950 // Wrapper that will be returned to the caller 6951 var wrapper = {}; 6952 6953 /** 6954 * Outward-facing function to check if a request is currently in progress. 6955 * @returns {boolean} - true if a request is currently in progress, false otherwise 6956 */ 6957 wrapper.isLoading = function(route = null) { 6958 if (route) { 6959 return (loadingRoutes[route] && loadingRoutes[route] > 0); 6960 } 6961 else { 6962 return (loading > 0); 6963 } 6964 }; 6965 6966 /** 6967 * Outward-facing function to reset the loading state. 6968 */ 6969 wrapper.unblock = function() { 6970 loading = 0; 6971 } 6972 6973 /** 6974 * Internal function to increment the loading counter at the start of a request. 6975 * @param {string|null} route The request route to use as a key 6976 */ 6977 const incrementLoading = function(route = null) { 6978 loading = Math.max(1, loading + 1); 6979 if (route) { 6980 loadingRoutes[route] = Math.max(1, (loadingRoutes[route] ?? 0) + 1); 6981 } 6982 } 6983 6984 /** 6985 * Internal function to decrement the loading counter at the end of a request. 6986 * @param {string|null} route The request route to use as a key 6987 */ 6988 const decrementLoading = function(route = null) { 6989 loading = Math.max(0, loading - 1); 6990 if (route) { 6991 loadingRoutes[route] = Math.max(0, (loadingRoutes[route] ?? 0) - 1); 6992 } 6993 } 6994 6995 // Wrap each route's call function to add the extra functionality
6996 Object.keys(routes).forEach(function(key) { 6997 const original = requester[key]; 6998 6999 /** 7000 * wpdutilsRequester wrapper around the $requester function to call a route 7001 * @param {object} params Set of HTTP parameters to send with the request 7002 * @param {function} successFn Success callback function that can optionally return { cache: true } to cache the response 7003 * @param {boolean} background 7004 * @param {object} opts 7005 */ 7006 wrapper[key] = function(params, successFn, background, opts = {}) { 7007 // Try to find a cached response, and bail out with it if found 7008 const cachedResponse = getCache(key, params); 7009 if (cachedResponse !== undefined) { 7010 successFn(cachedResponse); 7011 return { 7012 onSuccess: function(fn) {}, 7013 onFailure: function(fn) {}, 7014 setLoader: function(obj) {}, 7015 } 7016 } 7017 7018 // Increment the loading counter 7019 incrementLoading(key); 7020 7021 // Call original function 7022 const result = original(params, successFn, background); 7023 7024 // Modify the other functions for loading state changes 7025 const originalOnSuccess = result.onSuccess; 7026 7027 /** 7028 * wpdutilsRequester wrapper for the $requester onSuccess function 7029 * @param {function} successFn Success callback function that can optionally return { cache: true } to cache the response 7030 */ 7031 result.onSuccess = function(successFn) { 7032 originalOnSuccess(function(...args) { 7033 const successFnResult = successFn(...args); 7034 if ((successFnResult instanceof Object) && (successFnResult.cache)) { 7035 // If the success function returns an object with a trueish cache property, store the response 7036 delete params.authenticityToken; 7037 setCache(key, params, args[0]); 7038 } 7039 decrementLoading(key); 7040 }); 7041 }; 7042 result.onSuccess(successFn); 7043 7044 const originalOnFailure = result.onFailure; 7045 7046 /** 7047 * wpdutilsRequester wrapper for the $requester onFailure function 7048 * @param {function} failureFn Failure callback function 7049 */ 7050 result.onFailure = function(failureFn) { 7051 originalOnFailure(function(...args) { 7052 failureFn(...args); 7053 decrementLoading(key); 7054 }); 7055 } 7056 7057 // Set an empty failure function to ensure that the loading state is decremented, even if not set 7058 result.onFailure(() => {}); 7059 7060 return result; 7061 }; 7062 }); 7063 7064 // Return a proxy that returns wrapper properties if they exist, and falls back to the original requester if not 7065 return new Proxy({}, { 7066 get(_, property) { 7067 return (wrapper[property] !== undefined) ? wrapper[property] : requester[property]; 7068 } 7069 }); 7070 }; 7071}]); 7072 7073wpd.service('wpdutilsLoqateValidationService', [ 7074 '$q', 7075 '$timeout', 7076 '$window', 7077 function($q, $timeout, $window) { 7078 // Loqate *requires* we don't re-use stored results over 30 days old, though recommends we check for fresh 7079 // results every hour or so. Below is a practical TTL that meets both this requirement & recommendation. 7080 const LOQATE_RESULT_TTL_MS = 60 * 60 * 1000; 7081 7082 const memoisedValidationResults = new Map(); // Memoised results 7083 const pendingValidationRequests = new Map(); // In-flight requests 7084 7085 /** 7086 * @returns unique string key for a given validator type & input value. 7087 */ 7088 function validationKey(validatorType, viewValue) { 7089 return JSON.stringify([validatorType, viewValue]); 7090 } 7091 7092 function getPreviousResult(key) { 7093 const result = memoisedValidationResults.get(key); 7094 if (!result) return null; 7095 7096 // Remove entry if it's exceeded our TTL - we need to do a fresh Loqate call 7097 if (Date.now() - result.settledTime >
7097 LOQATE_RESULT_TTL_MS) { 7098 memoisedValidationResults.delete(key); 7099 return null; 7100 } 7101 return result.promise; 7102 } 7103 7104 /** 7105 * Sets up all shared directive infrastructure and returns a ready to assign $asyncValidators function. 7106 * 7107 * @param {object} options 7108 * @param {string} options.validatorType - Unique key for this validator (DIRECTIVE_KEY) 7109 * @param {object} options.ctrl - ngModel controller 7110 * @param {object} options.$elem - jQuery-wrapped element 7111 * @param {Function} options.onLoadStart - Called when a validation request begins 7112 * @param {Function} options.onLoadEnd - Called when a validation request completes 7113 * @param {Function} options.onDestroy - Accepts a cleanup function and registers it for execution on directive 7114 * destroy 7115 * @param {object} options.customInputMixin - wpdutilsCustomInputMixinService 7116 * @param {object} options.json - $requester result with setLoader 7117 * @param {Function} options.regexCheck - Pre-flight check run before the API call - return false to reject the 7118 * value immediately 7119 * @param {string} options.regexFailMessage - Error message shown on regex failure 7120 * @param {string} options.emptyMessage - Error message shown when value is empty 7121 * @param {number} options.timeoutMs - Milliseconds before a pending validation request is abandoned 7122 * @param {Function} options.buildApiCall - Constructs the API call function for this validator, given the json 7123 * requester and API key handle 7124 * @param {string} options.apiKeyHandle - API key handle from directive attribute 7125 * @param {boolean} options.debug - Enable debug logging 7126 */ 7127 this.createValidator = function(options) { 7128 if (!options || !options.validatorType || !options.ctrl || !options.$elem || 7129 !options.customInputMixin || !options.json || !options.regexCheck || !options.buildApiCall) { 7130 throw new Error('wpdutilsLoqateValidationService.createValidator: missing required options'); 7131 } 7132 7133 const { 7134 validatorType, ctrl, $elem, customInputMixin, json, regexCheck, regexFailMessage, emptyMessage, 7135 timeoutMs, buildApiCall, apiKeyHandle, debug 7136 } = options; 7137 7138 json.setLoader({ 7139 load: options.onLoadStart, 7140 clear: options.onLoadEnd 7141 }); 7142 7143 /** 7144 * If Loqate gave us a definitive result, memoise a lightweight already-settled promise rather than the 7145 * original - to avoid retaining the full validation closure in memory - and store a settledTime. 7146 * 7147 * @param {*} resolved - whether the promise was resolved (true) or rejected (false) 7148 * @param {*} lookupKey - key to store the validation result under 7149 * @param {*} isLoqateResultDefinitive - whether the result from loqate was definitive 7150 */ 7151 function memoiseDefinitiveResult(resolved, lookupKey, isLoqateResultDefinitive) { 7152 if (isLoqateResultDefinitive) { 7153 memoisedValidationResults.set(lookupKey, { 7154 promise: resolved ? $q.resolve() : $q.reject(), 7155 // We need to know when we performed this lookup, see LOQATE_RESULT_TTL_MS 7156 settledTime: Date.now() 7157 }); 7158 } 7159 } 7160 7161 function setError(msg) { 7162 ctrl.customErrorMessages[validatorType] = msg; 7163 } 7164 7165 // Error message initialisation 7166 if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {}; 7167 ctrl.customErrorMessages[validatorType] = null; 7168 7169 const apiCall = buildApiCall(json, apiKeyHandle); 7170 7171 // Creating cleanup function and passing to options onDestroy fn. 7172 function cleanup() {
7173 const keyPrefix = JSON.stringify([validatorType, '']).slice(0, -3); 7174 pendingValidationRequests.forEach(function(promise, key) { 7175 if (key.startsWith(keyPrefix)) pendingValidationRequests.delete(key); 7176 }); 7177 } 7178 options.onDestroy(cleanup); 7179 7180 return function $asyncValidator(modelValue, viewValue) { 7181 7182 if (ctrl.$isEmpty(modelValue)) { 7183 setError(null); // (Remove error) 7184 return $q.resolve(); 7185 } 7186 7187 const lookupKey = validationKey(validatorType, viewValue); 7188 7189 // If we have a pending promise for the given viewValue, return that promise instead of creating a 7190 // new one. 7191 const pendingRequest = pendingValidationRequests.get(lookupKey); 7192 if (pendingRequest) { 7193 debug && console.log(`[${validatorType}] Skipping - ${viewValue} already pending`); 7194 return pendingRequest; 7195 } 7196 7197 // If we've already validated this viewValue - with a definitive yes/no from Loqate - return the 7198 // associated promise instead of creating a new one. 7199 const previousResult = getPreviousResult(lookupKey); 7200 if (previousResult) { 7201 debug && console.log(`[${validatorType}] Skipping - ${viewValue} already validated`); 7202 return previousResult; 7203 } 7204 7205 // Perform supplied regex check on value 7206 if (!regexCheck(viewValue)) { 7207 debug && console.log(`[${validatorType}] Failed regex check for: ${viewValue}`); 7208 setError(regexFailMessage); 7209 return $q.reject(); 7210 } 7211 7212 let isLoqateResultDefinitive = false; 7213 7214 const validationPromise = $q(function(origResolve, origReject) { 7215 7216 let settled = false; 7217 let validationTimeout; 7218 7219 // Helper wrappers to allow pre/post logic before resolving/rejecting 7220 function resolve() { 7221 // Place any pre-resolve logic here 7222 if (settled) return; 7223 settled = true; 7224 $timeout.cancel(validationTimeout); 7225 $elem.removeAttr('disabled'); 7226 origResolve(); 7227 } 7228 7229 function reject() { 7230 // Place any pre-reject logic here 7231 if (settled) return; 7232 settled = true; 7233 $timeout.cancel(validationTimeout); 7234 $elem.removeAttr('disabled'); 7235 origReject(); 7236 } 7237 7238 // Disable the element while validation is in progress 7239 $elem.attr('disabled', 'disabled'); 7240 7241 // Prevent hung validation requests from leaving the validator pending indefinitely. 7242 validationTimeout = $timeout(function() { 7243 debug && console.warn(`[${validatorType}] Validation timed out for: ${viewValue}`); 7244 options.onLoadEnd(); 7245 setError(null); 7246 resolve(); 7247 }, timeoutMs); 7248 7249 apiCall({ 7250 viewValue, 7251 resolve, 7252 reject, 7253 setError, 7254 markDefinitive: function() { 7255 isLoqateResultDefinitive = true; 7256 }, 7257 onNetworkFailure: function() { 7258 // Loader clear not called on this path so need to do so ourselves 7259 options.onLoadEnd(); 7260 7261 if (false === $window.navigator.onLine) { 7262 // The user is offline, according to their browser 7263 setError("You appear to be offline. Please check your internet connection."); 7264 reject(); 7265 } else { 7266 // The user isn't offline, so something else has gone wrong. 7267 // We'll have to let this telephone number through. 7268 debug && console.warn(`[${validatorType}] Request failed for: ${viewValue}`); 7269 setError(null); 7270 resolve(); 7271 } 7272 } 7273 }); 7274 }); 7275 7276 // Guard duplicate in-flight requests for the same value 7277 pendingValidationRequests.set(lookupKey, validationPromise); 7278 7279 validationPromise.finally(function() { 7280 pendingValidationRequests.delete(lookupKey); // Remove viewValue from pending map - now settled 7281 }); 7282 7283 validationPromise.then( 7284 function() { memoiseDefinitiveResult(true, lookupKey, isLoqateResultDefinitive); }, 7285 function() { memoiseDefinitiveResult(false, lookupKey, isLoqateResultDefinitive); } 7286 ); 7287 7288 return validationPromise; 7289 }; 7290 }; 7291 } 7292]); 7293 7294/** 7295 * Sets up the loading spinner for a validation directive. 7296 * Injects the spinner into the directive's parent element, initialises it to hidden, 7297 * and returns the scope property name. 7298 * 7299 * @param {object} $scope - The directive's scope 7300 * @param {object} $elem - jQuery-wrapped directive element 7301 * @param {string} directiveKey - The directive's unique key 7302 * @param {object} customInputMixin - wpdutilsCustomInputMixinService 7303 * @returns {string} The scope property name used to control spinner visibility 7304 */ 7305function setupLoadingSpinner($scope, $elem, directiveKey, customInputMixin) { 7306 // The replaceAll needs to be here as stray '-' chars mess up the selection for the universal loading spinner 7307 // Selection is also too broad without $elem.attr('id') 7308 const loadingSpinnerProperty = (directiveKey + 'Loading_' + $elem.attr('id')).replaceAll('-', ''); 7309 // Inject a loading spinner into the parent element 7310 customInputMixin.injectLoadingSpinner($scope, $elem.parent(), loadingSpinnerProperty); 7311 $scope[loadingSpinnerProperty] = false; 7312 return loadingSpinnerProperty; 7313} 7314; 7315/** include: angular.wpdsearchAutoComplete.js **/ 7316window.core&&core.registerScript&&core.registerScript('/public/javascripts/angular.wpdsearchAutoComplete.js'); 7317var autocomplete = angular.module('wpdsearchAutoComplete', []); 7318 7319/** 7320 * Service for search AutoComplete calls 7321 */ 7322autocomplete.service('wpdsearchAutoCompleteService', ['$loading', '$requester', '$q', function($loading, $requester, $q) { 7323 // private vars 7324 var _json = $requester({ 7325 'autoComplete': 'SearchFront.autoComplete', 7326 'promotedResults': 'SearchFront.promotedSearchResultsAutoComplete' 7327 }), 7328 _loader = {load: function () { }, clear: function () { }}, 7329 _searchTerm = null, 7330 _successCb = [], 7331 _errorCb = angular.noop, 7332 _timeout = null; 7333 7334 _json.setLoader(_loader); 7335 7336 7337 var callRefresh = function() { 7338 var deferred = $q.defer(); 7339 7340 _json.autoComplete( 7341 { 7342 searchTerm: _searchTerm, 7343 }, function(response) { 7344 deferred.resolve({data: response}); 7345 }.bind(this), 7346 deferred.reject 7347 ); 7348 7349 return deferred.promise; 7350 }; 7351 7352 /** 7353 * Promoted serach results 7354 */ 7355 var promotedResultsSearch = function() { 7356 var deferred = $q.defer(); 7357 7358 _json.promotedResults( 7359 { 7360 searchTerm: _searchTerm 7361 },function(response) { 7362 deferred.resolve({success: true, data: response}); 7363 }.bind(this), 7364 deferred.reject 7365 ); 7366 7367 return deferred.promise; 7368 }; 7369 7370 this.refresh = function() { 7371 $q.all({searchData: callRefresh(), 7372 promotedData: promotedResultsSearch()}) 7373 .then(beforeSuccess) 7374 .catch(_errorCb); 7375 }; 7376 7377 /** 7378 * Function to set the current search term 7379 * @param {String} searchTerm search term to try and auto complete 7380 */ 7381 this.setSearchTerm = function(searchTerm) { 7382 _searchTerm = searchTerm; 7383 7384 //clear timeout if exists 7385 if(_timeout !== null) clearTimeout(_timeout); 7386 //set timeout before handling - auto complete. 7387 _timeout = setTimeout(this.refresh, 250); 7388 } 7389 7390 /** 7391 * Sets the success callback for auto complete 7392 * @param {Function} successCallback the success callback 7393 * @returns {AutoComplete} this 7394 */ 7395 this.onSuccess = function(successCallback) { 7396 _successCb.push(successCallback); 7397 return this; 7398 }; 7399 7400 /** 7401 * Sets the error callback for auto complete 7402 * @param {Function} errorCallback the error callback 7403 * @returns {AutoComplete} this 7404 */ 7405 this.onFailure = function(errorCallback) { 7406 _errorCb = errorCallback; 7407 return this; 7408 }; 7409 7410 /** 7411 * Trusts the hit description as html before 7412 * sending the response to the controller 7413 * 7414 * @param {searchResult} searchResult the response 7415 */ 7416 var beforeSuccess = function(searchResult) { 7417 var result = {}; 7418 7419 if (searchResult && searchResult.promotedData.data.promotedResults) { 7420 var promotedResults = JSON.parse(searchResult.promotedData.data.promotedResults); 7421 var uniquePromotedResults = []; 7422 7423 if (Array.isArray(promotedResults) && Array.isArray(searchResult.searchData.data.options)) { 7424 7425 // Ensure no duplicates in promoted result suggestions.
7426 promotedResults.forEach(function(promotedResult) { 7427 if (!doesArrayContainPromotedResult(uniquePromotedResults, promotedResult)) { 7428 uniquePromotedResults.push(promotedResult); 7429 } 7430 }); 7431 7432 uniquePromotedResults.forEach(function(pr) { 7433 pr.text = pr.title; 7434 }); 7435 result.promotedData = uniquePromotedResults; 7436 } 7437 } 7438 7439 if (searchResult && searchResult.searchData.data.options) { 7440 result.searchData = searchResult.searchData.data.options; 7441 } 7442 7443 _successCb.forEach(function(fn) { 7444 fn(result); 7445 }); 7446 }; 7447 7448 7449 /** 7450 * Check whether or not an item already exists within an array. 7451 * 7452 * @param {array} uniqueArray an array of unique values. 7453 * @param {object} item the item that might already exist in the unique array. 7454 * @returns {Boolean} whether or not an item already exists in the unique array. 7455 */ 7456 function doesArrayContainPromotedResult(uniqueArray, item) { 7457 return (uniqueArray.filter(function(uniqueItem) { return uniqueItem.id === item.id && uniqueItem.className === item.className; } )).length > 0; 7458 } 7459}]); 7460; 7461/** include: angular.wpdsearchSearchForm.js **/ 7462window.core&&core.registerScript&&core.registerScript('/public/javascripts/angular.wpdsearchSearchForm.js'); 7463/** 7464* Adapted from code within the wpdcore style module's angular.wpd.js. 7465* This is separated from the wpdsearchSearch module because we do not want to have to pull in needless dependencies that 7466* only apply to the search result page. 7467* 7468*/ 7469var searchForm = angular.module('wpdsearchSearchForm', [ 7470 'wpd', 7471 'wpdsearchAutoComplete', 7472]); 7473 7474/** 7475* Moved here from the wpdcore style module angular.wpd.js 7476* 7477* TODO: This and the normal SearchController do pretty much the same thing, but the SearchController also handles pagination and the like, 7478* make the move to splitting out this behaviour. 7479*/ 7480searchForm.controller('wpdsearchSiteSearchController', [ 7481 '$scope', 7482 '$timeout', 7483 '$element', 7484 'wpdsearchAutoCompleteService', 7485 'Analytics', 7486 function($scope, $timeout, $element, AutoComplete, Analytics) { 7487 var vm = this; 7488 7489 vm.baseUrl = null; 7490 vm.searchPageUrl = null; 7491 vm.enableAutoSuggestions = false; 7492 vm.searchFocused = false; 7493 vm.selectedAutoCompleteOption = false; 7494 vm.lastKeyUpSearchTerm = null; 7495 vm.autoCompleteLoadingText = ''; 7496 vm.searchTypeFilter = null; 7497 vm.searchForm = null; // The implicit FormController on the <form> element. 7498 7499 var appendedId = ''; 7500 var focusEventName = ''; 7501 var preBlurEventName = ''; 7502 var postBlurEventName = ''; 7503 7504 /** 7505 * Initialize the controller 7506 * 7507 * @param {string} baseUrl - the site baseUrl 7508 * @param {string} searchPageUrl - the controller action route that performs the search 7509 * @param {boolean} enableAutoSuggestions - enable or disable autosuggestions? 7510 * @param {String} id - the unique appended identifier for this form's inputs 7511 * @return {undefined} 7512 */ 7513 vm.init = function(baseUrl, searchPageUrl, enableAutoSuggestions, id) { 7514 vm.baseUrl = baseUrl; 7515 vm.searchPageUrl = searchPageUrl; 7516 vm.enableAutoSuggestions = enableAutoSuggestions; 7517 appendedId = id; 7518 focusEventName = 'searchFocused_' + appendedId; 7519 preBlurEventName = 'searchPreBlurred_' + appendedId; 7520 postBlurEventName = 'searchPostBlurred_' + appendedId; 7521 }; 7522 7523 /** 7524 * @return {string} - the form action endpoint, based on the search controller's action plus the search term. 7525 */ 7526 vm.getAction = function() { 7527 return vm.baseUrl + '/' + vm.searchPageUrl + (vm.searchTerm ? '/' + encodeURIComponent(vm.searchTerm) : ''); 7528 }; 7529 7530 /** 7531 * @param {String} url - the url to assess 7532 * @return {String} - the url, appended with the baseUrl if appropriate 7533 */ 7534 vm.getPromotedResultURL = function(url) { 7535 if (url) { 7536 // if we aren't prepended with http(s):// or www., then we have to assume this is a site url. 7537 return url.match(/^(http(s)?:\/\/|www[0-9]?\.)/gi) ? url : (vm.baseUrl + (url.startsWith('/') ? '' : '/') + url); 7538 } 7539 7540 return '#'; 7541 }; 7542 7543 /** 7544 * TODO: would be nice to have a generic service that can properly assess the visibility of any element in the page. 7545 * @return {boolean} - check to see if the filter is hidden via CSS 7546 */ 7547 vm.searchTypeFilterVisible = function() { 7548 var $filterElem = $element.find('.search-type-filter'); 7549 7550 return $filterElem && $filterElem.css('display') !== 'none'; 7551 }; 7552 7553 /** 7554 * @return {boolean} - the disabled state of the search form's button. Will be disabled if there is no searchTerm 7555 */ 7556 vm.searchDisabled = function() { 7557 return !angular.isDefined(vm.searchTerm) || vm.searchTerm === null || 7558 vm.searchTerm.replace(/^\s+/, '').replace(/\s+$/, '') === ''; 7559 }; 7560 7561 /** 7562 * Intercept the submit and do nothing if the search form is empty. 7563 * 7564 * @return {undefined} 7565 */ 7566 vm.onSubmit = function(event) { 7567 if (vm.searchDisabled()) { 7568 event.preventDefault(); 7569 } 7570 }; 7571 7572 /** 7573 * Trigger the form's submission 7574 * 7575 * @return {undefined} 7576 */ 7577 vm.search = function(event) { 7578 $timeout(function() { 7579 $element.find('form').submit(); 7580 }); 7581 }; 7582 7583 var keyupAutoTimeoutTop = null; // don't know if it matters, but I don't want to accidentally conflict with variables used for the Search page's version of this code. 7584 7585 vm.searchKeyup = function(event) { 7586 if (event.which === 13) { 7587 // was enter key 7588 vm.search(); 7589 } else { 7590 if (vm.searchTerm && vm.searchTerm.length > 2 && vm.lastKeyUpSearchTerm !== vm.searchTerm) { //only let autocomplete reassign the suggestions list if the input actually changed. 7591 vm.lastKeyUpSearchTerm = vm.searchTerm; 7592 vm.autoComplete = []; 7593 vm.promotedAutoComplete = []; 7594 7595 // you need the timeout to be able to 'cancel' a previously lined up setSearchTerm, otherwise you'll be in a position where they start overwriting each other once the first one populates the list 7596 // and shows it (this is the same as the original problem). 7597 clearTimeout(keyupAutoTimeoutTop); 7598 7599 keyupAutoTimeoutTop = setTimeout(function() { 7600 vm.autoCompleteLoadingText = 'Loading autocomplete results'; 7601 AutoComplete.setSearchTerm(vm.searchTerm); 7602 //console.log('search keyup w/ auto'); 7603 }, 500); // timeout interval needs to be set at a decent enough pace that you can assume when the person has paused typing, but not close enough that you risk multiple autocompletes lining themselves up. 7604 //console.log('settimeout'); 7605 } 7606 } 7607 }; 7608 7609 vm.useAutoComplete = function(autoComplete) { 7610 vm.selectedAutoCompleteOption = true; 7611 vm.searchTerm = autoComplete; 7612 vm.autoComplete = []; 7613 vm.promotedAutoComplete = []; 7614 vm.search(); 7615 }; 7616 7617 vm.logPromotedResultSelected = function(promotedResultId) { 7618 Analytics.logEvent('Site Search', 'AutoCompletePromotedHeader', promotedResultId); 7619 }; 7620 7621 vm.focus = function() { 7622 vm.searchFocused = true; 7623 $scope.$broadcast(focusEventName); 7624 $scope.$emit(focusEventName); 7625 }; 7626 7627 vm.defocus = function() { 7628 $timeout(function() { 7629 // only close the suggestions box if none of these have focus 7630 if (!$element.find('input#search_' + appendedId + ':focus, div.search-auto-suggestions:focus, div.search-auto-suggestions:focus-within, .search-suggestion-link:focus').length) { 7631 vm.searchFocused = false; 7632 $scope.$broadcast(postBlurEventName); 7633 $scope.$emit(postBlurEventName); 7634 } 7635 }, 500); // any reason for timeout here? 7636 7637 $scope.$broadcast(preBlurEventName); 7638 $scope.$emit(preBlurEventName); 7639 }; 7640 7641 vm.showAutoSuggestions = function() { 7642 return vm.autoComplete && vm.autoComplete.length && vm.searchFocused && vm.searchTerm && vm.enableAutoSuggestions; 7643 }; 7644 7645 AutoComplete.onSuccess(function(result) { 7646 vm.promotedAutoComplete = result && result.promotedData ? result.promotedData : []; 7647 vm.autoComplete = result && result.searchData ? result.searchData : []; 7648 vm.autoCompleteLoadingText = 'Autocomplete results loaded'; 7649 }); 7650 7651 AutoComplete.onFailure(function(error) { 7652 // TODO: error handling 7653 vm.autoCompleteLoadingText = 'No autocomplete results'; 7654 throw error; 7655 }); 7656 } 7657]); 7658; 7659/** include: angular.universal-header.js **/ 7660window.core&&core.registerScript&&core.registerScript('/public/javascripts/angular.universal-header.js'); 7661/** 7662* OVERRIDE of the foundation_base one 7663* 7664*/ 7665angular.module("universal-header", []) 7666 .controller("universalHeaderCtrl", ["$scope", function ($scope) { 7667 }]) 7668 .directive("universalSiteHeader", ["$window", function($window) { 7669 return { 7670 restrict: "C", 7671 link: function(scope, element, attributes) { 7672 scope.megaMenuOpen = false;
7673 scope.topMenuOpen = false; 7674 7675 if (typeof attributes['closeOnOther'] !== "undefined") { 7676 angular.element(window).on("header-link", function(e, data) { 7677 if (data.type !== "mega-menu" && data.open) { 7678 scope.megaMenuOpen = false; 7679 } 7680 if (data.type !== "search-form" && data.open) { 7681 scope.searchFormOpen = false; 7682 } 7683 }); 7684 } 7685 7686 scope.toggleMegaMenu = function() { 7687 scope.megaMenuOpen = !scope.megaMenuOpen; 7688 7689 angular.element(window).trigger("header-link", { open: scope.megaMenuOpen, type: "mega-menu" }); 7690 } 7691 7692 scope.toggleTopMenu = function() { 7693 scope.topMenuOpen = !scope.topMenuOpen; 7694 7695 angular.element(window).trigger("header-link", { open: scope.megaMenuOpen, type: "mega-menu" }); 7696 } 7697 7698 // NEW 7699 scope.toggleSearchForm = function() { 7700 scope.searchFormOpen = !scope.searchFormOpen; 7701 7702 angular.element(window).trigger("header-link", { open: scope.searchFormOpen, type: "search-form" }); 7703 } 7704 } 7705 } 7706 }]) 7707 .directive("universalSiteHeaderSticky", [ 7708 "$window", 7709 function ($window) { 7710 return { 7711 restrict: "C", 7712 scope: { 7713 stickyContainerSelector: "@", 7714 hideHeight: "@" 7715 }, 7716 link: function (scope, element) { 7717 var $ = angular.element, 7718 // The ticking flag ensures we only do potentially 7719 // expensive operations when we need to. We also 7720 // make use of requestAnimationFrame to limit updates 7721 // to the device's refresh rate for maximum performance 7722 // vs smoothness. 7723 ticking = false; 7724 7725 /** 7726 * This takes care of what should happen if the header's 7727 * height changes due to a screen resize. 7728 */ 7729 $($window).on("resize", function () { 7730 // Only do an update if we've not got one ready to go 7731 // in this frame. 7732 if (!ticking) { 7733 $window.requestAnimationFrame(function () { 7734 updateHeaderClearance(); 7735 // Once we've done our work, switch the flag back 7736 // to false to allow another run. 7737 ticking = false; 7738 }); 7739 // Set our flag to true to prevent this being called 7740 // more than once per frame. 7741 ticking = true; 7742 } 7743 }); 7744 7745 /** 7746 * This is our main 'hot' event listener for pinning the 7747 * header. The same 'ticking' flag is used here. 7748 */ 7749 $($window).on("scroll", function () { 7750 if (!ticking) { 7751 $window.requestAnimationFrame(function () { 7752 updateScroll(); 7753 ticking = false; 7754 }); 7755 ticking = true; 7756 } 7757 }); 7758 7759 /** 7760 * Store some initial state for our sticky calcs. 7761 */ 7762 var el = element[0], // the element itself separate from its jq wrapper 7763 stickyHeight = 0, // the header height 7764 stickyTop = 0, // the header's current position 7765 scrollTop = 0, // the window's current scroll position 7766 lastScroll = 0, // the previous update's scroll position 7767 scrollDelta = 0, // the window scroll delta 7768 hideHeight = parseInt(scope.hideHeight || 0, 10); // how far up to scroll before revealing the header 7769 7770 7771 /** 7772 * Refactored for speed - this uses (mostly) pure JS DOM manipulation 7773 * to update the position of the sticky header given the current 7774 * status of the window scroll position vs the header's height. 7775 */ 7776 function updateScroll() { 7777 stickyHeight = el.offsetHeight + hideHeight; 7778 scrollTop = $window.pageYOffset; 7779 scrollDelta = lastScroll - scrollTop; 7780 stickyTop = 7781 element.position().top + 7782 scrollDelta; 7783 7784 if (scrollTop <= 0) { 7785 // Scrolled to the very top; just set the value to 0 7786 el.style.top = "0px"; 7787 } 7788 else if (scrollDelta > 0) { 7789 // Scrolling up; reveal the element until it reaches the top 7790 el.style.top = (stickyTop > 0 ? 0 : stickyTop) + "px"; 7791 } else if (scrollDelta < 0) { 7792 // Scrolling down; hide the element until it scrolls out of view 7793 el.style.top = (Math.abs(stickyTop) > stickyHeight ? -stickyHeight : stickyTop) + "px"; 7794 } 7795 // Update the previous scroll position 7796 lastScroll = scrollTop; 7797 } 7798 7799 /** 7800 * This simply ensures that there's enough top padding on 7801 * an element so that the header 'clears' it when the page 7802 * is scrolled all the way to the top. 7803 */ 7804 function updateHeaderClearance() { 7805 $(scope.stickyContainerSelector || "body")[0].style.paddingTop = element[0].offsetHeight + "px"; 7806 } 7807 7808 // Do both of our checks on initialisation. 7809 updateHeaderClearance(); 7810 updateScroll(); 7811 7812 // Double check header clearance in 250ms 7813 setTimeout(updateHeaderClearance, 250); 7814 } 7815 }; 7816 } 7817]); 7818 7819; 7820/** include: angular.universal-collapsing-menu.js **/ 7821window.core&&core.registerScript&&core.registerScript('/public/javascripts/angular.universal-collapsing-menu.js'); 7822/**
7823 * Stolen from Connections site 7824 * Adapted from lewisl's topMenuCTRL controller in angular.menus.js in the wpdcore style module. 7825 * 7826 * Used to control the visibility of top menu links via a 'more' item responsively 7827 * @author lewisl, noahm 7828 * 7829 * TODO 7830 */ 7831angular.module('universal-collapsing-menu', []).controller('universal-collapsingMenuCtrl', [ 7832 '$window', 7833 '$element', 7834 '$timeout', 7835 function ($window, $element, $timeout) { 7836 var vm = this; 7837 7838 // Grab some of the elements we need via jQuery (yuck, I know!) 7839 var topMenuItems = $element.find('ul > li:not(.universal-collapsing-menu__show-more,.in-dropdown)'); 7840 var topMenuHiddenItems = $element.find('ul > li.in-dropdown'); 7841 var topMenuShow = $element.find('.universal-collapsing-menu__show-more'); 7842 7843 // The threshold height above which the menu is considered to be wrapping 7844 var wrapHeight = 50; 7845 7846 // Whether the menu is shown or not 7847 vm.showMore = false; 7848 7849 // Whether the Show More button is focused or not 7850 vm.isFocused = false; 7851 7852 // Our list of hidden links (not elements, just objects with href and text) 7853 vm.hiddenLinks = []; 7854 7855 var prevWindowWidth = angular.element($window).innerWidth(); 7856 7857 /** 7858 * Init function to tell which collapsing menu this is (mobile or desktop version) 7859 */ 7860 vm.init = function(mobileMenu) { 7861 vm.mobileMenu = mobileMenu; 7862 if (vm.mobileMenu) { 7863 window.addEventListener('click', function(e) { 7864 if (!this.document.getElementById("universal-collapsing-menu__show-more").contains(e.target)) { 7865 $timeout(function () { 7866 vm.showMore = false; 7867 }, 1); 7868 } 7869 }); 7870 } 7871 } 7872 7873 /** 7874 * Unhides all of the menu items, then if the element is wrapping 7875 * (determined by its height being above the wrapHeight) it hides them one 7876 * at a time until the height reduces to or below the wrapHeight 7877 * 7878 * @param {Boolean} force if true, skip the window width check 7879 */ 7880 vm.check = function (force) { 7881 if (!force) { 7882 var checkedWindowWidth = angular.element($window).innerWidth(); 7883 if (checkedWindowWidth === prevWindowWidth) { 7884 return; 7885 } 7886 } 7887 prevWindowWidth = checkedWindowWidth; 7888 topMenuItems.show(); 7889 if ($element.height() > wrapHeight) { 7890 var index = topMenuItems.length - 1; 7891 while ($element.height() > wrapHeight) { 7892 topMenuItems.eq(index--).hide(); 7893 if (!vm.mobileMenu && index < 1) { 7894 break; 7895 } else if (vm.mobileMenu && index < 0) { 7896 break; 7897 } 7898 } 7899 } 7900 $timeout(function() { 7901 vm.populateHidden(); 7902 }); 7903 }; 7904 7905 /** 7906 * Moves hidden links into the hiddenLinks array 7907 */ 7908 vm.populateHidden = function () { 7909 vm.hiddenLinks = []; 7910 var hidden = topMenuItems.filter(':hidden'); 7911 if (!hidden) { 7912 return; 7913 } 7914 hidden.each(function (i, link) { 7915 link = angular.element(link).find('a'); 7916 vm.hiddenLinks.push({ 7917 href: link.attr('href'), 7918 text: link.text() 7919 }); 7920 }); 7921 topMenuHiddenItems.each(function (i, link) { 7922 topMenuHiddenItems.eq(i).hide(); 7923 link = angular.element(link).find('a'); 7924 vm.hiddenLinks.push({ 7925 href: link.attr('href'), 7926 text: link.text() 7927 }); 7928 }); 7929 }; 7930 7931 /** 7932 * Toggles whether the 'more' menu is open or not 7933 */ 7934 vm.toggleOpen = function (doOnMobile) { 7935 if (doOnMobile == vm.mobileMenu) { 7936 vm.showMore = !vm.showMore; 7937 } 7938 }; 7939 7940 /** 7941 * Function that gets called when the enter key is pressed, checks for focus and then opens the menu - for accessibility reasons 7942 */ 7943 vm.accessibleToggle = function(event) {
7944 if (vm.isFocused && event.which === 13) { 7945 vm.showMore = !vm.showMore; 7946 } 7947 }; 7948 7949 /** 7950 * Simple function that sets a flag to check if the show more button is focused 7951 */ 7952 vm.focusButton = function() { 7953 vm.isFocused = true; 7954 } 7955 7956 /** 7957 * Clears isFocused flag when the focus shifts to another element 7958 */ 7959 vm.blurButton = function() { 7960 vm.isFocused = false; 7961 } 7962 7963 /** 7964 * Closes the 'more' menu 7965 */ 7966 vm.close = function () { 7967 if (!vm.mobileMenu) { 7968 vm.showMore = false; 7969 } 7970 }; 7971 7972 // Watch the window resize event - note we do not debounce this function 7973 // as we want this to be as instantaneous as possible 7974 angular.element($window).bind('resize', vm.check); 7975 7976 angular.element($window).bind('wpd.resize', function () { 7977 vm.check(true); 7978 }); 7979 7980 vm.check(true); 7981 $timeout(function () { 7982 vm.check(true); 7983 }, 1); 7984 7985 } 7986]); 7987; 7988/** include: angular.universal-mega-menu.js **/ 7989window.core&&core.registerScript&&core.registerScript('/public/javascripts/angular.universal-mega-menu.js'); 7990/** 7991* OVERRIDE of the foundation_base version 7992*/ 7993angular.module("universal-mega-menu", []) 7994//NEW constant 7995.constant('EVENT_MEGA_MENU_OPEN', 'megaMenuOpen') 7996.controller("universalMegaMenuCtrl", ["$scope", function($scope) {}]) 7997.directive("megaMenuItem", ["$window", 'EVENT_MEGA_MENU_OPEN', function($window, EVENT_MEGA_MENU_OPEN) { 7998 return { 7999 restrict: "C", 8000 scope: true, 8001 link: function(scope, element, attrs) { 8002 scope.isOpen = false; 8003 scope.isFocused = false; 8004 scope.expanderColour = "#ffffff"; 8005 8006 var childMenu = element.find(".mega-menu__child").first(); 8007 var animationSpeed = 200; 8008 // TODO: ask Lew to make the property to target configurable from the system prefs of something 8009 var breakpoint = parseInt(getComputedStyle(document.body).getPropertyValue("--universal-mega-menu-breakpoint"), 10) || 1024; 8010 8011 var setState = function(state) { 8012 if (angular.element($window).outerWidth() < breakpoint) { 8013 return; 8014 } 8015 scope.isOpen = !!state; 8016 8017 scope.$emit(EVENT_MEGA_MENU_OPEN, scope.isOpen); 8018 scope.$broadcast(EVENT_MEGA_MENU_OPEN, scope.isOpen); 8019 scope.isOpen ? $('html').addClass('mega-menu--open') : $('html').removeClass('mega-menu--open'); 8020 }; 8021 8022 var doAnimation = function() { 8023 if (scope.isOpen) { 8024 childMenu.stop().slideDown(animationSpeed); 8025 } 8026 else { 8027 childMenu.stop().slideUp(animationSpeed); 8028 } 8029 }; 8030 8031 scope.toggle = function() { 8032 // This condition is here to prevent click events working on desktop size, as they don't behave properly due to other event listeners 8033 if (angular.element($window).outerWidth() < breakpoint) { 8034 scope.isOpen = !scope.isOpen; 8035 if (scope.isOpen) { 8036 scope.expanderColour = "#00148C"; 8037 } else { 8038 scope.expanderColour = "#ffffff"; 8039 } 8040 doAnimation(); 8041 } 8042 }; 8043 8044 /** 8045 * Function that gets called when the enter key is pressed, checks for focus and then opens the menu - for accessibility reasons 8046 */ 8047 scope.accessibleToggle = function(event) { 8048 if (scope.isFocused && event.which === 13) { 8049 scope.isOpen = !scope.isOpen; 8050 } 8051 }; 8052 8053 /** 8054 * Simple function that sets a flag to enable scope.accessibleToggle to check which expander button is focused 8055 */
8056 scope.focusExpander = function() { 8057 scope.isFocused = true; 8058 } 8059 8060 /** 8061 * Clears isFocused flag when the focus shifts to another element 8062 */ 8063 scope.blurExpander = function() { 8064 scope.isFocused = false; 8065 } 8066 8067 /** 8068 * Function to open the mega menu on desktop if it is tabbed into for accessibility reasons 8069 */ 8070 scope.toggleDesktop = function() { 8071 if (angular.element($window).outerWidth() >= breakpoint) { 8072 setState(!scope.isOpen); 8073 } 8074 } 8075 8076 scope.open = function() { 8077 setState(true); 8078 }; 8079 8080 scope.close = function() { 8081 setState(false); 8082 }; 8083 8084 if (breakpoint) { 8085 let mq = $window.matchMedia('(min-width: ' + breakpoint + 'px)'); 8086 $($window).on('resize', function() { 8087 if (mq.matches) { 8088 // unset the dynamic height 8089 $('nav.mega-menu').css('height', ''); 8090 // remove the styles introduced as a result of the animations in the mobile menu child toggles 8091 $('nav.mega-menu .mega-menu__child').attr('style', null); 8092 scope.isOpen = false; 8093 } 8094 }); 8095 } 8096 } 8097 } 8098}]); 8099; 8100/** include: angular.wpd-accessibility.js **/ 8101window.core&&core.registerScript&&core.registerScript('/public/javascripts/angular.wpd-accessibility.js'); 8102/** 8103 * @author noahm 8104 */ 8105var accessibility = angular.module('wpd-accessibility', []); 8106 8107//class SelfResetRegex { 8108// constructor(reg) { 8109// this.reg = reg; 8110// } 8111// exec(str) { 8112// var result = this.reg.exec(str); 8113// this.reg.lastIndex = 0; 8114// return result; 8115// } 8116//} 8117// 8118//class WPDColorObject { 8119// constructor(R, G, B, A) { 8120// this.R = R; 8121// this.G = G; 8122// this.B = B; 8123// this.A = A; 8124// } 8125//} 8126 8127/** 8128 * @constructor 8129 * @param {RegExp} reg 8130 * @returns {SelfResetRegex} 8131 */ 8132function SelfResetRegex(reg) { 8133 this.reg = reg; 8134}; 8135SelfResetRegex.prototype.exec = function(str) { 8136 var result = this.reg.exec(str); 8137 this.reg.lastIndex = 0; 8138 return result; 8139}; 8140 8141/** 8142 * @constructor 8143 * @param {Number} R (0-255) 8144 * @param {Number} G 8145 * @param {Number} B 8146 * @param {Number} A (0.0-1.0) 8147 * @returns {WPDColorObject} 8148 */ 8149function WPDColorObject(R, G, B, A) { 8150 this.R = R; 8151 this.G = G; 8152 this.B = B; 8153 this.A = A; 8154}; 8155 8156accessibility.constant('accessibilityKeyCodes', { 8157 'ENTER' : 13, 8158 'SPACE' : 32, 8159 'LEFT_ARROW' : 37, 8160 'UP_ARROW' : 38, 8161 'RIGHT_ARROW' : 39, 8162 'DOWN_ARROW' : 40 8163}); 8164accessibility.constant('baseFocusableElements', [ 8165 'button', 8166 'a', 8167 '.focusable-element', 8168 'select', 8169 'input', 8170 'textarea' 8171]); 8172accessibility.constant('baseFocusableExceptions', [ 8173 'a.skiptocontent' 8174]); 8175accessibility.constant('focusVariables', { 8176 transparencyThreshold: 0.1, //anything <= this consider to be transparent 8177 opacityThreshold: 0.9, //anything >= this consider to be opaque 8178 blendLevels: 2, //how deep should recursion go to blend with parent(s) 8179 defaultDarkFocus: (new WPDColorObject(48, 124, 127, 1)), // #307c7f green 8180 defaultLightFocus: (new WPDColorObject(168, 229, 240, 1)), // #a8e5f0 cold-blue 8181 transparentObj: {effectiveColor: (new WPDColorObject(255, 255, 255, 0))}, 8182 whiteObj: {effectiveColor: (new WPDColorObject(255, 255, 255, 1))}, 8183 minimumContrastReqLarge: 3, // https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html 8184 minimumContrastReqSmall: 4.5, 8185 textBoldSize: 18.5, 8186 textLargeSize: 24, 8187 rgbaRegex: (new SelfResetRegex(/^rgba?\(([0-9]{1,3})\s?,\s?([0-9]{1,3})\s?,\s?([0-9]{1,3})\s?(,\s?([0-9]{1,3}\.?[0-9]*))?\)$/gi)),
8188 hexaRegex: (new SelfResetRegex(/^#?([0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})([0-9A-Fa-f]{2})?$/gi)) // this one assumes that it won't see an invalid hex value with the format RGBaa (using hexa means you need RRGGBBaa) 8189 8190}); 8191 8192/** 8193 * Inject accessibilitySettingsProvider into your config() function to make setting changes. 8194 * 8195 * NOTE: If your focusColors in your CSS differ from the default ones above, you need to set them to match your CSS values, 8196 * otherwise this script will be comparing luminance for the wrong colors! 8197 */ 8198accessibility.provider('accessibilitySettings', ['focusVariables', function(focusVariables) { 8199 var self = this; 8200 8201 self.focusableElements = []; 8202 self.focusableExceptions = []; 8203 self.darkFocus = focusVariables.defaultDarkFocus; 8204 self.lightFocus = focusVariables.defaultLightFocus; 8205 8206 /** 8207 * 8208 * @param {String[] | String} what 8209 * @returns {undefined} 8210 */ 8211 self.addFocusableElement = function(what) { 8212 if (typeof what === 'string') { 8213 this.focusableElements.push(what); 8214 } 8215 else if (Array.isArray(what)) { 8216 for(var i = 0; i < what.length; i++) { 8217 if (typeof what[i] === 'string') { 8218 this.focusableElements.push(what[i]); 8219 } 8220 } 8221 } 8222 }; 8223 8224 /** 8225 * 8226 * @param {String[] | String} what 8227 * @returns {undefined} 8228 */ 8229 self.addFocusableException = function(what) { 8230 if (typeof what === 'string') { 8231 this.focusableExceptions.push(what); 8232 } 8233 else if (Array.isArray(what)) { 8234 for(var i = 0; i < what.length; i++) { 8235 if (typeof what[i] === 'string') { 8236 this.focusableExceptions.push(what[i]); 8237 } 8238 } 8239 } 8240 }; 8241 8242 /** 8243 * 8244 * @param {Number} r 8245 * @param {Number} g 8246 * @param {Number} b 8247 * @param {Number} a 8248 * @returns {undefined} 8249 */ 8250 self.setLightFocus = function(r,g,b,a) { 8251 this.lightFocus = new WPDColorObject(r,g,b,a); 8252 }; 8253 8254 /** 8255 * 8256 * @param {Number} r 8257 * @param {Number} g 8258 * @param {Number} b 8259 * @param {Number} a 8260 * @returns {undefined} 8261 */ 8262 self.setDarkFocus = function(r,g,b,a) { 8263 this.darkFocus = new WPDColorObject(r,g,b,a); 8264 }; 8265 8266 // Service object (not available for use in config() functions) 8267 this.$get = [ 8268 'baseFocusableElements', 8269 'baseFocusableExceptions', 8270 function(baseFocusableElements, baseFocusableExceptions) { 8271 return { 8272 getFocusableElements: function() { 8273 return baseFocusableElements.concat(self.focusableElements); 8274 }, 8275 getFocusableExceptions: function() { 8276 return baseFocusableExceptions.concat(self.focusableExceptions); 8277 }, 8278 getLightFocusColor: function() { 8279 return self.lightFocus; 8280 }, 8281 getDarkFocusColor: function() { 8282 return self.darkFocus; 8283 } 8284 }; 8285 } 8286 ]; 8287 }] 8288); 8289 8290// [WSME-590] 8291/** 8292 * @author noahm 8293 */ 8294accessibility.service('colorService', ['focusVariables', function(focusVariables){ 8295 var service = {}; 8296 8297 /** 8298 * Take a hex or 0-255 and determine its worth in a 0-1 range 8299 * @param {String | Number} value 8300 * @returns {null | Number} 8301 */
8302 service.normaliseValue = function(value) { 8303 if (value !== null && value !== undefined) { 8304 var isHex = /^[0-9a-fA-f]+$/gi; 8305 if (isHex.test(value)) { 8306 value = parseInt('0x'+value); 8307 } 8308 if (!isNaN(parseFloat(value))) { 8309 var max = 255; 8310 var min = 0; 8311 var value = parseFloat(value); 8312 8313 return (value - min) / (max - min); 8314 } 8315 } 8316 return null; 8317 }; 8318 8319 /** 8320 * 8321 * @param {Number} alpha 8322 * @param {Number} col1 8323 * @param {Number} col2 8324 * @returns {Number} 8325 */ 8326 service.blendColorValue = function(alpha, col1, col2) { 8327 alpha = parseFloat(alpha); 8328 col1 = parseFloat(col1); 8329 col2 = parseFloat(col2); 8330 return (alpha * col1) + ((1-alpha) * col2); 8331 }; 8332 8333 /** 8334 * 8335 * @param {WPDColorObject} back 8336 * @param {WPDColorObject} fore 8337 * @returns {WPDColorObject} 8338 */ 8339 service.blendColorObj = function(back, fore) { 8340 return new WPDColorObject( 8341 service.blendColorValue(fore.A, fore.R, back.R), 8342 service.blendColorValue(fore.A, fore.G, back.G), 8343 service.blendColorValue(fore.A, fore.B, back.B), 8344 (parseFloat(fore.A) + (parseFloat(back.A) * (1-parseFloat(fore.A)))) 8345 ); 8346 }; 8347 8348 8349 /** 8350 * This is not foolproof! It can't take into consideration background images, or the visual contents of img elements. 8351 * 8352 * Returns null if the element is deemed to be invisible/transparent according to display, visibility, opacity and bgColor alpha value (if below threshold) 8353 * Colours above opacityThreahold are returned as-is. 8354 * Colours deemed semi-transparent will attempt to blend with their parent's colours (up to blendLevel times) to get an approximate colour of what the user is actually seeing on screen. 8355 * This action is recursive, and will terminate when the blendDepth is reached, or the document object has been reached. 8356 * 8357 * The returned object's effectiveColour.A value is for the purpose of blending within this function. 8358 * For all intents and purposes the alpha of the ultimately returned effective colour should always be considered 1. 8359 * 8360 * @param {jQuery} $elem 8361 * @param {Number} blendDepth 8362 * @returns {null | Object} 8363 */ 8364 service.getEffectiveColor = function($elem, blendDepth) { 8365 //console.log($elem); 8366 8367 if ($elem[0] === document) return null; 8368 8369 if (blendDepth === undefined || blendDepth === null || blendDepth > focusVariables.blendLevels) { 8370 blendDepth = focusVariables.blendLevels; 8371 } 8372 //console.log('blendDepth: '+blendDepth); 8373 8374 var display = $elem.css('display'); 8375 if (display === 'none') { 8376 return null; 8377 } 8378 8379 var visibility = $elem.css('visibility'); 8380 if (visibility === 'hidden') { 8381 return null; 8382 } 8383 8384 // Technically I should be doing the blending thing with this value too, but considering this opacity change will propagate to any text inside the element, 8385 // I'm making the assumption that it isn't supposed to be focusable anyway. 8386 var opacity = $elem.css('opacity'); 8387 if (opacity !== '' && opacity <= focusVariables.transparencyThreshold) { 8388 return null; 8389 } 8390 8391 var bgColor = $elem.css('background-color'); 8392 //console.log(bgColor); 8393 if (bgColor === null || bgColor === undefined || bgColor === '' || bgColor === 'transparent') { 8394 return null; 8395 } 8396 8397 var rgbaMatches = focusVariables.rgbaRegex.exec(bgColor); 8398 var hexaMatches = focusVariables.hexaRegex.exec(bgColor); 8399 8400 var bgColorAlpha = 1; 8401 if (rgbaMatches && rgbaMatches.length > 1) { 8402 if (rgbaMatches[5] !== null && rgbaMatches[5] !== undefined) bgColorAlpha = rgbaMatches[5]; //apparently the rgba value .css() returns normalises the alpha value by itself. 8403 } 8404 else if (hexaMatches && hexaMatches.length > 1) {
8405 var x = service.normaliseValue(hexaMatches[2]); 8406 if (x !== null) bgColorAlpha = x; 8407 } 8408 8409 if (bgColorAlpha !== null && bgColorAlpha !== undefined && bgColorAlpha !== '') { 8410 if (bgColorAlpha <= focusVariables.transparencyThreshold) { 8411 return null; 8412 } 8413 } 8414 8415 var trueColorObj = null; 8416 if (rgbaMatches && rgbaMatches.length > 1) { 8417 trueColorObj = new WPDColorObject( 8418 (rgbaMatches[1] || 0), 8419 (rgbaMatches[2] || 0), 8420 (rgbaMatches[3] || 0), 8421 bgColorAlpha 8422 ); 8423 } 8424 else if (hexaMatches && hexaMatches.length > 1) { 8425 if (hexaMatches[1] !== null && hexaMatches[1] !== undefined) { 8426 if (hexaMatches[1].length === 3) { 8427 trueColorObj = new WPDColorObject( 8428 (parseInt('0x' + hexaMatches[1][0] + hexaMatches[1][0]) || 0), 8429 (parseInt('0x' + hexaMatches[1][1] + hexaMatches[1][1]) || 0), 8430 (parseInt('0x' + hexaMatches[1][2] + hexaMatches[1][2]) || 0), 8431 bgColorAlpha 8432 ); 8433 } 8434 else if (hexaMatches[1].length === 6) { 8435 trueColorObj = new WPDColorObject( 8436 (parseInt('0x' + hexaMatches[1][0] + hexaMatches[1][1]) || 0), 8437 (parseInt('0x' + hexaMatches[1][2] + hexaMatches[1][3]) || 0), 8438 (parseInt('0x' + hexaMatches[1][4] + hexaMatches[1][5]) || 0), 8439 bgColorAlpha 8440 ); 8441 } 8442 } 8443 } 8444 8445 if (trueColorObj === null) return null; 8446 8447 var effectiveColor = trueColorObj; 8448 8449 //start blending with parent bg Colors if this isn't deemed to be opaque enough. 8450 if (bgColorAlpha < focusVariables.opacityThreshold && $elem.parent()[0] !== document && blendDepth > 0) { 8451 //console.log('blend with parent'); 8452 var $parent = $elem.parent(); 8453 //console.log($parent); 8454 var parentCol; 8455 //attempt to skip over transparent parents. 8456 while (true) { 8457 if ($parent[0] === document) { 8458 parentCol = focusVariables.whiteObj; 8459 break; 8460 } 8461 parentCol = service.getEffectiveColor($parent, blendDepth - 1); //recurse 8462 if (parentCol) break; 8463 //console.log('parent was transparent, try again for depth ' + blendDepth ); 8464 $parent = $parent.parent(); 8465 //console.log($parent); 8466 } 8467 8468 parentCol = parentCol || focusVariables.transparentObj; 8469 if (parentCol) { 8470 effectiveColor = service.blendColorObj(parentCol.effectiveColor, trueColorObj); 8471 } 8472 } 8473 8474 // console.log('returned effectiveColor for depth '+blendDepth); 8475 //console.log(effectiveColor); 8476 8477 return { 8478 trueColor: bgColor, 8479 trueColorObj: trueColorObj, 8480 effectiveColor: effectiveColor 8481 }; 8482 }; 8483 8484 /** 8485 * Traverse up parents and return the first *effectively determined* background-colour that isn't transparent. 8486 * If all parents are transparent, you'll eventually hit the document object, which will return white as its bgColor. 8487 * 8488 * Again, not foolproof! This assumes that the elements have static positioning and are visually nested in their containers. 8489 * 8490 * @param {jQuery} $elem 8491 * @param {boolean} includingElem 8492 * @returns {WPDColorObj} 8493 */ 8494 service.determineBgColor = function($elem, includingElem) { 8495 if (includingElem) { 8496 var elemCol = service.getEffectiveColor($elem); 8497 if (elemCol) { 8498 return elemCol; 8499 } 8500 } 8501 var $parent = $elem.parent(); 8502 while (true) { 8503 if ($parent[0] === document) return focusVariables.whiteObj; 8504 var parentCol = service.getEffectiveColor($parent); 8505 if (parentCol) { 8506 return parentCol; 8507 } 8508 $parent = $parent.parent(); 8509 } 8510 }; 8511 8512 8513 /** 8514 * https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef 8515 * 8516 * Calculate the luminance value of an rgb colour, according to the definition in the link above. 8517 * 8518 * @param {WPDColorObj} colorObj 8519 * @returns {Number} 8520 */ 8521 service.determineLuminance = function(colorObj) { 8522 var r, g, b; 8523 var srgbColObj = { 8524 R: colorObj.R / 255, 8525 G: colorObj.G / 255, 8526 B: colorObj.B / 255, 8527 calculate: function(what) { 8528 if (what <= 0.03928) { 8529 return what / 12.92; 8530 } 8531 else { 8532 return Math.pow(((what + 0.055) / 1.055), 2.4); 8533 } 8534 } 8535 }; 8536 8537 r = srgbColObj.calculate(srgbColObj.R); 8538 g = srgbColObj.calculate(srgbColObj.G); 8539 b = srgbColObj.calculate(srgbColObj.B); 8540 8541 return (0.2126 * r) + (0.7152 * g) + (0.0722 * b); 8542 }; 8543 8544 /** 8545 * https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef 8546 * 8547 * Calculate the contrast ratio between two luminance values, accordiong to the definition in the link above. 8548 * 8549 * @param {Number} luminance1 8550 * @param {Number} luminance2 8551 * @returns {Number} 8552 */ 8553 service.contrastRatio = function(luminance1, luminance2) { 8554 var lighter = Math.max(luminance1, luminance2); 8555 var darker = Math.min(luminance1, luminance2); 8556 return (lighter + 0.05) / (darker + 0.05); 8557 }; 8558 8559 return service; 8560}]); 8561 8562accessibility.run([ 8563 'colorService', 8564 'accessibilitySettings',
8565 'focusVariables', 8566 'DOMObserver', 8567 '$timeout', 8568 function(colorService, accessibilitySettings, focusVariables, DOMObserver, $timeout) { 8569 var lightFocusLum = colorService.determineLuminance(accessibilitySettings.getLightFocusColor()); 8570 var darkFocusLum = colorService.determineLuminance(accessibilitySettings.getDarkFocusColor()); 8571 var whiteLum = 1; 8572 var blackLum = 0; 8573 8574 /** 8575 * 8576 * @param {jQuery} $elements 8577 * @returns {undefined} 8578 */ 8579 var setupEventListeners = function($elements) { 8580 $elements.each(function(idx, $elem) { 8581 $elem = angular.element($elem); 8582 $elem.on('focus', function(){ 8583 var backgroundColor = colorService.determineBgColor($elem); 8584 //console.log(backgroundColor); 8585 var luminance = colorService.determineLuminance(backgroundColor.effectiveColor); 8586 8587 var contrastWithLight = colorService.contrastRatio(luminance, lightFocusLum); 8588 var contrastWithDark = colorService.contrastRatio(luminance, darkFocusLum); 8589 8590 //console.log(contrastWithLight); 8591 //console.log(contrastWithDark); 8592 8593 // https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html 8594 var finalContrastReq = (parseFloat($elem.css('font-size')) >= focusVariables.textLargeSize || 8595 ((parseFloat($elem.css('font-size')) >= focusVariables.textBoldSize) && 8596 ($elem.css('font-weight') === 'bold' || 8597 $elem.css('font-weight') === 'bolder' || 8598 $elem.css('font-weight') === '700' || 8599 $elem.css('font-weight') === '800' || 8600 $elem.css('font-weight') === '900') 8601 ) 8602 ) ? focusVariables.minimumContrastReqLarge : focusVariables.minimumContrastReqSmall; 8603 var finalClass = 'focusable-element-'; 8604 8605 // console.log(finalContrastReq); 8606 8607 //light is passes, dark doesn't 8608 if (contrastWithLight >= finalContrastReq && contrastWithDark < finalContrastReq) finalClass += 'light'; 8609 //dark passes, light doesn't 8610 else if (contrastWithDark >= finalContrastReq && contrastWithLight < finalContrastReq) finalClass += 'dark'; 8611 //both pass, so pick the better of the two 8612 else if (contrastWithLight >= finalContrastReq && contrastWithDark >= finalContrastReq) { 8613 finalClass += (contrastWithLight >= contrastWithDark ? 'light' : 'dark'); 8614 } 8615 //neither custom colours cut it so go straight to either black or white outline. 8616 else { 8617 contrastWithLight = colorService.contrastRatio(luminance, whiteLum); 8618 contrastWithDark = colorService.contrastRatio(luminance, blackLum); 8619 8620 finalClass += (contrastWithLight >= contrastWithDark ? 'white' : 'black'); 8621 } 8622 $elem.removeClass('focusable-element-light', 'focusable-element-dark', 'focusable-element-white', 'focusable-element-black'); 8623 $elem.addClass(finalClass); 8624 }); 8625 $elem.on('blur', function() { 8626 $elem.removeClass('focusable-element-light', 'focusable-element-dark', 'focusable-element-white', 'focusable-element-black'); 8627 }); 8628 }); 8629 }; 8630 8631 $timeout(function() { 8632 //console.log('accessibility run!'); 8633 // it might have been nicer to watch document.activeElement, but apparently this doesn't get assigned for anything other than text type inputs for iOS. 8634 var $allFocusableElements = angular.element(document).find(accessibilitySettings.getFocusableElements().join(', ')).not(accessibilitySettings.getFocusableExceptions().join(', ')); 8635 //console.log($allFocusableElements); 8636 setupEventListeners($allFocusableElements); 8637 8638 //setup something to add more listeners for focusable elements added to the DOM later on. 8639 DOMObserver.addObserver(document, function(mutations) { 8640 for(var i = 0; i < mutations.length; i ++) { 8641 if (mutations[i].addedNodes.length) { 8642 var $newFocusables = angular.element(Array.prototype.slice.call(mutations[i].addedNodes).filter(function(element) { 8643 return element.nodeType === 1; 8644 })).find(accessibilitySettings.getFocusableElements().join(', ')).not(accessibilitySettings.getFocusableExceptions().join(', ')); 8645 //console.log($newFocusables); 8646 setupEventListeners($newFocusables); 8647 } 8648 } 8649 }); 8650 }); 8651 } 8652]); 8653 8654 8655//https://stackoverflow.com/questions/3219758/detect-changes-in-the-dom 8656accessibility.service('DOMObserver', ['$timeout', function($timeout) { 8657 var MutationObserver = window.MutationObserver || window.WebKitMutationObserver; 8658 8659 this.addObserver = function(obj, callback) { 8660 if( !obj || (obj.nodeType !== 1 && obj.nodeType !== 9)) return null; //allow Elements and Document 8661 8662 if (MutationObserver) { 8663 var observer = new MutationObserver(function(mutations, observer){ 8664 $timeout(function() { 8665 callback(mutations); 8666 }); 8667 }); 8668 // have the observer observe obj for changes in children 8669 observer.observe( obj, { childList:true, subtree:true }); 8670 } 8671 else if(window.addEventListener){ 8672 //console.log('window.addeventListener'); 8673 var f = function(mutations){ 8674 $timeout(function() { 8675 callback(mutations); 8676 }); 8677 }; 8678 obj.addEventListener('DOMNodeInserted', f, false); 8679 obj.addEventListener('DOMNodeRemoved', f, false); 8680 } 8681 }; 8682}]); 8683 8684 8685accessibility.controller('MaxLengthNotice', [ 8686 '$scope', 8687 '$element', 8688 '$timeout', 8689 function($scope, $element, $timeout) { 8690 var mln = this; 8691 var timeoutQueued = false;
8692 var timeoutValue = 1000; 8693 8694 mln.setAriaLive = function(remainingCount, $message) { 8695 if (remainingCount > mln.politeThreshold) { 8696 $message.attr('aria-live', 'off'); 8697 8698 if (timeoutValue !== null && timeoutValue !== undefined) { 8699 timeoutValue = 1000; 8700 } 8701 } 8702 else { 8703 if (remainingCount > mln.assertiveThreshold) { 8704 $message.attr('aria-live', 'polite'); 8705 8706 if (timeoutValue !== null && timeoutValue !== undefined) { 8707 8708 if (remainingCount > mln.assertiveThreshold + 50) { 8709 timeoutValue = 500; 8710 } 8711 else { 8712 timeoutValue = 200; 8713 } 8714 } 8715 } 8716 else { 8717 $message.attr('aria-live', 'assertive'); 8718 8719 if (timeoutValue !== null && timeoutValue !== undefined) { 8720 timeoutValue = 0; 8721 } 8722 } 8723 } 8724 }; 8725 8726 mln.init = function() { 8727 // we have to wait until our template is rendered (how $timeout works here I have no clue) 8728 $timeout(function() { 8729 var $message = $element.find('[data-mln-message]'); 8730 var $messageTagon = $('<span data-mln-refresh-message></span>'); 8731 $message.append($messageTagon); 8732 var $textField = $element.find('[data-mln-field]'); 8733 var maxCharacters = $textField.attr('maxlength'); 8734 var toggleTagon = false; 8735 8736 if (mln.makeAtomic) { 8737 $message.attr('aria-atomic','true'); 8738 } 8739 8740 //initally get textfield char limit and set aria-live 8741 mln.setAriaLive(maxCharacters - $textField.val().length, $message); 8742 8743 var remainingCount = maxCharacters - $textField.val().length; 8744 8745 $textField.on('keyup', function() { 8746 //don't spam evaluating this on every input event, so use timeouts 8747 // let us know if we already have one queued so we don't end up stacking them. 8748 if (!timeoutQueued) { 8749 $timeout(function() { 8750 remainingCount = maxCharacters - $textField.val().length; 8751 8752 mln.setAriaLive(remainingCount, $message); 8753 8754 // reiterate message if they keep trying to type once limit is reached 8755 if (remainingCount <= 0) { 8756 toggleTagon = !toggleTagon; 8757 $messageTagon.html(toggleTagon ? '': ' '); // This is enough to get the screenreader to repeat the outer message. 8758 } // if you're wondering why we can't just tale and repaste the .text() 8759 // of the $message, it's because we'd lose the angular expression generating it. 8760 8761 8762 timeoutQueued = false; 8763 }, timeoutValue); 8764 8765 timeoutQueued = true; 8766 } 8767 }); 8768 }); 8769 }; 8770 } 8771]); 8772 8773accessibility.directive('maxLengthNotice', [ 8774 function() { 8775 return { 8776 scope: { 8777 makeAtomic: '<', 8778 politeThreshold: '<', 8779 assertiveThreshold: '<' 8780 }, 8781 transclude: true, 8782 bindToController: true, 8783 controllerAs: 'mln', 8784 controller: 'MaxLengthNotice', 8785 template: '<div ng-init="mln.init()"><ng-transclude></ng-transclude></div>' 8786 }; 8787 } 8788]); 8789 8790; 8791/** include: angular.autocomplete.js **/ 8792window.core&&core.registerScript&&core.registerScript('/public/javascripts/angular.autocomplete.js'); 8793var autocomplete = angular.module('autocomplete', []); 8794 8795/** 8796 * Service for search AutoComplete calls 8797 */ 8798autocomplete.service('AutoComplete', ['$loading', '$requester', '$q', function($loading, $requester, $q) { 8799 // private vars 8800 var _json = $requester({ 8801 'autoComplete': 'SearchFront.autoComplete', 8802 'promotedResults': 'SearchFront.promotedSearchResultsAutoComplete' 8803 }), 8804 _loader = {load: function () { }, clear: function () { }}, 8805 _searchTerm = null, 8806 _successCb = [], 8807 _errorCb = angular.noop, 8808 _timeout = null; 8809 8810 _json.setLoader(_loader); 8811 8812 8813 var callRefresh = function() { 8814 var deferred = $q.defer(); 8815 8816 _json.autoComplete( 8817 { 8818 searchTerm: _searchTerm, 8819 }, function(response) { 8820 deferred.resolve({data: response}); 8821 }.bind(this), 8822 deferred.reject 8823 ); 8824 8825 return deferred.promise; 8826 }; 8827 8828 /** 8829 * Promoted serach results 8830 */ 8831 var promotedResultsSearch = function() { 8832 var deferred = $q.defer(); 8833 8834 _json.promotedResults( 8835 { 8836 searchTerm: _searchTerm 8837 },function(response) { 8838 deferred.resolve({success: true, data: response}); 8839 }.bind(this), 8840 deferred.reject 8841 ); 8842 8843 return deferred.promise; 8844 }; 8845 8846 this.refresh = function() { 8847 $q.all({searchData: callRefresh(), 8848 promotedData: promotedResultsSearch()}) 8849 .then(beforeSuccess) 8850 .catch(_errorCb); 8851 }; 8852 8853 /** 8854 * Function to set the current search term 8855 * @param {String} searchTerm search term to try and auto complete 8856 */ 8857 this.setSearchTerm = function(searchTerm) { 8858 _searchTerm = searchTerm; 8859 8860 //clear timeout if exists 8861 if(_timeout !== null) clearTimeout(_timeout); 8862 //set timeout before handling - auto complete. 8863 _timeout = setTimeout(this.refresh, 250); 8864 } 8865 8866 /** 8867 * Sets the success callback for auto complete 8868 * @param {Function} successCallback the success callback 8869 * @returns {AutoComplete} this 8870 */ 8871 this.onSuccess = function(successCallback) { 8872 _successCb.push(successCallback); 8873 return this; 8874 }; 8875 8876 /** 8877 * Sets the error callback for auto complete 8878 * @param {Function} errorCallback the error callback 8879 * @returns {AutoComplete} this 8880 */ 8881 this.onFailure = function(errorCallback) { 8882 _errorCb = errorCallback; 8883 return this; 8884 }; 8885 8886 /** 8887 * Trusts the hit description as html before 8888 * sending the response to the controller 8889 * 8890 * @param {searchResult} searchResult the response 8891 */ 8892 var beforeSuccess = function(searchResult) { 8893 var result = {}; 8894 8895 if (searchResult && searchResult.promotedData.data.promotedResults) { 8896 var promotedResults = JSON.parse(searchResult.promotedData.data.promotedResults); 8897 var uniquePromotedResults = []; 8898 8899 if (Array.isArray(promotedResults) && Array.isArray(searchResult.searchData.data.options)) { 8900 8901 // Ensure no duplicates in promoted result suggestions.
8902 promotedResults.forEach(function(promotedResult) { 8903 if (!doesArrayContainPromotedResult(uniquePromotedResults, promotedResult)) { 8904 uniquePromotedResults.push(promotedResult); 8905 } 8906 }); 8907 8908 uniquePromotedResults.forEach(function(pr) { 8909 pr.text = pr.title; 8910 }); 8911 result.promotedData = uniquePromotedResults; 8912 } 8913 } 8914 8915 if (searchResult && searchResult.searchData.data.options) { 8916 result.searchData = searchResult.searchData.data.options; 8917 } 8918 8919 _successCb.forEach(function(fn) { 8920 fn(result); 8921 }); 8922 }; 8923 8924 8925 /** 8926 * Check whether or not an item already exists within an array. 8927 * 8928 * @param {array} uniqueArray an array of unique values. 8929 * @param {object} item the item that might already exist in the unique array. 8930 * @returns {Boolean} whether or not an item already exists in the unique array. 8931 */ 8932 function doesArrayContainPromotedResult(uniqueArray, item) { 8933 return (uniqueArray.filter(function(uniqueItem) { return uniqueItem.id === item.id && uniqueItem.className === item.className; } )).length > 0; 8934 } 8935}]); 8936; 8937/** include: angular.find-my-supplier.js **/ 8938window.core&&core.registerScript&&core.registerScript('/public/javascripts/angular.find-my-supplier.js'); 8939var findMySupplier = angular.module('find-my-supplier', []); 8940 8941findMySupplier.directive('findMySupplierLink', ['$timeout','vexAccessibility',function ($timeout,vexAccessibility) { 8942 return { 8943 restrict: 'EC', 8944 transclude: true, 8945 scope: { 8946 cssClasses: '@' 8947 }, 8948 template: '<button data-ng-click="openDialog()" tabindex="0" aria-pressed="false" class="find-my-supplier-btn" data-ng-class="cssClasses">' + 8949 '<ng-transclude>' + 8950 '<span>Find out more</span>' + 8951 '</ng-transclude>' + 8952 '</button>', 8953 controller: ['$scope', function($scope) { 8954 var template = '<iframe src="//supplier.westernpower.co.uk/" width="820" height="600" frameBorder="0"></iframe>'; 8955 $scope.openDialog = function() { 8956 vex.dialog.open({ 8957 input: template, 8958 contentClassName: 'find-my-supplier-dialog', 8959 showCloseButton: true, 8960 buttons: [ 8961 $.extend({}, vex.dialog.buttons.NO, {text: 'Close'}) 8962 ], 8963 afterOpen: function () { 8964 var elementsToHide = ['header', 'footer', 'section', '.breadcrumb-container', '.zopim', '#maincontent','.header-hero-panel-container']; 8965 var elementsToIndex = []; 8966 vexAccessibility.openVex(elementsToHide, elementsToIndex, ''); 8967 }, 8968 afterClose: function (){ 8969 var elementsToAdd = ['header', 'footer', 'section', '.breadcrumb-container', '.zopim', '#maincontent','.header-hero-panel-container']; 8970 vexAccessibility.closeVex(elementsToAdd, '.find-my-supplier-btn'); 8971 } 8972 }); 8973 angular.element('.find-my-supplier-dialog').css('min-width', '860px'); 8974 }; 8975 }] 8976 }; 8977}]); 8978; 8979/** include: angular.cookie-notice.js **/ 8980window.core&&core.registerScript&&core.registerScript('/public/javascripts/angular.cookie-notice.js'); 8981/** 8982 * AngularJS Cookie notice module 8983 * @author keiranc 8984 */ 8985var cookieNotice = angular.module('cookie-notice', ['ngCookies']); 8986 8987cookieNotice.service('CookieNotice', ['$cookies', '$window', '$timeout', '$requester', function($cookies, $window, $timeout, $requester) { 8988 8989 var $json = $requester({'set': 'CoreCookieFront.set_JSON'}), 8990 loader = { 8991 load: function () {}, 8992 clear: function () {} 8993 }; 8994 8995 $json.setLoader(loader); 8996 8997 8998 8999 /** 9000 * Gets the current cookie level cookie 9001 * @param {String} cookieName the name of the cookie 9002 * @returns {Object} the current cookie 9003 */ 9004 this.getCurrentCookie = function(cookieName) { 9005 return $cookies.getObject(cookieName); 9006 }; 9007 9008 /** 9009 * Gets the allowed cookie level 9010 * @param {String} cookieName the name of the cookie 9011 * @returns {Number} the current cookie level 9012 */
9013 this.getAllowedCookieLevel = function(cookieName) { 9014 var currentCookie = this.getCurrentCookie(cookieName); 9015 if (currentCookie === null || !angular.isDefined(currentCookie)) return 0; 9016 return parseInt(currentCookie); 9017 }; 9018 9019 9020 9021 /** 9022 * Method to get the acceted hash 9023 * 9024 * @param {String} cookieName 9025 * @returns {String} The hash value that the user accepted 9026 */ 9027 this.getAcceptedHash = function(cookieName) { 9028 return $cookies.getObject(cookieName+"_hash"); 9029 } 9030 9031 /** 9032 * A function to test the accpeted hash matches what we now exppect 9033 * 9034 * @param {String} expectedHash 9035 * @returns {Boolean} True if the accpeted hash matches our stored hash 9036 */ 9037 this.testAcceptedHash = function(expectedHash) { 9038 var acceptedHash = this.getAcceptedHash(); 9039 9040 var allow = acceptedHash === expectedHash; 9041 9042 //Clear cookies if hash does not match 9043 if(!allow) { 9044 this.reject(); 9045 } 9046 9047 return allow; 9048 } 9049 9050 /** 9051 * Checks whether a certain level is allowed 9052 * @param {String} cookieName the name of the cookie 9053 * @param {Number} level the level to match 9054 * @returns {Boolean} whether that level is allowed 9055 */ 9056 this.isAllowedAt = function(cookieName, level) { 9057 var cookie = this.getCurrentCookie(cookieName); 9058 return (parseInt(cookie) & level) > 0;
9059 }; 9060 9061 /** 9062 * A little cross-browser gimmickary to create events under IE 11 9063 * 9064 * @param {type} type 9065 * @returns {angular.cookie-noticeL#7.createEvent.event} 9066 */ 9067 function createEvent(type) { 9068 var event; 9069 9070 if (typeof(Event) === 'function') { 9071 event = new Event('submit'); 9072 } else{ 9073 event = document.createEvent('Event'); 9074 event.initEvent('submit', true, true); 9075 } 9076 9077 return event 9078 } 9079 /** 9080 * Accepts a certain level of cookies 9081 * @param {String} cookieName the name of the cookie 9082 * @param {Number} level level to accept 9083 * @param {String} domain the domain to set the cookie for 9084 */ 9085 this.accept = function(cookieName, level, domain, hash) { 9086 var expires = new Date(); 9087 expires = new Date(expires.getFullYear()+1, expires.getMonth(), expires.getDate()); 9088 9089 $cookies.putObject(cookieName, level, { 9090 domain: domain, 9091 expires: expires, 9092 path: "/" 9093 }); 9094 $cookies.putObject(cookieName+"_hash", hash, { 9095 domain: domain, 9096 expires: expires, 9097 path: "/" 9098 }); 9099 9100 $json.set({ level: level, hash: hash } ) 9101 9102 $timeout(function() { 9103 $window.dispatchEvent(createEvent('resize')); 9104 }, 500); 9105 }; 9106 9107 /** 9108 * Function to reject if the hash no longer matches 9109 * @param {String} cookieName the name of the cookie 9110 * @param {String} domain the domain to set the cookie for 9111 */ 9112 this.reject = function(cookieName, domain) { 9113 $cookies.remove(cookieName, { 9114 domain: domain, 9115 path: "/" 9116 }); 9117 9118 $cookies.remove(cookieName+"_hash", { 9119 domain: domain, 9120 path: "/" 9121 }); 9122 } 9123 9124 /** 9125 * This is used to generate a hash of the current cookie levels, so if these change consent can be re-requested 9126 * @param {Map} cookieLevels All levels plus bit wise value 9127 * @returns {String} Hash of the cookie levels and their consent level 9128 */ 9129 this.generateHash = function(cookieLevels) { 9130 var cookie = ""; 9131 9132 for (key in cookieLevels) { 9133 cookie += key + "-" + cookieLevels[key] + ";"; 9134 } 9135 9136 return cookie.split("").reduce(function(a,b){a=((a<<5)-a)+b.charCodeAt(0);return a&a},0); 9137 } 9138 9139}]); 9140 9141cookieNotice.controller('CookieNoticeController', ['CookieNotice', function(CookieNotice) { 9142 var vm = this; 9143 9144 vm.visible = false; 9145 9146 /** 9147 * Function to load script into page 9148 * @param {String} src The URL or body of the script to add to the site 9149 */ 9150 var addScript = function(src) { 9151 var nonce = document.querySelector('script[nonce]'); 9152 9153 if(nonce != null) { 9154 nonce = nonce.getAttribute('nonce'); 9155 } 9156 9157 var script = document.createElement('script'); 9158 script.type = 'text/javascript'; 9159 script.setAttribute("nonce", nonce); 9160 9161 //test if script is a URL 9162 if(!src.indexOf("/") || !src.indexOf("http")) { 9163 script.src = src; 9164 } else { 9165 script.appendChild(document.createTextNode(src)); 9166 } 9167 9168 document.getElementsByTagName('head')[0].appendChild(script); 9169 } 9170 9171 /** 9172 * Called by angular button click 9173 * uses CookieNotice service to accept cookie notice 9174 */ 9175 vm.accept = function() { 9176 9177 vm.visible = false; 9178 vm.level = 0; 9179 9180 //calculate level 9181 for(key in vm.ticked) { 9182 if(vm.ticked[key]) { 9183 vm.level += vm.cookieLevels[key]; 9184 9185 //load scripts 9186 var scripts = vm.cookieScripts[key]; 9187 for(script in scripts) { 9188 addScript(scripts[script]);
9189 } 9190 9191 //fire GTM events 9192 if(typeof dataLayer !== 'undefined') { 9193 dataLayer.push({'event': 'cookie_accepted_'+key}); 9194 } 9195 } 9196 } 9197 //store value for subsequent pages 9198 CookieNotice.accept(vm.key, vm.level, vm.domain, CookieNotice.generateHash(vm.cookieLevels)); 9199 9200 }; 9201 9202 vm.checkAll = function() { 9203 for(key in vm.ticked) { 9204 9205 vm.ticked[key] = true; 9206 } 9207 9208 this.accept(); 9209 9210 }; 9211 9212 // accept default level 9213 vm.init = function() { 9214 vm.cookieLevels = angular.fromJson(window.core.cookieLevels); 9215 vm.cookieScripts = angular.fromJson(window.core.cookieScripts); 9216 vm.ticked = {}; 9217 9218 //make sure domain does not have a leading or trailing space 9219 vm.domain = vm.domain.trim(); 9220 9221 //If not in CMS and we haven't accepted the current cookie groups show preference 9222 if (!vm.cmsMode && (typeof CookieNotice.getAcceptedHash(vm.key) === 'undefined' || CookieNotice.testAcceptedHash(CookieNotice.generateHash(vm.cookieLevels)))) { 9223 for(key in vm.cookieLevels) { 9224 9225 vm.ticked[key] = vm.allTicked == 'true'; 9226 console.log(vm.ticked[key], vm.allTicked); 9227 } 9228 vm.visible = true; 9229 } else { 9230 //Cookie preference has already been set 9231 for(key in vm.cookieLevels) { 9232 if(CookieNotice.isAllowedAt(vm.key, vm.cookieLevels[key])) { 9233 //load scripts 9234 var scripts = vm.cookieScripts[key]; 9235 for(script in scripts) { 9236 addScript(scripts[script]); 9237 } 9238 9239 //fire GTM events 9240 if(typeof dataLayer !== 'undefined') { 9241 dataLayer.push({'event': 'cookie_accepted_'+key}); 9242 } 9243 } 9244 } 9245 } 9246 }; 9247 9248 window.core = window.core || {} 9249 9250 /** 9251 * Method which returns if your allowed to set cookies for current level 9252 * 9253 * @param {type} level The level to check i.e. "Tracking" 9254 * @returns {boolean} True if you can set the cookie 9255 */ 9256 window.core.cookieAllowedForLevel = function(level) { 9257 return CookieNotice.isAllowedAt(vm.key, vm.cookieLevels[level]); 9258 } 9259}]); 9260 9261cookieNotice.directive('cookieNotice', [function() { 9262 return { 9263 scope: { 9264 domain: '@', 9265 key: '@', 9266 cmsMode: '=mode', 9267 allTicked: '@' 9268 }, 9269 controllerAs: 'vm', 9270 bindToController: true, 9271 controller: 'CookieNoticeController', 9272 transclude: true, 9273 template: '<div data-ng-show="vm.visible" data-ng-init="vm.init()">\ 9274 <ng-transclude></ng-transclude> \ 9275 </div>' 9276 }; 9277}]); 9278 9279;
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.