vendor: 4,299 bytes, lines 1-2
1/*! jQuery v2.1.4 | (c) 2005, 2015 jQuery Foundation, Inc. | jquery.org/license */ 2!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l=a.document,m="2.1.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)+1>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b="length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*ne
vendor: 18,010 bytes, line 2
2w Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,aa=/[+~]/,ba=/'|\\/g,ca=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),da=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ea=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fa){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(ba,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+ra(o[l]);w=aa.test(a)&&pa(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",ea,!1):e.attachEvent&&e.attachEvent("onunload",ea)),p=!f(g),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="<a id='"+u+"'></a><select id='"+u+"-\f]' msallowcapture=''><option selected=''></option></select>",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?la(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ca,da),a[3]=(a[3]||a[4]||a[5]||"").replace(ca,da),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ca,da).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(ca,da),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return W.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(ca,da).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:oa(function(){return[0]}),last:oa(function(a,b){return[b-1]}),eq:oa(function(a,b,c){return[0>c?c+b:c]}),even:oa(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:oa(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:oa(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:oa(function(a,b,c){for(var d=0>c?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=ma(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=na(b);function qa(){}qa.prototype=d.filters=d.pseudos,d.setFilters=new qa,g=ga.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){(!c||(e=S.exec(h)))&&(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=T.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(R," ")}),h=h.slice(c.length));for(g in d.filter)!(e=X[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?ga.error(a):z(a,i).slice(0)};function ra(a){for(var b=0,c=a.length,d="";c>b;b++)d+=a[b].value;return d}function sa(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function ta(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ua(a,b,c){for(var d=0,e=b.length;e>d;d++)ga(a,b[d],c);return c}function va(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wa(a,b,c,d,e,f){return d&&!d[u]&&(d=wa(d)),e&&!e[u]&&(e=wa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ua(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:va(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=va(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=va(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sa(function(a){return a===b},h,!0),l=sa(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sa(ta(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wa(i>1&&ta(m),i>1&&ra(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xa(a.slice(i,e)),f>e&&xa(a=a.slice(e)),f>e&&ra(a))}m.push(c)}return ta(m)}function ya(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=va(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&ga.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,ya(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ca,da),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ca,da),aa.test(j[0].type)&&pa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&ra(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,aa.test(a)&&pa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ja(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")}
vendor: 11,889 bytes, lines 2-3
2)||ka("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+K.uid++}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){ 3return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b)},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?n.queue(this[0],a):void 0===b?this:this.each(function(){var c=n.queue(this,a,b);n._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&n.dequeue(this,a)})},dequeue:function(a){return this.each(function(){n.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=n.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=L.get(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}
vendor: 7,763 bytes, line 3
3});var Q=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,R=["Top","Right","Bottom","Left"],S=function(a,b){return a=b||a,"none"===n.css(a,"display")||!n.contains(a.ownerDocument,a)},T=/^(?:checkbox|radio)$/i;!function(){var a=l.createDocumentFragment(),b=a.appendChild(l.createElement("div")),c=l.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="<textarea>x</textarea>",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h<b.length&&g.push({elem:this,handlers:b.slice(h)}),g},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return null==a.which&&(a.which=null!=b.charCode?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,b){var c,d,e,f=b.button;return null==a.pageX&&null!=b.clientX&&(c=a.target.ownerDocument||l,d=c.documentElement,e=c.body,a.pageX=b.clientX+(d&&d.scrollLeft||e&&e.scrollLeft||0)-(d&&d.clientLeft||e&&e.clientLeft||0),a.pageY=b.clientY+(d&&d.scrollTop||e&&e.scrollTop||0)-(d&&d.clientTop||e&&e.clientTop||0)),a.which||void 0===f||(a.which=1&f?1:2&f?3:4&f?2:0),a}},fix:function(a){if(a[n.expando])return a;var b,c,d,e=a.type,f=a,g=this.fixHooks[e];g||(this.fixHooks[e]=g=W.test(e)?this.mouseHooks:V.test(e)?this.keyHooks:{}),d=g.props?this.props.concat(g.props):this.props,a=new n.Event(f),b=d.length;while(b--)c=d[b],a[c]=f[c];return a.target||(a.target=l),3===a.target.nodeType&&(a.target=a.target.parentNode),g.filter?g.filter(a,f):a},special:{load:{noBubble:!0},focus:{trigger:function(){return this!==_()&&this.focus?(this.focus(),!1):void 0},delegateType:"focusin"},blur:{trigger:function(){return this===_()&&this.blur?(this.blur(),!1):void 0},delegateType:"focusout"},click:{trigger:function(){return"checkbox"===this.type&&this.click&&n.nodeName(this,"input")?(this.click(),!1):void 0},_default:function(a){return n.nodeName(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}},simulate:function(a,b,c,d){var e=n.extend(new n.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?n.event.trigger(e,null,b):n.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},n.removeEvent=function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)},n.Event=function(a,b){return this instanceof n.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?Z:$):this.type=a,b&&n.extend(this,b),this.timeStamp=a&&a.timeStamp||n.now(),void(this[n.expando]=!0)):new n.Event(a,b)},n.Event.prototype={isDefaultPrevented:$,isPropagationStopped:$,isImmediatePropagationStopped:$,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=Z,a&&a.preventDefault&&a.preventDefault()},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=Z,a&&a.stopPropagation&&a.stopPropagation()},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=Z,a&&a.stopImmediatePropagation&&a.stopImmediatePropagation(),this.stopPropagation()}}
vendor: 42,384 bytes, lines 3-4
3,n.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(a,b){n.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return(!e||e!==d&&!n.contains(d,e))&&(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),k.focusinBubbles||n.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){n.event.simulate(b,a.target,n.event.fix(a),!0)};n.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=L.access(d,b);e||d.addEventListener(a,c,!0),L.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=L.access(d,b)-1;e?L.access(d,b,e):(d.removeEventListener(a,c,!0),L.remove(d,b))}}}),n.fn.extend({on:function(a,b,c,d,e){var f,g;if("object"==typeof a){"string"!=typeof b&&(c=c||b,b=void 0);for(g in a)this.on(g,b,c,a[g],e);return this}if(null==c&&null==d?(d=b,c=b=void 0):null==d&&("string"==typeof b?(d=c,c=void 0):(d=c,c=b,b=void 0)),d===!1)d=$;else if(!d)return this;return 1===e&&(f=d,d=function(a){return n().off(a),f.apply(this,arguments)},d.guid=f.guid||(f.guid=n.guid++)),this.each(function(){n.event.add(this,a,d,c,b)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,n(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return(b===!1||"function"==typeof b)&&(c=b,b=void 0),c===!1&&(c=$),this.each(function(){n.event.remove(this,a,c,b)})},trigger:function(a,b){return this.each(function(){n.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];return c?n.event.trigger(a,b,c,!0):void 0}});var aa=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,ba=/<([\w:]+)/,ca=/<|&#?\w+;/,da=/<(?:script|style|link)/i,ea=/checked\s*(?:[^=]|=\s*.checked.)/i,fa=/^$|\/(?:java|ecma)script/i,ga=/^true\/(.*)/,ha=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,ia={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};ia.optgroup=ia.option,ia.tbody=ia.tfoot=ia.colgroup=ia.caption=ia.thead,ia.th=ia.td;function ja(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function ka(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function la(a){var b=ga.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function ma(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function na(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function oa(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pa(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=oa(h),f=oa(a),d=0,e=f.length;e>d;d++)pa(f[d],g[d]);if(b)if(c)for(f=f||oa(a),g=g||oa(h),d=0,e=f.length;e>d;d++)na(f[d],g[d]);else na(a,h);return g=oa(h,"script"),g.length>0&&ma(g,!i&&oa(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(ca.test(e)){f=f||k.appendChild(b.createElement("div")),g=(ba.exec(e)||["",""])[1].toLowerCase(),h=ia[g]||ia._default,f.innerHTML=h[1]+e.replace(aa,"<$1></$2>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=oa(k.appendChild(e),"script"),i&&ma(f),c)){j=0;while(e=f[j++])fa.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(oa(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&ma(oa(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(oa(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!da.test(a)&&!ia[(ba.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(aa,"<$1></$2>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(oa(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(oa(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&ea.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(oa(c,"script"),ka),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,oa(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,la),j=0;g>j;j++)h=f[j],fa.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(ha,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qa,ra={};function sa(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function ta(a){var b=l,c=ra[a];return c||(c=sa(a,b),"none"!==c&&c||(qa=(qa||n("<iframe frameborder='0' width='0' height='0'/>")).appendTo(b.documentElement),b=qa[0].contentDocument,b.write(),b.close(),c=sa(a,b),qa.detach()),ra[a]=c),c}var ua=/^margin/,va=new RegExp("^("+Q+")(?!px)[a-z%]+$","i"),wa=function(b){return b.ownerDocument.defaultView.opener?b.ownerDocument.defaultView.getComputedStyle(b,null):a.getComputedStyle(b,null)};function xa(a,b,c){var d,e,f,g,h=a.style;return c=c||wa(a),c&&(g=c.getPropertyValue(b)||c[b]),c&&(""!==g||n.contains(a.ownerDocument,a)||(g=n.style(a,b)),va.test(g)&&ua.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0!==g?g+"":g}function ya(a,b){return{get:function(){return a()?void delete this.get:(this.get=b).apply(this,arguments)}}}!function(){var b,c,d=l.documentElement,e=l.createElement("div"),f=l.createElement("div");if(f.style){f.style.backgroundClip="content-box",f.cloneNode(!0).style.backgroundClip="",k.clearCloneStyle="content-box"===f.style.backgroundClip,e.style.cssText="border:0;width:0;height:0;top:0;left:-9999px;margin-top:1px;position:absolute",e.appendChild(f);function g(){f.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:block;margin-top:1%;top:1%;border:1px;padding:1px;width:4px;position:absolute",f.innerHTML="",d.appendChild(e);var g=a.getComputedStyle(f,null);b="1%"!==g.top,c="4px"===g.width,d.removeChild(e)}a.getComputedStyle&&n.extend(k,{pixelPosition:function(){return g(),b},boxSizingReliable:function(){return null==c&&g(),c},reliableMarginRight:function(){var b,c=f.appendChild(l.createElement("div"));return c.style.cssText=f.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:0",c.style.marginRight=c.style.width="0",f.style.width="1px",d.appendChild(e),b=!parseFloat(a.getComputedStyle(c,null).marginRight),d.removeChild(e),f.removeChild(c),b}})}}(),n.swap=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};var za=/^(none|table(?!-c[ea]).+)/,Aa=new RegExp("^("+Q+")(.*)$","i"),Ba=new RegExp("^([+-])=("+Q+")","i"),Ca={position:"absolute",visibility:"hidden",display:"block"},Da={letterSpacing:"0",fontWeight:"400"},Ea=["Webkit","O","Moz","ms"];function Fa(a,b){if(b in a)return b;var c=b[0].toUpperCase()+b.slice(1),d=b,e=Ea.length;while(e--)if(b=Ea[e]+c,b in a)return b;return d}function Ga(a,b,c){var d=Aa.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function Ha(a,b,c,d,e){for(var f=c===(d?"border":"content")?4:"width"===b?1:0,g=0;4>f;f+=2)"margin"===c&&(g+=n.css(a,c+R[f],!0,e)),d?("content"===c&&(g-=n.css(a,"padding"+R[f],!0,e)),"margin"!==c&&(g-=n.css(a,"border"+R[f]+"Width",!0,e))):(g+=n.css(a,"padding"+R[f],!0,e),"padding"!==c&&(g+=n.css(a,"border"+R[f]+"Width",!0,e)));return g}function Ia(a,b,c){var d=!0,e="width"===b?a.offsetWidth:a.offsetHeight,f=wa(a),g="border-box"===n.css(a,"boxSizing",!1,f);if(0>=e||null==e){if(e=xa(a,b,f),(0>e||null==e)&&(e=a.style[b]),va.test(e))return e;d=g&&(k.boxSizingReliable()||e===a.style[b]),e=parseFloat(e)||0}return e+Ha(a,b,c||(g?"border":"content"),d,f)+"px"}function Ja(a,b){for(var c,d,e,f=[],g=0,h=a.length;h>g;g++)d=a[g],d.style&&(f[g]=L.get(d,"olddisplay"),c=d.style.display,b?(f[g]||"none"!==c||(d.style.display=""),""===d.style.display&&S(d)&&(f[g]=L.access(d,"olddisplay",ta(d.nodeName)))):(e=S(d),"none"===c&&e||L.set(d,"olddisplay",e?c:n.css(d,"display"))));for(g=0;h>g;g++)d=a[g],d.style&&(b&&"none"!==d.style.display&&""!==d.style.display||(d.style.display=b?f[g]||"":"none"));return a}n.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=xa(a,"opacity");return""===c?"1":c}}}},cssNumber:{columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=n.camelCase(b),i=a.style;return b=n.cssProps[h]||(n.cssProps[h]=Fa(i,h)),g=n.cssHooks[b]||n.cssHooks[h],void 0===c?g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b]:(f=typeof c,"string"===f&&(e=Ba.exec(c))&&(c=(e[1]+1)*e[2]+parseFloat(n.css(a,b)),f="number"),null!=c&&c===c&&("number"!==f||n.cssNumber[h]||(c+="px"),k.clearCloneStyle||""!==c||0!==b.indexOf("background")||(i[b]="inherit"),g&&"set"in g&&void 0===(c=g.set(a,c,d))||(i[b]=c)),void 0)}},css:function(a,b,c,d){var e,f,g,h=n.camelCase(b);return b=n.cssProps[h]||(n.cssProps[h]=Fa(a.style,h)),g=n.cssHooks[b]||n.cssHooks[h],g&&"get"in g&&(e=g.get(a,!0,c)),void 0===e&&(e=xa(a,b,d)),"normal"===e&&b in Da&&(e=Da[b]),""===c||c?(f=parseFloat(e),c===!0||n.isNumeric(f)?f||0:e):e}}),n.each(["height","width"],function(a,b){n.cssHooks[b]={get:function(a,c,d){return c?za.test(n.css(a,"display"))&&0===a.offsetWidth?n.swap(a,Ca,function(){return Ia(a,b,d)}):Ia(a,b,d):void 0},set:function(a,c,d){var e=d&&wa(a);return Ga(a,c,d?Ha(a,b,d,"border-box"===n.css(a,"boxSizing",!1,e),e):0)}}}),n.cssHooks.marginRight=ya(k.reliableMarginRight,function(a,b){return b?n.swap(a,{display:"inline-block"},xa,[a,"marginRight"]):void 0}),n.each({margin:"",padding:"",border:"Width"},function(a,b){n.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];4>d;d++)e[a+R[d]+b]=f[d]||f[d-2]||f[0];return e}},ua.test(a)||(n.cssHooks[a+b].set=Ga)}),n.fn.extend({css:function(a,b){return J(this,function(a,b,c){var d,e,f={},g=0;if(n.isArray(b)){for(d=wa(a),e=b.length;e>g;g++)f[b[g]]=n.css(a,b[g],!1,d);return f}return void 0!==c?n.style(a,b,c):n.css(a,b)},a,b,arguments.length>1)},show:function(){return Ja(this,!0)},hide:function(){return Ja(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){S(this)?n(this).show():n(this).hide()})}});function Ka(a,b,c,d,e){return new Ka.prototype.init(a,b,c,d,e)}n.Tween=Ka,Ka.prototype={constructor:Ka,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(n.cssNumber[c]?"":"px")},cur:function(){var a=Ka.propHooks[this.prop];return a&&a.get?a.get(this):Ka.propHooks._default.get(this)},run:function(a){var b,c=Ka.propHooks[this.prop];return this.options.duration?this.pos=b=n.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Ka.propHooks._default.set(this),this}},Ka.prototype.init.prototype=Ka.prototype,Ka.propHooks={_default:{get:function(a){var b;return null==a.elem[a.prop]||a.elem.style&&null!=a.elem.style[a.prop]?(b=n.css(a.elem,a.prop,""),b&&"auto"!==b?b:0):a.elem[a.prop]},set:function(a){n.fx.step[a.prop]?n.fx.step[a.prop](a):a.elem.style&&(null!=a.elem.style[n.cssProps[a.prop]]||n.cssHooks[a.prop])?n.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},Ka.propHooks.scrollTop=Ka.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},n.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},n.fx=Ka.prototype.init,n.fx.step={};var La,Ma,Na=/^(?:toggle|show|hide)$/,Oa=new RegExp("^(?:([+-])=|)("+Q+")([a-z%]*)$","i"),Pa=/queueHooks$/,Qa=[Va],Ra={"*":[function(a,b){var c=this.createTween(a,b),d=c.cur(),e=Oa.exec(b),f=e&&e[3]||(n.cssNumber[a]?"":"px"),g=(n.cssNumber[a]||"px"!==f&&+d)&&Oa.exec(n.css(c.elem,a)),h=1,i=20;if(g&&g[3]!==f){f=f||g[3],e=e||[],g=+d||1;do h=h||".5",g/=h,n.style(c.elem,a,g+f);while(h!==(h=c.cur()/d)&&1!==h&&--i)}return e&&(g=c.start=+g||+d||0,c.unit=f,c.end=e[1]?g+(e[1]+1)*e[2]:+e[2]),c}]};function Sa(){return setTimeout(function(){La=void 0}),La=n.now()}function Ta(a,b){var c,d=0,e={height:a};for(b=b?1:0;4>d;d+=2-b)c=R[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function Ua(a,b,c){for(var d,e=(Ra[b]||[]).concat(Ra["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function Va(a,b,c){var d,e,f,g,h,i,j,k,l=this,m={},o=a.style,p=a.nodeType&&S(a),q=L.get(a,"fxshow");c.queue||(h=n._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,l.always(function(){l.always(function(){h.unqueued--,n.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[o.overflow,o.overflowX,o.overflowY],j=n.css(a,"display"),k="none"===j?L.get(a,"olddisplay")||ta(a.nodeName):j,"inline"===k&&"none"===n.css(a,"float")&&(o.display="inline-block")),c.overflow&&(o.overflow="hidden",l.always(function(){o.overflow=c.overflow[0],o.overflowX=c.overflow[1],o.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],Na.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(p?"hide":"show")){if("show"!==e||!q||void 0===q[d])continue;p=!0}m[d]=q&&q[d]||n.style(a,d)}else j=void 0;if(n.isEmptyObject(m))"inline"===("none"===j?ta(a.nodeName):j)&&(o.display=j);else{q?"hidden"in q&&(p=q.hidden):q=L.access(a,"fxshow",{}),f&&(q.hidden=!p),p?n(a).show():l.done(function(){n(a).hide()}),l.done(function(){var b;L.remove(a,"fxshow");for(b in m)n.style(a,b,m[b])});for(d in m)g=Ua(p?q[d]:0,d,l),d in q||(q[d]=g.start,p&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function Wa(a,b){var c,d,e,f,g;for(c in a)if(d=n.camelCase(c),e=b[d],f=a[c],n.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=n.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function Xa(a,b,c){var d,e,f=0,g=Qa.length,h=n.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=La||Sa(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:n.extend({},b),opts:n.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:La||Sa(),duration:c.duration,tweens:[],createTween:function(b,c){var d=n.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;d>c;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;for(Wa(k,j.opts.specialEasing);g>f;f++)if(d=Qa[f].call(j,a,k,j.opts))return d;return n.map(k,Ua,j),n.isFunction(j.opts.start)&&j.opts.start.call(a,j),n.fx.timer(n.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}n.Animation=n.extend(Xa,{tweener:function(a,b){n.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");for(var c,d=0,e=a.length;e>d;d++)c=a[d],Ra[c]=Ra[c]||[],Ra[c].unshift(b)},prefilter:function(a,b){b?Qa.unshift(a):Qa.push(a)}}),n.speed=function(a,b,c){var d=a&&"object"==typeof a?n.extend({},a):{complete:c||!c&&b||n.isFunction(a)&&a,duration:a,easing:c&&b||b&&!n.isFunction(b)&&b};return d.duration=n.fx.off?0:"number"==typeof d.duration?d.duration:d.duration in n.fx.speeds?n.fx.speeds[d.duration]:n.fx.speeds._default,(null==d.queue||d.queue===!0)&&(d.queue="fx"),d.old=d.complete,d.complete=function(){n.isFunction(d.old)&&d.old.call(this),d.queue&&n.dequeue(this,d.queue)},d},n.fn.extend({fadeTo:function(a,b,c,d){return this.filter(S).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=n.isEmptyObject(a),f=n.speed(b,c,d),g=function(){var b=Xa(this,n.extend({},a),f);(e||L.get(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=n.timers,g=L.get(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&Pa.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));(b||!c)&&n.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=L.get(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=n.timers,g=d?d.length:0;for(c.finish=!0,n.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;g>b;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),n.each(["toggle","show","hide"],function(a,b){var c=n.fn[b];n.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(Ta(b,!0),a,d,e)}}),n.each({slideDown:Ta("show"),slideUp:Ta("hide"),slideToggle:Ta("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){n.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),n.timers=[],n.fx.tick=function(){var a,b=0,c=n.timers;for(La=n.now();b<c.length;b++)a=c[b],a()||c[b]!==a||c.splice(b--,1);c.length||n.fx.stop(),La=void 0},n.fx.timer=function(a){n.timers.push(a),a()?n.fx.start():n.timers.pop()},n.fx.interval=13,n.fx.start=function(){Ma||(Ma=setInterval(n.fx.tick,n.fx.interval))},n.fx.stop=function(){clearInterval(Ma),Ma=null},n.fx.speeds={slow:600,fast:200,_default:400},n.fn.delay=function(a,b){return a=n.fx?n.fx.speeds[a]||a:a,b=b||"fx",this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},function(){var a=l.createElement("input"),b=l.createElement("select"),c=b.appendChild(l.createElement("option"));a.type="checkbox",k.checkOn=""!==a.value,k.optSelected=c.selected,b.disabled=!0,k.optDisabled=!c.disabled,a=l.createElement("input"),a.value="t",a.type="radio",k.radioValue="t"===a.value}();var Ya,Za,$a=n.expr.attrHandle;n.fn.extend({attr:function(a,b){return J(this,n.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){n.removeAttr(this,a)})}}),n.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(a&&3!==f&&8!==f&&2!==f)return typeof a.getAttribute===U?n.prop(a,b,c):(1===f&&n.isXMLDoc(a)||(b=b.toLowerCase(),d=n.attrHooks[b]||(n.expr.match.bool.test(b)?Za:Ya)), 4void 0===c?d&&"get"in d&&null!==(e=d.get(a,b))?e:(e=n.find.attr(a,b),null==e?void 0:e):null!==c?d&&"set"in d&&void 0!==(e=d.set(a,c,b))?e:(a.setAttribute(b,c+""),c):void n.removeAttr(a,b))},removeAttr:function(a,b){var c,d,e=0,f=b&&b.match(E);if(f&&1===a.nodeType)while(c=f[e++])d=n.propFix[c]||c,n.expr.match.bool.test(c)&&(a[d]=!1),a.removeAttribute(c)},attrHooks:{type:{set:function(a,b){if(!k.radioValue&&"radio"===b&&n.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}}}),Za={set:function(a,b,c){return b===!1?n.removeAttr(a,c):a.setAttribute(c,c),c}},n.each(n.expr.match.bool.source.match(/\w+/g),function(a,b){var c=$a[b]||n.find.attr;$a[b]=function(a,b,d){var e,f;return d||(f=$a[b],$a[b]=e,e=null!=c(a,b,d)?b.toLowerCase():null,$a[b]=f),e}});var _a=/^(?:input|select|textarea|button)$/i;n.fn.extend({prop:function(a,b){return J(this,n.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[n.propFix[a]||a]})}}),n.extend({propFix:{"for":"htmlFor","class":"className"},prop:function(a,b,c){var d,e,f,g=a.nodeType;if(a&&3!==g&&8!==g&&2!==g)return f=1!==g||!n.isXMLDoc(a),f&&(b=n.propFix[b]||b,e=n.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){return a.hasAttribute("tabindex")||_a.test(a.nodeName)||a.href?a.tabIndex:-1}}}}),k.optSelected||(n.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null}}),n.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){n.propFix[this.toLowerCase()]=this});var ab=/[\t\r\n\f]/g;n.fn.extend({addClass:function(a){var b,c,d,e,f,g,h="string"==typeof a&&a,i=0,j=this.length;if(n.isFunction(a))return this.each(function(b){n(this).addClass(a.call(this,b,this.className))});if(h)for(b=(a||"").match(E)||[];j>i;i++)if(c=this[i],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(ab," "):" ")){f=0;while(e=b[f++])d.indexOf(" "+e+" ")<0&&(d+=e+" ");g=n.trim(d),c.className!==g&&(c.className=g)}return this},removeClass:function(a){var b,c,d,e,f,g,h=0===arguments.length||"string"==typeof a&&a,i=0,j=this.length;if(n.isFunction(a))return this.each(function(b){n(this).removeClass(a.call(this,b,this.className))});if(h)for(b=(a||"").match(E)||[];j>i;i++)if(c=this[i],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(ab," "):"")){f=0;while(e=b[f++])while(d.indexOf(" "+e+" ")>=0)d=d.replace(" "+e+" "," ");g=a?n.trim(d):"",c.className!==g&&(c.className=g)}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):this.each(n.isFunction(a)?function(c){n(this).toggleClass(a.call(this,c,this.className,b),b)}:function(){if("string"===c){var b,d=0,e=n(this),f=a.match(E)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else(c===U||"boolean"===c)&&(this.className&&L.set(this,"__className__",this.className),this.className=this.className||a===!1?"":L.get(this,"__className__")||"")})},hasClass:function(a){for(var b=" "+a+" ",c=0,d=this.length;d>c;c++)if(1===this[c].nodeType&&(" "+this[c].className+" ").replace(ab," ").indexOf(b)>=0)return!0;return!1}});var bb=/\r/g;n.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=n.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,n(this).val()):a,null==e?e="":"number"==typeof e?e+="":n.isArray(e)&&(e=n.map(e,function(a){return null==a?"":a+""})),b=n.valHooks[this.type]||n.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=n.valHooks[e.type]||n.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(bb,""):null==c?"":c)}}}),n.extend({valHooks:{option:{get:function(a){var b=n.find.attr(a,"value");return null!=b?b:n.trim(n.text(a))}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type||0>e,g=f?null:[],h=f?e+1:d.length,i=0>e?h:f?e:0;h>i;i++)if(c=d[i],!(!c.selected&&i!==e||(k.optDisabled?c.disabled:null!==c.getAttribute("disabled"))||c.parentNode.disabled&&n.nodeName(c.parentNode,"optgroup"))){if(b=n(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=n.makeArray(b),g=e.length;while(g--)d=e[g],(d.selected=n.inArray(d.value,f)>=0)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),n.each(["radio","checkbox"],function(){n.valHooks[this]={set:function(a,b){return n.isArray(b)?a.checked=n.inArray(n(a).val(),b)>=0:void 0}},k.checkOn||(n.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})}),n.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){n.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),n.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}});var cb=n.now(),db=/\?/;n.parseJSON=function(a){return JSON.parse(a+"")},n.parseXML=function(a){var b,c;if(!a||"string"!=typeof a)return null;try{c=new DOMParser,b=c.parseFromString(a,"text/xml")}catch(d){b=void 0}return(!b||b.getElementsByTagName("parsererror").length)&&n.error("Invalid XML: "+a),b};var eb=/#.*$/,fb=/([?&])_=[^&]*/,gb=/^(.*?):[ \t]*([^\r\n]*)$/gm,hb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,ib=/^(?:GET|HEAD)$/,jb=/^\/\//,kb=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,lb={},mb={},nb="*/".concat("*"),ob=a.location.href,pb=kb.exec(ob.toLowerCase())||[];function qb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(E)||[];if(n.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function rb(a,b,c,d){var e={},f=a===mb;function g(h){var i;return e[h]=!0,n.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function sb(a,b){var c,d,e=n.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&n.extend(!0,a,d),a}function tb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}return f?(f!==i[0]&&i.unshift(f),c[f]):void 0}function ub(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}n.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:ob,type:"GET",isLocal:hb.test(pb[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":nb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":n.parseJSON,"text xml":n.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?sb(sb(a,n.ajaxSettings),b):sb(n.ajaxSettings,a)},ajaxPrefilter:qb(lb),ajaxTransport:qb(mb),ajax:function(a,b){"object"==typeof a&&(b=a,a=void 0),b=b||{};var c,d,e,f,g,h,i,j,k=n.ajaxSetup({},b),l=k.context||k,m=k.context&&(l.nodeType||l.jquery)?n(l):n.event,o=n.Deferred(),p=n.Callbacks("once memory"),q=k.statusCode||{},r={},s={},t=0,u="canceled",v={readyState:0,getResponseHeader:function(a){var b;if(2===t){if(!f){f={};while(b=gb.exec(e))f[b[1].toLowerCase()]=b[2]}b=f[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return 2===t?e:null},setRequestHeader:function(a,b){var c=a.toLowerCase();return t||(a=s[c]=s[c]||a,r[a]=b),this},overrideMimeType:function(a){return t||(k.mimeType=a),this},statusCode:function(a){var b;if(a)if(2>t)for(b in a)q[b]=[q[b],a[b]];else v.always(a[v.status]);return this},abort:function(a){var b=a||u;return c&&c.abort(b),x(0,b),this}};if(o.promise(v).complete=p.add,v.success=v.done,v.error=v.fail,k.url=((a||k.url||ob)+"").replace(eb,"").replace(jb,pb[1]+"//"),k.type=b.method||b.type||k.method||k.type,k.dataTypes=n.trim(k.dataType||"*").toLowerCase().match(E)||[""],null==k.crossDomain&&(h=kb.exec(k.url.toLowerCase()),k.crossDomain=!(!h||h[1]===pb[1]&&h[2]===pb[2]&&(h[3]||("http:"===h[1]?"80":"443"))===(pb[3]||("http:"===pb[1]?"80":"443")))),k.data&&k.processData&&"string"!=typeof k.data&&(k.data=n.param(k.data,k.traditional)),rb(lb,k,b,v),2===t)return v;i=n.event&&k.global,i&&0===n.active++&&n.event.trigger("ajaxStart"),k.type=k.type.toUpperCase(),k.hasContent=!ib.test(k.type),d=k.url,k.hasContent||(k.data&&(d=k.url+=(db.test(d)?"&":"?")+k.data,delete k.data),k.cache===!1&&(k.url=fb.test(d)?d.replace(fb,"$1_="+cb++):d+(db.test(d)?"&":"?")+"_="+cb++)),k.ifModified&&(n.lastModified[d]&&v.setRequestHeader("If-Modified-Since",n.lastModified[d]),n.etag[d]&&v.setRequestHeader("If-None-Match",n.etag[d])),(k.data&&k.hasContent&&k.contentType!==!1||b.contentType)&&v.setRequestHeader("Content-Type",k.contentType),v.setRequestHeader("Accept",k.dataTypes[0]&&k.accepts[k.dataTypes[0]]?k.accepts[k.dataTypes[0]]+("*"!==k.dataTypes[0]?", "+nb+"; q=0.01":""):k.accepts["*"]);for(j in k.headers)v.setRequestHeader(j,k.headers[j]);if(k.beforeSend&&(k.beforeSend.call(l,v,k)===!1||2===t))return v.abort();u="abort";for(j in{success:1,error:1,complete:1})v[j](k[j]);if(c=rb(mb,k,b,v)){v.readyState=1,i&&m.trigger("ajaxSend",[v,k]),k.async&&k.timeout>0&&(g=setTimeout(function(){v.abort("timeout")},k.timeout));try{t=1,c.send(r,x)}catch(w){if(!(2>t))throw w;x(-1,w)}}else x(-1,"No Transport");function x(a,b,f,h){var j,r,s,u,w,x=b;2!==t&&(t=2,g&&clearTimeout(g),c=void 0,e=h||"",v.readyState=a>0?4:0,j=a>=200&&300>a||304===a,f&&(u=tb(k,v,f)),u=ub(k,u,v,j),j?(k.ifModified&&(w=v.getResponseHeader("Last-Modified"),w&&(n.lastModified[d]=w),w=v.getResponseHeader("etag"),w&&(n.etag[d]=w)),204===a||"HEAD"===k.type?x="nocontent":304===a?x="notmodified":(x=u.state,r=u.data,s=u.error,j=!s)):(s=x,(a||!x)&&(x="error",0>a&&(a=0))),v.status=a,v.statusText=(b||x)+"",j?o.resolveWith(l,[r,x,v]):o.rejectWith(l,[v,x,s]),v.statusCode(q),q=void 0,i&&m.trigger(j?"ajaxSuccess":"ajaxError",[v,k,j?r:s]),p.fireWith(l,[v,x]),i&&(m.trigger("ajaxComplete",[v,k]),--n.active||n.event.trigger("ajaxStop")))}return v},getJSON:function(a,b,c){return n.get(a,b,c,"json")},getScript:function(a,b){return n.get(a,void 0,b,"script")}}),n.each(["get","post"],function(a,b){n[b]=function(a,c,d,e){return n.isFunction(c)&&(e=e||d,d=c,c=void 0),n.ajax({url:a,type:b,dataType:e,data:c,success:d})}}),n._evalUrl=function(a){return n.ajax({url:a,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})},n.fn.extend({wrapAll:function(a){var b;return n.isFunction(a)?this.each(function(b){n(this).wrapAll(a.call(this,b))}):(this[0]&&(b=n(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this)},wrapInner:function(a){return this.each(n.isFunction(a)?function(b){n(this).wrapInner(a.call(this,b))}:function(){var b=n(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=n.isFunction(a);return this.each(function(c){n(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){n.nodeName(this,"body")||n(this).replaceWith(this.childNodes)}).end()}}),n.expr.filters.hidden=function(a){return a.offsetWidth<=0&&a.offsetHeight<=0},n.expr.filters.visible=function(a){return!n.expr.filters.hidden(a)};var vb=/%20/g,wb=/\[\]$/,xb=/\r?\n/g,yb=/^(?:submit|button|image|reset|file)$/i,zb=/^(?:input|select|textarea|keygen)/i;function Ab(a,b,c,d){var e;if(n.isArray(b))n.each(b,function(b,e){c||wb.test(a)?d(a,e):Ab(a+"["+("object"==typeof e?b:"")+"]",e,c,d)});else if(c||"object"!==n.type(b))d(a,b);else for(e in b)Ab(a+"["+e+"]",b[e],c,d)}n.param=function(a,b){var c,d=[],e=function(a,b){b=n.isFunction(b)?b():null==b?"":b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};if(void 0===b&&(b=n.ajaxSettings&&n.ajaxSettings.traditional),n.isArray(a)||a.jquery&&!n.isPlainObject(a))n.each(a,function(){e(this.name,this.value)});else for(c in a)Ab(c,a[c],b,e);return d.join("&").replace(vb,"+")},n.fn.extend({serialize:function(){return n.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=n.prop(this,"elements");return a?n.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!n(this).is(":disabled")&&zb.test(this.nodeName)&&!yb.test(a)&&(this.checked||!T.test(a))}).map(function(a,b){var c=n(this).val();return null==c?null:n.isArray(c)?n.map(c,function(a){return{name:b.name,value:a.replace(xb,"\r\n")}}):{name:b.name,value:c.replace(xb,"\r\n")}}).get()}}),n.ajaxSettings.xhr=function(){try{return new XMLHttpRequest}catch(a){}};var Bb=0,Cb={},Db={0:200,1223:204},Eb=n.ajaxSettings.xhr();a.attachEvent&&a.attachEvent("onunload",function(){for(var a in Cb)Cb[a]()}),k.cors=!!Eb&&"withCredentials"in Eb,k.ajax=Eb=!!Eb,n.ajaxTransport(function(a){var b;return k.cors||Eb&&!a.crossDomain?{send:function(c,d){var e,f=a.xhr(),g=++Bb;if(f.open(a.type,a.url,a.async,a.username,a.password),a.xhrFields)for(e in a.xhrFields)f[e]=a.xhrFields[e];a.mimeType&&f.overrideMimeType&&f.overrideMimeType(a.mimeType),a.crossDomain||c["X-Requested-With"]||(c["X-Requested-With"]="XMLHttpRequest");for(e in c)f.setRequestHeader(e,c[e]);b=function(a){return function(){b&&(delete Cb[g],b=f.onload=f.onerror=null,"abort"===a?f.abort():"error"===a?d(f.status,f.statusText):d(Db[f.status]||f.status,f.statusText,"string"==typeof f.responseText?{text:f.responseText}:void 0,f.getAllResponseHeaders()))}},f.onload=b(),f.onerror=b("error"),b=Cb[g]=b("abort");try{f.send(a.hasContent&&a.data||null)}catch(h){if(b)throw h}},abort:function(){b&&b()}}:void 0}),n.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(a){return n.globalEval(a),a}}}),n.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),n.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(d,e){b=n("<script>").prop({async:!0,charset:a.scriptCharset,src:a.url}).on("load error",c=function(a){b.remove(),c=null,a&&e("error"===a.type?404:200,a.type)}),l.head.appendChild(b[0])},abort:function(){c&&c()}}}});var Fb=[],Gb=/(=)\?(?=&|$)|\?\?/;n.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=Fb.pop()||n.expando+"_"+cb++;return this[a]=!0,a}}),n.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(Gb.test(b.url)?"url":"string"==typeof b.data&&!(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&Gb.test(b.data)&&"data");return h||"jsonp"===b.dataTypes[0]?(e=b.jsonpCallback=n.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(Gb,"$1"+e):b.jsonp!==!1&&(b.url+=(db.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||n.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,Fb.push(e)),g&&n.isFunction(f)&&f(g[0]),g=f=void 0}),"script"):void 0}),n.parseHTML=function(a,b,c){if(!a||"string"!=typeof a)return null;"boolean"==typeof b&&(c=b,b=!1),b=b||l;var d=v.exec(a),e=!c&&[];return d?[b.createElement(d[1])]:(d=n.buildFragment([a],b,e),e&&e.length&&n(e).remove(),n.merge([],d.childNodes))};var Hb=n.fn.load;n.fn.load=function(a,b,c){if("string"!=typeof a&&Hb)return Hb.apply(this,arguments);var d,e,f,g=this,h=a.indexOf(" ");return h>=0&&(d=n.trim(a.slice(h)),a=a.slice(0,h)),n.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(e="POST"),g.length>0&&n.ajax({url:a,type:e,dataType:"html",data:b}).done(function(a){f=arguments,g.html(d?n("<div>").append(n.parseHTML(a)).find(d):a)}).complete(c&&function(a,b){g.each(c,f||[a.responseText,b,a])}),this},n.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){n.fn[b]=function(a){return this.on(b,a)}}),n.expr.filters.animated=function(a){return n.grep(n.timers,function(b){return a===b.elem}).length};var Ib=a.document.documentElement;function Jb(a){return n.isWindow(a)?a:9===a.nodeType&&a.defaultView}n.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=n.css(a,"position"),l=n(a),m={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=n.css(a,"top"),i=n.css(a,"left"),j=("absolute"===k||"fixed"===k)&&(f+i).indexOf("auto")>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),n.isFunction(b)&&(b=b.call(a,c,h)),null!=b.top&&(m.top=b.top-h.top+g),null!=b.left&&(m.left=b.left-h.left+e),"using"in b?b.using.call(a,m):l.css(m)}},n.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){n.offset.setOffset(this,a,b)});var b,c,d=this[0],e={top:0,left:0},f=d&&d.ownerDocument;if(f)return b=f.documentElement,n.contains(b,d)?(typeof d.getBoundingClientRect!==U&&(e=d.getBoundingClientRect()),c=Jb(f),{top:e.top+c.pageYOffset-b.clientTop,left:e.left+c.pageXOffset-b.clientLeft}):e},position:function(){if(this[0]){var a,b,c=this[0],d={top:0,left:0};return"fixed"===n.css(c,"position")?b=c.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),n.nodeName(a[0],"html")||(d=a.offset()),d.top+=n.css(a[0],"borderTopWidth",!0),d.left+=n.css(a[0],"borderLeftWidth",!0)),{top:b.top-d.top-n.css(c,"marginTop",!0),left:b.left-d.left-n.css(c,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||Ib;while(a&&!n.nodeName(a,"html")&&"static"===n.css(a,"position"))a=a.offsetParent;return a||Ib})}}),n.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(b,c){var d="pageYOffset"===c;n.fn[b]=function(e){return J(this,function(b,e,f){var g=Jb(b);return void 0===f?g?g[c]:b[e]:void(g?g.scrollTo(d?a.pageXOffset:f,d?f:a.pageYOffset):b[e]=f)},b,e,arguments.length,null)}}),n.each(["top","left"],function(a,b){n.cssHooks[b]=ya(k.pixelPosition,function(a,c){return c?(c=xa(a,b),va.test(c)?n(a).position()[b]+"px":c):void 0})}),n.each({Height:"height",Width:"width"},function(a,b){n.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){n.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return J(this,function(b,c,d){var e;return n.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?n.css(b,c,g):n.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),n.fn.size=function(){return this.length},n.fn.andSelf=n.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return n});var Kb=a.jQuery,Lb=a.$;return n.noConflict=function(b){return a.$===n&&(a.$=Lb),b&&a.jQuery===n&&(a.jQuery=Kb),n},typeof b===U&&(a.jQuery=a.$=n),n});
5//# sourceMappingURL=jquery.min.map 6/*! 7 * jQuery UI Datepicker 1.11.4 8 * http://jqueryui.com 9 * 10 * Copyright jQuery Foundation and other contributors 11 * Released under the MIT license. 12 * http://jquery.org/license 13 * 14 * http://api.jqueryui.com/datepicker/ 15 */ 16(function( factory ) { 17 if ( typeof define === "function" && define.amd ) { 18 19 // AMD. Register as an anonymous module. 20 define([ 21 "jquery", 22 "./core" 23 ], factory ); 24 } else { 25 26 // Browser globals 27 factory( jQuery ); 28 } 29}(function( $ ) {
vendor: 77,476 bytes, lines 29-2086
29 30 31$.extend($.ui, { datepicker: { version: "1.11.4" } }); 32 33var datepicker_instActive; 34 35function datepicker_getZindex( elem ) { 36 var position, value; 37 while ( elem.length && elem[ 0 ] !== document ) { 38 // Ignore z-index if position is set to a value where z-index is ignored by the browser 39 // This makes behavior of this function consistent across browsers 40 // WebKit always returns auto if the element is positioned 41 position = elem.css( "position" ); 42 if ( position === "absolute" || position === "relative" || position === "fixed" ) { 43 // IE returns 0 when zIndex is not specified 44 // other browsers return a string 45 // we ignore the case of nested elements with an explicit value of 0 46 // <div style="z-index: -10;"><div style="z-index: 0;"></div></div> 47 value = parseInt( elem.css( "zIndex" ), 10 ); 48 if ( !isNaN( value ) && value !== 0 ) { 49 return value; 50 } 51 } 52 elem = elem.parent(); 53 } 54 55 return 0; 56} 57/* Date picker manager. 58 Use the singleton instance of this class, $.datepicker, to interact with the date picker. 59 Settings for (groups of) date pickers are maintained in an instance object, 60 allowing multiple different settings on the same page. */ 61 62function Datepicker() { 63 this._curInst = null; // The current instance in use 64 this._keyEvent = false; // If the last event was a key event 65 this._disabledInputs = []; // List of date picker inputs that have been disabled 66 this._datepickerShowing = false; // True if the popup picker is showing , false if not 67 this._inDialog = false; // True if showing within a "dialog", false if not 68 this._mainDivId = "ui-datepicker-div"; // The ID of the main datepicker division 69 this._inlineClass = "ui-datepicker-inline"; // The name of the inline marker class 70 this._appendClass = "ui-datepicker-append"; // The name of the append marker class 71 this._triggerClass = "ui-datepicker-trigger"; // The name of the trigger marker class 72 this._dialogClass = "ui-datepicker-dialog"; // The name of the dialog marker class 73 this._disableClass = "ui-datepicker-disabled"; // The name of the disabled covering marker class 74 this._unselectableClass = "ui-datepicker-unselectable"; // The name of the unselectable cell marker class 75 this._currentClass = "ui-datepicker-current-day"; // The name of the current day marker class 76 this._dayOverClass = "ui-datepicker-days-cell-over"; // The name of the day hover marker class 77 this.regional = []; // Available regional settings, indexed by language code 78 this.regional[""] = { // Default regional settings 79 closeText: "Done", // Display text for close link 80 prevText: "Prev", // Display text for previous month link 81 nextText: "Next", // Display text for next month link 82 currentText: "Today", // Display text for current month link 83 monthNames: ["January","February","March","April","May","June", 84 "July","August","September","October","November","December"], // Names of months for drop-down and formatting 85 monthNamesShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], // For formatting 86 dayNames: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], // For formatting 87 dayNamesShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], // For formatting 88 dayNamesMin: ["Su","Mo","Tu","We","Th","Fr","Sa"], // Column headings for days starting at Sunday 89 weekHeader: "Wk", // Column header for week of the year 90 dateFormat: "mm/dd/yy", // See format options on parseDate 91 firstDay: 0, // The first day of the week, Sun = 0, Mon = 1, ... 92 isRTL: false, // True if right-to-left language, false if left-to-right 93 showMonthAfterYear: false, // True if the year select precedes month, false for month then year 94 yearSuffix: "" // Additional text to append to the year in the month headers 95 }; 96 this._defaults = { // Global defaults for all the date picker instances 97 showOn: "focus", // "focus" for popup on focus, 98 // "button" for trigger button, or "both" for either 99 showAnim: "fadeIn", // Name of jQuery animation for popup 100 showOptions: {}, // Options for enhanced animations 101 defaultDate: null, // Used when field is blank: actual date, 102 // +/-number for offset from today, null for today 103 appendText: "", // Display text following the input box, e.g. showing the format 104 buttonText: "...", // Text for trigger button 105 buttonImage: "", // URL for trigger button image 106 buttonImageOnly: false, // True if the image appears alone, false if it appears on a button 107 hideIfNoPrevNext: false, // True to hide next/previous month links 108 // if not applicable, false to just disable them 109 navigationAsDateFormat: false, // True if date formatting applied to prev/today/next links 110 gotoCurrent: false, // True if today link goes back to current selection instead 111 changeMonth: false, // True if month can be selected directly, false if only prev/next 112 changeYear: false, // True if year can be selected directly, false if only prev/next 113 yearRange: "c-10:c+10", // Range of years to display in drop-down, 114 // either relative to today's year (-nn:+nn), relative to currently displayed year 115 // (c-nn:c+nn), absolute (nnnn:nnnn), or a combination of the above (nnnn:-n) 116 showOtherMonths: false, // True to show dates in other months, false to leave blank 117 selectOtherMonths: false, // True to allow selection of dates in other months, false for unselectable 118 showWeek: false, // True to show week of the year, false to not show it 119 calculateWeek: this.iso8601Week, // How to calculate the week of the year, 120 // takes a Date and returns the number of the week for it 121 shortYearCutoff: "+10", // Short year values < this are in the current century, 122 // > this are in the previous century, 123 // string value starting with "+" for current year + value 124 minDate: null, // The earliest selectable date, or null for no limit 125 maxDate: null, // The latest selectable date, or null for no limit 126 duration: "fast", // Duration of display/closure 127 beforeShowDay: null, // Function that takes a date and returns an array with 128 // [0] = true if selectable, false if not, [1] = custom CSS class name(s) or "", 129 // [2] = cell title (optional), e.g. $.datepicker.noWeekends 130 beforeShow: null, // Function that takes an input field and 131 // returns a set of custom settings for the date picker 132 onSelect: null, // Define a callback function when a date is selected 133 onChangeMonthYear: null, // Define a callback function when the month or year is changed 134 onClose: null, // Define a callback function when the datepicker is closed 135 numberOfMonths: 1, // Number of months to show at a time 136 showCurrentAtPos: 0, // The position in multipe months at which to show the current month (starting at 0) 137 stepMonths: 1, // Number of months to step back/forward 138 stepBigMonths: 12, // Number of months to step back/forward for the big links 139 altField: "", // Selector for an alternate field to store selected dates into 140 altFormat: "", // The date format to use for the alternate field 141 constrainInput: true, // The input is constrained by the current date format 142 showButtonPanel: false, // True to show button panel, false to not show it 143 autoSize: false, // True to size the input for the date format, false to leave as is 144 disabled: false // The initial disabled state 145 }; 146 $.extend(this._defaults, this.regional[""]); 147 this.regional.en = $.extend( true, {}, this.regional[ "" ]); 148 this.regional[ "en-US" ] = $.extend( true, {}, this.regional.en ); 149 this.dpDiv = datepicker_bindHover($("<div id='" + this._mainDivId + "' class='ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>")); 150} 151 152$.extend(Datepicker.prototype, { 153 /* Class name added to elements to indicate already configured with a date picker. */ 154 markerClassName: "hasDatepicker", 155 156 //Keep track of the maximum number of rows displayed (see #7043) 157 maxRows: 4, 158 159 // TODO rename to "widget" when switching to widget factory 160 _widgetDatepicker: function() { 161 return this.dpDiv; 162 }, 163 164 /* Override the default settings for all instances of the date picker. 165 * @param settings object - the new settings to use as defaults (anonymous object) 166 * @return the manager object 167 */ 168 setDefaults: function(settings) { 169 datepicker_extendRemove(this._defaults, settings || {}); 170 return this; 171 }, 172 173 /* Attach the date picker to a jQuery selection. 174 * @param target element - the target input field or division or span 175 * @param settings object - the new settings to use for this date picker instance (anonymous) 176 */ 177 _attachDatepicker: function(target, settings) { 178 var nodeName, inline, inst; 179 nodeName = target.nodeName.toLowerCase(); 180 inline = (nodeName === "div" || nodeName === "span"); 181 if (!target.id) { 182 this.uuid += 1; 183 target.id = "dp" + this.uuid; 184 } 185 inst = this._newInst($(target), inline); 186 inst.settings = $.extend({}, settings || {}); 187 if (nodeName === "input") { 188 this._connectDatepicker(target, inst); 189 } else if (inline) { 190 this._inlineDatepicker(target, inst); 191 } 192 }, 193 194 /* Create a new instance object. */ 195 _newInst: function(target, inline) { 196 var id = target[0].id.replace(/([^A-Za-z0-9_\-])/g, "\\\\$1"); // escape jQuery meta chars 197 return {id: id, input: target, // associated target 198 selectedDay: 0, selectedMonth: 0, selectedYear: 0, // current selection 199 drawMonth: 0, drawYear: 0, // month being drawn 200 inline: inline, // is datepicker inline or not 201 dpDiv: (!inline ? this.dpDiv : // presentation div 202 datepicker_bindHover($("<div class='" + this._inlineClass + " ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>")))}; 203 }, 204 205 /* Attach the date picker to an input field. */ 206 _connectDatepicker: function(target, inst) { 207 var input = $(target); 208 inst.append = $([]); 209 inst.trigger = $([]); 210 if (input.hasClass(this.markerClassName)) { 211 return; 212 } 213 this._attachments(input, inst); 214 input.addClass(this.markerClassName).keydown(this._doKeyDown). 215 keypress(this._doKeyPress).keyup(this._doKeyUp); 216 this._autoSize(inst); 217 $.data(target, "datepicker", inst); 218 //If disabled option is true, disable the datepicker once it has been attached to the input (see ticket #5665) 219 if( inst.settings.disabled ) { 220 this._disableDatepicker( target ); 221 } 222 }, 223 224 /* Make attachments based on settings. */ 225 _attachments: function(input, inst) { 226 var showOn, buttonText, buttonImage, 227 appendText = this._get(inst, "appendText"), 228 isRTL = this._get(inst, "isRTL"); 229 230 if (inst.append) { 231 inst.append.remove(); 232 } 233 if (appendText) { 234 inst.append = $("<span class='" + this._appendClass + "'>" + appendText + "</span>"); 235 input[isRTL ? "before" : "after"](inst.append); 236 } 237 238 input.unbind("focus", this._showDatepicker); 239 240 if (inst.trigger) { 241 inst.trigger.remove(); 242 } 243 244 showOn = this._get(inst, "showOn"); 245 if (showOn === "focus" || showOn === "both") { // pop-up date picker when in the marked field 246 input.focus(this._showDatepicker); 247 } 248 if (showOn === "button" || showOn === "both") { // pop-up date picker when button clicked 249 buttonText = this._get(inst, "buttonText"); 250 buttonImage = this._get(inst, "buttonImage"); 251 inst.trigger = $(this._get(inst, "buttonImageOnly") ? 252 $("<img/>").addClass(this._triggerClass). 253 attr({ src: buttonImage, alt: buttonText, title: buttonText }) : 254 $("<button type='button'></button>").addClass(this._triggerClass). 255 html(!buttonImage ? buttonText : $("<img/>").attr( 256 { src:buttonImage, alt:buttonText, title:buttonText }))); 257 input[isRTL ? "before" : "after"](inst.trigger); 258 inst.trigger.click(function() { 259 if ($.datepicker._datepickerShowing && $.datepicker._lastInput === input[0]) { 260 $.datepicker._hideDatepicker(); 261 } else if ($.datepicker._datepickerShowing && $.datepicker._lastInput !== input[0]) { 262 $.datepicker._hideDatepicker(); 263 $.datepicker._showDatepicker(input[0]); 264 } else { 265 $.datepicker._showDatepicker(input[0]); 266 } 267 return false; 268 }); 269 } 270 }, 271 272 /* Apply the maximum length for the date format. */ 273 _autoSize: function(inst) { 274 if (this._get(inst, "autoSize") && !inst.inline) { 275 var findMax, max, maxI, i, 276 date = new Date(2009, 12 - 1, 20), // Ensure double digits 277 dateFormat = this._get(inst, "dateFormat"); 278 279 if (dateFormat.match(/[DM]/)) { 280 findMax = function(names) { 281 max = 0; 282 maxI = 0; 283 for (i = 0; i < names.length; i++) { 284 if (names[i].length > max) { 285 max = names[i].length; 286 maxI = i; 287 } 288 } 289 return maxI; 290 }; 291 date.setMonth(findMax(this._get(inst, (dateFormat.match(/MM/) ? 292 "monthNames" : "monthNamesShort")))); 293 date.setDate(findMax(this._get(inst, (dateFormat.match(/DD/) ? 294 "dayNames" : "dayNamesShort"))) + 20 - date.getDay()); 295 } 296 inst.input.attr("size", this._formatDate(inst, date).length); 297 } 298 }, 299 300 /* Attach an inline date picker to a div. */ 301 _inlineDatepicker: function(target, inst) { 302 var divSpan = $(target); 303 if (divSpan.hasClass(this.markerClassName)) { 304 return; 305 } 306 divSpan.addClass(this.markerClassName).append(inst.dpDiv); 307 $.data(target, "datepicker", inst); 308 this._setDate(inst, this._getDefaultDate(inst), true); 309 this._updateDatepicker(inst); 310 this._updateAlternate(inst); 311 //If disabled option is true, disable the datepicker before showing it (see ticket #5665) 312 if( inst.settings.disabled ) { 313 this._disableDatepicker( target ); 314 } 315 // Set display:block in place of inst.dpDiv.show() which won't work on disconnected elements 316 // http://bugs.jqueryui.com/ticket/7552 - A Datepicker created on a detached div has zero height 317 inst.dpDiv.css( "display", "block" ); 318 }, 319 320 /* Pop-up the date picker in a "dialog" box. 321 * @param input element - ignored 322 * @param date string or Date - the initial date to display 323 * @param onSelect function - the function to call when a date is selected 324 * @param settings object - update the dialog date picker instance's settings (anonymous object) 325 * @param pos int[2] - coordinates for the dialog's position within the screen or 326 * event - with x/y coordinates or 327 * leave empty for default (screen centre) 328 * @return the manager object 329 */ 330 _dialogDatepicker: function(input, date, onSelect, settings, pos) { 331 var id, browserWidth, browserHeight, scrollX, scrollY, 332 inst = this._dialogInst; // internal instance 333 334 if (!inst) { 335 this.uuid += 1; 336 id = "dp" + this.uuid; 337 this._dialogInput = $("<input type='text' id='" + id + 338 "' style='position: absolute; top: -100px; width: 0px;'/>"); 339 this._dialogInput.keydown(this._doKeyDown); 340 $("body").append(this._dialogInput); 341 inst = this._dialogInst = this._newInst(this._dialogInput, false); 342 inst.settings = {}; 343 $.data(this._dialogInput[0], "datepicker", inst); 344 } 345 datepicker_extendRemove(inst.settings, settings || {}); 346 date = (date && date.constructor === Date ? this._formatDate(inst, date) : date); 347 this._dialogInput.val(date); 348 349 this._pos = (pos ? (pos.length ? pos : [pos.pageX, pos.pageY]) : null); 350 if (!this._pos) { 351 browserWidth = document.documentElement.clientWidth; 352 browserHeight = document.documentElement.clientHeight; 353 scrollX = document.documentElement.scrollLeft || document.body.scrollLeft; 354 scrollY = document.documentElement.scrollTop || document.body.scrollTop; 355 this._pos = // should use actual width/height below 356 [(browserWidth / 2) - 100 + scrollX, (browserHeight / 2) - 150 + scrollY]; 357 } 358 359 // move input on screen for focus, but hidden behind dialog 360 this._dialogInput.css("left", (this._pos[0] + 20) + "px").css("top", this._pos[1] + "px"); 361 inst.settings.onSelect = onSelect; 362 this._inDialog = true; 363 this.dpDiv.addClass(this._dialogClass); 364 this._showDatepicker(this._dialogInput[0]); 365 if ($.blockUI) { 366 $.blockUI(this.dpDiv); 367 } 368 $.data(this._dialogInput[0], "datepicker", inst); 369 return this; 370 }, 371 372 /* Detach a datepicker from its control. 373 * @param target element - the target input field or division or span 374 */ 375 _destroyDatepicker: function(target) { 376 var nodeName, 377 $target = $(target), 378 inst = $.data(target, "datepicker"); 379 380 if (!$target.hasClass(this.markerClassName)) { 381 return; 382 } 383 384 nodeName = target.nodeName.toLowerCase(); 385 $.removeData(target, "datepicker"); 386 if (nodeName === "input") { 387 inst.append.remove(); 388 inst.trigger.remove(); 389 $target.removeClass(this.markerClassName). 390 unbind("focus", this._showDatepicker). 391 unbind("keydown", this._doKeyDown). 392 unbind("keypress", this._doKeyPress). 393 unbind("keyup", this._doKeyUp); 394 } else if (nodeName === "div" || nodeName === "span") { 395 $target.removeClass(this.markerClassName).empty(); 396 } 397 398 if ( datepicker_instActive === inst ) { 399 datepicker_instActive = null; 400 } 401 }, 402 403 /* Enable the date picker to a jQuery selection. 404 * @param target element - the target input field or division or span 405 */ 406 _enableDatepicker: function(target) { 407 var nodeName, inline, 408 $target = $(target), 409 inst = $.data(target, "datepicker"); 410 411 if (!$target.hasClass(this.markerClassName)) { 412 return; 413 } 414 415 nodeName = target.nodeName.toLowerCase(); 416 if (nodeName === "input") { 417 target.disabled = false; 418 inst.trigger.filter("button"). 419 each(function() { this.disabled = false; }).end(). 420 filter("img").css({opacity: "1.0", cursor: ""}); 421 } else if (nodeName === "div" || nodeName === "span") { 422 inline = $target.children("." + this._inlineClass); 423 inline.children().removeClass("ui-state-disabled"); 424 inline.find("select.ui-datepicker-month, select.ui-datepicker-year"). 425 prop("disabled", false); 426 } 427 this._disabledInputs = $.map(this._disabledInputs, 428 function(value) { return (value === target ? null : value); }); // delete entry 429 }, 430 431 /* Disable the date picker to a jQuery selection. 432 * @param target element - the target input field or division or span 433 */ 434 _disableDatepicker: function(target) { 435 var nodeName, inline, 436 $target = $(target), 437 inst = $.data(target, "datepicker"); 438 439 if (!$target.hasClass(this.markerClassName)) { 440 return; 441 } 442 443 nodeName = target.nodeName.toLowerCase(); 444 if (nodeName === "input") { 445 target.disabled = true; 446 inst.trigger.filter("button"). 447 each(function() { this.disabled = true; }).end(). 448 filter("img").css({opacity: "0.5", cursor: "default"}); 449 } else if (nodeName === "div" || nodeName === "span") { 450 inline = $target.children("." + this._inlineClass); 451 inline.children().addClass("ui-state-disabled"); 452 inline.find("select.ui-datepicker-month, select.ui-datepicker-year"). 453 prop("disabled", true); 454 } 455 this._disabledInputs = $.map(this._disabledInputs, 456 function(value) { return (value === target ? null : value); }); // delete entry 457 this._disabledInputs[this._disabledInputs.length] = target; 458 }, 459 460 /* Is the first field in a jQuery collection disabled as a datepicker? 461 * @param target element - the target input field or division or span 462 * @return boolean - true if disabled, false if enabled 463 */ 464 _isDisabledDatepicker: function(target) { 465 if (!target) { 466 return false; 467 } 468 for (var i = 0; i < this._disabledInputs.length; i++) { 469 if (this._disabledInputs[i] === target) { 470 return true; 471 } 472 } 473 return false; 474 }, 475 476 /* Retrieve the instance data for the target control. 477 * @param target element - the target input field or division or span 478 * @return object - the associated instance data 479 * @throws error if a jQuery problem getting data 480 */ 481 _getInst: function(target) { 482 try { 483 return $.data(target, "datepicker"); 484 } 485 catch (err) { 486 throw "Missing instance data for this datepicker"; 487 } 488 }, 489 490 /* Update or retrieve the settings for a date picker attached to an input field or division. 491 * @param target element - the target input field or division or span 492 * @param name object - the new settings to update or 493 * string - the name of the setting to change or retrieve, 494 * when retrieving also "all" for all instance settings or 495 * "defaults" for all global defaults 496 * @param value any - the new value for the setting 497 * (omit if above is an object or to retrieve a value) 498 */ 499 _optionDatepicker: function(target, name, value) { 500 var settings, date, minDate, maxDate, 501 inst = this._getInst(target); 502 503 if (arguments.length === 2 && typeof name === "string") { 504 return (name === "defaults" ? $.extend({}, $.datepicker._defaults) : 505 (inst ? (name === "all" ? $.extend({}, inst.settings) : 506 this._get(inst, name)) : null)); 507 } 508 509 settings = name || {}; 510 if (typeof name === "string") { 511 settings = {}; 512 settings[name] = value; 513 } 514 515 if (inst) { 516 if (this._curInst === inst) { 517 this._hideDatepicker(); 518 } 519 520 date = this._getDateDatepicker(target, true); 521 minDate = this._getMinMaxDate(inst, "min"); 522 maxDate = this._getMinMaxDate(inst, "max"); 523 datepicker_extendRemove(inst.settings, settings); 524 // reformat the old minDate/maxDate values if dateFormat changes and a new minDate/maxDate isn't provided 525 if (minDate !== null && settings.dateFormat !== undefined && settings.minDate === undefined) { 526 inst.settings.minDate = this._formatDate(inst, minDate); 527 } 528 if (maxDate !== null && settings.dateFormat !== undefined && settings.maxDate === undefined) { 529 inst.settings.maxDate = this._formatDate(inst, maxDate); 530 } 531 if ( "disabled" in settings ) { 532 if ( settings.disabled ) { 533 this._disableDatepicker(target); 534 } else { 535 this._enableDatepicker(target); 536 } 537 } 538 this._attachments($(target), inst); 539 this._autoSize(inst); 540 this._setDate(inst, date); 541 this._updateAlternate(inst); 542 this._updateDatepicker(inst); 543 } 544 }, 545 546 // change method deprecated 547 _changeDatepicker: function(target, name, value) { 548 this._optionDatepicker(target, name, value); 549 }, 550 551 /* Redraw the date picker attached to an input field or division. 552 * @param target element - the target input field or division or span 553 */ 554 _refreshDatepicker: function(target) { 555 var inst = this._getInst(target); 556 if (inst) { 557 this._updateDatepicker(inst); 558 } 559 }, 560 561 /* Set the dates for a jQuery selection. 562 * @param target element - the target input field or division or span 563 * @param date Date - the new date 564 */ 565 _setDateDatepicker: function(target, date) { 566 var inst = this._getInst(target); 567 if (inst) { 568 this._setDate(inst, date); 569 this._updateDatepicker(inst); 570 this._updateAlternate(inst); 571 } 572 }, 573 574 /* Get the date(s) for the first entry in a jQuery selection. 575 * @param target element - the target input field or division or span 576 * @param noDefault boolean - true if no default date is to be used 577 * @return Date - the current date 578 */ 579 _getDateDatepicker: function(target, noDefault) { 580 var inst = this._getInst(target); 581 if (inst && !inst.inline) { 582 this._setDateFromField(inst, noDefault); 583 } 584 return (inst ? this._getDate(inst) : null); 585 }, 586 587 /* Handle keystrokes. */ 588 _doKeyDown: function(event) { 589 var onSelect, dateStr, sel, 590 inst = $.datepicker._getInst(event.target), 591 handled = true, 592 isRTL = inst.dpDiv.is(".ui-datepicker-rtl"); 593 594 inst._keyEvent = true; 595 if ($.datepicker._datepickerShowing) { 596 switch (event.keyCode) { 597 case 9: $.datepicker._hideDatepicker(); 598 handled = false; 599 break; // hide on tab out 600 case 13: sel = $("td." + $.datepicker._dayOverClass + ":not(." + 601 $.datepicker._currentClass + ")", inst.dpDiv); 602 if (sel[0]) { 603 $.datepicker._selectDay(event.target, inst.selectedMonth, inst.selectedYear, sel[0]); 604 } 605 606 onSelect = $.datepicker._get(inst, "onSelect"); 607 if (onSelect) { 608 dateStr = $.datepicker._formatDate(inst); 609 610 // trigger custom callback 611 onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]); 612 } else { 613 $.datepicker._hideDatepicker(); 614 } 615 616 return false; // don't submit the form 617 case 27: $.datepicker._hideDatepicker(); 618 break; // hide on escape 619 case 33: $.datepicker._adjustDate(event.target, (event.ctrlKey ? 620 -$.datepicker._get(inst, "stepBigMonths") : 621 -$.datepicker._get(inst, "stepMonths")), "M"); 622 break; // previous month/year on page up/+ ctrl 623 case 34: $.datepicker._adjustDate(event.target, (event.ctrlKey ? 624 +$.datepicker._get(inst, "stepBigMonths") : 625 +$.datepicker._get(inst, "stepMonths")), "M"); 626 break; // next month/year on page down/+ ctrl 627 case 35: if (event.ctrlKey || event.metaKey) { 628 $.datepicker._clearDate(event.target); 629 } 630 handled = event.ctrlKey || event.metaKey; 631 break; // clear on ctrl or command +end 632 case 36: if (event.ctrlKey || event.metaKey) { 633 $.datepicker._gotoToday(event.target); 634 } 635 handled = event.ctrlKey || event.metaKey; 636 break; // current on ctrl or command +home 637 case 37: if (event.ctrlKey || event.metaKey) { 638 $.datepicker._adjustDate(event.target, (isRTL ? +1 : -1), "D"); 639 } 640 handled = event.ctrlKey || event.metaKey; 641 // -1 day on ctrl or command +left 642 if (event.originalEvent.altKey) { 643 $.datepicker._adjustDate(event.target, (event.ctrlKey ? 644 -$.datepicker._get(inst, "stepBigMonths") : 645 -$.datepicker._get(inst, "stepMonths")), "M"); 646 } 647 // next month/year on alt +left on Mac 648 break; 649 case 38: if (event.ctrlKey || event.metaKey) { 650 $.datepicker._adjustDate(event.target, -7, "D"); 651 } 652 handled = event.ctrlKey || event.metaKey; 653 break; // -1 week on ctrl or command +up 654 case 39: if (event.ctrlKey || event.metaKey) { 655 $.datepicker._adjustDate(event.target, (isRTL ? -1 : +1), "D"); 656 } 657 handled = event.ctrlKey || event.metaKey; 658 // +1 day on ctrl or command +right 659 if (event.originalEvent.altKey) { 660 $.datepicker._adjustDate(event.target, (event.ctrlKey ? 661 +$.datepicker._get(inst, "stepBigMonths") : 662 +$.datepicker._get(inst, "stepMonths")), "M"); 663 } 664 // next month/year on alt +right 665 break; 666 case 40: if (event.ctrlKey || event.metaKey) { 667 $.datepicker._adjustDate(event.target, +7, "D"); 668 } 669 handled = event.ctrlKey || event.metaKey; 670 break; // +1 week on ctrl or command +down 671 default: handled = false; 672 } 673 } else if (event.keyCode === 36 && event.ctrlKey) { // display the date picker on ctrl+home 674 $.datepicker._showDatepicker(this); 675 } else { 676 handled = false; 677 } 678 679 if (handled) { 680 event.preventDefault(); 681 event.stopPropagation(); 682 } 683 }, 684 685 /* Filter entered characters - based on date format. */ 686 _doKeyPress: function(event) { 687 var chars, chr, 688 inst = $.datepicker._getInst(event.target); 689 690 if ($.datepicker._get(inst, "constrainInput")) { 691 chars = $.datepicker._possibleChars($.datepicker._get(inst, "dateFormat")); 692 chr = String.fromCharCode(event.charCode == null ? event.keyCode : event.charCode); 693 return event.ctrlKey || event.metaKey || (chr < " " || !chars || chars.indexOf(chr) > -1); 694 } 695 }, 696 697 /* Synchronise manual entry and field/alternate field. */ 698 _doKeyUp: function(event) { 699 var date, 700 inst = $.datepicker._getInst(event.target); 701 702 if (inst.input.val() !== inst.lastVal) { 703 try { 704 date = $.datepicker.parseDate($.datepicker._get(inst, "dateFormat"), 705 (inst.input ? inst.input.val() : null), 706 $.datepicker._getFormatConfig(inst)); 707 708 if (date) { // only if valid 709 $.datepicker._setDateFromField(inst); 710 $.datepicker._updateAlternate(inst); 711 $.datepicker._updateDatepicker(inst); 712 } 713 } 714 catch (err) { 715 } 716 } 717 return true; 718 }, 719 720 /* Pop-up the date picker for a given input field. 721 * If false returned from beforeShow event handler do not show. 722 * @param input element - the input field attached to the date picker or 723 * event - if triggered by focus 724 */ 725 _showDatepicker: function(input) { 726 input = input.target || input; 727 if (input.nodeName.toLowerCase() !== "input") { // find from button/image trigger 728 input = $("input", input.parentNode)[0]; 729 } 730 731 if ($.datepicker._isDisabledDatepicker(input) || $.datepicker._lastInput === input) { // already here 732 return; 733 } 734 735 var inst, beforeShow, beforeShowSettings, isFixed, 736 offset, showAnim, duration; 737 738 inst = $.datepicker._getInst(input); 739 if ($.datepicker._curInst && $.datepicker._curInst !== inst) { 740 $.datepicker._curInst.dpDiv.stop(true, true); 741 if ( inst && $.datepicker._datepickerShowing ) { 742 $.datepicker._hideDatepicker( $.datepicker._curInst.input[0] ); 743 } 744 } 745 746 beforeShow = $.datepicker._get(inst, "beforeShow"); 747 beforeShowSettings = beforeShow ? beforeShow.apply(input, [input, inst]) : {}; 748 if(beforeShowSettings === false){ 749 return; 750 } 751 datepicker_extendRemove(inst.settings, beforeShowSettings); 752 753 inst.lastVal = null; 754 $.datepicker._lastInput = input; 755 $.datepicker._setDateFromField(inst); 756 757 if ($.datepicker._inDialog) { // hide cursor 758 input.value = ""; 759 } 760 if (!$.datepicker._pos) { // position below input 761 $.datepicker._pos = $.datepicker._findPos(input); 762 $.datepicker._pos[1] += input.offsetHeight; // add the height 763 } 764 765 isFixed = false; 766 $(input).parents().each(function() { 767 isFixed |= $(this).css("position") === "fixed"; 768 return !isFixed; 769 }); 770 771 offset = {left: $.datepicker._pos[0], top: $.datepicker._pos[1]}; 772 $.datepicker._pos = null; 773 //to avoid flashes on Firefox 774 inst.dpDiv.empty(); 775 // determine sizing offscreen 776 inst.dpDiv.css({position: "absolute", display: "block", top: "-1000px"}); 777 $.datepicker._updateDatepicker(inst); 778 // fix width for dynamic number of date pickers 779 // and adjust position before showing 780 offset = $.datepicker._checkOffset(inst, offset, isFixed); 781 inst.dpDiv.css({position: ($.datepicker._inDialog && $.blockUI ? 782 "static" : (isFixed ? "fixed" : "absolute")), display: "none", 783 left: offset.left + "px", top: offset.top + "px"}); 784 785 if (!inst.inline) { 786 showAnim = $.datepicker._get(inst, "showAnim"); 787 duration = $.datepicker._get(inst, "duration"); 788 inst.dpDiv.css( "z-index", datepicker_getZindex( $( input ) ) + 1 ); 789 $.datepicker._datepickerShowing = true; 790 791 if ( $.effects && $.effects.effect[ showAnim ] ) { 792 inst.dpDiv.show(showAnim, $.datepicker._get(inst, "showOptions"), duration); 793 } else { 794 inst.dpDiv[showAnim || "show"](showAnim ? duration : null); 795 } 796 797 if ( $.datepicker._shouldFocusInput( inst ) ) { 798 inst.input.focus(); 799 } 800 801 $.datepicker._curInst = inst; 802 } 803 }, 804 805 /* Generate the date picker content. */ 806 _updateDatepicker: function(inst) { 807 this.maxRows = 4; //Reset the max number of rows being displayed (see #7043) 808 datepicker_instActive = inst; // for delegate hover events 809 inst.dpDiv.empty().append(this._generateHTML(inst)); 810 this._attachHandlers(inst); 811 812 var origyearshtml, 813 numMonths = this._getNumberOfMonths(inst), 814 cols = numMonths[1], 815 width = 17, 816 activeCell = inst.dpDiv.find( "." + this._dayOverClass + " a" ); 817 818 if ( activeCell.length > 0 ) { 819 datepicker_handleMouseover.apply( activeCell.get( 0 ) ); 820 } 821 822 inst.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width(""); 823 if (cols > 1) { 824 inst.dpDiv.addClass("ui-datepicker-multi-" + cols).css("width", (width * cols) + "em"); 825 } 826 inst.dpDiv[(numMonths[0] !== 1 || numMonths[1] !== 1 ? "add" : "remove") + 827 "Class"]("ui-datepicker-multi"); 828 inst.dpDiv[(this._get(inst, "isRTL") ? "add" : "remove") + 829 "Class"]("ui-datepicker-rtl"); 830 831 if (inst === $.datepicker._curInst && $.datepicker._datepickerShowing && $.datepicker._shouldFocusInput( inst ) ) { 832 inst.input.focus(); 833 } 834 835 // deffered render of the years select (to avoid flashes on Firefox) 836 if( inst.yearshtml ){ 837 origyearshtml = inst.yearshtml; 838 setTimeout(function(){ 839 //assure that inst.yearshtml didn't change. 840 if( origyearshtml === inst.yearshtml && inst.yearshtml ){ 841 inst.dpDiv.find("select.ui-datepicker-year:first").replaceWith(inst.yearshtml); 842 } 843 origyearshtml = inst.yearshtml = null; 844 }, 0); 845 } 846 }, 847 848 // #6694 - don't focus the input if it's already focused 849 // this breaks the change event in IE 850 // Support: IE and jQuery <1.9 851 _shouldFocusInput: function( inst ) { 852 return inst.input && inst.input.is( ":visible" ) && !inst.input.is( ":disabled" ) && !inst.input.is( ":focus" ); 853 }, 854 855 /* Check positioning to remain on screen. */ 856 _checkOffset: function(inst, offset, isFixed) { 857 var dpWidth = inst.dpDiv.outerWidth(), 858 dpHeight = inst.dpDiv.outerHeight(), 859 inputWidth = inst.input ? inst.input.outerWidth() : 0, 860 inputHeight = inst.input ? inst.input.outerHeight() : 0, 861 viewWidth = document.documentElement.clientWidth + (isFixed ? 0 : $(document).scrollLeft()), 862 viewHeight = document.documentElement.clientHeight + (isFixed ? 0 : $(document).scrollTop()); 863 864 offset.left -= (this._get(inst, "isRTL") ? (dpWidth - inputWidth) : 0); 865 offset.left -= (isFixed && offset.left === inst.input.offset().left) ? $(document).scrollLeft() : 0; 866 offset.top -= (isFixed && offset.top === (inst.input.offset().top + inputHeight)) ? $(document).scrollTop() : 0; 867 868 // now check if datepicker is showing outside window viewport - move to a better place if so. 869 offset.left -= Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ? 870 Math.abs(offset.left + dpWidth - viewWidth) : 0); 871 offset.top -= Math.min(offset.top, (offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ? 872 Math.abs(dpHeight + inputHeight) : 0); 873 874 return offset; 875 }, 876 877 /* Find an object's position on the screen. */ 878 _findPos: function(obj) { 879 var position, 880 inst = this._getInst(obj), 881 isRTL = this._get(inst, "isRTL"); 882 883 while (obj && (obj.type === "hidden" || obj.nodeType !== 1 || $.expr.filters.hidden(obj))) { 884 obj = obj[isRTL ? "previousSibling" : "nextSibling"]; 885 } 886 887 position = $(obj).offset(); 888 return [position.left, position.top]; 889 }, 890 891 /* Hide the date picker from view. 892 * @param input element - the input field attached to the date picker 893 */ 894 _hideDatepicker: function(input) { 895 var showAnim, duration, postProcess, onClose, 896 inst = this._curInst; 897 898 if (!inst || (input && inst !== $.data(input, "datepicker"))) { 899 return; 900 } 901 902 if (this._datepickerShowing) { 903 showAnim = this._get(inst, "showAnim"); 904 duration = this._get(inst, "duration"); 905 postProcess = function() { 906 $.datepicker._tidyDialog(inst); 907 }; 908 909 // DEPRECATED: after BC for 1.8.x $.effects[ showAnim ] is not needed 910 if ( $.effects && ( $.effects.effect[ showAnim ] || $.effects[ showAnim ] ) ) { 911 inst.dpDiv.hide(showAnim, $.datepicker._get(inst, "showOptions"), duration, postProcess); 912 } else { 913 inst.dpDiv[(showAnim === "slideDown" ? "slideUp" : 914 (showAnim === "fadeIn" ? "fadeOut" : "hide"))]((showAnim ? duration : null), postProcess); 915 } 916 917 if (!showAnim) { 918 postProcess(); 919 } 920 this._datepickerShowing = false; 921 922 onClose = this._get(inst, "onClose"); 923 if (onClose) { 924 onClose.apply((inst.input ? inst.input[0] : null), [(inst.input ? inst.input.val() : ""), inst]); 925 } 926 927 this._lastInput = null; 928 if (this._inDialog) { 929 this._dialogInput.css({ position: "absolute", left: "0", top: "-100px" }); 930 if ($.blockUI) { 931 $.unblockUI(); 932 $("body").append(this.dpDiv); 933 } 934 } 935 this._inDialog = false; 936 } 937 }, 938 939 /* Tidy up after a dialog display. */ 940 _tidyDialog: function(inst) { 941 inst.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar"); 942 }, 943 944 /* Close date picker if clicked elsewhere. */ 945 _checkExternalClick: function(event) { 946 if (!$.datepicker._curInst) { 947 return; 948 } 949 950 var $target = $(event.target), 951 inst = $.datepicker._getInst($target[0]); 952 953 if ( ( ( $target[0].id !== $.datepicker._mainDivId && 954 $target.parents("#" + $.datepicker._mainDivId).length === 0 && 955 !$target.hasClass($.datepicker.markerClassName) && 956 !$target.closest("." + $.datepicker._triggerClass).length && 957 $.datepicker._datepickerShowing && !($.datepicker._inDialog && $.blockUI) ) ) || 958 ( $target.hasClass($.datepicker.markerClassName) && $.datepicker._curInst !== inst ) ) { 959 $.datepicker._hideDatepicker(); 960 } 961 }, 962 963 /* Adjust one of the date sub-fields. */ 964 _adjustDate: function(id, offset, period) { 965 var target = $(id), 966 inst = this._getInst(target[0]); 967 968 if (this._isDisabledDatepicker(target[0])) { 969 return; 970 } 971 this._adjustInstDate(inst, offset + 972 (period === "M" ? this._get(inst, "showCurrentAtPos") : 0), // undo positioning 973 period); 974 this._updateDatepicker(inst); 975 }, 976 977 /* Action for current link. */ 978 _gotoToday: function(id) { 979 var date, 980 target = $(id), 981 inst = this._getInst(target[0]); 982 983 if (this._get(inst, "gotoCurrent") && inst.currentDay) { 984 inst.selectedDay = inst.currentDay; 985 inst.drawMonth = inst.selectedMonth = inst.currentMonth; 986 inst.drawYear = inst.selectedYear = inst.currentYear; 987 } else { 988 date = new Date(); 989 inst.selectedDay = date.getDate(); 990 inst.drawMonth = inst.selectedMonth = date.getMonth(); 991 inst.drawYear = inst.selectedYear = date.getFullYear(); 992 } 993 this._notifyChange(inst); 994 this._adjustDate(target); 995 }, 996 997 /* Action for selecting a new month/year. */ 998 _selectMonthYear: function(id, select, period) { 999 var target = $(id), 1000 inst = this._getInst(target[0]); 1001 1002 inst["selected" + (period === "M" ? "Month" : "Year")] = 1003 inst["draw" + (period === "M" ? "Month" : "Year")] = 1004 parseInt(select.options[select.selectedIndex].value,10); 1005 1006 this._notifyChange(inst); 1007 this._adjustDate(target); 1008 }, 1009 1010 /* Action for selecting a day. */ 1011 _selectDay: function(id, month, year, td) { 1012 var inst, 1013 target = $(id); 1014 1015 if ($(td).hasClass(this._unselectableClass) || this._isDisabledDatepicker(target[0])) { 1016 return; 1017 } 1018 1019 inst = this._getInst(target[0]); 1020 inst.selectedDay = inst.currentDay = $("a", td).html(); 1021 inst.selectedMonth = inst.currentMonth = month; 1022 inst.selectedYear = inst.currentYear = year; 1023 this._selectDate(id, this._formatDate(inst, 1024 inst.currentDay, inst.currentMonth, inst.currentYear)); 1025 }, 1026 1027 /* Erase the input field and hide the date picker. */ 1028 _clearDate: function(id) { 1029 var target = $(id); 1030 this._selectDate(target, ""); 1031 }, 1032 1033 /* Update the input field with the selected date. */ 1034 _selectDate: function(id, dateStr) { 1035 var onSelect, 1036 target = $(id), 1037 inst = this._getInst(target[0]); 1038 1039 dateStr = (dateStr != null ? dateStr : this._formatDate(inst)); 1040 if (inst.input) { 1041 inst.input.val(dateStr); 1042 } 1043 this._updateAlternate(inst); 1044 1045 onSelect = this._get(inst, "onSelect"); 1046 if (onSelect) { 1047 onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]); // trigger custom callback 1048 } else if (inst.input) { 1049 inst.input.trigger("change"); // fire the change event 1050 } 1051 1052 if (inst.inline){ 1053 this._updateDatepicker(inst); 1054 } else { 1055 this._hideDatepicker(); 1056 this._lastInput = inst.input[0]; 1057 if (typeof(inst.input[0]) !== "object") { 1058 inst.input.focus(); // restore focus 1059 } 1060 this._lastInput = null; 1061 } 1062 }, 1063 1064 /* Update any alternate field to synchronise with the main field. */ 1065 _updateAlternate: function(inst) { 1066 var altFormat, date, dateStr, 1067 altField = this._get(inst, "altField"); 1068 1069 if (altField) { // update alternate field too 1070 altFormat = this._get(inst, "altFormat") || this._get(inst, "dateFormat"); 1071 date = this._getDate(inst); 1072 dateStr = this.formatDate(altFormat, date, this._getFormatConfig(inst)); 1073 $(altField).each(function() { $(this).val(dateStr); }); 1074 } 1075 }, 1076 1077 /* Set as beforeShowDay function to prevent selection of weekends. 1078 * @param date Date - the date to customise 1079 * @return [boolean, string] - is this date selectable?, what is its CSS class? 1080 */ 1081 noWeekends: function(date) { 1082 var day = date.getDay(); 1083 return [(day > 0 && day < 6), ""]; 1084 }, 1085 1086 /* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition. 1087 * @param date Date - the date to get the week for 1088 * @return number - the number of the week within the year that contains this date 1089 */ 1090 iso8601Week: function(date) { 1091 var time, 1092 checkDate = new Date(date.getTime()); 1093 1094 // Find Thursday of this week starting on Monday 1095 checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); 1096 1097 time = checkDate.getTime(); 1098 checkDate.setMonth(0); // Compare with Jan 1 1099 checkDate.setDate(1); 1100 return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1; 1101 }, 1102 1103 /* Parse a string value into a date object. 1104 * See formatDate below for the possible formats. 1105 * 1106 * @param format string - the expected format of the date 1107 * @param value string - the date in the above format 1108 * @param settings Object - attributes include: 1109 * shortYearCutoff number - the cutoff year for determining the century (optional) 1110 * dayNamesShort string[7] - abbreviated names of the days from Sunday (optional) 1111 * dayNames string[7] - names of the days from Sunday (optional) 1112 * monthNamesShort string[12] - abbreviated names of the months (optional) 1113 * monthNames string[12] - names of the months (optional) 1114 * @return Date - the extracted date value or null if value is blank 1115 */ 1116 parseDate: function (format, value, settings) { 1117 if (format == null || value == null) { 1118 throw "Invalid arguments"; 1119 } 1120 1121 value = (typeof value === "object" ? value.toString() : value + ""); 1122 if (value === "") { 1123 return null; 1124 } 1125 1126 var iFormat, dim, extra, 1127 iValue = 0, 1128 shortYearCutoffTemp = (settings ? settings.shortYearCutoff : null) || this._defaults.shortYearCutoff, 1129 shortYearCutoff = (typeof shortYearCutoffTemp !== "string" ? shortYearCutoffTemp : 1130 new Date().getFullYear() % 100 + parseInt(shortYearCutoffTemp, 10)), 1131 dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort, 1132 dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames, 1133 monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort, 1134 monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames, 1135 year = -1, 1136 month = -1, 1137 day = -1, 1138 doy = -1, 1139 literal = false, 1140 date, 1141 // Check whether a format character is doubled 1142 lookAhead = function(match) { 1143 var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match); 1144 if (matches) { 1145 iFormat++; 1146 } 1147 return matches; 1148 }, 1149 // Extract a number from the string value 1150 getNumber = function(match) { 1151 var isDoubled = lookAhead(match), 1152 size = (match === "@" ? 14 : (match === "!" ? 20 : 1153 (match === "y" && isDoubled ? 4 : (match === "o" ? 3 : 2)))), 1154 minSize = (match === "y" ? size : 1), 1155 digits = new RegExp("^\\d{" + minSize + "," + size + "}"), 1156 num = value.substring(iValue).match(digits); 1157 if (!num) { 1158 throw "Missing number at position " + iValue; 1159 } 1160 iValue += num[0].length; 1161 return parseInt(num[0], 10); 1162 }, 1163 // Extract a name from the string value and convert to an index 1164 getName = function(match, shortNames, longNames) { 1165 var index = -1, 1166 names = $.map(lookAhead(match) ? longNames : shortNames, function (v, k) { 1167 return [ [k, v] ]; 1168 }).sort(function (a, b) { 1169 return -(a[1].length - b[1].length); 1170 }); 1171 1172 $.each(names, function (i, pair) { 1173 var name = pair[1]; 1174 if (value.substr(iValue, name.length).toLowerCase() === name.toLowerCase()) { 1175 index = pair[0]; 1176 iValue += name.length; 1177 return false; 1178 } 1179 }); 1180 if (index !== -1) { 1181 return index + 1; 1182 } else { 1183 throw "Unknown name at position " + iValue; 1184 } 1185 }, 1186 // Confirm that a literal character matches the string value 1187 checkLiteral = function() { 1188 if (value.charAt(iValue) !== format.charAt(iFormat)) { 1189 throw "Unexpected literal at position " + iValue; 1190 } 1191 iValue++; 1192 }; 1193 1194 for (iFormat = 0; iFormat < format.length; iFormat++) { 1195 if (literal) { 1196 if (format.charAt(iFormat) === "'" && !lookAhead("'")) { 1197 literal = false; 1198 } else { 1199 checkLiteral(); 1200 } 1201 } else { 1202 switch (format.charAt(iFormat)) { 1203 case "d": 1204 day = getNumber("d"); 1205 break; 1206 case "D": 1207 getName("D", dayNamesShort, dayNames); 1208 break; 1209 case "o": 1210 doy = getNumber("o"); 1211 break; 1212 case "m": 1213 month = getNumber("m"); 1214 break; 1215 case "M": 1216 month = getName("M", monthNamesShort, monthNames); 1217 break; 1218 case "y": 1219 year = getNumber("y"); 1220 break; 1221 case "@": 1222 date = new Date(getNumber("@")); 1223 year = date.getFullYear(); 1224 month = date.getMonth() + 1; 1225 day = date.getDate(); 1226 break; 1227 case "!": 1228 date = new Date((getNumber("!") - this._ticksTo1970) / 10000); 1229 year = date.getFullYear(); 1230 month = date.getMonth() + 1; 1231 day = date.getDate(); 1232 break; 1233 case "'": 1234 if (lookAhead("'")){ 1235 checkLiteral(); 1236 } else { 1237 literal = true; 1238 } 1239 break; 1240 default: 1241 checkLiteral(); 1242 } 1243 } 1244 } 1245 1246 if (iValue < value.length){ 1247 extra = value.substr(iValue); 1248 if (!/^\s+/.test(extra)) { 1249 throw "Extra/unparsed characters found in date: " + extra; 1250 } 1251 } 1252 1253 if (year === -1) { 1254 year = new Date().getFullYear(); 1255 } else if (year < 100) { 1256 year += new Date().getFullYear() - new Date().getFullYear() % 100 + 1257 (year <= shortYearCutoff ? 0 : -100); 1258 } 1259 1260 if (doy > -1) { 1261 month = 1; 1262 day = doy; 1263 do { 1264 dim = this._getDaysInMonth(year, month - 1); 1265 if (day <= dim) { 1266 break; 1267 } 1268 month++; 1269 day -= dim; 1270 } while (true); 1271 } 1272 1273 date = this._daylightSavingAdjust(new Date(year, month - 1, day)); 1274 if (date.getFullYear() !== year || date.getMonth() + 1 !== month || date.getDate() !== day) { 1275 throw "Invalid date"; // E.g. 31/02/00 1276 } 1277 return date; 1278 }, 1279 1280 /* Standard date formats. */ 1281 ATOM: "yy-mm-dd", // RFC 3339 (ISO 8601) 1282 COOKIE: "D, dd M yy", 1283 ISO_8601: "yy-mm-dd", 1284 RFC_822: "D, d M y", 1285 RFC_850: "DD, dd-M-y", 1286 RFC_1036: "D, d M y", 1287 RFC_1123: "D, d M yy", 1288 RFC_2822: "D, d M yy", 1289 RSS: "D, d M y", // RFC 822 1290 TICKS: "!", 1291 TIMESTAMP: "@", 1292 W3C: "yy-mm-dd", // ISO 8601 1293 1294 _ticksTo1970: (((1970 - 1) * 365 + Math.floor(1970 / 4) - Math.floor(1970 / 100) + 1295 Math.floor(1970 / 400)) * 24 * 60 * 60 * 10000000), 1296 1297 /* Format a date object into a string value. 1298 * The format can be combinations of the following: 1299 * d - day of month (no leading zero) 1300 * dd - day of month (two digit) 1301 * o - day of year (no leading zeros) 1302 * oo - day of year (three digit) 1303 * D - day name short 1304 * DD - day name long 1305 * m - month of year (no leading zero) 1306 * mm - month of year (two digit) 1307 * M - month name short 1308 * MM - month name long 1309 * y - year (two digit) 1310 * yy - year (four digit) 1311 * @ - Unix timestamp (ms since 01/01/1970) 1312 * ! - Windows ticks (100ns since 01/01/0001) 1313 * "..." - literal text 1314 * '' - single quote 1315 * 1316 * @param format string - the desired format of the date 1317 * @param date Date - the date value to format 1318 * @param settings Object - attributes include: 1319 * dayNamesShort string[7] - abbreviated names of the days from Sunday (optional) 1320 * dayNames string[7] - names of the days from Sunday (optional) 1321 * monthNamesShort string[12] - abbreviated names of the months (optional) 1322 * monthNames string[12] - names of the months (optional) 1323 * @return string - the date in the above format 1324 */ 1325 formatDate: function (format, date, settings) { 1326 if (!date) { 1327 return ""; 1328 } 1329 1330 var iFormat, 1331 dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort, 1332 dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames, 1333 monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort, 1334 monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames, 1335 // Check whether a format character is doubled 1336 lookAhead = function(match) { 1337 var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match); 1338 if (matches) { 1339 iFormat++; 1340 } 1341 return matches; 1342 }, 1343 // Format a number, with leading zero if necessary 1344 formatNumber = function(match, value, len) { 1345 var num = "" + value; 1346 if (lookAhead(match)) { 1347 while (num.length < len) { 1348 num = "0" + num; 1349 } 1350 } 1351 return num; 1352 }, 1353 // Format a name, short or long as requested 1354 formatName = function(match, value, shortNames, longNames) { 1355 return (lookAhead(match) ? longNames[value] : shortNames[value]); 1356 }, 1357 output = "", 1358 literal = false; 1359 1360 if (date) { 1361 for (iFormat = 0; iFormat < format.length; iFormat++) { 1362 if (literal) { 1363 if (format.charAt(iFormat) === "'" && !lookAhead("'")) { 1364 literal = false; 1365 } else { 1366 output += format.charAt(iFormat); 1367 } 1368 } else { 1369 switch (format.charAt(iFormat)) { 1370 case "d": 1371 output += formatNumber("d", date.getDate(), 2); 1372 break; 1373 case "D": 1374 output += formatName("D", date.getDay(), dayNamesShort, dayNames); 1375 break; 1376 case "o": 1377 output += formatNumber("o", 1378 Math.round((new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime() - new Date(date.getFullYear(), 0, 0).getTime()) / 86400000), 3); 1379 break; 1380 case "m": 1381 output += formatNumber("m", date.getMonth() + 1, 2); 1382 break; 1383 case "M": 1384 output += formatName("M", date.getMonth(), monthNamesShort, monthNames); 1385 break; 1386 case "y": 1387 output += (lookAhead("y") ? date.getFullYear() : 1388 (date.getYear() % 100 < 10 ? "0" : "") + date.getYear() % 100); 1389 break; 1390 case "@": 1391 output += date.getTime(); 1392 break; 1393 case "!": 1394 output += date.getTime() * 10000 + this._ticksTo1970; 1395 break; 1396 case "'": 1397 if (lookAhead("'")) { 1398 output += "'"; 1399 } else { 1400 literal = true; 1401 } 1402 break; 1403 default: 1404 output += format.charAt(iFormat); 1405 } 1406 } 1407 } 1408 } 1409 return output; 1410 }, 1411 1412 /* Extract all possible characters from the date format. */ 1413 _possibleChars: function (format) { 1414 var iFormat, 1415 chars = "", 1416 literal = false, 1417 // Check whether a format character is doubled 1418 lookAhead = function(match) { 1419 var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match); 1420 if (matches) { 1421 iFormat++; 1422 } 1423 return matches; 1424 }; 1425 1426 for (iFormat = 0; iFormat < format.length; iFormat++) { 1427 if (literal) { 1428 if (format.charAt(iFormat) === "'" && !lookAhead("'")) { 1429 literal = false; 1430 } else { 1431 chars += format.charAt(iFormat); 1432 } 1433 } else { 1434 switch (format.charAt(iFormat)) { 1435 case "d": case "m": case "y": case "@": 1436 chars += "0123456789"; 1437 break; 1438 case "D": case "M": 1439 return null; // Accept anything 1440 case "'": 1441 if (lookAhead("'")) { 1442 chars += "'"; 1443 } else { 1444 literal = true; 1445 } 1446 break; 1447 default: 1448 chars += format.charAt(iFormat); 1449 } 1450 } 1451 } 1452 return chars; 1453 }, 1454 1455 /* Get a setting value, defaulting if necessary. */ 1456 _get: function(inst, name) { 1457 return inst.settings[name] !== undefined ? 1458 inst.settings[name] : this._defaults[name]; 1459 }, 1460 1461 /* Parse existing date and initialise date picker. */ 1462 _setDateFromField: function(inst, noDefault) { 1463 if (inst.input.val() === inst.lastVal) { 1464 return; 1465 } 1466 1467 var dateFormat = this._get(inst, "dateFormat"), 1468 dates = inst.lastVal = inst.input ? inst.input.val() : null, 1469 defaultDate = this._getDefaultDate(inst), 1470 date = defaultDate, 1471 settings = this._getFormatConfig(inst); 1472 1473 try { 1474 date = this.parseDate(dateFormat, dates, settings) || defaultDate; 1475 } catch (event) { 1476 dates = (noDefault ? "" : dates); 1477 } 1478 inst.selectedDay = date.getDate(); 1479 inst.drawMonth = inst.selectedMonth = date.getMonth(); 1480 inst.drawYear = inst.selectedYear = date.getFullYear(); 1481 inst.currentDay = (dates ? date.getDate() : 0); 1482 inst.currentMonth = (dates ? date.getMonth() : 0); 1483 inst.currentYear = (dates ? date.getFullYear() : 0); 1484 this._adjustInstDate(inst); 1485 }, 1486 1487 /* Retrieve the default date shown on opening. */ 1488 _getDefaultDate: function(inst) { 1489 return this._restrictMinMax(inst, 1490 this._determineDate(inst, this._get(inst, "defaultDate"), new Date())); 1491 }, 1492 1493 /* A date may be specified as an exact value or a relative one. */ 1494 _determineDate: function(inst, date, defaultDate) { 1495 var offsetNumeric = function(offset) { 1496 var date = new Date(); 1497 date.setDate(date.getDate() + offset); 1498 return date; 1499 }, 1500 offsetString = function(offset) { 1501 try { 1502 return $.datepicker.parseDate($.datepicker._get(inst, "dateFormat"), 1503 offset, $.datepicker._getFormatConfig(inst)); 1504 } 1505 catch (e) { 1506 // Ignore 1507 } 1508 1509 var date = (offset.toLowerCase().match(/^c/) ? 1510 $.datepicker._getDate(inst) : null) || new Date(), 1511 year = date.getFullYear(), 1512 month = date.getMonth(), 1513 day = date.getDate(), 1514 pattern = /([+\-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g, 1515 matches = pattern.exec(offset); 1516 1517 while (matches) { 1518 switch (matches[2] || "d") { 1519 case "d" : case "D" : 1520 day += parseInt(matches[1],10); break; 1521 case "w" : case "W" : 1522 day += parseInt(matches[1],10) * 7; break; 1523 case "m" : case "M" : 1524 month += parseInt(matches[1],10); 1525 day = Math.min(day, $.datepicker._getDaysInMonth(year, month)); 1526 break; 1527 case "y": case "Y" : 1528 year += parseInt(matches[1],10); 1529 day = Math.min(day, $.datepicker._getDaysInMonth(year, month)); 1530 break; 1531 } 1532 matches = pattern.exec(offset); 1533 } 1534 return new Date(year, month, day); 1535 }, 1536 newDate = (date == null || date === "" ? defaultDate : (typeof date === "string" ? offsetString(date) : 1537 (typeof date === "number" ? (isNaN(date) ? defaultDate : offsetNumeric(date)) : new Date(date.getTime())))); 1538 1539 newDate = (newDate && newDate.toString() === "Invalid Date" ? defaultDate : newDate); 1540 if (newDate) { 1541 newDate.setHours(0); 1542 newDate.setMinutes(0); 1543 newDate.setSeconds(0); 1544 newDate.setMilliseconds(0); 1545 } 1546 return this._daylightSavingAdjust(newDate); 1547 }, 1548 1549 /* Handle switch to/from daylight saving. 1550 * Hours may be non-zero on daylight saving cut-over: 1551 * > 12 when midnight changeover, but then cannot generate 1552 * midnight datetime, so jump to 1AM, otherwise reset. 1553 * @param date (Date) the date to check 1554 * @return (Date) the corrected date 1555 */ 1556 _daylightSavingAdjust: function(date) { 1557 if (!date) { 1558 return null; 1559 } 1560 date.setHours(date.getHours() > 12 ? date.getHours() + 2 : 0); 1561 return date; 1562 }, 1563 1564 /* Set the date(s) directly. */ 1565 _setDate: function(inst, date, noChange) { 1566 var clear = !date, 1567 origMonth = inst.selectedMonth, 1568 origYear = inst.selectedYear, 1569 newDate = this._restrictMinMax(inst, this._determineDate(inst, date, new Date())); 1570 1571 inst.selectedDay = inst.currentDay = newDate.getDate(); 1572 inst.drawMonth = inst.selectedMonth = inst.currentMonth = newDate.getMonth(); 1573 inst.drawYear = inst.selectedYear = inst.currentYear = newDate.getFullYear(); 1574 if ((origMonth !== inst.selectedMonth || origYear !== inst.selectedYear) && !noChange) { 1575 this._notifyChange(inst); 1576 } 1577 this._adjustInstDate(inst); 1578 if (inst.input) { 1579 inst.input.val(clear ? "" : this._formatDate(inst)); 1580 } 1581 }, 1582 1583 /* Retrieve the date(s) directly. */ 1584 _getDate: function(inst) { 1585 var startDate = (!inst.currentYear || (inst.input && inst.input.val() === "") ? null : 1586 this._daylightSavingAdjust(new Date( 1587 inst.currentYear, inst.currentMonth, inst.currentDay))); 1588 return startDate; 1589 }, 1590 1591 /* Attach the onxxx handlers. These are declared statically so 1592 * they work with static code transformers like Caja. 1593 */ 1594 _attachHandlers: function(inst) { 1595 var stepMonths = this._get(inst, "stepMonths"), 1596 id = "#" + inst.id.replace( /\\\\/g, "\\" ); 1597 inst.dpDiv.find("[data-handler]").map(function () { 1598 var handler = { 1599 prev: function () { 1600 $.datepicker._adjustDate(id, -stepMonths, "M"); 1601 }, 1602 next: function () { 1603 $.datepicker._adjustDate(id, +stepMonths, "M"); 1604 }, 1605 hide: function () { 1606 $.datepicker._hideDatepicker(); 1607 }, 1608 today: function () { 1609 $.datepicker._gotoToday(id); 1610 }, 1611 selectDay: function () { 1612 $.datepicker._selectDay(id, +this.getAttribute("data-month"), +this.getAttribute("data-year"), this); 1613 return false; 1614 }, 1615 selectMonth: function () { 1616 $.datepicker._selectMonthYear(id, this, "M"); 1617 return false; 1618 }, 1619 selectYear: function () { 1620 $.datepicker._selectMonthYear(id, this, "Y"); 1621 return false; 1622 } 1623 }; 1624 $(this).bind(this.getAttribute("data-event"), handler[this.getAttribute("data-handler")]); 1625 }); 1626 }, 1627 1628 /* Generate the HTML for the current state of the date picker. */ 1629 _generateHTML: function(inst) { 1630 var maxDraw, prevText, prev, nextText, next, currentText, gotoDate, 1631 controls, buttonPanel, firstDay, showWeek, dayNames, dayNamesMin, 1632 monthNames, monthNamesShort, beforeShowDay, showOtherMonths, 1633 selectOtherMonths, defaultDate, html, dow, row, group, col, selectedDate, 1634 cornerClass, calender, thead, day, daysInMonth, leadDays, curRows, numRows, 1635 printDate, dRow, tbody, daySettings, otherMonth, unselectable, 1636 tempDate = new Date(), 1637 today = this._daylightSavingAdjust( 1638 new Date(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate())), // clear time 1639 isRTL = this._get(inst, "isRTL"), 1640 showButtonPanel = this._get(inst, "showButtonPanel"), 1641 hideIfNoPrevNext = this._get(inst, "hideIfNoPrevNext"), 1642 navigationAsDateFormat = this._get(inst, "navigationAsDateFormat"), 1643 numMonths = this._getNumberOfMonths(inst), 1644 showCurrentAtPos = this._get(inst, "showCurrentAtPos"), 1645 stepMonths = this._get(inst, "stepMonths"), 1646 isMultiMonth = (numMonths[0] !== 1 || numMonths[1] !== 1), 1647 currentDate = this._daylightSavingAdjust((!inst.currentDay ? new Date(9999, 9, 9) : 1648 new Date(inst.currentYear, inst.currentMonth, inst.currentDay))), 1649 minDate = this._getMinMaxDate(inst, "min"), 1650 maxDate = this._getMinMaxDate(inst, "max"), 1651 drawMonth = inst.drawMonth - showCurrentAtPos, 1652 drawYear = inst.drawYear; 1653 1654 if (drawMonth < 0) { 1655 drawMonth += 12; 1656 drawYear--; 1657 } 1658 if (maxDate) { 1659 maxDraw = this._daylightSavingAdjust(new Date(maxDate.getFullYear(), 1660 maxDate.getMonth() - (numMonths[0] * numMonths[1]) + 1, maxDate.getDate())); 1661 maxDraw = (minDate && maxDraw < minDate ? minDate : maxDraw); 1662 while (this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1)) > maxDraw) { 1663 drawMonth--; 1664 if (drawMonth < 0) { 1665 drawMonth = 11; 1666 drawYear--; 1667 } 1668 } 1669 } 1670 inst.drawMonth = drawMonth; 1671 inst.drawYear = drawYear; 1672 1673 prevText = this._get(inst, "prevText"); 1674 prevText = (!navigationAsDateFormat ? prevText : this.formatDate(prevText, 1675 this._daylightSavingAdjust(new Date(drawYear, drawMonth - stepMonths, 1)), 1676 this._getFormatConfig(inst))); 1677 1678 prev = (this._canAdjustMonth(inst, -1, drawYear, drawMonth) ? 1679 "<a class='ui-datepicker-prev ui-corner-all' data-handler='prev' data-event='click'" + 1680 " title='" + prevText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "e" : "w") + "'>" + prevText + "</span></a>" : 1681 (hideIfNoPrevNext ? "" : "<a class='ui-datepicker-prev ui-corner-all ui-state-disabled' title='"+ prevText +"'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "e" : "w") + "'>" + prevText + "</span></a>")); 1682 1683 nextText = this._get(inst, "nextText"); 1684 nextText = (!navigationAsDateFormat ? nextText : this.formatDate(nextText, 1685 this._daylightSavingAdjust(new Date(drawYear, drawMonth + stepMonths, 1)), 1686 this._getFormatConfig(inst))); 1687 1688 next = (this._canAdjustMonth(inst, +1, drawYear, drawMonth) ? 1689 "<a class='ui-datepicker-next ui-corner-all' data-handler='next' data-event='click'" + 1690 " title='" + nextText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "w" : "e") + "'>" + nextText + "</span></a>" : 1691 (hideIfNoPrevNext ? "" : "<a class='ui-datepicker-next ui-corner-all ui-state-disabled' title='"+ nextText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "w" : "e") + "'>" + nextText + "</span></a>")); 1692 1693 currentText = this._get(inst, "currentText"); 1694 gotoDate = (this._get(inst, "gotoCurrent") && inst.currentDay ? currentDate : today); 1695 currentText = (!navigationAsDateFormat ? currentText : 1696 this.formatDate(currentText, gotoDate, this._getFormatConfig(inst))); 1697 1698 controls = (!inst.inline ? "<button type='button' class='ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all' data-handler='hide' data-event='click'>" + 1699 this._get(inst, "closeText") + "</button>" : ""); 1700 1701 buttonPanel = (showButtonPanel) ? "<div class='ui-datepicker-buttonpane ui-widget-content'>" + (isRTL ? controls : "") + 1702 (this._isInRange(inst, gotoDate) ? "<button type='button' class='ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all' data-handler='today' data-event='click'" + 1703 ">" + currentText + "</button>" : "") + (isRTL ? "" : controls) + "</div>" : ""; 1704 1705 firstDay = parseInt(this._get(inst, "firstDay"),10); 1706 firstDay = (isNaN(firstDay) ? 0 : firstDay); 1707 1708 showWeek = this._get(inst, "showWeek"); 1709 dayNames = this._get(inst, "dayNames"); 1710 dayNamesMin = this._get(inst, "dayNamesMin"); 1711 monthNames = this._get(inst, "monthNames"); 1712 monthNamesShort = this._get(inst, "monthNamesShort"); 1713 beforeShowDay = this._get(inst, "beforeShowDay"); 1714 showOtherMonths = this._get(inst, "showOtherMonths"); 1715 selectOtherMonths = this._get(inst, "selectOtherMonths"); 1716 defaultDate = this._getDefaultDate(inst); 1717 html = ""; 1718 dow; 1719 for (row = 0; row < numMonths[0]; row++) { 1720 group = ""; 1721 this.maxRows = 4; 1722 for (col = 0; col < numMonths[1]; col++) { 1723 selectedDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, inst.selectedDay)); 1724 cornerClass = " ui-corner-all"; 1725 calender = ""; 1726 if (isMultiMonth) { 1727 calender += "<div class='ui-datepicker-group"; 1728 if (numMonths[1] > 1) { 1729 switch (col) { 1730 case 0: calender += " ui-datepicker-group-first"; 1731 cornerClass = " ui-corner-" + (isRTL ? "right" : "left"); break; 1732 case numMonths[1]-1: calender += " ui-datepicker-group-last"; 1733 cornerClass = " ui-corner-" + (isRTL ? "left" : "right"); break; 1734 default: calender += " ui-datepicker-group-middle"; cornerClass = ""; break; 1735 } 1736 } 1737 calender += "'>"; 1738 } 1739 calender += "<div class='ui-datepicker-header ui-widget-header ui-helper-clearfix" + cornerClass + "'>" + 1740 (/all|left/.test(cornerClass) && row === 0 ? (isRTL ? next : prev) : "") + 1741 (/all|right/.test(cornerClass) && row === 0 ? (isRTL ? prev : next) : "") + 1742 this._generateMonthYearHeader(inst, drawMonth, drawYear, minDate, maxDate, 1743 row > 0 || col > 0, monthNames, monthNamesShort) + // draw month headers 1744 "</div><table class='ui-datepicker-calendar'><thead>" + 1745 "<tr>"; 1746 thead = (showWeek ? "<th class='ui-datepicker-week-col'>" + this._get(inst, "weekHeader") + "</th>" : ""); 1747 for (dow = 0; dow < 7; dow++) { // days of the week 1748 day = (dow + firstDay) % 7; 1749 thead += "<th scope='col'" + ((dow + firstDay + 6) % 7 >= 5 ? " class='ui-datepicker-week-end'" : "") + ">" + 1750 "<span title='" + dayNames[day] + "'>" + dayNamesMin[day] + "</span></th>"; 1751 } 1752 calender += thead + "</tr></thead><tbody>"; 1753 daysInMonth = this._getDaysInMonth(drawYear, drawMonth); 1754 if (drawYear === inst.selectedYear && drawMonth === inst.selectedMonth) { 1755 inst.selectedDay = Math.min(inst.selectedDay, daysInMonth); 1756 } 1757 leadDays = (this._getFirstDayOfMonth(drawYear, drawMonth) - firstDay + 7) % 7; 1758 curRows = Math.ceil((leadDays + daysInMonth) / 7); // calculate the number of rows to generate 1759 numRows = (isMultiMonth ? this.maxRows > curRows ? this.maxRows : curRows : curRows); //If multiple months, use the higher number of rows (see #7043) 1760 this.maxRows = numRows; 1761 printDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1 - leadDays)); 1762 for (dRow = 0; dRow < numRows; dRow++) { // create date picker rows 1763 calender += "<tr>"; 1764 tbody = (!showWeek ? "" : "<td class='ui-datepicker-week-col'>" + 1765 this._get(inst, "calculateWeek")(printDate) + "</td>"); 1766 for (dow = 0; dow < 7; dow++) { // create date picker days 1767 daySettings = (beforeShowDay ? 1768 beforeShowDay.apply((inst.input ? inst.input[0] : null), [printDate]) : [true, ""]); 1769 otherMonth = (printDate.getMonth() !== drawMonth); 1770 unselectable = (otherMonth && !selectOtherMonths) || !daySettings[0] || 1771 (minDate && printDate < minDate) || (maxDate && printDate > maxDate); 1772 tbody += "<td class='" + 1773 ((dow + firstDay + 6) % 7 >= 5 ? " ui-datepicker-week-end" : "") + // highlight weekends 1774 (otherMonth ? " ui-datepicker-other-month" : "") + // highlight days from other months 1775 ((printDate.getTime() === selectedDate.getTime() && drawMonth === inst.selectedMonth && inst._keyEvent) || // user pressed key 1776 (defaultDate.getTime() === printDate.getTime() && defaultDate.getTime() === selectedDate.getTime()) ? 1777 // or defaultDate is current printedDate and defaultDate is selectedDate 1778 " " + this._dayOverClass : "") + // highlight selected day 1779 (unselectable ? " " + this._unselectableClass + " ui-state-disabled": "") + // highlight unselectable days 1780 (otherMonth && !showOtherMonths ? "" : " " + daySettings[1] + // highlight custom dates 1781 (printDate.getTime() === currentDate.getTime() ? " " + this._currentClass : "") + // highlight selected day 1782 (printDate.getTime() === today.getTime() ? " ui-datepicker-today" : "")) + "'" + // highlight today (if different) 1783 ((!otherMonth || showOtherMonths) && daySettings[2] ? " title='" + daySettings[2].replace(/'/g, "'") + "'" : "") + // cell title 1784 (unselectable ? "" : " data-handler='selectDay' data-event='click' data-month='" + printDate.getMonth() + "' data-year='" + printDate.getFullYear() + "'") + ">" + // actions 1785 (otherMonth && !showOtherMonths ? " " : // display for other months 1786 (unselectable ? "<span class='ui-state-default'>" + printDate.getDate() + "</span>" : "<a class='ui-state-default" + 1787 (printDate.getTime() === today.getTime() ? " ui-state-highlight" : "") + 1788 (printDate.getTime() === currentDate.getTime() ? " ui-state-active" : "") + // highlight selected day 1789 (otherMonth ? " ui-priority-secondary" : "") + // distinguish dates from other months 1790 "' href='#'>" + printDate.getDate() + "</a>")) + "</td>"; // display selectable date 1791 printDate.setDate(printDate.getDate() + 1); 1792 printDate = this._daylightSavingAdjust(printDate); 1793 } 1794 calender += tbody + "</tr>"; 1795 } 1796 drawMonth++; 1797 if (drawMonth > 11) { 1798 drawMonth = 0; 1799 drawYear++; 1800 } 1801 calender += "</tbody></table>" + (isMultiMonth ? "</div>" + 1802 ((numMonths[0] > 0 && col === numMonths[1]-1) ? "<div class='ui-datepicker-row-break'></div>" : "") : ""); 1803 group += calender; 1804 } 1805 html += group; 1806 } 1807 html += buttonPanel; 1808 inst._keyEvent = false; 1809 return html; 1810 }, 1811 1812 /* Generate the month and year header. */ 1813 _generateMonthYearHeader: function(inst, drawMonth, drawYear, minDate, maxDate, 1814 secondary, monthNames, monthNamesShort) { 1815 1816 var inMinYear, inMaxYear, month, years, thisYear, determineYear, year, endYear, 1817 changeMonth = this._get(inst, "changeMonth"), 1818 changeYear = this._get(inst, "changeYear"), 1819 showMonthAfterYear = this._get(inst, "showMonthAfterYear"), 1820 html = "<div class='ui-datepicker-title'>", 1821 monthHtml = ""; 1822 1823 // month selection 1824 if (secondary || !changeMonth) { 1825 monthHtml += "<span class='ui-datepicker-month'>" + monthNames[drawMonth] + "</span>"; 1826 } else { 1827 inMinYear = (minDate && minDate.getFullYear() === drawYear); 1828 inMaxYear = (maxDate && maxDate.getFullYear() === drawYear); 1829 monthHtml += "<select class='ui-datepicker-month' data-handler='selectMonth' data-event='change'>"; 1830 for ( month = 0; month < 12; month++) { 1831 if ((!inMinYear || month >= minDate.getMonth()) && (!inMaxYear || month <= maxDate.getMonth())) { 1832 monthHtml += "<option value='" + month + "'" + 1833 (month === drawMonth ? " selected='selected'" : "") + 1834 ">" + monthNamesShort[month] + "</option>"; 1835 } 1836 } 1837 monthHtml += "</select>"; 1838 } 1839 1840 if (!showMonthAfterYear) { 1841 html += monthHtml + (secondary || !(changeMonth && changeYear) ? " " : ""); 1842 } 1843 1844 // year selection 1845 if ( !inst.yearshtml ) { 1846 inst.yearshtml = ""; 1847 if (secondary || !changeYear) { 1848 html += "<span class='ui-datepicker-year'>" + drawYear + "</span>"; 1849 } else { 1850 // determine range of years to display 1851 years = this._get(inst, "yearRange").split(":"); 1852 thisYear = new Date().getFullYear(); 1853 determineYear = function(value) { 1854 var year = (value.match(/c[+\-].*/) ? drawYear + parseInt(value.substring(1), 10) : 1855 (value.match(/[+\-].*/) ? thisYear + parseInt(value, 10) : 1856 parseInt(value, 10))); 1857 return (isNaN(year) ? thisYear : year); 1858 }; 1859 year = determineYear(years[0]); 1860 endYear = Math.max(year, determineYear(years[1] || "")); 1861 year = (minDate ? Math.max(year, minDate.getFullYear()) : year); 1862 endYear = (maxDate ? Math.min(endYear, maxDate.getFullYear()) : endYear); 1863 inst.yearshtml += "<select class='ui-datepicker-year' data-handler='selectYear' data-event='change'>"; 1864 for (; year <= endYear; year++) { 1865 inst.yearshtml += "<option value='" + year + "'" + 1866 (year === drawYear ? " selected='selected'" : "") + 1867 ">" + year + "</option>"; 1868 } 1869 inst.yearshtml += "</select>"; 1870 1871 html += inst.yearshtml; 1872 inst.yearshtml = null; 1873 } 1874 } 1875 1876 html += this._get(inst, "yearSuffix"); 1877 if (showMonthAfterYear) { 1878 html += (secondary || !(changeMonth && changeYear) ? " " : "") + monthHtml; 1879 } 1880 html += "</div>"; // Close datepicker_header 1881 return html; 1882 }, 1883 1884 /* Adjust one of the date sub-fields. */ 1885 _adjustInstDate: function(inst, offset, period) { 1886 var year = inst.drawYear + (period === "Y" ? offset : 0), 1887 month = inst.drawMonth + (period === "M" ? offset : 0), 1888 day = Math.min(inst.selectedDay, this._getDaysInMonth(year, month)) + (period === "D" ? offset : 0), 1889 date = this._restrictMinMax(inst, this._daylightSavingAdjust(new Date(year, month, day))); 1890 1891 inst.selectedDay = date.getDate(); 1892 inst.drawMonth = inst.selectedMonth = date.getMonth(); 1893 inst.drawYear = inst.selectedYear = date.getFullYear(); 1894 if (period === "M" || period === "Y") { 1895 this._notifyChange(inst); 1896 } 1897 }, 1898 1899 /* Ensure a date is within any min/max bounds. */ 1900 _restrictMinMax: function(inst, date) { 1901 var minDate = this._getMinMaxDate(inst, "min"), 1902 maxDate = this._getMinMaxDate(inst, "max"), 1903 newDate = (minDate && date < minDate ? minDate : date); 1904 return (maxDate && newDate > maxDate ? maxDate : newDate); 1905 }, 1906 1907 /* Notify change of month/year. */ 1908 _notifyChange: function(inst) { 1909 var onChange = this._get(inst, "onChangeMonthYear"); 1910 if (onChange) { 1911 onChange.apply((inst.input ? inst.input[0] : null), 1912 [inst.selectedYear, inst.selectedMonth + 1, inst]); 1913 } 1914 }, 1915 1916 /* Determine the number of months to show. */ 1917 _getNumberOfMonths: function(inst) { 1918 var numMonths = this._get(inst, "numberOfMonths"); 1919 return (numMonths == null ? [1, 1] : (typeof numMonths === "number" ? [1, numMonths] : numMonths)); 1920 }, 1921 1922 /* Determine the current maximum date - ensure no time components are set. */ 1923 _getMinMaxDate: function(inst, minMax) { 1924 return this._determineDate(inst, this._get(inst, minMax + "Date"), null); 1925 }, 1926 1927 /* Find the number of days in a given month. */ 1928 _getDaysInMonth: function(year, month) { 1929 return 32 - this._daylightSavingAdjust(new Date(year, month, 32)).getDate(); 1930 }, 1931 1932 /* Find the day of the week of the first of a month. */ 1933 _getFirstDayOfMonth: function(year, month) { 1934 return new Date(year, month, 1).getDay(); 1935 }, 1936 1937 /* Determines if we should allow a "next/prev" month display change. */ 1938 _canAdjustMonth: function(inst, offset, curYear, curMonth) { 1939 var numMonths = this._getNumberOfMonths(inst), 1940 date = this._daylightSavingAdjust(new Date(curYear, 1941 curMonth + (offset < 0 ? offset : numMonths[0] * numMonths[1]), 1)); 1942 1943 if (offset < 0) { 1944 date.setDate(this._getDaysInMonth(date.getFullYear(), date.getMonth())); 1945 } 1946 return this._isInRange(inst, date); 1947 }, 1948 1949 /* Is the given date in the accepted range? */ 1950 _isInRange: function(inst, date) { 1951 var yearSplit, currentYear, 1952 minDate = this._getMinMaxDate(inst, "min"), 1953 maxDate = this._getMinMaxDate(inst, "max"), 1954 minYear = null, 1955 maxYear = null, 1956 years = this._get(inst, "yearRange"); 1957 if (years){ 1958 yearSplit = years.split(":"); 1959 currentYear = new Date().getFullYear(); 1960 minYear = parseInt(yearSplit[0], 10); 1961 maxYear = parseInt(yearSplit[1], 10); 1962 if ( yearSplit[0].match(/[+\-].*/) ) { 1963 minYear += currentYear; 1964 } 1965 if ( yearSplit[1].match(/[+\-].*/) ) { 1966 maxYear += currentYear; 1967 } 1968 } 1969 1970 return ((!minDate || date.getTime() >= minDate.getTime()) && 1971 (!maxDate || date.getTime() <= maxDate.getTime()) && 1972 (!minYear || date.getFullYear() >= minYear) && 1973 (!maxYear || date.getFullYear() <= maxYear)); 1974 }, 1975 1976 /* Provide the configuration settings for formatting/parsing. */ 1977 _getFormatConfig: function(inst) { 1978 var shortYearCutoff = this._get(inst, "shortYearCutoff"); 1979 shortYearCutoff = (typeof shortYearCutoff !== "string" ? shortYearCutoff : 1980 new Date().getFullYear() % 100 + parseInt(shortYearCutoff, 10)); 1981 return {shortYearCutoff: shortYearCutoff, 1982 dayNamesShort: this._get(inst, "dayNamesShort"), dayNames: this._get(inst, "dayNames"), 1983 monthNamesShort: this._get(inst, "monthNamesShort"), monthNames: this._get(inst, "monthNames")}; 1984 }, 1985 1986 /* Format the given date for display. */ 1987 _formatDate: function(inst, day, month, year) { 1988 if (!day) { 1989 inst.currentDay = inst.selectedDay; 1990 inst.currentMonth = inst.selectedMonth; 1991 inst.currentYear = inst.selectedYear; 1992 } 1993 var date = (day ? (typeof day === "object" ? day : 1994 this._daylightSavingAdjust(new Date(year, month, day))) : 1995 this._daylightSavingAdjust(new Date(inst.currentYear, inst.currentMonth, inst.currentDay))); 1996 return this.formatDate(this._get(inst, "dateFormat"), date, this._getFormatConfig(inst)); 1997 } 1998}); 1999 2000/* 2001 * Bind hover events for datepicker elements. 2002 * Done via delegate so the binding only occurs once in the lifetime of the parent div. 2003 * Global datepicker_instActive, set by _updateDatepicker allows the handlers to find their way back to the active picker. 2004 */ 2005function datepicker_bindHover(dpDiv) { 2006 var selector = "button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a"; 2007 return dpDiv.delegate(selector, "mouseout", function() { 2008 $(this).removeClass("ui-state-hover"); 2009 if (this.className.indexOf("ui-datepicker-prev") !== -1) { 2010 $(this).removeClass("ui-datepicker-prev-hover"); 2011 } 2012 if (this.className.indexOf("ui-datepicker-next") !== -1) { 2013 $(this).removeClass("ui-datepicker-next-hover"); 2014 } 2015 }) 2016 .delegate( selector, "mouseover", datepicker_handleMouseover ); 2017} 2018 2019function datepicker_handleMouseover() { 2020 if (!$.datepicker._isDisabledDatepicker( datepicker_instActive.inline? datepicker_instActive.dpDiv.parent()[0] : datepicker_instActive.input[0])) { 2021 $(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"); 2022 $(this).addClass("ui-state-hover"); 2023 if (this.className.indexOf("ui-datepicker-prev") !== -1) { 2024 $(this).addClass("ui-datepicker-prev-hover"); 2025 } 2026 if (this.className.indexOf("ui-datepicker-next") !== -1) { 2027 $(this).addClass("ui-datepicker-next-hover"); 2028 } 2029 } 2030} 2031 2032/* jQuery extend now ignores nulls! */ 2033function datepicker_extendRemove(target, props) { 2034 $.extend(target, props); 2035 for (var name in props) { 2036 if (props[name] == null) { 2037 target[name] = props[name]; 2038 } 2039 } 2040 return target; 2041} 2042 2043/* Invoke the datepicker functionality. 2044 @param options string - a command, optionally followed by additional parameters or 2045 Object - settings for attaching new datepicker functionality 2046 @return jQuery object */ 2047$.fn.datepicker = function(options){ 2048 2049 /* Verify an empty collection wasn't passed - Fixes #6976 */ 2050 if ( !this.length ) { 2051 return this; 2052 } 2053 2054 /* Initialise the date picker. */ 2055 if (!$.datepicker.initialized) { 2056 $(document).mousedown($.datepicker._checkExternalClick); 2057 $.datepicker.initialized = true; 2058 } 2059 2060 /* Append datepicker main container to body if not exist. */ 2061 if ($("#"+$.datepicker._mainDivId).length === 0) { 2062 $("body").append($.datepicker.dpDiv); 2063 } 2064 2065 var otherArgs = Array.prototype.slice.call(arguments, 1); 2066 if (typeof options === "string" && (options === "isDisabled" || options === "getDate" || options === "widget")) { 2067 return $.datepicker["_" + options + "Datepicker"]. 2068 apply($.datepicker, [this[0]].concat(otherArgs)); 2069 } 2070 if (options === "option" && arguments.length === 2 && typeof arguments[1] === "string") { 2071 return $.datepicker["_" + options + "Datepicker"]. 2072 apply($.datepicker, [this[0]].concat(otherArgs)); 2073 } 2074 return this.each(function() { 2075 typeof options === "string" ? 2076 $.datepicker["_" + options + "Datepicker"]. 2077 apply($.datepicker, [this].concat(otherArgs)) : 2078 $.datepicker._attachDatepicker(this, options); 2079 }); 2080}; 2081 2082$.datepicker = new Datepicker(); // singleton instance 2083$.datepicker.initialized = false; 2084$.datepicker.uuid = new Date().getTime(); 2085$.datepicker.version = "1.11.4"; 2086
2087return $.datepicker; 2088 2089})); 2090 2091/*! 2092 * Modernizr v2.8.3 2093 * www.modernizr.com 2094 * 2095 * Copyright (c) Faruk Ates, Paul Irish, Alex Sexton 2096 * Available under the BSD and MIT licenses: www.modernizr.com/license/ 2097 */ 2098 2099/* 2100 * Modernizr tests which native CSS3 and HTML5 features are available in 2101 * the current UA and makes the results available to you in two ways: 2102 * as properties on a global Modernizr object, and as classes on the 2103 * <html> element. This information allows you to progressively enhance 2104 * your pages with a granular level of control over the experience. 2105 * 2106 * Modernizr has an optional (not included) conditional resource loader 2107 * called Modernizr.load(), based on Yepnope.js (yepnopejs.com). 2108 * To get a build that includes Modernizr.load(), as well as choosing 2109 * which tests to include, go to www.modernizr.com/download/ 2110 * 2111 * Authors Faruk Ates, Paul Irish, Alex Sexton 2112 * Contributors Ryan Seddon, Ben Alman 2113 */ 2114 2115window.Modernizr = (function( window, document, undefined ) { 2116 2117 var version = '2.8.3', 2118 2119 Modernizr = {}, 2120 2121 /*>>cssclasses*/ 2122 // option for enabling the HTML classes to be added 2123 enableClasses = true, 2124 /*>>cssclasses*/ 2125 2126 docElement = document.documentElement, 2127 2128 /** 2129 * Create our "modernizr" element that we do most feature tests on. 2130 */ 2131 mod = 'modernizr', 2132 modElem = document.createElement(mod), 2133 mStyle = modElem.style, 2134 2135 /** 2136 * Create the input element for various Web Forms feature tests. 2137 */ 2138 inputElem /*>>inputelem*/ = document.createElement('input') /*>>inputelem*/ , 2139 2140 /*>>smile*/ 2141 smile = ':)', 2142 /*>>smile*/ 2143 2144 toString = {}.toString, 2145 2146 // TODO :: make the prefixes more granular 2147 /*>>prefixes*/ 2148 // List of property values to set for css tests. See ticket #21 2149 prefixes = ' -webkit- -moz- -o- -ms- '.split(' '), 2150 /*>>prefixes*/ 2151 2152 /*>>domprefixes*/ 2153 // Following spec is to expose vendor-specific style properties as: 2154 // elem.style.WebkitBorderRadius 2155 // and the following would be incorrect: 2156 // elem.style.webkitBorderRadius 2157 2158 // Webkit ghosts their properties in lowercase but Opera & Moz do not. 2159 // Microsoft uses a lowercase `ms` instead of the correct `Ms` in IE8+ 2160 // erik.eae.net/archives/2008/03/10/21.48.10/ 2161 2162 // More here: github.com/Modernizr/Modernizr/issues/issue/21 2163 omPrefixes = 'Webkit Moz O ms', 2164 2165 cssomPrefixes = omPrefixes.split(' '), 2166 2167 domPrefixes = omPrefixes.toLowerCase().split(' '), 2168 /*>>domprefixes*/ 2169 2170 /*>>ns*/ 2171 ns = {'svg': 'http://www.w3.org/2000/svg'}, 2172 /*>>ns*/ 2173 2174 tests = {}, 2175 inputs = {}, 2176 attrs = {}, 2177 2178 classes = [], 2179 2180 slice = classes.slice, 2181 2182 featureName, // used in testing loop 2183 2184 2185 /*>>teststyles*/ 2186 // Inject element with style element and some CSS rules 2187 injectElementWithStyles = function( rule, callback, nodes, testnames ) { 2188 2189 var style, ret, node, docOverflow, 2190 div = document.createElement('div'), 2191 // After page load injecting a fake body doesn't work so check if body exists 2192 body = document.body, 2193 // IE6 and 7 won't return offsetWidth or offsetHeight unless it's in the body element, so we fake it. 2194 fakeBody = body || document.createElement('body'); 2195 2196 if ( parseInt(nodes, 10) ) { 2197 // In order not to give false positives we create a node for each test 2198 // This also allows the method to scale for unspecified uses 2199 while ( nodes-- ) { 2200 node = document.createElement('div'); 2201 node.id = testnames ? testnames[nodes] : mod + (nodes + 1); 2202 div.appendChild(node); 2203 } 2204 } 2205 2206 // <style> elements in IE6-9 are considered 'NoScope' elements and therefore will be removed 2207 // when injected with innerHTML. To get around this you need to prepend the 'NoScope' element 2208 // with a 'scoped' element, in our case the soft-hyphen entity as it won't mess with our measurements. 2209 // msdn.microsoft.com/en-us/library/ms533897%28VS.85%29.aspx 2210 // Documents served as xml will throw if using ­ so use xml friendly encoded version. See issue #277 2211 style = ['­','<style id="s', mod, '">', rule, '</style>'].join(''); 2212 div.id = mod; 2213 // IE6 will false positive on some tests due to the style element inside the test div somehow interfer
2213ing offsetHeight, so insert it into body or fakebody. 2214 // Opera will act all quirky when injecting elements in documentElement when page is served as xml, needs fakebody too. #270 2215 (body ? div : fakeBody).innerHTML += style; 2216 fakeBody.appendChild(div); 2217 if ( !body ) { 2218 //avoid crashing IE8, if background image is used 2219 fakeBody.style.background = ''; 2220 //Safari 5.13/5.1.4 OSX stops loading if ::-webkit-scrollbar is used and scrollbars are visible 2221 fakeBody.style.overflow = 'hidden'; 2222 docOverflow = docElement.style.overflow; 2223 docElement.style.overflow = 'hidden'; 2224 docElement.appendChild(fakeBody); 2225 } 2226 2227 ret = callback(div, rule); 2228 // If this is done after page load we don't want to remove the body so check if body exists 2229 if ( !body ) { 2230 fakeBody.parentNode.removeChild(fakeBody); 2231 docElement.style.overflow = docOverflow; 2232 } else { 2233 div.parentNode.removeChild(div); 2234 } 2235 2236 return !!ret; 2237 2238 }, 2239 /*>>teststyles*/ 2240 2241 /*>>mq*/ 2242 // adapted from matchMedia polyfill 2243 // by Scott Jehl and Paul Irish 2244 // gist.github.com/786768 2245 testMediaQuery = function( mq ) { 2246 2247 var matchMedia = window.matchMedia || window.msMatchMedia; 2248 if ( matchMedia ) { 2249 return matchMedia(mq) && matchMedia(mq).matches || false; 2250 } 2251 2252 var bool; 2253 2254 injectElementWithStyles('@media ' + mq + ' { #' + mod + ' { position: absolute; } }', function( node ) { 2255 bool = (window.getComputedStyle ? 2256 getComputedStyle(node, null) : 2257 node.currentStyle)['position'] == 'absolute'; 2258 }); 2259 2260 return bool; 2261 2262 }, 2263 /*>>mq*/ 2264 2265 2266 /*>>hasevent*/ 2267 // 2268 // isEventSupported determines if a given element supports the given event 2269 // kangax.github.com/iseventsupported/ 2270 // 2271 // The following results are known incorrects: 2272 // Modernizr.hasEvent("webkitTransitionEnd", elem) // false negative 2273 // Modernizr.hasEvent("textInput") // in Webkit. github.com/Modernizr/Modernizr/issues/333 2274 // ... 2275 isEventSupported = (function() { 2276 2277 var TAGNAMES = { 2278 'select': 'input', 'change': 'input', 2279 'submit': 'form', 'reset': 'form', 2280 'error': 'img', 'load': 'img', 'abort': 'img' 2281 }; 2282 2283 function isEventSupported( eventName, element ) { 2284 2285 element = element || document.createElement(TAGNAMES[eventName] || 'div'); 2286 eventName = 'on' + eventName; 2287 2288 // When using `setAttribute`, IE skips "unload", WebKit skips "unload" and "resize", whereas `in` "catches" those 2289 var isSupported = eventName in element; 2290 2291 if ( !isSupported ) { 2292 // If it has no `setAttribute` (i.e. doesn't implement Node interface), try generic element 2293 if ( !element.setAttribute ) { 2294 element = document.createElement('div'); 2295 } 2296 if ( element.setAttribute && element.removeAttribute ) { 2297 element.setAttribute(eventName, ''); 2298 isSupported = is(element[eventName], 'function'); 2299 2300 // If property was created, "remove it" (by setting value to `undefined`) 2301 if ( !is(element[eventName], 'undefined') ) { 2302 element[eventName] = undefined; 2303 } 2304 element.removeAttribute(eventName); 2305 } 2306 } 2307 2308 element = null; 2309 return isSupported; 2310 } 2311 return isEventSupported; 2312 })(), 2313 /*>>hasevent*/ 2314 2315 // TODO :: Add flag for hasownprop ? didn't last time 2316 2317 // hasOwnProperty shim by kangax needed for Safari 2.0 support 2318 _hasOwnProperty = ({}).hasOwnProperty, hasOwnProp; 2319 2320 if ( !is(_hasOwnProperty, 'undefined') && !is(_hasOwnProperty.call, 'undefined') ) { 2321 hasOwnProp = function (object, property) { 2322 return _hasOwnProperty.call(object, property); 2323 }; 2324 } 2325 else { 2326 hasOwnProp = function (object, property) { /* yes, this can give false positives/negatives, but most of the time we don't care about those */ 2327 return ((property in object) && is(object.constructor.prototype[property], 'undefined')); 2328 }; 2329 } 2330 2331 // Adapted from ES5-shim https://github.com/kriskowal/es5-shim/blob/master/es5-shim.js 2332 // es5.github.com/#x15.3.4.5 2333 2334 if (!Function.prototype.bind) { 2335 Function.prototype.bind = function bind(that) { 2336 2337 var target = this; 2338 2339 if (typeof target != "function") { 2340 throw new TypeError(); 2341 } 2342 2343 var args = slice.call(arguments, 1), 2344 bound = function () { 2345 2346 if (this instanceof bound) { 2347 2348 var F = function(){}; 2349 F.prototype = target.prototype; 2350 var self = new F(); 2351 2352 var result = target.apply( 2353 self, 2354 args.concat(slice.call(arguments)) 2355 ); 2356 if (Object(result) === result) { 2357 return result; 2358 } 2359 return self; 2360 2361 } else { 2362 2363 return target.apply( 2364 that, 2365 args.concat(slice.call(arguments)) 2366 ); 2367 2368 } 2369 2370 }; 2371 2372 return bound; 2373 }; 2374 } 2375 2376 /** 2377 * setCss applies given styles to the Modernizr DOM node. 2378 */ 2379 function setCss( str ) { 2380 mStyle.cssText = str; 2381 } 2382 2383 /** 2384 * setCssAll extrapolates all vendor-specific css strings. 2385 */ 2386 function setCssAll( str1, str2 ) { 2387 return setCss(prefixes.join(str1 + ';') + ( str2 || '' )); 2388 } 2389 2390 /** 2391 * is returns a boolean for if typeof obj is exactly type. 2392 */ 2393 function is( obj, type ) { 2394 return typeof obj === type; 2395 } 2396 2397 /** 2398 * contains returns a boolean for if substr is found within str. 2399 */ 2400 function contains( str, substr ) {
2401 return !!~('' + str).indexOf(substr); 2402 } 2403 2404 /*>>testprop*/ 2405 2406 // testProps is a generic CSS / DOM property test. 2407 2408 // In testing support for a given CSS property, it's legit to test: 2409 // `elem.style[styleName] !== undefined` 2410 // If the property is supported it will return an empty string, 2411 // if unsupported it will return undefined. 2412 2413 // We'll take advantage of this quick test and skip setting a style 2414 // on our modernizr element, but instead just testing undefined vs 2415 // empty string. 2416 2417 // Because the testing of the CSS property names (with "-", as 2418 // opposed to the camelCase DOM properties) is non-portable and 2419 // non-standard but works in WebKit and IE (but not Gecko or Opera), 2420 // we explicitly reject properties with dashes so that authors 2421 // developing in WebKit or IE first don't end up with 2422 // browser-specific content by accident. 2423 2424 function testProps( props, prefixed ) { 2425 for ( var i in props ) { 2426 var prop = props[i]; 2427 if ( !contains(prop, "-") && mStyle[prop] !== undefined ) { 2428 return prefixed == 'pfx' ? prop : true; 2429 } 2430 } 2431 return false; 2432 } 2433 /*>>testprop*/ 2434 2435 // TODO :: add testDOMProps 2436 /** 2437 * testDOMProps is a generic DOM property test; if a browser supports 2438 * a certain property, it won't return undefined for it. 2439 */ 2440 function testDOMProps( props, obj, elem ) { 2441 for ( var i in props ) { 2442 var item = obj[props[i]]; 2443 if ( item !== undefined) { 2444 2445 // return the property name as a string 2446 if (elem === false) return props[i]; 2447 2448 // let's bind a function 2449 if (is(item, 'function')){ 2450 // default to autobind unless override 2451 return item.bind(elem || obj); 2452 } 2453 2454 // return the unbound function or obj or value 2455 return item; 2456 } 2457 } 2458 return false; 2459 } 2460 2461 /*>>testallprops*/ 2462 /** 2463 * testPropsAll tests a list of DOM properties we want to check against. 2464 * We specify literally ALL possible (known and/or likely) properties on 2465 * the element including the non-vendor prefixed one, for forward- 2466 * compatibility. 2467 */ 2468 function testPropsAll( prop, prefixed, elem ) { 2469 2470 var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1), 2471 props = (prop + ' ' + cssomPrefixes.join(ucProp + ' ') + ucProp).split(' '); 2472 2473 // did they call .prefixed('boxSizing') or are we just testing a prop? 2474 if(is(prefixed, "string") || is(prefixed, "undefined")) { 2475 return testProps(props, prefixed); 2476 2477 // otherwise, they called .prefixed('requestAnimationFrame', window[, elem]) 2478 } else { 2479 props = (prop + ' ' + (domPrefixes).join(ucProp + ' ') + ucProp).split(' '); 2480 return testDOMProps(props, prefixed, elem); 2481 } 2482 } 2483 /*>>testallprops*/ 2484 2485 2486 /** 2487 * Tests 2488 * ----- 2489 */ 2490 2491 // The *new* flexbox 2492 // dev.w3.org/csswg/css3-flexbox 2493 2494 tests['flexbox'] = function() { 2495 return testPropsAll('flexWrap'); 2496 }; 2497 2498 // The *old* flexbox 2499 // www.w3.org/TR/2009/WD-css3-flexbox-20090723/ 2500 2501 tests['flexboxlegacy'] = function() { 2502 return testPropsAll('boxDirection'); 2503 }; 2504 2505 // On the S60 and BB Storm, getContext exists, but always returns undefined 2506 // so we actually have to call getContext() to verify 2507 // github.com/Modernizr/Modernizr/issues/issue/97/ 2508 2509 tests['canvas'] = function() { 2510 var elem = document.createElement('canvas'); 2511 return !!(elem.getContext && elem.getContext('2d')); 2512 }; 2513 2514 tests['canvastext'] = function() { 2515 return !!(Modernizr['canvas'] && is(document.createElement('canvas').getContext('2d').fillText, 'function')); 2516 }; 2517 2518 // webk.it/70117 is tracking a legit WebGL feature detect proposal 2519 2520 // We do a soft detect which may false positive in order to avoid 2521 // an expensive context creation: bugzil.la/732441 2522 2523 tests['webgl'] = function() { 2524 return !!window.WebGLRenderingContext; 2525 }; 2526 2527 /* 2528 * The Modernizr.touch test only indicates if the browser supports 2529 * touch events, which does not necessarily reflect a touchscreen 2530 * device, as evidenced by tablets running Windows 7 or, alas, 2531 * the Palm Pre / WebOS (touch) phones. 2532 * 2533 * Additionally, Chrome (desktop) used to lie about its support on this, 2534 * but that has since been rectified: crbug.com/36415 2535 * 2536 * We also test for Firefox 4 Multitouch Support. 2537 * 2538 * For more info, see: modernizr.github.com/Modernizr/touch.html 2539 */ 2540 2541 tests['touch'] = function() { 2542 var bool; 2543 2544 if(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) { 2545 bool = true; 2546 } else { 2547 injectElementWithStyles(['@media (',prefixes.join('touch-enabled),('),mod,')','{#modernizr{top:9px;position:absolute}}'].join(''), function( node ) { 2548 bool = node.offsetTop === 9; 2549 }); 2550 } 2551 2552 return bool; 2553 }; 2554 2555 2556 // geolocation is often considered a trivial feature detect... 2557 // Turns out, it's quite tricky to get right: 2558 // 2559 // Using !!navigator.geolocation does two things we don't want. It: 2560 // 1. Leaks memory in IE9: github.com/Modernizr/Modernizr/issues/513 2561 // 2. Disables page caching in WebKit: webk.it/43956 2562 // 2563 // Meanwhile, in Firefox < 8, an about:config setting could expose 2564 // a false positive that would throw an exception: bugzil.la/688158 2565 2566 tests['geolocation'] = function() { 2567 return 'geolocation' in navigator; 2568 }; 2569 2570 2571 tests['postmessage'] = function() { 2572 return !!window.postMessage; 2573 }; 2574 2575 2576 // Chrome incognito mode used to throw an exception when using openDatabase 2577 // It doesn't anymore. 2578 tests['websqldatabase'] = function() {
2579 return !!window.openDatabase; 2580 }; 2581 2582 // Vendors had inconsistent prefixing with the experimental Indexed DB: 2583 // - Webkit's implementation is accessible through webkitIndexedDB 2584 // - Firefox shipped moz_indexedDB before FF4b9, but since then has been mozIndexedDB 2585 // For speed, we don't test the legacy (and beta-only) indexedDB 2586 tests['indexedDB'] = function() { 2587 return !!testPropsAll("indexedDB", window); 2588 }; 2589 2590 // documentMode logic from YUI to filter out IE8 Compat Mode 2591 // which false positives. 2592 tests['hashchange'] = function() { 2593 return isEventSupported('hashchange', window) && (document.documentMode === undefined || document.documentMode > 7); 2594 }; 2595 2596 // Per 1.6: 2597 // This used to be Modernizr.historymanagement but the longer 2598 // name has been deprecated in favor of a shorter and property-matching one. 2599 // The old API is still available in 1.6, but as of 2.0 will throw a warning, 2600 // and in the first release thereafter disappear entirely. 2601 tests['history'] = function() { 2602 return !!(window.history && history.pushState); 2603 }; 2604 2605 tests['draganddrop'] = function() { 2606 var div = document.createElement('div'); 2607 return ('draggable' in div) || ('ondragstart' in div && 'ondrop' in div); 2608 }; 2609 2610 // FF3.6 was EOL'ed on 4/24/12, but the ESR version of FF10 2611 // will be supported until FF19 (2/12/13), at which time, ESR becomes FF17. 2612 // FF10 still uses prefixes, so check for it until then. 2613 // for more ESR info, see: mozilla.org/en-US/firefox/organizations/faq/ 2614 tests['websockets'] = function() { 2615 return 'WebSocket' in window || 'MozWebSocket' in window; 2616 }; 2617 2618 2619 // css-tricks.com/rgba-browser-support/ 2620 tests['rgba'] = function() { 2621 // Set an rgba() color and check the returned value 2622 2623 setCss('background-color:rgba(150,255,150,.5)'); 2624 2625 return contains(mStyle.backgroundColor, 'rgba'); 2626 }; 2627 2628 tests['hsla'] = function() { 2629 // Same as rgba(), in fact, browsers re-map hsla() to rgba() internally, 2630 // except IE9 who retains it as hsla 2631 2632 setCss('background-color:hsla(120,40%,100%,.5)'); 2633 2634 return contains(mStyle.backgroundColor, 'rgba') || contains(mStyle.backgroundColor, 'hsla'); 2635 }; 2636 2637 tests['multiplebgs'] = function() { 2638 // Setting multiple images AND a color on the background shorthand property 2639 // and then querying the style.background property value for the number of 2640 // occurrences of "url(" is a reliable method for detecting ACTUAL support for this! 2641 2642 setCss('background:url(https://),url(https://),red url(https://)'); 2643 2644 // If the UA supports multiple backgrounds, there should be three occurrences 2645 // of the string "url(" in the return value for elemStyle.background 2646 2647 return (/(url\s*\(.*?){3}/).test(mStyle.background); 2648 }; 2649 2650 2651 2652 // this will false positive in Opera Mini 2653 // github.com/Modernizr/Modernizr/issues/396 2654 2655 tests['backgroundsize'] = function() { 2656 return testPropsAll('backgroundSize'); 2657 }; 2658 2659 tests['borderimage'] = function() { 2660 return testPropsAll('borderImage'); 2661 }; 2662 2663 2664 // Super comprehensive table about all the unique implementations of 2665 // border-radius: muddledramblings.com/table-of-css3-border-radius-compliance 2666
2667 tests['borderradius'] = function() { 2668 return testPropsAll('borderRadius'); 2669 }; 2670 2671 // WebOS unfortunately false positives on this test. 2672 tests['boxshadow'] = function() { 2673 return testPropsAll('boxShadow'); 2674 }; 2675 2676 // FF3.0 will false positive on this test 2677 tests['textshadow'] = function() { 2678 return document.createElement('div').style.textShadow === ''; 2679 }; 2680 2681 2682 tests['opacity'] = function() { 2683 // Browsers that actually have CSS Opacity implemented have done so 2684 // according to spec, which means their return values are within the 2685 // range of [0.0,1.0] - including the leading zero. 2686 2687 setCssAll('opacity:.55'); 2688 2689 // The non-literal . in this regex is intentional: 2690 // German Chrome returns this value as 0,55 2691 // github.com/Modernizr/Modernizr/issues/#issue/59/comment/516632 2692 return (/^0.55$/).test(mStyle.opacity); 2693 }; 2694 2695 2696 // Note, Android < 4 will pass this test, but can only animate 2697 // a single property at a time 2698 // goo.gl/v3V4Gp 2699 tests['cssanimations'] = function() { 2700 return testPropsAll('animationName'); 2701 }; 2702 2703 2704 tests['csscolumns'] = function() { 2705 return testPropsAll('columnCount'); 2706 }; 2707 2708 2709 tests['cssgradients'] = function() { 2710 /** 2711 * For CSS Gradients syntax, please see: 2712 * webkit.org/blog/175/introducing-css-gradients/ 2713 * developer.mozilla.org/en/CSS/-moz-linear-gradient 2714 * developer.mozilla.org/en/CSS/-moz-radial-gradient 2715 * dev.w3.org/csswg/css3-images/#gradients- 2716 */ 2717 2718 var str1 = 'background-image:', 2719 str2 = 'gradient(linear,left top,right bottom,from(#9f9),to(white));', 2720 str3 = 'linear-gradient(left top,#9f9, white);'; 2721 2722 setCss( 2723 // legacy webkit syntax (FIXME: remove when syntax not in use anymore) 2724 (str1 + '-webkit- '.split(' ').join(str2 + str1) + 2725 // standard syntax // trailing 'background-image:' 2726 prefixes.join(str3 + str1)).slice(0, -str1.length) 2727 ); 2728 2729 return contains(mStyle.backgroundImage, 'gradient'); 2730 }; 2731 2732 2733 tests['cssreflections'] = function() { 2734 return testPropsAll('boxReflect'); 2735 }; 2736 2737 2738 tests['csstransforms'] = function() { 2739 return !!testPropsAll('transform'); 2740 }; 2741 2742 2743 tests['csstransforms3d'] = function() { 2744 2745 var ret = !!testPropsAll('perspective'); 2746 2747 // Webkit's 3D transforms are passed off to the browser's own graphics renderer. 2748 // It works fine in Safari on Leopard and Snow Leopard, but not in Chrome in 2749 // some conditions. As a result, Webkit typically recognizes the syntax but 2750 // will sometimes throw a false positive, thus we must do a more thorough check: 2751 if ( ret && 'webkitPerspective' in docElement.style ) { 2752 2753 // Webkit allows this media query to succeed only if the feature is enabled. 2754 // `@media (transform-3d),(-webkit-transform-3d){ ... }` 2755 injectElementWithStyles('@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}', function( node, rule ) { 2756 ret = node.offsetLeft === 9 && node.offsetHeight === 3; 2757 }); 2758 } 2759 return ret; 2760 }; 2761 2762 2763 tests['csstransitions'] = function() { 2764 return testPropsAll('transition'); 2765 }; 2766 2767 2768 /*>>fontface*/ 2769 // @font-face detection routine by Diego Perini 2770 // javascript.nwbox.com/CSSSupport/ 2771 2772 // false positives: 2773 // WebOS github.com/Modernizr/Modernizr/issues/342 2774 // WP7 github.com/Modernizr/Modernizr/issues/538 2775 tests['fontface'] = function() { 2776 var bool; 2777 2778 injectElementWithStyles('@font-face {font-family:"font";src:url("https://")}', function( node, rule ) { 2779 var style = document.getElementById('smodernizr'), 2780 sheet = style.sheet || style.styleSheet, 2781 cssText = sheet ? (sheet.cssRules && sheet.cssRules[0] ? sheet.cssRules[0].cssText : sheet.cssText || '') : ''; 2782 2783 bool = /src/i.test(cssText) && cssText.indexOf(rule.split(' ')[0]) === 0; 2784 }); 2785 2786 return bool; 2787 }; 2788 /*>>fontface*/ 2789 2790 // CSS generated content detection 2791 tests['generatedcontent'] = function() { 2792 var bool; 2793 2794 injectElementWithStyles(['#',mod,'{font:0/0 a}
2794#',mod,':after{content:"',smile,'";visibility:hidden;font:3px/1 a}'].join(''), function( node ) { 2795 bool = node.offsetHeight >= 3; 2796 }); 2797 2798 return bool; 2799 }; 2800 2801 2802 2803 // These tests evaluate support of the video/audio elements, as well as 2804 // testing what types of content they support. 2805 // 2806 // We're using the Boolean constructor here, so that we can extend the value 2807 // e.g. Modernizr.video // true 2808 // Modernizr.video.ogg // 'probably' 2809 // 2810 // Codec values from : github.com/NielsLeenheer/html5test/blob/9106a8/index.html#L845 2811 // thx to NielsLeenheer and zcorpan 2812 2813 // Note: in some older browsers, "no" was a return value instead of empty string. 2814 // It was live in FF3.5.0 and 3.5.1, but fixed in 3.5.2 2815 // It was also live in Safari 4.0.0 - 4.0.4, but fixed in 4.0.5 2816 2817 tests['video'] = function() { 2818 var elem = document.createElement('video'), 2819 bool = false; 2820 2821 // IE9 Running on Windows Server SKU can cause an exception to be thrown, bug #224 2822 try { 2823 if ( bool = !!elem.canPlayType ) { 2824 bool = new Boolean(bool); 2825 bool.ogg = elem.canPlayType('video/ogg; codecs="theora"') .replace(/^no$/,''); 2826
2827 // Without QuickTime, this value will be `undefined`. github.com/Modernizr/Modernizr/issues/546 2828 bool.h264 = elem.canPlayType('video/mp4; codecs="avc1.42E01E"') .replace(/^no$/,''); 2829 2830 bool.webm = elem.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,''); 2831 } 2832 2833 } catch(e) { } 2834 2835 return bool; 2836 }; 2837 2838 tests['audio'] = function() { 2839 var elem = document.createElement('audio'), 2840 bool = false; 2841 2842 try { 2843 if ( bool = !!elem.canPlayType ) { 2844 bool = new Boolean(bool); 2845 bool.ogg = elem.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,''); 2846 bool.mp3 = elem.canPlayType('audio/mpeg;') .replace(/^no$/,''); 2847 2848 // Mimetypes accepted: 2849 // developer.mozilla.org/En/Media_formats_supported_by_the_audio_and_video_elements 2850 // bit.ly/iphoneoscodecs 2851 bool.wav = elem.canPlayType('audio/wav; codecs="1"') .replace(/^no$/,''); 2852 bool.m4a = ( elem.canPlayType('audio/x-m4a;') || 2853 elem.canPlayType('audio/aac;')) .replace(/^no$/,''); 2854 } 2855 } catch(e) { } 2856 2857 return bool; 2858 }; 2859 2860 2861 // In FF4, if disabled, window.localStorage should === null. 2862 2863 // Normally, we could not test that directly and need to do a 2864 // `('localStorage' in window) && ` test first because otherwise Firefox will 2865 // throw bugzil.la/365772 if cookies are disabled 2866 2867 // Also in iOS5 Private Browsing mode, attempting to use localStorage.setItem 2868 // will throw the exception: 2869 // QUOTA_EXCEEDED_ERRROR DOM Exception 22. 2870 // Peculiarly, getItem and removeItem calls do not throw. 2871 2872 // Because we are forced to try/catch this, we'll go aggressive. 2873 2874 // Just FWIW: IE8 Compat mode supports these features completely: 2875 // www.quirksmode.org/dom/html5.html 2876 // But IE8 doesn't support either with local files 2877 2878 tests['localstorage'] = function() { 2879 try { 2880 localStorage.setItem(mod, mod); 2881 localStorage.removeItem(mod); 2882 return true; 2883 } catch(e) { 2884 return false; 2885 } 2886 }; 2887 2888 tests['sessionstorage'] = function() { 2889 try { 2890 sessionStorage.setItem(mod, mod); 2891 sessionStorage.removeItem(mod); 2892 return true; 2893 } catch(e) { 2894 return false; 2895 } 2896 }; 2897 2898 2899 tests['webworkers'] = function() { 2900 return !!window.Worker; 2901 }; 2902 2903 2904 tests['applicationcache'] = function() { 2905 return !!window.applicationCache; 2906 }; 2907 2908 2909 // Thanks to Erik Dahlstrom 2910 tests['svg'] = function() { 2911 return !!document.createElementNS && !!document.createElementNS(ns.svg, 'svg').createSVGRect; 2912 }; 2913 2914 // specifically for SVG inline in HTML, not within XHTML 2915 // test page: paulirish.com/demo/inline-svg 2916 tests['inlinesvg'] = function() { 2917 var div = document.createElement('div'); 2918 div.innerHTML = '<svg/>'; 2919 return (div.firstChild && div.firstChild.namespaceURI) == ns.svg; 2920 }; 2921 2922 // SVG SMIL animation 2923 tests['smil'] = function() { 2924 return !!document.createElementNS && /SVGAnimate/.test(toString.call(document.createElementNS(ns.svg, 'animate'))); 2925 }; 2926 2927 // This test is only for clip paths in SVG proper, not clip paths on HTML content 2928 // demo: srufaculty.sru.edu/david.dailey/svg/newstuff/clipPath4.svg 2929 2930 // However read the comments to dig into applying SVG clippaths to HTML content here: 2931 // github.com/Modernizr/Modernizr/issues/213#issuecomment-1149491 2932 tests['svgclippaths'] = function() { 2933 return !!document.createElementNS && /SVGClipPath/.test(toString.call(document.createElementNS(ns.svg, 'clipPath'))); 2934 }; 2935 2936 /*>>webforms*/ 2937 // input features and input types go directly onto the ret object, bypassing the tests loop. 2938 // Hold this guy to execute in a moment. 2939 function webforms() { 2940 /*>>input*/ 2941 // Run through HTML5's new input attributes to see if the UA understands any. 2942 // We're using f which is the <input> element created early on 2943 // Mike Taylr has created a comprehensive resource for testing these attributes 2944 // when applied to all input types: 2945 // miketaylr.com/code/input-type-attr.html 2946 // spec: www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#input-type-attr-summary 2947 2948 // Only input placeholder is tested while textarea's placeholder is not. 2949 // Currently Safari 4 and Opera 11 have support only for the input placeholder 2950 // Both tests are available in feature-detects/forms-placeholder.js 2951 Modernizr['input'] = (function( props ) { 2952 for ( var i = 0, len = props.length; i < len; i++ ) { 2953 attrs[ props[i] ] = !!(props[i] in inputElem); 2954 } 2955 if (attrs.list){ 2956 // safari false positive's on datalist: webk.it/74252 2957 // see also github.com/Modernizr/Modernizr/issues/146 2958 attrs.list = !!(document.createElement('datalist') && window.HTMLDataListElement); 2959 } 2960 return attrs; 2961 })('autocomplete autofocus list placeholder max min multiple pattern required step'.split(' ')); 2962 /*>>input*/ 2963 2964 /*>>inputtypes*/ 2965 // Run through HTML5's new input types to see if the UA understands any. 2966 // This is put behind the tests runloop because it doesn't return a 2967 // true/false like all the other tests; instead, it returns an object 2968 // containing each input type with its corresponding true/false value 2969 2970 // Big thanks to @miketaylr for the html5 forms expertise. miketaylr.com/ 2971 Modernizr['inputtypes'] = (function(props) { 2972 2973 for ( var i = 0, bool, inputElemType, defaultView, len = props.length; i < len; i++ ) { 2974 2975 inputElem.setAttribute('type', inputElemType = props[i]); 2976 bool = inputElem.type !== 'text'; 2977 2978 // We first check to see if the type we give it sticks.. 2979 // If the type does, we feed it a textual value, which shouldn't be val
2979id. 2980 // If the value doesn't stick, we know there's input sanitization which infers a custom UI 2981 if ( bool ) { 2982 2983 inputElem.value = smile; 2984 inputElem.style.cssText = 'position:absolute;visibility:hidden;'; 2985 2986 if ( /^range$/.test(inputElemType) && inputElem.style.WebkitAppearance !== undefined ) { 2987 2988 docElement.appendChild(inputElem); 2989 defaultView = document.defaultView; 2990 2991 // Safari 2-4 allows the smiley as a value, despite making a slider 2992 bool = defaultView.getComputedStyle && 2993 defaultView.getComputedStyle(inputElem, null).WebkitAppearance !== 'textfield' && 2994 // Mobile android web browser has false positive, so must 2995 // check the height to see if the widget is actually there. 2996 (inputElem.offsetHeight !== 0); 2997 2998 docElement.removeChild(inputElem); 2999 3000 } else if ( /^(search|tel)$/.test(inputElemType) ){ 3001 // Spec doesn't define any special parsing or detectable UI 3002 // behaviors so we pass these through as true 3003 3004 // Interestingly, opera fails the earlier test, so it doesn't 3005 // even make it here. 3006 3007 } else if ( /^(url|email)$/.test(inputElemType) ) { 3008 // Real url and email support comes with prebaked validation. 3009 bool = inputElem.checkValidity && inputElem.checkValidity() === false; 3010 3011 } else { 3012 // If the upgraded input compontent rejects the :) text, we got a winner 3013 bool = inputElem.value != smile; 3014 } 3015 } 3016 3017 inputs[ props[i] ] = !!bool; 3018 } 3019 return inputs; 3020 })('search tel url email datetime date month week time datetime-local number range color'.split(' ')); 3021 /*>>inputtypes*/ 3022 } 3023 /*>>webforms*/ 3024 3025 3026 // End of test definitions 3027 // ----------------------- 3028 3029 3030 3031 // Run through all tests and detect their support in the current UA. 3032 // todo: hypothetically we could be doing an array of tests and use a basic loop here. 3033 for ( var feature in tests ) { 3034 if ( hasOwnProp(tests, feature) ) { 3035 // run the test, throw the return value into the Modernizr, 3036 // then based on that boolean, define an appropriate className 3037 // and push it into an array of classes we'll join later. 3038 featureName = feature.toLowerCase(); 3039 Modernizr[featureName] = tests[feature](); 3040 3041 classes.push((Modernizr[featureName] ? '' : 'no-') + featureName); 3042 } 3043 } 3044 3045 /*>>webforms*/ 3046 // input tests need to run. 3047 Modernizr.input || webforms(); 3048 /*>>webforms*/ 3049 3050 3051 /** 3052 * addTest allows the user to define their own feature tests 3053 * the result will be added onto the Modernizr object, 3054 * as well as an appropriate className set on the html element 3055 * 3056 * @param feature - String naming the feature 3057 * @param test - Function returning true if feature is supported, false if not 3058 */ 3059 Modernizr.addTest = function ( feature, test ) { 3060 if ( typeof feature == 'object' ) { 3061 for ( var key in feature ) { 3062 if ( hasOwnProp( feature, key ) ) { 3063 Modernizr.addTest( key, feature[ key ] ); 3064 } 3065 } 3066 } else { 3067 3068 feature = feature.toLowerCase(); 3069 3070 if ( Modernizr[feature] !== undefined ) { 3071 // we're going to quit if you're trying to overwrite an existing test 3072 // if we were to allow it, we'd do this: 3073 // var re = new RegExp("\\b(no-)?" + feature + "\\b"); 3074 // docElement.className = docElement.className.replace( re, '' ); 3075 // but, no rly, stuff 'em. 3076 return Modernizr; 3077 } 3078 3079 test = typeof test == 'function' ? test() : test; 3080 3081 if (typeof enableClasses !== "undefined" && enableClasses) { 3082 docElement.className += ' ' + (test ? '' : 'no-') + feature; 3083 } 3084 Modernizr[feature] = test; 3085 3086 } 3087 3088 return Modernizr; // allow chaining. 3089 }; 3090 3091 3092 // Reset modElem.cssText to nothing to reduce memory footprint. 3093 setCss(''); 3094 modElem = inputElem = null; 3095 3096 /*>>shiv*/ 3097 /** 3098 * @preserve HTML5 Shiv prev3.7.1 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3099 */ 3100 ;(function(window, document) { 3101 /*jshint evil:true */ 3102 /** version */ 3103 var version = '3.7.0'; 3104 3105 /** Preset options */ 3106 var options = window.html5 || {}; 3107 3108 /** Used to skip problem elements */ 3109 var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i; 3110 3111 /** Not all elements can be cloned in IE **/ 3112 var saveClones = /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i; 3113 3114 /** Detect whether the browser supports default html5 styles */ 3115 var supportsHtml5Styles; 3116 3117 /** Name of the expando, to work with multiple documents or to re-shiv one document */ 3118 var expando = '_html5shiv'; 3119 3120 /** The id for the the documents expando */ 3121 var expanID = 0; 3122 3123 /** Cached data for each document */ 3124 var expandoData = {}; 3125 3126 /** Detect whether the browser supports unknown elements */ 3127 var supportsUnknownElements; 3128 3129 (function() { 3130 try { 3131 var a = document.createElement('a'); 3132 a.innerHTML = '<xyz></xyz>'; 3133 //if the hidden property is implemented we can assume, that the browser supports basic HTML5 Styles 3134 supportsHtml5Styles = ('hidden' in a); 3135 3136 supportsUnknownElements = a.childNodes.length == 1 || (function() { 3137 // assign a false positive if unable to shiv 3138 (document.createElement)('a'); 3139 var frag = document.createDocumentFragment(); 3140 return ( 3141 typeof frag.cloneNode == 'undefined' || 3142 typeof frag.createDocumentFragment == 'undefined' || 3143 typeof frag.createElement == 'undefined' 3144 ); 3145 }()); 3146 } catch(e) { 3147 // assign a false positive if detection fails => unable to shiv 3148 supportsHtml5Styles = true; 3149 supportsUnknownElements = true; 3150 } 3151 3152 }()); 3153 3154 /*--------------------------------------------------------------------------*/ 3155 3156 /** 3157 * Creates a style sheet with the given CSS text and adds it to the document. 3158 * @private 3159 * @param {Document} ownerDocument The document. 3160 * @param {String} cssText The CSS text. 3161 * @returns {StyleSheet} The style element. 3162 */ 3163 function addStyleSheet(ownerDocument, cssText) { 3164 var p = ownerDocument.createElement('p'), 3165 parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement; 3166 3167 p.innerHTML = 'x<style>' + cssText + '</style>'; 3168 return parent.insertBefore(p.lastChild, parent.firstChild); 3169 } 3170 3171 /** 3172 * Returns the value of `html5.elements` as an array.
vendor: 5,515 bytes, lines 3173-3312
3173 * @private 3174 * @returns {Array} An array of shived element node names. 3175 */ 3176 function getElements() { 3177 var elements = html5.elements; 3178 return typeof elements == 'string' ? elements.split(' ') : elements; 3179 } 3180 3181 /** 3182 * Returns the data associated to the given document 3183 * @private 3184 * @param {Document} ownerDocument The document. 3185 * @returns {Object} An object of data. 3186 */ 3187 function getExpandoData(ownerDocument) { 3188 var data = expandoData[ownerDocument[expando]]; 3189 if (!data) { 3190 data = {}; 3191 expanID++; 3192 ownerDocument[expando] = expanID; 3193 expandoData[expanID] = data; 3194 } 3195 return data; 3196 } 3197 3198 /** 3199 * returns a shived element for the given nodeName and document 3200 * @memberOf html5 3201 * @param {String} nodeName name of the element 3202 * @param {Document} ownerDocument The context document. 3203 * @returns {Object} The shived element. 3204 */ 3205 function createElement(nodeName, ownerDocument, data){ 3206 if (!ownerDocument) { 3207 ownerDocument = document; 3208 } 3209 if(supportsUnknownElements){ 3210 return ownerDocument.createElement(nodeName); 3211 } 3212 if (!data) { 3213 data = getExpandoData(ownerDocument); 3214 } 3215 var node; 3216 3217 if (data.cache[nodeName]) { 3218 node = data.cache[nodeName].cloneNode(); 3219 } else if (saveClones.test(nodeName)) { 3220 node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode(); 3221 } else { 3222 node = data.createElem(nodeName); 3223 } 3224 3225 // Avoid adding some elements to fragments in IE < 9 because 3226 // * Attributes like `name` or `type` cannot be set/changed once an element 3227 // is inserted into a document/fragment 3228 // * Link elements with `src` attributes that are inaccessible, as with 3229 // a 403 response, will cause the tab/window to crash 3230 // * Script elements appended to fragments will execute when their `src` 3231 // or `text` property is set 3232 return node.canHaveChildren && !reSkip.test(nodeName) && !node.tagUrn ? data.frag.appendChild(node) : node; 3233 } 3234 3235 /** 3236 * returns a shived DocumentFragment for the given document 3237 * @memberOf html5 3238 * @param {Document} ownerDocument The context document. 3239 * @returns {Object} The shived DocumentFragment. 3240 */ 3241 function createDocumentFragment(ownerDocument, data){ 3242 if (!ownerDocument) { 3243 ownerDocument = document; 3244 } 3245 if(supportsUnknownElements){ 3246 return ownerDocument.createDocumentFragment(); 3247 } 3248 data = data || getExpandoData(ownerDocument); 3249 var clone = data.frag.cloneNode(), 3250 i = 0, 3251 elems = getElements(), 3252 l = elems.length; 3253 for(;i<l;i++){ 3254 clone.createElement(elems[i]); 3255 } 3256 return clone; 3257 } 3258 3259 /** 3260 * Shivs the `createElement` and `createDocumentFragment` methods of the document. 3261 * @private 3262 * @param {Document|DocumentFragment} ownerDocument The document. 3263 * @param {Object} data of the document. 3264 */ 3265 function shivMethods(ownerDocument, data) { 3266 if (!data.cache) { 3267 data.cache = {}; 3268 data.createElem = ownerDocument.createElement; 3269 data.createFrag = ownerDocument.createDocumentFragment; 3270 data.frag = data.createFrag(); 3271 } 3272 3273 3274 ownerDocument.createElement = function(nodeName) { 3275 //abort shiv 3276 if (!html5.shivMethods) { 3277 return data.createElem(nodeName); 3278 } 3279 return createElement(nodeName, ownerDocument, data); 3280 }; 3281 3282 ownerDocument.createDocumentFragment = Function('h,f', 'return function(){' + 3283 'var n=f.cloneNode(),c=n.createElement;' + 3284 'h.shivMethods&&(' + 3285 // unroll the `createElement` calls 3286 getElements().join().replace(/[\w\-]+/g, function(nodeName) { 3287 data.createElem(nodeName); 3288 data.frag.createElement(nodeName); 3289 return 'c("' + nodeName + '")'; 3290 }) + 3291 ');return n}' 3292 )(html5, data.frag); 3293 } 3294 3295 /*--------------------------------------------------------------------------*/ 3296 3297 /** 3298 * Shivs the given document. 3299 * @memberOf html5 3300 * @param {Document} ownerDocument The document to shiv. 3301 * @returns {Document} The shived document. 3302 */ 3303 function shivDocument(ownerDocument) { 3304 if (!ownerDocument) { 3305 ownerDocument = document; 3306 } 3307 var data = getExpandoData(ownerDocument); 3308 3309 if (html5.shivCSS && !supportsHtml5Styles && !data.hasCSS) { 3310 data.hasCSS = !!addStyleSheet(ownerDocument, 3311 // corrects block display not defined in IE6/7/8/9 3312 'article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{dis
3312play:block}' + 3313 // adds styling not present in IE6/7/8/9 3314 'mark{background:#FF0;color:#000}' + 3315 // hides non-rendered elements 3316 'template{display:none}' 3317 ); 3318 } 3319 if (!supportsUnknownElements) { 3320 shivMethods(ownerDocument, data); 3321 } 3322 return ownerDocument; 3323 } 3324 3325 /*--------------------------------------------------------------------------*/ 3326 3327 /** 3328 * The `html5` object is exposed so that more elements can be shived and 3329 * existing shiving can be detected on iframes. 3330 * @type Object 3331 * @example 3332 * 3333 * // options can be changed before the script is included 3334 * html5 = { 'elements': 'mark section', 'shivCSS': false, 'shivMethods': false }; 3335 */ 3336 var html5 = { 3337 3338 /** 3339 * An array or space separated string of node names of the elements to shiv. 3340 * @memberOf html5 3341 * @type Array|String 3342 */ 3343 'elements': options.elements || 'abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output progress section summary template time video', 3344 3345 /** 3346 * current version of html5shiv 3347 */ 3348 'version': version, 3349 3350 /** 3351 * A flag to indicate that the HTML5 style sheet should be inserted. 3352 * @memberOf html5 3353 * @type Boolean 3354 */ 3355 'shivCSS': (options.shivCSS !== false), 3356 3357 /** 3358 * Is equal to true if a browser supports creating unknown/HTML5 elements 3359 * @memberOf html5 3360 * @type boolean 3361 */ 3362 'supportsUnknownElements': supportsUnknownElements, 3363 3364 /** 3365 * A flag to indicate that the document's `createElement` and `createDocumentFragment` 3366 * methods should be overwritten. 3367 * @memberOf html5 3368 * @type Boolean 3369 */ 3370 'shivMethods': (options.shivMethods !== false), 3371 3372 /** 3373 * A string to describe the type of `html5` object ("default" or "default print"). 3374 * @memberOf html5
3375 * @type String 3376 */ 3377 'type': 'default', 3378 3379 // shivs the document according to the specified `html5` object options 3380 'shivDocument': shivDocument, 3381 3382 //creates a shived element 3383 createElement: createElement, 3384 3385 //creates a shived documentFragment 3386 createDocumentFragment: createDocumentFragment 3387 }; 3388 3389 /*--------------------------------------------------------------------------*/ 3390 3391 // expose html5 3392 window.html5 = html5; 3393 3394 // shiv the document 3395 shivDocument(document); 3396 3397 }(this, document)); 3398 /*>>shiv*/ 3399 3400 // Assign private properties to the return object with prefix 3401 Modernizr._version = version; 3402 3403 // expose these for the plugin API. Look in the source for how to join() them against your input 3404 /*>>prefixes*/ 3405 Modernizr._prefixes = prefixes; 3406 /*>>prefixes*/ 3407 /*>>domprefixes*/ 3408 Modernizr._domPrefixes = domPrefixes; 3409 Modernizr._cssomPrefixes = cssomPrefixes; 3410 /*>>domprefixes*/ 3411 3412 /*>>mq*/ 3413 // Modernizr.mq tests a given media query, live against the current state of the window 3414 // A few important notes: 3415 // * If a browser does not support media queries at all (eg. oldIE) the mq() will always return false 3416 // * A max-width or orientation query will be evaluated against the current state, which may change later. 3417 // * You must specify values. Eg. If you are testing support for the min-width media query use: 3418 // Modernizr.mq('(min-width:0)') 3419 // usage: 3420 // Modernizr.mq('only screen and (max-width:768)') 3421 Modernizr.mq = testMediaQuery; 3422 /*>>mq*/ 3423 3424 /*>>hasevent*/ 3425 // Modernizr.hasEvent() detects support for a given event, with an optional element to test on 3426 // Modernizr.hasEvent('gesturestart', elem) 3427 Modernizr.hasEvent = isEventSupported; 3428 /*>>hasevent*/ 3429 3430 /*>>testprop*/ 3431 // Modernizr.testProp() investigates whether a given style property is recognized 3432 // Note that the property names must be provided in the camelCase variant. 3433 // Modernizr.testProp('pointerEvents') 3434 Modernizr.testProp = function(prop){ 3435 return testProps([prop]); 3436 }; 3437 /*>>testprop*/ 3438 3439 /*>>testallprops*/ 3440 // Modernizr.testAllProps() investigates whether a given style property, 3441 // or any of its vendor-prefixed variants, is recognized 3442 // Note that the property names must be provided in the camelCase variant. 3443 // Modernizr.testAllProps('boxSizing') 3444 Modernizr.testAllProps = testPropsAll; 3445 /*>>testallprops*/ 3446 3447 3448 /*>>teststyles*/ 3449 // Modernizr.testStyles() allows you to add custom styles to the document and test an element afterwards 3450 // Modernizr.testStyles('#modernizr { position:absolute }', function(elem, rule){ ... }) 3451 Modernizr.testStyles = injectElementWithStyles; 3452 /*>>teststyles*/ 3453 3454 3455 /*>>prefixed*/ 3456 // Modernizr.prefixed() returns the prefixed or nonprefixed property name variant of your input 3457 // Modernizr.prefixed('boxSizing') // 'MozBoxSizing' 3458 3459 // Properties must be passed as dom-style camelcase, rather than `box-sizing` hypentated style. 3460 // Return values will also be the camelCase variant, if you need to translate that to hypenated style use: 3461 // 3462 // str.replace(/([A-Z])/g, function(str,m1){ return '-' + m1.toLowerCase(); }).replace(/^ms-/,'-ms-'); 3463 3464 // If you're trying to ascertain which transition end event to bind to, you might do something like... 3465 // 3466 // var transEndEventNames = { 3467 // 'WebkitTransition' : 'webkitTransitionEnd', 3468 // 'MozTransition' : 'transitionend', 3469 // 'OTransition' : 'oTransitionEnd', 3470 // 'msTransition' : 'MSTransitionEnd', 3471 // 'transition' : 'transitionend' 3472 // }, 3473 // transEndEventName = transEndEventNames[ Modernizr.prefixed('transition') ]; 3474 3475 Modernizr.prefixed = function(prop, obj, elem){ 3476 if(!obj) { 3477 return testPropsAll(prop, 'pfx'); 3478 } else { 3479 // Testing DOM property e.g. Modernizr.prefixed('requestAnimationFrame', window) // 'mozRequestAnimationFrame' 3480 return testPropsAll(prop, obj, elem); 3481 } 3482 }; 3483 /*>>prefixed*/ 3484 3485 3486 /*>>cssclasses*/ 3487 // Remove "no-js" class from <html> element, if it exists: 3488 docElement.className = docElement.className.replace(/(^|\s)no-js(\s|$)/, '$1$2') + 3489 3490 // Add the new classes to the <html> element. 3491 (enableClasses ? ' js ' + classes.join(' ') : ''); 3492 /*>>cssclasses*/ 3493 3494 return Modernizr; 3495 3496})(this, this.document); 3497 3498/** 3499 * stacktable.js 3500 * Author & copyright (c) 2012: John Polacek 3501 * CardTable by: Justin McNally (2015) 3502 * Dual MIT & GPL license 3503 * 3504 * Page: http://johnpolacek.github.com/stacktable.js 3505 * Repo: https://github.com/johnpolacek/stacktable.js/ 3506 * 3507 * jQuery plugin for stacking tables on small screens 3508 * Requires jQuery version 1.7 or above 3509 * 3510 */ 3511;(function($) { 3512 $.fn.cardtable = function(options) { 3513 var $tables = this, 3514 defaults = {headIndex:0}, 3515 settings = $.extend({}, defaults, options), 3516 headIndex; 3517 3518 // checking the "headIndex" option presence... or defaults it to 0 3519 if(options && options.headIndex) 3520 headIndex = options.headIndex; 3521 else 3522 headIndex = 0; 3523 3524 return $tables.each(function() { 3525 var $table = $(this); 3526 if ($table.hasClass('stacktable')) { 3527 return; 3528 } 3529 var table_css = $(this).prop('class'); 3530 var $stacktable = $('<div></div>'); 3531 if (typeof settings.myClass !== 'undefined') $stacktable.addClass(settings.myClass); 3532 var markup = ''; 3533 var $caption, $topRow, headMarkup, bodyMarkup, tr_class; 3534 3535 $table.addClass('stacktable large-only'); 3536 $caption = $table.find("caption").clone(); 3537 $topRow = $table.find('tr').eq(0); 3538 3539 // using rowIndex and cellIndex in order to reduce ambiguity 3540 $table.find('tbody tr').each(function() { 3541 3542 // declaring headMarkup and bodyMarkup, to be used for separately head and body of single records 3543 headMarkup = ''; 3544 bodyMarkup = ''; 3545 tr_class = $(this).prop('class'); 3546 // for the first row, "headIndex" cell is the head of the table 3547 // for the other rows, put the "headIndex" cell as the head for that row 3548 // then iterate through the key/values 3549 $(this).find('td,th').each(function(cellIndex) { 3550 if ($(this).html() !== ''){ 3551 bodyMarkup += '<tr class="' + tr_class +'">'; 3552 if ($topRow.find('td,th').eq(cellIndex).html()){ 3553 bodyMarkup += '<td class="st-key">
vendor: 4,697 bytes, lines 3553-3665
3553'+$topRow.find('td,th').eq(cellIndex).html()+'</td>'; 3554 } else { 3555 bodyMarkup += '<td class="st-key"></td>'; 3556 } 3557 bodyMarkup += '<td class="st-val '+$(this).prop('class') +'">'+$(this).html()+'</td>'; 3558 bodyMarkup += '</tr>'; 3559 } 3560 }); 3561 3562 markup += '<table class=" '+ table_css +' stacktable small-only"><tbody>' + headMarkup + bodyMarkup + '</tbody></table>'; 3563 }); 3564 3565 $table.find('tfoot tr td').each(function(rowIndex,value) { 3566 if ($.trim($(value).text()) !== '') { 3567 markup += '<table class="'+ table_css + ' stacktable small-only"><tbody><tr><td>' + $(value).html() + '</td></tr></tbody></table>'; 3568 } 3569 }); 3570 3571 $stacktable.prepend($caption); 3572 $stacktable.append($(markup)); 3573 $table.before($stacktable); 3574 }); 3575 }; 3576 3577 $.fn.stacktable = function(options) { 3578 var $tables = this, 3579 defaults = {headIndex:0}, 3580 settings = $.extend({}, defaults, options), 3581 headIndex; 3582 3583 // checking the "headIndex" option presence... or defaults it to 0 3584 if(options && options.headIndex) 3585 headIndex = options.headIndex; 3586 else 3587 headIndex = 0; 3588 3589 return $tables.each(function() { 3590 var table_css = $(this).prop('class'); 3591 var $stacktable = $('<table class="'+ table_css +' stacktable small-only"><tbody></tbody></table>'); 3592 if (typeof settings.myClass !== 'undefined') $stacktable.addClass(settings.myClass); 3593 var markup = ''; 3594 var $table, $caption, $topRow, headMarkup, bodyMarkup, tr_class; 3595 3596 $table = $(this); 3597 $table.addClass('stacktable large-only'); 3598 $caption = $table.find("caption").clone(); 3599 $topRow = $table.find('tr').eq(0); 3600 3601 // using rowIndex and cellIndex in order to reduce ambiguity 3602 $table.find('tr').each(function(rowIndex) { 3603 3604 // declaring headMarkup and bodyMarkup, to be used for separately head and body of single records 3605 headMarkup = ''; 3606 bodyMarkup = ''; 3607 tr_class = $(this).prop('class'); 3608 // for the first row, "headIndex" cell is the head of the table 3609 if (rowIndex === 0) { 3610 // the main heading goes into the markup variable 3611 markup += '<tr class=" '+tr_class +' "><th class="st-head-row st-head-row-main" colspan="2">'+$(this).find('th,td').eq(headIndex).html()+'</th></tr>'; 3612 } 3613 else { 3614 // for the other rows, put the "headIndex" cell as the head for that row 3615 // then iterate through the key/values 3616 $(this).find('td,th').each(function(cellIndex) { 3617 if (cellIndex === headIndex) { 3618 headMarkup = '<tr class="'+ tr_class+'"><th class="st-head-row" colspan="2">'+$(this).html()+'</th></tr>'; 3619 } else { 3620 if ($(this).html() !== ''){ 3621 bodyMarkup += '<tr class="' + tr_class +'">'; 3622 if ($topRow.find('td,th').eq(cellIndex).html()){ 3623 bodyMarkup += '<td class="st-key">'+$topRow.find('td,th').eq(cellIndex).html()+'</td>'; 3624 } else { 3625 bodyMarkup += '<td class="st-key"></td>'; 3626 } 3627 bodyMarkup += '<td class="st-val '+$(this).prop('class') +'">'+$(this).html()+'</td>'; 3628 bodyMarkup += '</tr>'; 3629 } 3630 } 3631 }); 3632 3633 markup += headMarkup + bodyMarkup; 3634 } 3635 }); 3636 3637 $stacktable.prepend($caption); 3638 $stacktable.append($(markup)); 3639 $table.before($stacktable); 3640 }); 3641 }; 3642 3643 $.fn.stackcolumns = function(options) { 3644 var $tables = this, 3645 defaults = {}, 3646 settings = $.extend({}, defaults, options); 3647 3648 return $tables.each(function() { 3649 var $table = $(this); 3650 var num_cols = $table.find('tr').eq(0).find('td,th').length; //first table <tr> must not contain colspans, or add sum(colspan-1) here. 3651 if(num_cols<3) //stackcolumns has no effect on tables with less than 3 columns 3652 return; 3653 3654 var $stackcolumns = $('<table class="stacktable small-only"></table>'); 3655 if (typeof settings.myClass !== 'undefined') $stackcolumns.addClass(settings.myClass); 3656 $table.addClass('stacktable large-only'); 3657 var tb = $('<tbody></tbody>'); 3658 var col_i = 1; //col index starts at 0 -> start copy at second column. 3659 3660 while (col_i < num_cols) { 3661 $table.find('tr').each(function(index) { 3662 var tem = $('<tr></tr>'); // todo opt. copy styles of $this; todo check if parent is thead or tfoot to handle accordingly 3663 if(index === 0) tem.addClass("st-head-row st-head-row-main"); 3664 var first = $(this).find('td,th').eq(0).clone().addClass("st-key"); 3665 var target = col_i;
3666 // if colspan apply, recompute target for second cell. 3667 if ($(this).find("*[colspan]").length) { 3668 var i =0; 3669 $(this).find('td,th').each(function() { 3670 var cs = $(this).attr("colspan"); 3671 if (cs) { 3672 cs = parseInt(cs, 10); 3673 target -= cs-1; 3674 if ((i+cs) > (col_i)) //out of current bounds 3675 target += i + cs - col_i -1; 3676 i += cs; 3677 } 3678 else 3679 i++; 3680 3681 if (i > col_i) 3682 return false; //target is set; break. 3683 }); 3684 } 3685 var second = $(this).find('td,th').eq(target).clone().addClass("st-val").removeAttr("colspan"); 3686 tem.append(first, second); 3687 tb.append(tem); 3688 }); 3689 ++col_i; 3690 } 3691 3692 $stackcolumns.append($(tb)); 3693 $table.before($stackcolumns); 3694 }); 3695 }; 3696 3697}(jQuery)); 3698 3699/** 3700* jquery.matchHeight.js master 3701* http://brm.io/jquery-match-height/ 3702* License: MIT 3703*/ 3704 3705;(function($) { 3706 /* 3707 * internal 3708 */ 3709 3710 var _previousResizeWidth = -1, 3711 _updateTimeout = -1; 3712 3713 /* 3714 * _parse 3715 * value parse utility function 3716 */ 3717 3718 var _parse = function(value) { 3719 // parse value and convert NaN to 0 3720 return parseFloat(value) || 0; 3721 }; 3722 3723 /* 3724 * _rows 3725 * utility function returns array of jQuery selections representing each row 3726 * (as displayed after float wrapping applied by browser) 3727 */ 3728 3729 var _rows = function(elements) { 3730 var tolerance = 1, 3731 $elements = $(elements), 3732 lastTop = null, 3733 rows = []; 3734 3735 // group elements by their top position 3736 $elements.each(function(){ 3737 var $that = $(this), 3738 top = $that.offset().top - _parse($that.css('margin-top')), 3739 lastRow = rows.length > 0 ? rows[rows.length - 1] : null; 3740 3741 if (lastRow === null) { 3742 // first item on the row, so just push it 3743 rows.push($that); 3744 } else { 3745 // if the row top is the same, add to the row group 3746 if (Math.floor(Math.abs(lastTop - top)) <= tolerance) { 3747 rows[rows.length - 1] = lastRow.add($that); 3748 } else { 3749 // otherwise start a new row group 3750 rows.push($that); 3751 } 3752 } 3753 3754 // keep track of the last row top 3755 lastTop = top; 3756 }); 3757 3758 return rows; 3759 }; 3760 3761 /* 3762 * _parseOptions 3763 * handle plugin options 3764 */ 3765 3766 var _parseOptions = function(options) { 3767 var opts = { 3768 byRow: true, 3769 property: 'height', 3770 target: null, 3771 remove: false 3772 }; 3773 3774 if (typeof options === 'object') { 3775 return $.extend(opts, options); 3776 } 3777 3778 if (typeof options === 'boolean') { 3779 opts.byRow = options; 3780 } else if (options === 'remove') { 3781 opts.remove = true; 3782 } 3783 3784 return opts; 3785 }; 3786 3787 /* 3788 * matchHeight 3789 * plugin definition 3790 */ 3791 3792 var matchHeight = $.fn.matchHeight = function(options) { 3793 var opts = _parseOptions(options); 3794 3795 // handle remove 3796 if (opts.remove) { 3797 var that = this; 3798 3799 // remove fixed height from all selected elements 3800 this.css(opts.property, ''); 3801 3802 // remove selected elements from all groups 3803 $.each(matchHeight._groups, function(key, group) { 3804 group.elements = group.elements.not(that); 3805 }); 3806 3807 // TODO: cleanup empty groups 3808 3809 return this; 3810 } 3811 3812 if (this.length <= 1 && !opts.target) { 3813 return this; 3814 } 3815 3816 // keep track of this group so we can re-apply later on load and resize events 3817 matchHeight._groups.push({ 3818 elements: this, 3819 options: opts 3820 }); 3821 3822 // match each element's height to the tallest element in the selection 3823 matchHeight._apply(this, opts); 3824 3825 return this; 3826 }; 3827 3828 /* 3829 * plugin global options 3830 */ 3831 3832 matchHeight._groups = []; 3833 matchHeight._throttle = 80; 3834 matchHeight._maintainScroll = false;
3835 matchHeight._beforeUpdate = null; 3836 matchHeight._afterUpdate = null; 3837 3838 /* 3839 * matchHeight._apply 3840 * apply matchHeight to given elements 3841 */ 3842 3843 matchHeight._apply = function(elements, options) { 3844 var opts = _parseOptions(options), 3845 $elements = $(elements), 3846 rows = [$elements]; 3847 3848 // take note of scroll position 3849 var scrollTop = $(window).scrollTop(), 3850 htmlHeight = $('html').outerHeight(true); 3851 3852 // get hidden parents 3853 var $hiddenParents = $elements.parents().filter(':hidden'); 3854 3855 // cache the original inline style 3856 $hiddenParents.each(function() { 3857 var $that = $(this); 3858 $that.data('style-cache', $that.attr('style')); 3859 }); 3860 3861 // temporarily must force hidden parents visible 3862 $hiddenParents.css('display', 'block'); 3863 3864 // get rows if using byRow, otherwise assume one row 3865 if (opts.byRow && !opts.target) { 3866 3867 // must first force an arbitrary equal height so floating elements break evenly 3868 $elements.each(function() { 3869 var $that = $(this), 3870 display = $that.css('display') === 'inline-block' ? 'inline-block' : 'block'; 3871 3872 // cache the original inline style 3873 $that.data('style-cache', $that.attr('style')); 3874 3875 $that.css({ 3876 'display': display, 3877 'padding-top': '0', 3878 'padding-bottom': '0', 3879 'margin-top': '0', 3880 'margin-bottom': '0', 3881 'border-top-width': '0', 3882 'border-bottom-width': '0', 3883 'height': '100px' 3884 }); 3885 }); 3886 3887 // get the array of rows (based on element top position) 3888 rows = _rows($elements); 3889 3890 // revert original inline styles 3891 $elements.each(function() { 3892 var $that = $(this); 3893 $that.attr('style', $that.data('style-cache') || ''); 3894 }); 3895 } 3896 3897 $.each(rows, function(key, row) { 3898 var $row = $(row), 3899 targetHeight = 0; 3900 3901 if (!opts.target) { 3902 // skip apply to rows with only one item 3903 if (opts.byRow && $row.length <= 1) { 3904 $row.css(opts.property, ''); 3905 return; 3906 } 3907 3908 // iterate the row and find the max height 3909 $row.each(function(){ 3910 var $that = $(this), 3911 display = $that.css('display') === 'inline-block' ? 'inline-block' : 'block'; 3912 3913 // ensure we get the correct actual height (and not a previously set height value) 3914 var css = { 'display': display }; 3915 css[opts.property] = ''; 3916 $that.css(css); 3917 3918 // find the max height (including padding, but not margin) 3919 if ($that.outerHeight(false) > targetHeight) { 3920 targetHeight = $that.outerHeight(false); 3921 } 3922 3923 // revert display block 3924 $that.css('display', ''); 3925 }); 3926 } else { 3927 // if target set, use the height of the target element 3928 targetHeight = opts.target.outerHeight(false); 3929 } 3930 3931 // iterate the row and apply the height to all elements 3932 $row.each(function(){ 3933 var $that = $(this), 3934 verticalPadding = 0; 3935 3936 // don't apply to a target 3937 if (opts.target && $that.is(opts.target)) { 3938 return; 3939 } 3940 3941 // handle padding and border correctly (required when not using border-box) 3942 if ($that.css('box-sizing') !== 'border-box') { 3943 verticalPadding += _parse($that.css('border-top-width')) + _parse($that.css('border-bottom-width')); 3944 verticalPadding += _parse($that.css('padding-top')) + _parse($that.css('padding-bottom')); 3945 } 3946 3947 // set the height (accounting for padding and border) 3948 $that.css(opts.property, targetHeight - verticalPadding); 3949 }); 3950 }); 3951 3952 // revert hidden parents 3953 $hiddenParents.each(function() { 3954 var $that = $(this); 3955 $that.attr('style', $that.data('style-cache') || null); 3956 }); 3957 3958 // restore scroll position if enabled 3959 if (matchHeight._maintainScroll) { 3960 $(window).scrollTop((scrollTop / htmlHeight) * $('html').outerHeight(true)); 3961 } 3962 3963 return this; 3964 }; 3965 3966 /* 3967 * matchHeight._applyDataApi 3968 * applies matchHeight to all elements with a data-match-height attribute 3969 */ 3970 3971 matchHeight._applyDataApi = function() { 3972 var groups = {}; 3973 3974 // generate groups by their groupId set by elements using data-match-height 3975 $('[data-match-height], [data-mh]').each(function() { 3976 var $this = $(this), 3977 groupId = $this.attr('data-mh') || $this.attr('data-match-height'); 3978 3979 if (groupId in groups) { 3980 groups[groupId] = groups[groupId].add($this); 3981 } else { 3982 groups[groupId] = $this; 3983 } 3984 }); 3985 3986 // apply matchHeight to each group 3987 $.each(groups, function() { 3988 this.matchHeight(true); 3989 }); 3990 }; 3991 3992 /* 3993 * matchHeight._update 3994 * updates matchHeight on all current groups with their correct options 3995 */ 3996 3997 var _update = function(event) { 3998 if (matchHeight._beforeUpdate) { 3999 matchHeight._beforeUpdate(event, matchHeight._groups); 4000 } 4001 4002 $.each(matchHeight._groups, function() { 4003 matchHeight._apply(this.elements, this.options); 4004 }); 4005 4006 if (matchHeight._afterUpdate) { 4007 matchHeight._afterUpdate(event, matchHeight._groups); 4008 } 4009 }; 4010 4011 matchHeight._update = function(throttle, event) { 4012 // prevent update if fired from a resize event 4013 // where the viewport width hasn't actually changed 4014 // fixes an event looping bug in IE8 4015 if (event && event.type === 'resize') { 4016 var windowWidth = $(window).width(); 4017 if (windowWidth === _previousResizeWidth) { 4018 return; 4019 } 4020 _previousResizeWidth = windowWidth; 4021 } 4022 4023 // throttle updates 4024 if (!throttle) { 4025 _update(event); 4026 } else if (_updateTimeout === -1) { 4027 _updateTimeout = setTimeout(function() { 4028 _update(event); 4029 _updateTimeout = -1; 4030 }, matchHeight._throttle); 4031 } 4032 }; 4033 4034 /* 4035 * bind events 4036 */ 4037 4038 // apply on DOM ready event 4039 $(matchHeight._applyDataApi); 4040 4041 // update heights on load and resize events 4042 $(window).bind('load', function(event) { 4043 matchHeight._update(false, event); 4044 }); 4045 4046 // throttled update heights on resize events 4047 $(window).bind('resize orientationchange', function(event) { 4048 matchHeight._update(true, event); 4049 }); 4050 4051})(jQuery); 4052 4053/*! 4054 * classie v1.0.1 4055 * class helper functions 4056 * from bonzo https://github.com/ded/bonzo 4057 * MIT license 4058 * 4059 * classie.has( elem, 'my-class' ) -> true/false 4060 * classie.add( elem, 'my-new-class' ) 4061 * classie.remove( elem, 'my-unwanted-class' ) 4062 * classie.toggle( elem, 'my-class' ) 4063 */ 4064 4065/*jshint browser: true, strict: true, undef: true, unused: true */ 4066/*global define: false, module: false */ 4067 4068( function( window ) { 4069 4070'use strict'; 4071 4072// class helper functions from bonzo https://github.com/ded/bonzo 4073 4074function classReg( className ) { 4075 return new RegExp("(^|\\s+)" + className + "(\\s+|$)"); 4076} 4077 4078// classList support for class management 4079// altho to be fair, the api sucks because it won't accept multiple classes at once 4080var hasClass, addClass, removeClass; 4081 4082if ( 'classList' in document.documentElement ) { 4083 hasClass = function( elem, c ) { 4084 return elem.classList.contains( c ); 4085 }; 4086 addClass = function( elem, c ) { 4087 elem.classList.add( c ); 4088 }; 4089 removeClass = function( elem, c ) { 4090 elem.classList.remove( c ); 4091 }; 4092} 4093else { 4094 hasClass = function( elem, c ) { 4095 return classReg( c ).test( elem.className ); 4096 }; 4097 addClass = function( elem, c ) { 4098 if ( !hasClass( elem, c ) ) { 4099 elem.className = elem.className + ' ' + c; 4100 } 4101 }; 4102 removeClass = function( elem, c ) { 4103 elem.className = elem.className.replace( classReg( c ), ' ' ); 4104 }; 4105} 4106 4107function toggleClass( elem, c ) { 4108 var fn = hasClass( elem, c ) ? removeClass : addClass; 4109 fn( elem, c ); 4110} 4111 4112var classie = { 4113 // full names 4114 hasClass: hasClass, 4115 addClass: addClass, 4116 removeClass: removeClass, 4117 toggleClass: toggleClass, 4118 // short names 4119 has: hasClass, 4120 add: addClass, 4121 remove: removeClass, 4122 toggle: toggleClass 4123}; 4124 4125// transport 4126if ( typeof define === 'function' && define.amd ) { 4127 // AMD 4128 define( classie ); 4129} else if ( typeof exports === 'object' ) { 4130 // CommonJS 4131 module.exports = classie; 4132} else { 4133 // browser global 4134 window.classie = classie; 4135} 4136 4137})( window ); 4138 4139/** 4140 * sidebarEffects.js v1.0.0 4141 * http://www.codrops.com 4142 * 4143 * Licensed under the MIT license. 4144 * http://www.opensource.org/licenses/mit-license.php 4145 * 4146 * Copyright 2013, Codrops 4147 * http://www.codrops.com 4148 */ 4149 var SidebarMenuEffects = (function() { 4150 4151 function hasParentClass(e, classname) { 4152 if (e === document) return false;
4153 if (classie.has(e, classname)) { 4154 return true; 4155 } 4156 return e.parentNode && hasParentClass(e.parentNode, classname); 4157 } 4158 4159 // http://coveroverflow.com/a/11381730/989439 4160 function mobilecheck() { 4161 var check = false; 4162 (function(a){if(/(android|ipad|playbook|silk|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4)))check = true})(navigator.userAgent||navigator.vendor||window.opera); 4163 return check; 4164 } 4165 4166 function init() { 4167 4168 var container = document.getElementById('st-container'), 4169 reset = document.getElementById('closeMenu'), 4170 buttons = Array.prototype.slice.call(document.querySelectorAll('#st-trigger-effects > button')), 4171 sidebarClosers = Array.prototype.slice.call(document.querySelectorAll('.st-menu .close-sidebar')), 4172 // event type (if mobile use touch events) 4173 actionStartedEvent = mobilecheck() ? 'touchstart' : 'click', 4174 actionCompletedEvent = mobilecheck() ? 'touchend' : 'click', 4175 resetMenu = function() { 4176 classie.remove(container, 'st-menu-open'); 4177 }, 4178 bodyClickFn = function(evt) { 4179 if (!hasParentClass(evt.target, 'st-menu')) { 4180 resetMenu(); 4181 document.removeEventListener(actionStartedEvent, bodyClickFn); 4182 } 4183 }, 4184 resetClickFn = function(evt) { 4185 if (evt.target == reset) { 4186 resetMenu(); 4187 document.removeEventListener(actionStartedEvent, bodyClickFn); 4188 } 4189 }; 4190 4191 buttons.forEach(function(el, i) { 4192 var effect = el.getAttribute('data-effect'); 4193 4194 el.addEventListener(actionStartedEvent, function(ev) { 4195 ev.stopPropagation(); 4196 ev.preventDefault(); 4197 container.className = 'st-container'; // clear 4198 classie.add(container, effect); 4199 setTimeout(function() { 4200 classie.add(container, 'st-menu-open'); 4201 }, 25); 4202 document.addEventListener(actionStartedEvent, bodyClickFn); 4203 document.addEventListener(actionStartedEvent, resetClickFn); 4204 }); 4205 } ); 4206 4207 sidebarClosers.forEach(function(el, i) { 4208 el.addEventListener(actionCompletedEvent, function(ev) { 4209 resetMenu(); 4210 }); 4211 }); 4212 } 4213
4214 init(); 4215 4216})(); 4217 4218/*! Magnific Popup - v1.0.1 - 2015-12-30 4219* http://dimsemenov.com/plugins/magnific-popup/ 4220* Copyright (c) 2015 Dmitry Semenov; */ 4221!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a("object"==typeof exports?require("jquery"):window.jQuery||window.Zepto)}(function(a){var b,c,d,e,f,g,h="Close",i="BeforeClose",j="AfterClose",k="BeforeAppend",l="MarkupParse",m="Open",n="Change",o="mfp",p="."+o,q="mfp-ready",r="mfp-removing",s="mfp-prevent-close",t=function(){},u=!!window.jQuery,v=a(window),w=function(a,c){b.ev.on(o+a+p,c)},x=function(b,c,d,e){var f=document.createElement("div");return f.className="mfp-"+b,d&&(f.innerHTML=d),e?c&&c.appendChild(f):(f=a(f),c&&f.appendTo(c)),f},y=function(c,d){b.ev.triggerHandler(o+c,d),b.st.callbacks&&(c=c.charAt(0).toLowerCase()+c.slice(1),b.st.callbacks[c]&&b.st.callbacks[c].apply(b,a.isArray(d)?d:[d]))},z=function(c){return c===g&&b.currTemplate.closeBtn||(b.currTemplate.closeBtn=a(b.st.closeMarkup.replace("%title%",b.st.tClose)),g=c),b.currTemplate.closeBtn},A=function(){a.magnificPopup.instance||(b=new t,b.init(),a.magnificPopup.instance=b)},B=function(){var a=document.createElement("p").style,b=["ms","O","Moz","Webkit"];if(void 0!==a.transition)return!0;for(;b.length;)if(b.pop()+"Transition"in a)return!0;return!1};t.prototype={constructor:t,init:function(){var c=navigator.appVersion;b.isIE7=-1!==c.indexOf("MSIE 7."),b.isIE8=-1!==c.indexOf("MSIE 8."),b.isLowIE=b.isIE7||b.isIE8,b.isAndroid=/android/gi.test(c),b.isIOS=/iphone|ipad|ipod/gi.test(c),b.supportsTransition=B(),b.probablyMobile=b.isAndroid||b.isIOS||/(Opera Mini)|Kindle|webOS|BlackBerry|(Opera Mobi)|(Windows Phone)|IEMobile/i.test(navigator.userAgent),d=a(document),b.popupsCache={}},open:function(c){var e;if(c.isObj===!1){b.items=c.items.toArray(),b.index=0;var g,h=c.items;for(e=0;e<h.length;e++)if(g=h[e],g.parsed&&(g=g.el[0]),g===c.el[0]){b.index=e;break}}else b.items=a.isArray(c.items)?c.items:[c.items],b.index=c.index||0;if(b.isOpen)return void b.updateItemHTML();b.types=[],f="",c.mainEl&&c.mainEl.length?b.ev=c.mainEl.eq(0):b.ev=d,c.key?(b.popupsCache[c.key]||(b.popupsCache[c.key]={}),b.currTemplate=b.popupsCache[c.key]):b.currTemplate={},b.st=a.extend(!0,{},a.magnificPopup.defaults,c),b.fixedContentPos="auto"===b.st.fixedContentPos?!b.probablyMobile:b.st.fixedContentPos,b.st.modal&&(b.st.closeOnContentClick=!1,b.st.closeOnBgClick=!1,b.st.showCloseBtn=!1,b.st.enableEscapeKey=!1),b.bgOverlay||(b.bgOverlay=x("bg").on("click"+p,function(){b.close()}),b.wrap=x("wrap").attr("tabindex",-1).on("click"+p,function(a){b._checkIfClose(a.target)&&b.close()}),b.container=x("container",b.wrap)),b.contentContainer=x("content"),b.st.preloader&&(b.preloader=x("preloader",b.container,b.st.tLoading));var i=a.magnificPopup.modules;for(e=0;e<i.length;e++){var j=i[e];j=j.charAt(0).toUpperCase()+j.slice(1),b["init"+j].call(b)}y("BeforeOpen"),b.st.showCloseBtn&&(b.st.closeBtnInside?(w(l,function(a,b,c,d){c.close_replaceWith=z(d.type)}),f+=" mfp-close-btn-in"):b.wrap.append(z())),b.st.alignTop&&(f+=" mfp-align-top"),b.fixedContentPos?b.wrap.css({overflow:b.st.overflowY,overflowX:"hidden",overflowY:b.st.overflowY}):b.wrap.css({top:v.scrollTop(),position:"absolute"}),(b.st.fixedBgPos===!1||"auto"===b.st.fixedBgPos&&!b.fixedContentPos)&&b.bgOverlay.css({height:d.height(),position:"absolute"}),b.st.enableEscapeKey&&d.on("keyup"+p,function(a){27===a.keyCode&&b.close()}),v.on("resize"+p,function(){b.updateSize()}),b.st.closeOnContentClick||(f+=" mfp-auto-cursor"),f&&b.wrap.addClass(f);var k=b.wH=v.height(),n={};if(b.fixedContentPos&&b._hasScrollBar(k)){var o=b._getScrollbarSize();o&&(n.marginRight=o)}b.fixedContentPos&&(b.isIE7?a("body, html").css("overflow","hidden"):n.overflow="hidden");var r=b.st.mainClass;return b.isIE7&&(r+=" mfp-ie7"),r&&b._addClassToMFP(r),b.updateItemHTML(),y("BuildControls"),a("html").css(n),b.bgOverlay.add(b.wrap).prependTo(b.st.prependTo||a(document.body)),b._lastFocusedEl=document.activeElement,setTimeout(function(){b.content?(b._addClassToMFP(q),b._setFocus()):b.bgOverlay.addClass(q),d.on("focusin"+p,b._onFocusIn)},16),b.isOpen=!0,b.updateSize(k),y(m),c},close:function(){b.isOpen&&(y(i),b.isOpen=!1,b.st.removalDelay&&!b.isLowIE&&b.supportsTransition?(b._addClassToMFP(r),setTimeout(function(){b._close()},b.st.removalDelay)):b._close())},_close:function(){y(h);var c=r+" "+q+" ";if(b.bgOverlay.detach(),b.wrap.detach(),b.container.empty(),b.st.mainClass&&(c+=b.st.mainClass+" "),b._removeClassFromMFP(c),b.fixedContentPos){var e={marginRight:""};b.isIE7?a("body, html").css("overflow",""):e.overflow="",a("html").css(e)}d.off("keyup"+p+" focusin"+p),b.ev.off(p),b.wrap.attr("class","mfp-wrap").removeAttr("style"),b.bgOverlay.attr("class","mfp-bg"),b.container.attr("class","mfp-container"),!b.st.showCloseBtn||b.st.closeBtnInside&&b.currTemplate[b.currItem.type]!==!0||b.currTemplate.closeBtn&&b.currTemplate.closeBtn.detach(),b.st.autoFocusLast&&b._lastFocusedEl&&a(b._lastFocusedEl).focus(),b.currItem=null,b.content=null,b.currTemplate=null,b.prevHeight=0,y(j)}
4221,updateSize:function(a){if(b.isIOS){var c=document.documentElement.clientWidth/window.innerWidth,d=window.innerHeight*c;b.wrap.css("height",d),b.wH=d}else b.wH=a||v.height();b.fixedContentPos||b.wrap.css("height",b.wH),y("Resize")},updateItemHTML:function(){var c=b.items[b.index];b.contentContainer.detach(),b.content&&b.content.detach(),c.parsed||(c=b.parseEl(b.index));var d=c.type;if(y("BeforeChange",[b.currItem?b.currItem.type:"",d]),b.currItem=c,!b.currTemplate[d]){var f=b.st[d]?b.st[d].markup:!1;y("FirstMarkupParse",f),f?b.currTemplate[d]=a(f):b.currTemplate[d]=!0}e&&e!==c.type&&b.container.removeClass("mfp-"+e+"-holder");var g=b["get"+d.charAt(0).toUpperCase()+d.slice(1)](c,b.currTemplate[d]);b.appendContent(g,d),c.preloaded=!0,y(n,c),e=c.type,b.container.prepend(b.contentContainer),y("AfterChange")},appendContent:function(a,c){b.content=a,a?b.st.showCloseBtn&&b.st.closeBtnInside&&b.currTemplate[c]===!0?b.content.find(".mfp-close").length||b.content.append(z()):b.content=a:b.content="",y(k),b.container.addClass("mfp-"+c+"-holder"),b.contentContainer.append(b.content)},parseEl:function(c){var d,e=b.items[c];if(e.tagName?e={el:a(e)}:(d=e.type,e={data:e,src:e.src}),e.el){for(var f=b.types,g=0;g<f.length;g++)if(e.el.hasClass("mfp-"+f[g])){d=f[g];break}e.src=e.el.attr("data-mfp-src"),e.src||(e.src=e.el.attr("href"))}return e.type=d||b.st.type||"inline",e.index=c,e.parsed=!0,b.items[c]=e,y("ElementParse",e),b.items[c]},addGroup:function(a,c){var d=function(d){d.mfpEl=this,b._openClick(d,a,c)};c||(c={});var e="click.magnificPopup";c.mainEl=a,c.items?(c.isObj=!0,a.off(e).on(e,d)):(c.isObj=!1,c.delegate?a.off(e).on(e,c.delegate,d):(c.items=a,a.off(e).on(e,d)))},_openClick:function(c,d,e){var f=void 0!==e.midClick?e.midClick:a.magnificPopup.defaults.midClick;if(f||!(2===c.which||c.ctrlKey||c.metaKey||c.altKey||c.shiftKey)){var g=void 0!==e.disableOn?e.disableOn:a.magnificPopup.defaults.disableOn;if(g)if(a.isFunction(g)){if(!g.call(b))return!0}else if(v.width()<g)return!0;c.type&&(c.preventDefault(),b.isOpen&&c.stopPropagation()),e.el=a(c.mfpEl),e.delegate&&(e.items=d.find(e.delegate)),b.open(e)}},updateStatus:function(a,d){if(b.preloader){c!==a&&b.container.removeClass("mfp-s-"+c),d||"loading"!==a||(d=b.st.tLoading);var e={status:a,text:d};y("UpdateStatus",e),a=e.status,d=e.text,b.preloader.html(d),b.preloader.find("a").on("click",function(a){a.stopImmediatePropagation()}),b.container.addClass("mfp-s-"+a),c=a}},_checkIfClose:function(c){if(!a(c).hasClass(s)){var d=b.st.closeOnContentClick,e=b.st.closeOnBgClick;if(d&&e)return!0;if(!b.content||a(c).hasClass("mfp-close")||b.preloader&&c===b.preloader[0])return!0;if(c===b.content[0]||a.contains(b.content[0],c)){if(d)return!0}else if(e&&a.contains(document,c))return!0;return!1}},_addClassToMFP:function(a){b.bgOverlay.addClass(a),b.wrap.addClass(a)},_removeClassFromMFP:function(a){this.bgOverlay.removeClass(a),b.wrap.removeClass(a)},_hasScrollBar:function(a){return(b.isIE7?d.height():document.body.scrollHeight)>(a||v.height())},_setFocus:function(){(b.st.focus?b.content.find(b.st.focus).eq(0):b.wrap).focus()},_onFocusIn:function(c){return c.target===b.wrap[0]||a.contains(b.wrap[0],c.target)?void 0:(b._setFocus(),!1)},_parseMarkup:function(b,c,d){var e;d.data&&(c=a.extend(d.data,c)),y(l,[b,c,d]),a.each(c,function(a,c){if(void 0===c||c===!1)return!0;if(e=a.split("_"),e.length>1){var d=b.find(p+"-"+e[0]);if(d.length>0){var f=e[1];"replaceWith"===f?d[0]!==c[0]&&d.replaceWith(c):"img"===f?d.is("img")?d.attr("src",c):d.replaceWith('<img src="'+c+'" class="'+d.attr("class")+'" />'):d.attr(e[1],c)}}else b.find(p+"-"+a).html(c)})},_getScrollbarSize:function(){if(void 0===b.scrollbarSize){var a=document.createElement("div");a.style.cssText="width: 99px; height: 99px; overflow: scroll; position: absolute; top: -9999px;",document.body.appendChild(a),b.scrollbarSize=a.offsetWidth-a.clientWidth,document.body.removeChild(a)}return b.scrollbarSize}},a.magnificPopup={instance:null,proto:t.prototype,modules:[],open:function(b,c){return A(),b=b?a.extend(!0,{},b):{},b.isObj=!0,b.index=c||0,this.instance.open(b)},close:function(){return a.magnificPopup.instance&&a.magnificPopup.instance.close()},registerModule:function(b,c){c.options&&(a.magnificPopup.defaults[b]=c.options),a.extend(this.proto,c.proto),this.modules.push(b)},defaults:{disableOn:0,key:null,midClick:!1,mainClass:"",preloader:!0,focus:"",closeOnContentClick:!1,closeOnBgClick:!0,closeBtnInside:!0,showCloseBtn:!0,enableEscapeKey:!0,modal:!1,alignTop:!1,removalDelay:0,prependTo:null,fixedContentPos:"auto",fixedBgPos:"auto",overflowY:"auto",closeMarkup:'<button title="%title%" type="button" class="mfp-close">×</button>',tClose:"Close (Esc)",tLoading:"Loading...",autoFocusLast:!0}},a.fn.magnificPopup=function(c){A();var d=a(this);if("string"==typeof c)if("open"===c){var e,f=u?d.data("magnificPopup"):d[0].magnificPopup,g=parseInt(arguments[1],10)||0;f.items?e=f.items[g]:(e=d,f.delegate&&(e=e.find(f.delegate)),e=e.eq(g)),b._openClick({mfpEl:e},d,f)}else b.isOpen&&b[c].apply(b,Array.prototype.slice.call(arguments,1
4221));else c=a.extend(!0,{},c),u?d.data("magnificPopup",c):d[0].magnificPopup=c,b.addGroup(d,c);return d};var C,D,E,F="inline",G=function(){E&&(D.after(E.addClass(C)).detach(),E=null)};a.magnificPopup.registerModule(F,{options:{hiddenClass:"hide",markup:"",tNotFound:"Content not found"},proto:{initInline:function(){b.types.push(F),w(h+"."+F,function(){G()})},getInline:function(c,d){if(G(),c.src){var e=b.st.inline,f=a(c.src);if(f.length){var g=f[0].parentNode;g&&g.tagName&&(D||(C=e.hiddenClass,D=x(C),C="mfp-"+C),E=f.after(D).detach().removeClass(C)),b.updateStatus("ready")}else b.updateStatus("error",e.tNotFound),f=a("<div>");return c.inlineElement=f,f}return b.updateStatus("ready"),b._parseMarkup(d,{},c),d}}});var H,I="ajax",J=function(){H&&a(document.body).removeClass(H)},K=function(){J(),b.req&&b.req.abort()};a.magnificPopup.registerModule(I,{options:{settings:null,cursor:"mfp-ajax-cur",tError:'<a href="%url%">The content</a> could not be loaded.'},proto:{initAjax:function(){b.types.push(I),H=b.st.ajax.cursor,w(h+"."+I,K),w("BeforeChange."+I,K)},getAjax:function(c){H&&a(document.body).addClass(H),b.updateStatus("loading");var d=a.extend({url:c.src,success:function(d,e,f){var g={data:d,xhr:f};y("ParseAjax",g),b.appendContent(a(g.data),I),c.finished=!0,J(),b._setFocus(),setTimeout(function(){b.wrap.addClass(q)},16),b.updateStatus("ready"),y("AjaxContentAdded")},error:function(){J(),c.finished=c.loadError=!0,b.updateStatus("error",b.st.ajax.tError.replace("%url%",c.src))}},b.st.ajax.settings);return b.req=a.ajax(d),""}}});var L,M=function(c){if(c.data&&void 0!==c.data.title)return c.data.title;
4221var d=b.st.image.titleSrc;if(d){if(a.isFunction(d))return d.call(b,c);if(c.el)return c.el.attr(d)||""}return""};a.magnificPopup.registerModule("image",{options:{markup:'<div class="mfp-figure"><div class="mfp-close"></div><figure><div class="mfp-img"></div><figcaption><div class="mfp-bottom-bar"><div class="mfp-title"></div><div class="mfp-counter"></div></div></figcaption></figure></div>',cursor:"mfp-zoom-out-cur",titleSrc:"title",verticalFit:!0,tError:'<a href="%url%">The image</a> could not be loaded.'},proto:{initImage:function(){var c=b.st.image,d=".image";b.types.push("image"),w(m+d,function(){"image"===b.currItem.type&&c.cursor&&a(document.body).addClass(c.cursor)}),w(h+d,function(){c.cursor&&a(document.body).removeClass(c.cursor),v.off("resize"+p)}),w("Resize"+d,b.resizeImage),b.isLowIE&&w("AfterChange",b.resizeImage)},resizeImage:function(){var a=b.currItem;if(a&&a.img&&b.st.image.verticalFit){var c=0;b.isLowIE&&(c=parseInt(a.img.css("padding-top"),10)+parseInt(a.img.css("padding-bottom"),10)),a.img.css("max-height",b.wH-c)}},_onImageHasSize:function(a){a.img&&(a.hasSize=!0,L&&clearInterval(L),a.isCheckingImgSize=!1,y("ImageHasSize",a),a.imgHidden&&(b.content&&b.content.removeClass("mfp-loading"),a.imgHidden=!1))},findImageSize:function(a){var c=0,d=a.img[0],e=function(f){L&&clearInterval(L),L=setInterval(function(){return d.naturalWidth>0?void b._onImageHasSize(a):(c>200&&clearInterval(L),c++,void(3===c?e(10):40===c?e(50):100===c&&e(500)))},f)};e(1)},getImage:function(c,d){var e=0,f=function(){c&&(c.img[0].complete?(c.img.off(".mfploader"),c===b.currItem&&(b._onImageHasSize(c),b.updateStatus("ready")),c.hasSize=!0,c.loaded=!0,y("ImageLoadComplete")):(e++,200>e?setTimeout(f,100):g()))},g=function(){c&&(c.img.off(".mfploader"),c===b.currItem&&(b._onImageHasSize(c),b.updateStatus("error",h.tError.replace("%url%",c.src))),c.hasSize=!0,c.loaded=!0,c.loadError=!0)},h=b.st.image,i=d.find(".mfp-img");if(i.length){var j=document.createElement("img");j.className="mfp-img",c.el&&c.el.find("img").length&&(j.alt=c.el.find("img").attr("alt")),c.img=a(j).on("load.mfploader",f).on("error.mfploader",g),j.src=c.src,i.is("img")&&(c.img=c.img.clone()),j=c.img[0],j.naturalWidth>0?c.hasSize=!0:j.width||(c.hasSize=!1)}return b._parseMarkup(d,{title:M(c),img_replaceWith:c.img},c),b.resizeImage(),c.hasSize?(L&&clearInterval(L),c.loadError?(d.addClass("mfp-loading"),b.updateStatus("error",h.tError.replace("%url%",c.src))):(d.removeClass("mfp-loading"),b.updateStatus("ready")),d):(b.updateStatus("loading"),c.loading=!0,c.hasSize||(c.imgHidden=!0,d.addClass("mfp-loading"),b.findImageSize(c)),d)}}});var N,O=function(){return void 0===N&&(N=void 0!==document.createElement("p").style.MozTransform),N};a.magnificPopup.registerModule("zoom",{options:{enabled:!1,easing:"ease-in-out",duration:300,opener:function(a){return a.is("img")?a:a.find("img")}},proto:{initZoom:function(){var a,c=b.st.zoom,d=".zoom";if(c.enabled&&b.supportsTransition){var e,f,g=c.duration,j=function(a){var b=a.clone().removeAttr("style").removeAttr("class").addClass("mfp-animated-image"),d="all "+c.duration/1e3+"s "+c.easing,e={position:"fixed",zIndex:9999,left:0,top:0,"-webkit-backface-visibility":"hidden"},f="transition";return e["-webkit-"+f]=e["-moz-"+f]=e["-o-"+f]=e[f]=d,b.css(e),b},k=function(){b.content.css("visibility","visible")};w("BuildControls"+d,function(){if(b._allowZoom()){if(clearTimeout(e),b.content.css("visibility","hidden"),a=b._getItemToZoom(),!a)return void k();f=j(a),f.css(b._getOffset()),b.wrap.append(f),e=setTimeout(function(){f.css(b._getOffset(!0)),e=setTimeout(function(){k(),setTimeout(function(){f.remove(),a=f=null,y("ZoomAnimationEnded")},16)},g)},16)}}),w(i+d,function(){if(b._allowZoom()){if(clearTimeout(e),b.st.removalDelay=g,!a){if(a=b._getItemToZoom(),!a)return;f=j(a)}f.css(b._getOffset(!0)),b.wrap.append(f),b.content.css("visibility","hidden"),setTimeout(function(){f.css(b._getOffset())},16)}}),w(h+d,function(){b._allowZoom()&&(k(),f&&f.remove(),a=null)})}},_allowZoom:function(){return"image"===b.currItem.type},_getItemToZoom:function(){return b.currItem.hasSize?b.currItem.img:!1},_getOffset:function(c){var d;d=c?b.currItem.img:b.st.zoom.opener(b.currItem.el||b.currItem);var e=d.offset(),f=parseInt(d.css("padding-top"),10),g=parseInt(d.css("padding-bottom"),10);e.top-=a(window).scrollTop()-f;var h={width:d.width(),height:(u?d.innerHeight():d[0].offsetHeight)-g-f};return O()?h["-moz-transform"]=h.transform="translate("+e.left+"px,"+e.top+"px)":(h.left=e.left,h.top=e.top),h}}});
4221var P="iframe",Q="//about:blank",R=function(a){if(b.currTemplate[P]){var c=b.currTemplate[P].find("iframe");c.length&&(a||(c[0].src=Q),b.isIE8&&c.css("display",a?"block":"none"))}};a.magnificPopup.registerModule(P,{options:{markup:'<div class="mfp-iframe-scaler"><div class="mfp-close"></div><iframe class="mfp-iframe" src="//about:blank" frameborder="0" allowfullscreen></iframe></div>',srcAction:"iframe_src",patterns:{youtube:{index:"youtube.com",id:"v=",src:"//www.youtube.com/embed/%id%?autoplay=1"},vimeo:{index:"vimeo.com/",id:"/",src:"//player.vimeo.com/video/%id%?autoplay=1"},gmaps:{index:"//maps.google.",src:"%id%&output=embed"}}},proto:{initIframe:function(){b.types.push(P),w("BeforeChange",function(a,b,c){b!==c&&(b===P?R():c===P&&R(!0))}),w(h+"."+P,function(){R()})},getIframe:function(c,d){var e=c.src,f=b.st.iframe;a.each(f.patterns,function(){return e.indexOf(this.index)>-1?(this.id&&(e="string"==typeof this.id?e.substr(e.lastIndexOf(this.id)+this.id.length,e.length):this.id.call(this,e)),e=this.src.replace("%id%",e),!1):void 0});var g={};return f.srcAction&&(g[f.srcAction]=e),b._parseMarkup(d,g,c),b.updateStatus("ready"),d}}});var S=function(a){var c=b.items.length;return a>c-1?a-c:0>a?c+a:a},T=function(a,b,c){return a.replace(/%curr%/gi,b+1).replace(/%total%/gi,c)};a.magnificPopup.registerModule("gallery",{options:{enabled:!1,arrowMarkup:'<button title="%title%" type="button" class="mfp-arrow mfp-arrow-%dir%"></button>',preload:[0,2],navigateByImgClick:!0,arrows:!0,tPrev:"Previous (Left arrow key)",tNext:"Next (Right arrow key)",tCounter:"%curr% of %total%"},proto:{initGallery:function(){var c=b.st.gallery,e=".mfp-gallery",g=Boolean(a.fn.mfpFastClick);return b.direction=!0,c&&c.enabled?(f+=" mfp-gallery",w(m+e,function(){c.navigateByImgClick&&b.wrap.on("click"+e,".mfp-img",function(){return b.items.length>1?(b.next(),!1):void 0}),d.on("keydown"+e,function(a){37===a.keyCode?b.prev():39===a.keyCode&&b.next()})}),w("UpdateStatus"+e,function(a,c){c.text&&(c.text=T(c.text,b.currItem.index,b.items.length))}),w(l+e,function(a,d,e,f){var g=b.items.length;e.counter=g>1?T(c.tCounter,f.index,g):""}),w("BuildControls"+e,function(){if(b.items.length>1&&c.arrows&&!b.arrowLeft){var d=c.arrowMarkup,e=b.arrowLeft=a(d.replace(/%title%/gi,c.tPrev).replace(/%dir%/gi,"left")).addClass(s),f=b.arrowRight=a(d.replace(/%title%/gi,c.tNext).replace(/%dir%/gi,"right")).addClass(s),h=g?"mfpFastClick":"click";e[h](function(){b.prev()}),f[h](function(){b.next()}),b.isIE7&&(x("b",e[0],!1,!0),x("a",e[0],!1,!0),x("b",f[0],!1,!0),x("a",f[0],!1,!0)),b.container.append(e.add(f))}}),w(n+e,function(){b._preloadTimeout&&clearTimeout(b._preloadTimeout),b._preloadTimeout=setTimeout(function(){b.preloadNearbyImages(),b._preloadTimeout=null},16)}),void w(h+e,function(){d.off(e),b.wrap.off("click"+e),b.arrowLeft&&g&&b.arrowLeft.add(b.arrowRight).destroyMfpFastClick(),b.arrowRight=b.arrowLeft=null})):!1},next:function(){b.direction=!0,b.index=S(b.index+1),b.updateItemHTML()},prev:function(){b.direction=!1,b.index=S(b.index-1),b.updateItemHTML()},goTo:function(a){b.direction=a>=b.index,b.index=a,b.updateItemHTML()},preloadNearbyImages:function(){var a,c=b.st.gallery.preload,d=Math.min(c[0],b.items.length),e=Math.min(c[1],b.items.length);for(a=1;a<=(b.direction?e:d);a++)b._preloadItem(b.index+a);for(a=1;a<=(b.direction?d:e);a++)b._preloadItem(b.index-a)},_preloadItem:function(c){if(c=S(c),!b.items[c].preloaded){var d=b.items[c];d.parsed||(d=b.parseEl(c)),y("LazyLoad",d),"image"===d.type&&(d.img=a('<img class="mfp-img" />').on("load.mfploader",function(){d.hasSize=!0}).on("error.mfploader",function(){d.hasSize=!0,d.loadError=!0,y("LazyLoadError",d)}).attr("src",d.src)),d.preloaded=!0}}}});var U="retina";a.magnificPopup.registerModule(U,{options:{replaceSrc:function(a){return a.src.replace(/\.\w+$/,function(a){return"@2x"+a})},ratio:1},proto:{initRetina:function(){if(window.devicePixelRatio>1){var a=b.st.retina,c=a.ratio;c=isNaN(c)?c():c,c>1&&(w("ImageHasSize."+U,function(a,b){b.img.css({"max-width":b.img[0].naturalWidth/c,width:"100%"})}),w("ElementParse."+U,function(b,d){d.src=a.replaceSrc(d,c)}))}}}}),function(){var b=1e3,c="ontouchstart"in window,d=function(){v.off("touchmove"+f+" touchend"+f)},e="mfpFastClick",f="."+e;a.fn.mfpFastClick=function(e){return a(this).each(function(){var g,h=a(this);if(c){var i,j,k,l,m,n;h.on("touchstart"+f,function(a){l=!1,n=1,m=a.originalEvent?a.originalEvent.touches[0]:a.touches[0],j=m.clientX,k=m.clientY,v.on("touchmove"+f,function(a){m=a.originalEvent?a.originalEvent.touches:a.touches,n=m.length,m=m[0],(Math.abs(m.clientX-j)>10||Math.abs(m.clientY-k)>10)&&(l=!0,d())}).on("touchend"+f,function(a){d(),l||n>1||(g=!0,a.preventDefault(),clearTimeout(i),i=setTimeout(function(){g=!1},b),e())})})}h.on("click"+f,function(){g||e()})})},a.fn.destroyMfpFastClick=function(){a(this).off("touchstart"+f+" click"+f),c&&v.off("touchmove"+f+" touchend"+f)}}(),A()}); 4222/*! 4223 * jQuery Validation Plugin v1.15.1 4224 * 4225 * http://jqueryvalidation.org/ 4226 * 4227 * Copyright (c) 2016 Jörn Zaefferer 4228 * Released under the MIT license 4229 */ 4230(function( factory ) { 4231 if ( typeof define === "function" && define.amd ) { 4232 define( ["jquery"], factory ); 4233 } else if (typeof module === "object" && module.exports) { 4234 module.exports = factory( require( "jquery" ) ); 4235 } else { 4236 factory( jQuery ); 4237 } 4238}(function( $ ) { 4239 4240$.extend( $.fn, { 4241 4242 // http://jqueryvalidation.org/validate/ 4243 validate: function( options ) { 4244 4245 // If nothing is selected, return nothing; can't chain anyway 4246 if ( !this.length ) { 4247 if ( options && options.debug && window.console ) { 4248 console.warn( "Nothing selected, can't validate, returning nothing." ); 4249 } 4250 return; 4251 } 4252 4253 // Check if a validator for this form was already created
4254 var validator = $.data( this[ 0 ], "validator" ); 4255 if ( validator ) { 4256 return validator; 4257 } 4258 4259 // Add novalidate tag if HTML5. 4260 this.attr( "novalidate", "novalidate" ); 4261 4262 validator = new $.validator( options, this[ 0 ] ); 4263 $.data( this[ 0 ], "validator", validator ); 4264 4265 if ( validator.settings.onsubmit ) { 4266 4267 this.on( "click.validate", ":submit", function( event ) { 4268 if ( validator.settings.submitHandler ) { 4269 validator.submitButton = event.target; 4270 } 4271 4272 // Allow suppressing validation by adding a cancel class to the submit button 4273 if ( $( this ).hasClass( "cancel" ) ) { 4274 validator.cancelSubmit = true; 4275 } 4276 4277 // Allow suppressing validation by adding the html5 formnovalidate attribute to the submit button 4278 if ( $( this ).attr( "formnovalidate" ) !== undefined ) { 4279 validator.cancelSubmit = true; 4280 } 4281 } ); 4282 4283 // Validate the form on submit 4284 this.on( "submit.validate", function( event ) { 4285 if ( validator.settings.debug ) { 4286 4287 // Prevent form submit to be able to see console output 4288 event.preventDefault(); 4289 } 4290 function handle() { 4291 var hidden, result; 4292 if ( validator.settings.submitHandler ) { 4293 if ( validator.submitButton ) { 4294 4295 // Insert a hidden input as a replacement for the missing submit button 4296 hidden = $( "<input type='hidden'/>" ) 4297 .attr( "name", validator.submitButton.name ) 4298 .val( $( validator.submitButton ).val() ) 4299 .appendTo( validator.currentForm ); 4300 } 4301 result = validator.settings.submitHandler.call( validator, validator.currentForm, event ); 4302 if ( validator.submitButton ) { 4303 4304 // And clean up afterwards; thanks to no-block-scope, hidden can be referenced 4305 hidden.remove(); 4306 } 4307 if ( result !== undefined ) { 4308 return result; 4309 } 4310 return false; 4311 } 4312 return true; 4313 } 4314 4315 // Prevent submit for invalid forms or custom submit handlers 4316 if ( validator.cancelSubmit ) { 4317 validator.cancelSubmit = false; 4318 return handle(); 4319 } 4320 if ( validator.form() ) { 4321 if ( validator.pendingRequest ) { 4322 validator.formSubmitted = true; 4323 return false; 4324 } 4325 return handle(); 4326 } else { 4327 validator.focusInvalid(); 4328 return false; 4329 } 4330 } ); 4331 } 4332 4333 return validator; 4334 }, 4335 4336 // http://jqueryvalidation.org/valid/ 4337 valid: function() { 4338 var valid, validator, errorList; 4339 4340 if ( $( this[ 0 ] ).is( "form" ) ) { 4341 valid = this.validate().form(); 4342 } else { 4343 errorList = []; 4344 valid = true; 4345 validator = $( this[ 0 ].form ).validate(); 4346 this.each( function() { 4347 valid = validator.element( this ) && valid; 4348 if ( !valid ) { 4349 errorList = errorList.concat( validator.errorList ); 4350 } 4351 } ); 4352 validator.errorList = errorList; 4353 } 4354 return valid; 4355 }, 4356 4357 // http://jqueryvalidation.org/rules/ 4358 rules: function( command, argument ) { 4359 var element = this[ 0 ], 4360 settings, staticRules, existingRules, data, param, filtered; 4361 4362 // If nothing is selected, return empty object; can't chain anyway 4363 if ( element == null || element.form == null ) { 4364 return; 4365 } 4366 4367 if ( command ) { 4368 settings = $.data( element.form, "validator" ).settings; 4369 staticRules = settings.rules; 4370 existingRules = $.validator.staticRules( element ); 4371 switch ( command ) { 4372 case "add": 4373 $.extend( existingRules, $.validator.normalizeRule( argument ) ); 4374 4375 // Remove messages from rules, but allow them to be set separately 4376 delete existingRules.messages; 4377 staticRules[ element.name ] = existingRules; 4378 if ( argument.messages ) { 4379 settings.messages[ element.name ] = $.extend( settings.messages[ element.name ], argument.messages ); 4380 } 4381 break; 4382 case "remove": 4383 if ( !argument ) { 4384 delete staticRules[ element.name ]; 4385 return existingRules; 4386 } 4387 filtered = {}; 4388 $.each( argument.split( /\s/ ), function( index, method ) { 4389 filtered[ method ] = existingRules[ method ]; 4390 delete existingRules[ method ]; 4391 if ( method === "required" ) { 4392 $( element ).removeAttr( "aria-required" ); 4393 } 4394 } ); 4395 return filtered; 4396 } 4397 } 4398 4399 data = $.validator.normalizeRules( 4400 $.extend( 4401 {}, 4402 $.validator.classRules( element ), 4403 $.validator.attributeRules( element ), 4404 $.validator.dataRules( element ), 4405 $.validator.staticRules( element ) 4406 ), element ); 4407 4408 // Make sure required is at front 4409 if ( data.required ) { 4410 param = data.required; 4411 delete data.required; 4412 data = $.extend( { required: param }, data ); 4413 $( element ).attr( "aria-required", "true" ); 4414 } 4415 4416 // Make sure remote is at back 4417 if ( data.remote ) { 4418 param = data.remote; 4419 delete data.remote; 4420 data = $.extend( data, { remote: param } ); 4421 } 4422 4423 return data; 4424 } 4425} ); 4426 4427// Custom selectors 4428$.extend( $.expr[ ":" ], { 4429 4430 // http://jqueryvalidation.org/blank-selector/ 4431 blank: function( a ) { 4432 return !$.trim( "" + $( a ).val() ); 4433 }, 4434 4435 // http://jqueryvalidation.org/filled-selector/ 4436 filled: function( a ) { 4437 var val = $( a ).val(); 4438 return val !== null && !!$.trim( "" + val ); 4439 }, 4440 4441 // http://jqueryvalidation.org/unchecked-selector/ 4442 unchecked: function( a ) { 4443 return !$( a ).prop( "checked" ); 4444 } 4445} ); 4446 4447// Constructor for validator 4448$.validator = function( options, form ) { 4449 this.settings = $.extend( true, {}, $.validator.defaults, options ); 4450 this.currentForm = form; 4451 this.init(); 4452}; 4453 4454// http://jqueryvalidation.org/jQuery.validator.format/ 4455$.validator.format = function( source, params ) { 4456 if ( arguments.length === 1 ) { 4457 return function() { 4458 var args = $.makeArray( arguments ); 4459 args.unshift( source ); 4460 return $.validator.format.apply( this, args ); 4461 }; 4462 } 4463 if ( params === undefined ) { 4464 return source; 4465 }
vendor: 9,941 bytes, lines 4466-4776
4466 if ( arguments.length > 2 && params.constructor !== Array ) { 4467 params = $.makeArray( arguments ).slice( 1 ); 4468 } 4469 if ( params.constructor !== Array ) { 4470 params = [ params ]; 4471 } 4472 $.each( params, function( i, n ) { 4473 source = source.replace( new RegExp( "\\{" + i + "\\}", "g" ), function() { 4474 return n; 4475 } ); 4476 } ); 4477 return source; 4478}; 4479 4480$.extend( $.validator, { 4481 4482 defaults: { 4483 messages: {}, 4484 groups: {}, 4485 rules: {}, 4486 errorClass: "error", 4487 pendingClass: "pending", 4488 validClass: "valid", 4489 errorElement: "label", 4490 focusCleanup: false, 4491 focusInvalid: true, 4492 errorContainer: $( [] ), 4493 errorLabelContainer: $( [] ), 4494 onsubmit: true, 4495 ignore: ":hidden", 4496 ignoreTitle: false, 4497 onfocusin: function( element ) { 4498 this.lastActive = element; 4499 4500 // Hide error label and remove error class on focus if enabled 4501 if ( this.settings.focusCleanup ) { 4502 if ( this.settings.unhighlight ) { 4503 this.settings.unhighlight.call( this, element, this.settings.errorClass, this.settings.validClass ); 4504 } 4505 this.hideThese( this.errorsFor( element ) ); 4506 } 4507 }, 4508 onfocusout: function( element ) { 4509 if ( !this.checkable( element ) && ( element.name in this.submitted || !this.optional( element ) ) ) { 4510 this.element( element ); 4511 } 4512 }, 4513 onkeyup: function( element, event ) { 4514 4515 // Avoid revalidate the field when pressing one of the following keys 4516 // Shift => 16 4517 // Ctrl => 17 4518 // Alt => 18 4519 // Caps lock => 20 4520 // End => 35 4521 // Home => 36 4522 // Left arrow => 37 4523 // Up arrow => 38 4524 // Right arrow => 39 4525 // Down arrow => 40 4526 // Insert => 45 4527 // Num lock => 144 4528 // AltGr key => 225 4529 var excludedKeys = [ 4530 16, 17, 18, 20, 35, 36, 37, 4531 38, 39, 40, 45, 144, 225 4532 ]; 4533 4534 if ( event.which === 9 && this.elementValue( element ) === "" || $.inArray( event.keyCode, excludedKeys ) !== -1 ) { 4535 return; 4536 } else if ( element.name in this.submitted || element.name in this.invalid ) { 4537 this.element( element ); 4538 } 4539 }, 4540 onclick: function( element ) { 4541 4542 // Click on selects, radiobuttons and checkboxes 4543 if ( element.name in this.submitted ) { 4544 this.element( element ); 4545 4546 // Or option elements, check parent select in that case 4547 } else if ( element.parentNode.name in this.submitted ) { 4548 this.element( element.parentNode ); 4549 } 4550 }, 4551 highlight: function( element, errorClass, validClass ) { 4552 if ( element.type === "radio" ) { 4553 this.findByName( element.name ).addClass( errorClass ).removeClass( validClass ); 4554 } else { 4555 $( element ).addClass( errorClass ).removeClass( validClass ); 4556 } 4557 }, 4558 unhighlight: function( element, errorClass, validClass ) { 4559 if ( element.type === "radio" ) { 4560 this.findByName( element.name ).removeClass( errorClass ).addClass( validClass ); 4561 } else { 4562 $( element ).removeClass( errorClass ).addClass( validClass ); 4563 } 4564 } 4565 }, 4566 4567 // http://jqueryvalidation.org/jQuery.validator.setDefaults/ 4568 setDefaults: function( settings ) { 4569 $.extend( $.validator.defaults, settings ); 4570 }, 4571 4572 messages: { 4573 required: "This field is required.", 4574 remote: "Please fix this field.", 4575 email: "Please enter a valid email address.", 4576 url: "Please enter a valid URL.", 4577 date: "Please enter a valid date.", 4578 dateISO: "Please enter a valid date (ISO).", 4579 number: "Please enter a valid number.", 4580 digits: "Please enter only digits.", 4581 equalTo: "Please enter the same value again.", 4582 maxlength: $.validator.format( "Please enter no more than {0} characters." ), 4583 minlength: $.validator.format( "Please enter at least {0} characters." ), 4584 rangelength: $.validator.format( "Please enter a value between {0} and {1} characters long." ), 4585 range: $.validator.format( "Please enter a value between {0} and {1}." ), 4586 max: $.validator.format( "Please enter a value less than or equal to {0}." ), 4587 min: $.validator.format( "Please enter a value greater than or equal to {0}." ), 4588 step: $.validator.format( "Please enter a multiple of {0}." ) 4589 }, 4590 4591 autoCreateRanges: false, 4592 4593 prototype: { 4594 4595 init: function() { 4596 this.labelContainer = $( this.settings.errorLabelContainer ); 4597 this.errorContext = this.labelContainer.length && this.labelContainer || $( this.currentForm ); 4598 this.containers = $( this.settings.errorContainer ).add( this.settings.errorLabelContainer ); 4599 this.submitted = {}; 4600 this.valueCache = {}; 4601 this.pendingRequest = 0; 4602 this.pending = {}; 4603 this.invalid = {}; 4604 this.reset(); 4605 4606 var groups = ( this.groups = {} ), 4607 rules; 4608 $.each( this.settings.groups, function( key, value ) { 4609 if ( typeof value === "string" ) { 4610 value = value.split( /\s/ ); 4611 } 4612 $.each( value, function( index, name ) { 4613 groups[ name ] = key; 4614 } ); 4615 } ); 4616 rules = this.settings.rules; 4617 $.each( rules, function( key, value ) { 4618 rules[ key ] = $.validator.normalizeRule( value ); 4619 } ); 4620 4621 function delegate( event ) { 4622 4623 // Set form expando on contenteditable 4624 if ( !this.form && this.hasAttribute( "contenteditable" ) ) { 4625 this.form = $( this ).closest( "form" )[ 0 ]; 4626 } 4627 4628 var validator = $.data( this.form, "validator" ), 4629 eventType = "on" + event.type.replace( /^validate/, "" ), 4630 settings = validator.settings; 4631 if ( settings[ eventType ] && !$( this ).is( settings.ignore ) ) { 4632 settings[ eventType ].call( validator, this, event ); 4633 } 4634 } 4635 4636 $( this.currentForm ) 4637 .on( "focusin.validate focusout.validate keyup.validate", 4638 ":text, [type='password'], [type='file'], select, textarea, [type='number'], [type='search'], " + 4639 "[type='tel'], [type='url'], [type='email'], [type='datetime'], [type='date'], [type='month'], " + 4640 "[type='week'], [type='time'], [type='datetime-local'], [type='range'], [type='color'], " + 4641 "[type='radio'], [type='checkbox'], [contenteditable]", delegate ) 4642 4643 // Support: Chrome, oldIE 4644 // "select" is provided as event.target when clicking a option 4645 .on( "click.validate", "select, option, [type='radio'], [type='checkbox']", delegate ); 4646 4647 if ( this.settings.invalidHandler ) { 4648 $( this.currentForm ).on( "invalid-form.validate", this.settings.invalidHandler ); 4649 } 4650 4651 // Add aria-required to any Static/Data/Class required fields before first validation 4652 // Screen readers require this attribute to be present before the initial submission http://www.w3.org/TR/WCAG-TECHS/ARIA2.html 4653 $( this.currentForm ).find( "[required], [data-rule-required], .required" ).attr( "aria-required", "true" ); 4654 }, 4655 4656 // http://jqueryvalidation.org/Validator.form/ 4657 form: function() { 4658 this.checkForm(); 4659 $.extend( this.submitted, this.errorMap ); 4660 this.invalid = $.extend( {}, this.errorMap ); 4661 if ( !this.valid() ) { 4662 $( this.currentForm ).triggerHandler( "invalid-form", [ this ] ); 4663 } 4664 this.showErrors(); 4665 return this.valid(); 4666 }, 4667 4668 checkForm: function() { 4669 this.prepareForm(); 4670 for ( var i = 0, elements = ( this.currentElements = this.elements() ); elements[ i ]; i++ ) { 4671 this.check( elements[ i ] ); 4672 } 4673 return this.valid(); 4674 }, 4675 4676 // http://jqueryvalidation.org/Validator.element/ 4677 element: function( element ) { 4678 var cleanElement = this.clean( element ), 4679 checkElement = this.validationTargetFor( cleanElement ), 4680 v = this, 4681 result = true, 4682 rs, group; 4683 4684 if ( checkElement === undefined ) { 4685 delete this.invalid[ cleanElement.name ]; 4686 } else { 4687 this.prepareElement( checkElement ); 4688 this.currentElements = $( checkElement ); 4689 4690 // If this element is grouped, then validate all group elements already 4691 // containing a value 4692 group = this.groups[ checkElement.name ]; 4693 if ( group ) { 4694 $.each( this.groups, function( name, testgroup ) { 4695 if ( testgroup === group && name !== checkElement.name ) { 4696 cleanElement = v.validationTargetFor( v.clean( v.findByName( name ) ) ); 4697 if ( cleanElement && cleanElement.name in v.invalid ) { 4698 v.currentElements.push( cleanElement ); 4699 result = v.check( cleanElement ) && result; 4700 } 4701 } 4702 } ); 4703 } 4704 4705 rs = this.check( checkElement ) !== false; 4706 result = result && rs; 4707 if ( rs ) { 4708 this.invalid[ checkElement.name ] = false; 4709 } else { 4710 this.invalid[ checkElement.name ] = true; 4711 } 4712 4713 if ( !this.numberOfInvalids() ) { 4714 4715 // Hide error containers on last error 4716 this.toHide = this.toHide.add( this.containers ); 4717 } 4718 this.showErrors(); 4719 4720 // Add aria-invalid status for screen readers 4721 $( element ).attr( "aria-invalid", !rs ); 4722 } 4723 4724 return result; 4725 }, 4726 4727 // http://jqueryvalidation.org/Validator.showErrors/ 4728 showErrors: function( errors ) { 4729 if ( errors ) { 4730 var validator = this; 4731 4732 // Add items to error list and map 4733 $.extend( this.errorMap, errors ); 4734 this.errorList = $.map( this.errorMap, function( message, name ) { 4735 return { 4736 message: message, 4737 element: validator.findByName( name )[ 0 ] 4738 }; 4739 } ); 4740 4741 // Remove items from success list 4742 this.successList = $.grep( this.successList, function( element ) { 4743 return !( element.name in errors ); 4744 } ); 4745 } 4746 if ( this.settings.showErrors ) { 4747 this.settings.showErrors.call( this, this.errorMap, this.errorList ); 4748 } else { 4749 this.defaultShowErrors(); 4750 } 4751 }, 4752 4753 // http://jqueryvalidation.org/Validator.resetForm/ 4754 resetForm: function() { 4755 if ( $.fn.resetForm ) { 4756 $( this.currentForm ).resetForm(); 4757 } 4758 this.invalid = {}; 4759 this.submitted = {}; 4760 this.prepareForm(); 4761 this.hideErrors(); 4762 var elements = this.elements() 4763 .removeData( "previousValue" ) 4764 .removeAttr( "aria-invalid" ); 4765 4766 this.resetElements( elements ); 4767 }, 4768 4769 resetElements: function( elements ) { 4770 var i; 4771 4772 if ( this.settings.unhighlight ) { 4773 for ( i = 0; elements[ i ]; i++ ) { 4774 this.settings.unhighlight.call( this, elements[ i ], 4775 this.settings.errorClass, "" ); 4776 this.findByName( elements[ i ].name ).removeClass( this.settings.val
4776idClass ); 4777 } 4778 } else { 4779 elements 4780 .removeClass( this.settings.errorClass ) 4781 .removeClass( this.settings.validClass ); 4782 } 4783 }, 4784 4785 numberOfInvalids: function() { 4786 return this.objectLength( this.invalid ); 4787 }, 4788 4789 objectLength: function( obj ) { 4790 /* jshint unused: false */ 4791 var count = 0, 4792 i; 4793 for ( i in obj ) { 4794 if ( obj[ i ] ) { 4795 count++; 4796 } 4797 } 4798 return count; 4799 }, 4800 4801 hideErrors: function() { 4802 this.hideThese( this.toHide ); 4803 }, 4804 4805 hideThese: function( errors ) { 4806 errors.not( this.containers ).text( "" ); 4807 this.addWrapper( errors ).hide(); 4808 }, 4809 4810 valid: function() { 4811 return this.size() === 0; 4812 }, 4813 4814 size: function() { 4815 return this.errorList.length; 4816 }, 4817 4818 focusInvalid: function() { 4819 if ( this.settings.focusInvalid ) { 4820 try { 4821 $( this.findLastActive() || this.errorList.length && this.errorList[ 0 ].element || [] ) 4822 .filter( ":visible" ) 4823 .focus() 4824 4825 // Manually trigger focusin event; without it, focusin handler isn't called, findLastActive won't have anything to find 4826 .trigger( "focusin" ); 4827 } catch ( e ) { 4828 4829 // Ignore IE throwing errors when focusing hidden elements 4830 } 4831 } 4832 }, 4833 4834 findLastActive: function() { 4835 var lastActive = this.lastActive; 4836 return lastActive && $.grep( this.errorList, function( n ) { 4837 return n.element.name === lastActive.name; 4838 } ).length === 1 && lastActive; 4839 }, 4840 4841 elements: function() { 4842 var validator = this, 4843 rulesCache = {}; 4844 4845 // Select all valid inputs inside the form (no submit or reset buttons) 4846 return $( this.currentForm ) 4847 .find( "input, select, textarea, [contenteditable]" ) 4848 .not( ":submit, :reset, :image, :disabled" ) 4849 .not( this.settings.ignore ) 4850 .filter( function() { 4851 var name = this.name || $( this ).attr( "name" ); // For contenteditable 4852 if ( !name && validator.settings.debug && window.console ) { 4853 console.error( "%o has no name assigned", this ); 4854 } 4855 4856 // Set form expando on contenteditable 4857 if ( this.hasAttribute( "contenteditable" ) ) { 4858 this.form = $( this ).closest( "form" )[ 0 ]; 4859 } 4860 4861 // Select only the first element for each name, and only those with rules specified 4862 if ( name in rulesCache || !validator.objectLength( $( this ).rules() ) ) { 4863 return false; 4864 } 4865 4866 rulesCache[ name ] = true; 4867 return true; 4868 } ); 4869 }, 4870 4871 clean: function( selector ) { 4872 return $( selector )[ 0 ]; 4873 }, 4874 4875 errors: function() { 4876 var errorClass = this.settings.errorClass.split( " " ).join( "." ); 4877 return $( this.settings.errorElement + "." + errorClass, this.errorContext ); 4878 }, 4879 4880 resetInternals: function() { 4881 this.successList = []; 4882 this.errorList = []; 4883 this.errorMap = {}; 4884 this.toShow = $( [] ); 4885 this.toHide = $( [] ); 4886 }, 4887 4888 reset: function() { 4889 this.resetInternals(); 4890 this.currentElements = $( [] ); 4891 }, 4892 4893 prepareForm: function() { 4894 this.reset(); 4895 this.toHide = this.errors().add( this.containers ); 4896 }, 4897 4898 prepareElement: function( element ) { 4899 this.reset(); 4900 this.toHide = this.errorsFor( element ); 4901 }, 4902 4903 elementValue: function( element ) { 4904 var $element = $( element ), 4905 type = element.type, 4906 val, idx; 4907 4908 if ( type === "radio" || type === "checkbox" ) { 4909 return this.findByName( element.name ).filter( ":checked" ).val(); 4910 } else if ( type === "number" && typeof element.validity !== "undefined" ) { 4911 return element.validity.badInput ? "NaN" : $element.val(); 4912 } 4913 4914 if ( element.hasAttribute( "contenteditable" ) ) { 4915 val = $element.text(); 4916 } else { 4917 val = $element.val(); 4918 } 4919 4920 if ( type === "file" ) { 4921 4922 // Modern browser (chrome & safari) 4923 if ( val.substr( 0, 12 ) === "C:\\fakepath\\" ) { 4924 return val.substr( 12 ); 4925 } 4926 4927 // Legacy browsers 4928 // Unix-based path 4929 idx = val.lastIndexOf( "/" ); 4930 if ( idx >= 0 ) { 4931 return val.substr( idx + 1 ); 4932 } 4933 4934 // Windows-based path 4935 idx = val.lastIndexOf( "\\" ); 4936 if ( idx >= 0 ) { 4937 return val.substr( idx + 1 ); 4938 } 4939 4940 // Just the file name 4941 return val; 4942 } 4943 4944 if ( typeof val === "string" ) { 4945 return val.replace( /\r/g, "" ); 4946 } 4947 return val; 4948 }, 4949 4950 check: function( element ) { 4951 element = this.validationTargetFor( this.clean( element ) ); 4952 4953 var rules = $( element ).rules(), 4954 rulesCount = $.map( rules, function( n, i ) { 4955 return i; 4956 } ).length, 4957 dependencyMismatch = false, 4958 val = this.elementValue( element ), 4959 result, method, rule; 4960 4961 // If a normalizer is defined for this element, then 4962 // call it to retreive the changed value instead 4963 // of using the real one. 4964 // Note that `this` in the normalizer is `element`. 4965 if ( typeof rules.normalizer === "function" ) { 4966 val = rules.normalizer.call( element, val ); 4967 4968 if ( typeof val !== "string" ) { 4969 throw new TypeError( "The normalizer should return a string value." ); 4970 } 4971 4972 // Delete the normalizer from rules to avoid treating 4973 // it as a pre-defined method. 4974 delete rules.normalizer; 4975 } 4976 4977 for ( method in rules ) { 4978 rule = { method: method, parameters: rules[ method ] };
vendor: 23,772 bytes, lines 4979-5719
4979 try { 4980 result = $.validator.methods[ method ].call( this, val, element, rule.parameters ); 4981 4982 // If a method indicates that the field is optional and therefore valid, 4983 // don't mark it as valid when there are no other rules 4984 if ( result === "dependency-mismatch" && rulesCount === 1 ) { 4985 dependencyMismatch = true; 4986 continue; 4987 } 4988 dependencyMismatch = false; 4989 4990 if ( result === "pending" ) { 4991 this.toHide = this.toHide.not( this.errorsFor( element ) ); 4992 return; 4993 } 4994 4995 if ( !result ) { 4996 this.formatAndAdd( element, rule ); 4997 return false; 4998 } 4999 } catch ( e ) { 5000 if ( this.settings.debug && window.console ) { 5001 console.log( "Exception occurred when checking element " + element.id + ", check the '" + rule.method + "' method.", e ); 5002 } 5003 if ( e instanceof TypeError ) { 5004 e.message += ". Exception occurred when checking element " + element.id + ", check the '" + rule.method + "' method."; 5005 } 5006 5007 throw e; 5008 } 5009 } 5010 if ( dependencyMismatch ) { 5011 return; 5012 } 5013 if ( this.objectLength( rules ) ) { 5014 this.successList.push( element ); 5015 } 5016 return true; 5017 }, 5018 5019 // Return the custom message for the given element and validation method 5020 // specified in the element's HTML5 data attribute 5021 // return the generic message if present and no method specific message is present 5022 customDataMessage: function( element, method ) { 5023 return $( element ).data( "msg" + method.charAt( 0 ).toUpperCase() + 5024 method.substring( 1 ).toLowerCase() ) || $( element ).data( "msg" ); 5025 }, 5026 5027 // Return the custom message for the given element name and validation method 5028 customMessage: function( name, method ) { 5029 var m = this.settings.messages[ name ]; 5030 return m && ( m.constructor === String ? m : m[ method ] ); 5031 }, 5032 5033 // Return the first defined argument, allowing empty strings 5034 findDefined: function() { 5035 for ( var i = 0; i < arguments.length; i++ ) { 5036 if ( arguments[ i ] !== undefined ) { 5037 return arguments[ i ]; 5038 } 5039 } 5040 return undefined; 5041 }, 5042 5043 // The second parameter 'rule' used to be a string, and extended to an object literal 5044 // of the following form: 5045 // rule = { 5046 // method: "method name", 5047 // parameters: "the given method parameters" 5048 // } 5049 // 5050 // The old behavior still supported, kept to maintain backward compatibility with 5051 // old code, and will be removed in the next major release. 5052 defaultMessage: function( element, rule ) { 5053 if ( typeof rule === "string" ) { 5054 rule = { method: rule }; 5055 } 5056 5057 var message = this.findDefined( 5058 this.customMessage( element.name, rule.method ), 5059 this.customDataMessage( element, rule.method ), 5060 5061 // 'title' is never undefined, so handle empty string as undefined 5062 !this.settings.ignoreTitle && element.title || undefined, 5063 $.validator.messages[ rule.method ], 5064 "<strong>Warning: No message defined for " + element.name + "</strong>" 5065 ), 5066 theregex = /\$?\{(\d+)\}/g; 5067 if ( typeof message === "function" ) { 5068 message = message.call( this, rule.parameters, element ); 5069 } else if ( theregex.test( message ) ) { 5070 message = $.validator.format( message.replace( theregex, "{$1}" ), rule.parameters ); 5071 } 5072 5073 return message; 5074 }, 5075 5076 formatAndAdd: function( element, rule ) { 5077 var message = this.defaultMessage( element, rule ); 5078 5079 this.errorList.push( { 5080 message: message, 5081 element: element, 5082 method: rule.method 5083 } ); 5084 5085 this.errorMap[ element.name ] = message; 5086 this.submitted[ element.name ] = message; 5087 }, 5088 5089 addWrapper: function( toToggle ) { 5090 if ( this.settings.wrapper ) { 5091 toToggle = toToggle.add( toToggle.parent( this.settings.wrapper ) ); 5092 } 5093 return toToggle; 5094 }, 5095 5096 defaultShowErrors: function() { 5097 var i, elements, error; 5098 for ( i = 0; this.errorList[ i ]; i++ ) { 5099 error = this.errorList[ i ]; 5100 if ( this.settings.highlight ) { 5101 this.settings.highlight.call( this, error.element, this.settings.errorClass, this.settings.validClass ); 5102 } 5103 this.showLabel( error.element, error.message ); 5104 } 5105 if ( this.errorList.length ) { 5106 this.toShow = this.toShow.add( this.containers ); 5107 } 5108 if ( this.settings.success ) { 5109 for ( i = 0; this.successList[ i ]; i++ ) { 5110 this.showLabel( this.successList[ i ] ); 5111 } 5112 } 5113 if ( this.settings.unhighlight ) { 5114 for ( i = 0, elements = this.validElements(); elements[ i ]; i++ ) { 5115 this.settings.unhighlight.call( this, elements[ i ], this.settings.errorClass, this.settings.validClass ); 5116 } 5117 } 5118 this.toHide = this.toHide.not( this.toShow ); 5119 this.hideErrors(); 5120 this.addWrapper( this.toShow ).show(); 5121 }, 5122 5123 validElements: function() { 5124 return this.currentElements.not( this.invalidElements() ); 5125 }, 5126 5127 invalidElements: function() { 5128 return $( this.errorList ).map( function() { 5129 return this.element; 5130 } ); 5131 }, 5132 5133 showLabel: function( element, message ) { 5134 var place, group, errorID, v, 5135 error = this.errorsFor( element ), 5136 elementID = this.idOrName( element ), 5137 describedBy = $( element ).attr( "aria-describedby" ); 5138 5139 if ( error.length ) { 5140 5141 // Refresh error/success class 5142 error.removeClass( this.settings.validClass ).addClass( this.settings.errorClass ); 5143 5144 // Replace message on existing label 5145 error.html( message ); 5146 } else { 5147 5148 // Create error element 5149 error = $( "<" + this.settings.errorElement + ">" ) 5150 .attr( "id", elementID + "-error" ) 5151 .addClass( this.settings.errorClass ) 5152 .html( message || "" ); 5153 5154 // Maintain reference to the element to be placed into the DOM 5155 place = error; 5156 if ( this.settings.wrapper ) { 5157 5158 // Make sure the element is visible, even in IE 5159 // actually showing the wrapped element is handled elsewhere 5160 place = error.hide().show().wrap( "<" + this.settings.wrapper + "/>" ).parent(); 5161 } 5162 if ( this.labelContainer.length ) { 5163 this.labelContainer.append( place ); 5164 } else if ( this.settings.errorPlacement ) { 5165 this.settings.errorPlacement.call( this, place, $( element ) ); 5166 } else { 5167 place.insertAfter( element ); 5168 } 5169 5170 // Link error back to the element 5171 if ( error.is( "label" ) ) { 5172 5173 // If the error is a label, then associate using 'for' 5174 error.attr( "for", elementID ); 5175 5176 // If the element is not a child of an associated label, then it's necessary 5177 // to explicitly apply aria-describedby 5178 } else if ( error.parents( "label[for='" + this.escapeCssMeta( elementID ) + "']" ).length === 0 ) { 5179 errorID = error.attr( "id" ); 5180 5181 // Respect existing non-error aria-describedby 5182 if ( !describedBy ) { 5183 describedBy = errorID; 5184 } else if ( !describedBy.match( new RegExp( "\\b" + this.escapeCssMeta( errorID ) + "\\b" ) ) ) { 5185 5186 // Add to end of list if not already present 5187 describedBy += " " + errorID; 5188 } 5189 $( element ).attr( "aria-describedby", describedBy ); 5190 5191 // If this element is grouped, then assign to all elements in the same group 5192 group = this.groups[ element.name ]; 5193 if ( group ) { 5194 v = this; 5195 $.each( v.groups, function( name, testgroup ) { 5196 if ( testgroup === group ) { 5197 $( "[name='" + v.escapeCssMeta( name ) + "']", v.currentForm ) 5198 .attr( "aria-describedby", error.attr( "id" ) ); 5199 } 5200 } ); 5201 } 5202 } 5203 } 5204 if ( !message && this.settings.success ) { 5205 error.text( "" ); 5206 if ( typeof this.settings.success === "string" ) { 5207 error.addClass( this.settings.success ); 5208 } else { 5209 this.settings.success( error, element ); 5210 } 5211 } 5212 this.toShow = this.toShow.add( error ); 5213 }, 5214 5215 errorsFor: function( element ) { 5216 var name = this.escapeCssMeta( this.idOrName( element ) ), 5217 describer = $( element ).attr( "aria-describedby" ), 5218 selector = "label[for='" + name + "'], label[for='" + name + "'] *"; 5219 5220 // 'aria-describedby' should directly reference the error element 5221 if ( describer ) { 5222 selector = selector + ", #" + this.escapeCssMeta( describer ) 5223 .replace( /\s+/g, ", #" ); 5224 } 5225 5226 return this 5227 .errors() 5228 .filter( selector ); 5229 }, 5230 5231 // See https://api.jquery.com/category/selectors/, for CSS 5232 // meta-characters that should be escaped in order to be used with JQuery 5233 // as a literal part of a name/id or any selector. 5234 escapeCssMeta: function( string ) { 5235 return string.replace( /([\\!"#$%&'()*+,./:;<=>?@\[\]^`{|}~])/g, "\\$1" ); 5236 }, 5237 5238 idOrName: function( element ) { 5239 return this.groups[ element.name ] || ( this.checkable( element ) ? element.name : element.id || element.name ); 5240 }, 5241 5242 validationTargetFor: function( element ) { 5243 5244 // If radio/checkbox, validate first element in group instead 5245 if ( this.checkable( element ) ) { 5246 element = this.findByName( element.name ); 5247 } 5248 5249 // Always apply ignore filter 5250 return $( element ).not( this.settings.ignore )[ 0 ]; 5251 }, 5252 5253 checkable: function( element ) { 5254 return ( /radio|checkbox/i ).test( element.type ); 5255 }, 5256 5257 findByName: function( name ) { 5258 return $( this.currentForm ).find( "[name='" + this.escapeCssMeta( name ) + "']" ); 5259 }, 5260 5261 getLength: function( value, element ) { 5262 switch ( element.nodeName.toLowerCase() ) { 5263 case "select": 5264 return $( "option:selected", element ).length; 5265 case "input": 5266 if ( this.checkable( element ) ) { 5267 return this.findByName( element.name ).filter( ":checked" ).length; 5268 } 5269 } 5270 return value.length; 5271 }, 5272 5273 depend: function( param, element ) { 5274 return this.dependTypes[ typeof param ] ? this.dependTypes[ typeof param ]( param, element ) : true; 5275 }, 5276 5277 dependTypes: { 5278 "boolean": function( param ) { 5279 return param; 5280 }, 5281 "string": function( param, element ) { 5282 return !!$( param, element.form ).length; 5283 }, 5284 "function": function( param, element ) { 5285 return param( element ); 5286 } 5287 }, 5288 5289 optional: function( element ) { 5290 var val = this.elementValue( element ); 5291 return !$.validator.methods.required.call( this, val, element ) && "dependency-mismatch"; 5292 }, 5293 5294 startRequest: function( element ) { 5295 if ( !this.pending[ element.name ] ) { 5296 this.pendingRequest++; 5297 $( element ).addClass( this.settings.pendingClass ); 5298 this.pending[ element.name ] = true; 5299 } 5300 }, 5301 5302 stopRequest: function( element, valid ) { 5303 this.pendingRequest--; 5304 5305 // Sometimes synchronization fails, make sure pendingRequest is never < 0 5306 if ( this.pendingRequest < 0 ) { 5307 this.pendingRequest = 0; 5308 } 5309 delete this.pending[ element.name ]; 5310 $( element ).removeClass( this.settings.pendingClass ); 5311 if ( valid && this.pendingRequest === 0 && this.formSubmitted && this.form() ) { 5312 $( this.currentForm ).submit(); 5313 this.formSubmitted = false; 5314 } else if ( !valid && this.pendingRequest === 0 && this.formSubmitted ) { 5315 $( this.currentForm ).triggerHandler( "invalid-form", [ this ] ); 5316 this.formSubmitted = false; 5317 } 5318 }, 5319 5320 previousValue: function( element, method ) { 5321 method = typeof method === "string" && method || "remote"; 5322 5323 return $.data( element, "previousValue" ) || $.data( element, "previousValue", { 5324 old: null, 5325 valid: true, 5326 message: this.defaultMessage( element, { method: method } ) 5327 } ); 5328 }, 5329 5330 // Cleans up all forms and elements, removes validator-specific events 5331 destroy: function() { 5332 this.resetForm(); 5333 5334 $( this.currentForm ) 5335 .off( ".validate" ) 5336 .removeData( "validator" ) 5337 .find( ".validate-equalTo-blur" ) 5338 .off( ".validate-equalTo" ) 5339 .removeClass( "validate-equalTo-blur" ); 5340 } 5341 5342 }, 5343 5344 classRuleSettings: { 5345 required: { required: true }, 5346 email: { email: true }, 5347 url: { url: true }, 5348 date: { date: true }, 5349 dateISO: { dateISO: true }, 5350 number: { number: true }, 5351 digits: { digits: true }, 5352 creditcard: { creditcard: true } 5353 }, 5354 5355 addClassRules: function( className, rules ) { 5356 if ( className.constructor === String ) { 5357 this.classRuleSettings[ className ] = rules; 5358 } else { 5359 $.extend( this.classRuleSettings, className ); 5360 } 5361 }, 5362 5363 classRules: function( element ) { 5364 var rules = {}, 5365 classes = $( element ).attr( "class" ); 5366 5367 if ( classes ) { 5368 $.each( classes.split( " " ), function() { 5369 if ( this in $.validator.classRuleSettings ) { 5370 $.extend( rules, $.validator.classRuleSettings[ this ] ); 5371 } 5372 } ); 5373 } 5374 return rules; 5375 }, 5376 5377 normalizeAttributeRule: function( rules, type, method, value ) { 5378 5379 // Convert the value to a number for number inputs, and for text for backwards compability 5380 // allows type="date" and others to be compared as strings 5381 if ( /min|max|step/.test( method ) && ( type === null || /number|range|text/.test( type ) ) ) { 5382 value = Number( value ); 5383 5384 // Support Opera Mini, which returns NaN for undefined minlength 5385 if ( isNaN( value ) ) { 5386 value = undefined; 5387 } 5388 } 5389 5390 if ( value || value === 0 ) { 5391 rules[ method ] = value; 5392 } else if ( type === method && type !== "range" ) { 5393 5394 // Exception: the jquery validate 'range' method 5395 // does not test for the html5 'range' type 5396 rules[ method ] = true; 5397 } 5398 }, 5399 5400 attributeRules: function( element ) { 5401 var rules = {}, 5402 $element = $( element ), 5403 type = element.getAttribute( "type" ), 5404 method, value; 5405 5406 for ( method in $.validator.methods ) { 5407 5408 // Support for <input required> in both html5 and older browsers 5409 if ( method === "required" ) { 5410 value = element.getAttribute( method ); 5411 5412 // Some browsers return an empty string for the required attribute 5413 // and non-HTML5 browsers might have required="" markup 5414 if ( value === "" ) { 5415 value = true; 5416 } 5417 5418 // Force non-HTML5 browsers to return bool 5419 value = !!value; 5420 } else { 5421 value = $element.attr( method ); 5422 } 5423 5424 this.normalizeAttributeRule( rules, type, method, value ); 5425 } 5426 5427 // 'maxlength' may be returned as -1, 2147483647 ( IE ) and 524288 ( safari ) for text inputs 5428 if ( rules.maxlength && /-1|2147483647|524288/.test( rules.maxlength ) ) { 5429 delete rules.maxlength; 5430 } 5431 5432 return rules; 5433 }, 5434 5435 dataRules: function( element ) { 5436 var rules = {}, 5437 $element = $( element ), 5438 type = element.getAttribute( "type" ), 5439 method, value; 5440 5441 for ( method in $.validator.methods ) { 5442 value = $element.data( "rule" + method.charAt( 0 ).toUpperCase() + method.substring( 1 ).toLowerCase() ); 5443 this.normalizeAttributeRule( rules, type, method, value ); 5444 } 5445 return rules; 5446 }, 5447 5448 staticRules: function( element ) { 5449 var rules = {}, 5450 validator = $.data( element.form, "validator" ); 5451 5452 if ( validator.settings.rules ) { 5453 rules = $.validator.normalizeRule( validator.settings.rules[ element.name ] ) || {}; 5454 } 5455 return rules; 5456 }, 5457 5458 normalizeRules: function( rules, element ) { 5459 5460 // Handle dependency check 5461 $.each( rules, function( prop, val ) { 5462 5463 // Ignore rule when param is explicitly false, eg. required:false 5464 if ( val === false ) { 5465 delete rules[ prop ]; 5466 return; 5467 } 5468 if ( val.param || val.depends ) { 5469 var keepRule = true; 5470 switch ( typeof val.depends ) { 5471 case "string": 5472 keepRule = !!$( val.depends, element.form ).length; 5473 break; 5474 case "function": 5475 keepRule = val.depends.call( element, element ); 5476 break; 5477 } 5478 if ( keepRule ) { 5479 rules[ prop ] = val.param !== undefined ? val.param : true; 5480 } else { 5481 $.data( element.form, "validator" ).resetElements( $( element ) ); 5482 delete rules[ prop ]; 5483 } 5484 } 5485 } ); 5486 5487 // Evaluate parameters 5488 $.each( rules, function( rule, parameter ) { 5489 rules[ rule ] = $.isFunction( parameter ) && rule !== "normalizer" ? parameter( element ) : parameter; 5490 } ); 5491 5492 // Clean number parameters 5493 $.each( [ "minlength", "maxlength" ], function() { 5494 if ( rules[ this ] ) { 5495 rules[ this ] = Number( rules[ this ] ); 5496 } 5497 } ); 5498 $.each( [ "rangelength", "range" ], function() { 5499 var parts; 5500 if ( rules[ this ] ) { 5501 if ( $.isArray( rules[ this ] ) ) { 5502 rules[ this ] = [ Number( rules[ this ][ 0 ] ), Number( rules[ this ][ 1 ] ) ]; 5503 } else if ( typeof rules[ this ] === "string" ) { 5504 parts = rules[ this ].replace( /[\[\]]/g, "" ).split( /[\s,]+/ ); 5505 rules[ this ] = [ Number( parts[ 0 ] ), Number( parts[ 1 ] ) ]; 5506 } 5507 } 5508 } ); 5509 5510 if ( $.validator.autoCreateRanges ) { 5511 5512 // Auto-create ranges 5513 if ( rules.min != null && rules.max != null ) { 5514 rules.range = [ rules.min, rules.max ]; 5515 delete rules.min; 5516 delete rules.max; 5517 } 5518 if ( rules.minlength != null && rules.maxlength != null ) { 5519 rules.rangelength = [ rules.minlength, rules.maxlength ]; 5520 delete rules.minlength; 5521 delete rules.maxlength; 5522 } 5523 } 5524 5525 return rules; 5526 }, 5527 5528 // Converts a simple string to a {string: true} rule, e.g., "required" to {required:true} 5529 normalizeRule: function( data ) { 5530 if ( typeof data === "string" ) { 5531 var transformed = {}; 5532 $.each( data.split( /\s/ ), function() { 5533 transformed[ this ] = true; 5534 } ); 5535 data = transformed; 5536 } 5537 return data; 5538 }, 5539 5540 // http://jqueryvalidation.org/jQuery.validator.addMethod/ 5541 addMethod: function( name, method, message ) { 5542 $.validator.methods[ name ] = method; 5543 $.validator.messages[ name ] = message !== undefined ? message : $.validator.messages[ name ]; 5544 if ( method.length < 3 ) { 5545 $.validator.addClassRules( name, $.validator.normalizeRule( name ) ); 5546 } 5547 }, 5548 5549 // http://jqueryvalidation.org/jQuery.validator.methods/ 5550 methods: { 5551 5552 // http://jqueryvalidation.org/required-method/ 5553 required: function( value, element, param ) { 5554 5555 // Check if dependency is met 5556 if ( !this.depend( param, element ) ) { 5557 return "dependency-mismatch"; 5558 } 5559 if ( element.nodeName.toLowerCase() === "select" ) { 5560 5561 // Could be an array for select-multiple or a string, both are fine this way 5562 var val = $( element ).val(); 5563 return val && val.length > 0; 5564 } 5565 if ( this.checkable( element ) ) { 5566 return this.getLength( value, element ) > 0; 5567 } 5568 return value.length > 0; 5569 }, 5570 5571 // http://jqueryvalidation.org/email-method/ 5572 email: function( value, element ) { 5573 5574 // From https://html.spec.whatwg.org/multipage/forms.html#valid-e-mail-address 5575 // Retrieved 2014-01-14 5576 // If you have a problem with this implementation, report a bug against the above spec 5577 // Or use custom methods to implement your own email validation 5578 return this.optional( element ) || /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[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])?)*$/.test( value ); 5579 }, 5580 5581 // http://jqueryvalidation.org/url-method/ 5582 url: function( value, element ) { 5583 5584 // Copyright (c) 2010-2013 Diego Perini, MIT licensed 5585 // https://gist.github.com/dperini/729294 5586 // see also https://mathiasbynens.be/demo/url-regex 5587 // modified to allow protocol-relative URLs 5588 return this.optional( element ) || /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test( value ); 5589 }, 5590 5591 // http://jqueryvalidation.org/date-method/ 5592 date: function( value, element ) { 5593 return this.optional( element ) || !/Invalid|NaN/.test( new Date( value ).toString() ); 5594 }, 5595 5596 // http://jqueryvalidation.org/dateISO-method/ 5597 dateISO: function( value, element ) { 5598 return this.optional( element ) || /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test( value ); 5599 }, 5600 5601 // http://jqueryvalidation.org/number-method/ 5602 number: function( value, element ) { 5603 return this.optional( element ) || /^(?:-?\d+|-?\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test( value ); 5604 }, 5605 5606 // http://jqueryvalidation.org/digits-method/ 5607 digits: function( value, element ) { 5608 return this.optional( element ) || /^\d+$/.test( value ); 5609 }, 5610 5611 // http://jqueryvalidation.org/minlength-method/ 5612 minlength: function( value, element, param ) { 5613 var length = $.isArray( value ) ? value.length : this.getLength( value, element ); 5614 return this.optional( element ) || length >= param; 5615 }, 5616 5617 // http://jqueryvalidation.org/maxlength-method/ 5618 maxlength: function( value, element, param ) { 5619 var length = $.isArray( value ) ? value.length : this.getLength( value, element ); 5620 return this.optional( element ) || length <= param; 5621 }, 5622 5623 // http://jqueryvalidation.org/rangelength-method/ 5624 rangelength: function( value, element, param ) { 5625 var length = $.isArray( value ) ? value.length : this.getLength( value, element ); 5626 return this.optional( element ) || ( length >= param[ 0 ] && length <= param[ 1 ] ); 5627 }, 5628 5629 // http://jqueryvalidation.org/min-method/ 5630 min: function( value, element, param ) { 5631 return this.optional( element ) || value >= param; 5632 }, 5633 5634 // http://jqueryvalidation.org/max-method/ 5635 max: function( value, element, param ) { 5636 return this.optional( element ) || value <= param; 5637 }, 5638 5639 // http://jqueryvalidation.org/range-method/ 5640 range: function( value, element, param ) { 5641 return this.optional( element ) || ( value >= param[ 0 ] && value <= param[ 1 ] ); 5642 }, 5643 5644 // http://jqueryvalidation.org/step-method/ 5645 step: function( value, element, param ) { 5646 var type = $( element ).attr( "type" ), 5647 errorMessage = "Step attribute on input type " + type + " is not supported.", 5648 supportedTypes = [ "text", "number", "range" ], 5649 re = new RegExp( "\\b" + type + "\\b" ), 5650 notSupported = type && !re.test( supportedTypes.join() ), 5651 decimalPlaces = function( num ) { 5652 var match = ( "" + num ).match( /(?:\.(\d+))?$/ ); 5653 if ( !match ) { 5654 return 0; 5655 } 5656 5657 // Number of digits right of decimal point. 5658 return match[ 1 ] ? match[ 1 ].length : 0; 5659 }, 5660 toInt = function( num ) { 5661 return Math.round( num * Math.pow( 10, decimals ) ); 5662 }, 5663 valid = true, 5664 decimals; 5665 5666 // Works only for text, number and range input types 5667 // TODO find a way to support input types date, datetime, datetime-local, month, time and week 5668 if ( notSupported ) { 5669 throw new Error( errorMessage ); 5670 } 5671 5672 decimals = decimalPlaces( param ); 5673 5674 // Value can't have too many decimals 5675 if ( decimalPlaces( value ) > decimals || toInt( value ) % toInt( param ) !== 0 ) { 5676 valid = false; 5677 } 5678 5679 return this.optional( element ) || valid; 5680 }, 5681 5682 // http://jqueryvalidation.org/equalTo-method/ 5683 equalTo: function( value, element, param ) { 5684 5685 // Bind to the blur event of the target in order to revalidate whenever the target field is updated 5686 var target = $( param ); 5687 if ( this.settings.onfocusout && target.not( ".validate-equalTo-blur" ).length ) { 5688 target.addClass( "validate-equalTo-blur" ).on( "blur.validate-equalTo", function() { 5689 $( element ).valid(); 5690 } ); 5691 } 5692 return value === target.val(); 5693 }, 5694 5695 // http://jqueryvalidation.org/remote-method/ 5696 remote: function( value, element, param, method ) { 5697 if ( this.optional( element ) ) { 5698 return "dependency-mismatch"; 5699 } 5700 5701 method = typeof method === "string" && method || "remote"; 5702 5703 var previous = this.previousValue( element, method ), 5704 validator, data, optionDataString; 5705 5706 if ( !this.settings.messages[ element.name ] ) { 5707 this.settings.messages[ element.name ] = {}; 5708 } 5709 previous.originalMessage = previous.originalMessage || this.settings.messages[ element.name ][ method ]; 5710 this.settings.messages[ element.name ][ method ] = previous.message; 5711 5712 param = typeof param === "string" && { url: param } || param; 5713 optionDataString = $.param( $.extend( { data: value }, param.data ) ); 5714 if ( previous.old === optionDataString ) { 5715 return previous.valid; 5716 } 5717 5718 previous.old = optionDataString; 5719 validator = this;
5720 this.startRequest( element ); 5721 data = {}; 5722 data[ element.name ] = value; 5723 $.ajax( $.extend( true, { 5724 mode: "abort", 5725 port: "validate" + element.name, 5726 dataType: "json", 5727 data: data, 5728 context: validator.currentForm, 5729 success: function( response ) { 5730 var valid = response === true || response === "true", 5731 errors, message, submitted; 5732 5733 validator.settings.messages[ element.name ][ method ] = previous.originalMessage; 5734 if ( valid ) { 5735 submitted = validator.formSubmitted; 5736 validator.resetInternals(); 5737 validator.toHide = validator.errorsFor( element ); 5738 validator.formSubmitted = submitted; 5739 validator.successList.push( element ); 5740 validator.invalid[ element.name ] = false; 5741 validator.showErrors(); 5742 } else { 5743 errors = {}; 5744 message = response || validator.defaultMessage( element, { method: method, parameters: value } ); 5745 errors[ element.name ] = previous.message = message; 5746 validator.invalid[ element.name ] = true; 5747 validator.showErrors( errors ); 5748 } 5749 previous.valid = valid; 5750 validator.stopRequest( element, valid ); 5751 } 5752 }, param ) ); 5753 return "pending"; 5754 } 5755 } 5756 5757} ); 5758 5759// Ajax mode: abort 5760// usage: $.ajax({ mode: "abort"[, port: "uniqueport"]}); 5761// if mode:"abort" is used, the previous request on that port (port can be undefined) is aborted via XMLHttpRequest.abort() 5762 5763var pendingRequests = {}, 5764 ajax; 5765 5766// Use a prefilter if available (1.5+) 5767if ( $.ajaxPrefilter ) { 5768 $.ajaxPrefilter( function( settings, _, xhr ) { 5769 var port = settings.port; 5770 if ( settings.mode === "abort" ) { 5771 if ( pendingRequests[ port ] ) { 5772 pendingRequests[ port ].abort(); 5773 } 5774 pendingRequests[ port ] = xhr; 5775 } 5776 } ); 5777} else { 5778 5779 // Proxy ajax 5780 ajax = $.ajax; 5781 $.ajax = function( settings ) { 5782 var mode = ( "mode" in settings ? settings : $.ajaxSettings ).mode, 5783 port = ( "port" in settings ? settings : $.ajaxSettings ).port; 5784 if ( mode === "abort" ) { 5785 if ( pendingRequests[ port ] ) { 5786 pendingRequests[ port ].abort(); 5787 } 5788 pendingRequests[ port ] = ajax.apply( this, arguments ); 5789 return pendingRequests[ port ]; 5790 } 5791 return ajax.apply( this, arguments ); 5792 }; 5793} 5794 5795})); 5796/** 5797 * @license AngularJS v1.5.11 5798 * (c) 2010-2017 Google, Inc. http://angularjs.org 5799 * License: MIT 5800 */ 5801(function(window) {'use strict'; 5802 5803/** 5804 * @description 5805 * 5806 * This object provides a utility for producing rich Error messages within 5807 * Angular. It can be called as follows: 5808 * 5809 * var exampleMinErr = minErr('example'); 5810 * throw exampleMinErr('one', 'This {0} is {1}', foo, bar); 5811 * 5812 * The above creates an instance of minErr in the example namespace. The 5813 * resulting error will have a namespaced error code of example.one. The 5814 * resulting error will replace {0} with the value of foo, and {1} with the 5815 * value of bar. The object is not restricted in the number of arguments it can 5816 * take. 5817 * 5818 * If fewer arguments are specified than necessary for interpolation, the extra 5819 * interpolation markers will be preserved in the final string. 5820 * 5821 * Since data will be parsed statically during a build step, some restrictions 5822 * are applied with respect to how minErr instances are created and called. 5823 * Instances should have names of the form namespaceMinErr for a minErr created 5824 * using minErr('namespace') . Error codes, namespaces and template strings 5825 * should all be static strings, not variables or general expressions. 5826 * 5827 * @param {string} module The namespace to use for the new minErr instance. 5828 * @param {function} ErrorConstructor Custom error constructor to be instantiated when returning 5829 * error from returned function, for cases when a particular type of error is useful. 5830 * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance 5831 */ 5832 5833function minErr(module, ErrorConstructor) { 5834 ErrorConstructor = ErrorConstructor || Error; 5835 return function() { 5836 var SKIP_INDEXES = 2; 5837 5838 var templateArgs = arguments, 5839 code = templateArgs[0], 5840 message = '[' + (module ? module + ':' : '') + code + '] ', 5841 template = templateArgs[1], 5842 paramPrefix, i; 5843 5844 message += template.replace(/\{\d+\}/g, function(match) { 5845 var index = +match.slice(1, -1), 5846 shiftedIndex = index + SKIP_INDEXES; 5847 5848 if (shiftedIndex < templateArgs.length) { 5849 return toDebugString(templateArgs[shiftedIndex]); 5850 } 5851 5852 return match; 5853 }); 5854 5855 message += '\nhttp://errors.angularjs.org/1.5.11/' + 5856 (module ? module + '/' : '') + code; 5857 5858 for (i = SKIP_INDEXES, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
5859 message += paramPrefix + 'p' + (i - SKIP_INDEXES) + '=' + 5860 encodeURIComponent(toDebugString(templateArgs[i])); 5861 } 5862 5863 return new ErrorConstructor(message); 5864 }; 5865} 5866 5867/* We need to tell ESLint what variables are being exported */ 5868/* exported 5869 angular, 5870 msie, 5871 jqLite, 5872 jQuery, 5873 slice, 5874 splice, 5875 push, 5876 toString, 5877 ngMinErr, 5878 angularModule, 5879 uid, 5880 REGEX_STRING_REGEXP, 5881 VALIDITY_STATE_PROPERTY, 5882 5883 lowercase, 5884 uppercase, 5885 manualLowercase, 5886 manualUppercase, 5887 nodeName_, 5888 isArrayLike, 5889 forEach, 5890 forEachSorted, 5891 reverseParams, 5892 nextUid, 5893 setHashKey, 5894 extend, 5895 toInt, 5896 inherit, 5897 merge, 5898 noop, 5899 identity, 5900 valueFn, 5901 isUndefined, 5902 isDefined, 5903 isObject, 5904 isBlankObject, 5905 isString, 5906 isNumber, 5907 isNumberNaN, 5908 isDate, 5909 isArray, 5910 isFunction, 5911 isRegExp, 5912 isWindow, 5913 isScope, 5914 isFile, 5915 isFormData, 5916 isBlob, 5917 isBoolean, 5918 isPromiseLike, 5919 trim, 5920 escapeForRegexp, 5921 isElement, 5922 makeMap, 5923 includes, 5924 arrayRemove, 5925 copy, 5926 equals, 5927 csp, 5928 jq, 5929 concat, 5930 sliceArgs, 5931 bind, 5932 toJsonReplacer, 5933 toJson, 5934 fromJson, 5935 convertTimezoneToLocal, 5936 timezoneToOffset, 5937 startingTag, 5938 tryDecodeURIComponent, 5939 parseKeyValue, 5940 toKeyValue, 5941 encodeUriSegment, 5942 encodeUriQuery, 5943 angularInit, 5944 bootstrap, 5945 getTestability, 5946 snake_case, 5947 bindJQuery, 5948 assertArg, 5949 assertArgFn, 5950 assertNotHasOwnProperty, 5951 getter, 5952 getBlockNodes, 5953 hasOwnProperty, 5954 createMap, 5955 5956 NODE_TYPE_ELEMENT, 5957 NODE_TYPE_ATTRIBUTE, 5958 NODE_TYPE_TEXT, 5959 NODE_TYPE_COMMENT, 5960 NODE_TYPE_DOCUMENT, 5961 NODE_TYPE_DOCUMENT_FRAGMENT 5962*/ 5963 5964//////////////////////////////////// 5965 5966/** 5967 * @ngdoc module 5968 * @name ng 5969 * @module ng 5970 * @installation 5971 * @description 5972 * 5973 * # ng (core module) 5974 * The ng module is loaded by default when an AngularJS application is started. The module itself 5975 * contains the essential components for an AngularJS application to function. The table below 5976 * lists a high level breakdown of each of the services/factories, filters, directives and testing 5977 * components available within this core module. 5978 * 5979 * <div doc-module-components="ng"></div> 5980 */ 5981 5982var REGEX_STRING_REGEXP = /^\/(.+)\/([a-z]*)$/; 5983 5984// The name of a form control's ValidityState property. 5985// This is used so that it's possible for internal tests to create mock ValidityStates. 5986var VALIDITY_STATE_PROPERTY = 'validity'; 5987 5988var hasOwnProperty = Object.prototype.hasOwnProperty; 5989 5990var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;}; 5991var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;}; 5992 5993 5994var manualLowercase = function(s) { 5995 /* eslint-disable no-bitwise */ 5996 return isString(s) 5997 ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);}) 5998 : s; 5999 /* eslint-enable */ 6000}; 6001var manualUppercase = function(s) { 6002 /* eslint-disable no-bitwise */ 6003 return isString(s) 6004 ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);}) 6005 : s; 6006 /* eslint-enable */ 6007}; 6008 6009 6010// String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish 6011// locale, for this reason we need to detect this case and redefine lowercase/uppercase methods 6012// with correct but slower alternatives. See https://github.com/angular/angular.js/issues/11387 6013if ('i' !== 'I'.toLowerCase()) { 6014 lowercase = manualLowercase; 6015 uppercase = manualUppercase; 6016} 6017 6018 6019var 6020 msie, // holds major version number for IE, or NaN if UA is not IE. 6021 jqLite, // delay binding since jQuery could be loaded after us. 6022 jQuery, // delay binding 6023 slice = [].slice, 6024 splice = [].splice, 6025 push = [].push, 6026 toString = Object.prototype.toString, 6027 getPrototypeOf = Object.getPrototypeOf, 6028 ngMinErr = minErr('ng'), 6029 6030 /** @name angular */ 6031 angular = window.angular || (window.angular = {}), 6032 angularModule, 6033 uid = 0; 6034 6035/** 6036 * documentMode is an IE-only property 6037 * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx 6038 */ 6039msie = window.document.documentMode; 6040 6041 6042/** 6043 * @private 6044 * @param {*} obj 6045 * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, 6046 * String ...) 6047 */ 6048function isArrayLike(obj) { 6049 6050 // `null`, `undefined` and `window` are not array-like 6051 if (obj == null || isWindow(obj)) return false;
vendor: 15,053 bytes, lines 6052-6624
6052 6053 // arrays, strings and jQuery/jqLite objects are array like 6054 // * jqLite is either the jQuery or jqLite constructor function 6055 // * we have to check the existence of jqLite first as this method is called 6056 // via the forEach method when constructing the jqLite object in the first place 6057 if (isArray(obj) || isString(obj) || (jqLite && obj instanceof jqLite)) return true; 6058 6059 // Support: iOS 8.2 (not reproducible in simulator) 6060 // "length" in obj used to prevent JIT error (gh-11508) 6061 var length = 'length' in Object(obj) && obj.length; 6062 6063 // NodeList objects (with `item` method) and 6064 // other objects with suitable length characteristics are array-like 6065 return isNumber(length) && 6066 (length >= 0 && ((length - 1) in obj || obj instanceof Array) || typeof obj.item === 'function'); 6067 6068} 6069 6070/** 6071 * @ngdoc function 6072 * @name angular.forEach 6073 * @module ng 6074 * @kind function 6075 * 6076 * @description 6077 * Invokes the `iterator` function once for each item in `obj` collection, which can be either an 6078 * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value` 6079 * is the value of an object property or an array element, `key` is the object property key or 6080 * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional. 6081 * 6082 * It is worth noting that `.forEach` does not iterate over inherited properties because it filters 6083 * using the `hasOwnProperty` method. 6084 * 6085 * Unlike ES262's 6086 * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18), 6087 * providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just 6088 * return the value provided. 6089 * 6090 ```js 6091 var values = {name: 'misko', gender: 'male'}; 6092 var log = []; 6093 angular.forEach(values, function(value, key) { 6094 this.push(key + ': ' + value); 6095 }, log); 6096 expect(log).toEqual(['name: misko', 'gender: male']); 6097 ``` 6098 * 6099 * @param {Object|Array} obj Object to iterate over. 6100 * @param {Function} iterator Iterator function. 6101 * @param {Object=} context Object to become context (`this`) for the iterator function. 6102 * @returns {Object|Array} Reference to `obj`. 6103 */ 6104 6105function forEach(obj, iterator, context) { 6106 var key, length; 6107 if (obj) { 6108 if (isFunction(obj)) { 6109 for (key in obj) { 6110 // Need to check if hasOwnProperty exists, 6111 // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function 6112 if (key !== 'prototype' && key !== 'length' && key !== 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) { 6113 iterator.call(context, obj[key], key, obj); 6114 } 6115 } 6116 } else if (isArray(obj) || isArrayLike(obj)) { 6117 var isPrimitive = typeof obj !== 'object'; 6118 for (key = 0, length = obj.length; key < length; key++) { 6119 if (isPrimitive || key in obj) { 6120 iterator.call(context, obj[key], key, obj); 6121 } 6122 } 6123 } else if (obj.forEach && obj.forEach !== forEach) { 6124 obj.forEach(iterator, context, obj); 6125 } else if (isBlankObject(obj)) { 6126 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty 6127 for (key in obj) { 6128 iterator.call(context, obj[key], key, obj); 6129 } 6130 } else if (typeof obj.hasOwnProperty === 'function') { 6131 // Slow path for objects inheriting Object.prototype, hasOwnProperty check needed 6132 for (key in obj) { 6133 if (obj.hasOwnProperty(key)) { 6134 iterator.call(context, obj[key], key, obj); 6135 } 6136 } 6137 } else { 6138 // Slow path for objects which do not have a method `hasOwnProperty` 6139 for (key in obj) { 6140 if (hasOwnProperty.call(obj, key)) { 6141 iterator.call(context, obj[key], key, obj); 6142 } 6143 } 6144 } 6145 } 6146 return obj; 6147} 6148 6149function forEachSorted(obj, iterator, context) { 6150 var keys = Object.keys(obj).sort(); 6151 for (var i = 0; i < keys.length; i++) { 6152 iterator.call(context, obj[keys[i]], keys[i]); 6153 } 6154 return keys; 6155} 6156 6157 6158/** 6159 * when using forEach the params are value, key, but it is often useful to have key, value. 6160 * @param {function(string, *)} iteratorFn 6161 * @returns {function(*, string)} 6162 */ 6163function reverseParams(iteratorFn) { 6164 return function(value, key) {iteratorFn(key, value);}; 6165} 6166 6167/** 6168 * A consistent way of creating unique IDs in angular. 6169 * 6170 * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before 6171 * we hit number precision issues in JavaScript. 6172 * 6173 * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M 6174 * 6175 * @returns {number} an unique alpha-numeric string 6176 */ 6177function nextUid() { 6178 return ++uid; 6179} 6180 6181 6182/** 6183 * Set or clear the hashkey for an object. 6184 * @param obj object 6185 * @param h the hashkey (!truthy to delete the hashkey) 6186 */ 6187function setHashKey(obj, h) { 6188 if (h) { 6189 obj.$$hashKey = h; 6190 } else { 6191 delete obj.$$hashKey; 6192 } 6193} 6194 6195 6196function baseExtend(dst, objs, deep) { 6197 var h = dst.$$hashKey; 6198 6199 for (var i = 0, ii = objs.length; i < ii; ++i) { 6200 var obj = objs[i]; 6201 if (!isObject(obj) && !isFunction(obj)) continue; 6202 var keys = Object.keys(obj); 6203 for (var j = 0, jj = keys.length; j < jj; j++) { 6204 var key = keys[j]; 6205 var src = obj[key]; 6206 6207 if (deep && isObject(src)) { 6208 if (isDate(src)) { 6209 dst[key] = new Date(src.valueOf()); 6210 } else if (isRegExp(src)) { 6211 dst[key] = new RegExp(src); 6212 } else if (src.nodeName) { 6213 dst[key] = src.cloneNode(true); 6214 } else if (isElement(src)) { 6215 dst[key] = src.clone(); 6216 } else { 6217 if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {}; 6218 baseExtend(dst[key], [src], true); 6219 } 6220 } else { 6221 dst[key] = src; 6222 } 6223 } 6224 } 6225 6226 setHashKey(dst, h); 6227 return dst; 6228} 6229 6230/** 6231 * @ngdoc function 6232 * @name angular.extend 6233 * @module ng 6234 * @kind function 6235 * 6236 * @description 6237 * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s) 6238 * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so 6239 * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`. 6240 * 6241 * **Note:** Keep in mind that `angular.extend` does not support recursive merge (deep copy). Use 6242 * {@link angular.merge} for this. 6243 * 6244 * @param {Object} dst Destination object. 6245 * @param {...Object} src Source object(s). 6246 * @returns {Object} Reference to `dst`. 6247 */ 6248function extend(dst) { 6249 return baseExtend(dst, slice.call(arguments, 1), false); 6250} 6251 6252 6253/** 6254* @ngdoc function 6255* @name angular.merge 6256* @module ng 6257* @kind function 6258* 6259* @description 6260* Deeply extends the destination object `dst` by copying own enumerable properties from the `src` object(s) 6261* to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so 6262* by passing an empty object as the target: `var object = angular.merge({}, object1, object2)`. 6263* 6264* Unlike {@link angular.extend extend()}, `merge()` recursively descends into object properties of source 6265* objects, performing a deep copy. 6266* 6267* @param {Object} dst Destination object. 6268* @param {...Object} src Source object(s). 6269* @returns {Object} Reference to `dst`. 6270*/ 6271function merge(dst) { 6272 return baseExtend(dst, slice.call(arguments, 1), true); 6273} 6274 6275 6276 6277function toInt(str) { 6278 return parseInt(str, 10); 6279} 6280 6281var isNumberNaN = Number.isNaN || function isNumberNaN(num) { 6282 // eslint-disable-next-line no-self-compare 6283 return num !== num; 6284}; 6285 6286 6287function inherit(parent, extra) { 6288 return extend(Object.create(parent), extra); 6289} 6290 6291/** 6292 * @ngdoc function 6293 * @name angular.noop 6294 * @module ng 6295 * @kind function 6296 * 6297 * @description 6298 * A function that performs no operations. This function can be useful when writing code in the 6299 * functional style. 6300 ```js 6301 function foo(callback) { 6302 var result = calculateResult(); 6303 (callback || angular.noop)(result); 6304 } 6305 ``` 6306 */ 6307function noop() {} 6308noop.$inject = []; 6309 6310 6311/** 6312 * @ngdoc function 6313 * @name angular.identity 6314 * @module ng 6315 * @kind function 6316 * 6317 * @description 6318 * A function that returns its first argument. This function is useful when writing code in the 6319 * functional style. 6320 * 6321 ```js 6322 function transformer(transformationFn, value) { 6323 return (transformationFn || angular.identity)(value); 6324 }; 6325 6326 // E.g. 6327 function getResult(fn, input) { 6328 return (fn || angular.identity)(input); 6329 }; 6330 6331 getResult(function(n) { return n * 2; }, 21); // returns 42 6332 getResult(null, 21); // returns 21 6333 getResult(undefined, 21); // returns 21 6334 ``` 6335 * 6336 * @param {*} value to be returned. 6337 * @returns {*} the value passed in. 6338 */ 6339function identity($) {return $;} 6340identity.$inject = []; 6341 6342 6343function valueFn(value) {return function valueRef() {return value;};} 6344 6345function hasCustomToString(obj) { 6346 return isFunction(obj.toString) && obj.toString !== toString; 6347} 6348 6349 6350/** 6351 * @ngdoc function 6352 * @name angular.isUndefined 6353 * @module ng 6354 * @kind function 6355 * 6356 * @description 6357 * Determines if a reference is undefined. 6358 * 6359 * @param {*} value Reference to check. 6360 * @returns {boolean} True if `value` is undefined. 6361 */ 6362function isUndefined(value) {return typeof value === 'undefined';} 6363 6364 6365/** 6366 * @ngdoc function 6367 * @name angular.isDefined 6368 * @module ng 6369 * @kind function 6370 * 6371 * @description 6372 * Determines if a reference is defined. 6373 * 6374 * @param {*} value Reference to check. 6375 * @returns {boolean} True if `value` is defined. 6376 */ 6377function isDefined(value) {return typeof value !== 'undefined';} 6378 6379 6380/** 6381 * @ngdoc function 6382 * @name angular.isObject 6383 * @module ng 6384 * @kind function 6385 * 6386 * @description 6387 * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not 6388 * considered to be objects. Note that JavaScript arrays are objects. 6389 * 6390 * @param {*} value Reference to check. 6391 * @returns {boolean} True if `value` is an `Object` but not `null`. 6392 */ 6393function isObject(value) { 6394 // http://jsperf.com/isobject4 6395 return value !== null && typeof value === 'object'; 6396} 6397 6398 6399/** 6400 * Determine if a value is an object with a null prototype 6401 * 6402 * @returns {boolean} True if `value` is an `Object` with a null prototype 6403 */ 6404function isBlankObject(value) { 6405 return value !== null && typeof value === 'object' && !getPrototypeOf(value); 6406} 6407 6408 6409/** 6410 * @ngdoc function 6411 * @name angular.isString 6412 * @module ng 6413 * @kind function 6414 * 6415 * @description 6416 * Determines if a reference is a `String`. 6417 * 6418 * @param {*} value Reference to check. 6419 * @returns {boolean} True if `value` is a `String`. 6420 */ 6421function isString(value) {return typeof value === 'string';} 6422 6423 6424/** 6425 * @ngdoc function 6426 * @name angular.isNumber 6427 * @module ng 6428 * @kind function 6429 * 6430 * @description 6431 * Determines if a reference is a `Number`. 6432 * 6433 * This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`. 6434 * 6435 * If you wish to exclude these then you can use the native 6436 * [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite) 6437 * method. 6438 * 6439 * @param {*} value Reference to check. 6440 * @returns {boolean} True if `value` is a `Number`. 6441 */ 6442function isNumber(value) {return typeof value === 'number';} 6443 6444 6445/** 6446 * @ngdoc function 6447 * @name angular.isDate 6448 * @module ng 6449 * @kind function 6450 * 6451 * @description 6452 * Determines if a value is a date. 6453 * 6454 * @param {*} value Reference to check. 6455 * @returns {boolean} True if `value` is a `Date`. 6456 */ 6457function isDate(value) { 6458 return toString.call(value) === '[object Date]'; 6459} 6460 6461 6462/** 6463 * @ngdoc function 6464 * @name angular.isArray 6465 * @module ng 6466 * @kind function 6467 * 6468 * @description 6469 * Determines if a reference is an `Array`. Alias of Array.isArray. 6470 * 6471 * @param {*} value Reference to check. 6472 * @returns {boolean} True if `value` is an `Array`. 6473 */ 6474var isArray = Array.isArray; 6475 6476/** 6477 * @ngdoc function 6478 * @name angular.isFunction 6479 * @module ng 6480 * @kind function 6481 * 6482 * @description 6483 * Determines if a reference is a `Function`. 6484 * 6485 * @param {*} value Reference to check. 6486 * @returns {boolean} True if `value` is a `Function`. 6487 */ 6488function isFunction(value) {return typeof value === 'function';} 6489 6490 6491/** 6492 * Determines if a value is a regular expression object. 6493 * 6494 * @private 6495 * @param {*} value Reference to check. 6496 * @returns {boolean} True if `value` is a `RegExp`. 6497 */ 6498function isRegExp(value) { 6499 return toString.call(value) === '[object RegExp]'; 6500} 6501 6502 6503/** 6504 * Checks if `obj` is a window object. 6505 * 6506 * @private 6507 * @param {*} obj Object to check 6508 * @returns {boolean} True if `obj` is a window obj. 6509 */ 6510function isWindow(obj) { 6511 return obj && obj.window === obj; 6512} 6513 6514 6515function isScope(obj) { 6516 return obj && obj.$evalAsync && obj.$watch; 6517} 6518 6519 6520function isFile(obj) { 6521 return toString.call(obj) === '[object File]'; 6522} 6523 6524 6525function isFormData(obj) { 6526 return toString.call(obj) === '[object FormData]'; 6527} 6528 6529 6530function isBlob(obj) { 6531 return toString.call(obj) === '[object Blob]'; 6532} 6533 6534 6535function isBoolean(value) { 6536 return typeof value === 'boolean'; 6537} 6538 6539 6540function isPromiseLike(obj) { 6541 return obj && isFunction(obj.then); 6542} 6543 6544 6545var TYPED_ARRAY_REGEXP = /^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array]$/; 6546function isTypedArray(value) { 6547 return value && isNumber(value.length) && TYPED_ARRAY_REGEXP.test(toString.call(value)); 6548} 6549 6550function isArrayBuffer(obj) { 6551 return toString.call(obj) === '[object ArrayBuffer]'; 6552} 6553 6554 6555var trim = function(value) { 6556 return isString(value) ? value.trim() : value; 6557}; 6558 6559// Copied from: 6560// http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021 6561// Prereq: s is a string. 6562var escapeForRegexp = function(s) { 6563 return s 6564 .replace(/([-()[\]{}+?*.$^|,:#<!\\])/g, '\\$1') 6565 // eslint-disable-next-line no-control-regex 6566 .replace(/\x08/g, '\\x08'); 6567}; 6568 6569 6570/** 6571 * @ngdoc function 6572 * @name angular.isElement 6573 * @module ng 6574 * @kind function 6575 * 6576 * @description 6577 * Determines if a reference is a DOM element (or wrapped jQuery element). 6578 * 6579 * @param {*} value Reference to check. 6580 * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element). 6581 */ 6582function isElement(node) { 6583 return !!(node && 6584 (node.nodeName // We are a direct element. 6585 || (node.prop && node.attr && node.find))); // We have an on and find method part of jQuery API. 6586} 6587 6588/** 6589 * @param str 'key1,key2,...' 6590 * @returns {object} in the form of {key1:true, key2:true, ...} 6591 */ 6592function makeMap(str) { 6593 var obj = {}, items = str.split(','), i; 6594 for (i = 0; i < items.length; i++) { 6595 obj[items[i]] = true; 6596 } 6597 return obj; 6598} 6599 6600 6601function nodeName_(element) { 6602 return lowercase(element.nodeName || (element[0] && element[0].nodeName)); 6603} 6604 6605function includes(array, obj) { 6606 return Array.prototype.indexOf.call(array, obj) !== -1; 6607} 6608 6609function arrayRemove(array, value) { 6610 var index = array.indexOf(value); 6611 if (index >= 0) { 6612 array.splice(index, 1); 6613 } 6614 return index; 6615} 6616 6617/** 6618 * @ngdoc function 6619 * @name angular.copy 6620 * @module ng 6621 * @kind function 6622 * 6623 * @description 6624 * Creates a deep copy of `source`, which should be an object or an array.
6625 * 6626 * * If no destination is supplied, a copy of the object or array is created. 6627 * * If a destination is provided, all of its elements (for arrays) or properties (for objects) 6628 * are deleted and then all elements/properties from the source are copied to it. 6629 * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned. 6630 * * If `source` is identical to `destination` an exception will be thrown. 6631 * 6632 * <br /> 6633 * <div class="alert alert-warning"> 6634 * Only enumerable properties are taken into account. Non-enumerable properties (both on `source` 6635 * and on `destination`) will be ignored. 6636 * </div> 6637 * 6638 * @param {*} source The source that will be used to make a copy. 6639 * Can be any type, including primitives, `null`, and `undefined`. 6640 * @param {(Object|Array)=} destination Destination into which the source is copied. If 6641 * provided, must be of the same type as `source`. 6642 * @returns {*} The copy or updated `destination`, if `destination` was specified. 6643 * 6644 * @example 6645 <example module="copyExample" name="angular-copy"> 6646 <file name="index.html">
6647 <div ng-controller="ExampleController"> 6648 <form novalidate class="simple-form"> 6649 <label>Name: <input type="text" ng-model="user.name" /></label><br /> 6650 <label>Age: <input type="number" ng-model="user.age" /></label><br /> 6651 Gender: <label><input type="radio" ng-model="user.gender" value="male" />male</label> 6652 <label><input type="radio" ng-model="user.gender" value="female" />female</label><br /> 6653 <button ng-click="reset()">RESET</button> 6654 <button ng-click="update(user)">SAVE</button> 6655 </form> 6656 <pre>form = {{user | json}}</pre> 6657 <pre>master = {{master | json}}</pre> 6658 </div> 6659 </file> 6660 <file name="script.js"> 6661 // Module: copyExample 6662 angular. 6663 module('copyExample', []). 6664 controller('ExampleController', ['$scope', function($scope) { 6665 $scope.master = {}; 6666 6667 $scope.reset = function() { 6668 // Example with 1 argument 6669 $scope.user = angular.copy($scope.master); 6670 }; 6671 6672 $scope.update = function(user) { 6673 // Example with 2 arguments 6674 angular.copy(user, $scope.master); 6675 }; 6676 6677 $scope.reset(); 6678 }]); 6679 </file> 6680 </example> 6681 */ 6682function copy(source, destination) { 6683 var stackSource = []; 6684 var stackDest = []; 6685 6686 if (destination) { 6687 if (isTypedArray(destination) || isArrayBuffer(destination)) { 6688 throw ngMinErr('cpta', 'Can\'t copy! TypedArray destination cannot be mutated.'); 6689 } 6690 if (source === destination) { 6691 throw ngMinErr('cpi', 'Can\'t copy! Source and destination are identical.'); 6692 } 6693 6694 // Empty the destination object 6695 if (isArray(destination)) { 6696 destination.length = 0; 6697 } else { 6698 forEach(destination, function(value, key) { 6699 if (key !== '$$hashKey') { 6700 delete destination[key]; 6701 } 6702 }); 6703 } 6704 6705 stackSource.push(source); 6706 stackDest.push(destination); 6707 return copyRecurse(source, destination); 6708 } 6709 6710 return copyElement(source); 6711 6712 function copyRecurse(source, destination) { 6713 var h = destination.$$hashKey; 6714 var key; 6715 if (isArray(source)) { 6716 for (var i = 0, ii = source.length; i < ii; i++) { 6717 destination.push(copyElement(source[i])); 6718 } 6719 } else if (isBlankObject(source)) { 6720 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty 6721 for (key in source) { 6722 destination[key] = copyElement(source[key]); 6723 } 6724 } else if (source && typeof source.hasOwnProperty === 'function') { 6725 // Slow path, which must rely on hasOwnProperty 6726 for (key in source) { 6727 if (source.hasOwnProperty(key)) { 6728 destination[key] = copyElement(source[key]); 6729 } 6730 } 6731 } else { 6732 // Slowest path --- hasOwnProperty can't be called as a method 6733 for (key in source) { 6734 if (hasOwnProperty.call(source, key)) { 6735 destination[key] = copyElement(source[key]); 6736 } 6737 } 6738 } 6739 setHashKey(destination, h); 6740 return destination; 6741 } 6742 6743 function copyElement(source) { 6744 // Simple values 6745 if (!isObject(source)) { 6746 return source; 6747 } 6748 6749 // Already copied values 6750 var index = stackSource.indexOf(source); 6751 if (index !== -1) { 6752 return stackDest[index]; 6753 } 6754 6755 if (isWindow(source) || isScope(source)) { 6756 throw ngMinErr('cpws', 6757 'Can\'t copy! Making copies of Window or Scope instances is not supported.'); 6758 } 6759 6760 var needsRecurse = false; 6761 var destination = copyType(source); 6762 6763 if (destination === undefined) { 6764 destination = isArray(source) ? [] : Object.create(getPrototypeOf(source)); 6765 needsRecurse = true; 6766 } 6767 6768 stackSource.push(source); 6769 stackDest.push(destination); 6770 6771 return needsRecurse 6772 ? copyRecurse(source, destination) 6773 : destination; 6774 } 6775 6776 function copyType(source) { 6777 switch (toString.call(source)) { 6778 case '[object Int8Array]': 6779 case '[object Int16Array]': 6780 case '[object Int32Array]': 6781 case '[object Float32Array]':
vendor: 2,512 bytes, lines 6782-6854
6782 case '[object Float64Array]': 6783 case '[object Uint8Array]': 6784 case '[object Uint8ClampedArray]': 6785 case '[object Uint16Array]': 6786 case '[object Uint32Array]': 6787 return new source.constructor(copyElement(source.buffer), source.byteOffset, source.length); 6788 6789 case '[object ArrayBuffer]': 6790 // Support: IE10 6791 if (!source.slice) { 6792 // If we're in this case we know the environment supports ArrayBuffer 6793 /* eslint-disable no-undef */ 6794 var copied = new ArrayBuffer(source.byteLength); 6795 new Uint8Array(copied).set(new Uint8Array(source)); 6796 /* eslint-enable */ 6797 return copied; 6798 } 6799 return source.slice(0); 6800 6801 case '[object Boolean]': 6802 case '[object Number]': 6803 case '[object String]': 6804 case '[object Date]': 6805 return new source.constructor(source.valueOf()); 6806 6807 case '[object RegExp]': 6808 var re = new RegExp(source.source, source.toString().match(/[^/]*$/)[0]); 6809 re.lastIndex = source.lastIndex; 6810 return re; 6811 6812 case '[object Blob]': 6813 return new source.constructor([source], {type: source.type}); 6814 } 6815 6816 if (isFunction(source.cloneNode)) { 6817 return source.cloneNode(true); 6818 } 6819 } 6820} 6821 6822 6823/** 6824 * @ngdoc function 6825 * @name angular.equals 6826 * @module ng 6827 * @kind function 6828 * 6829 * @description 6830 * Determines if two objects or two values are equivalent. Supports value types, regular 6831 * expressions, arrays and objects. 6832 * 6833 * Two objects or values are considered equivalent if at least one of the following is true: 6834 * 6835 * * Both objects or values pass `===` comparison. 6836 * * Both objects or values are of the same type and all of their properties are equal by 6837 * comparing them with `angular.equals`. 6838 * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal) 6839 * * Both values represent the same regular expression (In JavaScript, 6840 * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual 6841 * representation matches). 6842 * 6843 * During a property comparison, properties of `function` type and properties with names 6844 * that begin with `$` are ignored. 6845 * 6846 * Scope and DOMWindow objects are being compared only by identify (`===`). 6847 * 6848 * @param {*} o1 Object or value to compare. 6849 * @param {*} o2 Object or value to compare. 6850 * @returns {boolean} True if arguments are equal. 6851 * 6852 * @example 6853 <example module="equalsExample" name="equalsExample"> 6854 <file name="index.html">
6855 <div ng-controller="ExampleController"> 6856 <form novalidate> 6857 <h3>User 1</h3> 6858 Name: <input type="text" ng-model="user1.name"> 6859 Age: <input type="number" ng-model="user1.age"> 6860 6861 <h3>User 2</h3> 6862 Name: <input type="text" ng-model="user2.name"> 6863 Age: <input type="number" ng-model="user2.age"> 6864 6865 <div> 6866 <br/> 6867 <input type="button" value="Compare" ng-click="compare()"> 6868 </div> 6869 User 1: <pre>{{user1 | json}}</pre> 6870 User 2: <pre>{{user2 | json}}</pre> 6871 Equal: <pre>{{result}}</pre> 6872 </form> 6873 </div> 6874 </file> 6875 <file name="script.js"> 6876 angular.module('equalsExample', []).controller('ExampleController', ['$scope', function($scope) { 6877 $scope.user1 = {}; 6878 $scope.user2 = {}; 6879 $scope.compare = function() { 6880 $scope.result = angular.equals($scope.user1, $scope.user2); 6881 }; 6882 }]); 6883 </file> 6884 </example> 6885 */ 6886function equals(o1, o2) { 6887 if (o1 === o2) return true; 6888 if (o1 === null || o2 === null) return false; 6889 // eslint-disable-next-line no-self-compare 6890 if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN 6891 var t1 = typeof o1, t2 = typeof o2, length, key, keySet; 6892 if (t1 === t2 && t1 === 'object') { 6893 if (isArray(o1)) { 6894 if (!isArray(o2)) return false; 6895 if ((length = o1.length) === o2.length) { 6896 for (key = 0; key < length; key++) { 6897 if (!equals(o1[key], o2[key])) return false; 6898 } 6899 return true; 6900 } 6901 } else if (isDate(o1)) { 6902 if (!isDate(o2)) return false; 6903 return equals(o1.getTime(), o2.getTime()); 6904 } else if (isRegExp(o1)) { 6905 if (!isRegExp(o2)) return false; 6906 return o1.toString() === o2.toString(); 6907 } else { 6908 if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || 6909 isArray(o2) || isDate(o2) || isRegExp(o2)) return false; 6910 keySet = createMap(); 6911 for (key in o1) { 6912 if (key.charAt(0) === '$' || isFunction(o1[key])) continue; 6913 if (!equals(o1[key], o2[key])) return false; 6914 keySet[key] = true; 6915 } 6916 for (key in o2) { 6917 if (!(key in keySet) && 6918 key.charAt(0) !== '$' && 6919 isDefined(o2[key]) && 6920 !isFunction(o2[key])) return false; 6921 } 6922 return true; 6923 } 6924 } 6925 return false; 6926} 6927 6928var csp = function() { 6929 if (!isDefined(csp.rules)) { 6930 6931 6932 var ngCspElement = (window.document.querySelector('[ng-csp]') || 6933 window.document.querySelector('[data-ng-csp]')); 6934 6935 if (ngCspElement) { 6936 var ngCspAttribute = ngCspElement.getAttribute('ng-csp') || 6937 ngCspElement.getAttribute('data-ng-csp'); 6938 csp.rules = { 6939 noUnsafeEval: !ngCspAttribute || (ngCspAttribute.indexOf('no-unsafe-eval') !== -1), 6940 noInlineStyle: !ngCspAttribute || (ngCspAttribute.indexOf('no-inline-style') !== -1) 6941 }; 6942 } else { 6943 csp.rules = { 6944 noUnsafeEval: noUnsafeEval(), 6945 noInlineStyle: false 6946 }; 6947 } 6948 } 6949 6950 return csp.rules; 6951 6952 function noUnsafeEval() { 6953 try { 6954 // eslint-disable-next-line no-new, no-new-func 6955 new Function(''); 6956 return false; 6957 } catch (e) { 6958 return true; 6959 } 6960 } 6961}; 6962 6963/** 6964 * @ngdoc directive 6965 * @module ng 6966 * @name ngJq 6967 * 6968 * @element ANY 6969 * @param {string=} ngJq the name of the library available under `window` 6970 * to be used for angular.element 6971 * @description 6972 * Use this directive to force the angular.element library. This should be 6973 * used to force either jqLite by leaving ng-jq blank or setting the name of 6974 * the jquery variable under window (eg. jQuery). 6975 * 6976 * Since angular looks for this directive when it is loaded (doesn't wait for the 6977 * DOMContentLoaded event), it must be placed on an element that comes before the script 6978 * which loads angular. Also, only the first instance of `ng-jq` will be used and all 6979 * others ignored. 6980 * 6981 * @example 6982 * This example shows how to force jqLite using the `ngJq` directive to the `html` tag. 6983 ```html 6984 <!doctype html> 6985 <html ng-app ng-jq> 6986 ... 6987 ... 6988 </html> 6989 ``` 6990 * @example 6991 * This example shows how to use a jQuery based library of a different name. 6992 * The library name must be available at the top most 'window'. 6993 ```html 6994 <!doctype html> 6995 <html ng-app ng-jq="jQueryLib"> 6996 ... 6997 ... 6998 </html> 6999 ``` 7000 */ 7001var jq = function() { 7002 if (isDefined(jq.name_)) return jq.name_; 7003 var el; 7004 var i, ii = ngAttrPrefixes.length, prefix, name; 7005 for (i = 0; i < ii; ++i) { 7006 prefix = ngAttrPrefixes[i]; 7007 el = window.document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]'); 7008 if (el) { 7009 name = el.getAttribute(prefix + 'jq'); 7010 break; 7011 } 7012 } 7013 7014 return (jq.name_ = name); 7015}; 7016 7017function concat(array1, array2, index) { 7018 return array1.concat(slice.call(array2, index)); 7019} 7020 7021function sliceArgs(args, startIndex) { 7022 return slice.call(args, startIndex || 0); 7023} 7024 7025 7026/** 7027 * @ngdoc function 7028 * @name angular.bind 7029 * @module ng 7030 * @kind function 7031 * 7032 * @description 7033 * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for 7034 * `fn`). You can supply optional `args` that are prebound to the function. This feature is also 7035 * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as 7036 * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application). 7037 * 7038 * @param {Object} self Context which `fn` should be evaluated in. 7039 * @param {function()} fn Function to be bound. 7040 * @param {...*} args Optional arguments to be prebound to the `fn` function call. 7041 * @returns {function()} Function that wraps the `fn` with all the specified bindings. 7042 */ 7043function bind(self, fn) { 7044 var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : []; 7045 if (isFunction(fn) && !(fn instanceof RegExp)) { 7046 return curryArgs.length 7047 ? function() { 7048 return arguments.length 7049 ? fn.apply(self, concat(curryArgs, arguments, 0)) 7050 : fn.apply(self, curryArgs); 7051 } 7052 : function() { 7053 return arguments.length 7054 ? fn.apply(self, arguments) 7055 : fn.call(self); 7056 }; 7057 } else { 7058 // In IE, native methods are not functions so they cannot be bound (note: they don't need to be). 7059 return fn; 7060 } 7061} 7062 7063 7064function toJsonReplacer(key, value) { 7065 var val = value; 7066 7067 if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') { 7068 val = undefined; 7069 } else if (isWindow(value)) { 7070 val = '$WINDOW'; 7071 } else if (value && window.document === value) { 7072 val = '$DOCUMENT'; 7073 } else if (isScope(value)) { 7074 val = '$SCOPE'; 7075 } 7076 7077 return val; 7078} 7079 7080 7081/** 7082 * @ngdoc function 7083 * @name angular.toJson 7084 * @module ng 7085 * @kind function 7086 * 7087 * @description 7088 * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be 7089 * stripped since angular uses this notation internally. 7090 * 7091 * @param {Object|Array|Date|string|number|boolean} obj Input to be serialized into JSON. 7092 * @param {boolean|number}
7092 [pretty=2] If set to true, the JSON output will contain newlines and whitespace. 7093 * If set to an integer, the JSON output will contain that many spaces per indentation. 7094 * @returns {string|undefined} JSON-ified string representing `obj`. 7095 * @knownIssue 7096 * 7097 * The Safari browser throws a `RangeError` instead of returning `null` when it tries to stringify a `Date` 7098 * object with an invalid date value. The only reliable way to prevent this is to monkeypatch the 7099 * `Date.prototype.toJSON` method as follows: 7100 * 7101 * ``` 7102 * var _DatetoJSON = Date.prototype.toJSON; 7103 * Date.prototype.toJSON = function() { 7104 * try { 7105 * return _DatetoJSON.call(this); 7106 * } catch(e) { 7107 * if (e instanceof RangeError) { 7108 * return null; 7109 * } 7110 * throw e; 7111 * } 7112 * }; 7113 * ``` 7114 * 7115 * See https://github.com/angular/angular.js/pull/14221 for more information. 7116 */ 7117function toJson(obj, pretty) { 7118 if (isUndefined(obj)) return undefined; 7119 if (!isNumber(pretty)) { 7120 pretty = pretty ? 2 : null; 7121 } 7122 return JSON.stringify(obj, toJsonReplacer, pretty); 7123} 7124 7125 7126/** 7127 * @ngdoc function 7128 * @name angular.fromJson 7129 * @module ng 7130 * @kind function 7131 * 7132 * @description 7133 * Deserializes a JSON string. 7134 * 7135 * @param {string} json JSON string to deserialize. 7136 * @returns {Object|Array|string|number} Deserialized JSON string. 7137 */ 7138function fromJson(json) { 7139 return isString(json) 7140 ? JSON.parse(json) 7141 : json; 7142} 7143 7144 7145var ALL_COLONS = /:/g; 7146function timezoneToOffset(timezone, fallback) { 7147 // IE/Edge do not "understand" colon (`:`) in timezone 7148 timezone = timezone.replace(ALL_COLONS, ''); 7149 var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000; 7150 return isNumberNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset; 7151} 7152 7153 7154function addDateMinutes(date, minutes) { 7155 date = new Date(date.getTime()); 7156 date.setMinutes(date.getMinutes() + minutes); 7157 return date; 7158} 7159 7160 7161function convertTimezoneToLocal(date, timezone, reverse) { 7162 reverse = reverse ? -1 : 1; 7163 var dateTimezoneOffset = date.getTimezoneOffset(); 7164 var timezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset); 7165 return addDateMinutes(date, reverse * (timezoneOffset - dateTimezoneOffset)); 7166} 7167 7168 7169/** 7170 * @returns {string} Returns the string representation of the element. 7171 */ 7172function startingTag(element) { 7173 element = jqLite(element).clone(); 7174 try { 7175 // turns out IE does not let you set .html() on elements which 7176 // are not allowed to have children. So we just ignore it. 7177 element.empty(); 7178 } catch (e) { /* empty */ } 7179 var elemHtml = jqLite('<div>').append(element).html(); 7180 try { 7181 return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) : 7182 elemHtml. 7183 match(/^(<[^>]+>)/)[1]. 7184 replace(/^<([\w-]+)/, function(match, nodeName) {return '<' + lowercase(nodeName);}); 7185 } catch (e) { 7186 return lowercase(elemHtml); 7187 } 7188 7189} 7190 7191 7192///////////////////////////////////////////////// 7193 7194/** 7195 * Tries to decode the URI component without throwing an exception. 7196 * 7197 * @private 7198 * @param str value potential URI component to check. 7199 * @returns {boolean} True if `value` can be decoded 7200 * with the decodeURIComponent function. 7201 */ 7202function tryDecodeURIComponent(value) { 7203 try { 7204 return decodeURIComponent(value); 7205 } catch (e) { 7206 // Ignore any invalid uri component. 7207 } 7208} 7209 7210 7211/** 7212 * Parses an escaped url query string into key-value pairs. 7213 * @returns {Object.<string,boolean|Array>} 7214 */ 7215function parseKeyValue(/**string*/keyValue) { 7216 var obj = {}; 7217 forEach((keyValue || '').split('&'), function(keyValue) { 7218 var splitPoint, key, val; 7219 if (keyValue) { 7220 key = keyValue = keyValue.replace(/\+/g,'%20'); 7221 splitPoint = keyValue.indexOf('='); 7222 if (splitPoint !== -1) { 7223 key = keyValue.substring(0, splitPoint); 7224 val = keyValue.substring(splitPoint + 1); 7225 } 7226 key = tryDecodeURIComponent(key); 7227 if (isDefined(key)) { 7228 val = isDefined(val) ? tryDecodeURIComponent(val) : true; 7229 if (!hasOwnProperty.call(obj, key)) { 7230 obj[key] = val; 7231 } else if (isArray(obj[key])) { 7232 obj[key].push(val); 7233 } else { 7234 obj[key] = [obj[key],val]; 7235 } 7236 } 7237 } 7238 }); 7239 return obj; 7240} 7241 7242function toKeyValue(obj) { 7243 var parts = []; 7244 forEach(obj, function(value, key) { 7245 if (isArray(value)) {
vendor: 4,355 bytes, lines 7246-7367
7246 forEach(value, function(arrayValue) { 7247 parts.push(encodeUriQuery(key, true) + 7248 (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true))); 7249 }); 7250 } else { 7251 parts.push(encodeUriQuery(key, true) + 7252 (value === true ? '' : '=' + encodeUriQuery(value, true))); 7253 } 7254 }); 7255 return parts.length ? parts.join('&') : ''; 7256} 7257 7258 7259/** 7260 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow 7261 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path 7262 * segments: 7263 * segment = *pchar 7264 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 7265 * pct-encoded = "%" HEXDIG HEXDIG 7266 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 7267 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 7268 * / "*" / "+" / "," / ";" / "=" 7269 */ 7270function encodeUriSegment(val) { 7271 return encodeUriQuery(val, true). 7272 replace(/%26/gi, '&'). 7273 replace(/%3D/gi, '='). 7274 replace(/%2B/gi, '+'); 7275} 7276 7277 7278/** 7279 * This method is intended for encoding *key* or *value* parts of query component. We need a custom 7280 * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be 7281 * encoded per http://tools.ietf.org/html/rfc3986: 7282 * query = *( pchar / "/" / "?" ) 7283 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 7284 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 7285 * pct-encoded = "%" HEXDIG HEXDIG 7286 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 7287 * / "*" / "+" / "," / ";" / "=" 7288 */ 7289function encodeUriQuery(val, pctEncodeSpaces) { 7290 return encodeURIComponent(val). 7291 replace(/%40/gi, '@'). 7292 replace(/%3A/gi, ':'). 7293 replace(/%24/g, '$'). 7294 replace(/%2C/gi, ','). 7295 replace(/%3B/gi, ';'). 7296 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); 7297} 7298 7299var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-']; 7300 7301function getNgAttribute(element, ngAttr) { 7302 var attr, i, ii = ngAttrPrefixes.length; 7303 for (i = 0; i < ii; ++i) { 7304 attr = ngAttrPrefixes[i] + ngAttr; 7305 if (isString(attr = element.getAttribute(attr))) { 7306 return attr; 7307 } 7308 } 7309 return null; 7310} 7311 7312function allowAutoBootstrap(document) { 7313 var script = document.currentScript; 7314 var src = script && script.getAttribute('src'); 7315 7316 if (!src) { 7317 return true; 7318 } 7319 7320 var link = document.createElement('a'); 7321 link.href = src; 7322 7323 if (document.location.origin === link.origin) { 7324 // Same-origin resources are always allowed, even for non-whitelisted schemes. 7325 return true; 7326 } 7327 // Disabled bootstrapping unless angular.js was loaded from a known scheme used on the web. 7328 // This is to prevent angular.js bundled with browser extensions from being used to bypass the 7329 // content security policy in web pages and other browser extensions. 7330 switch (link.protocol) { 7331 case 'http:': 7332 case 'https:': 7333 case 'ftp:': 7334 case 'blob:': 7335 case 'file:': 7336 case 'data:': 7337 return true; 7338 default: 7339 return false; 7340 } 7341} 7342 7343// Cached as it has to run during loading so that document.currentScript is available. 7344var isAutoBootstrapAllowed = allowAutoBootstrap(window.document); 7345 7346/** 7347 * @ngdoc directive 7348 * @name ngApp 7349 * @module ng 7350 * 7351 * @element ANY 7352 * @param {angular.Module} ngApp an optional application 7353 * {@link angular.module module} name to load. 7354 * @param {boolean=} ngStrictDi if this attribute is present on the app element, the injector will be 7355 * created in "strict-di" mode. This means that the application will fail to invoke functions which 7356 * do not use explicit function annotation (and are thus unsuitable for minification), as described 7357 * in {@link guide/di the Dependency Injection guide}, and useful debugging info will assist in 7358 * tracking down the root of these bugs. 7359 * 7360 * @description 7361 * 7362 * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive 7363 * designates the **root element** of the application and is typically placed near the root element 7364 * of the page - e.g. on the `<body>` or `<html>` tags. 7365 * 7366 * There are a few things to keep in mind when using `ngApp`: 7367 * - only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngAp
7367p` 7368 * found in the document will be used to define the root element to auto-bootstrap as an 7369 * application. To run multiple applications in an HTML document you must manually bootstrap them using 7370 * {@link angular.bootstrap} instead. 7371 * - AngularJS applications cannot be nested within each other. 7372 * - Do not use a directive that uses {@link ng.$compile#transclusion transclusion} on the same element as `ngApp`. 7373 * This includes directives such as {@link ng.ngIf `ngIf`}, {@link ng.ngInclude `ngInclude`} and 7374 * {@link ngRoute.ngView `ngView`}. 7375 * Doing this misplaces the app {@link ng.$rootElement `$rootElement`} and the app's {@link auto.$injector injector}, 7376 * causing animations to stop working and making the injector inaccessible from outside the app. 7377 * 7378 * You can specify an **AngularJS module** to be used as the root module for the application. This 7379 * module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It 7380 * should contain the application code needed or have dependencies on other modules that will 7381 * contain the code. See {@link angular.module} for more information. 7382 * 7383 * In the example below if the `ngApp` directive were not placed on the `html` element then the 7384 * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}` 7385 * would not be resolved to `3`. 7386 * 7387 * `ngApp` is the easiest, and most common way to bootstrap an application. 7388 * 7389 <example module="ngAppDemo" name="ng-app"> 7390 <file name="index.html"> 7391 <div ng-controller="ngAppDemoController"> 7392 I can add: {{a}} + {{b}} = {{ a+b }} 7393 </div> 7394 </file> 7395 <file name="script.js"> 7396 angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) { 7397 $scope.a = 1; 7398 $scope.b = 2; 7399 }); 7400 </file> 7401 </example> 7402 * 7403 * Using `ngStrictDi`, you would see something like this: 7404 * 7405 <example ng-app-included="true" name="strict-di"> 7406 <file name="index.html"> 7407 <div ng-app="ngAppStrictDemo" ng-strict-di> 7408 <div ng-controller="GoodController1"> 7409 I can add: {{a}} + {{b}} = {{ a+b }} 7410 7411 <p>This renders because the controller does not fail to 7412 instantiate, by using explicit annotation style (see 7413 script.js for details) 7414 </p> 7415 </div> 7416 7417 <div ng-controller="GoodController2"> 7418 Name: <input ng-model="name"><br /> 7419 Hello, {{name}}! 7420 7421 <p>This renders because the controller does not fail to 7422 instantiate, by using explicit annotation style 7423 (see script.js for details) 7424 </p> 7425 </div> 7426 7427 <div ng-controller="BadController"> 7428 I can add: {{a}} + {{b}} = {{ a+b }} 7429 7430 <p>The controller could not be instantiated, due to relying 7431 on automatic function annotations (which are disabled in 7432 strict mode). As such, the content of this section is not 7433 interpolated, and there should be an error in your web console. 7434 </p> 7435 </div> 7436 </div> 7437 </file> 7438 <file name="script.js"> 7439 angular.module('ngAppStrictDemo', []) 7440 // BadController will fail to instantiate, due to relying on automatic function annotation, 7441 // rather than an explicit annotation 7442 .controller('BadController', function($scope) { 7443 $scope.a = 1; 7444 $scope.b = 2; 7445 }) 7446 // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated, 7447 // due to using explicit annotations using the array style and $inject property, respectively. 7448 .controller('GoodController1', ['$scope', function($scope) { 7449 $scope.a = 1; 7450 $scope.b = 2; 7451 }]) 7452 .controller('GoodController2', GoodController2); 7453 function GoodController2($scope) { 7454 $scope.name = 'World'; 7455 } 7456 GoodController2.$inject = ['$scope']; 7457 </file> 7458 <file name="style.css"> 7459 div[ng-controller] { 7460 margin-bottom: 1em; 7461 -webkit-border-radius: 4px; 7462 border-radius: 4px; 7463 border: 1px solid; 7464 padding: .5em; 7465 } 7466 div[ng-controller^=Good] { 7467 border-color: #d6e9c6; 7468 background-color: #dff0d8; 7469 color: #3c763d; 7470 } 7471 div[ng-controller^=Bad] { 7472 border-color: #ebccd1; 7473 background-color: #f2dede; 7474 color: #a94442; 7475 margin-bottom: 0; 7476 } 7477 </file> 7478 </example> 7479 */ 7480function angularInit(element, bootstrap) { 7481 var appElement, 7482 module, 7483 config = {}; 7484 7485 // The element `element` has priority over any other element. 7486 forEach(ngAttrPrefixes, function(prefix) { 7487 var name = prefix + 'app'; 7488 7489 if (!appElement && element.hasAttribute && element.hasAttribute(name)) { 7490 appElement = element; 7491 module = element.getAttribute(name); 7492 } 7493 }); 7494 forEach(ngAttrPrefixes, function(prefix) { 7495 var name = prefix + 'app'; 7496 var candidate; 7497 7498 if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) { 7499 appElement = candidate; 7500 module = candidate.getAttribute(name); 7501 } 7502 }); 7503 if (appElement) { 7504 if (!isAutoBootstrapAllowed) { 7505 window.console.error('Angular: disabling automatic bootstrap. <script> protocol indicates ' + 7506 'an extension, document.location.href does not match.'); 7507 return; 7508 } 7509 config.strictDi = getNgAttribute(appElement, 'strict-di') !== null; 7510 bootstrap(appElement, module ? [module] : [], config); 7511 } 7512} 7513 7514/** 7515 * @ngdoc function 7516 * @name angular.bootstrap 7517 * @module ng 7518 * @description 7519 * Use this function to manually start up angular application. 7520 * 7521 * For more information, see the {@link guide/bootstrap Bootstrap guide}. 7522 * 7523 * Angular will detect if it has been loaded into the browser more than once and only allow the 7524 * first loaded script to be bootstrapped and will report a warning to the browser console for 7525 * each of the subsequent scripts. This prevents strange results in applications, where otherwise 7526 * multiple instances of Angular try to work on the DOM. 7527 * 7528 * <div class="alert alert-warning">
7529 * **Note:** Protractor based end-to-end tests cannot use this function to bootstrap manually. 7530 * They must use {@link ng.directive:ngApp ngApp}. 7531 * </div> 7532 * 7533 * <div class="alert alert-warning"> 7534 * **Note:** Do not bootstrap the app on an element with a directive that uses {@link ng.$compile#transclusion transclusion}, 7535 * such as {@link ng.ngIf `ngIf`}, {@link ng.ngInclude `ngInclude`} and {@link ngRoute.ngView `ngView`}. 7536 * Doing this misplaces the app {@link ng.$rootElement `$rootElement`} and the app's {@link auto.$injector injector}, 7537 * causing animations to stop working and making the injector inaccessible from outside the app. 7538 * </div> 7539 * 7540 * ```html 7541 * <!doctype html> 7542 * <html> 7543 * <body> 7544 * <div ng-controller="WelcomeController"> 7545 * {{greeting}} 7546 * </div> 7547 * 7548 * <script src="angular.js"></script> 7549 * <script> 7550 * var app = angular.module('demo', []) 7551 * .controller('WelcomeController', function($scope) { 7552 * $scope.greeting = 'Welcome!'; 7553 * }); 7554 * angular.bootstrap(document, ['demo']); 7555 * </script> 7556 * </body> 7557 * </html> 7558 * ``` 7559 * 7560 * @param {DOMElement} element DOM element which is the root of angular application. 7561 * @param {Array<String|Function|Array>=} modules an array of modules to load into the application. 7562 * Each item in the array should be the name of a predefined module or a (DI annotated) 7563 * function that will be invoked by the injector as a `config` block. 7564 * See: {@link angular.module modules} 7565 * @param {Object=} config an object for defining configuration options for the application. The 7566 * following keys are supported: 7567 * 7568 * * `strictDi` - disable automatic function annotation for the application. This is meant to 7569 * assist in finding bugs which break minified code. Defaults to `false`. 7570 * 7571 * @returns {auto.$injector} Returns the newly created injector for this app. 7572 */ 7573function bootstrap(element, modules, config) { 7574 if (!isObject(config)) config = {}; 7575 var defaultConfig = { 7576 strictDi: false 7577 }; 7578 config = extend(defaultConfig, config); 7579 var doBootstrap = function() { 7580 element = jqLite(element); 7581 7582 if (element.injector()) { 7583 var tag = (element[0] === window.document) ? 'document' : startingTag(element); 7584 // Encode angle brackets to prevent input from being sanitized to empty string #8683. 7585 throw ngMinErr( 7586 'btstrpd', 7587 'App already bootstrapped with this element \'{0}\'', 7588 tag.replace(/</,'<').replace(/>/,'>')); 7589 } 7590 7591 modules = modules || []; 7592 modules.unshift(['$provide', function($provide) { 7593 $provide.value('$rootElement', element); 7594 }]); 7595 7596 if (config.debugInfoEnabled) { 7597 // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`. 7598 modules.push(['$compileProvider', function($compileProvider) { 7599 $compileProvider.debugInfoEnabled(true); 7600 }]); 7601 } 7602 7603 modules.unshift('ng'); 7604 var injector = createInjector(modules, config.strictDi); 7605 injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', 7606 function bootstrapApply(scope, element, compile, injector) { 7607 scope.$apply(function() { 7608 element.data('$injector', injector); 7609 compile(element)(scope); 7610 }); 7611 }] 7612 ); 7613 return injector; 7614 }; 7615 7616 var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/; 7617 var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/; 7618 7619 if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) { 7620 config.debugInfoEnabled = true; 7621 window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, ''); 7622 } 7623 7624 if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) { 7625 return doBootstrap(); 7626 } 7627 7628 window.name = window.name.replace(NG_DEFER_BOOTSTRAP, ''); 7629 angular.resumeBootstrap = function(extraModules) { 7630 forEach(extraModules, function(module) { 7631 modules.push(module); 7632 }); 7633 return doBootstrap(); 7634 }; 7635 7636 if (isFunction(angular.resumeDeferredBootstrap)) { 7637 angular.resumeDeferredBootstrap(); 7638 } 7639} 7640 7641/** 7642 * @ngdoc function 7643 * @name angular.reloadWithDebugInfo 7644 * @module ng 7645 * @description 7646 * Use this function to reload the current application with debug information turned on. 7647 * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`. 7648 * 7649 * See {@link ng.$compileProvider#debugInfoEnabled} for more. 7650 */ 7651function reloadWithDebugInfo() { 7652 window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name; 7653 window.location.reload(); 7654} 7655 7656/** 7657 * @name angular.getTestability 7658 * @module ng 7659 * @description 7660 * Get the testability service for the instance of Angular on the given 7661 * element. 7662 * @param {DOMElement} element DOM element which is the root of angular application. 7663 */ 7664function getTestability(rootElement) { 7665 var injector = angular.element(rootElement).injector(); 7666 if (!injector) { 7667 throw ngMinErr('test', 7668 'no injector found for element argument to getTestability'); 7669 } 7670 return injector.get('$$testability'); 7671} 7672 7673var SNAKE_CASE_REGEXP = /[A-Z]/g; 7674function snake_case(name, separator) { 7675 separator = separator || '_'; 7676 return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) { 7677 return (pos ? separator : '') + letter.toLowerCase(); 7678 }); 7679} 7680 7681var bindJQueryFired = false;
vendor: 8,941 bytes, lines 7682-7966
7682function bindJQuery() { 7683 var originalCleanData; 7684 7685 if (bindJQueryFired) { 7686 return; 7687 } 7688 7689 // bind to jQuery if present; 7690 var jqName = jq(); 7691 jQuery = isUndefined(jqName) ? window.jQuery : // use jQuery (if present) 7692 !jqName ? undefined : // use jqLite 7693 window[jqName]; // use jQuery specified by `ngJq` 7694 7695 // Use jQuery if it exists with proper functionality, otherwise default to us. 7696 // Angular 1.2+ requires jQuery 1.7+ for on()/off() support. 7697 // Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older 7698 // versions. It will not work for sure with jQuery <1.7, though. 7699 if (jQuery && jQuery.fn.on) { 7700 jqLite = jQuery; 7701 extend(jQuery.fn, { 7702 scope: JQLitePrototype.scope, 7703 isolateScope: JQLitePrototype.isolateScope, 7704 controller: JQLitePrototype.controller, 7705 injector: JQLitePrototype.injector, 7706 inheritedData: JQLitePrototype.inheritedData 7707 }); 7708 7709 // All nodes removed from the DOM via various jQuery APIs like .remove() 7710 // are passed through jQuery.cleanData. Monkey-patch this method to fire 7711 // the $destroy event on all removed nodes. 7712 originalCleanData = jQuery.cleanData; 7713 jQuery.cleanData = function(elems) { 7714 var events; 7715 for (var i = 0, elem; (elem = elems[i]) != null; i++) { 7716 events = jQuery._data(elem, 'events'); 7717 if (events && events.$destroy) { 7718 jQuery(elem).triggerHandler('$destroy'); 7719 } 7720 } 7721 originalCleanData(elems); 7722 }; 7723 } else { 7724 jqLite = JQLite; 7725 } 7726 7727 angular.element = jqLite; 7728 7729 // Prevent double-proxying. 7730 bindJQueryFired = true; 7731} 7732 7733/** 7734 * throw error if the argument is falsy. 7735 */ 7736function assertArg(arg, name, reason) { 7737 if (!arg) { 7738 throw ngMinErr('areq', 'Argument \'{0}\' is {1}', (name || '?'), (reason || 'required')); 7739 } 7740 return arg; 7741} 7742 7743function assertArgFn(arg, name, acceptArrayAnnotation) { 7744 if (acceptArrayAnnotation && isArray(arg)) { 7745 arg = arg[arg.length - 1]; 7746 } 7747 7748 assertArg(isFunction(arg), name, 'not a function, got ' + 7749 (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg)); 7750 return arg; 7751} 7752 7753/** 7754 * throw error if the name given is hasOwnProperty 7755 * @param {String} name the name to test 7756 * @param {String} context the context in which the name is used, such as module or directive 7757 */ 7758function assertNotHasOwnProperty(name, context) { 7759 if (name === 'hasOwnProperty') { 7760 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); 7761 } 7762} 7763 7764/** 7765 * Return the value accessible from the object by path. Any undefined traversals are ignored 7766 * @param {Object} obj starting object 7767 * @param {String} path path to traverse 7768 * @param {boolean} [bindFnToScope=true] 7769 * @returns {Object} value as accessible by path 7770 */ 7771//TODO(misko): this function needs to be removed 7772function getter(obj, path, bindFnToScope) { 7773 if (!path) return obj; 7774 var keys = path.split('.'); 7775 var key; 7776 var lastInstance = obj; 7777 var len = keys.length; 7778 7779 for (var i = 0; i < len; i++) { 7780 key = keys[i]; 7781 if (obj) { 7782 obj = (lastInstance = obj)[key]; 7783 } 7784 } 7785 if (!bindFnToScope && isFunction(obj)) { 7786 return bind(lastInstance, obj); 7787 } 7788 return obj; 7789} 7790 7791/** 7792 * Return the DOM siblings between the first and last node in the given array. 7793 * @param {Array} array like object 7794 * @returns {Array} the inputted object or a jqLite collection containing the nodes 7795 */ 7796function getBlockNodes(nodes) { 7797 // TODO(perf): update `nodes` instead of creating a new object? 7798 var node = nodes[0]; 7799 var endNode = nodes[nodes.length - 1]; 7800 var blockNodes; 7801 7802 for (var i = 1; node !== endNode && (node = node.nextSibling); i++) { 7803 if (blockNodes || nodes[i] !== node) { 7804 if (!blockNodes) { 7805 blockNodes = jqLite(slice.call(nodes, 0, i)); 7806 } 7807 blockNodes.push(node); 7808 } 7809 } 7810 7811 return blockNodes || nodes; 7812} 7813 7814 7815/** 7816 * Creates a new object without a prototype. This object is useful for lookup without having to 7817 * guard against prototypically inherited properties via hasOwnProperty. 7818 * 7819 * Related micro-benchmarks: 7820 * - http://jsperf.com/object-create2 7821 * - http://jsperf.com/proto-map-lookup/2 7822 * - http://jsperf.com/for-in-vs-object-keys2 7823 * 7824 * @returns {Object} 7825 */ 7826function createMap() { 7827 return Object.create(null); 7828} 7829 7830var NODE_TYPE_ELEMENT = 1; 7831var NODE_TYPE_ATTRIBUTE = 2; 7832var NODE_TYPE_TEXT = 3; 7833var NODE_TYPE_COMMENT = 8; 7834var NODE_TYPE_DOCUMENT = 9; 7835var NODE_TYPE_DOCUMENT_FRAGMENT = 11; 7836 7837/** 7838 * @ngdoc type 7839 * @name angular.Module 7840 * @module ng 7841 * @description 7842 * 7843 * Interface for configuring angular {@link angular.module modules}. 7844 */ 7845 7846function setupModuleLoader(window) { 7847 7848 var $injectorMinErr = minErr('$injector'); 7849 var ngMinErr = minErr('ng'); 7850 7851 function ensure(obj, name, factory) { 7852 return obj[name] || (obj[name] = factory()); 7853 } 7854 7855 var angular = ensure(window, 'angular', Object); 7856 7857 // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap 7858 angular.$$minErr = angular.$$minErr || minErr; 7859 7860 return ensure(angular, 'module', function() { 7861 /** @type {Object.<string, angular.Module>} */ 7862 var modules = {}; 7863 7864 /** 7865 * @ngdoc function 7866 * @name angular.module 7867 * @module ng 7868 * @description 7869 * 7870 * The `angular.module` is a global place for creating, registering and retrieving Angular 7871 * modules. 7872 * All modules (angular core or 3rd party) that should be available to an application must be 7873 * registered using this mechanism. 7874 * 7875 * Passing one argument retrieves an existing {@link angular.Module}, 7876 * whereas passing more than one argument creates a new {@link angular.Module} 7877 * 7878 * 7879 * # Module 7880 * 7881 * A module is a collection of services, directives, controllers, filters, and configuration information. 7882 * `angular.module` is used to configure the {@link auto.$injector $injector}. 7883 * 7884 * ```js 7885 * // Create a new module 7886 * var myModule = angular.module('myModule', []); 7887 * 7888 * // register a new service 7889 * myModule.value('appName', 'MyCoolApp'); 7890 * 7891 * // configure existing services inside initialization blocks. 7892 * myModule.config(['$locationProvider', function($locationProvider) { 7893 * // Configure existing providers 7894 * $locationProvider.hashPrefix('!'); 7895 * }]); 7896 * ``` 7897 * 7898 * Then you can create an injector and load your modules like this: 7899 * 7900 * ```js 7901 * var injector = angular.injector(['ng', 'myModule']) 7902 * ``` 7903 * 7904 * However it's more likely that you'll just use 7905 * {@link ng.directive:ngApp ngApp} or 7906 * {@link angular.bootstrap} to simplify this process for you. 7907 * 7908 * @param {!string} name The name of the module to create or retrieve. 7909 * @param {!Array.<string>=} requires If specified then new module is being created. If 7910 * unspecified then the module is being retrieved for further configuration. 7911 * @param {Function=} configFn Optional configuration function for the module. Same as 7912 * {@link angular.Module#config Module#config()}. 7913 * @returns {angular.Module} new module with the {@link angular.Module} api. 7914 */ 7915 return function module(name, requires, configFn) { 7916 var assertNotHasOwnProperty = function(name, context) { 7917 if (name === 'hasOwnProperty') { 7918 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); 7919 } 7920 }; 7921 7922 assertNotHasOwnProperty(name, 'module'); 7923 if (requires && modules.hasOwnProperty(name)) { 7924 modules[name] = null; 7925 } 7926 return ensure(modules, name, function() { 7927 if (!requires) { 7928 throw $injectorMinErr('nomod', 'Module \'{0}\' is not available! You either misspelled ' + 7929 'the module name or forgot to load it. If registering a module ensure that you ' + 7930 'specify the dependencies as the second argument.', name); 7931 } 7932 7933 /** @type {!Array.<Array.<*>>} */ 7934 var invokeQueue = []; 7935 7936 /** @type {!Array.<Function>} */ 7937 var configBlocks = []; 7938 7939 /** @type {!Array.<Function>} */ 7940 var runBlocks = []; 7941 7942 var config = invokeLater('$injector', 'invoke', 'push', configBlocks); 7943 7944 /** @type {angular.Module} */ 7945 var moduleInstance = { 7946 // Private state 7947 _invokeQueue: invokeQueue, 7948 _configBlocks: configBlocks, 7949 _runBlocks: runBlocks, 7950 7951 /** 7952 * @ngdoc property 7953 * @name angular.Module#requires 7954 * @module ng 7955 * 7956 * @description 7957 * Holds the list of modules which the injector will load before the current module is 7958 * loaded. 7959 */ 7960 requires: requires, 7961 7962 /** 7963 * @ngdoc property 7964 * @name angular.Module#name 7965 * @module ng 7966 *
7967 * @description 7968 * Name of the module. 7969 */ 7970 name: name, 7971 7972 7973 /** 7974 * @ngdoc method 7975 * @name angular.Module#provider 7976 * @module ng 7977 * @param {string} name service name 7978 * @param {Function} providerType Construction function for creating new instance of the 7979 * service. 7980 * @description 7981 * See {@link auto.$provide#provider $provide.provider()}. 7982 */ 7983 provider: invokeLaterAndSetModuleName('$provide', 'provider'), 7984 7985 /** 7986 * @ngdoc method 7987 * @name angular.Module#factory 7988 * @module ng 7989 * @param {string} name service name 7990 * @param {Function} providerFunction Function for creating new instance of the service. 7991 * @description 7992 * See {@link auto.$provide#factory $provide.factory()}. 7993 */ 7994 factory: invokeLaterAndSetModuleName('$provide', 'factory'), 7995 7996 /** 7997 * @ngdoc method 7998 * @name angular.Module#service 7999 * @module ng 8000 * @param {string} name service name 8001 * @param {Function} constructor A constructor function that will be instantiated. 8002 * @description 8003 * See {@link auto.$provide#service $provide.service()}. 8004 */ 8005 service: invokeLaterAndSetModuleName('$provide', 'service'), 8006 8007 /** 8008 * @ngdoc method 8009 * @name angular.Module#value 8010 * @module ng 8011 * @param {string} name service name 8012 * @param {*} object Service instance object. 8013 * @description 8014 * See {@link auto.$provide#value $provide.value()}. 8015 */ 8016 value: invokeLater('$provide', 'value'), 8017 8018 /** 8019 * @ngdoc method 8020 * @name angular.Module#constant 8021 * @module ng 8022 * @param {string} name constant name 8023 * @param {*} object Constant value. 8024 * @description 8025 * Because the constants are fixed, they get applied before other provide methods. 8026 * See {@link auto.$provide#constant $provide.constant()}. 8027 */ 8028 constant: invokeLater('$provide', 'constant', 'unshift'), 8029 8030 /** 8031 * @ngdoc method 8032 * @name angular.Module#decorator 8033 * @module ng 8034 * @param {string} name The name of the service to decorate. 8035 * @param {Function} decorFn This function will be invoked when the service needs to be 8036 * instantiated and should return the decorated service instance. 8037 * @description 8038 * See {@link auto.$provide#decorator $provide.decorator()}. 8039 */ 8040 decorator: invokeLaterAndSetModuleName('$provide', 'decorator'), 8041 8042 /** 8043 * @ngdoc method 8044 * @name angular.Module#animation 8045 * @module ng 8046 * @param {string} name animation name 8047 * @param {Function} animationFactory Factory function for creating new instance of an 8048 * animation. 8049 * @description 8050 * 8051 * **NOTE**: animations take effect only if the **ngAnimate** module is loaded. 8052 * 8053 * 8054 * Defines an animation hook that can be later used with 8055 * {@link $animate $animate} service and directives that use this service. 8056 * 8057 * ```js 8058 * module.animation('.animation-name', function($inject1, $inject2) { 8059 * return { 8060 * eventName : function(element, done) { 8061 * //code to run the animation 8062 * //once complete, then run done() 8063 * return function cancellationFunction(element) { 8064 * //code to cancel the animation 8065 * } 8066 * } 8067 * } 8068 * }) 8069 * ``` 8070 * 8071 * See {@link ng.$animateProvider#register $animateProvider.register()} and 8072 * {@link ngAnimate ngAnimate module} for more information. 8073 */ 8074 animation: invokeLaterAndSetModuleName('$animateProvider', 'register'), 8075 8076 /** 8077 * @ngdoc method 8078 * @name angular.Module#filter 8079 * @module ng 8080 * @param {string} name Filter name - this must be a valid angular expression identifier 8081 * @param {Function} filterFactory Factory function for creating new instance of filter.
8082 * @description 8083 * See {@link ng.$filterProvider#register $filterProvider.register()}. 8084 * 8085 * <div class="alert alert-warning"> 8086 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. 8087 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 8088 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 8089 * (`myapp_subsection_filterx`). 8090 * </div> 8091 */ 8092 filter: invokeLaterAndSetModuleName('$filterProvider', 'register'), 8093 8094 /** 8095 * @ngdoc method 8096 * @name angular.Module#controller 8097 * @module ng 8098 * @param {string|Object} name Controller name, or an object map of controllers where the 8099 * keys are the names and the values are the constructors. 8100 * @param {Function} constructor Controller constructor function. 8101 * @description 8102 * See {@link ng.$controllerProvider#register $controllerProvider.register()}. 8103 */ 8104 controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'), 8105 8106 /** 8107 * @ngdoc method 8108 * @name angular.Module#directive 8109 * @module ng 8110 * @param {string|Object} name Directive name, or an object map of directives where the 8111 * keys are the names and the values are the factories. 8112 * @param {Function} directiveFactory Factory function for creating new instance of 8113 * directives. 8114 * @description 8115 * See {@link ng.$compileProvider#directive $compileProvider.directive()}. 8116 */ 8117 directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'), 8118 8119 /** 8120 * @ngdoc method 8121 * @name angular.Module#component 8122 * @module ng 8123 * @param {string} name Name of the component in camel-case (i.e. myComp which will match as my-comp) 8124 * @param {Object} options Component definition object (a simplified 8125 * {@link ng.$compile#directive-definition-object directive definition object}) 8126 * 8127 * @description 8128 * See {@link ng.$compileProvider#component $compileProvider.component()}. 8129 */ 8130 component: invokeLaterAndSetModuleName('$compileProvider', 'component'), 8131 8132 /** 8133 * @ngdoc method 8134 * @name angular.Module#config 8135 * @module ng 8136 * @param {Function} configFn Execute this function on module load. Useful for service 8137 * configuration. 8138 * @description 8139 * Use this method to register work which needs to be performed on module loading. 8140 * For more about how to configure services, see 8141 * {@link providers#provider-recipe Provider Recipe}. 8142 */ 8143 config: config, 8144 8145 /** 8146 * @ngdoc method 8147 * @name angular.Module#run 8148 * @module ng 8149 * @param {Function} initializationFn Execute this function after injector creation. 8150 * Useful for application initialization. 8151 * @description 8152 * Use this method to register work which should be performed when the injector is done 8153 * loading all modules. 8154 */ 8155 run: function(block) { 8156 runBlocks.push(block); 8157 return this; 8158 } 8159 }; 8160 8161 if (configFn) { 8162 config(configFn); 8163 } 8164 8165 return moduleInstance; 8166 8167 /** 8168 * @param {string} provider 8169 * @param {string} method 8170 * @param {String=} insertMethod 8171 * @returns {angular.Module} 8172 */ 8173 function invokeLater(provider, method, insertMethod, queue) { 8174 if (!queue) queue = invokeQueue; 8175 return function() { 8176 queue[insertMethod || 'push']([provider, method, arguments]); 8177 return moduleInstance; 8178 }; 8179 } 8180 8181 /** 8182 * @param {string} provider 8183 * @param {string} method 8184 * @returns {angular.Module} 8185 */
vendor: 8,464 bytes, lines 8186-8490
8186 function invokeLaterAndSetModuleName(provider, method) { 8187 return function(recipeName, factoryFunction) { 8188 if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name; 8189 invokeQueue.push([provider, method, arguments]); 8190 return moduleInstance; 8191 }; 8192 } 8193 }); 8194 }; 8195 }); 8196 8197} 8198 8199/* global shallowCopy: true */ 8200 8201/** 8202 * Creates a shallow copy of an object, an array or a primitive. 8203 * 8204 * Assumes that there are no proto properties for objects. 8205 */ 8206function shallowCopy(src, dst) { 8207 if (isArray(src)) { 8208 dst = dst || []; 8209 8210 for (var i = 0, ii = src.length; i < ii; i++) { 8211 dst[i] = src[i]; 8212 } 8213 } else if (isObject(src)) { 8214 dst = dst || {}; 8215 8216 for (var key in src) { 8217 if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) { 8218 dst[key] = src[key]; 8219 } 8220 } 8221 } 8222 8223 return dst || src; 8224} 8225 8226/* global toDebugString: true */ 8227 8228function serializeObject(obj) { 8229 var seen = []; 8230 8231 return JSON.stringify(obj, function(key, val) { 8232 val = toJsonReplacer(key, val); 8233 if (isObject(val)) { 8234 8235 if (seen.indexOf(val) >= 0) return '...'; 8236 8237 seen.push(val); 8238 } 8239 return val; 8240 }); 8241} 8242 8243function toDebugString(obj) { 8244 if (typeof obj === 'function') { 8245 return obj.toString().replace(/ \{[\s\S]*$/, ''); 8246 } else if (isUndefined(obj)) { 8247 return 'undefined'; 8248 } else if (typeof obj !== 'string') { 8249 return serializeObject(obj); 8250 } 8251 return obj; 8252} 8253 8254/* global angularModule: true, 8255 version: true, 8256 8257 $CompileProvider, 8258 8259 htmlAnchorDirective, 8260 inputDirective, 8261 inputDirective, 8262 formDirective, 8263 scriptDirective, 8264 selectDirective, 8265 optionDirective, 8266 ngBindDirective, 8267 ngBindHtmlDirective, 8268 ngBindTemplateDirective, 8269 ngClassDirective, 8270 ngClassEvenDirective, 8271 ngClassOddDirective, 8272 ngCloakDirective, 8273 ngControllerDirective, 8274 ngFormDirective, 8275 ngHideDirective, 8276 ngIfDirective, 8277 ngIncludeDirective, 8278 ngIncludeFillContentDirective, 8279 ngInitDirective, 8280 ngNonBindableDirective, 8281 ngPluralizeDirective, 8282 ngRepeatDirective, 8283 ngShowDirective, 8284 ngStyleDirective, 8285 ngSwitchDirective, 8286 ngSwitchWhenDirective, 8287 ngSwitchDefaultDirective, 8288 ngOptionsDirective, 8289 ngTranscludeDirective, 8290 ngModelDirective, 8291 ngListDirective, 8292 ngChangeDirective, 8293 patternDirective, 8294 patternDirective, 8295 requiredDirective, 8296 requiredDirective, 8297 minlengthDirective, 8298 minlengthDirective, 8299 maxlengthDirective, 8300 maxlengthDirective, 8301 ngValueDirective, 8302 ngModelOptionsDirective, 8303 ngAttributeAliasDirectives, 8304 ngEventDirectives, 8305 8306 $AnchorScrollProvider, 8307 $AnimateProvider, 8308 $CoreAnimateCssProvider, 8309 $$CoreAnimateJsProvider, 8310 $$CoreAnimateQueueProvider, 8311 $$AnimateRunnerFactoryProvider, 8312 $$AnimateAsyncRunFactoryProvider, 8313 $BrowserProvider, 8314 $CacheFactoryProvider, 8315 $ControllerProvider, 8316 $DateProvider, 8317 $DocumentProvider, 8318 $ExceptionHandlerProvider, 8319 $FilterProvider, 8320 $$ForceReflowProvider, 8321 $InterpolateProvider, 8322 $IntervalProvider, 8323 $$HashMapProvider, 8324 $HttpProvider, 8325 $HttpParamSerializerProvider, 8326 $HttpParamSerializerJQLikeProvider, 8327 $HttpBackendProvider, 8328 $xhrFactoryProvider, 8329 $jsonpCallbacksProvider, 8330 $LocationProvider, 8331 $LogProvider, 8332 $ParseProvider, 8333 $RootScopeProvider, 8334 $QProvider, 8335 $$QProvider, 8336 $$SanitizeUriProvider, 8337 $SceProvider, 8338 $SceDelegateProvider, 8339 $SnifferProvider, 8340 $TemplateCacheProvider, 8341 $TemplateRequestProvider, 8342 $$TestabilityProvider, 8343 $TimeoutProvider, 8344 $$RAFProvider, 8345 $WindowProvider, 8346 $$jqLiteProvider, 8347 $$CookieReaderProvider 8348*/ 8349 8350 8351/** 8352 * @ngdoc object 8353 * @name angular.version 8354 * @module ng 8355 * @description 8356 * An object that contains information about the current AngularJS version. 8357 * 8358 * This object has the following properties: 8359 * 8360 * - `full` â `{string}` â Full version string, such as "0.9.18". 8361 * - `major` â `{number}` â Major version number, such as "0". 8362 * - `minor` â `{number}` â Minor version number, such as "9". 8363 * - `dot` â `{number}` â Dot version number, such as "18". 8364 * - `codeName` â `{string}` â Code name of the release, such as "jiggling-armfat". 8365 */ 8366var version = { 8367 // These placeholder strings will be replaced by grunt's `build` task. 8368 // They need to be double- or single-quoted. 8369 full: '1.5.11', 8370 major: 1, 8371 minor: 5, 8372 dot: 11, 8373 codeName: 'princely-quest' 8374}; 8375 8376 8377function publishExternalAPI(angular) { 8378 extend(angular, { 8379 'bootstrap': bootstrap, 8380 'copy': copy, 8381 'extend': extend, 8382 'merge': merge, 8383 'equals': equals, 8384 'element': jqLite, 8385 'forEach': forEach, 8386 'injector': createInjector, 8387 'noop': noop, 8388 'bind': bind, 8389 'toJson': toJson, 8390 'fromJson': fromJson, 8391 'identity': identity, 8392 'isUndefined': isUndefined, 8393 'isDefined': isDefined, 8394 'isString': isString, 8395 'isFunction': isFunction, 8396 'isObject': isObject, 8397 'isNumber': isNumber, 8398 'isElement': isElement, 8399 'isArray': isArray, 8400 'version': version, 8401 'isDate': isDate, 8402 'lowercase': lowercase, 8403 'uppercase': uppercase, 8404 'callbacks': {$$counter: 0}, 8405 'getTestability': getTestability, 8406 '$$minErr': minErr, 8407 '$$csp': csp, 8408 'reloadWithDebugInfo': reloadWithDebugInfo 8409 }); 8410 8411 angularModule = setupModuleLoader(window); 8412 8413 angularModule('ng', ['ngLocale'], ['$provide', 8414 function ngModule($provide) { 8415 // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it. 8416 $provide.provider({ 8417 $$sanitizeUri: $$SanitizeUriProvider 8418 }); 8419 $provide.provider('$compile', $CompileProvider). 8420 directive({ 8421 a: htmlAnchorDirective, 8422 input: inputDirective, 8423 textarea: inputDirective, 8424 form: formDirective, 8425 script: scriptDirective, 8426 select: selectDirective, 8427 option: optionDirective, 8428 ngBind: ngBindDirective, 8429 ngBindHtml: ngBindHtmlDirective, 8430 ngBindTemplate: ngBindTemplateDirective, 8431 ngClass: ngClassDirective, 8432 ngClassEven: ngClassEvenDirective, 8433 ngClassOdd: ngClassOddDirective, 8434 ngCloak: ngCloakDirective, 8435 ngController: ngControllerDirective, 8436 ngForm: ngFormDirective, 8437 ngHide: ngHideDirective, 8438 ngIf: ngIfDirective, 8439 ngInclude: ngIncludeDirective, 8440 ngInit: ngInitDirective, 8441 ngNonBindable: ngNonBindableDirective, 8442 ngPluralize: ngPluralizeDirective, 8443 ngRepeat: ngRepeatDirective, 8444 ngShow: ngShowDirective, 8445 ngStyle: ngStyleDirective, 8446 ngSwitch: ngSwitchDirective, 8447 ngSwitchWhen: ngSwitchWhenDirective, 8448 ngSwitchDefault: ngSwitchDefaultDirective, 8449 ngOptions: ngOptionsDirective, 8450 ngTransclude: ngTranscludeDirective, 8451 ngModel: ngModelDirective, 8452 ngList: ngListDirective, 8453 ngChange: ngChangeDirective, 8454 pattern: patternDirective, 8455 ngPattern: patternDirective, 8456 required: requiredDirective, 8457 ngRequired: requiredDirective, 8458 minlength: minlengthDirective, 8459 ngMinlength: minlengthDirective, 8460 maxlength: maxlengthDirective, 8461 ngMaxlength: maxlengthDirective, 8462 ngValue: ngValueDirective, 8463 ngModelOptions: ngModelOptionsDirective 8464 }). 8465 directive({ 8466 ngInclude: ngIncludeFillContentDirective 8467 }). 8468 directive(ngAttributeAliasDirectives). 8469 directive(ngEventDirectives); 8470 $provide.provider({ 8471 $anchorScroll: $AnchorScrollProvider, 8472 $animate: $AnimateProvider, 8473 $animateCss: $CoreAnimateCssProvider, 8474 $$animateJs: $$CoreAnimateJsProvider, 8475 $$animateQueue: $$CoreAnimateQueueProvider, 8476 $$AnimateRunner: $$AnimateRunnerFactoryProvider, 8477 $$animateAsyncRun: $$AnimateAsyncRunFactoryProvider, 8478 $browser: $BrowserProvider, 8479 $cacheFactory: $CacheFactoryProvider, 8480 $controller: $ControllerProvider, 8481 $document: $DocumentProvider, 8482 $exceptionHandler: $ExceptionHandlerProvider, 8483 $filter: $FilterProvider, 8484 $$forceReflow: $$ForceReflowProvider, 8485 $interpolate: $InterpolateProvider, 8486 $interval: $IntervalProvider, 8487 $http: $HttpProvider, 8488 $httpParamSerializer: $HttpParamSerializerProvider, 8489 $httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider, 8490 $httpBackend: $HttpBackendProvider,
8491 $xhrFactory: $xhrFactoryProvider, 8492 $jsonpCallbacks: $jsonpCallbacksProvider, 8493 $location: $LocationProvider, 8494 $log: $LogProvider, 8495 $parse: $ParseProvider, 8496 $rootScope: $RootScopeProvider, 8497 $q: $QProvider, 8498 $$q: $$QProvider, 8499 $sce: $SceProvider, 8500 $sceDelegate: $SceDelegateProvider, 8501 $sniffer: $SnifferProvider, 8502 $templateCache: $TemplateCacheProvider, 8503 $templateRequest: $TemplateRequestProvider, 8504 $$testability: $$TestabilityProvider, 8505 $timeout: $TimeoutProvider, 8506 $window: $WindowProvider, 8507 $$rAF: $$RAFProvider, 8508 $$jqLite: $$jqLiteProvider, 8509 $$HashMap: $$HashMapProvider, 8510 $$cookieReader: $$CookieReaderProvider 8511 }); 8512 } 8513 ]); 8514} 8515 8516/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 8517 * Any commits to this file should be reviewed with security in mind. * 8518 * Changes to this file can potentially create security vulnerabilities. * 8519 * An approval from 2 Core members with history of modifying * 8520 * this file is required. * 8521 * * 8522 * Does the change somehow allow for arbitrary javascript to be executed? * 8523 * Or allows for someone to change the prototype of built-in objects? * 8524 * Or gives undesired access to variables likes document or window? * 8525 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 8526 8527/* global JQLitePrototype: true, 8528 addEventListenerFn: true, 8529 removeEventListenerFn: true, 8530 BOOLEAN_ATTR: true, 8531 ALIASED_ATTR: true 8532*/ 8533 8534////////////////////////////////// 8535//JQLite 8536////////////////////////////////// 8537 8538/** 8539 * @ngdoc function 8540 * @name angular.element 8541 * @module ng 8542 * @kind function 8543 * 8544 * @description 8545 * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element. 8546 * 8547 * If jQuery is available, `angular.element` is an alias for the 8548 * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element` 8549 * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or **jqLite**. 8550 * 8551 * jqLite is a tiny, API-compatible subset of jQuery that allows 8552 * Angular to manipulate the DOM in a cross-browser compatible way. jqLite implements only the most 8553 * commonly needed functionality with the goal of having a very small footprint. 8554 * 8555 * To use `jQuery`, simply ensure it is loaded before the `angular.js` file. You can also use the 8556 * {@link ngJq `ngJq`} directive to specify that jqlite should be used over jQuery, or to use a 8557 * specific version of jQuery if multiple versions exist on the page. 8558 * 8559 * <div class="alert alert-info">**Note:** All element references in Angular are always wrapped with jQuery or 8560 * jqLite (such as the element argument in a directive's compile / link function). They are never raw DOM references.</div> 8561 * 8562 * <div class="alert alert-warning">**Note:** Keep in mind that this function will not find elements 8563 * by tag name / CSS selector. For lookups by tag name, try instead `angular.element(document).find(...)` 8564 * or `$document.find()`, or use the standard DOM APIs, e.g. `document.querySelectorAll()`.</div> 8565 * 8566 * ## Angular's jqLite 8567 * jqLite provides only the following jQuery methods: 8568 * 8569 * - [`addClass()`](http://api.jquery.com/addClass/) - Does not support a function as first argument 8570 * - [`after()`](http://api.jquery.com/after/) 8571 * - [`append()`](http://api.jquery.com/append/) 8572 * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters 8573 * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData 8574 * - [`children()`](http://api.jquery.com/children/) - Does not support selectors 8575 * - [`clone()`](http://api.jquery.com/clone/) 8576 * - [`contents()`](http://api.jquery.com/contents/) 8577 * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`. 8578 * As a setter, does not convert numbers to strings or append 'px', and also does not have automatic property prefixing. 8579 * - [`data()`](http://api.jquery.com/data/) 8580 * - [`detach()`](http://api.jquery.com/detach/) 8581 * - [`empty()`](http://api.jquery.com/empty/) 8582 * - [`eq()`](http://api.jquery.com/eq/) 8583 * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name 8584 * - [`hasClass()`](http://api.jquery.com/hasClass/) 8585 * - [`html()`](http://api.jquery.com/html/) 8586 * - [`next()`](http://api.jquery.com/next/) - Does not support selectors 8587 * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData 8588 * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces, selectors or event object as parameter 8589 * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors 8590 * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors 8591 * - [`prepend()`](http://api.jquery.com/prepend/) 8592 * - [`prop()`](http://api.jquery.com/prop/) 8593 * - [`ready()`](http://api.jquery.com/ready/) 8594 * - [`remove()`](http://api.jquery.com/remove/) 8595 * - [`removeAttr()`](http://api.jquery.com/removeAttr/) - Does not support multiple attributes 8596 * - [`removeClass()`](http://api.jquery.com/removeClass/) - Does not support a function as first argument 8597 * - [`removeData()`](http://api.jquery.com/removeData/) 8598 * - [`replaceWith()`](http://api.jquery.com/replaceWith/) 8599 * - [`text()`](http://api.jquery.com/text/) 8600 * - [`toggleClass()`](http://api.jquery.com/toggleClass/) - Does not support a function as first argument 8601 * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers 8602 * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces or event object as parameter 8603 * - [`val()`](http://api.jquery.com/val/) 8604 * - [`wrap()`](http://api.jquery.com/wrap/) 8605 * 8606 * ## jQuery/jqLite Extras 8607 * Angular also provides the following additional methods and events to both jQuery and jqLite: 8608 * 8609 * ### Events 8610 * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction a
8610pis and fires this event 8611 * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM 8612 * element before it is removed. 8613 * 8614 * ### Methods 8615 * - `controller(name)` - retrieves the controller of the current element or its parent. By default 8616 * retrieves controller associated with the `ngController` directive. If `name` is provided as 8617 * camelCase directive name, then the controller for this directive will be retrieved (e.g. 8618 * `'ngModel'`). 8619 * - `injector()` - retrieves the injector of the current element or its parent. 8620 * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current 8621 * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to 8622 * be enabled. 8623 * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the 8624 * current element. This getter should be used only on elements that contain a directive which starts a new isolate 8625 * scope. Calling `scope()` on this element always returns the original non-isolate scope. 8626 * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled. 8627 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top 8628 * parent element is reached. 8629 * 8630 * @knownIssue You cannot spy on `angular.element` if you are using Jasmine version 1.x. See 8631 * https://github.com/angular/angular.js/issues/14251 for more information. 8632 * 8633 * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery. 8634 * @returns {Object} jQuery object. 8635 */ 8636 8637JQLite.expando = 'ng339'; 8638 8639var jqCache = JQLite.cache = {}, 8640 jqId = 1, 8641 addEventListenerFn = function(element, type, fn) { 8642 element.addEventListener(type, fn, false); 8643 }, 8644 removeEventListenerFn = function(element, type, fn) { 8645 element.removeEventListener(type, fn, false); 8646 }; 8647 8648/* 8649 * !!! This is an undocumented "private" function !!! 8650 */ 8651JQLite._data = function(node) { 8652 //jQuery always returns an object on cache miss 8653 return this.cache[node[this.expando]] || {}; 8654}; 8655 8656function jqNextId() { return ++jqId; } 8657 8658 8659var SPECIAL_CHARS_REGEXP = /([:\-_]+(.))/g; 8660var MOZ_HACK_REGEXP = /^moz([A-Z])/; 8661var MOUSE_EVENT_MAP = { mouseleave: 'mouseout', mouseenter: 'mouseover' }; 8662var jqLiteMinErr = minErr('jqLite'); 8663 8664/** 8665 * Converts snake_case to camelCase. 8666 * Also there is special case for Moz prefix starting with upper case letter. 8667 * @param name Name to normalize 8668 */ 8669function camelCase(name) { 8670 return name. 8671 replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { 8672 return offset ? letter.toUpperCase() : letter; 8673 }). 8674 replace(MOZ_HACK_REGEXP, 'Moz$1'); 8675} 8676 8677var SINGLE_TAG_REGEXP = /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/; 8678var HTML_REGEXP = /<|&#?\w+;/; 8679var TAG_NAME_REGEXP = /<([\w:-]+)/; 8680var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi; 8681 8682var wrapMap = { 8683 'option': [1, '<select multiple="multiple">', '</select>'], 8684 8685 'thead': [1, '<table>', '</table>'], 8686 'col': [2, '<table><colgroup>', '</colgroup></table>'], 8687 'tr': [2, '<table><tbody>', '</tbody></table>'], 8688 'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'], 8689 '_default': [0, '', ''] 8690}; 8691 8692wrapMap.optgroup = wrapMap.option; 8693wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; 8694wrapMap.th = wrapMap.td; 8695 8696 8697function jqLiteIsTextNode(html) { 8698 return !HTML_REGEXP.test(html); 8699} 8700 8701function jqLiteAcceptsData(node) { 8702 // The window object can accept data but has no nodeType 8703 // Otherwise we are only interested in elements (1) and documents (9) 8704 var nodeType = node.nodeType; 8705 return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT; 8706} 8707 8708function jqLiteHasData(node) { 8709 for (var key in jqCache[node.ng339]) { 8710 return true; 8711 } 8712 return false; 8713} 8714 8715function jqLiteCleanData(nodes) { 8716 for (var i = 0, ii = nodes.length; i < ii; i++) { 8717 jqLiteRemoveData(nodes[i]); 8718 } 8719} 8720 8721function jqLiteBuildFragment(html, context) { 8722 var tmp, tag, wrap, 8723 fragment = context.createDocumentFragment(), 8724 nodes = [], i; 8725 8726 if (jqLiteIsTextNode(html)) { 8727 // Convert non-html into a text node 8728 nodes.push(context.createTextNode(html)); 8729 } else { 8730 // Convert html into DOM nodes 8731 tmp = fragment.appendChild(context.createElement('div')); 8732 tag = (TAG_NAME_REGEXP.exec(html) || ['', ''])[1].toLowerCase(); 8733 wrap = wrapMap[tag] || wrapMap._default; 8734 tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, '<$1></$2>') + wrap[2]; 8735 8736 // Descend through wrappers to the right content 8737 i = wrap[0]; 8738 while (i--) { 8739 tmp = tmp.lastChild; 8740 } 8741 8742 nodes = concat(nodes, tmp.childNodes); 8743 8744 tmp = fragment.firstChild; 8745 tmp.textContent = ''; 8746 } 8747 8748 // Remove wrapper from fragment 8749 fragment.textContent = ''; 8750 fragment.innerHTML = ''; // Clear inner HTML 8751 forEach(nodes, function(node) { 8752 fragment.appendChild(node); 8753 }); 8754 8755 return fragment; 8756} 8757 8758function jqLiteParseHTML(html, context) { 8759 context = context || window.document; 8760 var parsed; 8761 8762 if ((parsed = SINGLE_TAG_REGEXP.exec(html))) { 8763 return [context.createElement(parsed[1])]; 8764 } 8765 8766 if ((parsed = jqLiteBuildFragment(html, context))) { 8767 return parsed.childNodes; 8768 } 8769 8770 return []; 8771} 8772 8773function jqLiteWrapNode(node, wrapper) { 8774 var parent = node.parentNode; 8775 8776 if (parent) { 8777 parent.replaceChild(wrapper, node); 8778 } 8779 8780 wrapper.appendChild(node); 8781} 8782 8783 8784// IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259. 8785var jqLiteContains = window.Node.prototype.contains || /** @this */ function(arg) { 8786 // eslint-disable-next-line no-bitwise 8787 return !!(this.compareDocumentPosition(arg) & 16); 8788}; 8789 8790///////////////////////////////////////////// 8791function JQLite(element) { 8792 if (element instanceof JQLite) { 8793 return element; 8794 } 8795 8796 var argIsString; 8797 8798 if (isString(element)) { 8799 element = trim(element); 8800 argIsString = true; 8801 } 8802 if (!(this instanceof JQLite)) { 8803 if (argIsString && element.charAt(0) !== '<') { 8804 throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element'); 8805 } 8806 return new JQLite(element); 8807 } 8808 8809 if (argIsString) { 8810 jqLiteAddNodes(this, jqLiteParseHTML(element)); 8811 } else { 8812 jqLiteAddNodes(this, element); 8813 } 8814} 8815 8816function jqLiteClone(element) { 8817 return element.cloneNode(true); 8818} 8819 8820function jqLiteDealoc(element, onlyDescendants) { 8821 if (!onlyDescendants) jqLiteRemoveData(element); 8822 8823 if (element.querySelectorAll) { 8824 var descendants = element.querySelectorAll('*');
8825 for (var i = 0, l = descendants.length; i < l; i++) { 8826 jqLiteRemoveData(descendants[i]); 8827 } 8828 } 8829} 8830 8831function jqLiteOff(element, type, fn, unsupported) { 8832 if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument'); 8833 8834 var expandoStore = jqLiteExpandoStore(element); 8835 var events = expandoStore && expandoStore.events; 8836 var handle = expandoStore && expandoStore.handle; 8837 8838 if (!handle) return; //no listeners registered 8839 8840 if (!type) { 8841 for (type in events) { 8842 if (type !== '$destroy') { 8843 removeEventListenerFn(element, type, handle); 8844 } 8845 delete events[type]; 8846 } 8847 } else { 8848 8849 var removeHandler = function(type) { 8850 var listenerFns = events[type]; 8851 if (isDefined(fn)) { 8852 arrayRemove(listenerFns || [], fn); 8853 } 8854 if (!(isDefined(fn) && listenerFns && listenerFns.length > 0)) { 8855 removeEventListenerFn(element, type, handle); 8856 delete events[type]; 8857 } 8858 }; 8859 8860 forEach(type.split(' '), function(type) { 8861 removeHandler(type); 8862 if (MOUSE_EVENT_MAP[type]) { 8863 removeHandler(MOUSE_EVENT_MAP[type]); 8864 } 8865 }); 8866 } 8867} 8868 8869function jqLiteRemoveData(element, name) { 8870 var expandoId = element.ng339; 8871 var expandoStore = expandoId && jqCache[expandoId]; 8872 8873 if (expandoStore) { 8874 if (name) { 8875 delete expandoStore.data[name]; 8876 return; 8877 } 8878 8879 if (expandoStore.handle) { 8880 if (expandoStore.events.$destroy) { 8881 expandoStore.handle({}, '$destroy'); 8882 } 8883 jqLiteOff(element); 8884 } 8885 delete jqCache[expandoId]; 8886 element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it 8887 } 8888} 8889 8890 8891function jqLiteExpandoStore(element, createIfNecessary) { 8892 var expandoId = element.ng339, 8893 expandoStore = expandoId && jqCache[expandoId]; 8894 8895 if (createIfNecessary && !expandoStore) { 8896 element.ng339 = expandoId = jqNextId(); 8897 expandoStore = jqCache[expandoId] = {events: {}, data: {}, handle: undefined}; 8898 } 8899 8900 return expandoStore; 8901} 8902 8903 8904function jqLiteData(element, key, value) { 8905 if (jqLiteAcceptsData(element)) { 8906 8907 var isSimpleSetter = isDefined(value); 8908 var isSimpleGetter = !isSimpleSetter && key && !isObject(key); 8909 var massGetter = !key; 8910 var expandoStore = jqLiteExpandoStore(element, !isSimpleGetter); 8911 var data = expandoStore && expandoStore.data; 8912 8913 if (isSimpleSetter) { // data('key', value) 8914 data[key] = value; 8915 } else { 8916 if (massGetter) { // data() 8917 return data; 8918 } else { 8919 if (isSimpleGetter) { // data('key') 8920 // don't force creation of expandoStore if it doesn't exist yet 8921 return data && data[key]; 8922 } else { // mass-setter: data({key1: val1, key2: val2}) 8923 extend(data, key); 8924 } 8925 } 8926 } 8927 } 8928} 8929 8930function jqLiteHasClass(element, selector) { 8931 if (!element.getAttribute) return false; 8932 return ((' ' + (element.getAttribute('class') || '') + ' ').replace(/[\n\t]/g, ' '). 8933 indexOf(' ' + selector + ' ') > -1); 8934} 8935 8936function jqLiteRemoveClass(element, cssClasses) { 8937 if (cssClasses && element.setAttribute) { 8938 forEach(cssClasses.split(' '), function(cssClass) { 8939 element.setAttribute('class', trim( 8940 (' ' + (element.getAttribute('class') || '') + ' ') 8941 .replace(/[\n\t]/g, ' ') 8942 .replace(' ' + trim(cssClass) + ' ', ' ')) 8943 ); 8944 }); 8945 } 8946} 8947 8948function jqLiteAddClass(element, cssClasses) { 8949 if (cssClasses && element.setAttribute) { 8950 var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') 8951 .replace(/[\n\t]/g, ' '); 8952 8953 forEach(cssClasses.split(' '), function(cssClass) { 8954 cssClass = trim(cssClass); 8955 if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) { 8956 existingClasses += cssClass + ' '; 8957 } 8958 }); 8959 8960 element.setAttribute('class', trim(existingClasses)); 8961 } 8962} 8963 8964 8965function jqLiteAddNodes(root, elements) { 8966 // THIS CODE IS VERY HOT. Don't make changes without benchmarking. 8967 8968 if (elements) { 8969 8970 // if a Node (the most common case) 8971 if (elements.nodeType) { 8972 root[root.length++] = elements; 8973 } else { 8974 var length = elements.length; 8975 8976 // if an Array or NodeList and not a Window 8977 if (typeof length === 'number' && elements.window !== elements) { 8978 if (length) { 8979 for (var i = 0; i < length; i++) { 8980 root[root.length++] = elements[i]; 8981 } 8982 } 8983 } else { 8984 root[root.length++] = elements; 8985 } 8986 } 8987 } 8988} 8989 8990 8991function jqLiteController(element, name) { 8992 return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller'); 8993} 8994 8995function jqLiteInheritedData(element, name, value) { 8996 // if element is the document object work with the html element instead 8997 // this makes $(document).scope() possible 8998 if (element.nodeType === NODE_TYPE_DOCUMENT) { 8999 element = element.documentElement; 9000 } 9001 var names = isArray(name) ? name : [name]; 9002 9003 while (element) { 9004 for (var i = 0, ii = names.length; i < ii; i++) { 9005 if (isDefined(value = jqLite.data(element, names[i]))) return value; 9006 } 9007 9008 // If dealing with a document fragment node with a host element, and no parent, use the host 9009 // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM 9010 // to lookup parent controllers. 9011 element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host); 9012 } 9013} 9014 9015function jqLiteEmpty(element) { 9016 jqLiteDealoc(element, true); 9017 while (element.firstChild) { 9018 element.removeChild(element.firstChild); 9019 } 9020} 9021 9022function jqLiteRemove(element, keepData) { 9023 if (!keepData) jqLiteDealoc(element); 9024 var parent = element.parentNode; 9025 if (parent) parent.removeChild(element); 9026} 9027 9028 9029function jqLiteDocumentLoaded(action, win) { 9030 win = win || window; 9031 if (win.document.readyState === 'complete') { 9032 // Force the action to be run async for consistent behavior 9033 // from the action's point of view 9034 // i.e. it will definitely not be in a $apply 9035 win.setTimeout(action); 9036 } else { 9037 // No need to unbind this handler as load is only ever called once 9038 jqLite(win).on('load', action); 9039 } 9040} 9041
9042////////////////////////////////////////// 9043// Functions which are declared directly. 9044////////////////////////////////////////// 9045var JQLitePrototype = JQLite.prototype = { 9046 ready: function(fn) { 9047 var fired = false; 9048 9049 function trigger() { 9050 if (fired) return; 9051 fired = true; 9052 fn(); 9053 } 9054 9055 // check if document is already loaded 9056 if (window.document.readyState === 'complete') { 9057 window.setTimeout(trigger); 9058 } else { 9059 this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 9060 // we can not use jqLite since we are not done loading and jQuery could be loaded later. 9061 // eslint-disable-next-line new-cap 9062 JQLite(window).on('load', trigger); // fallback to window.onload for others 9063 } 9064 }, 9065 toString: function() { 9066 var value = []; 9067 forEach(this, function(e) { value.push('' + e);}); 9068 return '[' + value.join(', ') + ']'; 9069 }, 9070 9071 eq: function(index) { 9072 return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]); 9073 }, 9074 9075 length: 0, 9076 push: push, 9077 sort: [].sort, 9078 splice: [].splice 9079}; 9080 9081////////////////////////////////////////// 9082// Functions iterating getter/setters. 9083// these functions return self on setter and 9084// value on get. 9085////////////////////////////////////////// 9086var BOOLEAN_ATTR = {}; 9087forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) { 9088 BOOLEAN_ATTR[lowercase(value)] = value; 9089}); 9090var BOOLEAN_ELEMENTS = {}; 9091forEach('input,select,option,textarea,button,form,details'.split(','), function(value) { 9092 BOOLEAN_ELEMENTS[value] = true; 9093}); 9094var ALIASED_ATTR = { 9095 'ngMinlength': 'minlength', 9096 'ngMaxlength': 'maxlength', 9097 'ngMin': 'min', 9098 'ngMax': 'max', 9099 'ngPattern': 'pattern' 9100}; 9101 9102function getBooleanAttrName(element, name) { 9103 // check dom last since we will most likely fail on name 9104 var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()]; 9105 9106 // booleanAttr is here twice to minimize DOM access 9107 return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr; 9108} 9109 9110function getAliasedAttrName(name) { 9111 return ALIASED_ATTR[name]; 9112} 9113 9114forEach({ 9115 data: jqLiteData, 9116 removeData: jqLiteRemoveData, 9117 hasData: jqLiteHasData, 9118 cleanData: jqLiteCleanData 9119}, function(fn, name) { 9120 JQLite[name] = fn; 9121}); 9122 9123forEach({ 9124 data: jqLiteData, 9125 inheritedData: jqLiteInheritedData, 9126 9127 scope: function(element) { 9128 // Can't use jqLiteData here directly so we stay compatible with jQuery! 9129 return jqLite.data(element, '$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']); 9130 }, 9131 9132 isolateScope: function(element) { 9133 // Can't use jqLiteData here directly so we stay compatible with jQuery! 9134 return jqLite.data(element, '$isolateScope') || jqLite.data(element, '$isolateScopeNoTemplate'); 9135 }, 9136 9137 controller: jqLiteController, 9138 9139 injector: function(element) { 9140 return jqLiteInheritedData(element, '$injector'); 9141 }, 9142 9143 removeAttr: function(element, name) { 9144 element.removeAttribute(name); 9145 }, 9146 9147 hasClass: jqLiteHasClass, 9148 9149 css: function(element, name, value) { 9150 name = camelCase(name); 9151 9152 if (isDefined(value)) { 9153 element.style[name] = value; 9154 } else { 9155 return element.style[name]; 9156 } 9157 }, 9158 9159 attr: function(element, name, value) { 9160 var nodeType = element.nodeType; 9161 if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT) { 9162 return; 9163 } 9164 var lowercasedName = lowercase(name); 9165 if (BOOLEAN_ATTR[lowercasedName]) { 9166 if (isDefined(value)) { 9167 if (value) { 9168 element[name] = true; 9169 element.setAttribute(name, lowercasedName); 9170 } else { 9171 element[name] = false; 9172 element.removeAttribute(lowercasedName); 9173 } 9174 } else { 9175 return (element[name] || 9176 (element.attributes.getNamedItem(name) || noop).specified) 9177 ? lowercasedName 9178 : undefined; 9179 } 9180 } else if (isDefined(value)) { 9181 element.setAttribute(name, value); 9182 } else if (element.getAttribute) { 9183 // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code 9184 // some elements (e.g. Document) don't have get attribute, so return undefined 9185 var ret = element.getAttribute(name, 2); 9186 // normalize non-existing attributes to undefined (as jQuery) 9187 return ret === null ? undefined : ret; 9188 } 9189 }, 9190 9191 prop: function(element, name, value) { 9192 if (isDefined(value)) { 9193 element[name] = value; 9194 } else { 9195 return element[name]; 9196 } 9197 }, 9198 9199 text: (function() { 9200 getText.$dv = ''; 9201 return getText; 9202 9203 function getText(element, value) { 9204 if (isUndefined(value)) { 9205 var nodeType = element.nodeType; 9206 return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : ''; 9207 } 9208 element.textContent = value; 9209 } 9210 })(), 9211 9212 val: function(element, value) { 9213 if (isUndefined(value)) { 9214 if (element.multiple && nodeName_(element) === 'select') { 9215 var result = [];
vendor: 10,287 bytes, lines 9216-9558
9216 forEach(element.options, function(option) { 9217 if (option.selected) { 9218 result.push(option.value || option.text); 9219 } 9220 }); 9221 return result.length === 0 ? null : result; 9222 } 9223 return element.value; 9224 } 9225 element.value = value; 9226 }, 9227 9228 html: function(element, value) { 9229 if (isUndefined(value)) { 9230 return element.innerHTML; 9231 } 9232 jqLiteDealoc(element, true); 9233 element.innerHTML = value; 9234 }, 9235 9236 empty: jqLiteEmpty 9237}, function(fn, name) { 9238 /** 9239 * Properties: writes return selection, reads return first value 9240 */ 9241 JQLite.prototype[name] = function(arg1, arg2) { 9242 var i, key; 9243 var nodeCount = this.length; 9244 9245 // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it 9246 // in a way that survives minification. 9247 // jqLiteEmpty takes no arguments but is a setter. 9248 if (fn !== jqLiteEmpty && 9249 (isUndefined((fn.length === 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) { 9250 if (isObject(arg1)) { 9251 9252 // we are a write, but the object properties are the key/values 9253 for (i = 0; i < nodeCount; i++) { 9254 if (fn === jqLiteData) { 9255 // data() takes the whole object in jQuery 9256 fn(this[i], arg1); 9257 } else { 9258 for (key in arg1) { 9259 fn(this[i], key, arg1[key]); 9260 } 9261 } 9262 } 9263 // return self for chaining 9264 return this; 9265 } else { 9266 // we are a read, so read the first child. 9267 // TODO: do we still need this? 9268 var value = fn.$dv; 9269 // Only if we have $dv do we iterate over all, otherwise it is just the first element. 9270 var jj = (isUndefined(value)) ? Math.min(nodeCount, 1) : nodeCount; 9271 for (var j = 0; j < jj; j++) { 9272 var nodeValue = fn(this[j], arg1, arg2); 9273 value = value ? value + nodeValue : nodeValue; 9274 } 9275 return value; 9276 } 9277 } else { 9278 // we are a write, so apply to all children 9279 for (i = 0; i < nodeCount; i++) { 9280 fn(this[i], arg1, arg2); 9281 } 9282 // return self for chaining 9283 return this; 9284 } 9285 }; 9286}); 9287 9288function createEventHandler(element, events) { 9289 var eventHandler = function(event, type) { 9290 // jQuery specific api 9291 event.isDefaultPrevented = function() { 9292 return event.defaultPrevented; 9293 }; 9294 9295 var eventFns = events[type || event.type]; 9296 var eventFnsLength = eventFns ? eventFns.length : 0; 9297 9298 if (!eventFnsLength) return; 9299 9300 if (isUndefined(event.immediatePropagationStopped)) { 9301 var originalStopImmediatePropagation = event.stopImmediatePropagation; 9302 event.stopImmediatePropagation = function() { 9303 event.immediatePropagationStopped = true; 9304 9305 if (event.stopPropagation) { 9306 event.stopPropagation(); 9307 } 9308 9309 if (originalStopImmediatePropagation) { 9310 originalStopImmediatePropagation.call(event); 9311 } 9312 }; 9313 } 9314 9315 event.isImmediatePropagationStopped = function() { 9316 return event.immediatePropagationStopped === true; 9317 }; 9318 9319 // Some events have special handlers that wrap the real handler 9320 var handlerWrapper = eventFns.specialHandlerWrapper || defaultHandlerWrapper; 9321 9322 // Copy event handlers in case event handlers array is modified during execution. 9323 if ((eventFnsLength > 1)) { 9324 eventFns = shallowCopy(eventFns); 9325 } 9326 9327 for (var i = 0; i < eventFnsLength; i++) { 9328 if (!event.isImmediatePropagationStopped()) { 9329 handlerWrapper(element, event, eventFns[i]); 9330 } 9331 } 9332 }; 9333 9334 // TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all 9335 // events on `element` 9336 eventHandler.elem = element; 9337 return eventHandler; 9338} 9339 9340function defaultHandlerWrapper(element, event, handler) { 9341 handler.call(element, event); 9342} 9343 9344function specialMouseHandlerWrapper(target, event, handler) { 9345 // Refer to jQuery's implementation of mouseenter & mouseleave 9346 // Read about mouseenter and mouseleave: 9347 // http://www.quirksmode.org/js/events_mouse.html#link8 9348 var related = event.relatedTarget; 9349 // For mousenter/leave call the handler if related is outside the target. 9350 // NB: No relatedTarget if the mouse left/entered the browser window 9351 if (!related || (related !== target && !jqLiteContains.call(target, related))) { 9352 handler.call(target, event); 9353 } 9354} 9355 9356////////////////////////////////////////// 9357// Functions iterating traversal. 9358// These functions chain results into a single 9359// selector. 9360////////////////////////////////////////// 9361forEach({ 9362 removeData: jqLiteRemoveData, 9363 9364 on: function jqLiteOn(element, type, fn, unsupported) { 9365 if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); 9366 9367 // Do not add event handlers to non-elements because they will not be cleaned up. 9368 if (!jqLiteAcceptsData(element)) { 9369 return; 9370 } 9371 9372 var expandoStore = jqLiteExpandoStore(element, true); 9373 var events = expandoStore.events; 9374 var handle = expandoStore.handle; 9375 9376 if (!handle) { 9377 handle = expandoStore.handle = createEventHandler(element, events); 9378 } 9379 9380 // http://jsperf.com/string-indexof-vs-split 9381 var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type]; 9382 var i = types.length; 9383 9384 var addHandler = function(type, specialHandlerWrapper, noEventListener) { 9385 var eventFns = events[type]; 9386 9387 if (!eventFns) { 9388 eventFns = events[type] = []; 9389 eventFns.specialHandlerWrapper = specialHandlerWrapper; 9390 if (type !== '$destroy' && !noEventListener) { 9391 addEventListenerFn(element, type, handle); 9392 } 9393 } 9394 9395 eventFns.push(fn); 9396 }; 9397 9398 while (i--) { 9399 type = types[i]; 9400 if (MOUSE_EVENT_MAP[type]) { 9401 addHandler(MOUSE_EVENT_MAP[type], specialMouseHandlerWrapper); 9402 addHandler(type, undefined, true); 9403 } else { 9404 addHandler(type); 9405 } 9406 } 9407 }, 9408 9409 off: jqLiteOff, 9410 9411 one: function(element, type, fn) { 9412 element = jqLite(element); 9413 9414 //add the listener twice so that when it is called 9415 //you can remove the original function and still be 9416 //able to call element.off(ev, fn) normally 9417 element.on(type, function onFn() { 9418 element.off(type, fn); 9419 element.off(type, onFn); 9420 }); 9421 element.on(type, fn); 9422 }, 9423 9424 replaceWith: function(element, replaceNode) { 9425 var index, parent = element.parentNode; 9426 jqLiteDealoc(element); 9427 forEach(new JQLite(replaceNode), function(node) { 9428 if (index) { 9429 parent.insertBefore(node, index.nextSibling); 9430 } else { 9431 parent.replaceChild(node, element); 9432 } 9433 index = node; 9434 }); 9435 }, 9436 9437 children: function(element) { 9438 var children = []; 9439 forEach(element.childNodes, function(element) { 9440 if (element.nodeType === NODE_TYPE_ELEMENT) { 9441 children.push(element); 9442 } 9443 }); 9444 return children; 9445 }, 9446 9447 contents: function(element) { 9448 return element.contentDocument || element.childNodes || []; 9449 }, 9450 9451 append: function(element, node) { 9452 var nodeType = element.nodeType; 9453 if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return; 9454 9455 node = new JQLite(node); 9456 9457 for (var i = 0, ii = node.length; i < ii; i++) { 9458 var child = node[i]; 9459 element.appendChild(child); 9460 } 9461 }, 9462 9463 prepend: function(element, node) { 9464 if (element.nodeType === NODE_TYPE_ELEMENT) { 9465 var index = element.firstChild; 9466 forEach(new JQLite(node), function(child) { 9467 element.insertBefore(child, index); 9468 }); 9469 } 9470 }, 9471 9472 wrap: function(element, wrapNode) { 9473 jqLiteWrapNode(element, jqLite(wrapNode).eq(0).clone()[0]); 9474 }, 9475 9476 remove: jqLiteRemove, 9477 9478 detach: function(element) { 9479 jqLiteRemove(element, true); 9480 }, 9481 9482 after: function(element, newElement) { 9483 var index = element, parent = element.parentNode; 9484 9485 if (parent) { 9486 newElement = new JQLite(newElement); 9487 9488 for (var i = 0, ii = newElement.length; i < ii; i++) { 9489 var node = newElement[i]; 9490 parent.insertBefore(node, index.nextSibling); 9491 index = node; 9492 } 9493 } 9494 }, 9495 9496 addClass: jqLiteAddClass, 9497 removeClass: jqLiteRemoveClass, 9498 9499 toggleClass: function(element, selector, condition) { 9500 if (selector) { 9501 forEach(selector.split(' '), function(className) { 9502 var classCondition = condition; 9503 if (isUndefined(classCondition)) { 9504 classCondition = !jqLiteHasClass(element, className); 9505 } 9506 (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className); 9507 }); 9508 } 9509 }, 9510 9511 parent: function(element) { 9512 var parent = element.parentNode; 9513 return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null; 9514 }, 9515 9516 next: function(element) { 9517 return element.nextElementSibling; 9518 }, 9519 9520 find: function(element, selector) { 9521 if (element.getElementsByTagName) { 9522 return element.getElementsByTagName(selector); 9523 } else { 9524 return []; 9525 } 9526 }, 9527 9528 clone: jqLiteClone, 9529 9530 triggerHandler: function(element, event, extraParameters) { 9531 9532 var dummyEvent, eventFnsCopy, handlerArgs; 9533 var eventName = event.type || event; 9534 var expandoStore = jqLiteExpandoStore(element); 9535 var events = expandoStore && expandoStore.events; 9536 var eventFns = events && events[eventName]; 9537 9538 if (eventFns) { 9539 // Create a dummy event to pass to the handlers 9540 dummyEvent = { 9541 preventDefault: function() { this.defaultPrevented = true; }, 9542 isDefaultPrevented: function() { return this.defaultPrevented === true; }, 9543 stopImmediatePropagation: function() { this.immediatePropagationStopped = true; }, 9544 isImmediatePropagationStopped: function() { return this.immediatePropagationStopped === true; }, 9545 stopPropagation: noop, 9546 type: eventName, 9547 target: element 9548 }; 9549 9550 // If a custom event was provided then extend our dummy event with it 9551 if (event.type) { 9552 dummyEvent = extend(dummyEvent, event); 9553 } 9554 9555 // Copy event handlers in case event handlers array is modified during execution. 9556 eventFnsCopy = shallowCopy(eventFns); 9557 handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent]; 9558
9559 forEach(eventFnsCopy, function(fn) { 9560 if (!dummyEvent.isImmediatePropagationStopped()) { 9561 fn.apply(element, handlerArgs); 9562 } 9563 }); 9564 } 9565 } 9566}, function(fn, name) { 9567 /** 9568 * chaining functions 9569 */ 9570 JQLite.prototype[name] = function(arg1, arg2, arg3) { 9571 var value; 9572 9573 for (var i = 0, ii = this.length; i < ii; i++) { 9574 if (isUndefined(value)) { 9575 value = fn(this[i], arg1, arg2, arg3); 9576 if (isDefined(value)) { 9577 // any function which returns a value needs to be wrapped 9578 value = jqLite(value); 9579 } 9580 } else { 9581 jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3)); 9582 } 9583 } 9584 return isDefined(value) ? value : this; 9585 }; 9586}); 9587 9588// bind legacy bind/unbind to on/off 9589JQLite.prototype.bind = JQLite.prototype.on; 9590JQLite.prototype.unbind = JQLite.prototype.off; 9591 9592 9593// Provider for private $$jqLite service 9594/** @this */ 9595function $$jqLiteProvider() { 9596 this.$get = function $$jqLite() { 9597 return extend(JQLite, { 9598 hasClass: function(node, classes) { 9599 if (node.attr) node = node[0]; 9600 return jqLiteHasClass(node, classes); 9601 }, 9602 addClass: function(node, classes) { 9603 if (node.attr) node = node[0]; 9604 return jqLiteAddClass(node, classes); 9605 }, 9606 removeClass: function(node, classes) { 9607 if (node.attr) node = node[0]; 9608 return jqLiteRemoveClass(node, classes); 9609 } 9610 }); 9611 }; 9612} 9613 9614/** 9615 * Computes a hash of an 'obj'. 9616 * Hash of a: 9617 * string is string 9618 * number is number as string 9619 * object is either result of calling $$hashKey function on the object or uniquely generated id, 9620 * that is also assigned to the $$hashKey property of the object. 9621 * 9622 * @param obj 9623 * @returns {string} hash string such that the same input will have the same hash string. 9624 * The resulting string key is in 'type:hashKey' format. 9625 */ 9626function hashKey(obj, nextUidFn) { 9627 var key = obj && obj.$$hashKey; 9628 9629 if (key) { 9630 if (typeof key === 'function') { 9631 key = obj.$$hashKey(); 9632 } 9633 return key; 9634 } 9635 9636 var objType = typeof obj; 9637 if (objType === 'function' || (objType === 'object' && obj !== null)) { 9638 key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)(); 9639 } else { 9640 key = objType + ':' + obj; 9641 } 9642 9643 return key; 9644} 9645 9646/** 9647 * HashMap which can use objects as keys 9648 */ 9649function HashMap(array, isolatedUid) { 9650 if (isolatedUid) { 9651 var uid = 0; 9652 this.nextUid = function() { 9653 return ++uid; 9654 }; 9655 } 9656 forEach(array, this.put, this); 9657} 9658HashMap.prototype = { 9659 /** 9660 * Store key value pair 9661 * @param key key to store can be any type 9662 * @param value value to store can be any type 9663 */ 9664 put: function(key, value) { 9665 this[hashKey(key, this.nextUid)] = value; 9666 }, 9667 9668 /** 9669 * @param key 9670 * @returns {Object} the value for the key 9671 */ 9672 get: function(key) { 9673 return this[hashKey(key, this.nextUid)]; 9674 }, 9675 9676 /** 9677 * Remove the key/value pair 9678 * @param key 9679 */ 9680 remove: function(key) { 9681 var value = this[key = hashKey(key, this.nextUid)]; 9682 delete this[key]; 9683 return value; 9684 } 9685}; 9686 9687var $$HashMapProvider = [/** @this */function() { 9688 this.$get = [function() { 9689 return HashMap; 9690 }]; 9691}]; 9692 9693/** 9694 * @ngdoc function 9695 * @module ng 9696 * @name angular.injector 9697 * @kind function 9698 * 9699 * @description 9700 * Creates an injector object that can be used for retrieving services as well as for 9701 * dependency injection (see {@link guide/di dependency injection}). 9702 * 9703 * @param {Array.<string|Function>} modules A list of module functions or their aliases. See 9704 * {@link angular.module}. The `ng` module must be explicitly added. 9705 * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which 9706 * disallows argument name annotation inference. 9707 * @returns {injector} Injector object. See {@link auto.$injector $injector}. 9708 * 9709 * @example 9710 * Typical usage 9711 * ```js 9712 * // create an injector 9713 * var $injector = angular.injector(['ng']); 9714 * 9715 * // use the injector to kick off your application 9716 * // use the type inference to auto inject arguments, or use impl
9716icit injection 9717 * $injector.invoke(function($rootScope, $compile, $document) { 9718 * $compile($document)($rootScope); 9719 * $rootScope.$digest(); 9720 * }); 9721 * ``` 9722 * 9723 * Sometimes you want to get access to the injector of a currently running Angular app 9724 * from outside Angular. Perhaps, you want to inject and compile some markup after the 9725 * application has been bootstrapped. You can do this using the extra `injector()` added 9726 * to JQuery/jqLite elements. See {@link angular.element}. 9727 * 9728 * *This is fairly rare but could be the case if a third party library is injecting the 9729 * markup.* 9730 * 9731 * In the following example a new block of HTML containing a `ng-controller` 9732 * directive is added to the end of the document body by JQuery. We then compile and link 9733 * it into the current AngularJS scope. 9734 * 9735 * ```js 9736 * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>'); 9737 * $(document.body).append($div); 9738 * 9739 * angular.element(document).injector().invoke(function($compile) { 9740 * var scope = angular.element($div).scope(); 9741 * $compile($div)(scope); 9742 * }); 9743 * ``` 9744 */ 9745 9746 9747/** 9748 * @ngdoc module 9749 * @name auto 9750 * @installation 9751 * @description 9752 * 9753 * Implicit module which gets automatically added to each {@link auto.$injector $injector}. 9754 */ 9755 9756var ARROW_ARG = /^([^(]+?)=>/; 9757var FN_ARGS = /^[^(]*\(\s*([^)]*)\)/m; 9758var FN_ARG_SPLIT = /,/; 9759var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; 9760var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; 9761var $injectorMinErr = minErr('$injector'); 9762 9763function stringifyFn(fn) { 9764 // Support: Chrome 50-51 only 9765 // Creating a new string by adding `' '` at the end, to hack around some bug in Chrome v50/51 9766 // (See https://github.com/angular/angular.js/issues/14487.) 9767 // TODO (gkalpak): Remove workaround when Chrome v52 is released 9768 return Function.prototype.toString.call(fn) + ' '; 9769} 9770 9771function extractArgs(fn) { 9772 var fnText = stringifyFn(fn).replace(STRIP_COMMENTS, ''), 9773 args = fnText.match(ARROW_ARG) || fnText.match(FN_ARGS); 9774 return args; 9775} 9776 9777function anonFn(fn) { 9778 // For anonymous functions, showing at the very least the function signature can help in 9779 // debugging. 9780 var args = extractArgs(fn); 9781 if (args) { 9782 return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')'; 9783 } 9784 return 'fn'; 9785} 9786 9787function annotate(fn, strictDi, name) { 9788 var $inject, 9789 argDecl, 9790 last; 9791 9792 if (typeof fn === 'function') { 9793 if (!($inject = fn.$inject)) { 9794 $inject = []; 9795 if (fn.length) { 9796 if (strictDi) { 9797 if (!isString(name) || !name) { 9798 name = fn.name || anonFn(fn); 9799 } 9800 throw $injectorMinErr('strictdi', 9801 '{0} is not using explicit annotation and cannot be invoked in strict mode', name); 9802 } 9803 argDecl = extractArgs(fn); 9804 forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) { 9805 arg.replace(FN_ARG, function(all, underscore, name) { 9806 $inject.push(name); 9807 }); 9808 }); 9809 } 9810 fn.$inject = $inject; 9811 } 9812 } else if (isArray(fn)) { 9813 last = fn.length - 1; 9814 assertArgFn(fn[last], 'fn'); 9815 $inject = fn.slice(0, last); 9816 } else { 9817 assertArgFn(fn, 'fn', true); 9818 } 9819 return $inject; 9820} 9821 9822/////////////////////////////////////// 9823 9824/** 9825 * @ngdoc service 9826 * @name $injector 9827 * 9828 * @description 9829 * 9830 * `$injector` is used to retrieve object instances as defined by 9831 * {@link auto.$provide provider}, instantiate types, invoke methods, 9832 * and load modules. 9833 * 9834 * The following always holds true: 9835 * 9836 * ```js 9837 * var $injector = angular.injector(); 9838 * expect($injector.get('$injector')).toBe($injector); 9839 * expect($injector.invoke(function($injector) { 9840 * return $injector; 9841 * })).toBe($injector); 9842 * ``` 9843 * 9844 * # Injection Function Annotation 9845 * 9846 * JavaScript does not have annotations, and annotations are needed for dependency injection. The 9847 * following are all valid ways of annotating function with injection arguments and are equivalent. 9848 * 9849 * ```js 9850 * // inferred (only works if code not minified/obfuscated) 9851 * $injector.invoke(function(serviceA){}); 9852 * 9853 * // annotated 9854 * function explicit(serviceA) {}; 9855 * explicit.$inject = ['serviceA']; 9856 * $injector.invoke(explicit); 9857 * 9858 * // inline 9859 * $injector.invoke(['serviceA', function(serviceA){}]); 9860 * ``` 9861 * 9862 * ## Inference 9863 * 9864 * In JavaScript calling `toString()` on a function returns the function definition. The definition 9865 * can then be parsed and the function arguments can be extracted. This method of discovering 9866 * annotations is disallowed when the injector is in strict mode. 9867 * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the 9868 * argument names. 9869 * 9870 * ## `$inject` Annotation 9871 * By adding an `$inject` property onto a function the injection parameters can be specified. 9872 * 9873 * ## Inline 9874 * As an array of injection names, where the last item in the array is the function to call. 9875 */ 9876 9877/** 9878 * @ngdoc method 9879 * @name $injector#get 9880 * 9881 * @description 9882 * Return an instance of the service. 9883 * 9884 * @param {string} name The name of the instance to retrieve. 9885 * @param {string=} caller An optional string to provide the origin of the function call for error messages. 9886 * @return {*} The instance. 9887 */ 9888 9889/** 9890 * @ngdoc method 9891 * @name $injector#invoke 9892 * 9893 * @description 9894 * Invoke the method and supply the method arguments from the `$injector`. 9895 * 9896 * @param {Function|Array.<string|Function>} fn The injectable function to invoke. Function parameters are 9897 * injected according to the {@link guide/di $inject Annotation} rules. 9898 * @param {Object=} self The `this` for the invoked method. 9899 * @param {Object=} locals Optional object. If preset then any argument names are read from this 9900 * object first, before the `$injector` is consulted. 9901 * @returns {*} the value returned by the invoked `fn` function. 9902 */ 9903 9904/** 9905 * @ngdoc method 9906 * @name $injector#has 9907 * 9908 * @description 9909 * Allows the user to query if the particular service exists. 9910 * 9911 * @param {string} name Name of the service to query. 9912 * @returns {boolean} `true` if injector has given service. 9913 */ 9914 9915/** 9916 * @ngdoc method 9917 * @name $injector#instantiate 9918 * @description 9919 * Create a new instance of JS type. The method takes a constructor function, invokes the new 9920 * operator, and supplies all of the arguments to the constructor function as specified by the 9921 * constructor annotation. 9922 * 9923 * @param {Function} Type Annotated constructor function. 9924 * @param {Object=} locals Optional object. If preset then any argument names are read from this 9925 * object first, before the `$injector` is consulted. 9926 * @returns {Object} new instance of `Type`. 9927 */ 9928 9929/** 9930 * @ngdoc method 9931 * @name $injector#annotate 9932 * 9933 * @description 9934 * Returns an array of service names which the function is requesting for injection. This API is 9935 * used by the injector to determine which services need to be injected into the function when the 9936 * function is invoked. There are three ways in which the function can be annotated with the needed 9937 * dependencies. 9938 * 9939 * # Argument names 9940 * 9941 * The simplest form is to extract the dependencies from the arguments of the function. This is done
9942 * by converting the function into a string using `toString()` method and extracting the argument 9943 * names. 9944 * ```js 9945 * // Given 9946 * function MyController($scope, $route) { 9947 * // ... 9948 * } 9949 * 9950 * // Then 9951 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 9952 * ``` 9953 * 9954 * You can disallow this method by using strict injection mode. 9955 * 9956 * This method does not work with code minification / obfuscation. For this reason the following 9957 * annotation strategies are supported. 9958 * 9959 * # The `$inject` property 9960 * 9961 * If a function has an `$inject` property and its value is an array of strings, then the strings 9962 * represent names of services to be injected into the function. 9963 * ```js 9964 * // Given 9965 * var MyController = function(obfuscatedScope, obfuscatedRoute) { 9966 * // ... 9967 * } 9968 * // Define function dependencies 9969 * MyController['$inject'] = ['$scope', '$route']; 9970 * 9971 * // Then 9972 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 9973 * ``` 9974 * 9975 * # The array notation 9976 * 9977 * It is often desirable to inline Injected functions and that's when setting the `$inject` property 9978 * is very inconvenient. In these situations using the array notation to specify the dependencies in 9979 * a way that survives minification is a better choice: 9980 * 9981 * ```js 9982 * // We wish to write this (not minification / obfuscation safe) 9983 * injector.invoke(function($compile, $rootScope) { 9984 * // ... 9985 * }); 9986 * 9987 * // We are forced to write break inlining 9988 * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) { 9989 * // ... 9990 * }; 9991 * tmpFn.$inject = ['$compile', '$rootScope']; 9992 * injector.invoke(tmpFn); 9993 * 9994 * // To better support inline function the inline annotation is supported 9995 * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) { 9996 * // ... 9997 * }]); 9998 * 9999 * // Therefore 10000 * expect(injector.annotate( 10001 * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}]) 10002 * ).toEqual(['$compile', '$rootScope']); 10003 * ``` 10004 * 10005 * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to 10006 * be retrieved as described above. 10007 * 10008 * @param {boolean=} [strictDi=false] Disallow argument name annotation inference. 10009 * 10010 * @returns {Array.<string>} The names of the services which the function requires. 10011 */ 10012 10013 10014 10015/** 10016 * @ngdoc service 10017 * @name $provide 10018 * 10019 * @description 10020 * 10021 * The {@link auto.$provide $provide} service has a number of methods for registering components 10022 * with the {@link auto.$injector $injector}. Many of these functions are also exposed on 10023 * {@link angular.Module}. 10024 * 10025 * An Angular **service** is a singleton object created by a **service factory**. These **service 10026 * factories** are functions which, in turn, are created by a **service provider**. 10027 * The **service providers** are constructor functions. When instantiated they must contain a 10028 * property called `$get`, which holds the **service factory** function. 10029 * 10030 * When you request a service, the {@link auto.$injector $injector} is responsible for finding the 10031 * correct **service provider**, instantiating it and then calling its `$get` **service factory** 10032 * function to get the instance of the **service**. 10033 * 10034 * Often services have no configuration options and there is no need to add methods to the service 10035 * provider. The provider will be no more than a constructor function with a `$get` property. For 10036 * these cases the {@link auto.$provide $provide} service has additional helper methods to register 10037 * services without specifying a provider. 10038 * 10039 * * {@link auto.$provide#provider provider(name, provider)} - registers a **service provider** with the 10040 * {@link auto.$injector $injector} 10041 * * {@link auto.$provide#constant constant(name, obj)} - registers a value/object that can be accessed by 10042 * providers and services. 10043 * * {@link auto.$provide#value value(name, obj)} - registers a value/object that can only be accessed by 10044 * services, not providers. 10045 * * {@link auto.$provide#factory factory(name, fn)} - registers a service **factory function** 10046 * that will be wrapped in a **service provider** object, whose `$get` property will contain the 10047 * given factory function. 10048 * * {@link auto.$provide#service service(name, Fn)} - registers a **constructor function** 10049 * that will be wrapped in a **service provider** object, whose `$get` property will instantiate 10050 * a new object using the given constructor function. 10051 * * {@link auto.$provide#decorator decorator(name, decorFn)} - registers a **decorator function** that 10052 * will be able to modify or replace the implementation of another service. 10053 * 10054 * See the individual methods for more information and examples. 10055 */ 10056 10057/** 10058 * @ngdoc method 10059 * @name $provide#provider 10060 * @description 10061 * 10062 * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions 10063 * are constructor functions, whose instances are responsible for "providing" a factory for a 10064 * service. 10065 * 10066 * Service provider names start with the name of the service they provide followed by `Provider`. 10067 * For example, the {@link ng.$log $log} service has a provider called 10068 * {@link ng.$logProvider $logProvider}. 10069 * 10070 * Service provider objects can have additional methods which allow configuration of the provider 10071 * and its service. Importantly, you can configure what kind of service is created by the `$get` 10072 * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a 10073 * method {@link ng.$logProvider#debugEnabled debugEnabled} 10074 * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the 10075 * console or not. 10076 * 10077 * @param {string} name The name of the instance. NOTE: the provider will be available under `name + 10078 'Provider'` key. 10079 * @param {(Object|function())} provider If the provider is: 10080 * 10081 * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using 10082 * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created. 10083 * - `Constructor`: a new instance of the provider will be created using 10084 * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`. 10085 * 10086 * @returns {Object} registered provider instance 10087 10088 * @example 10089 * 10090 * The following example shows how to create a simple event tracking service and register it using 10091 * {@link auto.$provide#provider $provide.provider()}. 10092 * 10093 * ```js 10094 * // Define the eventTracker provider 10095 * function EventTrackerProvider() { 10096 * var trackingUrl = '/track'; 10097 * 10098 * // A provider method for configuring where the tracked events should been saved 10099 * this.setTrackingUrl = function(url) { 10100 * trackingUrl = url; 10101 * }; 10102 * 10103 * // The service factory function 10104 * this.$get = ['$http', function($http) { 10105 * var trackedEvents = {}; 10106 * return { 10107 * // Call this to track an event 10108 * event: function(event) { 10109 * var count = trackedEvents[event] || 0; 10110 * count += 1; 10111 * trackedEvents[event] = count; 10112 * return count; 10113 * }, 10114 * // Call this to save the tracked events to the trackingUrl 10115 * save: function() { 10116 * $http.post(trackingUrl, trackedEvents); 10117 * } 10118 * }; 10119 * }]; 10120 * } 10121 * 10122 * describe('eventTracker', function() { 10123 * var postSpy; 10124 * 10125 * beforeEach(module(function($provide) { 10126 * // Register the eventTracker provider 10127 * $provide.provider('eventTracker', EventTrackerProvider); 10128 * })); 10129 * 10130 * beforeEach(module(function(eventTrackerProvider) { 10131 * // Configure eventTracker provider 10132 * eventTrackerProvider.setTrackingUrl('/custom-track'); 10133 * })); 10134 * 10135 * it('tracks events', inject(function(eventTracker) { 10136 * expect(eventTracker.event('login')).toEqual(1); 10137 * expect(eventTracker.event('login')).toEqual(2); 10138 * })); 10139 * 10140 * it('saves to the tracking url', inject(function(eventTracker, $http) { 10141 * postSpy = spyOn($http, 'post'); 10142 * eventTracker.event('login'); 10143 * eventTracker.save(); 10144 * expect(postSpy).toHaveBeenCalled(); 10145 * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track'); 10146 * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track'); 10147 * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 }); 10148 * })); 10149 * }); 10150 * ``` 10151 */ 10152 10153/** 10154 * @ngdoc method 10155 * @name $provide#factory 10156 * @description 10157 * 10158 * Register a **service factory**, which will be called to return the service instance. 10159 * This is short for registering a service where its provider consists of only a `$get` property, 10160 * which is the given service factory function. 10161 * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to 10162 * configure your service in a provider. 10163 * 10164 * @param {string} name The name of the instance. 10165 * @param {Function|Array.<string|Function>} $getFn The injectable $getFn for the instance creation. 10166 * Internally this is a short hand for `$provide.provider(name, {$get: $getFn})`. 10167 * @returns {Object} registered provider instance 10168 * 10169 * @example 10170 * Here is an example of registering a service 10171 * ```js 10172 * $provide.factory('ping', ['$http', function($http) { 10173 * return function ping() { 10174 * return $http.send('/ping'); 10175 * }; 10176 * }]); 10177 * ``` 10178 * You would then inject and use this service like this: 10179 * ```js 10180 * someModule.controller('Ctrl', ['ping', function(ping) { 10181 * ping(); 10182 * }]); 10183 * ``` 10184 */ 10185 10186 10187/** 10188 * @ngdoc method 10189 * @name $provide#service 10190 * @description 10191 * 10192 * Register a **service constructor**, which will be invoked with `new` to create the service 10193 * instance. 10194 * This is short for registering a service where its provider's `$get` property is a factory 10195 * function that returns an instance instantiated by the injector from the service constructor 10196 * function. 10197 * 10198 * Internally it looks a bit like this: 10199 * 10200 * ``` 10201 * { 10202 * $get: function() { 10203 * return $injector.instantiate(constructor); 10204 * } 10205 * } 10206 * ``` 10207 * 10208 * 10209 * You should use {@link auto.$provide#service $provide.service(class)} if you define your service 10210 * as a type/class. 10211 * 10212 * @param {string} name The name of the instance. 10213 * @param {Function|Array.<string|Function>} constructor An injectable class (constructor function) 10214 * that will be instantiated. 10215 * @returns {Object} registered provider instance 10216 * 10217 * @example 10218 * Here is an example of registering a service using 10219 * {@link auto.$provide#service $provide.service(class)}. 10220 * ```js 10221 * var Ping = function($http) { 10222 * this.$http = $http; 10223 * }; 10224 * 10225 * Ping.$inject = ['$http']; 10226 * 10227 * Ping.prototype.send = function() { 10228 * return this.$http.get('/ping'); 10229 * }; 10230 * $provide.service('ping', Ping); 10231 * ``` 10232 * You would then inject and use this service like this: 10233 * ```js 10234 * someModule.controller('Ctrl', ['ping', function(ping) { 10235 * ping.send(); 10236 * }]); 10237 * ``` 10238 */ 10239 10240 10241/** 10242 * @ngdoc method 10243 * @name $provide#value 10244 * @description 10245 * 10246 * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a 10247 * number, an array, an object or a function. This is short for registering a service where its 10248 * provider's `$get` property is a factory function that takes no arguments and returns the **value 10249 * service**. That also means it is not possible to inject other services into a value service. 10250 * 10251 * Value services are similar to constant services, except that they cannot be injected into a 10252 * module configuration function (see {@link angular.Module#config}) but they can be overridden by 10253 * an Angular {@link auto.$provide#decorator decorator}. 10254 * 10255 * @param {string} name The name of the instance. 10256 * @param {*} value The value. 10257 * @returns {Object} registered provider instance 10258 * 10259 * @example 10260 * Here are some examples of creating value services. 10261 * ```js 10262 * $provide.value('ADMIN_USER', 'admin'); 10263 *
10264 * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 }); 10265 * 10266 * $provide.value('halfOf', function(value) { 10267 * return value / 2; 10268 * }); 10269 * ``` 10270 */ 10271 10272 10273/** 10274 * @ngdoc method 10275 * @name $provide#constant 10276 * @description 10277 * 10278 * Register a **constant service** with the {@link auto.$injector $injector}, such as a string, 10279 * a number, an array, an object or a function. Like the {@link auto.$provide#value value}, it is not 10280 * possible to inject other services into a constant. 10281 * 10282 * But unlike {@link auto.$provide#value value}, a constant can be 10283 * injected into a module configuration function (see {@link angular.Module#config}) and it cannot 10284 * be overridden by an Angular {@link auto.$provide#decorator decorator}. 10285 * 10286 * @param {string} name The name of the constant. 10287 * @param {*} value The constant value. 10288 * @returns {Object} registered instance 10289 * 10290 * @example 10291 * Here a some examples of creating constants: 10292 * ```js 10293 * $provide.constant('SHARD_HEIGHT', 306); 10294 * 10295 * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']); 10296 * 10297 * $provide.constant('double', function(value) { 10298 * return value * 2; 10299 * }); 10300 * ``` 10301 */ 10302 10303 10304/** 10305 * @ngdoc method 10306 * @name $provide#decorator 10307 * @description 10308 * 10309 * Register a **decorator function** with the {@link auto.$injector $injector}. A decorator function 10310 * intercepts the creation of a service, allowing it to override or modify the behavior of the 10311 * service. The return value of the decorator function may be the original service, or a new service 10312 * that replaces (or wraps and delegates to) the original service. 10313 * 10314 * You can find out more about using decorators in the {@link guide/decorators} guide. 10315 * 10316 * @param {string} name The name of the service to decorate. 10317 * @param {Function|Array.<string|Function>} decorator This function will be invoked when the service needs to be 10318 * provided and should return the decorated service instance. The function is called using 10319 * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable. 10320 * Local injection arguments: 10321 * 10322 * * `$delegate` - The original service instance, which can be replaced, monkey patched, configured, 10323 * decorated or delegated to. 10324 * 10325 * @example 10326 * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting 10327 * calls to {@link ng.$log#error $log.warn()}. 10328 * ```js 10329 * $provide.decorator('$log', ['$delegate', function($delegate) { 10330 * $delegate.warn = $delegate.error; 10331 * return $delegate; 10332 * }]); 10333 * ``` 10334 */ 10335 10336 10337function createInjector(modulesToLoad, strictDi) { 10338 strictDi = (strictDi === true); 10339 var INSTANTIATING = {}, 10340 providerSuffix = 'Provider', 10341 path = [], 10342 loadedModules = new HashMap([], true), 10343 providerCache = { 10344 $provide: { 10345 provider: supportObject(provider), 10346 factory: supportObject(factory), 10347 service: supportObject(service), 10348 value: supportObject(value), 10349 constant: supportObject(constant), 10350 decorator: decorator 10351 } 10352 }, 10353 providerInjector = (providerCache.$injector = 10354 createInternalInjector(providerCache, function(serviceName, caller) { 10355 if (angular.isString(caller)) { 10356 path.push(caller); 10357 } 10358 throw $injectorMinErr('unpr', 'Unknown provider: {0}', path.join(' <- ')); 10359 })), 10360 instanceCache = {}, 10361 protoInstanceInjector = 10362 createInternalInjector(instanceCache, function(serviceName, caller) { 10363 var provider = providerInjector.get(serviceName + providerSuffix, caller); 10364 return instanceInjector.invoke( 10365 provider.$get, provider, undefined, serviceName); 10366 }), 10367 instanceInjector = protoInstanceInjector; 10368 10369 providerCache['$injector' + providerSuffix] = { $get: valueFn(protoInstanceInjector) }; 10370 var runBlocks = loadModules(modulesToLoad); 10371 instanceInjector = protoInstanceInjector.get('$injector'); 10372 instanceInjector.strictDi = strictDi; 10373 forEach(runBlocks, function(fn) { if (fn) instanceInjector.invoke(fn); }); 10374 10375 return instanceInjector; 10376 10377 //////////////////////////////////// 10378 // $provider 10379 //////////////////////////////////// 10380 10381 function supportObject(delegate) { 10382 return function(key, value) { 10383 if (isObject(key)) {
vendor: 5,751 bytes, lines 10384-10549
10384 forEach(key, reverseParams(delegate)); 10385 } else { 10386 return delegate(key, value); 10387 } 10388 }; 10389 } 10390 10391 function provider(name, provider_) { 10392 assertNotHasOwnProperty(name, 'service'); 10393 if (isFunction(provider_) || isArray(provider_)) { 10394 provider_ = providerInjector.instantiate(provider_); 10395 } 10396 if (!provider_.$get) { 10397 throw $injectorMinErr('pget', 'Provider \'{0}\' must define $get factory method.', name); 10398 } 10399 return (providerCache[name + providerSuffix] = provider_); 10400 } 10401 10402 function enforceReturnValue(name, factory) { 10403 return /** @this */ function enforcedReturnValue() { 10404 var result = instanceInjector.invoke(factory, this); 10405 if (isUndefined(result)) { 10406 throw $injectorMinErr('undef', 'Provider \'{0}\' must return a value from $get factory method.', name); 10407 } 10408 return result; 10409 }; 10410 } 10411 10412 function factory(name, factoryFn, enforce) { 10413 return provider(name, { 10414 $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn 10415 }); 10416 } 10417 10418 function service(name, constructor) { 10419 return factory(name, ['$injector', function($injector) { 10420 return $injector.instantiate(constructor); 10421 }]); 10422 } 10423 10424 function value(name, val) { return factory(name, valueFn(val), false); } 10425 10426 function constant(name, value) { 10427 assertNotHasOwnProperty(name, 'constant'); 10428 providerCache[name] = value; 10429 instanceCache[name] = value; 10430 } 10431 10432 function decorator(serviceName, decorFn) { 10433 var origProvider = providerInjector.get(serviceName + providerSuffix), 10434 orig$get = origProvider.$get; 10435 10436 origProvider.$get = function() { 10437 var origInstance = instanceInjector.invoke(orig$get, origProvider); 10438 return instanceInjector.invoke(decorFn, null, {$delegate: origInstance}); 10439 }; 10440 } 10441 10442 //////////////////////////////////// 10443 // Module Loading 10444 //////////////////////////////////// 10445 function loadModules(modulesToLoad) { 10446 assertArg(isUndefined(modulesToLoad) || isArray(modulesToLoad), 'modulesToLoad', 'not an array'); 10447 var runBlocks = [], moduleFn; 10448 forEach(modulesToLoad, function(module) { 10449 if (loadedModules.get(module)) return; 10450 loadedModules.put(module, true); 10451 10452 function runInvokeQueue(queue) { 10453 var i, ii; 10454 for (i = 0, ii = queue.length; i < ii; i++) { 10455 var invokeArgs = queue[i], 10456 provider = providerInjector.get(invokeArgs[0]); 10457 10458 provider[invokeArgs[1]].apply(provider, invokeArgs[2]); 10459 } 10460 } 10461 10462 try { 10463 if (isString(module)) { 10464 moduleFn = angularModule(module); 10465 runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); 10466 runInvokeQueue(moduleFn._invokeQueue); 10467 runInvokeQueue(moduleFn._configBlocks); 10468 } else if (isFunction(module)) { 10469 runBlocks.push(providerInjector.invoke(module)); 10470 } else if (isArray(module)) { 10471 runBlocks.push(providerInjector.invoke(module)); 10472 } else { 10473 assertArgFn(module, 'module'); 10474 } 10475 } catch (e) { 10476 if (isArray(module)) { 10477 module = module[module.length - 1]; 10478 } 10479 if (e.message && e.stack && e.stack.indexOf(e.message) === -1) { 10480 // Safari & FF's stack traces don't contain error.message content 10481 // unlike those of Chrome and IE 10482 // So if stack doesn't contain message, we create a new string that contains both. 10483 // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here. 10484 // eslint-disable-next-line no-ex-assign 10485 e = e.message + '\n' + e.stack; 10486 } 10487 throw $injectorMinErr('modulerr', 'Failed to instantiate module {0} due to:\n{1}', 10488 module, e.stack || e.message || e); 10489 } 10490 }); 10491 return runBlocks; 10492 } 10493 10494 //////////////////////////////////// 10495 // internal Injector 10496 //////////////////////////////////// 10497 10498 function createInternalInjector(cache, factory) { 10499 10500 function getService(serviceName, caller) { 10501 if (cache.hasOwnProperty(serviceName)) { 10502 if (cache[serviceName] === INSTANTIATING) { 10503 throw $injectorMinErr('cdep', 'Circular dependency found: {0}', 10504 serviceName + ' <- ' + path.join(' <- ')); 10505 } 10506 return cache[serviceName]; 10507 } else { 10508 try { 10509 path.unshift(serviceName); 10510 cache[serviceName] = INSTANTIATING; 10511 cache[serviceName] = factory(serviceName, caller); 10512 return cache[serviceName]; 10513 } catch (err) { 10514 if (cache[serviceName] === INSTANTIATING) { 10515 delete cache[serviceName]; 10516 } 10517 throw err; 10518 } finally { 10519 path.shift(); 10520 } 10521 } 10522 } 10523 10524 10525 function injectionArgs(fn, locals, serviceName) { 10526 var args = [], 10527 $inject = createInjector.$$annotate(fn, strictDi, serviceName); 10528 10529 for (var i = 0, length = $inject.length; i < length; i++) { 10530 var key = $inject[i]; 10531 if (typeof key !== 'string') { 10532 throw $injectorMinErr('itkn', 10533 'Incorrect injection token! Expected service name as string, got {0}', key); 10534 } 10535 args.push(locals && locals.hasOwnProperty(key) ? locals[key] : 10536 getService(key, serviceName)); 10537 } 10538 return args; 10539 } 10540 10541 function isClass(func) { 10542 // IE 9-11 do not support classes and IE9 leaks with the code below. 10543 if (msie <= 11) { 10544 return false; 10545 } 10546 // Support: Edge 12-13 only 10547 // See: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/6156135/ 10548 return typeof func === 'function' 10549 && /^(?:class\b|constructor\()/.test(stringifyFn(func))
vendor: 13,053 bytes, lines 10549-10932
10549; 10550 } 10551 10552 function invoke(fn, self, locals, serviceName) { 10553 if (typeof locals === 'string') { 10554 serviceName = locals; 10555 locals = null; 10556 } 10557 10558 var args = injectionArgs(fn, locals, serviceName); 10559 if (isArray(fn)) { 10560 fn = fn[fn.length - 1]; 10561 } 10562 10563 if (!isClass(fn)) { 10564 // http://jsperf.com/angularjs-invoke-apply-vs-switch 10565 // #5388 10566 return fn.apply(self, args); 10567 } else { 10568 args.unshift(null); 10569 return new (Function.prototype.bind.apply(fn, args))(); 10570 } 10571 } 10572 10573 10574 function instantiate(Type, locals, serviceName) { 10575 // Check if Type is annotated and use just the given function at n-1 as parameter 10576 // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]); 10577 var ctor = (isArray(Type) ? Type[Type.length - 1] : Type); 10578 var args = injectionArgs(Type, locals, serviceName); 10579 // Empty object at position 0 is ignored for invocation with `new`, but required. 10580 args.unshift(null); 10581 return new (Function.prototype.bind.apply(ctor, args))(); 10582 } 10583 10584 10585 return { 10586 invoke: invoke, 10587 instantiate: instantiate, 10588 get: getService, 10589 annotate: createInjector.$$annotate, 10590 has: function(name) { 10591 return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); 10592 } 10593 }; 10594 } 10595} 10596 10597createInjector.$$annotate = annotate; 10598 10599/** 10600 * @ngdoc provider 10601 * @name $anchorScrollProvider 10602 * @this 10603 * 10604 * @description 10605 * Use `$anchorScrollProvider` to disable automatic scrolling whenever 10606 * {@link ng.$location#hash $location.hash()} changes. 10607 */ 10608function $AnchorScrollProvider() { 10609 10610 var autoScrollingEnabled = true; 10611 10612 /** 10613 * @ngdoc method 10614 * @name $anchorScrollProvider#disableAutoScrolling 10615 * 10616 * @description 10617 * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to 10618 * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br /> 10619 * Use this method to disable automatic scrolling. 10620 * 10621 * If automatic scrolling is disabled, one must explicitly call 10622 * {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the 10623 * current hash. 10624 */ 10625 this.disableAutoScrolling = function() { 10626 autoScrollingEnabled = false; 10627 }; 10628 10629 /** 10630 * @ngdoc service 10631 * @name $anchorScroll 10632 * @kind function 10633 * @requires $window 10634 * @requires $location 10635 * @requires $rootScope 10636 * 10637 * @description 10638 * When called, it scrolls to the element related to the specified `hash` or (if omitted) to the 10639 * current value of {@link ng.$location#hash $location.hash()}, according to the rules specified 10640 * in the 10641 * [HTML5 spec](http://www.w3.org/html/wg/drafts/html/master/browsers.html#an-indicated-part-of-the-document). 10642 * 10643 * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to 10644 * match any anchor whenever it changes. This can be disabled by calling 10645 * {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}. 10646 * 10647 * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a 10648 * vertical scroll-offset (either fixed or dynamic). 10649 * 10650 * @param {string=} hash The hash specifying the element to scroll to. If omitted, the value of 10651 * {@link ng.$location#hash $location.hash()} will be used. 10652 * 10653 * @property {(number|function|jqLite)} yOffset 10654 * If set, specifies a vertical scroll-offset. This is often useful when there are fixed 10655 * positioned elements at the top of the page, such as navbars, headers etc. 10656 * 10657 * `yOffset` can be specified in various ways: 10658 * - **number**: A fixed number of pixels to be used as offset.<br /><br /> 10659 * - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return 10660 * a number representing the offset (in pixels).<br /><br /> 10661 * - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The distance from 10662 * the top of the page to the element's bottom will be used as offset.<br /> 10663 * **Note**: The element will be taken into account only as long as its `position` is set to 10664 * `fixed`. This option is useful, when dealing with responsive navbars/headers that adjust 10665 * their height and/or positioning according to the viewport's size. 10666 * 10667 * <br /> 10668 * <div class="alert alert-warning"> 10669 * In order for `yOffset` to work properly, scrolling should take place on the document's root and 10670 * not some child element. 10671 * </div> 10672 * 10673 * @example 10674 <example module="anchorScrollExample" name="anchor-scroll"> 10675 <file name="index.html"> 10676 <div id="scrollArea" ng-controller="ScrollController"> 10677 <a ng-click="gotoBottom()">Go to bottom</a> 10678 <a id="bottom"></a> You're at the bottom! 10679 </div> 10680 </file> 10681 <file name="script.js"> 10682 angular.module('anchorScrollExample', []) 10683 .controller('ScrollController', ['$scope', '$location', '$anchorScroll', 10684 function($scope, $location, $anchorScroll) { 10685 $scope.gotoBottom = function() { 10686 // set the location.hash to the id of 10687 // the element you wish to scroll to. 10688 $location.hash('bottom'); 10689 10690 // call $anchorScroll() 10691 $anchorScroll(); 10692 }; 10693 }]); 10694 </file> 10695 <file name="style.css"> 10696 #scrollArea { 10697 height: 280px; 10698 overflow: auto; 10699 } 10700 10701 #bottom { 10702 display: block; 10703 margin-top: 2000px; 10704 } 10705 </file> 10706 </example> 10707 * 10708 * <hr /> 10709 * The example below illustrates the use of a vertical scroll-offset (specified as a fixed value). 10710 * See {@link ng.$anchorScroll#yOffset $anchorScroll.yOffset} for more details. 10711 * 10712 * @example 10713 <example module="anchorScrollOffsetExample" name="anchor-scroll-offset"> 10714 <file name="index.html"> 10715 <div class="fixed-header" ng-controller="headerCtrl"> 10716 <a href="" ng-click="gotoAnchor(x)" ng-repeat="x in [1,2,3,4,5]"> 10717 Go to anchor {{x}} 10718 </a> 10719 </div> 10720 <div id="anchor{{x}}" class="anchor" ng-repeat="x in [1,2,3,4,5]"> 10721 Anchor {{x}} of 5 10722 </div> 10723 </file> 10724 <file name="script.js"> 10725 angular.module('anchorScrollOffsetExample', []) 10726 .run(['$anchorScroll', function($anchorScroll) { 10727 $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels 10728 }]) 10729 .controller('headerCtrl', ['$anchorScroll', '$location', '$scope', 10730 function($anchorScroll, $location, $scope) { 10731 $scope.gotoAnchor = function(x) { 10732 var newHash = 'anchor' + x; 10733 if ($location.hash() !== newHash) { 10734 // set the $location.hash to `newHash` and 10735 // $anchorScroll will automatically scroll to it 10736 $location.hash('anchor' + x); 10737 } else { 10738 // call $anchorScroll() explicitly, 10739 // since $location.hash hasn't changed 10740 $anchorScroll(); 10741 } 10742 }; 10743 } 10744 ]); 10745 </file> 10746 <file name="style.css"> 10747 body { 10748 padding-top: 50px; 10749 } 10750 10751 .anchor { 10752 border: 2px dashed DarkOrchid; 10753 padding: 10px 10px 200px 10px; 10754 } 10755 10756 .fixed-header { 10757 background-color: rgba(0, 0, 0, 0.2); 10758 height: 50px; 10759 position: fixed; 10760 top: 0; left: 0; right: 0; 10761 } 10762 10763 .fixed-header > a { 10764 display: inline-block; 10765 margin: 5px 15px; 10766 } 10767 </file> 10768 </example> 10769 */ 10770 this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) { 10771 var document = $window.document; 10772 10773 // Helper function to get first anchor from a NodeList 10774 // (using `Array#some()` instead of `angular#forEach()` since it's more performant 10775 // and working in all supported browsers.) 10776 function getFirstAnchor(list) { 10777 var result = null; 10778 Array.prototype.some.call(list, function(element) { 10779 if (nodeName_(element) === 'a') { 10780 result = element; 10781 return true; 10782 } 10783 }); 10784 return result; 10785 } 10786 10787 function getYOffset() { 10788 10789 var offset = scroll.yOffset; 10790 10791 if (isFunction(offset)) { 10792 offset = offset(); 10793 } else if (isElement(offset)) { 10794 var elem = offset[0]; 10795 var style = $window.getComputedStyle(elem); 10796 if (style.position !== 'fixed') { 10797 offset = 0; 10798 } else { 10799 offset = elem.getBoundingClientRect().bottom; 10800 } 10801 } else if (!isNumber(offset)) { 10802 offset = 0; 10803 } 10804 10805 return offset; 10806 } 10807 10808 function scrollTo(elem) { 10809 if (elem) { 10810 elem.scrollIntoView(); 10811 10812 var offset = getYOffset(); 10813 10814 if (offset) { 10815 // `offset` is the number of pixels we should scroll UP in order to align `elem` properly. 10816 // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the 10817 // top of the viewport. 10818 // 10819 // IF the number of pixels from the top of `elem` to the end of the page's content is less 10820 // than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some 10821 // way down the page. 10822 // 10823 // This is often the case for elements near the bottom of the page. 10824 // 10825 // In such cases we do not need to scroll the whole `offset` up, just the difference between 10826 // the top of the element and the offset, which is enough to align the top of `elem` at the 10827 // desired position. 10828 var elemTop = elem.getBoundingClientRect().top; 10829 $window.scrollBy(0, elemTop - offset); 10830 } 10831 } else { 10832 $window.scrollTo(0, 0); 10833 } 10834 } 10835 10836 function scroll(hash) { 10837 // Allow numeric hashes 10838 hash = isString(hash) ? hash : isNumber(hash) ? hash.toString() : $location.hash(); 10839 var elm; 10840 10841 // empty hash, scroll to the top of the page 10842 if (!hash) scrollTo(null); 10843 10844 // element with given id 10845 else if ((elm = document.getElementById(hash))) scrollTo(elm); 10846 10847 // first anchor with given name :-D 10848 else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm); 10849 10850 // no element and hash === 'top', scroll to the top of the page 10851 else if (hash === 'top') scrollTo(null); 10852 } 10853 10854 // does not scroll when user clicks on anchor link that is currently on 10855 // (no url change, no $location.hash() change), browser native does scroll 10856 if (autoScrollingEnabled) { 10857 $rootScope.$watch(function autoScrollWatch() {return $location.hash();}, 10858 function autoScrollWatchAction(newVal, oldVal) { 10859 // skip the initial scroll if $location.hash is empty 10860 if (newVal === oldVal && newVal === '') return; 10861 10862 jqLiteDocumentLoaded(function() { 10863 $rootScope.$evalAsync(scroll); 10864 }); 10865 }); 10866 } 10867 10868 return scroll; 10869 }]; 10870} 10871 10872var $animateMinErr = minErr('$animate'); 10873var ELEMENT_NODE = 1; 10874var NG_ANIMATE_CLASSNAME = 'ng-animate'; 10875 10876function mergeClasses(a,b) { 10877 if (!a && !b) return ''; 10878 if (!a) return b; 10879 if (!b) return a; 10880 if (isArray(a)) a = a.join(' '); 10881 if (isArray(b)) b = b.join(' '); 10882 return a + ' ' + b; 10883} 10884 10885function extractElementNode(element) { 10886 for (var i = 0; i < element.length; i++) { 10887 var elm = element[i]; 10888 if (elm.nodeType === ELEMENT_NODE) { 10889 return elm; 10890 } 10891 } 10892} 10893 10894function splitClasses(classes) { 10895 if (isString(classes)) { 10896 classes = classes.split(' '); 10897 } 10898 10899 // Use createMap() to prevent class assumptions involving property names in 10900 // Object.prototype 10901 var obj = createMap(); 10902 forEach(classes, function(klass) { 10903 // sometimes the split leaves empty string values 10904 // incase extra spaces were applied to the options 10905 if (klass.length) { 10906 obj[klass] = true; 10907 } 10908 }); 10909 return obj; 10910} 10911 10912// if any other type of options value besides an Object value is 10913// passed into the $animate.method() animation then this helper code 10914// will be run which will ignore it. While this patch is not the 10915// greatest solution to this, a lot of existing plugins depend on 10916// $animate to either call the callback (< 1.2) or return a promise 10917// that can be changed. This helper function ensures that the options 10918// are wiped clean incase a callback function is provided. 10919function prepareAnimateOptions(options) { 10920 return isObject(options) 10921 ? options 10922 : {}; 10923} 10924 10925var $$CoreAnimateJsProvider = /** @this */ function() { 10926 this.$get = noop; 10927}; 10928 10929// this is prefixed with Core since it conflicts with 10930// the animateQueueProvider defined in ngAnimate/animateQueue.js 10931var $$CoreAnimateQueueProvider = /** @this */ function() { 10932 var postDigestQueue = new
vendor: 4,027 bytes, lines 10932-11067
10932HashMap(); 10933 var postDigestElements = []; 10934 10935 this.$get = ['$$AnimateRunner', '$rootScope', 10936 function($$AnimateRunner, $rootScope) { 10937 return { 10938 enabled: noop, 10939 on: noop, 10940 off: noop, 10941 pin: noop, 10942 10943 push: function(element, event, options, domOperation) { 10944 if (domOperation) { 10945 domOperation(); 10946 } 10947 10948 options = options || {}; 10949 if (options.from) { 10950 element.css(options.from); 10951 } 10952 if (options.to) { 10953 element.css(options.to); 10954 } 10955 10956 if (options.addClass || options.removeClass) { 10957 addRemoveClassesPostDigest(element, options.addClass, options.removeClass); 10958 } 10959 10960 var runner = new $$AnimateRunner(); 10961 10962 // since there are no animations to run the runner needs to be 10963 // notified that the animation call is complete. 10964 runner.complete(); 10965 return runner; 10966 } 10967 }; 10968 10969 10970 function updateData(data, classes, value) { 10971 var changed = false; 10972 if (classes) { 10973 classes = isString(classes) ? classes.split(' ') : 10974 isArray(classes) ? classes : []; 10975 forEach(classes, function(className) { 10976 if (className) { 10977 changed = true; 10978 data[className] = value; 10979 } 10980 }); 10981 } 10982 return changed; 10983 } 10984 10985 function handleCSSClassChanges() { 10986 forEach(postDigestElements, function(element) { 10987 var data = postDigestQueue.get(element); 10988 if (data) { 10989 var existing = splitClasses(element.attr('class')); 10990 var toAdd = ''; 10991 var toRemove = ''; 10992 forEach(data, function(status, className) { 10993 var hasClass = !!existing[className]; 10994 if (status !== hasClass) { 10995 if (status) { 10996 toAdd += (toAdd.length ? ' ' : '') + className; 10997 } else { 10998 toRemove += (toRemove.length ? ' ' : '') + className; 10999 } 11000 } 11001 }); 11002 11003 forEach(element, function(elm) { 11004 if (toAdd) { 11005 jqLiteAddClass(elm, toAdd); 11006 } 11007 if (toRemove) { 11008 jqLiteRemoveClass(elm, toRemove); 11009 } 11010 }); 11011 postDigestQueue.remove(element); 11012 } 11013 }); 11014 postDigestElements.length = 0; 11015 } 11016 11017 11018 function addRemoveClassesPostDigest(element, add, remove) { 11019 var data = postDigestQueue.get(element) || {}; 11020 11021 var classesAdded = updateData(data, add, true); 11022 var classesRemoved = updateData(data, remove, false); 11023 11024 if (classesAdded || classesRemoved) { 11025 11026 postDigestQueue.put(element, data); 11027 postDigestElements.push(element); 11028 11029 if (postDigestElements.length === 1) { 11030 $rootScope.$$postDigest(handleCSSClassChanges); 11031 } 11032 } 11033 } 11034 }]; 11035}; 11036 11037/** 11038 * @ngdoc provider 11039 * @name $animateProvider 11040 * 11041 * @description 11042 * Default implementation of $animate that doesn't perform any animations, instead just 11043 * synchronously performs DOM updates and resolves the returned runner promise. 11044 * 11045 * In order to enable animations the `ngAnimate` module has to be loaded. 11046 * 11047 * To see the functional implementation check out `src/ngAnimate/animate.js`. 11048 */ 11049var $AnimateProvider = ['$provide', /** @this */ function($provide) { 11050 var provider = this; 11051 11052 this.$$registeredAnimations = Object.create(null); 11053 11054 /** 11055 * @ngdoc method 11056 * @name $animateProvider#register 11057 * 11058 * @description 11059 * Registers a new injectable animation factory function. The factory function produces the 11060 * animation object which contains callback functions for each event that is expected to be 11061 * animated. 11062 * 11063 * * `eventFn`: `function(element, ... , doneFunction, options)` 11064 * The element to animate, the `doneFunction` and the options fed into the animation. Depending 11065 * on the type of animation additional arguments will be injected into the animation function. The 11066 * list below explains the function signatures for the different animation methods: 11067 *
11068 * - setClass: function(element, addedClasses, removedClasses, doneFunction, options) 11069 * - addClass: function(element, addedClasses, doneFunction, options) 11070 * - removeClass: function(element, removedClasses, doneFunction, options) 11071 * - enter, leave, move: function(element, doneFunction, options) 11072 * - animate: function(element, fromStyles, toStyles, doneFunction, options) 11073 * 11074 * Make sure to trigger the `doneFunction` once the animation is fully complete. 11075 * 11076 * ```js 11077 * return { 11078 * //enter, leave, move signature 11079 * eventFn : function(element, done, options) { 11080 * //code to run the animation 11081 * //once complete, then run done() 11082 * return function endFunction(wasCancelled) { 11083 * //code to cancel the animation 11084 * } 11085 * } 11086 * } 11087 * ``` 11088 * 11089 * @param {string} name The name of the animation (this is what the class-based CSS value will be compared to). 11090 * @param {Function} factory The factory function that will be executed to return the animation 11091 * object. 11092 */ 11093 this.register = function(name, factory) { 11094 if (name && name.charAt(0) !== '.') { 11095 throw $animateMinErr('notcsel', 'Expecting class selector starting with \'.\' got \'{0}\'.', name); 11096 } 11097 11098 var key = name + '-animation'; 11099 provider.$$registeredAnimations[name.substr(1)] = key; 11100 $provide.factory(key, factory); 11101 }; 11102 11103 /** 11104 * @ngdoc method 11105 * @name $animateProvider#classNameFilter 11106 * 11107 * @description 11108 * Sets and/or returns the CSS class regular expression that is checked when performing 11109 * an animation. Upon bootstrap the classNameFilter value is not set at all and will 11110 * therefore enable $animate to attempt to perform an animation on any element that is triggered. 11111 * When setting the `classNameFilter` value, animations will only be performed on elements 11112 * that successfully match the filter expression. This in turn can boost performance 11113 * for low-powered devices as well as applications containing a lot of structural operations. 11114 * @param {RegExp=} expression The className expression which will be checked against all animations 11115 * @return {RegExp} The current CSS className expression value. If null then there is no expression value 11116 */ 11117 this.classNameFilter = function(expression) { 11118 if (arguments.length === 1) { 11119 this.$$classNameFilter = (expression instanceof RegExp) ? expression : null; 11120 if (this.$$classNameFilter) { 11121 var reservedRegex = new RegExp('(\\s+|\\/)' + NG_ANIMATE_CLASSNAME + '(\\s+|\\/)'); 11122 if (reservedRegex.test(this.$$classNameFilter.toString())) { 11123 throw $animateMinErr('nongcls','$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME); 11124 11125 } 11126 } 11127 } 11128 return this.$$classNameFilter; 11129 }; 11130 11131 this.$get = ['$$animateQueue', function($$animateQueue) { 11132 function domInsert(element, parentElement, afterElement) { 11133 // if for some reason the previous element was removed 11134 // from the dom sometime before this code runs then let's 11135 // just stick to using the parent element as the anchor 11136 if (afterElement) { 11137 var afterNode = extractElementNode(afterElement); 11138 if (afterNode && !afterNode.parentNode && !afterNode.previousElementSibling) { 11139 afterElement = null; 11140 } 11141 } 11142 if (afterElement) { 11143 afterElement.after(element); 11144 } else { 11145 parentElement.prepend(element); 11146 } 11147 } 11148 11149 /** 11150 * @ngdoc service 11151 * @name $animate 11152 * @description The $animate service exposes a series of DOM utility methods that provide support 11153 * for animation hooks. The default behavior is the application of DOM operations, however, 11154 * when an animation is detected (and animations are enabled), $animate will do the heavy lifting 11155 * to ensure that animation runs with the triggered DOM operation. 11156 * 11157 * By default $animate doesn't trigger any animations. This is because the `ngAnimate` module isn't 11158 * included and only when it is active then the animation hooks that `$animate` triggers will be 11159 * functional. Once active then all structural `ng-` directives will trigger animations as they perform 11160 * their DOM-related operations (enter, leave and move). Other directives such as `ngClass`, 11161 * `ngShow`, `ngHide` and `ngMessages` also provide support for animations. 11162 * 11163 * It is recommended that the`$animate` service is always used when executing DOM-related procedures within directives. 11164 * 11165 * To learn more about enabling animation support, click here to visit the 11166 * {@link ngAnimate ngAnimate module page}. 11167 */ 11168 return { 11169 // we don't call it directly since non-existant arguments may 11170 // be interpreted as null within the sub enabled function 11171 11172 /** 11173 * 11174 * @ngdoc method 11175 * @name $animate#on 11176 * @kind function
11177 * @description Sets up an event listener to fire whenever the animation event (enter, leave, move, etc...) 11178 * has fired on the given element or among any of its children. Once the listener is fired, the provided callback 11179 * is fired with the following params: 11180 * 11181 * ```js 11182 * $animate.on('enter', container, 11183 * function callback(element, phase) { 11184 * // cool we detected an enter animation within the container 11185 * } 11186 * ); 11187 * ``` 11188 * 11189 * @param {string} event the animation event that will be captured (e.g. enter, leave, move, addClass, removeClass, etc...) 11190 * @param {DOMElement} container the container element that will capture each of the animation events that are fired on itself 11191 * as well as among its children 11192 * @param {Function} callback the callback function that will be fired when the listener is triggered 11193 * 11194 * The arguments present in the callback function are: 11195 * * `element` - The captured DOM element that the animation was fired on. 11196 * * `phase` - The phase of the animation. The two possible phases are **start** (when the animation starts) and **close** (when it ends). 11197 */ 11198 on: $$animateQueue.on, 11199 11200 /** 11201 * 11202 * @ngdoc method 11203 * @name $animate#off 11204 * @kind function 11205 * @description Deregisters an event listener based on the event which has been associated with the provided element. This method 11206 * can be used in three different ways depending on the arguments: 11207 * 11208 * ```js 11209 * // remove all the animation event listeners listening for `enter` 11210 * $animate.off('enter'); 11211 * 11212 * // remove listeners for all animation events from the container element 11213 * $animate.off(container); 11214 * 11215 * // remove all the animation event listeners listening for `enter` on the given element and its children 11216 * $animate.off('enter', container); 11217 * 11218 * // remove the event listener function provided by `callback` that is set 11219 * // to listen for `enter` on the given `container` as well as its children 11220 * $animate.off('enter', container, callback); 11221 * ``` 11222 * 11223 * @param {string|DOMElement} event|container the animation event (e.g. enter, leave, move, 11224 * addClass, removeClass, etc...), or the container element. If it is the element, all other 11225 * arguments are ignored. 11226 * @param {DOMElement=} container the container element the event listener was placed on 11227 * @param {Function=} callback the callback function that was registered as the listener 11228 */ 11229 off: $$animateQueue.off, 11230 11231 /** 11232 * @ngdoc method 11233 * @name $animate#pin 11234 * @kind function 11235 * @description Associates the provided element with a host parent element to allow the element to be animated even if it exists 11236 * outside of the DOM structure of the Angular application. By doing so, any animation triggered via `$animate` can be issued on the 11237 * element despite being outside the realm of the application or within another application. Say for example if the application 11238 * was bootstrapped on an element that is somewhere inside of the `<body>` tag, but we wanted to allow for an element to be situated 11239 * as a direct child of `document.body`, then this can be achieved by pinning the element via `$animate.pin(element)`. Keep in mind 11240 * that calling `$animate.pin(element, parentElement)` will not actually insert into the DOM anywhere; it will just create the association. 11241 * 11242 * Note that this feature is only active when the `ngAnimate` module is used. 11243 * 11244 * @param {DOMElement} element the external element that will be pinned 11245 * @param {DOMElement} parentElement the host parent element that will be associated with the external element 11246 */ 11247 pin: $$animateQueue.pin, 11248 11249 /** 11250 * 11251 * @ngdoc method 11252 * @name $animate#enabled 11253 * @kind function 11254 * @description Used to get and set whether animations are enabled or not on the entire application or on an element and its children. This 11255 * function can be called in four ways: 11256 * 11257 * ```js 11258 * // returns true or false 11259 * $animate.enabled(); 11260 * 11261 * // changes the enabled state for all animations 11262 * $animate.enabled(false); 11263 * $animate.enabled(true); 11264 * 11265 * // returns true or false if animations are enabled for an element 11266 * $animate.enabled(element); 11267 * 11268 * // changes the enabled state for an element and its children 11269 * $animate.enabled(element, true); 11270 * $animate.enabled(element, false); 11271 * ``` 11272 * 11273 * @param {DOMElement=} element the element that will be considered for checking/setting the enabled state 11274 * @param {boolean=} enabled whether or not the animations will be enabled for the element 11275 * 11276 * @return {boolean} whether or not animations are enabled 11277 */ 11278 enabled: $$animateQueue.enabled, 11279 11280 /** 11281 * @ngdoc method 11282 * @name $animate#cancel 11283 * @kind function
11284 * @description Cancels the provided animation. 11285 * 11286 * @param {Promise} animationPromise The animation promise that is returned when an animation is started. 11287 */ 11288 cancel: function(runner) { 11289 if (runner.end) { 11290 runner.end(); 11291 } 11292 }, 11293 11294 /** 11295 * 11296 * @ngdoc method 11297 * @name $animate#enter 11298 * @kind function 11299 * @description Inserts the element into the DOM either after the `after` element (if provided) or 11300 * as the first child within the `parent` element and then triggers an animation. 11301 * A promise is returned that will be resolved during the next digest once the animation 11302 * has completed. 11303 * 11304 * @param {DOMElement} element the element which will be inserted into the DOM 11305 * @param {DOMElement} parent the parent element which will append the element as 11306 * a child (so long as the after element is not present) 11307 * @param {DOMElement=} after the sibling element after which the element will be appended 11308 * @param {object=} options an optional collection of options/styles that will be applied to the element. 11309 * The object can have the following properties: 11310 * 11311 * - **addClass** - `{string}` - space-separated CSS classes to add to element 11312 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` 11313 * - **removeClass** - `{string}` - space-separated CSS classes to remove from element 11314 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` 11315 * 11316 * @return {Promise} the animation callback promise 11317 */ 11318 enter: function(element, parent, after, options) { 11319 parent = parent && jqLite(parent); 11320 after = after && jqLite(after); 11321 parent = parent || after.parent(); 11322 domInsert(element, parent, after); 11323 return $$animateQueue.push(element, 'enter', prepareAnimateOptions(options)); 11324 }, 11325 11326 /** 11327 * 11328 * @ngdoc method 11329 * @name $animate#move 11330 * @kind function 11331 * @description Inserts (moves) the element into its new position in the DOM either after 11332 * the `after` element (if provided) or as the first child within the `parent` element 11333 * and then triggers an animation. A promise is returned that will be resolved 11334 * during the next digest once the animation has completed. 11335 * 11336 * @param {DOMElement} element the element which will be moved into the new DOM position 11337 * @param {DOMElement} parent the parent element which will append the element as 11338 * a child (so long as the after element is not present) 11339 * @param {DOMElement=} after the sibling element after which the element will be appended 11340 * @param {object=} options an optional collection of options/styles that will be applied to the element. 11341 * The object can have the following properties: 11342 * 11343 * - **addClass** - `{string}` - space-separated CSS classes to add to element 11344 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` 11345 * - **removeClass** - `{string}` - space-separated CSS classes to remove from element 11346 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` 11347 * 11348 * @return {Promise} the animation callback promise 11349 */ 11350 move: function(element, parent, after, options) { 11351 parent = parent && jqLite(parent); 11352 after = after && jqLite(after); 11353 parent = parent || after.parent(); 11354 domInsert(element, parent, after); 11355 return $$animateQueue.push(element, 'move', prepareAnimateOptions(options)); 11356 }, 11357 11358 /** 11359 * @ngdoc method 11360 * @name $animate#leave 11361 * @kind function 11362 * @description Triggers an animation and then removes the element from the DOM. 11363 * When the function is called a promise is returned that will be resolved during the next 11364 * digest once the animation has completed. 11365 * 11366 * @param {DOMElement} element the element which will be removed from the DOM 11367 * @param {object=} options an optional collection of options/styles that will be applied to the element. 11368 * The object can have the following properties: 11369 * 11370 * - **addClass** - `{string}` - space-separated CSS classes to add to element 11371 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` 11372 * - **removeClass** - `{string}` - space-separated CSS classes to remove from element 11373 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` 11374 * 11375 * @return {Promise} the animation callback promise 11376 */ 11377 leave: function(element, options) { 11378 return $$animateQueue.push(element, 'leave', prepareAnimateOptions(options), function() { 11379 element.remove(); 11380 }); 11381 }, 11382 11383 /** 11384 * @ngdoc method 11385 * @name $animate#addClass 11386 * @kind function 11387 *
11388 * @description Triggers an addClass animation surrounding the addition of the provided CSS class(es). Upon 11389 * execution, the addClass operation will only be handled after the next digest and it will not trigger an 11390 * animation if element already contains the CSS class or if the class is removed at a later step. 11391 * Note that class-based animations are treated differently compared to structural animations 11392 * (like enter, move and leave) since the CSS classes may be added/removed at different points 11393 * depending if CSS or JavaScript animations are used. 11394 * 11395 * @param {DOMElement} element the element which the CSS classes will be applied to 11396 * @param {string} className the CSS class(es) that will be added (multiple classes are separated via spaces) 11397 * @param {object=} options an optional collection of options/styles that will be applied to the element. 11398 * The object can have the following properties: 11399 * 11400 * - **addClass** - `{string}` - space-separated CSS classes to add to element 11401 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` 11402 * - **removeClass** - `{string}` - space-separated CSS classes to remove from element 11403 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` 11404 * 11405 * @return {Promise} the animation callback promise 11406 */ 11407 addClass: function(element, className, options) { 11408 options = prepareAnimateOptions(options); 11409 options.addClass = mergeClasses(options.addclass, className); 11410 return $$animateQueue.push(element, 'addClass', options); 11411 }, 11412 11413 /** 11414 * @ngdoc method 11415 * @name $animate#removeClass 11416 * @kind function 11417 * 11418 * @description Triggers a removeClass animation surrounding the removal of the provided CSS class(es). Upon 11419 * execution, the removeClass operation will only be handled after the next digest and it will not trigger an 11420 * animation if element does not contain the CSS class or if the class is added at a later step. 11421 * Note that class-based animations are treated differently compared to structural animations 11422 * (like enter, move and leave) since the CSS classes may be added/removed at different points 11423 * depending if CSS or JavaScript animations are used. 11424 * 11425 * @param {DOMElement} element the element which the CSS classes will be applied to 11426 * @param {string} className the CSS class(es) that will be removed (multiple classes are separated via spaces) 11427 * @param {object=} options an optional collection of options/styles that will be applied to the element. 11428 * The object can have the following properties: 11429 * 11430 * - **addClass** - `{string}` - space-separated CSS classes to add to element 11431 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` 11432 * - **removeClass** - `{string}` - space-separated CSS classes to remove from element 11433 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` 11434 * 11435 * @return {Promise} the animation callback promise 11436 */ 11437 removeClass: function(element, className, options) { 11438 options = prepareAnimateOptions(options); 11439 options.removeClass = mergeClasses(options.removeClass, className); 11440 return $$animateQueue.push(element, 'removeClass', options); 11441 }, 11442 11443 /** 11444 * @ngdoc method 11445 * @name $animate#setClass 11446 * @kind function 11447 * 11448 * @description Performs both the addition and removal of a CSS classes on an element and (during the process) 11449 * triggers an animation surrounding the class addition/removal. Much like `$animate.addClass` and 11450 * `$animate.removeClass`, `setClass` will only evaluate the classes being added/removed once a digest has 11451 * passed. Note that class-based animations are treated differently compared to structural animations 11452 * (like enter, move and leave) since the CSS classes may be added/removed at different points 11453 * depending if CSS or JavaScript animations are used. 11454 * 11455 * @param {DOMElement} element the element which the CSS classes will be applied to 11456 * @param {string} add the CSS class(es) that will be added (multiple classes are separated via spaces) 11457 * @param {string} remove the CSS class(es) that will be removed (multiple classes are separated via spaces) 11458 * @param {object=} options an optional collection of options/styles that will be applied to the element. 11459 * The object can have the following properties: 11460 * 11461 * - **addClass** - `{string}` - space-separated CSS classes to add to element 11462 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` 11463 * - **removeClass** - `{string}` - space-separated CSS classes to remove from element 11464 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` 11465 * 11466 * @return {Promise} the animation callback promise 11467 */ 11468 setClass: function(element, add, remove, options) { 11469 options = prepareAnimateOptions(options); 11470 options.addClass = mergeClasses(options.addClass, add); 11471 options.removeClass = mergeClasses(options.removeClass, remove); 11472 return $$animateQueue.push(element, 'setClass', options); 11473 }, 11474 11475 /** 11476 * @ngdoc method 11477 * @name $animate#animate 11478 * @kind function 11479 *
11480 * @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element. 11481 * If any detected CSS transition, keyframe or JavaScript matches the provided className value, then the animation will take 11482 * on the provided styles. For example, if a transition animation is set for the given className, then the provided `from` and 11483 * `to` styles will be applied alongside the given transition. If the CSS style provided in `from` does not have a corresponding 11484 * style in `to`, the style in `from` is applied immediately, and no animation is run. 11485 * If a JavaScript animation is detected then the provided styles will be given in as function parameters into the `animate` 11486 * method (or as part of the `options` parameter): 11487 * 11488 * ```js 11489 * ngModule.animation('.my-inline-animation', function() { 11490 * return { 11491 * animate : function(element, from, to, done, options) { 11492 * //animation 11493 * done(); 11494 * } 11495 * } 11496 * }); 11497 * ``` 11498 * 11499 * @param {DOMElement} element the element which the CSS styles will be applied to 11500 * @param {object} from the from (starting) CSS styles that will be applied to the element and across the animation. 11501 * @param {object} to the to (destination) CSS styles that will be applied to the element and across the animation. 11502 * @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If 11503 * this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element. 11504 * (Note that if no animation is detected then this value will not be applied to the element.) 11505 * @param {object=} options an optional collection of options/styles that will be applied to the element. 11506 * The object can have the following properties: 11507 * 11508 * - **addClass** - `{string}` - space-separated CSS classes to add to element 11509 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` 11510 * - **removeClass** - `{string}` - space-separated CSS classes to remove from element 11511 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` 11512 * 11513 * @return {Promise} the animation callback promise 11514 */ 11515 animate: function(element, from, to, className, options) { 11516 options = prepareAnimateOptions(options); 11517 options.from = options.from ? extend(options.from, from) : from; 11518 options.to = options.to ? extend(options.to, to) : to; 11519 11520 className = className || 'ng-inline-animate'; 11521 options.tempClasses = mergeClasses(options.tempClasses, className); 11522 return $$animateQueue.push(element, 'animate', options); 11523 } 11524 }; 11525 }]; 11526}]; 11527 11528var $$AnimateAsyncRunFactoryProvider = /** @this */ function() { 11529 this.$get = ['$$rAF', function($$rAF) { 11530 var waitQueue = []; 11531 11532 function waitForTick(fn) { 11533 waitQueue.push(fn); 11534 if (waitQueue.length > 1) return; 11535 $$rAF(function() { 11536 for (var i = 0; i < waitQueue.length; i++) { 11537 waitQueue[i](); 11538 } 11539 waitQueue = []; 11540 }); 11541 } 11542 11543 return function() { 11544 var passed = false; 11545 waitForTick(function() { 11546 passed = true; 11547 }); 11548 return function(callback) { 11549 if (passed) { 11550 callback(); 11551 } else { 11552 waitForTick(callback); 11553 } 11554 }; 11555 }; 11556 }]; 11557}; 11558 11559var $$AnimateRunnerFactoryProvider = /** @this */ function() { 11560 this.$get = ['$q', '$sniffer', '$$animateAsyncRun', '$document', '$timeout', 11561 function($q, $sniffer, $$animateAsyncRun, $document, $timeout) { 11562 11563 var INITIAL_STATE = 0; 11564 var DONE_PENDING_STATE = 1; 11565 var DONE_COMPLETE_STATE = 2; 11566 11567 AnimateRunner.chain = function(chain, callback) { 11568 var index = 0; 11569 11570 next(); 11571 function next() { 11572 if (index === chain.length) { 11573 callback(true); 11574 return; 11575 } 11576 11577 chain[index](function(response) { 11578 if (response === false) { 11579 callback(false); 11580 return; 11581 } 11582 index++; 11583 next(); 11584 }); 11585 } 11586 }; 11587 11588 AnimateRunner.all = function(runners, callback) { 11589 var count = 0; 11590 var status = true;
vendor: 5,265 bytes, lines 11591-11807
11591 forEach(runners, function(runner) { 11592 runner.done(onProgress); 11593 }); 11594 11595 function onProgress(response) { 11596 status = status && response; 11597 if (++count === runners.length) { 11598 callback(status); 11599 } 11600 } 11601 }; 11602 11603 function AnimateRunner(host) { 11604 this.setHost(host); 11605 11606 var rafTick = $$animateAsyncRun(); 11607 var timeoutTick = function(fn) { 11608 $timeout(fn, 0, false); 11609 }; 11610 11611 this._doneCallbacks = []; 11612 this._tick = function(fn) { 11613 var doc = $document[0]; 11614 11615 // the document may not be ready or attached 11616 // to the module for some internal tests 11617 if (doc && doc.hidden) { 11618 timeoutTick(fn); 11619 } else { 11620 rafTick(fn); 11621 } 11622 }; 11623 this._state = 0; 11624 } 11625 11626 AnimateRunner.prototype = { 11627 setHost: function(host) { 11628 this.host = host || {}; 11629 }, 11630 11631 done: function(fn) { 11632 if (this._state === DONE_COMPLETE_STATE) { 11633 fn(); 11634 } else { 11635 this._doneCallbacks.push(fn); 11636 } 11637 }, 11638 11639 progress: noop, 11640 11641 getPromise: function() { 11642 if (!this.promise) { 11643 var self = this; 11644 this.promise = $q(function(resolve, reject) { 11645 self.done(function(status) { 11646 if (status === false) { 11647 reject(); 11648 } else { 11649 resolve(); 11650 } 11651 }); 11652 }); 11653 } 11654 return this.promise; 11655 }, 11656 11657 then: function(resolveHandler, rejectHandler) { 11658 return this.getPromise().then(resolveHandler, rejectHandler); 11659 }, 11660 11661 'catch': function(handler) { 11662 return this.getPromise()['catch'](handler); 11663 }, 11664 11665 'finally': function(handler) { 11666 return this.getPromise()['finally'](handler); 11667 }, 11668 11669 pause: function() { 11670 if (this.host.pause) { 11671 this.host.pause(); 11672 } 11673 }, 11674 11675 resume: function() { 11676 if (this.host.resume) { 11677 this.host.resume(); 11678 } 11679 }, 11680 11681 end: function() { 11682 if (this.host.end) { 11683 this.host.end(); 11684 } 11685 this._resolve(true); 11686 }, 11687 11688 cancel: function() { 11689 if (this.host.cancel) { 11690 this.host.cancel(); 11691 } 11692 this._resolve(false); 11693 }, 11694 11695 complete: function(response) { 11696 var self = this; 11697 if (self._state === INITIAL_STATE) { 11698 self._state = DONE_PENDING_STATE; 11699 self._tick(function() { 11700 self._resolve(response); 11701 }); 11702 } 11703 }, 11704 11705 _resolve: function(response) { 11706 if (this._state !== DONE_COMPLETE_STATE) { 11707 forEach(this._doneCallbacks, function(fn) { 11708 fn(response); 11709 }); 11710 this._doneCallbacks.length = 0; 11711 this._state = DONE_COMPLETE_STATE; 11712 } 11713 } 11714 }; 11715 11716 return AnimateRunner; 11717 }]; 11718}; 11719 11720/* exported $CoreAnimateCssProvider */ 11721 11722/** 11723 * @ngdoc service 11724 * @name $animateCss 11725 * @kind object 11726 * @this 11727 * 11728 * @description 11729 * This is the core version of `$animateCss`. By default, only when the `ngAnimate` is included, 11730 * then the `$animateCss` service will actually perform animations. 11731 * 11732 * Click here {@link ngAnimate.$animateCss to read the documentation for $animateCss}. 11733 */ 11734var $CoreAnimateCssProvider = function() { 11735 this.$get = ['$$rAF', '$q', '$$AnimateRunner', function($$rAF, $q, $$AnimateRunner) { 11736 11737 return function(element, initialOptions) { 11738 // all of the animation functions should create 11739 // a copy of the options data, however, if a 11740 // parent service has already created a copy then 11741 // we should stick to using that 11742 var options = initialOptions || {}; 11743 if (!options.$$prepared) { 11744 options = copy(options); 11745 } 11746 11747 // there is no point in applying the styles since 11748 // there is no animation that goes on at all in 11749 // this version of $animateCss. 11750 if (options.cleanupStyles) { 11751 options.from = options.to = null; 11752 } 11753 11754 if (options.from) { 11755 element.css(options.from); 11756 options.from = null; 11757 } 11758 11759 var closed, runner = new $$AnimateRunner(); 11760 return { 11761 start: run, 11762 end: run 11763 }; 11764 11765 function run() { 11766 $$rAF(function() { 11767 applyAnimationContents(); 11768 if (!closed) { 11769 runner.complete(); 11770 } 11771 closed = true; 11772 }); 11773 return runner; 11774 } 11775 11776 function applyAnimationContents() { 11777 if (options.addClass) { 11778 element.addClass(options.addClass); 11779 options.addClass = null; 11780 } 11781 if (options.removeClass) { 11782 element.removeClass(options.removeClass); 11783 options.removeClass = null; 11784 } 11785 if (options.to) { 11786 element.css(options.to); 11787 options.to = null; 11788 } 11789 } 11790 }; 11791 }]; 11792}; 11793 11794/* global stripHash: true */ 11795 11796/** 11797 * ! This is a private undocumented service ! 11798 * 11799 * @name $browser 11800 * @requires $log 11801 * @description 11802 * This object has two goals: 11803 * 11804 * - hide all the global state in the browser caused by the window object 11805 * - abstract away all the browser specific features and inconsistencies 11806 * 11807 * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser`
11808 * service, which can be used for convenient testing of the application without the interaction with 11809 * the real browser apis. 11810 */ 11811/** 11812 * @param {object} window The global window object. 11813 * @param {object} document jQuery wrapped document. 11814 * @param {object} $log window.console or an object with the same interface. 11815 * @param {object} $sniffer $sniffer service 11816 */ 11817function Browser(window, document, $log, $sniffer) { 11818 var self = this, 11819 location = window.location, 11820 history = window.history, 11821 setTimeout = window.setTimeout, 11822 clearTimeout = window.clearTimeout, 11823 pendingDeferIds = {}; 11824 11825 self.isMock = false; 11826 11827 var outstandingRequestCount = 0; 11828 var outstandingRequestCallbacks = []; 11829 11830 // TODO(vojta): remove this temporary api 11831 self.$$completeOutstandingRequest = completeOutstandingRequest; 11832 self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; }; 11833 11834 /** 11835 * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks` 11836 * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed. 11837 */ 11838 function completeOutstandingRequest(fn) { 11839 try { 11840 fn.apply(null, sliceArgs(arguments, 1)); 11841 } finally { 11842 outstandingRequestCount--; 11843 if (outstandingRequestCount === 0) { 11844 while (outstandingRequestCallbacks.length) { 11845 try { 11846 outstandingRequestCallbacks.pop()(); 11847 } catch (e) { 11848 $log.error(e); 11849 } 11850 } 11851 } 11852 } 11853 } 11854 11855 function getHash(url) { 11856 var index = url.indexOf('#'); 11857 return index === -1 ? '' : url.substr(index); 11858 } 11859 11860 /** 11861 * @private 11862 * Note: this method is used only by scenario runner 11863 * TODO(vojta): prefix this method with $$ ? 11864 * @param {function()} callback Function that will be called when no outstanding request 11865 */ 11866 self.notifyWhenNoOutstandingRequests = function(callback) { 11867 if (outstandingRequestCount === 0) { 11868 callback(); 11869 } else { 11870 outstandingRequestCallbacks.push(callback); 11871 } 11872 }; 11873 11874 ////////////////////////////////////////////////////////////// 11875 // URL API 11876 ////////////////////////////////////////////////////////////// 11877 11878 var cachedState, lastHistoryState, 11879 lastBrowserUrl = location.href, 11880 baseElement = document.find('base'), 11881 pendingLocation = null, 11882 getCurrentState = !$sniffer.history ? noop : function getCurrentState() { 11883 try { 11884 return history.state; 11885 } catch (e) { 11886 // MSIE can reportedly throw when there is no state (UNCONFIRMED). 11887 } 11888 }; 11889 11890 cacheState(); 11891 lastHistoryState = cachedState; 11892 11893 /** 11894 * @name $browser#url 11895 * 11896 * @description 11897 * GETTER: 11898 * Without any argument, this method just returns current value of location.href. 11899 * 11900 * SETTER: 11901 * With at least one argument, this method sets url to new value. 11902 * If html5 history api supported, pushState/replaceState is used, otherwise 11903 * location.href/location.replace is used. 11904 * Returns its own instance to allow chaining 11905 * 11906 * NOTE: this api is intended for use only by the $location service. Please use the 11907 * {@link ng.$location $location service} to change url. 11908 * 11909 * @param {string} url New url (when used as setter) 11910 * @param {boolean=} replace Should new url replace current history record? 11911 * @param {object=} state object to use with pushState/replaceState 11912 */ 11913 self.url = function(url, replace, state) { 11914 // In modern browsers `history.state` is `null` by default; treating it separately 11915 // from `undefined` would cause `$browser.url('/foo')` to change `history.state` 11916 // to undefined via `pushState`. Instead, let's change `undefined` to `null` here. 11917 if (isUndefined(state)) { 11918 state = null; 11919 } 11920 11921 // Android Browser BFCache causes location, history reference to become stale. 11922 if (location !== window.location) location = window.location; 11923 if (history !== window.history) history = window.history; 11924 11925 // setter 11926 if (url) { 11927 var sameState = lastHistoryState === state; 11928 11929 // Don't change anything if previous and current URLs and states match. This also prevents 11930 // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode. 11931 // See https://github.com/angular/angular.js/commit/ffb2701 11932 if (lastBrowserUrl === url && (!$sniffer.history || sameState)) { 11933 return self; 11934 } 11935 var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url); 11936 lastBrowserUrl = url; 11937 lastHistoryState = state; 11938 // Don't use history API if only the hash changed 11939 // due to a bug in IE10/IE11 which leads 11940 // to not firing a `hashchange` nor `popstate` event 11941 // in some cases (see #9143). 11942 if ($sniffer.history && (!sameBase || !sameState)) { 11943 history[replace ? 'replaceState' : 'pushState'](state, '', url); 11944 cacheState(); 11945 // Do the assignment again so that those two variables are referentially identical. 11946 lastHistoryState = cachedState; 11947 } else { 11948 if (!sameBase) { 11949 pendingLocation = url; 11950 } 11951 if (replace) { 11952 location.replace(url); 11953 } else if (!sameBase) { 11954 location.href = url; 11955 } else { 11956 location.hash = getHash(url); 11957 } 11958 if (location.href !== url) { 11959 pendingLocation = url; 11960 } 11961 } 11962 if (pendingLocation) { 11963 pendingLocation = url; 11964 } 11965 return self; 11966 // getter 11967 } else { 11968 // - pendingLocation is needed as browsers don't allow to read out 11969 // the new location.href if a reload happened or if there is a bug like in iOS 9 (see 11970 // https://openradar.appspot.com/22186109). 11971 // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172 11972 return pendingLocation || location.href.replace(/%27/g,'\''); 11973 } 11974 }; 11975 11976 /** 11977 * @name $browser#state 11978 * 11979 * @description 11980 * This method is a getter. 11981 * 11982 * Return history.state or null if history.state is undefined. 11983 * 11984 * @returns {object} state 11985 */ 11986 self.state = function() { 11987 return cachedState; 11988 }; 11989 11990 var urlChangeListeners = [], 11991 urlChangeInit = false;
11992 11993 function cacheStateAndFireUrlChange() { 11994 pendingLocation = null; 11995 cacheState(); 11996 fireUrlChange(); 11997 } 11998 11999 // This variable should be used *only* inside the cacheState function. 12000 var lastCachedState = null; 12001 function cacheState() { 12002 // This should be the only place in $browser where `history.state` is read. 12003 cachedState = getCurrentState(); 12004 cachedState = isUndefined(cachedState) ? null : cachedState; 12005 12006 // Prevent callbacks fo fire twice if both hashchange & popstate were fired. 12007 if (equals(cachedState, lastCachedState)) { 12008 cachedState = lastCachedState; 12009 } 12010 lastCachedState = cachedState; 12011 } 12012 12013 function fireUrlChange() { 12014 if (lastBrowserUrl === self.url() && lastHistoryState === cachedState) { 12015 return; 12016 } 12017 12018 lastBrowserUrl = self.url(); 12019 lastHistoryState = cachedState; 12020 forEach(urlChangeListeners, function(listener) { 12021 listener(self.url(), cachedState); 12022 }); 12023 } 12024 12025 /** 12026 * @name $browser#onUrlChange 12027 * 12028 * @description 12029 * Register callback function that will be called, when url changes. 12030 * 12031 * It's only called when the url is changed from outside of angular: 12032 * - user types different url into address bar 12033 * - user clicks on history (forward/back) button 12034 * - user clicks on a link 12035 * 12036 * It's not called when url is changed by $browser.url() method 12037 * 12038 * The listener gets called with new url as parameter. 12039 * 12040 * NOTE: this api is intended for use only by the $location service. Please use the 12041 * {@link ng.$location $location service} to monitor url changes in angular apps. 12042 * 12043 * @param {function(string)} listener Listener function to be called when url changes. 12044 * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous. 12045 */ 12046 self.onUrlChange = function(callback) { 12047 // TODO(vojta): refactor to use node's syntax for events 12048 if (!urlChangeInit) { 12049 // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera) 12050 // don't fire popstate when user change the address bar and don't fire hashchange when url 12051 // changed by push/replaceState 12052 12053 // html5 history api - popstate event 12054 if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange); 12055 // hashchange event 12056 jqLite(window).on('hashchange', cacheStateAndFireUrlChange); 12057 12058 urlChangeInit = true; 12059 } 12060 12061 urlChangeListeners.push(callback); 12062 return callback; 12063 }; 12064 12065 /** 12066 * @private 12067 * Remove popstate and hashchange handler from window. 12068 * 12069 * NOTE: this api is intended for use only by $rootScope. 12070 */ 12071 self.$$applicationDestroyed = function() { 12072 jqLite(window).off('hashchange popstate', cacheStateAndFireUrlChange); 12073 }; 12074 12075 /** 12076 * Checks whether the url has changed outside of Angular. 12077 * Needs to be exported to be able to check for changes that have been done in sync, 12078 * as hashchange/popstate events fire in async. 12079 */ 12080 self.$$checkUrlChange = fireUrlChange; 12081 12082 ////////////////////////////////////////////////////////////// 12083 // Misc API 12084 ////////////////////////////////////////////////////////////// 12085 12086 /** 12087 * @name $browser#baseHref 12088 * 12089 * @description 12090 * Returns current <base href> 12091 * (always relative - without domain) 12092 * 12093 * @returns {string} The current base href 12094 */ 12095 self.baseHref = function() { 12096 var href = baseElement.attr('href'); 12097 return href ? href.replace(/^(https?:)?\/\/[^/]*/, '') : ''; 12098 }; 12099 12100 /** 12101 * @name $browser#defer 12102 * @param {function()} fn A function, who's execution should be deferred. 12103 * @param {number=} [delay=0] of milliseconds to defer the function execution. 12104 * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`. 12105 * 12106 * @description 12107 * Executes a fn asynchronously via `setTimeout(fn, delay)`. 12108 * 12109 * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using 12110 * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed 12111 * via `$browser.defer.flush()`. 12112 * 12113 */ 12114 self.defer = function(fn, delay) { 12115 var timeoutId; 12116 outstandingRequestCount++; 12117 timeoutId = setTimeout(function() {
12118 delete pendingDeferIds[timeoutId]; 12119 completeOutstandingRequest(fn); 12120 }, delay || 0); 12121 pendingDeferIds[timeoutId] = true; 12122 return timeoutId; 12123 }; 12124 12125 12126 /** 12127 * @name $browser#defer.cancel 12128 * 12129 * @description 12130 * Cancels a deferred task identified with `deferId`. 12131 * 12132 * @param {*} deferId Token returned by the `$browser.defer` function. 12133 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 12134 * canceled. 12135 */ 12136 self.defer.cancel = function(deferId) { 12137 if (pendingDeferIds[deferId]) { 12138 delete pendingDeferIds[deferId]; 12139 clearTimeout(deferId); 12140 completeOutstandingRequest(noop); 12141 return true; 12142 } 12143 return false; 12144 }; 12145 12146} 12147 12148/** @this */ 12149function $BrowserProvider() { 12150 this.$get = ['$window', '$log', '$sniffer', '$document', 12151 function($window, $log, $sniffer, $document) { 12152 return new Browser($window, $document, $log, $sniffer); 12153 }]; 12154} 12155 12156/** 12157 * @ngdoc service 12158 * @name $cacheFactory 12159 * @this 12160 * 12161 * @description 12162 * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to 12163 * them. 12164 * 12165 * ```js 12166 * 12167 * var cache = $cacheFactory('cacheId'); 12168 * expect($cacheFactory.get('cacheId')).toBe(cache); 12169 * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined(); 12170 * 12171 * cache.put("key", "value"); 12172 * cache.put("another key", "another value"); 12173 * 12174 * // We've specified no options on creation 12175 * expect(cache.info()).toEqual({id: 'cacheId', size: 2}); 12176 * 12177 * ``` 12178 * 12179 * 12180 * @param {string} cacheId Name or id of the newly created cache. 12181 * @param {object=} options Options object that specifies the cache behavior. Properties: 12182 * 12183 * - `{number=}` `capacity` â turns the cache into LRU cache. 12184 * 12185 * @returns {object} Newly created cache object with the following set of methods: 12186 * 12187 * - `{object}` `info()` â Returns id, size, and options of cache. 12188 * - `{{*}}` `put({string} key, {*} value)` â Puts a new key-value pair into the cache and returns 12189 * it. 12190 * - `{{*}}` `get({string} key)` â Returns cached value for `key` or undefined for cache miss. 12191 * - `{void}` `remove({string} key)` â Removes a key-value pair from the cache. 12192 * - `{void}` `removeAll()` â Removes all cached values. 12193 * - `{void}` `destroy()` â Removes references to this cache from $cacheFactory. 12194 * 12195 * @example 12196 <example module="cacheExampleApp" name="cache-factory"> 12197 <file name="index.html"> 12198 <div ng-controller="CacheController"> 12199 <input ng-model="newCacheKey" placeholder="Key"> 12200 <input ng-model="newCacheValue" placeholder="Value"> 12201 <button ng-click="put(newCacheKey, newCacheValue)">Cache</button> 12202 12203 <p ng-if="keys.length">Cached Values</p> 12204 <div ng-repeat="key in keys"> 12205 <span ng-bind="key"></span> 12206 <span>: </span> 12207 <b ng-bind="cache.get(key)"></b> 12208 </div> 12209 12210 <p>Cache Info</p> 12211 <div ng-repeat="(key, value) in cache.info()"> 12212 <span ng-bind="key"></span> 12213 <span>: </span> 12214 <b ng-bind="value"></b> 12215 </div> 12216 </div> 12217 </file> 12218 <file name="script.js"> 12219 angular.module('cacheExampleApp', []). 12220 controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) { 12221 $scope.keys = []; 12222 $scope.cache = $cacheFactory('cacheId'); 12223 $scope.put = function(key, value) { 12224 if (angular.isUndefined($scope.cache.get(key))) { 12225 $scope.keys.push(key); 12226 } 12227 $scope.cache.put(key, angular.isUndefined(value) ? null : value); 12228 }; 12229 }]); 12230 </file> 12231 <file name="style.css"> 12232 p { 12233 margin: 10px 0 3px; 12234 } 12235 </file> 12236 </example> 12237 */ 12238function $CacheFactoryProvider() { 12239 12240 this.$get = function() { 12241 var caches = {}; 12242 12243 function cacheFactory(cacheId, options) { 12244 if (cacheId in caches) { 12245 throw minErr('$cacheFactory')('iid', 'CacheId \'{0}\' is already taken!', cacheId); 12246 } 12247 12248 var size = 0, 12249 stats = extend({}, options, {id: cacheId}), 12250 data = createMap(), 12251 capacity = (options && options.capacity) || Number.MAX_VALUE, 12252 lruHash = createMap(), 12253 freshEnd = null, 12254 staleEnd = null; 12255 12256 /** 12257 * @ngdoc type 12258 * @name $cacheFactory.Cache 12259 *
12260 * @description 12261 * A cache object used to store and retrieve data, primarily used by 12262 * {@link $http $http} and the {@link ng.directive:script script} directive to cache 12263 * templates and other data. 12264 * 12265 * ```js 12266 * angular.module('superCache') 12267 * .factory('superCache', ['$cacheFactory', function($cacheFactory) { 12268 * return $cacheFactory('super-cache'); 12269 * }]); 12270 * ``` 12271 * 12272 * Example test: 12273 * 12274 * ```js 12275 * it('should behave like a cache', inject(function(superCache) { 12276 * superCache.put('key', 'value'); 12277 * superCache.put('another key', 'another value'); 12278 * 12279 * expect(superCache.info()).toEqual({ 12280 * id: 'super-cache', 12281 * size: 2 12282 * }); 12283 * 12284 * superCache.remove('another key'); 12285 * expect(superCache.get('another key')).toBeUndefined(); 12286 * 12287 * superCache.removeAll(); 12288 * expect(superCache.info()).toEqual({ 12289 * id: 'super-cache', 12290 * size: 0 12291 * }); 12292 * })); 12293 * ``` 12294 */ 12295 return (caches[cacheId] = { 12296 12297 /** 12298 * @ngdoc method 12299 * @name $cacheFactory.Cache#put 12300 * @kind function 12301 * 12302 * @description 12303 * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be 12304 * retrieved later, and incrementing the size of the cache if the key was not already 12305 * present in the cache. If behaving like an LRU cache, it will also remove stale 12306 * entries from the set. 12307 * 12308 * It will not insert undefined values into the cache. 12309 * 12310 * @param {string} key the key under which the cached data is stored. 12311 * @param {*} value the value to store alongside the key. If it is undefined, the key 12312 * will not be stored. 12313 * @returns {*} the value stored. 12314 */ 12315 put: function(key, value) { 12316 if (isUndefined(value)) return; 12317 if (capacity < Number.MAX_VALUE) { 12318 var lruEntry = lruHash[key] || (lruHash[key] = {key: key}); 12319 12320 refresh(lruEntry); 12321 } 12322 12323 if (!(key in data)) size++; 12324 data[key] = value; 12325 12326 if (size > capacity) { 12327 this.remove(staleEnd.key); 12328 } 12329 12330 return value; 12331 }, 12332 12333 /** 12334 * @ngdoc method 12335 * @name $cacheFactory.Cache#get 12336 * @kind function 12337 * 12338 * @description 12339 * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object. 12340 * 12341 * @param {string} key the key of the data to be retrieved 12342 * @returns {*} the value stored. 12343 */ 12344 get: function(key) { 12345 if (capacity < Number.MAX_VALUE) { 12346 var lruEntry = lruHash[key]; 12347 12348 if (!lruEntry) return; 12349 12350 refresh(lruEntry); 12351 } 12352 12353 return data[key]; 12354 }, 12355 12356 12357 /** 12358 * @ngdoc method 12359 * @name $cacheFactory.Cache#remove 12360 * @kind function 12361 * 12362 * @description 12363 * Removes an entry from the {@link $cacheFactory.Cache Cache} object. 12364 * 12365 * @param {string} key the key of the entry to be removed 12366 */ 12367 remove: function(key) { 12368 if (capacity < Number.MAX_VALUE) { 12369 var lruEntry = lruHash[key]; 12370 12371 if (!lruEntry) return; 12372 12373 if (lruEntry === freshEnd) freshEnd = lruEntry.p; 12374 if (lruEntry === staleEnd) staleEnd = lruEntry.n; 12375 link(lruEntry.n,lruEntry.p); 12376 12377 delete lruHash[key]; 12378 } 12379 12380 if (!(key in data)) return; 12381 12382 delete data[key]; 12383 size--; 12384 }, 12385 12386 12387 /** 12388 * @ngdoc method 12389 * @name $cacheFactory.Cache#removeAll 12390 * @kind function 12391 * 12392 * @description 12393 * Clears the cache object of any entries. 12394 */ 12395 removeAll: function() { 12396 data = createMap(); 12397 size = 0; 12398 lruHash = createMap(); 12399 freshEnd = staleEnd = null; 12400 }, 12401 12402 12403 /** 12404 * @ngdoc method 12405 * @name $cacheFactory.Cache#destroy 12406 * @kind function 12407 *
vendor: 4,055 bytes, lines 12408-12553
12408 * @description 12409 * Destroys the {@link $cacheFactory.Cache Cache} object entirely, 12410 * removing it from the {@link $cacheFactory $cacheFactory} set. 12411 */ 12412 destroy: function() { 12413 data = null; 12414 stats = null; 12415 lruHash = null; 12416 delete caches[cacheId]; 12417 }, 12418 12419 12420 /** 12421 * @ngdoc method 12422 * @name $cacheFactory.Cache#info 12423 * @kind function 12424 * 12425 * @description 12426 * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}. 12427 * 12428 * @returns {object} an object with the following properties: 12429 * <ul> 12430 * <li>**id**: the id of the cache instance</li> 12431 * <li>**size**: the number of entries kept in the cache instance</li> 12432 * <li>**...**: any additional properties from the options object when creating the 12433 * cache.</li> 12434 * </ul> 12435 */ 12436 info: function() { 12437 return extend({}, stats, {size: size}); 12438 } 12439 }); 12440 12441 12442 /** 12443 * makes the `entry` the freshEnd of the LRU linked list 12444 */ 12445 function refresh(entry) { 12446 if (entry !== freshEnd) { 12447 if (!staleEnd) { 12448 staleEnd = entry; 12449 } else if (staleEnd === entry) { 12450 staleEnd = entry.n; 12451 } 12452 12453 link(entry.n, entry.p); 12454 link(entry, freshEnd); 12455 freshEnd = entry; 12456 freshEnd.n = null; 12457 } 12458 } 12459 12460 12461 /** 12462 * bidirectionally links two entries of the LRU linked list 12463 */ 12464 function link(nextEntry, prevEntry) { 12465 if (nextEntry !== prevEntry) { 12466 if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify 12467 if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify 12468 } 12469 } 12470 } 12471 12472 12473 /** 12474 * @ngdoc method 12475 * @name $cacheFactory#info 12476 * 12477 * @description 12478 * Get information about all the caches that have been created 12479 * 12480 * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info` 12481 */ 12482 cacheFactory.info = function() { 12483 var info = {}; 12484 forEach(caches, function(cache, cacheId) { 12485 info[cacheId] = cache.info(); 12486 }); 12487 return info; 12488 }; 12489 12490 12491 /** 12492 * @ngdoc method 12493 * @name $cacheFactory#get 12494 * 12495 * @description 12496 * Get access to a cache object by the `cacheId` used when it was created. 12497 * 12498 * @param {string} cacheId Name or id of a cache to access. 12499 * @returns {object} Cache object identified by the cacheId or undefined if no such cache. 12500 */ 12501 cacheFactory.get = function(cacheId) { 12502 return caches[cacheId]; 12503 }; 12504 12505 12506 return cacheFactory; 12507 }; 12508} 12509 12510/** 12511 * @ngdoc service 12512 * @name $templateCache 12513 * @this 12514 * 12515 * @description 12516 * The first time a template is used, it is loaded in the template cache for quick retrieval. You 12517 * can load templates directly into the cache in a `script` tag, or by consuming the 12518 * `$templateCache` service directly. 12519 * 12520 * Adding via the `script` tag: 12521 * 12522 * ```html 12523 * <script type="text/ng-template" id="templateId.html"> 12524 * <p>This is the content of the template</p> 12525 * </script> 12526 * ``` 12527 * 12528 * **Note:** the `script` tag containing the template does not need to be included in the `head` of 12529 * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE, 12530 * element with ng-app attribute), otherwise the template will be ignored. 12531 * 12532 * Adding via the `$templateCache` service: 12533 * 12534 * ```js 12535 * var myApp = angular.module('myApp', []); 12536 * myApp.run(function($templateCache) { 12537 * $templateCache.put('templateId.html', 'This is the content of the template'); 12538 * }); 12539 * ``` 12540 * 12541 * To retrieve the template later, simply use it in your component: 12542 * ```js 12543 * myApp.component('myComponent', { 12544 * templateUrl: 'templateId.html' 12545 * }); 12546 * ``` 12547 * 12548 * or get it via the `$templateCache` service: 12549 * ```js 12550 * $templateCache.get('templateId.html') 12551 * ``` 12552 * 12553 * See {@link ng.$cacheFactory $cacheFactory}.
vendor: 5,589 bytes, lines 12553-12682
12553 12554 * 12555 */ 12556function $TemplateCacheProvider() { 12557 this.$get = ['$cacheFactory', function($cacheFactory) { 12558 return $cacheFactory('templates'); 12559 }]; 12560} 12561 12562/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 12563 * Any commits to this file should be reviewed with security in mind. * 12564 * Changes to this file can potentially create security vulnerabilities. * 12565 * An approval from 2 Core members with history of modifying * 12566 * this file is required. * 12567 * * 12568 * Does the change somehow allow for arbitrary javascript to be executed? * 12569 * Or allows for someone to change the prototype of built-in objects? * 12570 * Or gives undesired access to variables like document or window? * 12571 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 12572 12573/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! 12574 * 12575 * DOM-related variables: 12576 * 12577 * - "node" - DOM Node 12578 * - "element" - DOM Element or Node 12579 * - "$node" or "$element" - jqLite-wrapped node or element 12580 * 12581 * 12582 * Compiler related stuff: 12583 * 12584 * - "linkFn" - linking fn of a single directive 12585 * - "nodeLinkFn" - function that aggregates all linking fns for a particular node 12586 * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node 12587 * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList) 12588 */ 12589 12590 12591/** 12592 * @ngdoc service 12593 * @name $compile 12594 * @kind function 12595 * 12596 * @description 12597 * Compiles an HTML string or DOM into a template and produces a template function, which 12598 * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together. 12599 * 12600 * The compilation is a process of walking the DOM tree and matching DOM elements to 12601 * {@link ng.$compileProvider#directive directives}. 12602 * 12603 * <div class="alert alert-warning"> 12604 * **Note:** This document is an in-depth reference of all directive options. 12605 * For a gentle introduction to directives with examples of common use cases, 12606 * see the {@link guide/directive directive guide}. 12607 * </div> 12608 * 12609 * ## Comprehensive Directive API 12610 * 12611 * There are many different options for a directive. 12612 * 12613 * The difference resides in the return value of the factory function. 12614 * You can either return a {@link $compile#directive-definition-object Directive Definition Object (see below)} 12615 * that defines the directive properties, or just the `postLink` function (all other properties will have 12616 * the default values). 12617 * 12618 * <div class="alert alert-success"> 12619 * **Best Practice:** It's recommended to use the "directive definition object" form. 12620 * </div> 12621 * 12622 * Here's an example directive declared with a Directive Definition Object: 12623 * 12624 * ```js 12625 * var myModule = angular.module(...); 12626 * 12627 * myModule.directive('directiveName', function factory(injectables) { 12628 * var directiveDefinitionObject = { 12629 * {@link $compile#-priority- priority}: 0, 12630 * {@link $compile#-template- template}: '<div></div>', // or // function(tElement, tAttrs) { ... }, 12631 * // or 12632 * // {@link $compile#-templateurl- templateUrl}: 'directive.html', // or // function(tElement, tAttrs) { ... }, 12633 * {@link $compile#-transclude- transclude}: false, 12634 * {@link $compile#-restrict- restrict}: 'A', 12635 * {@link $compile#-templatenamespace- templateNamespace}: 'html', 12636 * {@link $compile#-scope- scope}: false, 12637 * {@link $compile#-controller- controller}: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, 12638 * {@link $compile#-controlleras- controllerAs}: 'stringIdentifier', 12639 * {@link $compile#-bindtocontroller- bindToController}: false, 12640 * {@link $compile#-require- require}: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], 12641 * {@link $compile#-multielement- multiElement}: false, 12642 * {@link $compile#-compile- compile}: function compile(tElement, tAttrs, transclude) { 12643 * return { 12644 * {@link $compile#pre-linking-function pre}: function preLink(scope, iElement, iAttrs, controller) { ... }, 12645 * {@link $compile#post-linking-function post}: function postLink(scope, iElement, iAttrs, controller) { ... } 12646 * } 12647 * // or 12648 * // return function postLink( ... ) { ... } 12649 * }, 12650 * // or 12651 * // {@link $compile#-link- link}: { 12652 * // {@link $compile#pre-linking-function pre}: function preLink(scope, iElement, iAttrs, controller) { ... }, 12653 * // {@link $compile#post-linking-function post}: function postLink(scope, iElement, iAttrs, controller) { ... } 12654 * // } 12655 * // or 12656 * // {@link $compile#-link- link}: function postLink( ... ) { ... } 12657 * }; 12658 * return directiveDefinitionObject; 12659 * }); 12660 * ``` 12661 * 12662 * <div class="alert alert-warning"> 12663 * **Note:** Any unspecified options will use the default value. You can see the default values below. 12664 * </div> 12665 * 12666 * Therefore the above can be simplified as: 12667 * 12668 * ```js 12669 * var myModule = angular.module(...); 12670 * 12671 * myModule.directive('directiveName', function factory(injectables) { 12672 * var directiveDefinitionObject = { 12673 * link: function postLink(scope, iElement, iAttrs) { ... } 12674 * }; 12675 * return directiveDefinitionObject; 12676 * // or 12677 * // return function postLink(scope, iElement, iAttrs) { ... } 12678 * }); 12679 * ``` 12680 * 12681 * ### Life-cycle hooks 12682 * Directive controllers can provide the following methods that are called by Angular
12682 at points in the life-cycle of the 12683 * directive: 12684 * * `$onInit()` - Called on each controller after all the controllers on an element have been constructed and 12685 * had their bindings initialized (and before the pre & post linking functions for the directives on 12686 * this element). This is a good place to put initialization code for your controller. 12687 * * `$onChanges(changesObj)` - Called whenever one-way (`<`) or interpolation (`@`) bindings are updated. The 12688 * `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an 12689 * object of the form `{ currentValue, previousValue, isFirstChange() }`. Use this hook to trigger updates within a 12690 * component such as cloning the bound value to prevent accidental mutation of the outer value. 12691 * * `$doCheck()` - Called on each turn of the digest cycle. Provides an opportunity to detect and act on 12692 * changes. Any actions that you wish to take in response to the changes that you detect must be 12693 * invoked from this hook; implementing this has no effect on when `$onChanges` is called. For example, this hook 12694 * could be useful if you wish to perform a deep equality check, or to check a Date object, changes to which w
12694ould not 12695 * be detected by Angular's change detector and thus not trigger `$onChanges`. This hook is invoked with no arguments; 12696 * if detecting changes, you must store the previous value(s) for comparison to the current values. 12697 * * `$onDestroy()` - Called on a controller when its containing scope is destroyed. Use this hook for releasing 12698 * external resources, watches and event handlers. Note that components have their `$onDestroy()` hooks called in 12699 * the same order as the `$scope.$broadcast` events are triggered, which is top down. This means that parent 12700 * components will have their `$onDestroy()` hook called before child components. 12701 * * `$postLink()` - Called after this controller's element and its children have been linked. Similar to the post-link 12702 * function this hook can be used to set up DOM event handlers and do direct DOM manipulation. 12703 * Note that child elements that contain `templateUrl` directives will not have been compiled and linked since 12704 * they are waiting for their template to load asynchronously and their own compilation and linking has been 12705 * suspended until that occurs. 12706 * 12707 * #### Comparison with Angular 2 life-cycle hooks 12708 * Angular 2 also uses life-cycle hooks for its components. While the Angular 1 life-cycle hooks are similar there are 12709 * some differences that you should be aware of, especially when it comes to moving your co
12709de from Angular 1 to Angular 2: 12710 * 12711 * * Angular 1 hooks are prefixed with `$`, such as `$onInit`. Angular 2 hooks are prefixed with `ng`, such as `ngOnInit`. 12712 * * Angular 1 hooks can be defined on the controller prototype or added to the controller inside its constructor. 12713 * In Angular 2 you can only define hooks on the prototype of the Component class. 12714 * * Due to the differences in change-detection, you may get many more calls to `$doCheck` in Angular 1 than you would to 12715 * `ngDoCheck` in Angular 2 12716 * * Changes to the model inside `$doCheck` will trigger new turns of the digest loop, which will cause the changes to be 12717 * propagated throughout the application. 12718 * Angular 2 does not allow the `ngDoCheck` hook to trigger a change outside of the component. It will either throw an 12719 * error or do nothing depending upon the state of `enableProdMode()`. 12720 * 12721 * #### Life-cycle hook examples 12722 * 12723 * This example shows how you can check for mutations to a Date object even though the identity of the object 12724 * has not changed. 12725 * 12726 * <example name="doCheckDateExample" module="do-check-module"> 12727 * <file name="app.js"> 12728 * angular.module('do-check-module', []) 12729 * .component('app', { 12730 * template: 12731 * 'Month: <input ng-model="$ctrl.month" ng-change="$ctrl.updateDate()">' + 12732 * 'Date: {{ $ctrl.date }}' + 12733 * '<test date="$ctrl.date"></test>', 12734 * controller: function() { 12735 * this.date = new Date(); 12736 * this.month = this.date.getMonth(); 12737 * this.updateDate = function() { 12738 * this.date.setMonth(this.month); 12739 * }; 12740 * } 12741 * }) 12742 * .component('test', { 12743 * bindings: { date: '<' }, 12744 * template: 12745 * '<pre>{{ $ctrl.log | json }}</pre>', 12746 * controller: function() { 12747 * var previousValue; 12748 * this.log = []; 12749 * this.$doCheck = function() { 12750 * var currentValue = this.date && this.date.valueOf(); 12751 * if (previousValue !== currentValue) { 12752 * this.log.push('doCheck: date mutated: ' + this.date); 12753 * previousValue = currentValue; 12754 * } 12755 * }; 12756 * } 12757 * }); 12758 * </file> 12759 * <file name="index.html"> 12760 * <app></app> 12761 * </file> 12762 * </example> 12763 * 12764 * This example show how you might use `$doCheck` to trigger changes in your component's input
12764s even if the 12765 * actual identity of the component doesn't change. (Be aware that cloning and deep equality checks on large 12766 * arrays or objects can have a negative impact on your application performance) 12767 * 12768 * <example name="doCheckArrayExample" module="do-check-module"> 12769 * <file name="index.html"> 12770 * <div ng-init="items = []"> 12771 * <button ng-click="items.push(items.length)">Add Item</button> 12772 * <button ng-click="items = []">Reset Items</button> 12773 * <pre>{{ items }}</pre> 12774 * <test items="items"></test> 12775 * </div> 12776 * </file> 12777 * <file name="app.js"> 12778 * angular.module('do-check-module', []) 12779 * .component('test', { 12780 * bindings: { items: '<' }, 12781 * template: 12782 * '<pre>{{ $ctrl.log | json }}</pre>', 12783 * controller: function() { 12784 * this.log = []; 12785 * 12786 * this.$doCheck = function() { 12787 * if (this.items_ref !== this.items) { 12788 * this.log.push('doCheck: items changed'); 12789 * this.items_ref = this.items; 12790 * } 12791 * if (!angular.equals(this.items_clone, this.items)) { 12792 * this.log.push('doCheck: items mutated'); 12793 * this.items_clone = angular.copy(this.items); 12794 * } 12795 * }; 12796 * } 12797 * }); 12798 * </file> 12799 * </example> 12800 * 12801 * 12802 * ### Directive Definition Object 12803 * 12804 * The directive definition object provides instructions to the {@link ng.$compile 12805 * compiler}. The attributes are: 12806 * 12807 * #### `multiElement` 12808 * When this property is set to true (default is `false`), the HTML compiler will collect DOM nodes between 12809 * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them 12810 * together as the directive elements. It is recommended that this feature be used on directives 12811 * which are not strictly behavioral (such as {@link ngClick}), and which 12812 * do not manipulate or replace child nodes (such as {@link ngInclude}). 12813 * 12814 * #### `priority` 12815 * When there are multiple directives defined on a single DOM element, sometimes it 12816 * is necessary to specify the order in which the directives are applied. The `priority` is used 12817 * to sort the directives before their `compile` functions get called. Priority is defined as a 12818 * number. Directives with greater numerical `priority` are compiled first. Pre-link functions 12819 * are also run in priority order, but post-link functions are run in reverse order. The order 12820 * of directives with the same priority is undefined. The default priority is `0`. 12821 * 12822 * #### `terminal` 12823 * If set to true then the current `priority` will be the last set of directives 12824 * which will execute (any directives at the current priority will still execute 12825 * as the order of execution on same `priority` is undefined). Note that expressions 12826 * and other directives used in the directive's template will also be excluded from execution. 12827 * 12828 * #### `scope` 12829 * The scope property can be `false`, `true`, or an object: 12830 * 12831 * * **`false` (default):** No scope will be created for the directive. The directive will use its 12832 * parent's scope. 12833 * 12834 * * **`true`:** A new child scope that prototypically inherits from its parent will be created for 12835 * the directive's element. If multiple directives on the same element request a new scope, 12836 * only one new scope is created. 12837 * 12838 * * **`{...}` (an object hash):** A new "isolate" scope is created for the directive's element. The 12839 * 'isolate' scope differs from normal scope in that it does not prototypically inherit from its parent 12840 * scope. This is useful when creating reusable components, which should not accidentally read or modify 12841 * data in the parent scope. 12842 * 12843 * The 'isolate' scope object hash defines a set of local scope properties derived from attributes on the 12844 * directive's element. These local properties are useful for aliasing values for templates. The keys in 12845 * the object hash map to the name of the property on the isolate scope; the values define how the property 12846 * is bound to the parent scope, via matching attributes on the directive's element: 12847 * 12848 * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is
12849 * always a string since DOM attributes are strings. If no `attr` name is specified then the 12850 * attribute name is assumed to be the same as the local name. Given `<my-component 12851 * my-attr="hello {{name}}">` and the isolate scope definition `scope: { localName:'@myAttr' }`, 12852 * the directive's scope property `localName` will reflect the interpolated value of `hello 12853 * {{name}}`. As the `name` attribute changes so will the `localName` property on the directive's 12854 * scope. The `name` is read from the parent scope (not the directive's scope). 12855 * 12856 * * `=` or `=attr` - set up a bidirectional binding between a local scope property and an expression 12857 * passed via the attribute `attr`. The expression is evaluated in the context of the parent scope. 12858 * If no `attr` name is specified then the attribute name is assumed to be the same as the local 12859 * name. Given `<my-component my-attr="parentModel">` and the isolate scope definition `scope: { 12860 * localModel: '=myAttr' }`, the property `localModel` on the directive's scope will reflect the 12861 * value of `parentModel` on the parent scope. Changes to `parentModel` will be reflected in 12862 * `localModel` and vice versa. Optional attributes should be marked as such with a question mark: 12863 * `=?` or `=?attr`. If the binding expression is non-assignable, or if the attribute isn't 12864 * optional and doesn't exist, an exception ({@link error/$compile/nonassign `$compile:nonassign`}) 12865 * will be thrown upon discovering changes to the local value, since it will be impossible to sync 12866 * them back to the parent scope. By default, the {@link ng.$rootScope.Scope#$watch `$watch`} 12867 * method is used for tracking changes, and the equality check is based on object identity. 12868 * However, if an object literal or an array literal is passed as the binding expression, the 12869 * equality check is done by value (using the {@link angular.equals} function). It's also possible 12870 * to watch the evaluated value shallowly with {@link ng.$rootScope.Scope#$watchCollection 12871 * `$watchCollection`}: use `=*` or `=*attr` (`=*?` or `=*?attr` if the attribute is optional). 12872 * 12873 * * `<` or `<attr` - set up a one-way (one-directional) binding between a local scope property and an 12874 * expression passed via the attribute `attr`. The expression is evaluated in the context of the 12875 * parent scope. If no `attr` name is specified then the attribute name is assumed to be the same as the 12876 * local name. You can also make the binding optional by adding `?`: `<?` or `<?attr`. 12877 * 12878 * For example, given `<my-component my-attr="parentModel">` and directive definition of 12879 * `scope: { localModel:'<myAttr' }`, then the isolated scope property `localModel` will reflect the 12880 * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected 12881 * in `localModel`, but changes in `localModel` will not reflect in `parentModel`. There are however 12882 * two caveats: 12883 * 1. one-way binding does not copy the value from the parent to the isolate scope, it simply 12884 * sets the same value. That means if your bound value is an object, changes to its properties 12885 * in the isolated scope will be reflected in the parent scope (because both reference the same object). 12886 * 2. one-way binding watches changes to the **identity** of the parent value. That means the 12887 * {@link ng.$rootScope.Scope#$watch `$watch`} on the parent value only fires if the reference 12888 * to the value has changed. In most cases, this should not be of concern, but can be important 12889 * to know if you one-way bind to an object, and then replace that object in the isolated scope. 12890 * If you now change a property of the object in your parent scope, the change will not be 12891 * propagated to the isolated scope, because the identity of the object on the parent scope 12892 * has not changed. Instead you must assign a new object. 12893 * 12894 * One-way binding is useful if you do not plan to propagate changes to your isolated scope bindings 12895 * back to the parent. However, it does not make this completely impossible. 12896 * 12897 * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. If 12898 * no `attr` name is specified then the attribute name is assumed to be the same as the local name. 12899 * Given `<my-component my-attr="count = count + value">` and the isolate scope definition `scope: { 12900 * localFn:'&myAttr' }`, the isolate scope property `localFn` will point to a function wrapper for 12901 * the `count = count + value` expression. Often it's desirable to pass data from the isolated scope 12902 * via an expression to the parent scope. This can be done by passing a map of local variable names 12903 * and values into the expression wrapper fn. For example, if the expression is `increment(amount)` 12904 * then we can specify the amount value by calling the `localFn` as `localFn({amount: 22})`. 12905 * 12906 * In general it's possible to apply more than one directive to one element, but there might be limitations 12907 * depending on the type of scope required by the directives. The following points will help explain these limitations. 12908 * For simplicity only two directives are taken into account, but it is also applicable for several directives: 12909 * 12910 * * **no scope** + **no scope** => Two directives which don't require their own scope will use their parent's scope 12911 * * **child scope** + **no scope** => Both directives will share one single child scope 12912 * * **child scope** + **child scope** => Both directives will share one single child scope 12913 * * **isolated scope** + **no scope** => The isolated directive will use it's own created isolated scope. The other directive will use 12914 * its parent's scope 12915 * * **isolated scope** + **child scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives cannot 12916 * be applied to the same element. 12917 * * **isolated scope** + **isolated scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives 12918 * cannot be applied to the same element. 12919 * 12920 * 12921 * #### `bindToController` 12922 * This property is used to bind scope properties directly to the controller. It can be either 12923 * `true` or an object hash with the same format as the `scope` property. 12924 * 12925 * When an isolate scope is used for a directive (see above), `bindToController: true` will 12926 * allow a component to have its properties bound to the controller, rather than to scope. 12927 * 12928 * After the controller is instantiated, the initial values of the isolate scope bindings will be bound to the controller 12929 * properties. You can access these bindings once they have been initialized by providing a controller method called 12930 * `$onInit`, which is called after all the controllers on an element have been constructed and had their bindings 12931 * initialized. 12932 * 12933 * <div class="alert alert-warning"> 12934 * **Deprecation warning:** although bindings for non-ES6 class controllers are currently
12935 * bound to `this` before the controller constructor is called, this use is now deprecated. Please place initialization 12936 * code that relies upon bindings inside a `$onInit` method on the controller, instead. 12937 * </div> 12938 * 12939 * It is also possible to set `bindToController` to an object hash with the same format as the `scope` property. 12940 * This will set up the scope bindings to the controller directly. Note that `scope` can still be used 12941 * to define which kind of scope is created. By default, no scope is created. Use `scope: {}` to create an isolate 12942 * scope (useful for component directives). 12943 * 12944 * If both `bindToController` and `scope` are defined and have object hashes, `bindToController` overrides `scope`. 12945 * 12946 * 12947 * #### `controller` 12948 * Controller constructor function. The controller is instantiated before the 12949 * pre-linking phase and can be accessed by other directives (see 12950 * `require` attribute). This allows the directives to communicate with each other and augment 12951 * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals: 12952 * 12953 * * `$scope` - Current scope associated with the element 12954 * * `$element` - Current element 12955 * * `$attrs` - Current attributes object for the element 12956 * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope: 12957 * `function([scope], cloneLinkingFn, futureParentElement, slotName)`: 12958 * * `scope`: (optional) override the scope. 12959 * * `cloneLinkingFn`: (optional) argument to create clones of the original transcluded content. 12960 * * `futureParentElement` (optional): 12961 * * defines the parent to which the `cloneLinkingFn` will add the cloned elements. 12962 * * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`. 12963 * * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements) 12964 * and when the `cloneLinkingFn` is passed, 12965 * as those elements need to created and cloned in a special way when they are defined outside their 12966 * usual containers (e.g. like `<svg>`). 12967 * * See also the `directive.templateNamespace` property. 12968 * * `slotName`: (optional) the name of the slot to transclude. If falsy (e.g. `null`, `undefined` or `''`) 12969 * then the default transclusion is provided. 12970 * The `$transclude` function also has a method on it, `$transclude.isSlotFilled(slotName)`, which returns 12971 * `true` if the specified slot contains content (i.e. one or more DOM nodes). 12972 * 12973 * #### `require` 12974 * Require another directive and inject its controller as the fourth argument to the linking function. The 12975 * `require` property can be a string, an array or an object: 12976 * * a **string** containing the name of the directive to pass to the linking function 12977 * * an **array** containing the names of directives to pass to the linking function. The argument passed to the 12978 * linking function will be an array of controllers in the same order as the names in the `require` property 12979 * * an **object** whose property values are the names of the directives to pass to the linking function. The argument 12980 * passed to the linking function will also be an object with matching keys, whose values will hold the corresponding 12981 * controllers. 12982 * 12983 * If the `require` property is an object and `bindToController` is truthy, then the required controllers are 12984 * bound to the controller using the keys of the `require` property. This binding occurs after all the controllers 12985 * have been constructed but before `$onInit` is called. 12986 * If the name of the required controller is the same as the local name (the key), the name can be 12987 * omitted. For example, `{parentDir: '^^'}` is equivalent to `{parentDir: '^^parentDir'}`. 12988 * See the {@link $compileProvider#component} helper for an example of how this can be used. 12989 * If no such required directive(s) can be found, or if the directive does not have a controller, then an error is 12990 * raised (unless no link function is specified and the required controllers are not being bound to the directive 12991 * controller, in which case error checking is skipped). The name can be prefixed with: 12992 * 12993 * * (no prefix) - Locate the required controller on the current element. Throw an error if not found. 12994 * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found. 12995 * * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found. 12996 * * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found. 12997 * * `?^` - Attempt to locate the required controller by searching the element and its parents or pass 12998 * `null` to the `link` fn if not found. 12999 * * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass 13000 * `null` to the `link` fn if not found. 13001 * 13002 * 13003 * #### `controllerAs` 13004 * Identifier name for a reference to the controller in the directive's scope. 13005 * This allows the controller to be referenced from the directive template. This is especially 13006 * useful when a directive is used as component, i.e. with an `isolate` scope. It's also possible 13007 * to use it in a directive without an `isolate` / `new` scope, but you need to be aware that the 13008 * `controllerAs` reference might overwrite a property that already exists on the parent scope. 13009 * 13010 * 13011 * #### `restrict` 13012 * String of subset of `EACM` which restricts the directive to a specific directive 13013 * declaration style. If omitted, the defaults (elements and attributes) are used. 13014 * 13015 * * `E` - Element name (default): `<my-directive></my-directive>` 13016 * * `A` - Attribute (default): `<div my-directive="exp"></div>` 13017 * * `C` - Class: `<div class="my-directive: exp;"></div>` 13018 * * `M` - Comment: `<!-- directive: my-directive exp -->` 13019 * 13020 * 13021 * #### `templateNamespace` 13022 * String representing the document type used by the markup in the template. 13023 * AngularJS needs this information as those elements need to be created and cloned 13024 * in a special way when they are defined outside their usual containers like `<svg>` and `<math>`. 13025 * 13026 * * `html` - All root nodes in the template are HTML. Root nodes may also be 13027 * top-level elements such as `<svg>` or `<math>`. 13028 * * `svg` - The root nodes in the template are SVG elements (excluding `<math>`). 13029 * * `math` - The root nodes in the template are MathML elements (excluding `<svg>`). 13030 * 13031 * If no `templateNamespace` is specified, then the namespace is considered to be `html`. 13032 * 13033 * #### `template` 13034 * HTML markup that may: 13035 * * Replace the contents of the directive's element (default). 13036 * * Replace the directive's element itself (if `replace` is true - DEPRECATED). 13037 * * Wrap the contents of the directive's element (if `transclude` is true). 13038 * 13039 * Value may be: 13040 * 13041 * * A string. For example `<div red-on-hover>{{delete_str}}</div>`. 13042 * * A function which takes two arguments `tElement` and `tAttrs` (described
13042in the `compile` 13043 * function api below) and returns a string value. 13044 * 13045 * 13046 * #### `templateUrl` 13047 * This is similar to `template` but the template is loaded from the specified URL, asynchronously. 13048 * 13049 * Because template loading is asynchronous the compiler will suspend compilation of directives on that element 13050 * for later when the template has been resolved. In the meantime it will continue to compile and link 13051 * sibling and parent elements as though this element had not contained any directives. 13052 * 13053 * The compiler does not suspend the entire compilation to wait for templates to be loaded because this 13054 * would result in the whole app "stalling" until all templates are loaded asynchronously - even in the 13055 * case when only one deeply nested directive has `templateUrl`. 13056 * 13057 * Template loading is asynchronous even if the template has been preloaded into the {@link $templateCache} 13058 * 13059 * You can specify `templateUrl` as a string representing the URL or as a function which takes two 13060 * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns 13061 * a string value representing the url. In either case, the template URL is passed through {@link 13062 * $sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}. 13063 * 13064 * 13065 * #### `replace` ([*DEPRECATED*!], will be removed in next major release - i.e. v2.0) 13066 * specify what the template should replace. Defaults to `false`. 13067 * 13068 * * `true` - the template will replace the directive's element. 13069 * * `false` - the template will replace the contents of the directive's element. 13070 * 13071 * The replacement process migrates all of the attributes / classes from the old element to the new 13072 * one. See the {@link guide/directive#template-expanding-directive 13073 * Directives Guide} for an example. 13074 * 13075 * There are very few scenarios where element replacement is required for the application function, 13076 * the main one being reusable custom components that are used within SVG contexts 13077 * (because SVG doesn't work with custom elements in the DOM tree). 13078 * 13079 * #### `transclude` 13080 * Extract the contents of the element where the directive appears and make it available to the directive. 13081 * The contents are compiled and provided to the directive as a **transclusion function**. See the 13082 * {@link $compile#transclusion Transclusion} section below. 13083 * 13084 * 13085 * #### `compile` 13086 * 13087 * ```js 13088 * function compile(tElement, tAttrs, transclude) { ... } 13089 * ``` 13090 * 13091 * The compile function deals with transforming the template DOM. Since most directives do not do 13092 * template transformation, it is not used often. The compile function takes the following arguments: 13093 * 13094 * * `tElement` - template element - The element where the directive has been declared. It is 13095 * safe to do template transformation on the element and child elements only. 13096 * 13097 * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared 13098 * between all directive compile functions. 13099 * 13100 * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)` 13101 * 13102 * <div class="alert alert-warning"> 13103 * **Note:** The template instance and the link instance may be different objects if the template has 13104 * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that 13105 * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration 13106 * should be done in a linking function rather than in a compile function. 13107 * </div> 13108 13109 * <div class="alert alert-warning"> 13110 * **Note:** The compile function cannot handle directives that recursively use themselves in their 13111 * own templates or compile functions. Compiling these directives results in an infinite loop and 13112 * stack overflow errors. 13113 * 13114 * This can be avoided by manually using $compile
vendor: 5,458 bytes, lines 13114-13218
13114 in the postLink function to imperatively compile 13115 * a directive's template instead of relying on automatic template compilation via `template` or 13116 * `templateUrl` declaration or manual compilation inside the compile function. 13117 * </div> 13118 * 13119 * <div class="alert alert-danger"> 13120 * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it 13121 * e.g. does not know about the right outer scope. Please use the transclude function that is passed 13122 * to the link function instead. 13123 * </div> 13124 13125 * A compile function can have a return value which can be either a function or an object. 13126 * 13127 * * returning a (post-link) function - is equivalent to registering the linking function via the 13128 * `link` property of the config object when the compile function is empty. 13129 * 13130 * * returning an object with function(s) registered via `pre` and `post` properties - allows you to 13131 * control when a linking function should be called during the linking phase. See info about 13132 * pre-linking and post-linking functions below. 13133 * 13134 * 13135 * #### `link` 13136 * This property is used only if the `compile` property is not defined. 13137 * 13138 * ```js 13139 * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... } 13140 * ``` 13141 * 13142 * The link function is responsible for registering DOM listeners as well as updating the DOM. It is 13143 * executed after the template has been cloned. This is where most of the directive logic will be 13144 * put. 13145 * 13146 * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the 13147 * directive for registering {@link ng.$rootScope.Scope#$watch watches}. 13148 * 13149 * * `iElement` - instance element - The element where the directive is to be used. It is safe to 13150 * manipulate the children of the element only in `postLink` function since the children have 13151 * already been linked. 13152 * 13153 * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared 13154 * between all directive linking functions. 13155 * 13156 * * `controller` - the directive's required controller instance(s) - Instances are shared 13157 * among all directives, which allows the directives to use the controllers as a communication 13158 * channel. The exact value depends on the directive's `require` property: 13159 * * no controller(s) required: the directive's own controller, or `undefined` if it doesn't have one 13160 * * `string`: the controller instance 13161 * * `array`: array of controller instances 13162 * 13163 * If a required controller cannot be found, and it is optional, the instance is `null`, 13164 * otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown. 13165 * 13166 * Note that you can also require the directive's own controller - it will be made available like 13167 * any other controller. 13168 * 13169 * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope. 13170 * This is the same as the `$transclude` parameter of directive controllers, 13171 * see {@link ng.$compile#-controller- the controller section for details}. 13172 * `function([scope], cloneLinkingFn, futureParentElement)`. 13173 * 13174 * #### Pre-linking function 13175 * 13176 * Executed before the child elements are linked. Not safe to do DOM transformation since the 13177 * compiler linking function will fail to locate the correct elements for linking. 13178 * 13179 * #### Post-linking function 13180 * 13181 * Executed after the child elements are linked. 13182 * 13183 * Note that child elements that contain `templateUrl` directives will not have been compiled 13184 * and linked since they are waiting for their template to load asynchronously and their own 13185 * compilation and linking has been suspended until that occurs. 13186 * 13187 * It is safe to do DOM transformation in the post-linking function on elements that are not waiting 13188 * for their async templates to be resolved. 13189 * 13190 * 13191 * ### Transclusion 13192 * 13193 * Transclusion is the process of extracting a collection of DOM elements from one part of the DOM and 13194 * copying them to another part of the DOM, while maintaining their connection to the original AngularJS 13195 * scope from where they were taken. 13196 * 13197 * Transclusion is used (often with {@link ngTransclude}) to insert the 13198 * original contents of a directive's element into a specified place in the template of the directive. 13199 * The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded 13200 * content has access to the properties on the scope from which it was taken, even if the directive 13201 * has isolated scope. 13202 * See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}. 13203 * 13204 * This makes it possible for the widget to have private state for its template, while the transcluded 13205 * content has access to its originating scope. 13206 * 13207 * <div class="alert alert-warning"> 13208 * **Note:** When testing an element transclude directive you must not place the directive at the root of the 13209 * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives 13210 * Testing Transclusion Directives}. 13211 * </div> 13212 * 13213 * There are three kinds of transclusion depending upon whether you want to transclude just the contents of the 13214 * directive's element, the entire element or multiple parts of the element contents: 13215 * 13216 * * `true` - transclude the content (i.e. the child nodes) of the directive's element. 13217 * * `'element'` - transclude the whole of the directive's element including any directives on this 13218 * element that
13218defined at a lower priority than this directive. When used, the `template` 13219 * property is ignored. 13220 * * **`{...}` (an object hash):** - map elements of the content onto transclusion "slots" in the template. 13221 * 13222 * **Mult-slot transclusion** is declared by providing an object for the `transclude` property. 13223 * 13224 * This object is a map where the keys are the name of the slot to fill and the value is an element selector 13225 * used to match the HTML to the slot. The element selector should be in normalized form (e.g. `myElement`) 13226 * and will match the standard element variants (e.g. `my-element`, `my:element`, `data-my-element`, etc). 13227 * 13228 * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives} 13229 * 13230 * If the element selector is prefixed with a `?` then that slot is optional. 13231 * 13232 * For example, the transclude object `{ slotA: '?myCustomElement' }` maps `<my-custom-element>` elements to 13233 * the `slotA` slot, which can be accessed via the `$transclude` function or via the {@link ngTransclude} directive. 13234 * 13235 * Slots that are not marked as optional (`?`) will trigger a compile time error if there are no matching elements 13236 * in the transclude content. If you wish to know if an optional slot was filled with content, then you can call 13237 * `$transclude.isSlotFilled(slotName)` on the transclude function passed to the directive's link function and 13238 * injectable into the directive's controller. 13239 * 13240 * 13241 * #### Transclusion Functions 13242 * 13243 * When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion 13244 * function** to the directive's `link` function and `controller`. This transclusion function is a special 13245 * **linking function** that will return the compiled contents linked to a new transclusion scope. 13246 * 13247 * <div class="alert alert-info"> 13248 * If you are just using {@link ngTransclude} then you don't need to worry about this function, since 13249 * ngTransclude will deal with it for us. 13250 * </div> 13251 * 13252 * If you want to manually control the insertion and removal of the transcluded content in your directive 13253 * then you must use this transclude function. When you call a transclude function it returns a a jqLite/JQuery 13254 * object that contains the compiled DOM, which is linked to the correct transclusion scope. 13255 * 13256 * When you call a transclusion function you can pass in a **clone attach function**. This function accepts 13257 * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded 13258 * content and the `scope` is the newly created transclusion scope, which the clone will be linked to. 13259 * 13260 * <div class="alert alert-info"> 13261 * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a transclude function 13262 * since you then get a fresh clone of the original DOM and also have access to the new transclusion scope. 13263 * </div> 13264 * 13265 * It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone 13266 * attach function**: 13267 * 13268 * ```js 13269 * var transcludedContent, transclusionScope; 13270 * 13271 * $transclude(function(clone, scope) { 13272 * element.append(clone); 13273 * transcludedContent = clone; 13274 * transclusionScope = scope; 13275 * }); 13276 * ``` 13277 * 13278 * Later, if you want to remove the transcluded content from your DOM then you should also destroy the 13279 * associated transclusion scope: 13280 * 13281 * ```js 13282 * transcludedContent.remove(); 13283 * transclusionScope.$destroy(); 13284 * ``` 13285 * 13286 * <div class="alert alert-info"> 13287 * **Best Practice**: if you intend to add and remove transcluded content manually in your directive 13288 * (by calling the transclude function to get the DOM and calling `element.remove()` to remove it), 13289 * then you are also responsible for calling `$destroy` on the transclusion scope. 13290 * </div> 13291 * 13292 * The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat} 13293 * automatically destroy their transcluded clones as necessary so you do not need to worry about this if 13294 * you are simply using {@link ngTransclude} to inject the transclusion into your directive. 13295 * 13296 * 13297 * #### Transclusion Scopes 13298 * 13299 * When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion 13300 * scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed 13301 * when the directive's scope gets destroyed) but it inherits the properties of the scope from which it 13302 * was taken. 13303 * 13304 * For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look 13305 * like this: 13306 * 13307 * ```html 13308 * <div ng-app> 13309 * <div isolate> 13310 * <div transclusion>
13311 * </div> 13312 * </div> 13313 * </div> 13314 * ``` 13315 * 13316 * The `$parent` scope hierarchy will look like this: 13317 * 13318 ``` 13319 - $rootScope 13320 - isolate 13321 - transclusion 13322 ``` 13323 * 13324 * but the scopes will inherit prototypically from different scopes to their `$parent`. 13325 * 13326 ``` 13327 - $rootScope 13328 - transclusion 13329 - isolate 13330 ``` 13331 * 13332 * 13333 * ### Attributes 13334 * 13335 * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the 13336 * `link()` or `compile()` functions. It has a variety of uses. 13337 * 13338 * * *Accessing normalized attribute names:* Directives like 'ngBind' can be expressed in many ways: 13339 * 'ng:bind', `data-ng-bind`, or 'x-ng-bind'. The attributes object allows for normalized access 13340 * to the attributes. 13341 * 13342 * * *Directive inter-communication:* All directives share the same instance of the attributes 13343 * object which allows the directives to use the attributes object as inter directive 13344 * communication. 13345 * 13346 * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object 13347 * allowing other directives to read the interpolated value. 13348 * 13349 * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes 13350 * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also 13351 * the only way to easily get the actual value because during the linking phase the interpolation 13352 * hasn't been evaluated yet and so the value is at this time set to `undefined`. 13353 * 13354 * ```js 13355 * function linkingFn(scope, elm, attrs, ctrl) { 13356 * // get the attribute value 13357 * console.log(attrs.ngModel); 13358 * 13359 * // change the attribute 13360 * attrs.$set('ngModel', 'new value'); 13361 * 13362 * // observe changes to interpolated attribute 13363 * attrs.$observe('ngModel', function(value) { 13364 * console.log('ngModel has changed value to ' + value); 13365 * }); 13366 * } 13367 * ``` 13368 * 13369 * ## Example 13370 * 13371 * <div class="alert alert-warning"> 13372 * **Note**: Typically directives are registered with `module.directive`. The example below is 13373 * to illustrate how `$compile` works. 13374 * </div> 13375 * 13376 <example module="compileExample" name="compile"> 13377 <file name="index.html"> 13378 <script> 13379 angular.module('compileExample', [], function($compileProvider) { 13380 // configure new 'compile' directive by passing a directive 13381 // factory function. The factory function injects the '$compile' 13382 $compileProvider.directive('compile', function($compile) { 13383 // directive factory creates a link function 13384 return function(scope, element, attrs) { 13385 scope.$watch( 13386 function(scope) { 13387 // watch the 'compile' expression for changes 13388 return scope.$eval(attrs.compile); 13389 }, 13390 function(value) { 13391 // when the 'compile' expression changes 13392 // assign it into the current DOM 13393 element.html(value); 13394 13395 // compile the new DOM and link it to the current 13396 // scope. 13397 // NOTE: we only compile .childNodes so that 13398 // we don't get into infinite loop compiling ourselves 13399 $compile(element.contents())(scope); 13400 } 13401 ); 13402 }; 13403 }); 13404 }) 13405 .controller('GreeterController', ['$scope', function($scope) { 13406 $scope.name = 'Angular'; 13407 $scope.html = 'Hello {{name}}'; 13408 }]); 13409 </script> 13410 <div ng-controller="GreeterController"> 13411 <input ng-model="name"> <br/> 13412 <textarea ng-model="html"></textarea> <br/> 13413 <div compile="html"></div> 13414 </div> 13415 </file> 13416 <file name="protractor.js" type="protractor"> 13417 it('should auto compile', function() { 13418 var textarea = $('textarea'); 13419 var output = $('div[compile]'); 13420 // The initial state reads 'Hello Angular'. 13421 expect(output.getText()).toBe('Hello Angular'); 13422 textarea.clear(); 13423 textarea.sendKeys('{{name}}!'); 13424 expect(output.getText()).toBe('Angular!'); 13425 }); 13426 </file> 13427 </example> 13428 13429 * 13430 * 13431 * @param {string|DOMElement} element Element or HTML string to compile into a template function. 13432 * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED. 13433 * 13434 * <div class="alert alert-danger"> 13435 * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it 13436 * e.g. will not use the right outer scope. Please pass the transclude function as a 13437 * `parentBoundTranscludeFn` to the link function instead. 13438 * </div> 13439 * 13440 * @param {number} maxPriority only apply directives lower than given priority (Only effects the
13441 * root element(s), not their children) 13442 * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template 13443 * (a DOM element/tree) to a scope. Where: 13444 * 13445 * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. 13446 * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the 13447 * `template` and call the `cloneAttachFn` function allowing the caller to attach the 13448 * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is 13449 * called as: <br/> `cloneAttachFn(clonedElement, scope)` where: 13450 * 13451 * * `clonedElement` - is a clone of the original `element` passed into the compiler. 13452 * * `scope` - is the current scope with which the linking function is working with. 13453 * 13454 * * `options` - An optional object hash with linking options. If `options` is provided, then the following 13455 * keys may be used to control linking behavior: 13456 * 13457 * * `parentBoundTranscludeFn` - the transclude function made available to 13458 * directives; if given, it will be passed through to the link functions of 13459 * directives found in `element` during compilation. 13460 * * `transcludeControllers` - an object hash with keys that map controller names 13461 * to a hash with the key `instance`, which maps to the controller instance; 13462 * if given, it will make the controllers available to directives on the compileNode: 13463 * ``` 13464 * { 13465 * parent: { 13466 * instance: parentControllerInstance 13467 * } 13468 * } 13469 * ``` 13470 * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add 13471 * the cloned elements; only needed for transcludes that are allowed to contain non html 13472 * elements (e.g. SVG elements). See also the directive.controller property. 13473 * 13474 * Calling the linking function returns the element of the template. It is either the original 13475 * element passed in, or the clone of the element if the `cloneAttachFn` is provided. 13476 * 13477 * After linking the view is not updated until after a call to $digest which typically is done by 13478 * Angular automatically. 13479 * 13480 * If you need access to the bound view, there are two ways to do it: 13481 * 13482 * - If you are not asking the linking function to clone the template, create the DOM element(s) 13483 * before you send them to the compiler and keep this reference around. 13484 * ```js 13485 * var element = $compile('<p>{{total}}</p>')(scope); 13486 * ``` 13487 * 13488 * - if on the other hand, you need the element to be cloned, the view reference from the original 13489 * example would not point to the clone, but rather to the original template that was cloned. In 13490 * this case, you can access the clone via the cloneAttachFn: 13491 * ```js 13492 * var templateElement = angular.element('<p>{{total}}</p>'), 13493 * scope = ....; 13494 * 13495 * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) { 13496 * //attach the clone to DOM document at the right place 13497 * }); 13498 * 13499 * //now we have reference to the cloned DOM via `clonedElement` 13500 * ``` 13501 * 13502 * 13503 * For information on how the compiler works, see the 13504 * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide. 13505 * 13506 * @knownIssue 13507 * 13508 * ### Double Compilation 13509 * 13510 Double compilation occurs when an already compiled part of the DOM gets 13511 compiled again. This is an undesired effect and can lead to misbehaving directives, performance issues, 13512 and memory leaks. Refer to the Compiler Guide {@link guide/compiler#double-compilation-and-how-to-avoid-it 13513 section on double compilation} for an in-depth explanation and ways to avoid it. 13514 * 13515 */ 13516 13517var $compileMinErr = minErr('$compile'); 13518 13519function UNINITIALIZED_VALUE() {} 13520var _UNINITIALIZED_VALUE = new UNINITIALIZED_VALUE(); 13521 13522/** 13523 * @ngdoc provider 13524 * @name $compileProvider 13525 * 13526 * @description 13527 */
vendor: 15,193 bytes, lines 13528-13895
13528$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider']; 13529/** @this */ 13530function $CompileProvider($provide, $$sanitizeUriProvider) { 13531 var hasDirectives = {}, 13532 Suffix = 'Directive', 13533 COMMENT_DIRECTIVE_REGEXP = /^\s*directive:\s*([\w-]+)\s+(.*)$/, 13534 CLASS_DIRECTIVE_REGEXP = /(([\w-]+)(?::([^;]+))?;?)/, 13535 ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'), 13536 REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/; 13537 13538 // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes 13539 // The assumption is that future DOM event attribute names will begin with 13540 // 'on' and be composed of only English letters. 13541 var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; 13542 var bindingCache = createMap(); 13543 13544 function parseIsolateBindings(scope, directiveName, isController) { 13545 var LOCAL_REGEXP = /^\s*([@&<]|=(\*?))(\??)\s*([\w$]*)\s*$/; 13546 13547 var bindings = createMap(); 13548 13549 forEach(scope, function(definition, scopeName) { 13550 if (definition in bindingCache) { 13551 bindings[scopeName] = bindingCache[definition]; 13552 return; 13553 } 13554 var match = definition.match(LOCAL_REGEXP); 13555 13556 if (!match) { 13557 throw $compileMinErr('iscp', 13558 'Invalid {3} for directive \'{0}\'.' + 13559 ' Definition: {... {1}: \'{2}\' ...}', 13560 directiveName, scopeName, definition, 13561 (isController ? 'controller bindings definition' : 13562 'isolate scope definition')); 13563 } 13564 13565 bindings[scopeName] = { 13566 mode: match[1][0], 13567 collection: match[2] === '*', 13568 optional: match[3] === '?', 13569 attrName: match[4] || scopeName 13570 }; 13571 if (match[4]) { 13572 bindingCache[definition] = bindings[scopeName]; 13573 } 13574 }); 13575 13576 return bindings; 13577 } 13578 13579 function parseDirectiveBindings(directive, directiveName) { 13580 var bindings = { 13581 isolateScope: null, 13582 bindToController: null 13583 }; 13584 if (isObject(directive.scope)) { 13585 if (directive.bindToController === true) { 13586 bindings.bindToController = parseIsolateBindings(directive.scope, 13587 directiveName, true); 13588 bindings.isolateScope = {}; 13589 } else { 13590 bindings.isolateScope = parseIsolateBindings(directive.scope, 13591 directiveName, false); 13592 } 13593 } 13594 if (isObject(directive.bindToController)) { 13595 bindings.bindToController = 13596 parseIsolateBindings(directive.bindToController, directiveName, true); 13597 } 13598 if (bindings.bindToController && !directive.controller) { 13599 // There is no controller 13600 throw $compileMinErr('noctrl', 13601 'Cannot bind to controller without directive \'{0}\'s controller.', 13602 directiveName); 13603 } 13604 return bindings; 13605 } 13606 13607 function assertValidDirectiveName(name) { 13608 var letter = name.charAt(0); 13609 if (!letter || letter !== lowercase(letter)) { 13610 throw $compileMinErr('baddir', 'Directive/Component name \'{0}\' is invalid. The first character must be a lowercase letter', name); 13611 } 13612 if (name !== name.trim()) { 13613 throw $compileMinErr('baddir', 13614 'Directive/Component name \'{0}\' is invalid. The name should not contain leading or trailing whitespaces', 13615 name); 13616 } 13617 } 13618 13619 function getDirectiveRequire(directive) { 13620 var require = directive.require || (directive.controller && directive.name); 13621 13622 if (!isArray(require) && isObject(require)) { 13623 forEach(require, function(value, key) { 13624 var match = value.match(REQUIRE_PREFIX_REGEXP); 13625 var name = value.substring(match[0].length); 13626 if (!name) require[key] = match[0] + key; 13627 }); 13628 } 13629 13630 return require; 13631 } 13632 13633 function getDirectiveRestrict(restrict, name) { 13634 if (restrict && !(isString(restrict) && /[EACM]/.test(restrict))) { 13635 throw $compileMinErr('badrestrict', 13636 'Restrict property \'{0}\' of directive \'{1}\' is invalid', 13637 restrict, 13638 name); 13639 } 13640 13641 return restrict || 'EA'; 13642 } 13643 13644 /** 13645 * @ngdoc method 13646 * @name $compileProvider#directive 13647 * @kind function 13648 * 13649 * @description 13650 * Register a new directive with the compiler. 13651 * 13652 * @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which 13653 * will match as <code>ng-bind</code>), or an object map of directives where the keys are the 13654 * names and the values are the factories. 13655 * @param {Function|Array} directiveFactory An injectable directive factory function. See the 13656 * {@link guide/directive directive guide} and the {@link $compile compile API} for more info. 13657 * @returns {ng.$compileProvider} Self for chaining. 13658 */ 13659 this.directive = function registerDirective(name, directiveFactory) { 13660 assertArg(name, 'name'); 13661 assertNotHasOwnProperty(name, 'directive'); 13662 if (isString(name)) { 13663 assertValidDirectiveName(name); 13664 assertArg(directiveFactory, 'directiveFactory'); 13665 if (!hasDirectives.hasOwnProperty(name)) { 13666 hasDirectives[name] = []; 13667 $provide.factory(name + Suffix, ['$injector', '$exceptionHandler', 13668 function($injector, $exceptionHandler) { 13669 var directives = []; 13670 forEach(hasDirectives[name], function(directiveFactory, index) { 13671 try { 13672 var directive = $injector.invoke(directiveFactory); 13673 if (isFunction(directive)) { 13674 directive = { compile: valueFn(directive) }; 13675 } else if (!directive.compile && directive.link) { 13676 directive.compile = valueFn(directive.link); 13677 } 13678 directive.priority = directive.priority || 0; 13679 directive.index = index; 13680 directive.name = directive.name || name; 13681 directive.require = getDirectiveRequire(directive); 13682 directive.restrict = getDirectiveRestrict(directive.restrict, name); 13683 directive.$$moduleName = directiveFactory.$$moduleName; 13684 directives.push(directive); 13685 } catch (e) { 13686 $exceptionHandler(e); 13687 } 13688 }); 13689 return directives; 13690 }]); 13691 } 13692 hasDirectives[name].push(directiveFactory); 13693 } else { 13694 forEach(name, reverseParams(registerDirective)); 13695 } 13696 return this; 13697 }; 13698 13699 /** 13700 * @ngdoc method 13701 * @name $compileProvider#component 13702 * @module ng 13703 * @param {string} name Name of the component in camelCase (i.e. `myComp` which will match `<my-comp>`) 13704 * @param {Object} options Component definition object (a simplified 13705 * {@link ng.$compile#directive-definition-object directive definition object}), 13706 * with the following properties (all optional): 13707 * 13708 * - `controller` â `{(string|function()=}` â controller constructor function that should be 13709 * associated with newly created scope or the name of a {@link ng.$compile#-controller- 13710 * registered controller} if passed as a string. An empty `noop` function by default. 13711 * - `controllerAs` â `{string=}` â identifier name for to reference the controller in the component's scope. 13712 * If present, the controller will be published to scope under the `controllerAs` name. 13713 * If not present, this will default to be `$ctrl`. 13714 * - `template` â `{string=|function()=}` â html template as a string or a function that 13715 * returns an html template as a string which should be used as the contents of this component. 13716 * Empty string by default. 13717 * 13718 * If `template` is a function, then it is {@link auto.$injector#invoke injected} with 13719 * the following locals: 13720 * 13721 * - `$element` - Current element 13722 * - `$attrs` - Current attributes object for the element 13723 * 13724 * - `templateUrl` â `{string=|function()=}` â path or function that returns a path to an html 13725 * template that should be used as the contents of this component. 13726 * 13727 * If `templateUrl` is a function, then it is {@link auto.$injector#invoke injected} with 13728 * the following locals: 13729 * 13730 * - `$element` - Current element 13731 * - `$attrs` - Current attributes object for the element 13732 * 13733 * - `bindings` â `{object=}` â defines bindings between DOM attributes and component properties. 13734 * Component properties are always bound to the component controller and not to the scope. 13735 * See {@link ng.$compile#-bindtocontroller- `bindToController`}. 13736 * - `transclude` â `{boolean=}` â whether {@link $compile#transclusion content transclusion} is enabled. 13737 * Disabled by default. 13738 * - `require` - `{Object<string, string>=}` - requires the controllers of other directives and binds them to 13739 * this component's controller. The object keys specify the property names under which the required 13740 * controllers (object values) will be bound. See {@link ng.$compile#-require- `require`}. 13741 * - `$...` â additional properties to attach to the directive factory function and the controller 13742 * constructor function. (This is used by the component router to annotate) 13743 * 13744 * @returns {ng.$compileProvider} the compile provider itself, for chaining of function calls. 13745 * @description 13746 * Register a **component definition** with the compiler. This is a shorthand for registering a special 13747 * type of directive, which represents a self-contained UI component in your application. Such components 13748 * are always isolated (i.e. `scope: {}`) and are always restricted to elements (i.e. `restrict: 'E'`). 13749 * 13750 * Component definitions are very simple and do not require as much configuration as defining general 13751 * directives. Component definitions usually consist only of a template and a controller backing it. 13752 * 13753 * In order to make the definition easier, components enforce best practices like use of `controllerAs`, 13754 * `bindToController`. They always have **isolate scope** and are restricted to elements. 13755 * 13756 * Here are a few examples of how you would usually define components: 13757 * 13758 * ```js 13759 * var myMod = angular.module(...); 13760 * myMod.component('myComp', { 13761 * template: '<div>My name is {{$ctrl.name}}</div>', 13762 * controller: function() { 13763 * this.name = 'shahar'; 13764 * } 13765 * }); 13766 * 13767 * myMod.component('myComp', { 13768 * template: '<div>My name is {{$ctrl.name}}</div>', 13769 * bindings: {name: '@'} 13770 * }); 13771 * 13772 * myMod.component('myComp', { 13773 * templateUrl: 'views/my-comp.html', 13774 * controller: 'MyCtrl', 13775 * controllerAs: 'ctrl', 13776 * bindings: {name: '@'} 13777 * }); 13778 * 13779 * ``` 13780 * For more examples, and an in-depth guide, see the {@link guide/component component guide}. 13781 * 13782 * <br /> 13783 * See also {@link ng.$compileProvider#directive $compileProvider.directive()}. 13784 */ 13785 this.component = function registerComponent(name, options) { 13786 var controller = options.controller || function() {}; 13787 13788 function factory($injector) { 13789 function makeInjectable(fn) { 13790 if (isFunction(fn) || isArray(fn)) { 13791 return /** @this */ function(tElement, tAttrs) { 13792 return $injector.invoke(fn, this, {$element: tElement, $attrs: tAttrs}); 13793 }; 13794 } else { 13795 return fn; 13796 } 13797 } 13798 13799 var template = (!options.template && !options.templateUrl ? '' : options.template); 13800 var ddo = { 13801 controller: controller, 13802 controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl', 13803 template: makeInjectable(template), 13804 templateUrl: makeInjectable(options.templateUrl), 13805 transclude: options.transclude, 13806 scope: {}, 13807 bindToController: options.bindings || {}, 13808 restrict: 'E', 13809 require: options.require 13810 }; 13811 13812 // Copy annotations (starting with $) over to the DDO 13813 forEach(options, function(val, key) { 13814 if (key.charAt(0) === '$') ddo[key] = val; 13815 }); 13816 13817 return ddo; 13818 } 13819 13820 // TODO(pete) remove the following `forEach` before we release 1.6.0 13821 // The [email protected] looks for the annotations on the controller constructor 13822 // Nothing in Angular looks for annotations on the factory function but we can't remove 13823 // it from 1.5.x yet. 13824 13825 // Copy any annotation properties (starting with $) over to the factory and controller constructor functions 13826 // These could be used by libraries such as the new component router 13827 forEach(options, function(val, key) { 13828 if (key.charAt(0) === '$') { 13829 factory[key] = val; 13830 // Don't try to copy over annotations to named controller 13831 if (isFunction(controller)) controller[key] = val; 13832 } 13833 }); 13834 13835 factory.$inject = ['$injector']; 13836 13837 return this.directive(name, factory); 13838 }; 13839 13840 13841 /** 13842 * @ngdoc method 13843 * @name $compileProvider#aHrefSanitizationWhitelist 13844 * @kind function 13845 * 13846 * @description 13847 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 13848 * urls during a[href] sanitization. 13849 * 13850 * The sanitization is a security measure aimed at preventing XSS attacks via html links. 13851 * 13852 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 13853 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 13854 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 13855 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 13856 * 13857 * @param {RegExp=} regexp New regexp to whitelist urls with. 13858 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 13859 * chaining otherwise. 13860 */ 13861 this.aHrefSanitizationWhitelist = function(regexp) { 13862 if (isDefined(regexp)) { 13863 $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp); 13864 return this; 13865 } else { 13866 return $$sanitizeUriProvider.aHrefSanitizationWhitelist(); 13867 } 13868 }; 13869 13870 13871 /** 13872 * @ngdoc method 13873 * @name $compileProvider#imgSrcSanitizationWhitelist 13874 * @kind function 13875 * 13876 * @description 13877 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 13878 * urls during img[src] sanitization. 13879 * 13880 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 13881 * 13882 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 13883 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 13884 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 13885 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 13886 * 13887 * @param {RegExp=} regexp New regexp to whitelist urls with. 13888 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 13889 * chaining otherwise. 13890 */ 13891 this.imgSrcSanitizationWhitelist = function(regexp) { 13892 if (isDefined(regexp)) { 13893 $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp); 13894 return this; 13895 } else {
13896 return $$sanitizeUriProvider.imgSrcSanitizationWhitelist(); 13897 } 13898 }; 13899 13900 /** 13901 * @ngdoc method 13902 * @name $compileProvider#debugInfoEnabled 13903 * 13904 * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the 13905 * current debugInfoEnabled state 13906 * @returns {*} current value if used as getter or itself (chaining) if used as setter 13907 * 13908 * @kind function 13909 * 13910 * @description 13911 * Call this method to enable/disable various debug runtime information in the compiler such as adding 13912 * binding information and a reference to the current scope on to DOM elements. 13913 * If enabled, the compiler will add the following to DOM elements that have been bound to the scope 13914 * * `ng-binding` CSS class 13915 * * `$binding` data property containing an array of the binding expressions 13916 * 13917 * You may want to disable this in production for a significant performance boost. See 13918 * {@link guide/production#disabling-debug-data Disabling Debug Data} for more. 13919 * 13920 * The default value is true. 13921 */ 13922 var debugInfoEnabled = true; 13923 this.debugInfoEnabled = function(enabled) { 13924 if (isDefined(enabled)) { 13925 debugInfoEnabled = enabled; 13926 return this; 13927 } 13928 return debugInfoEnabled; 13929 }; 13930 13931 /** 13932 * @ngdoc method 13933 * @name $compileProvider#preAssignBindingsEnabled 13934 * 13935 * @param {boolean=} enabled update the preAssignBindingsEnabled state if provided, otherwise just return the 13936 * current preAssignBindingsEnabled state 13937 * @returns {*} current value if used as getter or itself (chaining) if used as setter 13938 * 13939 * @kind function 13940 * 13941 * @description 13942 * Call this method to enable/disable whether directive controllers are assigned bindings before 13943 * calling the controller's constructor. 13944 * If enabled (true), the compiler assigns the value of each of the bindings to the 13945 * properties of the controller object before the constructor of this object is called. 13946 * 13947 * If disabled (false), the compiler calls the constructor first before assigning bindings. 13948 * 13949 * The default value is true in Angular 1.5.x but will switch to false in Angular 1.6.x. 13950 */ 13951 var preAssignBindingsEnabled = true; 13952 this.preAssignBindingsEnabled = function(enabled) { 13953 if (isDefined(enabled)) { 13954 preAssignBindingsEnabled = enabled; 13955 return this; 13956 } 13957 return preAssignBindingsEnabled; 13958 }; 13959 13960 13961 var TTL = 10; 13962 /** 13963 * @ngdoc method 13964 * @name $compileProvider#onChangesTtl 13965 * @description 13966 * 13967 * Sets the number of times `$onChanges` hooks can trigger new changes before giving up and 13968 * assuming that the model is unstable. 13969 * 13970 * The current default is 10 iterations. 13971 * 13972 * In complex applications it's possible that dependencies between `$onChanges` hooks and bindings will result 13973 * in several iterations of calls to these hooks. However if an application needs more than the default 10 13974 * iterations to stabilize then you should investigate what is causing the model to continuously change during 13975 * the `$onChanges` hook execution. 13976 * 13977 * Increasing the TTL could have performance implications, so you should not change it without proper justification. 13978 * 13979 * @param {number} limit The number of `$onChanges` hook iterations. 13980 * @returns {number|object} the current limit (or `this` if called as a setter for chaining) 13981 */ 13982 this.onChangesTtl = function(value) { 13983 if (arguments.length) { 13984 TTL = value; 13985 return this; 13986 } 13987 return TTL; 13988 }; 13989 13990 var commentDirectivesEnabledConfig = true; 13991 /** 13992 * @ngdoc method 13993 * @name $compileProvider#commentDirectivesEnabled 13994 * @description 13995 * 13996 * It indicates to the compiler 13997 * whether or not directives on comments should be compiled. 13998 * Defaults to `true`. 13999 * 14000 * Calling this function with false disables the compilation of directives 14001 * on comments for the whole application. 14002 * This results in a compilation performance gain, 14003 * as the compiler doesn't have to check comments when looking for directives. 14004 * This should however only be used if you are sure that no comment directives are used in 14005 * the application (including any 3rd party directives). 14006 * 14007 * @param {boolean} enabled `false` if the compiler may ignore directives on comments 14008 * @returns {boolean|object} the current value (or `this` if called as a setter for chaining) 14009 */ 14010 this.commentDirectivesEnabled = function(value) { 14011 if (arguments.length) { 14012 commentDirectivesEnabledConfig = value; 14013 return this; 14014 } 14015 return commentDirectivesEnabledConfig; 14016 }; 14017 14018 14019 var cssClassDirectivesEnabledConfig = true; 14020 /** 14021 * @ngdoc method 14022 * @name $compileProvider#cssClassDirectivesEnabled 14023 * @description 14024 * 14025 * It indicates to the compiler 14026 * whether or not directives on element classes should be compiled. 14027 * Defaults to `true`. 14028 *
14029 * Calling this function with false disables the compilation of directives 14030 * on element classes for the whole application. 14031 * This results in a compilation performance gain, 14032 * as the compiler doesn't have to check element classes when looking for directives. 14033 * This should however only be used if you are sure that no class directives are used in 14034 * the application (including any 3rd party directives). 14035 * 14036 * @param {boolean} enabled `false` if the compiler may ignore directives on element classes 14037 * @returns {boolean|object} the current value (or `this` if called as a setter for chaining) 14038 */ 14039 this.cssClassDirectivesEnabled = function(value) { 14040 if (arguments.length) { 14041 cssClassDirectivesEnabledConfig = value; 14042 return this; 14043 } 14044 return cssClassDirectivesEnabledConfig; 14045 }; 14046 14047 this.$get = [ 14048 '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse', 14049 '$controller', '$rootScope', '$sce', '$animate', '$$sanitizeUri', 14050 function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse, 14051 $controller, $rootScope, $sce, $animate, $$sanitizeUri) { 14052 14053 var SIMPLE_ATTR_NAME = /^\w/; 14054 var specialAttrHolder = window.document.createElement('div'); 14055 14056 14057 var commentDirectivesEnabled = commentDirectivesEnabledConfig; 14058 var cssClassDirectivesEnabled = cssClassDirectivesEnabledConfig; 14059 14060 14061 var onChangesTtl = TTL; 14062 // The onChanges hooks should all be run together in a single digest 14063 // When changes occur, the call to trigger their hooks will be added to this queue 14064 var onChangesQueue; 14065 14066 // This function is called in a $$postDigest to trigger all the onChanges hooks in a single digest 14067 function flushOnChangesQueue() { 14068 try { 14069 if (!(--onChangesTtl)) { 14070 // We have hit the TTL limit so reset everything 14071 onChangesQueue = undefined; 14072 throw $compileMinErr('infchng', '{0} $onChanges() iterations reached. Aborting!\n', TTL); 14073 } 14074 // We must run this hook in an apply since the $$postDigest runs outside apply 14075 $rootScope.$apply(function() { 14076 var errors = []; 14077 for (var i = 0, ii = onChangesQueue.length; i < ii; ++i) { 14078 try { 14079 onChangesQueue[i](); 14080 } catch (e) { 14081 errors.push(e); 14082 } 14083 } 14084 // Reset the queue to trigger a new schedule next time there is a change 14085 onChangesQueue = undefined; 14086 if (errors.length) { 14087 throw errors; 14088 } 14089 }); 14090 } finally { 14091 onChangesTtl++; 14092 } 14093 } 14094 14095 14096 function Attributes(element, attributesToCopy) { 14097 if (attributesToCopy) { 14098 var keys = Object.keys(attributesToCopy); 14099 var i, l, key; 14100 14101 for (i = 0, l = keys.length; i < l; i++) { 14102 key = keys[i]; 14103 this[key] = attributesToCopy[key]; 14104 } 14105 } else { 14106 this.$attr = {}; 14107 } 14108 14109 this.$$element = element; 14110 } 14111 14112 Attributes.prototype = { 14113 /** 14114 * @ngdoc method 14115 * @name $compile.directive.Attributes#$normalize 14116 * @kind function 14117 * 14118 * @description 14119 * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or 14120 * `data-`) to its normalized, camelCase form. 14121 * 14122 * Also there is special case for Moz prefix starting with upper case letter. 14123 * 14124 * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives} 14125 * 14126 * @param {string} name Name to normalize 14127 */ 14128 $normalize: directiveNormalize, 14129 14130 14131 /** 14132 * @ngdoc method 14133 * @name $compile.directive.Attributes#$addClass 14134 * @kind function 14135 * 14136 * @description 14137 * Adds the CSS class value specified by the classVal parameter to the element. If animations 14138 * are enabled then an animation will be triggered for the class addition. 14139 * 14140 * @param {string} classVal The className value that will be added to the element 14141 */ 14142 $addClass: function(classVal) { 14143 if (classVal && classVal.length > 0) { 14144 $animate.addClass(this.$$element, classVal); 14145 } 14146 }, 14147 14148 /** 14149 * @ngdoc method 14150 * @name $compile.directive.Attributes#$removeClass 14151 * @kind function 14152 *
14153 * @description 14154 * Removes the CSS class value specified by the classVal parameter from the element. If 14155 * animations are enabled then an animation will be triggered for the class removal. 14156 * 14157 * @param {string} classVal The className value that will be removed from the element 14158 */ 14159 $removeClass: function(classVal) { 14160 if (classVal && classVal.length > 0) { 14161 $animate.removeClass(this.$$element, classVal); 14162 } 14163 }, 14164 14165 /** 14166 * @ngdoc method 14167 * @name $compile.directive.Attributes#$updateClass 14168 * @kind function 14169 * 14170 * @description 14171 * Adds and removes the appropriate CSS class values to the element based on the difference 14172 * between the new and old CSS class values (specified as newClasses and oldClasses). 14173 * 14174 * @param {string} newClasses The current CSS className value 14175 * @param {string} oldClasses The former CSS className value 14176 */ 14177 $updateClass: function(newClasses, oldClasses) { 14178 var toAdd = tokenDifference(newClasses, oldClasses); 14179 if (toAdd && toAdd.length) { 14180 $animate.addClass(this.$$element, toAdd); 14181 } 14182 14183 var toRemove = tokenDifference(oldClasses, newClasses); 14184 if (toRemove && toRemove.length) { 14185 $animate.removeClass(this.$$element, toRemove); 14186 } 14187 }, 14188 14189 /** 14190 * Set a normalized attribute on the element in a way such that all directives 14191 * can share the attribute. This function properly handles boolean attributes. 14192 * @param {string} key Normalized key. (ie ngAttribute) 14193 * @param {string|boolean} value The value to set. If `null` attribute will be deleted. 14194 * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. 14195 * Defaults to true. 14196 * @param {string=} attrName Optional none normalized name. Defaults to key. 14197 */ 14198 $set: function(key, value, writeAttr, attrName) { 14199 // TODO: decide whether or not to throw an error if "class" 14200 //is set through this function since it may cause $updateClass to 14201 //become unstable. 14202 14203 var node = this.$$element[0], 14204 booleanKey = getBooleanAttrName(node, key), 14205 aliasedKey = getAliasedAttrName(key), 14206 observer = key, 14207 nodeName; 14208 14209 if (booleanKey) { 14210 this.$$element.prop(key, value); 14211 attrName = booleanKey; 14212 } else if (aliasedKey) { 14213 this[aliasedKey] = value; 14214 observer = aliasedKey; 14215 } 14216 14217 this[key] = value; 14218 14219 // translate normalized key to actual key 14220 if (attrName) { 14221 this.$attr[key] = attrName; 14222 } else { 14223 attrName = this.$attr[key]; 14224 if (!attrName) { 14225 this.$attr[key] = attrName = snake_case(key, '-'); 14226 } 14227 } 14228 14229 nodeName = nodeName_(this.$$element); 14230 14231 if ((nodeName === 'a' && (key === 'href' || key === 'xlinkHref')) || 14232 (nodeName === 'img' && key === 'src')) { 14233 // sanitize a[href] and img[src] values 14234 this[key] = value = $$sanitizeUri(value, key === 'src'); 14235 } else if (nodeName === 'img' && key === 'srcset' && isDefined(value)) { 14236 // sanitize img[srcset] values 14237 var result = ''; 14238 14239 // first check if there are spaces because it's not the same pattern 14240 var trimmedSrcset = trim(value); 14241 // ( 999x ,| 999w ,| ,|, ) 14242 var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/; 14243 var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/; 14244 14245 // split srcset into tuple of uri and descriptor except for the last item 14246 var rawUris = trimmedSrcset.split(pattern); 14247 14248 // for each tuples 14249 var nbrUrisWith2parts = Math.floor(rawUris.length / 2); 14250 for (var i = 0; i < nbrUrisWith2parts; i++) { 14251 var innerIdx = i * 2; 14252 // sanitize the uri 14253 result += $$sanitizeUri(trim(rawUris[innerIdx]), true); 14254 // add the descriptor 14255 result += (' ' + trim(rawUris[innerIdx + 1])); 14256 } 14257 14258 // split the last item into uri and descriptor 14259 var lastTuple = trim(rawUris[i * 2]).split(/\s/); 14260 14261 // sanitize the last uri 14262 result += $$sanitizeUri(trim(lastTuple[0]), true); 14263 14264 // and add the last descriptor if any 14265 if (lastTuple.length === 2) { 14266 result += (' ' + trim(lastTuple[1])); 14267 } 14268 this[key] = value = result; 14269 } 14270 14271 if (writeAttr !== false) { 14272 if (value === null || isUndefined(value)) { 14273 this.$$element.removeAttr(attrName); 14274 } else { 14275 if (SIMPLE_ATTR_NAME.test(attrName)) { 14276 this.$$element.attr(attrName, value); 14277 } else { 14278 setSpecialAttr(this.$$element[0], attrName, value); 14279 } 14280 } 14281 } 14282 14283 // fire observers 14284 var $$observers = this.$$observers; 14285 if ($$observers) {
vendor: 29,459 bytes, lines 14286-14985
14286 forEach($$observers[observer], function(fn) { 14287 try { 14288 fn(value); 14289 } catch (e) { 14290 $exceptionHandler(e); 14291 } 14292 }); 14293 } 14294 }, 14295 14296 14297 /** 14298 * @ngdoc method 14299 * @name $compile.directive.Attributes#$observe 14300 * @kind function 14301 * 14302 * @description 14303 * Observes an interpolated attribute. 14304 * 14305 * The observer function will be invoked once during the next `$digest` following 14306 * compilation. The observer is then invoked whenever the interpolated value 14307 * changes. 14308 * 14309 * @param {string} key Normalized key. (ie ngAttribute) . 14310 * @param {function(interpolatedValue)} fn Function that will be called whenever 14311 the interpolated value of the attribute changes. 14312 * See the {@link guide/interpolation#how-text-and-attribute-bindings-work Interpolation 14313 * guide} for more info. 14314 * @returns {function()} Returns a deregistration function for this observer. 14315 */ 14316 $observe: function(key, fn) { 14317 var attrs = this, 14318 $$observers = (attrs.$$observers || (attrs.$$observers = createMap())), 14319 listeners = ($$observers[key] || ($$observers[key] = [])); 14320 14321 listeners.push(fn); 14322 $rootScope.$evalAsync(function() { 14323 if (!listeners.$$inter && attrs.hasOwnProperty(key) && !isUndefined(attrs[key])) { 14324 // no one registered attribute interpolation function, so lets call it manually 14325 fn(attrs[key]); 14326 } 14327 }); 14328 14329 return function() { 14330 arrayRemove(listeners, fn); 14331 }; 14332 } 14333 }; 14334 14335 function setSpecialAttr(element, attrName, value) { 14336 // Attributes names that do not start with letters (such as `(click)`) cannot be set using `setAttribute` 14337 // so we have to jump through some hoops to get such an attribute 14338 // https://github.com/angular/angular.js/pull/13318 14339 specialAttrHolder.innerHTML = '<span ' + attrName + '>'; 14340 var attributes = specialAttrHolder.firstChild.attributes; 14341 var attribute = attributes[0]; 14342 // We have to remove the attribute from its container element before we can add it to the destination element 14343 attributes.removeNamedItem(attribute.name); 14344 attribute.value = value; 14345 element.attributes.setNamedItem(attribute); 14346 } 14347 14348 function safeAddClass($element, className) { 14349 try { 14350 $element.addClass(className); 14351 } catch (e) { 14352 // ignore, since it means that we are trying to set class on 14353 // SVG element, where class name is read-only. 14354 } 14355 } 14356 14357 14358 var startSymbol = $interpolate.startSymbol(), 14359 endSymbol = $interpolate.endSymbol(), 14360 denormalizeTemplate = (startSymbol === '{{' && endSymbol === '}}') 14361 ? identity 14362 : function denormalizeTemplate(template) { 14363 return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); 14364 }, 14365 NG_ATTR_BINDING = /^ngAttr[A-Z]/; 14366 var MULTI_ELEMENT_DIR_RE = /^(.+)Start$/; 14367 14368 compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) { 14369 var bindings = $element.data('$binding') || []; 14370 14371 if (isArray(binding)) { 14372 bindings = bindings.concat(binding); 14373 } else { 14374 bindings.push(binding); 14375 } 14376 14377 $element.data('$binding', bindings); 14378 } : noop; 14379 14380 compile.$$addBindingClass = debugInfoEnabled ? function $$addBindingClass($element) { 14381 safeAddClass($element, 'ng-binding'); 14382 } : noop; 14383 14384 compile.$$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo($element, scope, isolated, noTemplate) { 14385 var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope'; 14386 $element.data(dataName, scope); 14387 } : noop; 14388 14389 compile.$$addScopeClass = debugInfoEnabled ? function $$addScopeClass($element, isolated) { 14390 safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope'); 14391 } : noop; 14392 14393 compile.$$createComment = function(directiveName, comment) { 14394 var content = ''; 14395 if (debugInfoEnabled) { 14396 content = ' ' + (directiveName || '') + ': '; 14397 if (comment) content += comment + ' '; 14398 } 14399 return window.document.createComment(content); 14400 }; 14401 14402 return compile; 14403 14404 //================================ 14405 14406 function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, 14407 previousCompileContext) { 14408 if (!($compileNodes instanceof jqLite)) { 14409 // jquery always rewraps, whereas we need to preserve the original selector so that we can 14410 // modify it. 14411 $compileNodes = jqLite($compileNodes); 14412 } 14413 14414 var NOT_EMPTY = /\S+/; 14415 14416 // We can not compile top level text elements since text nodes can be merged and we will 14417 // not be able to attach scope data to them, so we will wrap them in <span> 14418 for (var i = 0, len = $compileNodes.length; i < len; i++) { 14419 var domNode = $compileNodes[i]; 14420 14421 if (domNode.nodeType === NODE_TYPE_TEXT && domNode.nodeValue.match(NOT_EMPTY) /* non-empty */) { 14422 jqLiteWrapNode(domNode, $compileNodes[i] = window.document.createElement('span')); 14423 } 14424 } 14425 14426 var compositeLinkFn = 14427 compileNodes($compileNodes, transcludeFn, $compileNodes, 14428 maxPriority, ignoreDirective, previousCompileContext); 14429 compile.$$addScopeClass($compileNodes); 14430 var namespace = null; 14431 return function publicLinkFn(scope, cloneConnectFn, options) { 14432 assertArg(scope, 'scope'); 14433 14434 if (previousCompileContext && previousCompileContext.needsNewScope) { 14435 // A parent directive did a replace and a directive on this element asked 14436 // for transclusion, which caused us to lose a layer of element on which 14437 // we could hold the new transclusion scope, so we will create it manually 14438 // here. 14439 scope = scope.$parent.$new(); 14440 } 14441 14442 options = options || {}; 14443 var parentBoundTranscludeFn = options.parentBoundTranscludeFn, 14444 transcludeControllers = options.transcludeControllers, 14445 futureParentElement = options.futureParentElement; 14446 14447 // When `parentBoundTranscludeFn` is passed, it is a 14448 // `controllersBoundTransclude` function (it was previously passed 14449 // as `transclude` to directive.link) so we must unwrap it to get 14450 // its `boundTranscludeFn` 14451 if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) { 14452 parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude; 14453 } 14454 14455 if (!namespace) { 14456 namespace = detectNamespaceForChildElements(futureParentElement); 14457 } 14458 var $linkNode; 14459 if (namespace !== 'html') { 14460 // When using a directive with replace:true and templateUrl the $compileNodes 14461 // (or a child element inside of them) 14462 // might change, so we need to recreate the namespace adapted compileNodes 14463 // for call to the link function. 14464 // Note: This will already clone the nodes... 14465 $linkNode = jqLite( 14466 wrapTemplate(namespace, jqLite('<div>').append($compileNodes).html()) 14467 ); 14468 } else if (cloneConnectFn) { 14469 // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart 14470 // and sometimes changes the structure of the DOM. 14471 $linkNode = JQLitePrototype.clone.call($compileNodes); 14472 } else { 14473 $linkNode = $compileNodes; 14474 } 14475 14476 if (transcludeControllers) { 14477 for (var controllerName in transcludeControllers) { 14478 $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance); 14479 } 14480 } 14481 14482 compile.$$addScopeInfo($linkNode, scope); 14483 14484 if (cloneConnectFn) cloneConnectFn($linkNode, scope); 14485 if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn); 14486 return $linkNode; 14487 }; 14488 } 14489 14490 function detectNamespaceForChildElements(parentElement) { 14491 // TODO: Make this detect MathML as well... 14492 var node = parentElement && parentElement[0]; 14493 if (!node) { 14494 return 'html'; 14495 } else { 14496 return nodeName_(node) !== 'foreignobject' && toString.call(node).match(/SVG/) ? 'svg' : 'html'; 14497 } 14498 } 14499 14500 /** 14501 * Compile function matches each node in nodeList against the directives. Once all directives 14502 * for a particular node are collected their compile functions are executed. The compile 14503 * functions return values - the linking functions - are combined into a composite linking 14504 * function, which is the a linking function for the node. 14505 * 14506 * @param {NodeList} nodeList an array of nodes or NodeList to compile 14507 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 14508 * scope argument is auto-generated to the new child of the transcluded parent scope. 14509 * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then 14510 * the rootElement must be set the jqLite collection of the compile root. This is 14511 * needed so that the jqLite collection items can be replaced with widgets. 14512 * @param {number=} maxPriority Max directive priority. 14513 * @returns {Function} A composite linking function of all of the matched directives or null. 14514 */ 14515 function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, 14516 previousCompileContext) { 14517 var linkFns = [], 14518 attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound; 14519 14520 for (var i = 0; i < nodeList.length; i++) { 14521 attrs = new Attributes(); 14522 14523 // we must always refer to nodeList[i] since the nodes can be replaced underneath us. 14524 directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined, 14525 ignoreDirective); 14526 14527 nodeLinkFn = (directives.length) 14528 ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, 14529 null, [], [], previousCompileContext) 14530 : null; 14531 14532 if (nodeLinkFn && nodeLinkFn.scope) { 14533 compile.$$addScopeClass(attrs.$$element); 14534 } 14535 14536 childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || 14537 !(childNodes = nodeList[i].childNodes) || 14538 !childNodes.length) 14539 ? null 14540 : compileNodes(childNodes, 14541 nodeLinkFn ? ( 14542 (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement) 14543 && nodeLinkFn.transclude) : transcludeFn); 14544 14545 if (nodeLinkFn || childLinkFn) { 14546 linkFns.push(i, nodeLinkFn, childLinkFn); 14547 linkFnFound = true; 14548 nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn; 14549 } 14550 14551 //use the previous context only for the first element in the virtual group 14552 previousCompileContext = null; 14553 } 14554 14555 // return a linking function if we have found anything, null otherwise 14556 return linkFnFound ? compositeLinkFn : null; 14557 14558 function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) { 14559 var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn; 14560 var stableNodeList; 14561 14562 14563 if (nodeLinkFnFound) { 14564 // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our 14565 // offsets don't get screwed up 14566 var nodeListLength = nodeList.length; 14567 stableNodeList = new Array(nodeListLength); 14568 14569 // create a sparse array by only copying the elements which have a linkFn 14570 for (i = 0; i < linkFns.length; i += 3) { 14571 idx = linkFns[i]; 14572 stableNodeList[idx] = nodeList[idx]; 14573 } 14574 } else { 14575 stableNodeList = nodeList; 14576 } 14577 14578 for (i = 0, ii = linkFns.length; i < ii;) { 14579 node = stableNodeList[linkFns[i++]]; 14580 nodeLinkFn = linkFns[i++]; 14581 childLinkFn = linkFns[i++]; 14582 14583 if (nodeLinkFn) { 14584 if (nodeLinkFn.scope) { 14585 childScope = scope.$new(); 14586 compile.$$addScopeInfo(jqLite(node), childScope); 14587 } else { 14588 childScope = scope; 14589 } 14590 14591 if (nodeLinkFn.transcludeOnThisElement) { 14592 childBoundTranscludeFn = createBoundTranscludeFn( 14593 scope, nodeLinkFn.transclude, parentBoundTranscludeFn); 14594 14595 } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) { 14596 childBoundTranscludeFn = parentBoundTranscludeFn; 14597 14598 } else if (!parentBoundTranscludeFn && transcludeFn) { 14599 childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn); 14600 14601 } else { 14602 childBoundTranscludeFn = null; 14603 } 14604 14605 nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn); 14606 14607 } else if (childLinkFn) { 14608 childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn); 14609 } 14610 } 14611 } 14612 } 14613 14614 function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) { 14615 function boundTranscludeFn(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) { 14616 14617 if (!transcludedScope) { 14618 transcludedScope = scope.$new(false, containingScope); 14619 transcludedScope.$$transcluded = true; 14620 } 14621 14622 return transcludeFn(transcludedScope, cloneFn, { 14623 parentBoundTranscludeFn: previousBoundTranscludeFn, 14624 transcludeControllers: controllers, 14625 futureParentElement: futureParentElement 14626 }); 14627 } 14628 14629 // We need to attach the transclusion slots onto the `boundTranscludeFn` 14630 // so that they are available inside the `controllersBoundTransclude` function 14631 var boundSlots = boundTranscludeFn.$$slots = createMap(); 14632 for (var slotName in transcludeFn.$$slots) { 14633 if (transcludeFn.$$slots[slotName]) { 14634 boundSlots[slotName] = createBoundTranscludeFn(scope, transcludeFn.$$slots[slotName], previousBoundTranscludeFn); 14635 } else { 14636 boundSlots[slotName] = null; 14637 } 14638 } 14639 14640 return boundTranscludeFn; 14641 } 14642 14643 /** 14644 * Looks for directives on the given node and adds them to the directive collection which is 14645 * sorted. 14646 * 14647 * @param node Node to search. 14648 * @param directives An array to which the directives are added to. This array is sorted before 14649 * the function returns. 14650 * @param attrs The shared attrs object which is used to populate the normalized attributes. 14651 * @param {number=} maxPriority Max directive priority. 14652 */ 14653 function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) { 14654 var nodeType = node.nodeType, 14655 attrsMap = attrs.$attr, 14656 match, 14657 nodeName, 14658 className; 14659 14660 switch (nodeType) { 14661 case NODE_TYPE_ELEMENT: /* Element */ 14662 14663 nodeName = nodeName_(node); 14664 14665 // use the node name: <directive> 14666 addDirective(directives, 14667 directiveNormalize(nodeName), 'E', maxPriority, ignoreDirective); 14668 14669 // iterate over the attributes 14670 for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes, 14671 j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { 14672 var attrStartName = false; 14673 var attrEndName = false; 14674 14675 attr = nAttrs[j]; 14676 name = attr.name; 14677 value = trim(attr.value); 14678 14679 // support ngAttr attribute binding 14680 ngAttrName = directiveNormalize(name); 14681 isNgAttr = NG_ATTR_BINDING.test(ngAttrName); 14682 if (isNgAttr) { 14683 name = name.replace(PREFIX_REGEXP, '') 14684 .substr(8).replace(/_(.)/g, function(match, letter) { 14685 return letter.toUpperCase(); 14686 }); 14687 } 14688 14689 var multiElementMatch = ngAttrName.match(MULTI_ELEMENT_DIR_RE); 14690 if (multiElementMatch && directiveIsMultiElement(multiElementMatch[1])) { 14691 attrStartName = name; 14692 attrEndName = name.substr(0, name.length - 5) + 'end'; 14693 name = name.substr(0, name.length - 6); 14694 } 14695 14696 nName = directiveNormalize(name.toLowerCase()); 14697 attrsMap[nName] = name; 14698 if (isNgAttr || !attrs.hasOwnProperty(nName)) { 14699 attrs[nName] = value; 14700 if (getBooleanAttrName(node, nName)) { 14701 attrs[nName] = true; // presence means true 14702 } 14703 } 14704 addAttrInterpolateDirective(node, directives, value, nName, isNgAttr); 14705 addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, 14706 attrEndName); 14707 } 14708 14709 if (nodeName === 'input' && node.getAttribute('type') === 'hidden') { 14710 // Hidden input elements can have strange behaviour when navigating back to the page 14711 // This tells the browser not to try to cache and reinstate previous values 14712 node.setAttribute('autocomplete', 'off'); 14713 } 14714 14715 // use class as directive 14716 if (!cssClassDirectivesEnabled) break; 14717 className = node.className; 14718 if (isObject(className)) { 14719 // Maybe SVGAnimatedString 14720 className = className.animVal; 14721 } 14722 if (isString(className) && className !== '') { 14723 while ((match = CLASS_DIRECTIVE_REGEXP.exec(className))) { 14724 nName = directiveNormalize(match[2]); 14725 if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) { 14726 attrs[nName] = trim(match[3]); 14727 } 14728 className = className.substr(match.index + match[0].length); 14729 } 14730 } 14731 break; 14732 case NODE_TYPE_TEXT: /* Text Node */ 14733 if (msie === 11) { 14734 // Workaround for #11781 14735 while (node.parentNode && node.nextSibling && node.nextSibling.nodeType === NODE_TYPE_TEXT) { 14736 node.nodeValue = node.nodeValue + node.nextSibling.nodeValue; 14737 node.parentNode.removeChild(node.nextSibling); 14738 } 14739 } 14740 addTextInterpolateDirective(directives, node.nodeValue); 14741 break; 14742 case NODE_TYPE_COMMENT: /* Comment */ 14743 if (!commentDirectivesEnabled) break; 14744 collectCommentDirectives(node, directives, attrs, maxPriority, ignoreDirective); 14745 break; 14746 } 14747 14748 directives.sort(byPriority); 14749 return directives; 14750 } 14751 14752 function collectCommentDirectives(node, directives, attrs, maxPriority, ignoreDirective) { 14753 // function created because of performance, try/catch disables 14754 // the optimization of the whole function #14848 14755 try { 14756 var match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); 14757 if (match) { 14758 var nName = directiveNormalize(match[1]); 14759 if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) { 14760 attrs[nName] = trim(match[2]); 14761 } 14762 } 14763 } catch (e) { 14764 // turns out that under some circumstances IE9 throws errors when one attempts to read 14765 // comment's node value. 14766 // Just ignore it and continue. (Can't seem to reproduce in test case.) 14767 } 14768 } 14769 14770 /** 14771 * Given a node with a directive-start it collects all of the siblings until it finds 14772 * directive-end. 14773 * @param node 14774 * @param attrStart 14775 * @param attrEnd 14776 * @returns {*} 14777 */ 14778 function groupScan(node, attrStart, attrEnd) { 14779 var nodes = []; 14780 var depth = 0; 14781 if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { 14782 do { 14783 if (!node) { 14784 throw $compileMinErr('uterdir', 14785 'Unterminated attribute, found \'{0}\' but no matching \'{1}\' found.', 14786 attrStart, attrEnd); 14787 } 14788 if (node.nodeType === NODE_TYPE_ELEMENT) { 14789 if (node.hasAttribute(attrStart)) depth++; 14790 if (node.hasAttribute(attrEnd)) depth--; 14791 } 14792 nodes.push(node); 14793 node = node.nextSibling; 14794 } while (depth > 0); 14795 } else { 14796 nodes.push(node); 14797 } 14798 14799 return jqLite(nodes); 14800 } 14801 14802 /** 14803 * Wrapper for linking function which converts normal linking function into a grouped 14804 * linking function. 14805 * @param linkFn 14806 * @param attrStart 14807 * @param attrEnd 14808 * @returns {Function} 14809 */ 14810 function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) { 14811 return function groupedElementsLink(scope, element, attrs, controllers, transcludeFn) { 14812 element = groupScan(element[0], attrStart, attrEnd); 14813 return linkFn(scope, element, attrs, controllers, transcludeFn); 14814 }; 14815 } 14816 14817 /** 14818 * A function generator that is used to support both eager and lazy compilation 14819 * linking function. 14820 * @param eager 14821 * @param $compileNodes 14822 * @param transcludeFn 14823 * @param maxPriority 14824 * @param ignoreDirective 14825 * @param previousCompileContext 14826 * @returns {Function} 14827 */ 14828 function compilationGenerator(eager, $compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext) { 14829 var compiled; 14830 14831 if (eager) { 14832 return compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext); 14833 } 14834 return /** @this */ function lazyCompilation() { 14835 if (!compiled) { 14836 compiled = compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext); 14837 14838 // Null out all of these references in order to make them eligible for garbage collection 14839 // since this is a potentially long lived closure 14840 $compileNodes = transcludeFn = previousCompileContext = null; 14841 } 14842 return compiled.apply(this, arguments); 14843 }; 14844 } 14845 14846 /** 14847 * Once the directives have been collected, their compile functions are executed. This method 14848 * is responsible for inlining directive templates as well as terminating the application 14849 * of the directives if the terminal directive has been reached. 14850 * 14851 * @param {Array} directives Array of collected directives to execute their compile function. 14852 * this needs to be pre-sorted by priority order. 14853 * @param {Node} compileNode The raw DOM node to apply the compile functions to 14854 * @param {Object} templateAttrs The shared attribute function 14855 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 14856 * scope argument is auto-generated to the new 14857 * child of the transcluded parent scope. 14858 * @param {JQLite} jqCollection If we are working on the root of the compile tree then this 14859 * argument has the root jqLite array so that we can replace nodes 14860 * on it. 14861 * @param {Object=} originalReplaceDirective An optional directive that will be ignored when 14862 * compiling the transclusion. 14863 * @param {Array.<Function>} preLinkFns 14864 * @param {Array.<Function>} postLinkFns 14865 * @param {Object} previousCompileContext Context used for previous compilation of the current 14866 * node 14867 * @returns {Function} linkFn 14868 */ 14869 function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, 14870 jqCollection, originalReplaceDirective, preLinkFns, postLinkFns, 14871 previousCompileContext) { 14872 previousCompileContext = previousCompileContext || {}; 14873 14874 var terminalPriority = -Number.MAX_VALUE, 14875 newScopeDirective = previousCompileContext.newScopeDirective, 14876 controllerDirectives = previousCompileContext.controllerDirectives, 14877 newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective, 14878 templateDirective = previousCompileContext.templateDirective, 14879 nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective, 14880 hasTranscludeDirective = false, 14881 hasTemplate = false, 14882 hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective, 14883 $compileNode = templateAttrs.$$element = jqLite(compileNode), 14884 directive, 14885 directiveName, 14886 $template, 14887 replaceDirective = originalReplaceDirective, 14888 childTranscludeFn = transcludeFn, 14889 linkFn, 14890 didScanForMultipleTransclusion = false, 14891 mightHaveMultipleTransclusionError = false, 14892 directiveValue; 14893 14894 // executes all directives on the current element 14895 for (var i = 0, ii = directives.length; i < ii; i++) { 14896 directive = directives[i]; 14897 var attrStart = directive.$$start; 14898 var attrEnd = directive.$$end; 14899 14900 // collect multiblock sections 14901 if (attrStart) { 14902 $compileNode = groupScan(compileNode, attrStart, attrEnd); 14903 } 14904 $template = undefined; 14905 14906 if (terminalPriority > directive.priority) { 14907 break; // prevent further processing of directives 14908 } 14909 14910 directiveValue = directive.scope; 14911 14912 if (directiveValue) { 14913 14914 // skip the check for directives with async templates, we'll check the derived sync 14915 // directive when the template arrives 14916 if (!directive.templateUrl) { 14917 if (isObject(directiveValue)) { 14918 // This directive is trying to add an isolated scope. 14919 // Check that there is no scope of any kind already 14920 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective || newScopeDirective, 14921 directive, $compileNode); 14922 newIsolateScopeDirective = directive; 14923 } else { 14924 // This directive is trying to add a child scope. 14925 // Check that there is no isolated scope already 14926 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, 14927 $compileNode); 14928 } 14929 } 14930 14931 newScopeDirective = newScopeDirective || directive; 14932 } 14933 14934 directiveName = directive.name; 14935 14936 // If we encounter a condition that can result in transclusion on the directive, 14937 // then scan ahead in the remaining directives for others that may cause a multiple 14938 // transclusion error to be thrown during the compilation process. If a matching directive 14939 // is found, then we know that when we encounter a transcluded directive, we need to eagerly 14940 // compile the `transclude` function rather than doing it lazily in order to throw 14941 // exceptions at the correct time 14942 if (!didScanForMultipleTransclusion && ((directive.replace && (directive.templateUrl || directive.template)) 14943 || (directive.transclude && !directive.$$tlb))) { 14944 var candidateDirective; 14945 14946 for (var scanningIndex = i + 1; (candidateDirective = directives[scanningIndex++]);) { 14947 if ((candidateDirective.transclude && !candidateDirective.$$tlb) 14948 || (candidateDirective.replace && (candidateDirective.templateUrl || candidateDirective.template))) { 14949 mightHaveMultipleTransclusionError = true; 14950 break; 14951 } 14952 } 14953 14954 didScanForMultipleTransclusion = true; 14955 } 14956 14957 if (!directive.templateUrl && directive.controller) { 14958 controllerDirectives = controllerDirectives || createMap(); 14959 assertNoDuplicate('\'' + directiveName + '\' controller', 14960 controllerDirectives[directiveName], directive, $compileNode); 14961 controllerDirectives[directiveName] = directive; 14962 } 14963 14964 directiveValue = directive.transclude; 14965 14966 if (directiveValue) { 14967 hasTranscludeDirective = true; 14968 14969 // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion. 14970 // This option should only be used by directives that know how to safely handle element transclusion, 14971 // where the transcluded nodes are added or replaced after linking. 14972 if (!directive.$$tlb) { 14973 assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode); 14974 nonTlbTranscludeDirective = directive; 14975 } 14976 14977 if (directiveValue === 'element') { 14978 hasElementTranscludeDirective = true; 14979 terminalPriority = directive.priority; 14980 $template = $compileNode; 14981 $compileNode = templateAttrs.$$element = 14982 jqLite(compile.$$createComment(directiveName, templateAttrs[directiveName])); 14983 compileNode = $compileNode[0]; 14984 replaceWith(jqCollection, sliceArgs($template), compileNode); 14985
14986 // Support: Chrome < 50 14987 // https://github.com/angular/angular.js/issues/14041 14988 14989 // In the versions of V8 prior to Chrome 50, the document fragment that is created 14990 // in the `replaceWith` function is improperly garbage collected despite still 14991 // being referenced by the `parentNode` property of all of the child nodes. By adding 14992 // a reference to the fragment via a different property, we can avoid that incorrect 14993 // behavior. 14994 // TODO: remove this line after Chrome 50 has been released 14995 $template[0].$$parentNode = $template[0].parentNode; 14996 14997 childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, terminalPriority, 14998 replaceDirective && replaceDirective.name, { 14999 // Don't pass in: 15000 // - controllerDirectives - otherwise we'll create duplicates controllers 15001 // - newIsolateScopeDirective or templateDirective - comb
vendor: 12,791 bytes, lines 15001-15281
15001ining templates with 15002 // element transclusion doesn't make sense. 15003 // 15004 // We need only nonTlbTranscludeDirective so that we prevent putting transclusion 15005 // on the same element more than once. 15006 nonTlbTranscludeDirective: nonTlbTranscludeDirective 15007 }); 15008 } else { 15009 15010 var slots = createMap(); 15011 15012 $template = jqLite(jqLiteClone(compileNode)).contents(); 15013 15014 if (isObject(directiveValue)) { 15015 15016 // We have transclusion slots, 15017 // collect them up, compile them and store their transclusion functions 15018 $template = []; 15019 15020 var slotMap = createMap(); 15021 var filledSlots = createMap(); 15022 15023 // Parse the element selectors 15024 forEach(directiveValue, function(elementSelector, slotName) { 15025 // If an element selector starts with a ? then it is optional 15026 var optional = (elementSelector.charAt(0) === '?'); 15027 elementSelector = optional ? elementSelector.substring(1) : elementSelector; 15028 15029 slotMap[elementSelector] = slotName; 15030 15031 // We explicitly assign `null` since this implies that a slot was defined but not filled. 15032 // Later when calling boundTransclusion functions with a slot name we only error if the 15033 // slot is `undefined` 15034 slots[slotName] = null; 15035 15036 // filledSlots contains `true` for all slots that are either optional or have been 15037 // filled. This is used to check that we have not missed any required slots 15038 filledSlots[slotName] = optional; 15039 }); 15040 15041 // Add the matching elements into their slot 15042 forEach($compileNode.contents(), function(node) { 15043 var slotName = slotMap[directiveNormalize(nodeName_(node))]; 15044 if (slotName) { 15045 filledSlots[slotName] = true; 15046 slots[slotName] = slots[slotName] || []; 15047 slots[slotName].push(node); 15048 } else { 15049 $template.push(node); 15050 } 15051 }); 15052 15053 // Check for required slots that were not filled 15054 forEach(filledSlots, function(filled, slotName) { 15055 if (!filled) { 15056 throw $compileMinErr('reqslot', 'Required transclusion slot `{0}` was not filled.', slotName); 15057 } 15058 }); 15059 15060 for (var slotName in slots) { 15061 if (slots[slotName]) { 15062 // Only define a transclusion function if the slot was filled 15063 slots[slotName] = compilationGenerator(mightHaveMultipleTransclusionError, slots[slotName], transcludeFn); 15064 } 15065 } 15066 } 15067 15068 $compileNode.empty(); // clear contents 15069 childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, undefined, 15070 undefined, { needsNewScope: directive.$$isolateScope || directive.$$newScope}); 15071 childTranscludeFn.$$slots = slots; 15072 } 15073 } 15074 15075 if (directive.template) { 15076 hasTemplate = true; 15077 assertNoDuplicate('template', templateDirective, directive, $compileNode); 15078 templateDirective = directive; 15079 15080 directiveValue = (isFunction(directive.template)) 15081 ? directive.template($compileNode, templateAttrs) 15082 : directive.template; 15083 15084 directiveValue = denormalizeTemplate(directiveValue); 15085 15086 if (directive.replace) { 15087 replaceDirective = directive; 15088 if (jqLiteIsTextNode(directiveValue)) { 15089 $template = []; 15090 } else { 15091 $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue))); 15092 } 15093 compileNode = $template[0]; 15094 15095 if ($template.length !== 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { 15096 throw $compileMinErr('tplrt', 15097 'Template for directive \'{0}\' must have exactly one root element. {1}', 15098 directiveName, ''); 15099 } 15100 15101 replaceWith(jqCollection, $compileNode, compileNode); 15102 15103 var newTemplateAttrs = {$attr: {}}; 15104 15105 // combine directives from the original node and from the template: 15106 // - take the array of directives for this element 15107 // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed) 15108 // - collect directives from the template and sort them by priority 15109 // - combine directives as: processed + template + unprocessed 15110 var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs); 15111 var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1)); 15112 15113 if (newIsolateScopeDirective || newScopeDirective) { 15114 // The original directive caused the current element to be replaced but this element 15115 // also needs to have a new scope, so we need to tell the template directives 15116 // that they would need to get their scope from further up, if they require transclusion 15117 markDirectiveScope(templateDirectives, newIsolateScopeDirective, newScopeDirective); 15118 } 15119 directives = directives.concat(templateDirectives).concat(unprocessedDirectives); 15120 mergeTemplateAttributes(templateAttrs, newTemplateAttrs); 15121 15122 ii = directives.length; 15123 } else { 15124 $compileNode.html(directiveValue); 15125 } 15126 } 15127 15128 if (directive.templateUrl) { 15129 hasTemplate = true; 15130 assertNoDuplicate('template', templateDirective, directive, $compileNode); 15131 templateDirective = directive; 15132 15133 if (directive.replace) { 15134 replaceDirective = directive; 15135 } 15136 15137 // eslint-disable-next-line no-func-assign 15138 nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode, 15139 templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, { 15140 controllerDirectives: controllerDirectives, 15141 newScopeDirective: (newScopeDirective !== directive) && newScopeDirective, 15142 newIsolateScopeDirective: newIsolateScopeDirective, 15143 templateDirective: templateDirective, 15144 nonTlbTranscludeDirective: nonTlbTranscludeDirective 15145 }); 15146 ii = directives.length; 15147 } else if (directive.compile) { 15148 try { 15149 linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); 15150 var context = directive.$$originalDirective || directive; 15151 if (isFunction(linkFn)) { 15152 addLinkFns(null, bind(context, linkFn), attrStart, attrEnd); 15153 } else if (linkFn) { 15154 addLinkFns(bind(context, linkFn.pre), bind(context, linkFn.post), attrStart, attrEnd); 15155 } 15156 } catch (e) { 15157 $exceptionHandler(e, startingTag($compileNode)); 15158 } 15159 } 15160 15161 if (directive.terminal) { 15162 nodeLinkFn.terminal = true; 15163 terminalPriority = Math.max(terminalPriority, directive.priority); 15164 } 15165 15166 } 15167 15168 nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; 15169 nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective; 15170 nodeLinkFn.templateOnThisElement = hasTemplate; 15171 nodeLinkFn.transclude = childTranscludeFn; 15172 15173 previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective; 15174 15175 // might be normal or delayed nodeLinkFn depending on if templateUrl is present 15176 return nodeLinkFn; 15177 15178 //////////////////// 15179 15180 function addLinkFns(pre, post, attrStart, attrEnd) { 15181 if (pre) { 15182 if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd); 15183 pre.require = directive.require; 15184 pre.directiveName = directiveName; 15185 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 15186 pre = cloneAndAnnotateFn(pre, {isolateScope: true}); 15187 } 15188 preLinkFns.push(pre); 15189 } 15190 if (post) { 15191 if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd); 15192 post.require = directive.require; 15193 post.directiveName = directiveName; 15194 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 15195 post = cloneAndAnnotateFn(post, {isolateScope: true}); 15196 } 15197 postLinkFns.push(post); 15198 } 15199 } 15200 15201 function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) { 15202 var i, ii, linkFn, isolateScope, controllerScope, elementControllers, transcludeFn, $element, 15203 attrs, scopeBindingInfo; 15204 15205 if (compileNode === linkNode) { 15206 attrs = templateAttrs; 15207 $element = templateAttrs.$$element; 15208 } else { 15209 $element = jqLite(linkNode); 15210 attrs = new Attributes($element, templateAttrs); 15211 } 15212 15213 controllerScope = scope; 15214 if (newIsolateScopeDirective) { 15215 isolateScope = scope.$new(true); 15216 } else if (newScopeDirective) { 15217 controllerScope = scope.$parent; 15218 } 15219 15220 if (boundTranscludeFn) { 15221 // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn` 15222 // is later passed as `parentBoundTranscludeFn` to `publicLinkFn` 15223 transcludeFn = controllersBoundTransclude; 15224 transcludeFn.$$boundTransclude = boundTranscludeFn; 15225 // expose the slots on the `$transclude` function 15226 transcludeFn.isSlotFilled = function(slotName) { 15227 return !!boundTranscludeFn.$$slots[slotName]; 15228 }; 15229 } 15230 15231 if (controllerDirectives) { 15232 elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope, newIsolateScopeDirective); 15233 } 15234 15235 if (newIsolateScopeDirective) { 15236 // Initialize isolate scope bindings for new isolate scope directive. 15237 compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective || 15238 templateDirective === newIsolateScopeDirective.$$originalDirective))); 15239 compile.$$addScopeClass($element, true); 15240 isolateScope.$$isolateBindings = 15241 newIsolateScopeDirective.$$isolateBindings; 15242 scopeBindingInfo = initializeDirectiveBindings(scope, attrs, isolateScope, 15243 isolateScope.$$isolateBindings, 15244 newIsolateScopeDirective); 15245 if (scopeBindingInfo.removeWatches) { 15246 isolateScope.$on('$destroy', scopeBindingInfo.removeWatches); 15247 } 15248 } 15249 15250 // Initialize bindToController bindings 15251 for (var name in elementControllers) { 15252 var controllerDirective = controllerDirectives[name]; 15253 var controller = elementControllers[name]; 15254 var bindings = controllerDirective.$$bindings.bindToController; 15255 15256 if (preAssignBindingsEnabled) { 15257 if (bindings) { 15258 controller.bindingInfo = 15259 initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective); 15260 } else { 15261 controller.bindingInfo = {}; 15262 } 15263 15264 var controllerResult = controller(); 15265 if (controllerResult !== controller.instance) { 15266 // If the controller constructor has a return value, overwrite the instance 15267 // from setupControllers 15268 controller.instance = controllerResult; 15269 $element.data('$' + controllerDirective.name + 'Controller', controllerResult); 15270 if (controller.bindingInfo.removeWatches) { 15271 controller.bindingInfo.removeWatches(); 15272 } 15273 controller.bindingInfo = 15274 initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective); 15275 } 15276 } else { 15277 controller.instance = controller(); 15278 $element.data('$' + controllerDirective.name + 'Controller', controller.instance); 15279 controller.bindingInfo = 15280 initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective); 15281 }
vendor: 5,779 bytes, lines 15282-15422
15282 } 15283 15284 // Bind the required controllers to the controller, if `require` is an object and `bindToController` is truthy 15285 forEach(controllerDirectives, function(controllerDirective, name) { 15286 var require = controllerDirective.require; 15287 if (controllerDirective.bindToController && !isArray(require) && isObject(require)) { 15288 extend(elementControllers[name].instance, getControllers(name, require, $element, elementControllers)); 15289 } 15290 }); 15291 15292 // Handle the init and destroy lifecycle hooks on all controllers that have them 15293 forEach(elementControllers, function(controller) { 15294 var controllerInstance = controller.instance; 15295 if (isFunction(controllerInstance.$onChanges)) { 15296 try { 15297 controllerInstance.$onChanges(controller.bindingInfo.initialChanges); 15298 } catch (e) { 15299 $exceptionHandler(e); 15300 } 15301 } 15302 if (isFunction(controllerInstance.$onInit)) { 15303 try { 15304 controllerInstance.$onInit(); 15305 } catch (e) { 15306 $exceptionHandler(e); 15307 } 15308 } 15309 if (isFunction(controllerInstance.$doCheck)) { 15310 controllerScope.$watch(function() { controllerInstance.$doCheck(); }); 15311 controllerInstance.$doCheck(); 15312 } 15313 if (isFunction(controllerInstance.$onDestroy)) { 15314 controllerScope.$on('$destroy', function callOnDestroyHook() { 15315 controllerInstance.$onDestroy(); 15316 }); 15317 } 15318 }); 15319 15320 // PRELINKING 15321 for (i = 0, ii = preLinkFns.length; i < ii; i++) { 15322 linkFn = preLinkFns[i]; 15323 invokeLinkFn(linkFn, 15324 linkFn.isolateScope ? isolateScope : scope, 15325 $element, 15326 attrs, 15327 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), 15328 transcludeFn 15329 ); 15330 } 15331 15332 // RECURSION 15333 // We only pass the isolate scope, if the isolate directive has a template, 15334 // otherwise the child elements do not belong to the isolate directive. 15335 var scopeToChild = scope; 15336 if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) { 15337 scopeToChild = isolateScope; 15338 } 15339 if (childLinkFn) { 15340 childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn); 15341 } 15342 15343 // POSTLINKING 15344 for (i = postLinkFns.length - 1; i >= 0; i--) { 15345 linkFn = postLinkFns[i]; 15346 invokeLinkFn(linkFn, 15347 linkFn.isolateScope ? isolateScope : scope, 15348 $element, 15349 attrs, 15350 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), 15351 transcludeFn 15352 ); 15353 } 15354 15355 // Trigger $postLink lifecycle hooks 15356 forEach(elementControllers, function(controller) { 15357 var controllerInstance = controller.instance; 15358 if (isFunction(controllerInstance.$postLink)) { 15359 controllerInstance.$postLink(); 15360 } 15361 }); 15362 15363 // This is the function that is injected as `$transclude`. 15364 // Note: all arguments are optional! 15365 function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement, slotName) { 15366 var transcludeControllers; 15367 // No scope passed in: 15368 if (!isScope(scope)) { 15369 slotName = futureParentElement; 15370 futureParentElement = cloneAttachFn; 15371 cloneAttachFn = scope; 15372 scope = undefined; 15373 } 15374 15375 if (hasElementTranscludeDirective) { 15376 transcludeControllers = elementControllers; 15377 } 15378 if (!futureParentElement) { 15379 futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element; 15380 } 15381 if (slotName) { 15382 // slotTranscludeFn can be one of three things: 15383 // * a transclude function - a filled slot 15384 // * `null` - an optional slot that was not filled 15385 // * `undefined` - a slot that was not declared (i.e. invalid) 15386 var slotTranscludeFn = boundTranscludeFn.$$slots[slotName]; 15387 if (slotTranscludeFn) { 15388 return slotTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild); 15389 } else if (isUndefined(slotTranscludeFn)) { 15390 throw $compileMinErr('noslot', 15391 'No parent directive that requires a transclusion with slot name "{0}". ' + 15392 'Element: {1}', 15393 slotName, startingTag($element)); 15394 } 15395 } else { 15396 return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild); 15397 } 15398 } 15399 } 15400 } 15401 15402 function getControllers(directiveName, require, $element, elementControllers) { 15403 var value; 15404 15405 if (isString(require)) { 15406 var match = require.match(REQUIRE_PREFIX_REGEXP); 15407 var name = require.substring(match[0].length); 15408 var inheritType = match[1] || match[3]; 15409 var optional = match[2] === '?'; 15410 15411 //If only parents then start at the parent element 15412 if (inheritType === '^^') { 15413 $element = $element.parent(); 15414 //Otherwise attempt getting the controller from elementControllers in case 15415 //the element is transcluded (and has no data) and to avoid .data if possible 15416 } else { 15417 value = elementControllers && elementControllers[name]; 15418 value = value && value.instance; 15419 } 15420 15421 if (!value) { 15422 var dataName = '$' + name + 'Controller';
vendor: 38,712 bytes, lines 15423-16434
15423 value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName); 15424 } 15425 15426 if (!value && !optional) { 15427 throw $compileMinErr('ctreq', 15428 'Controller \'{0}\', required by directive \'{1}\', can\'t be found!', 15429 name, directiveName); 15430 } 15431 } else if (isArray(require)) { 15432 value = []; 15433 for (var i = 0, ii = require.length; i < ii; i++) { 15434 value[i] = getControllers(directiveName, require[i], $element, elementControllers); 15435 } 15436 } else if (isObject(require)) { 15437 value = {}; 15438 forEach(require, function(controller, property) { 15439 value[property] = getControllers(directiveName, controller, $element, elementControllers); 15440 }); 15441 } 15442 15443 return value || null; 15444 } 15445 15446 function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope, newIsolateScopeDirective) { 15447 var elementControllers = createMap(); 15448 for (var controllerKey in controllerDirectives) { 15449 var directive = controllerDirectives[controllerKey]; 15450 var locals = { 15451 $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope, 15452 $element: $element, 15453 $attrs: attrs, 15454 $transclude: transcludeFn 15455 }; 15456 15457 var controller = directive.controller; 15458 if (controller === '@') { 15459 controller = attrs[directive.name]; 15460 } 15461 15462 var controllerInstance = $controller(controller, locals, true, directive.controllerAs); 15463 15464 // For directives with element transclusion the element is a comment. 15465 // In this case .data will not attach any data. 15466 // Instead, we save the controllers for the element in a local hash and attach to .data 15467 // later, once we have the actual element. 15468 elementControllers[directive.name] = controllerInstance; 15469 $element.data('$' + directive.name + 'Controller', controllerInstance.instance); 15470 } 15471 return elementControllers; 15472 } 15473 15474 // Depending upon the context in which a directive finds itself it might need to have a new isolated 15475 // or child scope created. For instance: 15476 // * if the directive has been pulled into a template because another directive with a higher priority 15477 // asked for element transclusion 15478 // * if the directive itself asks for transclusion but it is at the root of a template and the original 15479 // element was replaced. See https://github.com/angular/angular.js/issues/12936 15480 function markDirectiveScope(directives, isolateScope, newScope) { 15481 for (var j = 0, jj = directives.length; j < jj; j++) { 15482 directives[j] = inherit(directives[j], {$$isolateScope: isolateScope, $$newScope: newScope}); 15483 } 15484 } 15485 15486 /** 15487 * looks up the directive and decorates it with exception handling and proper parameters. We 15488 * call this the boundDirective. 15489 * 15490 * @param {string} name name of the directive to look up. 15491 * @param {string} location The directive must be found in specific format. 15492 * String containing any of theses characters: 15493 * 15494 * * `E`: element name 15495 * * `A': attribute 15496 * * `C`: class 15497 * * `M`: comment 15498 * @returns {boolean} true if directive was added. 15499 */ 15500 function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, 15501 endAttrName) { 15502 if (name === ignoreDirective) return null; 15503 var match = null; 15504 if (hasDirectives.hasOwnProperty(name)) { 15505 for (var directive, directives = $injector.get(name + Suffix), 15506 i = 0, ii = directives.length; i < ii; i++) { 15507 directive = directives[i]; 15508 if ((isUndefined(maxPriority) || maxPriority > directive.priority) && 15509 directive.restrict.indexOf(location) !== -1) { 15510 if (startAttrName) { 15511 directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); 15512 } 15513 if (!directive.$$bindings) { 15514 var bindings = directive.$$bindings = 15515 parseDirectiveBindings(directive, directive.name); 15516 if (isObject(bindings.isolateScope)) { 15517 directive.$$isolateBindings = bindings.isolateScope; 15518 } 15519 } 15520 tDirectives.push(directive); 15521 match = directive; 15522 } 15523 } 15524 } 15525 return match; 15526 } 15527 15528 15529 /** 15530 * looks up the directive and returns true if it is a multi-element directive, 15531 * and therefore requires DOM nodes between -start and -end markers to be grouped 15532 * together. 15533 * 15534 * @param {string} name name of the directive to look up. 15535 * @returns true if directive was registered as multi-element. 15536 */ 15537 function directiveIsMultiElement(name) { 15538 if (hasDirectives.hasOwnProperty(name)) { 15539 for (var directive, directives = $injector.get(name + Suffix), 15540 i = 0, ii = directives.length; i < ii; i++) { 15541 directive = directives[i]; 15542 if (directive.multiElement) { 15543 return true; 15544 } 15545 } 15546 } 15547 return false; 15548 } 15549 15550 /** 15551 * When the element is replaced with HTML template then the new attributes 15552 * on the template need to be merged with the existing attributes in the DOM. 15553 * The desired effect is to have both of the attributes present. 15554 * 15555 * @param {object} dst destination attributes (original DOM) 15556 * @param {object} src source attributes (from the directive template) 15557 */ 15558 function mergeTemplateAttributes(dst, src) { 15559 var srcAttr = src.$attr, 15560 dstAttr = dst.$attr; 15561 15562 // reapply the old attributes to the new element 15563 forEach(dst, function(value, key) { 15564 if (key.charAt(0) !== '$') { 15565 if (src[key] && src[key] !== value) { 15566 value += (key === 'style' ? ';' : ' ') + src[key]; 15567 } 15568 dst.$set(key, value, true, srcAttr[key]); 15569 } 15570 }); 15571 15572 // copy the new attributes on the old attrs object 15573 forEach(src, function(value, key) { 15574 // Check if we already set this attribute in the loop above. 15575 // `dst` will never contain hasOwnProperty as DOM parser won't let it. 15576 // You will get an "InvalidCharacterError: DOM Exception 5" error if you 15577 // have an attribute like "has-own-property" or "data-has-own-property", etc. 15578 if (!dst.hasOwnProperty(key) && key.charAt(0) !== '$') { 15579 dst[key] = value; 15580 15581 if (key !== 'class' && key !== 'style') { 15582 dstAttr[key] = srcAttr[key]; 15583 } 15584 } 15585 }); 15586 } 15587 15588 15589 function compileTemplateUrl(directives, $compileNode, tAttrs, 15590 $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) { 15591 var linkQueue = [], 15592 afterTemplateNodeLinkFn, 15593 afterTemplateChildLinkFn, 15594 beforeTemplateCompileNode = $compileNode[0], 15595 origAsyncDirective = directives.shift(), 15596 derivedSyncDirective = inherit(origAsyncDirective, { 15597 templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective 15598 }), 15599 templateUrl = (isFunction(origAsyncDirective.templateUrl)) 15600 ? origAsyncDirective.templateUrl($compileNode, tAttrs) 15601 : origAsyncDirective.templateUrl, 15602 templateNamespace = origAsyncDirective.templateNamespace; 15603 15604 $compileNode.empty(); 15605 15606 $templateRequest(templateUrl) 15607 .then(function(content) { 15608 var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn; 15609 15610 content = denormalizeTemplate(content); 15611 15612 if (origAsyncDirective.replace) { 15613 if (jqLiteIsTextNode(content)) { 15614 $template = []; 15615 } else { 15616 $template = removeComments(wrapTemplate(templateNamespace, trim(content))); 15617 } 15618 compileNode = $template[0]; 15619 15620 if ($template.length !== 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { 15621 throw $compileMinErr('tplrt', 15622 'Template for directive \'{0}\' must have exactly one root element. {1}', 15623 origAsyncDirective.name, templateUrl); 15624 } 15625 15626 tempTemplateAttrs = {$attr: {}}; 15627 replaceWith($rootElement, $compileNode, compileNode); 15628 var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs); 15629 15630 if (isObject(origAsyncDirective.scope)) { 15631 // the original directive that caused the template to be loaded async required 15632 // an isolate scope 15633 markDirectiveScope(templateDirectives, true); 15634 } 15635 directives = templateDirectives.concat(directives); 15636 mergeTemplateAttributes(tAttrs, tempTemplateAttrs); 15637 } else { 15638 compileNode = beforeTemplateCompileNode; 15639 $compileNode.html(content); 15640 } 15641 15642 directives.unshift(derivedSyncDirective); 15643 15644 afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, 15645 childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns, 15646 previousCompileContext); 15647 forEach($rootElement, function(node, i) { 15648 if (node === compileNode) { 15649 $rootElement[i] = $compileNode[0]; 15650 } 15651 }); 15652 afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); 15653 15654 while (linkQueue.length) { 15655 var scope = linkQueue.shift(), 15656 beforeTemplateLinkNode = linkQueue.shift(), 15657 linkRootElement = linkQueue.shift(), 15658 boundTranscludeFn = linkQueue.shift(), 15659 linkNode = $compileNode[0]; 15660 15661 if (scope.$$destroyed) continue; 15662 15663 if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { 15664 var oldClasses = beforeTemplateLinkNode.className; 15665 15666 if (!(previousCompileContext.hasElementTranscludeDirective && 15667 origAsyncDirective.replace)) { 15668 // it was cloned therefore we have to clone as well. 15669 linkNode = jqLiteClone(compileNode); 15670 } 15671 replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); 15672 15673 // Copy in CSS classes from original node 15674 safeAddClass(jqLite(linkNode), oldClasses); 15675 } 15676 if (afterTemplateNodeLinkFn.transcludeOnThisElement) { 15677 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); 15678 } else { 15679 childBoundTranscludeFn = boundTranscludeFn; 15680 } 15681 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, 15682 childBoundTranscludeFn); 15683 } 15684 linkQueue = null; 15685 }); 15686 15687 return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) { 15688 var childBoundTranscludeFn = boundTranscludeFn; 15689 if (scope.$$destroyed) return; 15690 if (linkQueue) { 15691 linkQueue.push(scope, 15692 node, 15693 rootElement, 15694 childBoundTranscludeFn); 15695 } else { 15696 if (afterTemplateNodeLinkFn.transcludeOnThisElement) { 15697 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); 15698 } 15699 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn); 15700 } 15701 }; 15702 } 15703 15704 15705 /** 15706 * Sorting function for bound directives. 15707 */ 15708 function byPriority(a, b) { 15709 var diff = b.priority - a.priority; 15710 if (diff !== 0) return diff; 15711 if (a.name !== b.name) return (a.name < b.name) ? -1 : 1; 15712 return a.index - b.index; 15713 } 15714 15715 function assertNoDuplicate(what, previousDirective, directive, element) { 15716 15717 function wrapModuleNameIfDefined(moduleName) { 15718 return moduleName ? 15719 (' (module: ' + moduleName + ')') : 15720 ''; 15721 } 15722 15723 if (previousDirective) { 15724 throw $compileMinErr('multidir', 'Multiple directives [{0}{1}, {2}{3}] asking for {4} on: {5}', 15725 previousDirective.name, wrapModuleNameIfDefined(previousDirective.$$moduleName), 15726 directive.name, wrapModuleNameIfDefined(directive.$$moduleName), what, startingTag(element)); 15727 } 15728 } 15729 15730 15731 function addTextInterpolateDirective(directives, text) { 15732 var interpolateFn = $interpolate(text, true); 15733 if (interpolateFn) { 15734 directives.push({ 15735 priority: 0, 15736 compile: function textInterpolateCompileFn(templateNode) { 15737 var templateNodeParent = templateNode.parent(), 15738 hasCompileParent = !!templateNodeParent.length; 15739 15740 // When transcluding a template that has bindings in the root 15741 // we don't have a parent and thus need to add the class during linking fn. 15742 if (hasCompileParent) compile.$$addBindingClass(templateNodeParent); 15743 15744 return function textInterpolateLinkFn(scope, node) { 15745 var parent = node.parent(); 15746 if (!hasCompileParent) compile.$$addBindingClass(parent); 15747 compile.$$addBindingInfo(parent, interpolateFn.expressions); 15748 scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { 15749 node[0].nodeValue = value; 15750 }); 15751 }; 15752 } 15753 }); 15754 } 15755 } 15756 15757 15758 function wrapTemplate(type, template) { 15759 type = lowercase(type || 'html'); 15760 switch (type) { 15761 case 'svg': 15762 case 'math': 15763 var wrapper = window.document.createElement('div'); 15764 wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>'; 15765 return wrapper.childNodes[0].childNodes; 15766 default: 15767 return template; 15768 } 15769 } 15770 15771 15772 function getTrustedContext(node, attrNormalizedName) { 15773 if (attrNormalizedName === 'srcdoc') { 15774 return $sce.HTML; 15775 } 15776 var tag = nodeName_(node); 15777 // All tags with src attributes require a RESOURCE_URL value, except for 15778 // img and various html5 media tags. 15779 if (attrNormalizedName === 'src' || attrNormalizedName === 'ngSrc') { 15780 if (['img', 'video', 'audio', 'source', 'track'].indexOf(tag) === -1) { 15781 return $sce.RESOURCE_URL; 15782 } 15783 // maction[xlink:href] can source SVG. It's not limited to <maction>. 15784 } else if (attrNormalizedName === 'xlinkHref' || 15785 (tag === 'form' && attrNormalizedName === 'action') 15786 ) { 15787 return $sce.RESOURCE_URL; 15788 } 15789 } 15790 15791 15792 function addAttrInterpolateDirective(node, directives, value, name, isNgAttr) { 15793 var trustedContext = getTrustedContext(node, name); 15794 var mustHaveExpression = !isNgAttr; 15795 var allOrNothing = ALL_OR_NOTHING_ATTRS[name] || isNgAttr; 15796 15797 var interpolateFn = $interpolate(value, mustHaveExpression, trustedContext, allOrNothing); 15798 15799 // no interpolation found -> ignore 15800 if (!interpolateFn) return; 15801 15802 if (name === 'multiple' && nodeName_(node) === 'select') { 15803 throw $compileMinErr('selmulti', 15804 'Binding to the \'multiple\' attribute is not supported. Element: {0}', 15805 startingTag(node)); 15806 } 15807 15808 directives.push({ 15809 priority: 100, 15810 compile: function() { 15811 return { 15812 pre: function attrInterpolatePreLinkFn(scope, element, attr) { 15813 var $$observers = (attr.$$observers || (attr.$$observers = createMap())); 15814 15815 if (EVENT_HANDLER_ATTR_REGEXP.test(name)) { 15816 throw $compileMinErr('nodomevents', 15817 'Interpolations for HTML DOM event attributes are disallowed. Please use the ' + 15818 'ng- versions (such as ng-click instead of onclick) instead.'); 15819 } 15820 15821 // If the attribute has changed since last $interpolate()ed 15822 var newValue = attr[name]; 15823 if (newValue !== value) { 15824 // we need to interpolate again since the attribute value has been updated 15825 // (e.g. by another directive's compile function) 15826 // ensure unset/empty values make interpolateFn falsy 15827 interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing); 15828 value = newValue; 15829 } 15830 15831 // if attribute was updated so that there is no interpolation going on we don't want to 15832 // register any observers 15833 if (!interpolateFn) return; 15834 15835 // initialize attr object so that it's ready in case we need the value for isolate 15836 // scope initialization, otherwise the value would not be available from isolate 15837 // directive's linking fn during linking phase 15838 attr[name] = interpolateFn(scope); 15839 15840 ($$observers[name] || ($$observers[name] = [])).$$inter = true; 15841 (attr.$$observers && attr.$$observers[name].$$scope || scope). 15842 $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) { 15843 //special case for class attribute addition + removal 15844 //so that class changes can tap into the animation 15845 //hooks provided by the $animate service. Be sure to 15846 //skip animations when the first digest occurs (when 15847 //both the new and the old values are the same) since 15848 //the CSS classes are the non-interpolated values 15849 if (name === 'class' && newValue !== oldValue) { 15850 attr.$updateClass(newValue, oldValue); 15851 } else { 15852 attr.$set(name, newValue); 15853 } 15854 }); 15855 } 15856 }; 15857 } 15858 }); 15859 } 15860 15861 15862 /** 15863 * This is a special jqLite.replaceWith, which can replace items which 15864 * have no parents, provided that the containing jqLite collection is provided. 15865 * 15866 * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes 15867 * in the root of the tree. 15868 * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep 15869 * the shell, but replace its DOM node reference. 15870 * @param {Node} newNode The new DOM node. 15871 */ 15872 function replaceWith($rootElement, elementsToRemove, newNode) { 15873 var firstElementToRemove = elementsToRemove[0], 15874 removeCount = elementsToRemove.length, 15875 parent = firstElementToRemove.parentNode, 15876 i, ii; 15877 15878 if ($rootElement) { 15879 for (i = 0, ii = $rootElement.length; i < ii; i++) { 15880 if ($rootElement[i] === firstElementToRemove) { 15881 $rootElement[i++] = newNode; 15882 for (var j = i, j2 = j + removeCount - 1, 15883 jj = $rootElement.length; 15884 j < jj; j++, j2++) { 15885 if (j2 < jj) { 15886 $rootElement[j] = $rootElement[j2]; 15887 } else { 15888 delete $rootElement[j]; 15889 } 15890 } 15891 $rootElement.length -= removeCount - 1; 15892 15893 // If the replaced element is also the jQuery .context then replace it 15894 // .context is a deprecated jQuery api, so we should set it only when jQuery set it 15895 // http://api.jquery.com/context/ 15896 if ($rootElement.context === firstElementToRemove) { 15897 $rootElement.context = newNode; 15898 } 15899 break; 15900 } 15901 } 15902 } 15903 15904 if (parent) { 15905 parent.replaceChild(newNode, firstElementToRemove); 15906 } 15907 15908 // Append all the `elementsToRemove` to a fragment. This will... 15909 // - remove them from the DOM 15910 // - allow them to still be traversed with .nextSibling 15911 // - allow a single fragment.qSA to fetch all elements being removed 15912 var fragment = window.document.createDocumentFragment(); 15913 for (i = 0; i < removeCount; i++) { 15914 fragment.appendChild(elementsToRemove[i]); 15915 } 15916 15917 if (jqLite.hasData(firstElementToRemove)) { 15918 // Copy over user data (that includes Angular's $scope etc.). Don't copy private 15919 // data here because there's no public interface in jQuery to do that and copying over 15920 // event listeners (which is the main use of private data) wouldn't work anyway. 15921 jqLite.data(newNode, jqLite.data(firstElementToRemove)); 15922 15923 // Remove $destroy event listeners from `firstElementToRemove` 15924 jqLite(firstElementToRemove).off('$destroy'); 15925 } 15926 15927 // Cleanup any data/listeners on the elements and children. 15928 // This includes invoking the $destroy event on any elements with listeners. 15929 jqLite.cleanData(fragment.querySelectorAll('*')); 15930 15931 // Update the jqLite collection to only contain the `newNode` 15932 for (i = 1; i < removeCount; i++) { 15933 delete elementsToRemove[i]; 15934 } 15935 elementsToRemove[0] = newNode; 15936 elementsToRemove.length = 1; 15937 } 15938 15939 15940 function cloneAndAnnotateFn(fn, annotation) { 15941 return extend(function() { return fn.apply(null, arguments); }, fn, annotation); 15942 } 15943 15944 15945 function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) { 15946 try { 15947 linkFn(scope, $element, attrs, controllers, transcludeFn); 15948 } catch (e) { 15949 $exceptionHandler(e, startingTag($element)); 15950 } 15951 } 15952 15953 15954 // Set up $watches for isolate scope and controller bindings. 15955 function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) { 15956 var removeWatchCollection = []; 15957 var initialChanges = {}; 15958 var changes; 15959 forEach(bindings, function initializeBinding(definition, scopeName) { 15960 var attrName = definition.attrName, 15961 optional = definition.optional, 15962 mode = definition.mode, // @, =, <, or & 15963 lastValue, 15964 parentGet, parentSet, compare, removeWatch; 15965 15966 switch (mode) { 15967 15968 case '@': 15969 if (!optional && !hasOwnProperty.call(attrs, attrName)) { 15970 destination[scopeName] = attrs[attrName] = undefined; 15971 } 15972 removeWatch = attrs.$observe(attrName, function(value) { 15973 if (isString(value) || isBoolean(value)) { 15974 var oldValue = destination[scopeName]; 15975 recordChanges(scopeName, value, oldValue); 15976 destination[scopeName] = value; 15977 } 15978 }); 15979 attrs.$$observers[attrName].$$scope = scope; 15980 lastValue = attrs[attrName]; 15981 if (isString(lastValue)) { 15982 // If the attribute has been provided then we trigger an interpolation to ensure 15983 // the value is there for use in the link fn 15984 destination[scopeName] = $interpolate(lastValue)(scope); 15985 } else if (isBoolean(lastValue)) { 15986 // If the attributes is one of the BOOLEAN_ATTR then Angular will have converted 15987 // the value to boolean rather than a string, so we special case this situation 15988 destination[scopeName] = lastValue; 15989 } 15990 initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]); 15991 removeWatchCollection.push(removeWatch); 15992 break; 15993 15994 case '=': 15995 if (!hasOwnProperty.call(attrs, attrName)) { 15996 if (optional) break; 15997 attrs[attrName] = undefined; 15998 } 15999 if (optional && !attrs[attrName]) break; 16000 16001 parentGet = $parse(attrs[attrName]); 16002 if (parentGet.literal) { 16003 compare = equals; 16004 } else { 16005 // eslint-disable-next-line no-self-compare 16006 compare = function simpleCompare(a, b) { return a === b || (a !== a && b !== b); }; 16007 } 16008 parentSet = parentGet.assign || function() { 16009 // reset the change, or we will throw this exception on every $digest 16010 lastValue = destination[scopeName] = parentGet(scope); 16011 throw $compileMinErr('nonassign', 16012 'Expression \'{0}\' in attribute \'{1}\' used with directive \'{2}\' is non-assignable!', 16013 attrs[attrName], attrName, directive.name); 16014 }; 16015 lastValue = destination[scopeName] = parentGet(scope); 16016 var parentValueWatch = function parentValueWatch(parentValue) { 16017 if (!compare(parentValue, destination[scopeName])) { 16018 // we are out of sync and need to copy 16019 if (!compare(parentValue, lastValue)) { 16020 // parent changed and it has precedence 16021 destination[scopeName] = parentValue; 16022 } else { 16023 // if the parent can be assigned then do so 16024 parentSet(scope, parentValue = destination[scopeName]); 16025 } 16026 } 16027 lastValue = parentValue; 16028 return lastValue; 16029 }; 16030 parentValueWatch.$stateful = true; 16031 if (definition.collection) { 16032 removeWatch = scope.$watchCollection(attrs[attrName], parentValueWatch); 16033 } else { 16034 removeWatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal); 16035 } 16036 removeWatchCollection.push(removeWatch); 16037 break; 16038 16039 case '<': 16040 if (!hasOwnProperty.call(attrs, attrName)) { 16041 if (optional) break; 16042 attrs[attrName] = undefined; 16043 } 16044 if (optional && !attrs[attrName]) break; 16045 16046 parentGet = $parse(attrs[attrName]); 16047 var deepWatch = parentGet.literal; 16048 16049 var initialValue = destination[scopeName] = parentGet(scope); 16050 initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]); 16051 16052 removeWatch = scope.$watch(parentGet, function parentValueWatchAction(newValue, oldValue) { 16053 if (oldValue === newValue) { 16054 if (oldValue === initialValue || (deepWatch && equals(oldValue, initialValue))) { 16055 return; 16056 } 16057 oldValue = initialValue; 16058 } 16059 recordChanges(scopeName, newValue, oldValue); 16060 destination[scopeName] = newValue; 16061 }, deepWatch); 16062 16063 removeWatchCollection.push(removeWatch); 16064 break; 16065 16066 case '&': 16067 // Don't assign Object.prototype method to scope 16068 parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop; 16069 16070 // Don't assign noop to destination if expression is not valid 16071 if (parentGet === noop && optional) break; 16072 16073 destination[scopeName] = function(locals) { 16074 return parentGet(scope, locals); 16075 }; 16076 break; 16077 } 16078 }); 16079 16080 function recordChanges(key, currentValue, previousValue) { 16081 if (isFunction(destination.$onChanges) && currentValue !== previousValue && 16082 // eslint-disable-next-line no-self-compare 16083 (currentValue === currentValue || previousValue === previousValue)) { 16084 // If we have not already scheduled the top level onChangesQueue handler then do so now 16085 if (!onChangesQueue) { 16086 scope.$$postDigest(flushOnChangesQueue); 16087 onChangesQueue = []; 16088 } 16089 // If we have not already queued a trigger of onChanges for this controller then do so now 16090 if (!changes) { 16091 changes = {}; 16092 onChangesQueue.push(triggerOnChangesHook); 16093 } 16094 // If the has been a change on this property already then we need to reuse the previous value 16095 if (changes[key]) { 16096 previousValue = changes[key].previousValue; 16097 } 16098 // Store this change 16099 changes[key] = new SimpleChange(previousValue, currentValue); 16100 } 16101 } 16102 16103 function triggerOnChangesHook() { 16104 destination.$onChanges(changes); 16105 // Now clear the changes so that we schedule onChanges when more changes arrive 16106 changes = undefined; 16107 } 16108 16109 return { 16110 initialChanges: initialChanges, 16111 removeWatches: removeWatchCollection.length && function removeWatches() { 16112 for (var i = 0, ii = removeWatchCollection.length; i < ii; ++i) { 16113 removeWatchCollection[i](); 16114 } 16115 } 16116 }; 16117 } 16118 }]; 16119} 16120 16121function SimpleChange(previous, current) { 16122 this.previousValue = previous; 16123 this.currentValue = current; 16124} 16125SimpleChange.prototype.isFirstChange = function() { return this.previousValue === _UNINITIALIZED_VALUE; }; 16126 16127 16128var PREFIX_REGEXP = /^((?:x|data)[:\-_])/i; 16129/** 16130 * Converts all accepted directives format into proper directive name. 16131 * @param name Name to normalize 16132 */ 16133function directiveNormalize(name) { 16134 return camelCase(name.replace(PREFIX_REGEXP, '')); 16135} 16136 16137/** 16138 * @ngdoc type 16139 * @name $compile.directive.Attributes 16140 * 16141 * @description 16142 * A shared object between directive compile / linking functions which contains normalized DOM 16143 * element attributes. The values reflect current binding state `{{ }}`. The normalization is 16144 * needed since all of these are treated as equivalent in Angular: 16145 * 16146 * ``` 16147 * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a"> 16148 * ``` 16149 */ 16150 16151/** 16152 * @ngdoc property 16153 * @name $compile.directive.Attributes#$attr 16154 * 16155 * @description 16156 * A map of DOM element attribute names to the normalized name. This is 16157 * needed to do reverse lookup from normalized name back to actual name. 16158 */ 16159 16160 16161/** 16162 * @ngdoc method 16163 * @name $compile.directive.Attributes#$set 16164 * @kind function 16165 * 16166 * @description 16167 * Set DOM element attribute value. 16168 * 16169 * 16170 * @param {string} name Normalized element attribute name of the property to modify. The name is 16171 * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr} 16172 * property to the original name. 16173 * @param {string} value Value to set the attribute to. The value can be an interpolated string. 16174 */ 16175 16176 16177 16178/** 16179 * Closure compiler type information 16180 */ 16181 16182function nodesetLinkingFn( 16183 /* angular.Scope */ scope, 16184 /* NodeList */ nodeList, 16185 /* Element */ rootElement, 16186 /* function(Function) */ boundTranscludeFn 16187) {} 16188 16189function directiveLinkingFn( 16190 /* nodesetLinkingFn */ nodesetLinkingFn, 16191 /* angular.Scope */ scope, 16192 /* Node */ node, 16193 /* Element */ rootElement, 16194 /* function(Function) */ boundTranscludeFn 16195) {} 16196 16197function tokenDifference(str1, str2) { 16198 var values = '', 16199 tokens1 = str1.split(/\s+/), 16200 tokens2 = str2.split(/\s+/); 16201 16202 outer: 16203 for (var i = 0; i < tokens1.length; i++) { 16204 var token = tokens1[i]; 16205 for (var j = 0; j < tokens2.length; j++) { 16206 if (token === tokens2[j]) continue outer; 16207 } 16208 values += (values.length > 0 ? ' ' : '') + token; 16209 } 16210 return values; 16211} 16212 16213function removeComments(jqNodes) { 16214 jqNodes = jqLite(jqNodes); 16215 var i = jqNodes.length; 16216 16217 if (i <= 1) { 16218 return jqNodes; 16219 } 16220 16221 while (i--) { 16222 var node = jqNodes[i]; 16223 if (node.nodeType === NODE_TYPE_COMMENT || 16224 (node.nodeType === NODE_TYPE_TEXT && node.nodeValue.trim() === '')) { 16225 splice.call(jqNodes, i, 1); 16226 } 16227 } 16228 return jqNodes; 16229} 16230 16231var $controllerMinErr = minErr('$controller'); 16232 16233 16234var CNTRL_REG = /^(\S+)(\s+as\s+([\w$]+))?$/; 16235function identifierForController(controller, ident) { 16236 if (ident && isString(ident)) return ident; 16237 if (isString(controller)) { 16238 var match = CNTRL_REG.exec(controller); 16239 if (match) return match[3]; 16240 } 16241} 16242 16243 16244/** 16245 * @ngdoc provider 16246 * @name $controllerProvider 16247 * @this 16248 * 16249 * @description 16250 * The {@link ng.$controller $controller service} is used by Angular to create new 16251 * controllers. 16252 * 16253 * This provider allows controller registration via the 16254 * {@link ng.$controllerProvider#register register} method. 16255 */ 16256function $ControllerProvider() { 16257 var controllers = {}, 16258 globals = false; 16259 16260 /** 16261 * @ngdoc method 16262 * @name $controllerProvider#has 16263 * @param {string} name Controller name to check. 16264 */ 16265 this.has = function(name) { 16266 return controllers.hasOwnProperty(name); 16267 }; 16268 16269 /** 16270 * @ngdoc method 16271 * @name $controllerProvider#register 16272 * @param {string|Object} name Controller name, or an object map of controllers where the keys are 16273 * the names and the values are the constructors. 16274 * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI 16275 * annotations in the array notation). 16276 */ 16277 this.register = function(name, constructor) { 16278 assertNotHasOwnProperty(name, 'controller'); 16279 if (isObject(name)) { 16280 extend(controllers, name); 16281 } else { 16282 controllers[name] = constructor; 16283 } 16284 }; 16285 16286 /** 16287 * @ngdoc method 16288 * @name $controllerProvider#allowGlobals 16289 * 16290 * @deprecated 16291 * sinceVersion="v1.3.0" 16292 * removeVersion="v1.7.0" 16293 * This method of finding controllers has been deprecated. 16294 * 16295 * @description If called, allows `$controller` to find controller constructors on `window` * 16296 */ 16297 this.allowGlobals = function() { 16298 globals = true; 16299 }; 16300 16301 16302 this.$get = ['$injector', '$window', function($injector, $window) { 16303 16304 /** 16305 * @ngdoc service 16306 * @name $controller 16307 * @requires $injector 16308 * 16309 * @param {Function|string} constructor If called with a function then it's considered to be the 16310 * controller constructor function. Otherwise it's considered to be a string which is used 16311 * to retrieve the controller constructor using the following steps: 16312 * 16313 * * check if a controller with given name is registered via `$controllerProvider` 16314 * * check if evaluating the string on the current scope returns a constructor 16315 * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global 16316 * `window` object (not recommended) 16317 * 16318 * The string can use the `controller as property` syntax, where the controller instance is published 16319 * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this 16320 * to work correctly. 16321 * 16322 * @param {Object} locals Injection locals for Controller. 16323 * @return {Object} Instance of given controller. 16324 * 16325 * @description 16326 * `$controller` service is responsible for instantiating controllers. 16327 * 16328 * It's just a simple call to {@link auto.$injector $injector}, but extracted into 16329 * a service, so that one can override this service with [BC version](https://gist.github.com/1649788). 16330 */ 16331 return function $controller(expression, locals, later, ident) { 16332 // PRIVATE API: 16333 // param `later` --- indicates that the controller's constructor is invoked at a later time. 16334 // If true, $controller will allocate the object with the correct 16335 // prototype chain, but will not invoke the controller until a returned 16336 // callback is invoked. 16337 // param `ident` --- An optional label which overrides the label parsed from the controller 16338 // expression, if any. 16339 var instance, match, constructor, identifier; 16340 later = later === true; 16341 if (ident && isString(ident)) { 16342 identifier = ident; 16343 } 16344 16345 if (isString(expression)) { 16346 match = expression.match(CNTRL_REG); 16347 if (!match) { 16348 throw $controllerMinErr('ctrlfmt', 16349 'Badly formed controller string \'{0}\'. ' + 16350 'Must match `__name__ as __id__` or `__name__`.', expression); 16351 } 16352 constructor = match[1]; 16353 identifier = identifier || match[3]; 16354 expression = controllers.hasOwnProperty(constructor) 16355 ? controllers[constructor] 16356 : getter(locals.$scope, constructor, true) || 16357 (globals ? getter($window, constructor, true) : undefined); 16358 16359 if (!expression) { 16360 throw $controllerMinErr('ctrlreg', 16361 'The controller with the name \'{0}\' is not registered.', constructor); 16362 } 16363 16364 assertArgFn(expression, constructor, true); 16365 } 16366 16367 if (later) { 16368 // Instantiate controller later: 16369 // This machinery is used to create an instance of the object before calling the 16370 // controller's constructor itself. 16371 // 16372 // This allows properties to be added to the controller before the constructor is 16373 // invoked. Primarily, this is used for isolate scope bindings in $compile. 16374 // 16375 // This feature is not intended for use by applications, and is thus not documented 16376 // publicly. 16377 // Object creation: http://jsperf.com/create-constructor/2 16378 var controllerPrototype = (isArray(expression) ? 16379 expression[expression.length - 1] : expression).prototype; 16380 instance = Object.create(controllerPrototype || null); 16381 16382 if (identifier) { 16383 addIdentifier(locals, identifier, instance, constructor || expression.name); 16384 } 16385 16386 return extend(function $controllerInit() { 16387 var result = $injector.invoke(expression, instance, locals, constructor); 16388 if (result !== instance && (isObject(result) || isFunction(result))) { 16389 instance = result; 16390 if (identifier) { 16391 // If result changed, re-assign controllerAs value to scope. 16392 addIdentifier(locals, identifier, instance, constructor || expression.name); 16393 } 16394 } 16395 return instance; 16396 }, { 16397 instance: instance, 16398 identifier: identifier 16399 }); 16400 } 16401 16402 instance = $injector.instantiate(expression, locals, constructor); 16403 16404 if (identifier) { 16405 addIdentifier(locals, identifier, instance, constructor || expression.name); 16406 } 16407 16408 return instance; 16409 }; 16410 16411 function addIdentifier(locals, identifier, instance, name) { 16412 if (!(locals && isObject(locals.$scope))) { 16413 throw minErr('$controller')('noscp', 16414 'Cannot export controller \'{0}\' as \'{1}\'! No $scope object provided via `locals`.', 16415 name, identifier); 16416 } 16417 16418 locals.$scope[identifier] = instance; 16419 } 16420 }]; 16421} 16422 16423/** 16424 * @ngdoc service 16425 * @name $document 16426 * @requires $window 16427 * @this 16428 * 16429 * @description 16430 * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object. 16431 * 16432 * @example 16433 <example module="documentExample" name="document"> 16434 <file name="index.html">
16435 <div ng-controller="ExampleController"> 16436 <p>$document title: <b ng-bind="title"></b></p> 16437 <p>window.document title: <b ng-bind="windowTitle"></b></p> 16438 </div> 16439 </file> 16440 <file name="script.js"> 16441 angular.module('documentExample', []) 16442 .controller('ExampleController', ['$scope', '$document', function($scope, $document) { 16443 $scope.title = $document[0].title; 16444 $scope.windowTitle = angular.element(window.document)[0].title; 16445 }]); 16446 </file> 16447 </example> 16448 */ 16449function $DocumentProvider() { 16450 this.$get = ['$window', function(window) { 16451 return jqLite(window.document); 16452 }]; 16453} 16454 16455/** 16456 * @ngdoc service 16457 * @name $exceptionHandler 16458 * @requires ng.$log 16459 * @this 16460 * 16461 * @description 16462 * Any uncaught exception in angular expressions is delegated to this service. 16463 * The default implementation simply delegates to `$log.error` which logs it into 16464 * the browser console. 16465 * 16466 * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by 16467 * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing. 16468 * 16469 * ## Example: 16470 * 16471 * The example below will overwrite the default `$exceptionHandler` in order to (a) log uncaught 16472 * errors to the backend for later inspection by the developers and (b) to use `$log.warn()` instead 16473 * of `$log.error()`. 16474 * 16475 * ```js 16476 * angular. 16477 * module('exceptionOverwrite', []). 16478 * factory('$exceptionHandler', ['$log', 'logErrorsToBackend', function($log, logErrorsToBackend) { 16479 * return function myExceptionHandler(exception, cause) { 16480 * logErrorsToBackend(exception, cause); 16481 * $log.warn(exception, cause); 16482 * }; 16483 * }
16483]); 16484 * ``` 16485 * 16486 * <hr /> 16487 * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind` 16488 * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler} 16489 * (unless executed during a digest). 16490 * 16491 * If you wish, you can manually delegate exceptions, e.g. 16492 * `try { ... } catch(e) { $exceptionHandler(e); }` 16493 * 16494 * @param {Error} exception Exception associated with the error. 16495 * @param {string=} cause Optional information about the context in which 16496 * the error was thrown. 16497 * 16498 */ 16499function $ExceptionHandlerProvider() { 16500 this.$get = ['$log', function($log) { 16501 return function(exception, cause) { 16502 $log.error.apply($log, arguments); 16503 }; 16504 }]; 16505} 16506 16507var $$ForceReflowProvider = /** @this */ function() { 16508 this.$get = ['$document', function($document) { 16509 return function(domNode) { 16510 //the line below will force the browser to perform a repaint so 16511 //that all the animated elements within the animation frame will 16512 //be properly updated and drawn on screen. This is required to 16513 //ensure that the preparation animation is properly flushed so that 16514 //the active state picks up from there. DO NOT REMOVE THIS LINE. 16515 //DO NOT OPTIMIZE THIS LINE. THE MINIFIER WILL REMOVE IT OTHERWISE WHICH 16516 //WILL RESULT IN AN UNPREDICTABLE BUG THAT IS VERY HARD TO TRACK DOWN AND 16517 //WILL TAKE YEARS AWAY FROM YOUR LIFE. 16518 if (domNode) { 16519 if (!domNode.nodeType && domNode instanceof jqLite) { 16520 domNode = domNode[0]; 16521 } 16522 } else { 16523 domNode = $document[0].body; 16524 } 16525 return domNode.offsetWidth + 1; 16526 }; 16527 }]; 16528}; 16529 16530var APPLICATION_JSON = 'application/json'; 16531var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'}; 16532var JSON_START = /^\[|^\{(?!\{)/; 16533var JSON_ENDS = { 16534 '[': /]$/, 16535 '{': /}$/ 16536}; 16537var JSON_PROTECTION_PREFIX = /^\)]\}',?\n/; 16538var $httpMinErr = minErr('$http'); 16539var $httpMinErrLegacyFn = function(method) { 16540 return function() { 16541 throw $httpMinErr('legacy', 'The method `{0}` on the promise returned from `$http` has been disabled.', method); 16542 }; 16543}; 16544 16545function serializeValue(v) { 16546 if (isObject(v)) { 16547 return isDate(v) ? v.toISOString() : toJson(v); 16548 } 16549 return v; 16550} 16551 16552 16553/** @this */ 16554function $HttpParamSerializerProvider() { 16555 /** 16556 * @ngdoc service 16557 * @name $httpParamSerializer 16558 * @description 16559 * 16560 * Default {@link $http `$http`} params serializer that converts objects to strings 16561 * according to the following rules: 16562 * 16563 * * `{'foo': 'bar'}` results in `foo=bar` 16564 * * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object) 16565 * * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element) 16566 * * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D` (stringified and encoded representation of an object) 16567 * 16568 * Note that serializer will sort the request parameters alphabetically. 16569 * */ 16570 16571 this.$get = function() { 16572 return function ngParamSerializer(params) { 16573 if (!params) return ''; 16574 var parts = []; 16575 forEachSorted(params, function(value, key) { 16576 if (value === null || isUndefined(value)) return; 16577 if (isArray(value)) { 16578 forEach(value, function(v) { 16579 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v))); 16580 }); 16581 } else { 16582 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value))); 16583 } 16584 }); 16585 16586 return parts.join('&'); 16587 }; 16588 }; 16589} 16590 16591/** @this */ 16592function $HttpParamSerializerJQLikeProvider() { 16593 /** 16594 * @ngdoc service 16595 * @name $httpParamSerializerJQLike 16596 * 16597 * @description 16598 * 16599 * Alternative {@link $http `$http`} params serializer that follows 16600 * jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic. 16601 * The serializer will also sort the params alphabetically. 16602 * 16603 * To use it for serializing `$http` request parameters, set it as the `paramSerializer` property: 16604 * 16605 * ```js 16606 * $http({ 16607 * url: myUrl, 16608 * method: 'GET', 16609 * params: myParams, 16610 * paramSerializer: '$httpParamSerializerJQLike' 16611 * }); 16612 * ``` 16613 * 16614 * It is also possible to set it as the default `paramSerializer` in the 16615 * {@link $httpProvider#defaults `$httpProvider`}. 16616 * 16617 * Additionally, you can inject the serializer and use it explicitly, for example to serialize 16618 * form data for submission: 16619 * 16620 * ```js 16621 * .controller(function($http, $httpParamSerializerJQLike) { 16622 * //... 16623 * 16624 * $http({ 16625 * url: myUrl, 16626 * method: 'POST', 16627 * data: $httpParamSerializerJQLike(myData), 16628 * headers: { 16629 * 'Content-Type': 'application/x-www-form-urlencoded' 16630 * } 16631 * }); 16632 * 16633 * }); 16634 * ``` 16635 * 16636 * */ 16637 this.$get = function() { 16638 return function jQueryLikeParamSerializer(params) { 16639 if (!params) return ''; 16640 var parts = []; 16641 serialize(params, '', true); 16642 return parts.join('&'); 16643 16644 function serialize(toSerialize, prefix, topLevel) { 16645 if (toSerialize === null || isUndefined(toSerialize)) return; 16646 if (isArray(toSerialize)) {
16647 forEach(toSerialize, function(value, index) { 16648 serialize(value, prefix + '[' + (isObject(value) ? index : '') + ']'); 16649 }); 16650 } else if (isObject(toSerialize) && !isDate(toSerialize)) { 16651 forEachSorted(toSerialize, function(value, key) { 16652 serialize(value, prefix + 16653 (topLevel ? '' : '[') + 16654 key + 16655 (topLevel ? '' : ']')); 16656 }); 16657 } else { 16658 parts.push(encodeUriQuery(prefix) + '=' + encodeUriQuery(serializeValue(toSerialize))); 16659 } 16660 } 16661 }; 16662 }; 16663} 16664 16665function defaultHttpResponseTransform(data, headers) { 16666 if (isString(data)) { 16667 // Strip json vulnerability protection prefix and trim whitespace 16668 var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim(); 16669 16670 if (tempData) { 16671 var contentType = headers('Content-Type'); 16672 if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) { 16673 data = fromJson(tempData); 16674 } 16675 } 16676 } 16677 16678 return data; 16679} 16680 16681function isJsonLike(str) { 16682 var jsonStart = str.match(JSON_START); 16683 return jsonStart && JSON_ENDS[jsonStart[0]].test(str); 16684} 16685 16686/** 16687 * Parse headers into key value object 16688 * 16689 * @param {string} headers Raw headers as a string 16690 * @returns {Object} Parsed headers as key value object 16691 */ 16692function parseHeaders(headers) { 16693 var parsed = createMap(), i; 16694 16695 function fillInParsed(key, val) { 16696 if (key) { 16697 parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; 16698 } 16699 } 16700 16701 if (isString(headers)) { 16702 forEach(headers.split('\n'), function(line) { 16703 i = line.indexOf(':');
vendor: 8,606 bytes, lines 16704-16958
16704 fillInParsed(lowercase(trim(line.substr(0, i))), trim(line.substr(i + 1))); 16705 }); 16706 } else if (isObject(headers)) { 16707 forEach(headers, function(headerVal, headerKey) { 16708 fillInParsed(lowercase(headerKey), trim(headerVal)); 16709 }); 16710 } 16711 16712 return parsed; 16713} 16714 16715 16716/** 16717 * Returns a function that provides access to parsed headers. 16718 * 16719 * Headers are lazy parsed when first requested. 16720 * @see parseHeaders 16721 * 16722 * @param {(string|Object)} headers Headers to provide access to. 16723 * @returns {function(string=)} Returns a getter function which if called with: 16724 * 16725 * - if called with an argument returns a single header value or null 16726 * - if called with no arguments returns an object containing all headers. 16727 */ 16728function headersGetter(headers) { 16729 var headersObj; 16730 16731 return function(name) { 16732 if (!headersObj) headersObj = parseHeaders(headers); 16733 16734 if (name) { 16735 var value = headersObj[lowercase(name)]; 16736 if (value === undefined) { 16737 value = null; 16738 } 16739 return value; 16740 } 16741 16742 return headersObj; 16743 }; 16744} 16745 16746 16747/** 16748 * Chain all given functions 16749 * 16750 * This function is used for both request and response transforming 16751 * 16752 * @param {*} data Data to transform. 16753 * @param {function(string=)} headers HTTP headers getter fn. 16754 * @param {number} status HTTP status code of the response. 16755 * @param {(Function|Array.<Function>)} fns Function or an array of functions. 16756 * @returns {*} Transformed data. 16757 */ 16758function transformData(data, headers, status, fns) { 16759 if (isFunction(fns)) { 16760 return fns(data, headers, status); 16761 } 16762 16763 forEach(fns, function(fn) { 16764 data = fn(data, headers, status); 16765 }); 16766 16767 return data; 16768} 16769 16770 16771function isSuccess(status) { 16772 return 200 <= status && status < 300; 16773} 16774 16775 16776/** 16777 * @ngdoc provider 16778 * @name $httpProvider 16779 * @this 16780 * 16781 * @description 16782 * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service. 16783 * */ 16784function $HttpProvider() { 16785 /** 16786 * @ngdoc property 16787 * @name $httpProvider#defaults 16788 * @description 16789 * 16790 * Object containing default values for all {@link ng.$http $http} requests. 16791 * 16792 * - **`defaults.cache`** - {boolean|Object} - A boolean value or object created with 16793 * {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of HTTP responses 16794 * by default. See {@link $http#caching $http Caching} for more information. 16795 * 16796 * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token. 16797 * Defaults value is `'XSRF-TOKEN'`. 16798 * 16799 * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the 16800 * XSRF token. Defaults value is `'X-XSRF-TOKEN'`. 16801 * 16802 * - **`defaults.headers`** - {Object} - Default headers for all $http requests. 16803 * Refer to {@link ng.$http#setting-http-headers $http} for documentation on 16804 * setting default headers. 16805 * - **`defaults.headers.common`** 16806 * - **`defaults.headers.post`** 16807 * - **`defaults.headers.put`** 16808 * - **`defaults.headers.patch`** 16809 * 16810 * 16811 * - **`defaults.paramSerializer`** - `{string|function(Object<string,string>):string}` - A function 16812 * used to the prepare string representation of request parameters (specified as an object). 16813 * If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}. 16814 * Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}. 16815 * 16816 **/ 16817 var defaults = this.defaults = { 16818 // transform incoming response data 16819 transformResponse: [defaultHttpResponseTransform], 16820 16821 // transform outgoing request data 16822 transformRequest: [function(d) { 16823 return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d; 16824 }], 16825 16826 // default headers 16827 headers: { 16828 common: { 16829 'Accept': 'application/json, text/plain, */*' 16830 }, 16831 post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), 16832 put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), 16833 patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON) 16834 }, 16835 16836 xsrfCookieName: 'XSRF-TOKEN', 16837 xsrfHeaderName: 'X-XSRF-TOKEN', 16838 16839 paramSerializer: '$httpParamSerializer' 16840 }; 16841 16842 var useApplyAsync = false; 16843 /** 16844 * @ngdoc method 16845 * @name $httpProvider#useApplyAsync 16846 * @description 16847 * 16848 * Configure $http service to combine processing of multiple http responses received at around 16849 * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in 16850 * significant performance improvement for bigger applications that make many HTTP requests 16851 * concurrently (common during application bootstrap). 16852 * 16853 * Defaults to false. If no value is specified, returns the current configured value. 16854 * 16855 * @param {boolean=} value If true, when requests are loaded, they will schedule a deferred 16856 * "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window 16857 * to load and share the same digest cycle. 16858 * 16859 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining. 16860 * otherwise, returns the current configured value. 16861 **/ 16862 this.useApplyAsync = function(value) { 16863 if (isDefined(value)) { 16864 useApplyAsync = !!value; 16865 return this; 16866 } 16867 return useApplyAsync; 16868 }; 16869 16870 var useLegacyPromise = true; 16871 /** 16872 * @ngdoc method 16873 * @name $httpProvider#useLegacyPromiseExtensions 16874 * @description 16875 * 16876 * @deprecated 16877 * sinceVersion="v1.4.4" 16878 * removeVersion="v1.6.0" 16879 * This method will be removed in v1.6.0 along with the legacy promise methods. 16880 * 16881 * Configure `$http` service to return promises without the shorthand methods `success` and `error`. 16882 * This should be used to make sure that applications work without these methods. 16883 * 16884 * Defaults to true. If no value is specified, returns the current configured value. 16885 * 16886 * @param {boolean=} value If true, `$http` will return a promise with the deprecated legacy `success` and `error` methods. 16887 * 16888 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining. 16889 * otherwise, returns the current configured value. 16890 **/ 16891 this.useLegacyPromiseExtensions = function(value) { 16892 if (isDefined(value)) { 16893 useLegacyPromise = !!value; 16894 return this; 16895 } 16896 return useLegacyPromise; 16897 }; 16898 16899 /** 16900 * @ngdoc property 16901 * @name $httpProvider#interceptors 16902 * @description 16903 * 16904 * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http} 16905 * pre-processing of request or postprocessing of responses. 16906 * 16907 * These service factories are ordered by request, i.e. they are applied in the same order as the 16908 * array, on request, but reverse order, on response. 16909 * 16910 * {@link ng.$http#interceptors Interceptors detailed info} 16911 **/ 16912 var interceptorFactories = this.interceptors = []; 16913 16914 this.$get = ['$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector', 16915 function($httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector) { 16916 16917 var defaultCache = $cacheFactory('$http'); 16918 16919 /** 16920 * Make sure that default param serializer is exposed as a function 16921 */ 16922 defaults.paramSerializer = isString(defaults.paramSerializer) ? 16923 $injector.get(defaults.paramSerializer) : defaults.paramSerializer; 16924 16925 /** 16926 * Interceptors stored in reverse order. Inner interceptors before outer interceptors. 16927 * The reversal is needed so that we can build up the interception chain around the 16928 * server request. 16929 */ 16930 var reversedInterceptors = []; 16931 16932 forEach(interceptorFactories, function(interceptorFactory) { 16933 reversedInterceptors.unshift(isString(interceptorFactory) 16934 ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); 16935 }); 16936 16937 /** 16938 * @ngdoc service 16939 * @kind function 16940 * @name $http 16941 * @requires ng.$httpBackend 16942 * @requires $cacheFactory 16943 * @requires $rootScope 16944 * @requires $q 16945 * @requires $injector 16946 * 16947 * @description 16948 * The `$http` service is a core Angular service that facilitates communication with the remote 16949 * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest) 16950 * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP). 16951 * 16952 * For unit testing applications that use `$http` service, see 16953 * {@link ngMock.$httpBackend $httpBackend mock}. 16954 * 16955 * For a higher level of abstraction, please check out the {@link ngResource.$resource 16956 * $resource} service. 16957 * 16958 * The $http API is based on the {@link ng.$q deferred/promise APIs}
16958 exposed by 16959 * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage 16960 * it is important to familiarize yourself with these APIs and the guarantees they provide. 16961 * 16962 * 16963 * ## General usage 16964 * The `$http` service is a function which takes a single argument â a {@link $http#usage configuration object} â 16965 * that is used to generate an HTTP request and returns a {@link ng.$q promise}. 16966 * 16967 * ```js 16968 * // Simple GET request example: 16969 * $http({ 16970 * method: 'GET', 16971 * url: '/someUrl' 16972 * }).then(function successCallback(response) { 16973 * // this callback will be called asynchronously 16974 * // when the response is available 16975 * }, function errorCallback(response) { 16976 * // called asynchronously if an error occurs 16977 * // or server returns response with an error status. 16978 * }); 16979 * ``` 16980 * 16981 * The response object has these properties: 16982 * 16983 * - **data** â `{string|Object}` â The response body transformed with the transform 16984 * functions. 16985 * - **status** â `{number}` â HTTP status code of the response. 16986 * - **headers** â `{function([headerName])}` â Header getter function. 16987 * - **config** â `{Object}` â The configuration object that was used to generate the request. 16988 * - **statusText** â `{string}` â HTTP status text of the response. 16989 * 16990 * A response status code between 200 and 299 is considered a success status and will result in 16991 * the success callback being called. Any response status code outside of that range is 16992 * considered an error status and will result in the error callback being called. 16993 * Also, status codes less than -1 are normalized to zero. -1 usually means the request was 16994 * aborted, e.g. using a `config.timeout`. 16995 * Note that if the response is a redirect, XMLHttpRequest will transparently follow it, meaning 16996 * that the outcome (success or error) will be determined by the final response status code. 16997 * 16998 * 16999 * ## Shortcut methods 17000 * 17001 * Shortcut methods are also available. All shortcut methods require passing in the URL, and 17002 * request data must be passed in for POST/PUT requests. An optional config can be passed as the 17003 * last argument. 17004 * 17005 * ```js 17006 * $http.get('/someUrl', config).then(successCallback, errorCallback); 17007 * $http.post('/someUrl', data, config).then(successCallback, errorCallback); 17008 * ``` 17009 * 17010 * Complete list of shortcut methods: 17011 * 17012 * - {@link ng.$http#get $http.get} 17013 * - {@link ng.$http#head $http.head} 17014 * - {@link ng.$http#post $http.post} 17015 * - {@link ng.$http#put $http.put} 17016 * - {@link ng.$http#delete $http.delete} 17017 * - {@link ng.$http#jsonp $http.jsonp} 17018 * - {@link ng.$http#patch $http.patch} 17019 * 17020 * 17021 * ## Writing Unit Tests that use $http 17022 * When unit testing (using {@link ngMock ngMock}), it is necessary to call 17023 * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending 17024 * request using trained responses. 17025 * 17026 * ``` 17027 * $httpBackend.expectGET(...); 17028 * $http.get(...); 17029 * $httpBackend.flush(); 17030 * ``` 17031 * 17032 * ## Deprecation Notice 17033 * <div class="alert alert-danger"> 17034 * The `$http` legacy promise methods `success` and `error` have been deprecated and will be 17035 * removed in v1.6.0. 17036 * Use the standard `then` method instead. 17037 * If {@link $httpProvider#useLegacyPromiseExtensions `$httpProvider.useLegacyPromiseExtensions`} is set to 17038 * `false` then these methods will throw {@link $http:legacy `$http/legacy`} error. 17039 * </div> 17040 * 17041 * ## Setting HTTP Headers 17042 * 17043 * The $http service will automatically add certain HTTP headers to all requests. These defaults 17044 * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration 17045 * object, which currently contains this default configuration: 17046 * 17047 * - `$httpProvider.defaults.headers.common` (headers that are common for all requests): 17048 * - <code>Accept: application/json, text/plain, \*/\*</code> 17049 * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests) 17050 * - `Content-Type: application/json` 17051 * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests) 17052 * - `Content-Type: application/json` 17053 *
17054 * To add or overwrite these defaults, simply add or remove a property from these configuration 17055 * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object 17056 * with the lowercased HTTP method name as the key, e.g. 17057 * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }`. 17058 * 17059 * The defaults can also be set at runtime via the `$http.defaults` object in the same 17060 * fashion. For example: 17061 * 17062 * ``` 17063 * module.run(function($http) { 17064 * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w'; 17065 * }); 17066 * ``` 17067 * 17068 * In addition, you can supply a `headers` property in the config object passed when 17069 * calling `$http(config)`, which overrides the defaults without changing them globally. 17070 * 17071 * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis, 17072 * Use the `headers` property, setting the desired header to `undefined`. For example: 17073 * 17074 * ```js 17075 * var req = { 17076 * method: 'POST', 17077 * url: 'http://example.com', 17078 * headers: { 17079 * 'Content-Type': undefined 17080 * }, 17081 * data: { test: 'test' } 17082 * } 17083 * 17084 * $http(req).then(function(){...}, function(){...}); 17085 * ``` 17086 * 17087 * ## Transforming Requests and Responses 17088 * 17089 * Both requests and responses can be transformed using transformation functions: `transformRequest` 17090 * and `transformResponse`. These properties can be a single function that returns 17091 * the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions, 17092 * which allows you to `push` or `unshift` a new transformation function into the transformation chain. 17093 * 17094 * <div class="alert alert-warning"> 17095 * **Note:** Angular does not make a copy of the `data` parameter before it is passed into the `transformRequest` pipeline. 17096 * That means changes to the properties of `data` are not local to the transform function (since Javascript passes objects by reference). 17097 * For example, when calling `$http.get(url, $scope.myObject)`, modifications to the object's properties in a transformRequest 17098 * function will be reflected on the scope and in any templates where the object is data-bound. 17099 * To prevent this, transform functions should have no side-effects. 17100 * If you need to modify properties, it is recommended to make a copy of the data, or create new object to return. 17101 * </div> 17102 * 17103 * ### Default Transformations 17104 * 17105 * The `$httpProvider` provider and `$http` service expose `defaults.transformRequest` and 17106 * `defaults.transformResponse` properties. If a request does not provide its own transformations 17107 * then these will be applied. 17108 * 17109 * You can augment or replace the default transformations by modifying these properties by adding to or 17110 * replacing the array. 17111 * 17112 * Angular provides the following default transformations: 17113 * 17114 * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`): 17115 * 17116 * - If the `data` property of the request configuration object contains an object, serialize it 17117 * into JSON format. 17118 * 17119 * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`): 17120 * 17121 * - If XSRF prefix is detected, strip it (see Security Considerations section below). 17122 * - If JSON response is detected,
vendor: 7,183 bytes, lines 17122-17282
17122 deserialize it using a JSON parser. 17123 * 17124 * 17125 * ### Overriding the Default Transformations Per Request 17126 * 17127 * If you wish to override the request/response transformations only for a single request then provide 17128 * `transformRequest` and/or `transformResponse` properties on the configuration object passed 17129 * into `$http`. 17130 * 17131 * Note that if you provide these properties on the config object the default transformations will be 17132 * overwritten. If you wish to augment the default transformations then you must include them in your 17133 * local transformation array. 17134 * 17135 * The following code demonstrates adding a new response transformation to be run after the default response 17136 * transformations have been run. 17137 * 17138 * ```js 17139 * function appendTransform(defaults, transform) { 17140 * 17141 * // We can't guarantee that the default transformation is an array 17142 * defaults = angular.isArray(defaults) ? defaults : [defaults]; 17143 * 17144 * // Append the new transformation to the defaults 17145 * return defaults.concat(transform); 17146 * } 17147 * 17148 * $http({ 17149 * url: '...', 17150 * method: 'GET', 17151 * transformResponse: appendTransform($http.defaults.transformResponse, function(value) { 17152 * return doTransform(value); 17153 * }) 17154 * }); 17155 * ``` 17156 * 17157 * 17158 * ## Caching 17159 * 17160 * {@link ng.$http `$http`} responses are not cached by default. To enable caching, you must 17161 * set the config.cache value or the default cache value to TRUE or to a cache object (created 17162 * with {@link ng.$cacheFactory `$cacheFactory`}). If defined, the value of config.cache takes 17163 * precedence over the default cache value. 17164 * 17165 * In order to: 17166 * * cache all responses - set the default cache value to TRUE or to a cache object 17167 * * cache a specific response - set config.cache value to TRUE or to a cache object 17168 * 17169 * If caching is enabled, but neither the default cache nor config.cache are set to a cache object, 17170 * then the default `$cacheFactory("$http")` object is used. 17171 * 17172 * The default cache value can be set by updating the 17173 * {@link ng.$http#defaults `$http.defaults.cache`} property or the 17174 * {@link $httpProvider#defaults `$httpProvider.defaults.cache`} property. 17175 * 17176 * When caching is enabled, {@link ng.$http `$http`} stores the response from the server using 17177 * the relevant cache object. The next time the same request is made, the response is returned 17178 * from the cache without sending a request to the server. 17179 * 17180 * Take note that: 17181 * 17182 * * Only GET and JSONP requests are cached. 17183 * * The cache key is the request URL including search parameters; headers are not considered. 17184 * * Cached responses are returned asynchronously, in the same way as responses from the server. 17185 * * If multiple identical requests are made using the same cache, which is not yet populated, 17186 * one request will be made to the server and remaining requests will return the same response. 17187 * * A cache-control header on the response does not affect if or how responses are cached. 17188 * 17189 * 17190 * ## Interceptors 17191 * 17192 * Before you start creating interceptors, be sure to understand the 17193 * {@link ng.$q $q and deferred/promise APIs}. 17194 * 17195 * For purposes of global error handling, authentication, or any kind of synchronous or 17196 * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be 17197 * able to intercept requests before they are handed to the server and 17198 * responses before they are handed over to the application code that 17199 * initiated these requests. The interceptors leverage the {@link ng.$q 17200 * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing. 17201 * 17202 * The interceptors are service factories that are registered with the `$httpProvider` by 17203 * adding them to the `$httpProvider.interceptors` array. The factory is called and 17204 * injected with dependencies (if specified) and returns the interceptor. 17205 * 17206 * There are two kinds of interceptors (and two kinds of rejection interceptors): 17207 * 17208 * * `request`: interceptors get called with a http {@link $http#usage config} object. The function is free to 17209 * modify the `config` object or create a new one. The function needs to return the `config` 17210 * object directly, or a promise containing the `config` or a new `config` object. 17211 * * `requestError`: interceptor gets called when a previous interceptor threw an error or 17212 * resolved with a rejection. 17213 * * `response`: interceptors get called with http `response` object. The function is free to 17214 * modify the `response` object or create a new one. The function needs to return the `response` 17215 * object directly, or as a promise containing the `response` or a new `response` object. 17216 * * `responseError`: interceptor gets called when a previous interceptor threw an error or 17217 * resolved with a rejection. 17218 * 17219 * 17220 * ```js 17221 * // register the interceptor as a service 17222 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { 17223 * return { 17224 * // optional method 17225 * 'request': function(config) { 17226 * // do something on success 17227 * return config; 17228 * }, 17229 * 17230 * // optional method 17231 * 'requestError': function(rejection) { 17232 * // do something on error 17233 * if (canRecover(rejection)) { 17234 * return responseOrNewPromise 17235 * } 17236 * return $q.reject(rejection); 17237 * }, 17238 * 17239 * 17240 * 17241 * // optional method 17242 * 'response': function(response) { 17243 * // do something on success 17244 * return response; 17245 * }, 17246 * 17247 * // optional method 17248 * 'responseError': function(rejection) { 17249 * // do something on error 17250 * if (canRecover(rejection)) { 17251 * return responseOrNewPromise 17252 * } 17253 * return $q.reject(rejection); 17254 * } 17255 * }; 17256 * }); 17257 * 17258 * $httpProvider.interceptors.push('myHttpInterceptor'); 17259 * 17260 * 17261 * // alternatively, register the interceptor via an anonymous factory 17262 * $httpProvider.interceptors.push(function($q, dependency1, dependency2) { 17263 * return { 17264 * 'request': function(config) { 17265 * // same as above 17266 * }, 17267 * 17268 * 'response': function(response) { 17269 * // same as above 17270 * } 17271 * }; 17272 * }); 17273 * ``` 17274 * 17275 * ## Security Considerations 17276 * 17277 * When designing web applications, consider security threats from: 17278 * 17279 * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 17280 * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) 17281 * 17282 * Both server and the client must cooperate in order to eliminate these threats. Angular
17282 comes 17283 * pre-configured with strategies that address these issues, but for this to work backend server 17284 * cooperation is required. 17285 * 17286 * ### JSON Vulnerability Protection 17287 * 17288 * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 17289 * allows third party website to turn your JSON resource URL into 17290 * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To 17291 * counter this your server can prefix all JSON requests with following string `")]}',\n"`. 17292 * Angular will automatically strip the prefix before processing it as JSON. 17293 * 17294 * For example if your server needs to return: 17295 * ```js 17296 * ['one','two'] 17297 * ``` 17298 * 17299 * which is vulnerable to attack, your server can return: 17300 * ```js 17301 * )]}', 17302 * ['one','two'] 17303 * ``` 17304 * 17305 * Angular will strip the prefix, before processing the JSON. 17306 * 17307 * 17308 * ### Cross Site Request Forgery (XSRF) Protection 17309 * 17310 * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is an attack technique by 17311 * which the attacker can trick an authenticated user into unknowingly executing actions on your 17312 * website. Angular provides a mechanism to counter XSRF. When performing XHR requests, the 17313 * $http service reads a token from a cookie (by default, `XSRF-TOKEN`) and sets it as an HTTP 17314 * header (`X-XSRF-TOKEN`). Since only JavaScript that runs on your domain could read the 17315 * cookie, your server can be assured that the XHR came from JavaScript running on your domain. 17316 * The header will not be set for cross-domain requests. 17317 * 17318 * To take advantage of this, your server needs to set a token in a JavaScript readable session 17319 * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the 17320 * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure 17321 * that only JavaScript running on your domain could have sent the request. The token must be 17322 * unique for each user and must be verifiable by the server (to prevent the JavaScript from 17323 * making up its own tokens). We recommend that the token is a digest of your site's 17324 * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography)) 17325 * for added security. 17326 * 17327 * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName 17328 * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time, 17329 * or the per-request config object. 17330 * 17331 * In order to prevent collisions in environments where multiple Angular apps share the 17332 * same domain or subdomain, we recommend that each application uses unique cookie name. 17333 * 17334 * @param {object} config Object describing the request to be made and how it should be 17335 * processed. The object has following properties: 17336 * 17337 * - **method** â `{string}` â HTTP method (e.g. 'GET', 'POST', etc) 17338 * - **url** â `{string}` â Absolute or relative URL of the resource that is being requested. 17339 * - **params** â `{Object.<string|Object>}` â Map of strings or objects which will be serialized 17340 * with the `paramSerializer` and appended as GET parameters. 17341 * - **data** â `{string|Object}` â Data to be sent as the request message data. 17342 * - **headers** â `{Object}` â Map of strings or functions which return strings representing 17343 * HTTP headers to send to the server. If the return value of a function is null, the 17344 * header will not be sent. Functions accept a config object as an argument. 17345 * - **eventHandlers** - `{Object}` - Event listeners to be bound to the XMLHttpRequest object. 17346 * To bind events to the XMLHttpRequest upload object, use `uploadEventHandlers`. 17347 * The handler will be called in the context of a `$apply` block. 17348 * - **uploadEventHandlers** - `{Object}` - Event listeners to be bound to the XMLHttpRequest upload 17349 * object. To bind events to the XMLHttpRequest object, use `eventHandlers`. 17350 * The handler will be called in the context of a `$apply` block. 17351 * - **xsrfHeaderName** â `{string}` â Name of HTTP header to populate with the XSRF token. 17352 * - **xsrfCookieName** â `{string}` â Name of cookie containing the XSRF token. 17353 * - **transformRequest** â 17354 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>
17354}` â 17355 * transform function or an array of such functions. The transform function takes the http 17356 * request body and headers and returns its transformed (typically serialized) version. 17357 * See {@link ng.$http#overriding-the-default-transformations-per-request 17358 * Overriding the Default Transformations} 17359 * - **transformResponse** â 17360 * `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}` â 17361 * transform function or an array of such functions. The transform function takes the http 17362 * response body, headers and status and returns its transformed (typically deserialized) version. 17363 * See {@link ng.$http#overriding-the-default-transformations-per-request 17364 * Overriding the Default Transformations} 17365 * - **paramSerializer** - `{string|function(Object<string,string>):string}` - A function used to 17366 * prepare the string representation of request parameters (specified as an object). 17367 * If specified as string, it is interpreted as function registered with the 17368 * {@link $injector $injector}, which means you can create your own serializer 17369 * by registering it as a {@link auto.$provide#service service}. 17370 * The default serializer is the {@link $httpParamSerializer $httpParamSerializer}; 17371 * alternatively, you can use the {@link $httpParamSerializerJQLike $httpParamSerializerJQLike} 17372 * - **cache** â `{boolean|Object}` â A boolean value or object created with 17373 * {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of the HTTP response. 17374 * See {@link $http#caching $http Caching} for more information. 17375 * - **timeout** â `{number|Promise}` â timeout in milliseconds, or {@link ng.$q promise} 17376 * that should abort the request when resolved. 17377 * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the 17378 * XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials) 17379 * for more information. 17380 * - **responseType** - `{string}` - see 17381 * [XMLHttpRequest.responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#xmlhttprequest-responsetype). 17382 * 17383 * @returns {HttpPromise} Returns a {@link ng.$q `Promise}` that will be resolved to a response object 17384 * when the request succeeds or fails. 17385 * 17386 * 17387 * @property {Array.<Object>} pendingRequests Array of config objects for currently pending 17388 * requests. This is primarily meant to be used for debugging purposes. 17389 * 17390 * 17391 * @example 17392<example module="httpExample" name="http-service"> 17393<file name="index.html"> 17394 <div ng-controller="FetchController"> 17395 <select ng-model="method" aria-label="Request method"> 17396 <option>GET</option> 17397 <option>JSONP</option> 17398 </select> 17399 <input type="text" ng-model="url" size="80" aria-label="URL" /> 17400 <button id="fetchbtn" ng-click="fetch()">fetch</button><br> 17401 <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button> 17402 <button id="samplejsonpbtn" 17403 ng-click="updateModel('JSONP', 17404 'https://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')"> 17405 Sample JSONP 17406 </button> 17407 <button id="invalidjsonpbtn" 17408 ng-click="updateModel('JSONP', 'https://angularjs.org/doesntexist&callback=JSON_CALLBACK')"> 17409 Invalid JSONP 17410 </button> 17411 <pre>http status code: {{status}}</pre> 17412 <pre>http response data: {{data}}</pre> 17413 </div> 17414</file> 17415<file name="script.js"> 17416 angular.module('httpExample', []) 17417 .controller('FetchController', ['$scope', '$http', '$templateCache', 17418 function($scope, $http, $templateCache) { 17419 $scope.method = 'GET'; 17420 $scope.url = 'http-hello.html'; 17421 17422 $scope.fetch = function() { 17423 $scope.code = null; 17424 $scope.response = null; 17425 17426 $http({method: $scope.method, url: $scope.url, cache: $templateCache}). 17427 then(function(response) { 17428 $scope.status = response.status; 17429 $scope.data = response.data; 17430 }, function(response) { 17431 $scope.data = response.data || 'Request failed'; 17432 $scope.status = response.status; 17433 }); 17434 }; 17435 17436 $scope.updateModel = function(method, url) { 17437 $scope.method = method; 17438 $scope.url = url; 17439 }; 17440 }]); 17441</file> 17442<file name="http-hello.html"> 17443 Hello, $http! 17444</file> 17445<file name="protractor.js" type="protractor"> 17446 var status = element(by.binding('status')); 17447 var data = element(by.binding('data')); 17448 var fetchBtn = element(by.id('fetchbtn')); 17449 var sampleGetBtn = element(by.id('samplegetbtn')); 17450 var invalidJsonpBtn = element(by.id('invalidjsonpbtn')); 17451 17452 it('should make an xhr GET request', function() { 17453 sampleGetBtn.click(); 17454 fetchBtn.click(); 17455 expect(status.getText()).toMatch('200'); 17456 expect(data.getText()).toMatch(/Hello, \$http!/); 17457 }); 17458 17459// Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185 17460// it('should make a JSONP request to angularjs.org', function() { 17461// var sampleJsonpBtn = element(by.id('samplejsonpbtn')); 17462// sampleJsonpBtn.click(); 17463// fetchBtn.click(); 17464// expect(status.getText()).toMatch('200'); 17465// expect(data.getText()).toMatch(/Super Hero!/); 17466// }); 17467
17468 it('should make JSONP request to invalid URL and invoke the error handler', 17469 function() { 17470 invalidJsonpBtn.click(); 17471 fetchBtn.click(); 17472 expect(status.getText()).toMatch('0'); 17473 expect(data.getText()).toMatch('Request failed'); 17474 }); 17475</file> 17476</example> 17477 */ 17478 function $http(requestConfig) { 17479 17480 if (!isObject(requestConfig)) { 17481 throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig); 17482 } 17483 17484 if (!isString(requestConfig.url)) { 17485 throw minErr('$http')('badreq', 'Http request configuration url must be a string. Received: {0}', requestConfig.url); 17486 } 17487 17488 var config = extend({ 17489 method: 'get', 17490 transformRequest: defaults.transformRequest, 17491 transformResponse: defaults.transformResponse, 17492 paramSerializer: defaults.paramSerializer 17493 }, requestConfig); 17494 17495 config.headers = mergeHeaders(requestConfig); 17496 config.method = uppercase(config.method); 17497 config.paramSerializer = isString(config.paramSerializer) ? 17498 $injector.get(config.paramSerializer) : config.paramSerializer; 17499 17500 var requestInterceptors = []; 17501 var responseInterceptors = []; 17502 var promise = $q.when(config); 17503 17504 // apply interceptors 17505 forEach(reversedInterceptors, function(interceptor) { 17506 if (interceptor.request || interceptor.requestError) { 17507 requestInterceptors.unshift(interceptor.request, interceptor.requestError); 17508 } 17509 if (interceptor.response || interceptor.responseError) { 17510 responseInterceptors.push(interceptor.response, interceptor.responseError); 17511 } 17512 }); 17513 17514 promise = chainInterceptors(promise, requestInterceptors); 17515 promise = promise.then(serverRequest); 17516 promise = chainInterceptors(promise, responseInterceptors); 17517 17518 if (useLegacyPromise) { 17519 promise.success = function(fn) { 17520 assertArgFn(fn, 'fn'); 17521 17522 promise.then(function(response) { 17523 fn(response.data, response.status, response.headers, config); 17524 }); 17525 return promise; 17526 }; 17527 17528 promise.error = function(fn) { 17529 assertArgFn(fn, 'fn'); 17530 17531 promise.then(null, function(response) { 17532 fn(response.data, response.status, response.headers, config); 17533 }); 17534 return promise; 17535 }; 17536 } else { 17537 promise.success = $httpMinErrLegacyFn('success'); 17538 promise.error = $httpMinErrLegacyFn('error'); 17539 } 17540 17541 return promise; 17542 17543 17544 function chainInterceptors(promise, interceptors) { 17545 for (var i = 0, ii = interceptors.length; i < ii;) { 17546 var thenFn = interceptors[i++]; 17547 var rejectFn = interceptors[i++]; 17548 17549 promise = promise.then(thenFn, rejectFn); 17550 } 17551 17552 interceptors.length = 0; 17553 17554 return promise; 17555 } 17556 17557 function executeHeaderFns(headers, config) { 17558 var headerContent, processedHeaders = {}; 17559 17560 forEach(headers, function(headerFn, header) { 17561 if (isFunction(headerFn)) { 17562 headerContent = headerFn(config); 17563 if (headerContent != null) { 17564 processedHeaders[header] = headerContent; 17565 } 17566 } else { 17567 processedHeaders[header] = headerFn; 17568 } 17569 }); 17570 17571 return processedHeaders; 17572 } 17573 17574 function mergeHeaders(config) { 17575 var defHeaders = defaults.headers, 17576 reqHeaders = extend({}, config.headers), 17577 defHeaderName, lowercaseDefHeaderName, reqHeaderName; 17578 17579 defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]); 17580 17581 // using for-in instead of forEach to avoid unnecessary iteration after header has been found 17582 defaultHeadersIteration: 17583 for (defHeaderName in defHeaders) { 17584 lowercaseDefHeaderName = lowercase(defHeaderName); 17585 17586 for (reqHeaderName in reqHeaders) { 17587 if (lowercase(reqHeaderName) === lowercaseDefHeaderName) { 17588 continue defaultHeadersIteration; 17589 } 17590 } 17591 17592 reqHeaders[defHeaderName] = defHeaders[defHeaderName]; 17593 } 17594 17595 // execute if header value is a function for merged headers 17596 return executeHeaderFns(reqHeaders, shallowCopy(config)); 17597 } 17598
vendor: 14,919 bytes, lines 17599-18059
17599 function serverRequest(config) { 17600 var headers = config.headers; 17601 var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest); 17602 17603 // strip content-type if data is undefined 17604 if (isUndefined(reqData)) { 17605 forEach(headers, function(value, header) { 17606 if (lowercase(header) === 'content-type') { 17607 delete headers[header]; 17608 } 17609 }); 17610 } 17611 17612 if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) { 17613 config.withCredentials = defaults.withCredentials; 17614 } 17615 17616 // send request 17617 return sendReq(config, reqData).then(transformResponse, transformResponse); 17618 } 17619 17620 function transformResponse(response) { 17621 // make a copy since the response must be cacheable 17622 var resp = extend({}, response); 17623 resp.data = transformData(response.data, response.headers, response.status, 17624 config.transformResponse); 17625 return (isSuccess(response.status)) 17626 ? resp 17627 : $q.reject(resp); 17628 } 17629 } 17630 17631 $http.pendingRequests = []; 17632 17633 /** 17634 * @ngdoc method 17635 * @name $http#get 17636 * 17637 * @description 17638 * Shortcut method to perform `GET` request. 17639 * 17640 * @param {string} url Relative or absolute URL specifying the destination of the request 17641 * @param {Object=} config Optional configuration object 17642 * @returns {HttpPromise} Future object 17643 */ 17644 17645 /** 17646 * @ngdoc method 17647 * @name $http#delete 17648 * 17649 * @description 17650 * Shortcut method to perform `DELETE` request. 17651 * 17652 * @param {string} url Relative or absolute URL specifying the destination of the request 17653 * @param {Object=} config Optional configuration object 17654 * @returns {HttpPromise} Future object 17655 */ 17656 17657 /** 17658 * @ngdoc method 17659 * @name $http#head 17660 * 17661 * @description 17662 * Shortcut method to perform `HEAD` request. 17663 * 17664 * @param {string} url Relative or absolute URL specifying the destination of the request 17665 * @param {Object=} config Optional configuration object 17666 * @returns {HttpPromise} Future object 17667 */ 17668 17669 /** 17670 * @ngdoc method 17671 * @name $http#jsonp 17672 * 17673 * @description 17674 * Shortcut method to perform `JSONP` request. 17675 * If you would like to customize where and how the callbacks are stored then try overriding 17676 * or decorating the {@link $jsonpCallbacks} service. 17677 * 17678 * @param {string} url Relative or absolute URL specifying the destination of the request. 17679 * The name of the callback should be the string `JSON_CALLBACK`. 17680 * @param {Object=} config Optional configuration object 17681 * @returns {HttpPromise} Future object 17682 */ 17683 createShortMethods('get', 'delete', 'head', 'jsonp'); 17684 17685 /** 17686 * @ngdoc method 17687 * @name $http#post 17688 * 17689 * @description 17690 * Shortcut method to perform `POST` request. 17691 * 17692 * @param {string} url Relative or absolute URL specifying the destination of the request 17693 * @param {*} data Request content 17694 * @param {Object=} config Optional configuration object 17695 * @returns {HttpPromise} Future object 17696 */ 17697 17698 /** 17699 * @ngdoc method 17700 * @name $http#put 17701 * 17702 * @description 17703 * Shortcut method to perform `PUT` request. 17704 * 17705 * @param {string} url Relative or absolute URL specifying the destination of the request 17706 * @param {*} data Request content 17707 * @param {Object=} config Optional configuration object 17708 * @returns {HttpPromise} Future object 17709 */ 17710 17711 /** 17712 * @ngdoc method 17713 * @name $http#patch 17714 * 17715 * @description 17716 * Shortcut method to perform `PATCH` request. 17717 * 17718 * @param {string} url Relative or absolute URL specifying the destination of the request 17719 * @param {*} data Request content 17720 * @param {Object=} config Optional configuration object 17721 * @returns {HttpPromise} Future object 17722 */ 17723 createShortMethodsWithData('post', 'put', 'patch'); 17724 17725 /** 17726 * @ngdoc property 17727 * @name $http#defaults 17728 * 17729 * @description 17730 * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of 17731 * default headers, withCredentials as well as request and response transformations. 17732 * 17733 * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. 17734 */ 17735 $http.defaults = defaults; 17736 17737 17738 return $http; 17739 17740 17741 function createShortMethods(names) { 17742 forEach(arguments, function(name) { 17743 $http[name] = function(url, config) { 17744 return $http(extend({}, config || {}, { 17745 method: name, 17746 url: url 17747 })); 17748 }; 17749 }); 17750 } 17751 17752 17753 function createShortMethodsWithData(name) { 17754 forEach(arguments, function(name) { 17755 $http[name] = function(url, data, config) { 17756 return $http(extend({}, config || {}, { 17757 method: name, 17758 url: url, 17759 data: data 17760 })); 17761 }; 17762 }); 17763 } 17764 17765 17766 /** 17767 * Makes the request. 17768 * 17769 * !!! ACCESSES CLOSURE VARS: 17770 * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests 17771 */ 17772 function sendReq(config, reqData) { 17773 var deferred = $q.defer(), 17774 promise = deferred.promise, 17775 cache, 17776 cachedResp, 17777 reqHeaders = config.headers, 17778 url = buildUrl(config.url, config.paramSerializer(config.params)); 17779 17780 $http.pendingRequests.push(config); 17781 promise.then(removePendingReq, removePendingReq); 17782 17783 17784 if ((config.cache || defaults.cache) && config.cache !== false && 17785 (config.method === 'GET' || config.method === 'JSONP')) { 17786 cache = isObject(config.cache) ? config.cache 17787 : isObject(defaults.cache) ? defaults.cache 17788 : defaultCache; 17789 } 17790 17791 if (cache) { 17792 cachedResp = cache.get(url); 17793 if (isDefined(cachedResp)) { 17794 if (isPromiseLike(cachedResp)) { 17795 // cached request has already been sent, but there is no response yet 17796 cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult); 17797 } else { 17798 // serving from cache 17799 if (isArray(cachedResp)) { 17800 resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]); 17801 } else { 17802 resolvePromise(cachedResp, 200, {}, 'OK'); 17803 } 17804 } 17805 } else { 17806 // put the promise for the non-transformed response into cache as a placeholder 17807 cache.put(url, promise); 17808 } 17809 } 17810 17811 17812 // if we won't have the response in cache, set the xsrf headers and 17813 // send the request to the backend 17814 if (isUndefined(cachedResp)) { 17815 var xsrfValue = urlIsSameOrigin(config.url) 17816 ? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName] 17817 : undefined; 17818 if (xsrfValue) { 17819 reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue; 17820 } 17821 17822 $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, 17823 config.withCredentials, config.responseType, 17824 createApplyHandlers(config.eventHandlers), 17825 createApplyHandlers(config.uploadEventHandlers)); 17826 } 17827 17828 return promise; 17829 17830 function createApplyHandlers(eventHandlers) { 17831 if (eventHandlers) { 17832 var applyHandlers = {}; 17833 forEach(eventHandlers, function(eventHandler, key) { 17834 applyHandlers[key] = function(event) { 17835 if (useApplyAsync) { 17836 $rootScope.$applyAsync(callEventHandler); 17837 } else if ($rootScope.$$phase) { 17838 callEventHandler(); 17839 } else { 17840 $rootScope.$apply(callEventHandler); 17841 } 17842 17843 function callEventHandler() { 17844 eventHandler(event); 17845 } 17846 }; 17847 }); 17848 return applyHandlers; 17849 } 17850 } 17851 17852 17853 /** 17854 * Callback registered to $httpBackend(): 17855 * - caches the response if desired 17856 * - resolves the raw $http promise 17857 * - calls $apply 17858 */ 17859 function done(status, response, headersString, statusText) { 17860 if (cache) { 17861 if (isSuccess(status)) { 17862 cache.put(url, [status, response, parseHeaders(headersString), statusText]); 17863 } else { 17864 // remove promise from the cache 17865 cache.remove(url); 17866 } 17867 } 17868 17869 function resolveHttpPromise() { 17870 resolvePromise(response, status, headersString, statusText); 17871 } 17872 17873 if (useApplyAsync) { 17874 $rootScope.$applyAsync(resolveHttpPromise); 17875 } else { 17876 resolveHttpPromise(); 17877 if (!$rootScope.$$phase) $rootScope.$apply(); 17878 } 17879 } 17880 17881 17882 /** 17883 * Resolves the raw $http promise. 17884 */ 17885 function resolvePromise(response, status, headers, statusText) { 17886 //status: HTTP response status code, 0, -1 (aborted by timeout / promise) 17887 status = status >= -1 ? status : 0; 17888 17889 (isSuccess(status) ? deferred.resolve : deferred.reject)({ 17890 data: response, 17891 status: status, 17892 headers: headersGetter(headers), 17893 config: config, 17894 statusText: statusText 17895 }); 17896 } 17897 17898 function resolvePromiseWithResult(result) { 17899 resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText); 17900 } 17901 17902 function removePendingReq() { 17903 var idx = $http.pendingRequests.indexOf(config); 17904 if (idx !== -1) $http.pendingRequests.splice(idx, 1); 17905 } 17906 } 17907 17908 17909 function buildUrl(url, serializedParams) { 17910 if (serializedParams.length > 0) { 17911 url += ((url.indexOf('?') === -1) ? '?' : '&') + serializedParams; 17912 } 17913 return url; 17914 } 17915 }]; 17916} 17917 17918/** 17919 * @ngdoc service 17920 * @name $xhrFactory 17921 * @this 17922 * 17923 * @description 17924 * Factory function used to create XMLHttpRequest objects. 17925 * 17926 * Replace or decorate this service to create your own custom XMLHttpRequest objects. 17927 * 17928 * ``` 17929 * angular.module('myApp', []) 17930 * .factory('$xhrFactory', function() { 17931 * return function createXhr(method, url) { 17932 * return new window.XMLHttpRequest({mozSystem: true}); 17933 * }; 17934 * }); 17935 * ``` 17936 * 17937 * @param {string} method HTTP method of the request (GET, POST, PUT, ..) 17938 * @param {string} url URL of the request. 17939 */ 17940function $xhrFactoryProvider() { 17941 this.$get = function() { 17942 return function createXhr() { 17943 return new window.XMLHttpRequest(); 17944 }; 17945 }; 17946} 17947 17948/** 17949 * @ngdoc service 17950 * @name $httpBackend 17951 * @requires $jsonpCallbacks 17952 * @requires $document 17953 * @requires $xhrFactory 17954 * @this 17955 * 17956 * @description 17957 * HTTP backend used by the {@link ng.$http service} that delegates to 17958 * XMLHttpRequest object or JSONP and deals with browser incompatibilities. 17959 * 17960 * You should never need to use this service directly, instead use the higher-level abstractions: 17961 * {@link ng.$http $http} or {@link ngResource.$resource $resource}. 17962 * 17963 * During testing this implementation is swapped with {@link ngMock.$httpBackend mock 17964 * $httpBackend} which can be trained with responses. 17965 */ 17966function $HttpBackendProvider() { 17967 this.$get = ['$browser', '$jsonpCallbacks', '$document', '$xhrFactory', function($browser, $jsonpCallbacks, $document, $xhrFactory) { 17968 return createHttpBackend($browser, $xhrFactory, $browser.defer, $jsonpCallbacks, $document[0]); 17969 }]; 17970} 17971 17972function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) { 17973 // TODO(vojta): fix the signature 17974 return function(method, url, post, callback, headers, timeout, withCredentials, responseType, eventHandlers, uploadEventHandlers) { 17975 $browser.$$incOutstandingRequestCount(); 17976 url = url || $browser.url(); 17977 17978 if (lowercase(method) === 'jsonp') { 17979 var callbackPath = callbacks.createCallback(url); 17980 var jsonpDone = jsonpReq(url, callbackPath, function(status, text) { 17981 // jsonpReq only ever sets status to 200 (OK), 404 (ERROR) or -1 (WAITING) 17982 var response = (status === 200) && callbacks.getResponse(callbackPath); 17983 completeRequest(callback, status, response, '', text); 17984 callbacks.removeCallback(callbackPath); 17985 }); 17986 } else { 17987 17988 var xhr = createXhr(method, url); 17989 17990 xhr.open(method, url, true); 17991 forEach(headers, function(value, key) { 17992 if (isDefined(value)) { 17993 xhr.setRequestHeader(key, value); 17994 } 17995 }); 17996 17997 xhr.onload = function requestLoaded() { 17998 var statusText = xhr.statusText || ''; 17999 18000 // responseText is the old-school way of retrieving response (supported by IE9) 18001 // response/responseType properties were introduced in XHR Level2 spec (supported by IE10) 18002 var response = ('response' in xhr) ? xhr.response : xhr.responseText; 18003 18004 // normalize IE9 bug (http://bugs.jquery.com/ticket/1450) 18005 var status = xhr.status === 1223 ? 204 : xhr.status; 18006 18007 // fix status code when it is 0 (0 status is undocumented). 18008 // Occurs when accessing file resources or on Android 4.1 stock browser 18009 // while retrieving files from application cache. 18010 if (status === 0) { 18011 status = response ? 200 : urlResolve(url).protocol === 'file' ? 404 : 0; 18012 } 18013 18014 completeRequest(callback, 18015 status, 18016 response, 18017 xhr.getAllResponseHeaders(), 18018 statusText); 18019 }; 18020 18021 var requestError = function() { 18022 // The response is always empty 18023 // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error 18024 completeRequest(callback, -1, null, null, ''); 18025 }; 18026 18027 xhr.onerror = requestError; 18028 xhr.onabort = requestError; 18029 xhr.ontimeout = requestError; 18030 18031 forEach(eventHandlers, function(value, key) { 18032 xhr.addEventListener(key, value); 18033 }); 18034 18035 forEach(uploadEventHandlers, function(value, key) { 18036 xhr.upload.addEventListener(key, value); 18037 }); 18038 18039 if (withCredentials) { 18040 xhr.withCredentials = true; 18041 } 18042 18043 if (responseType) { 18044 try { 18045 xhr.responseType = responseType; 18046 } catch (e) { 18047 // WebKit added support for the json responseType value on 09/03/2013 18048 // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are 18049 // known to throw when setting the value "json" as the response type. Other older 18050 // browsers implementing the responseType 18051 // 18052 // The json response type can be ignored if not supported, because JSON payloads are 18053 // parsed on the client-side regardless. 18054 if (responseType !== 'json') { 18055 throw e; 18056 } 18057 } 18058 } 18059
18060 xhr.send(isUndefined(post) ? null : post); 18061 } 18062 18063 if (timeout > 0) { 18064 var timeoutId = $browserDefer(timeoutRequest, timeout); 18065 } else if (isPromiseLike(timeout)) { 18066 timeout.then(timeoutRequest); 18067 } 18068 18069 18070 function timeoutRequest() { 18071 if (jsonpDone) { 18072 jsonpDone(); 18073 } 18074 if (xhr) { 18075 xhr.abort(); 18076 } 18077 } 18078 18079 function completeRequest(callback, status, response, headersString, statusText) { 18080 // cancel timeout and subsequent timeout promise resolution 18081 if (isDefined(timeoutId)) { 18082 $browserDefer.cancel(timeoutId); 18083 } 18084 jsonpDone = xhr = null; 18085 18086 callback(status, response, headersString, statusText); 18087 $browser.$$completeOutstandingRequest(noop); 18088 } 18089 }; 18090 18091 function jsonpReq(url, callbackPath, done) { 18092 url = url.replace('JSON_CALLBACK', callbackPath); 18093 // we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.: 18094 // - fetches local scripts via XHR and evals them 18095 // - adds and immediately removes script elements from the document 18096 var script = rawDocument.createElement('script'), callback = null; 18097 script.type = 'text/javascript'; 18098 script.src = url; 18099 script.async = true; 18100 18101 callback = function(event) { 18102 removeEventListenerFn(script, 'load', callback); 18103 removeEventListenerFn(script, 'error', callback); 18104 rawDocument.body.removeChild(script); 18105 script = null; 18106 var status = -1; 18107 var text = 'unknown'; 18108 18109 if (event) { 18110 if (event.type === 'load' && !callbacks.wasCalled(callbackPath)) { 18111 event = { type: 'error' }; 18112 } 18113 text = event.type; 18114 status = event.type === 'error' ? 404 : 200; 18115 } 18116 18117 if (done) { 18118 done(status, text); 18119 } 18120 }; 18121 18122 addEventListenerFn(script, 'load', callback); 18123 addEventListenerFn(script, 'error', callback); 18124 rawDocument.body.appendChild(script); 18125 return callback; 18126 } 18127} 18128 18129var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate'); 18130$interpolateMinErr.throwNoconcat = function(text) { 18131 throw $interpolateMinErr('noconcat', 18132 'Error while interpolating: {0}\nStrict Contextual Escaping disallows ' + 18133 'interpolations that concatenate multiple expressions when a trusted value is ' + 18134 'required. See http://docs.angularjs.org/api/ng.$sce', text); 18135}; 18136 18137$interpolateMinErr.interr = function(text, err) { 18138 return $interpolateMinErr('interr', 'Can\'t interpolate: {0}\n{1}', text, err.toString()); 18139}; 18140 18141/** 18142 * @ngdoc provider 18143 * @name $interpolateProvider 18144 * @this 18145 * 18146 * @description 18147 *
18148 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. 18149 * 18150 * <div class="alert alert-danger"> 18151 * This feature is sometimes used to mix different markup languages, e.g. to wrap an Angular 18152 * template within a Python Jinja template (or any other template language). Mixing templating 18153 * languages is **very dangerous**. The embedding template language will not safely escape Angular 18154 * expressions, so any user-controlled values in the template will cause Cross Site Scripting (XSS) 18155 * security bugs! 18156 * </div> 18157 * 18158 * @example 18159<example name="custom-interpolation-markup" module="customInterpolationApp"> 18160<file name="index.html"> 18161<script> 18162 var customInterpolationApp = angular.module('customInterpolationApp', []); 18163 18164 customInterpolationApp.config(function($interpolateProvider) { 18165 $interpolateProvider.startSymbol('//'); 18166 $interpolateProvider.endSymbol('//'); 18167 }); 18168 18169 18170 customInterpolationApp.controller('DemoController', function() { 18171 this.label = "This binding is brought you by // interpolation symbols."; 18172 }); 18173</script> 18174<div ng-controller="DemoController as demo"> 18175 //demo.label// 18176</div> 18177</file> 18178<file name="protractor.js" type="protractor"> 18179 it('should interpolate binding with custom symbols', function() { 18180 expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.'); 18181 }); 18182</file> 18183</example> 18184 */ 18185function $InterpolateProvider() { 18186 var startSymbol = '{{'; 18187 var endSymbol = '}}'; 18188 18189 /** 18190 * @ngdoc method 18191 * @name $interpolateProvider#startSymbol 18192 * @description 18193 * Symbol to denote start of expression in the interpolated string. Defaults to `{{`. 18194 * 18195 * @param {string=} value new value to set the starting symbol to. 18196 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 18197 */ 18198 this.startSymbol = function(value) { 18199 if (value) { 18200 startSymbol = value; 18201 return this; 18202 } else { 18203 return startSymbol; 18204 } 18205 }; 18206 18207 /** 18208 * @ngdoc method 18209 * @name $interpolateProvider#endSymbol 18210 * @description 18211 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 18212 * 18213 * @param {string=} value new value to set the ending symbol to. 18214 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 18215 */ 18216 this.endSymbol = function(value) { 18217 if (value) { 18218 endSymbol = value; 18219 return this; 18220 } else { 18221 return endSymbol; 18222 } 18223 }; 18224 18225 18226 this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) { 18227 var startSymbolLength = startSymbol.length, 18228 endSymbolLength = endSymbol.length, 18229 escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'), 18230 escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g'); 18231 18232 function escape(ch) { 18233 return '\\\\\\' + ch; 18234 } 18235 18236 function unescapeText(text) { 18237 return text.replace(escapedStartRegexp, startSymbol). 18238 replace(escapedEndRegexp, endSymbol); 18239 } 18240 18241 function stringify(value) { 18242 if (value == null) { // null || undefined 18243 return ''; 18244 } 18245 switch (typeof value) { 18246 case 'string': 18247 break; 18248 case 'number': 18249 value = '' + value; 18250 break; 18251 default: 18252 value = toJson(value); 18253 } 18254 18255 return value; 18256 } 18257 18258 // TODO: this is the same as the constantWatchDelegate in parse.js 18259 function constantWatchDelegate(scope, listener, objectEquality, constantInterp) { 18260 var unwatch = scope.$watch(function constantInterpolateWatch(scope) { 18261 unwatch(); 18262 return constantInterp(scope); 18263 }, listener, objectEquality); 18264 return unwatch; 18265 } 18266 18267 /** 18268 * @ngdoc service 18269 * @name $interpolate 18270 * @kind function 18271 * 18272 * @requires $parse 18273 * @requires $sce 18274 * 18275 * @description 18276 * 18277 * Compiles a string with markup into an interpolation function. This service is used by the 18278 * HTML {@link ng.$compile $compile} service for data binding. See 18279 * {@link ng.$interpolateProvider $interpolateProvider} for configuring the 18280 * interpolation markup. 18281 * 18282 * 18283 * ```js 18284 * var $interpolate = ...; // injected 18285 * var exp = $interpolate('Hello {{name | uppercase}}!'); 18286 * expect(exp({name:'Angular'})).toEqual('Hello ANGULAR!'); 18287 * ``` 18288 * 18289 * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is 18290 * `true`, the interpolation function will return `undefined` unless all embedded expressions 18291 * evaluate to a value other than `undefined`. 18292 * 18293 * ```js 18294 * var $interpolate = ...; // injected 18295 * var context = {greeting: 'Hello', name: undefined }; 18296 * 18297 * // default "forgiving" mode 18298 * var exp = $interpolate('{{greeting}} {{name}}!'); 18299 * expect(exp(context)).toEqual('Hello !'); 18300 * 18301 * // "allOrNothing" mode 18302 * exp = $interpolate('{{greeting}} {{name}}!', false, null, true); 18303 * expect(exp(context)).toBeUndefined(); 18304 * context.name = 'Angular'; 18305 * expect(exp(context)).toEqual('Hello Angular!'); 18306 * ``` 18307 * 18308 * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior. 18309 * 18310 * #### Escaped Interpolation 18311 * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers 18312 * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash). 18313 * It will be rendered as a regular start/end marker, and will not be interpreted as an expression 18314 * or binding. 18315 * 18316 * This enables web-servers to prevent script injection attacks and defacing attacks, to some 18317 * degree, while also enabling code examples to work without relying on the 18318 * {@link ng.directive:ngNonBindable ngNonBindable} directive. 18319 * 18320 * **For security purposes, it is strongly encouraged that web servers escape user-supplied data, 18321 * replacing angle brackets (<, >) with &lt; and &gt; respectively, and replacing all 18322 * interpolation start/end markers with their escaped counterparts.** 18323 * 18324 * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered 18325 * output when the $interpolate service processes the text. So, for HTML elements interpolated 18326 * by {@link ng.$compile $compile}
18326, or otherwise interpolated with the `mustHaveExpression` parameter 18327 * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such, 18328 * this is typically useful only when user-data is used in rendering a template from the server, or 18329 * when otherwise untrusted data is used by a directive. 18330 * 18331 * <example name="interpolation"> 18332 * <file name="index.html"> 18333 * <div ng-init="username='A user'"> 18334 * <p ng-init="apptitle='Escaping demo'">{{apptitle}}: \{\{ username = "defaced value"; \}\} 18335 * </p> 18336 * <p><strong>{{username}}</strong> attempts to inject code which will deface the 18337 * application, but fails to accomplish their task, because the server has correctly 18338 * escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash) 18339 * characters.</p> 18340 * <p>Instead, the result of the attempted script injection is visible, and can be removed 18341 * from the database by an administrator.</p> 18342 * </div> 18343 * </file> 18344 * </example> 18345 * 18346 * @knownIssue 18347 * It is currently not possible for an interpolated expression to contain the interpolation end 18348 * symbol. For example, `{{ '}}' }}` will be incorrectly interpreted as `{{ ' }}` + `' }}`, i.e. 18349 * an interpolated expression consisting of a single-quote (`'`) and the `' }}` string. 18350 * 18351 * @knownIssue 18352 * All directives and components must use the standard `{{` `}}` interpolation symbols 18353 * in their templates. If you change the application interpolation symbols the {@link $compile} 18354 * service will attempt to denormalize the standard symbols to the custom symbols. 18355 * The denormalization process is not clever enough to know not to replace instances of the standard 18356 * symbols where they would not normally be treated as interpolation symbols. For example in the following 18357 * code snippet the closing braces of the literal object will get incorrectly denormalized: 18358 * 18359 * ``` 18360 * <div data-context='{"context":{"id":3,"type":"page"}}"> 18361 * ``` 18362 * 18363 * The workaround is to ensure that such instances are separated by whitespace: 18364 * ``` 18365 * <div data-context='{"context":{"id":3,"type":"page"} }"> 18366 * ``` 18367 * 18368 * See https://github.com/angular/angular.js/pull/14610#issuecomment-219401099 for more information. 18369 * 18370 * @param {string} text The text with markup to interpolate. 18371 * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have 18372 * embedded expression in order to return an interpolation function. Strings with no 18373 * embedded expression will return null for the interpolation function. 18374 * @param {string=} trustedContext when provided, the returned function passes the interpolated 18375 * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult, 18376 * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that 18377 * provides Strict Contextual Escaping for details. 18378 * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined 18379 * unless all embedded expressions evaluate to a value other than `undefined`. 18380 * @returns {function(context)} an interpolation function which is used to compute the 18381 * interpolated string. The function has these parameters: 18382 * 18383 * - `context`: evaluation context for all expressions embedded in the interpolated text 18384 */ 18385 function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) { 18386 // Provide a quick exit and simplified result function for text with no interpolation 18387 if (!text.length || text.indexOf(startSymbol) === -1) { 18388 var constantInterp; 18389 if (!mustHaveExpression) { 18390 var unescapedText = unescapeText(text); 18391 constantInterp = valueFn(unescapedText); 18392 constantInterp.exp = text; 18393 constantInterp.expressions = []; 18394 constantInterp.$$watchDelegate = constantWatchDelegate; 18395 } 18396 return constantInterp; 18397 } 18398 18399 allOrNothing = !!allOrNothing; 18400 var startIndex, 18401 endIndex, 18402 index = 0, 18403 expressions = [], 18404 parseFns = [], 18405 textLength = text.length, 18406 exp, 18407 concat = [], 18408 expressionPositions = []; 18409 18410 while (index < textLength) { 18411 if (((startIndex = text.indexOf(startSymbol, index)) !== -1) && 18412 ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) !== -1)) { 18413 if (index !== startIndex) { 18414 concat.push(unescapeText(text.substring(index, startIndex))); 18415 } 18416 exp = text.substring(startIndex + startSymbolLength, endIndex); 18417 expressions.push(exp); 18418 parseFns.push($parse(exp, parseStringifyInterceptor));
18419 index = endIndex + endSymbolLength; 18420 expressionPositions.push(concat.length); 18421 concat.push(''); 18422 } else { 18423 // we did not find an interpolation, so we have to add the remainder to the separators array 18424 if (index !== textLength) { 18425 concat.push(unescapeText(text.substring(index))); 18426 } 18427 break; 18428 } 18429 } 18430 18431 // Concatenating expressions makes it hard to reason about whether some combination of 18432 // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a 18433 // single expression be used for iframe[src], object[src], etc., we ensure that the value 18434 // that's used is assigned or constructed by some JS code somewhere that is more testable or 18435 // make it obvious that you bound the value to some user controlled value. This helps reduce 18436 // the load when auditing for XSS issues. 18437 if (trustedContext && concat.length > 1) { 18438 $interpolateMinErr.throwNoconcat(text); 18439 } 18440 18441 if (!mustHaveExpression || expressions.length) { 18442 var compute = function(values) { 18443 for (var i = 0, ii = expressions.length; i < ii; i++) { 18444 if (allOrNothing && isUndefined(values[i])) return; 18445 concat[expressionPositions[i]] = values[i]; 18446 } 18447 return concat.join(''); 18448 }; 18449 18450 var getValue = function(value) { 18451 return trustedContext ? 18452 $sce.getTrusted(trustedContext, value) : 18453 $sce.valueOf(value); 18454 }; 18455 18456 return extend(function interpolationFn(context) { 18457 var i = 0; 18458 var ii = expressions.length; 18459 var values = new Array(ii); 18460 18461 try { 18462 for (; i < ii; i++) { 18463 values[i] = parseFns[i](context); 18464 } 18465 18466 return compute(values); 18467 } catch (err) { 18468 $exceptionHandler($interpolateMinErr.interr(text, err)); 18469 } 18470 18471 }, { 18472 // all of these properties are undocumented for now 18473 exp: text, //just for compatibility with regular watchers created via $watch 18474 expressions: expressions, 18475 $$watchDelegate: function(scope, listener) { 18476 var lastValue; 18477 return scope.$watchGroup(parseFns, /** @this */ function interpolateFnWatcher(values, oldValues) { 18478 var currValue = compute(values); 18479 if (isFunction(listener)) { 18480 listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope); 18481 } 18482 lastValue = currValue; 18483 }); 18484 } 18485 }); 18486 } 18487 18488 function parseStringifyInterceptor(value) { 18489 try { 18490 value = getValue(value); 18491 return allOrNothing && !isDefined(value) ? value : stringify(value); 18492 } catch (err) { 18493 $exceptionHandler($interpolateMinErr.interr(text, err)); 18494 } 18495 } 18496 } 18497 18498 18499 /** 18500 * @ngdoc method 18501 * @name $interpolate#startSymbol 18502 * @description 18503 * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`. 18504 * 18505 * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change 18506 * the symbol. 18507 * 18508 * @returns {string} start symbol. 18509 */ 18510 $interpolate.startSymbol = function() { 18511 return startSymbol; 18512 }; 18513 18514 18515 /** 18516 * @ngdoc method 18517 * @name $interpolate#endSymbol 18518 * @description 18519 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 18520 * 18521 * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change 18522 * the symbol. 18523 * 18524 * @returns {string} end symbol. 18525 */ 18526 $interpolate.endSymbol = function() { 18527 return endSymbol; 18528 }; 18529 18530 return $interpolate; 18531 }]; 18532} 18533 18534/** @this */ 18535function $IntervalProvider() { 18536 this.$get = ['$rootScope', '$window', '$q', '$$q', '$browser', 18537 function($rootScope, $window, $q, $$q, $browser) { 18538 var intervals = {}; 18539 18540 18541 /** 18542 * @ngdoc service 18543 * @name $interval 18544 *
18545 * @description 18546 * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay` 18547 * milliseconds. 18548 * 18549 * The return value of registering an interval function is a promise. This promise will be 18550 * notified upon each tick of the interval, and will be resolved after `count` iterations, or 18551 * run indefinitely if `count` is not defined. The value of the notification will be the 18552 * number of iterations that have run. 18553 * To cancel an interval, call `$interval.cancel(promise)`. 18554 * 18555 * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to 18556 * move forward by `millis` milliseconds and trigger any functions scheduled to run in that 18557 * time. 18558 * 18559 * <div class="alert alert-warning"> 18560 * **Note**: Intervals created by this service must be explicitly destroyed when you are finished 18561 * with them. In particular they are not automatically destroyed when a controller's scope or a 18562 * directive's element are destroyed. 18563 * You should take this into consideration and make sure to always cancel the interval at the 18564 * appropriate moment. See the example below for more details on how and when to do this. 18565 * </div> 18566 * 18567 * @param {function()} fn A function that should be called repeatedly. If no additional arguments 18568 * are passed (see below), the function is called with the current iteration count. 18569 * @param {number} delay Number of milliseconds between each function call. 18570 * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat 18571 * indefinitely. 18572 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 18573 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 18574 * @param {...*=} Pass additional parameters to the executed function. 18575 * @returns {promise} A promise which will be notified on each iteration. 18576 * 18577 * @example 18578 * <example module="intervalExample" name="interval-service"> 18579 * <file name="index.html"> 18580 * <script> 18581 * angular.module('intervalExample', []) 18582 * .controller('ExampleController', ['$scope', '$interval', 18583 * function($scope, $interval) { 18584 * $scope.format = 'M/d/yy h:mm:ss a'; 18585 * $scope.blood_1 = 100; 18586 * $scope.blood_2 = 120; 18587 * 18588 * var stop; 18589 * $scope.fight = function() { 18590 * // Don't start a new fight if we are already fighting 18591 * if ( angular.isDefined(stop) ) return; 18592 * 18593 * stop = $interval(function() { 18594 * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) { 18595 * $scope.blood_1 = $scope.blood_1 - 3; 18596 * $scope.blood_2 = $scope.blood_2 - 4; 18597 * } else { 18598 * $scope.stopFight(); 18599 * } 18600 * }, 100); 18601 * }; 18602 * 18603 * $scope.stopFight = function() { 18604 * if (angular.isDefined(stop)) { 18605 * $interval.cancel(stop); 18606 * stop = undefined; 18607 * } 18608 * }; 18609 * 18610 * $scope.resetFight = function() { 18611 * $scope.blood_1 = 100; 18612 * $scope.blood_2 = 120; 18613 * }; 18614 * 18615 * $scope.$on('$destroy', function() { 18616 * // Make sure that the interval is destroyed too 18617 * $scope.stopFight(); 18618 * }); 18619 * }]) 18620 * // Register the 'myCurrentTime' directive factory method. 18621 * // We inject $interval and dateFilter service since the factory method is DI. 18622 * .directive('myCurrentTime', ['$interval', 'dateFilter', 18623 * function($interval, dateFilter) { 18624 * // return the directive link function. (compile function not needed) 18625 * return function(scope, element, attrs) { 18626 * var format, // date format 18627 * stopTime; // so that we can cancel the time updates 18628 * 18629 * // used to update the UI 18630 * function updateTime() { 18631 * element.text(dateFilter(new Date(), format)); 18632 * } 18633 * 18634 * // watch the expression, and update the UI on change. 18635 * scope.$watch(attrs.myCurrentTime, function(value) { 18636 * format = value; 18637 * updateTime(); 18638 * }); 18639 * 18640 * stopTime = $interval(updateTime, 1000); 18641 * 18642 * // listen on DOM destroy (removal) event, and cancel the next UI update 18643 * // to prevent updating time after the DOM element was removed. 18644 * element.on('$destroy', function() { 18645 * $interval.cancel(stopTime); 18646 * }); 18647 * } 18648 * }]); 18649 * </script> 18650 * 18651 * <div>
18652 * <div ng-controller="ExampleController"> 18653 * <label>Date format: <input ng-model="format"></label> <hr/> 18654 * Current time is: <span my-current-time="format"></span> 18655 * <hr/> 18656 * Blood 1 : <font color='red'>{{blood_1}}</font> 18657 * Blood 2 : <font color='red'>{{blood_2}}</font> 18658 * <button type="button" data-ng-click="fight()">Fight</button> 18659 * <button type="button" data-ng-click="stopFight()">StopFight</button> 18660 * <button type="button" data-ng-click="resetFight()">resetFight</button> 18661 * </div> 18662 * </div> 18663 * 18664 * </file> 18665 * </example> 18666 */ 18667 function interval(fn, delay, count, invokeApply) { 18668 var hasParams = arguments.length > 4, 18669 args = hasParams ? sliceArgs(arguments, 4) : [], 18670 setInterval = $window.setInterval, 18671 clearInterval = $window.clearInterval, 18672 iteration = 0, 18673 skipApply = (isDefined(invokeApply) && !invokeApply), 18674 deferred = (skipApply ? $$q : $q).defer(), 18675 promise = deferred.promise; 18676 18677 count = isDefined(count) ? count : 0; 18678 18679 promise.$$intervalId = setInterval(function tick() { 18680 if (skipApply) { 18681 $browser.defer(callback); 18682 } else { 18683 $rootScope.$evalAsync(callback); 18684 } 18685 deferred.notify(iteration++); 18686 18687 if (count > 0 && iteration >= count) { 18688 deferred.resolve(iteration); 18689 clearInterval(promise.$$intervalId); 18690 delete intervals[promise.$$intervalId]; 18691 } 18692 18693 if (!skipApply) $rootScope.$apply(); 18694 18695 }, delay); 18696 18697 intervals[promise.$$intervalId] = deferred; 18698 18699 return promise; 18700 18701 function callback() { 18702 if (!hasParams) { 18703 fn(iteration); 18704 } else { 18705 fn.apply(null, args); 18706 } 18707 } 18708 } 18709 18710 18711 /** 18712 * @ngdoc method 18713 * @name $interval#cancel 18714 * 18715 * @description 18716 * Cancels a task associated with the `promise`. 18717 * 18718 * @param {Promise=} promise returned by the `$interval` function. 18719 * @returns {boolean} Returns `true` if the task was successfully canceled. 18720 */ 18721 interval.cancel = function(promise) { 18722 if (promise && promise.$$intervalId in intervals) { 18723 intervals[promise.$$intervalId].reject('canceled'); 18724 $window.clearInterval(promise.$$intervalId); 18725 delete intervals[promise.$$intervalId]; 18726 return true; 18727 } 18728 return false; 18729 }; 18730 18731 return interval; 18732 }]; 18733} 18734 18735/** 18736 * @ngdoc service 18737 * @name $jsonpCallbacks 18738 * @requires $window 18739 * @description 18740 * This service handles the lifecycle of callbacks to handle JSONP requests. 18741 * Override this service if you wish to customise where the callbacks are stored and 18742 * how they vary compared to the requested url. 18743 */ 18744var $jsonpCallbacksProvider = /** @this */ function() { 18745 this.$get = ['$window', function($window) { 18746 var callbacks = $window.angular.callbacks; 18747 var callbackMap = {}; 18748 18749 function createCallback(callbackId) { 18750 var callback = function(data) { 18751 callback.data = data; 18752 callback.called = true; 18753 }; 18754 callback.id = callbackId; 18755 return callback; 18756 } 18757 18758 return { 18759 /** 18760 * @ngdoc method 18761 * @name $jsonpCallbacks#createCallback 18762 * @param {string} url the url of the JSONP request 18763 * @returns {string} the callback path to send to the server as part of the JSONP request 18764 * @description 18765 * {@link $httpBackend} calls this method to create a callback and get hold of the path to the callback 18766 * to pass to the server, which will be used to call the callback with its payload in the JSONP response. 18767 */ 18768 createCallback: function(url) { 18769 var callbackId = '_' + (callbacks.$$counter++).toString(36); 18770 var callbackPath = 'angular.callbacks.' + callbackId; 18771 var callback = createCallback(callbackId); 18772 callbackMap[callbackPath] = callbacks[callbackId] = callback; 18773 return callbackPath; 18774 }, 18775 /** 18776 * @ngdoc method 18777 * @name $jsonpCallbacks#wasCalled 18778 * @param {string} callbackPath the path to the callback that was sent in the JSONP request 18779 * @returns {boolean} whether the callback has been called, as a result of the JSONP response
vendor: 9,221 bytes, lines 18780-19091
18780 * @description 18781 * {@link $httpBackend} calls this method to find out whether the JSONP response actually called the 18782 * callback that was passed in the request. 18783 */ 18784 wasCalled: function(callbackPath) { 18785 return callbackMap[callbackPath].called; 18786 }, 18787 /** 18788 * @ngdoc method 18789 * @name $jsonpCallbacks#getResponse 18790 * @param {string} callbackPath the path to the callback that was sent in the JSONP request 18791 * @returns {*} the data received from the response via the registered callback 18792 * @description 18793 * {@link $httpBackend} calls this method to get hold of the data that was provided to the callback 18794 * in the JSONP response. 18795 */ 18796 getResponse: function(callbackPath) { 18797 return callbackMap[callbackPath].data; 18798 }, 18799 /** 18800 * @ngdoc method 18801 * @name $jsonpCallbacks#removeCallback 18802 * @param {string} callbackPath the path to the callback that was sent in the JSONP request 18803 * @description 18804 * {@link $httpBackend} calls this method to remove the callback after the JSONP request has 18805 * completed or timed-out. 18806 */ 18807 removeCallback: function(callbackPath) { 18808 var callback = callbackMap[callbackPath]; 18809 delete callbacks[callback.id]; 18810 delete callbackMap[callbackPath]; 18811 } 18812 }; 18813 }]; 18814}; 18815 18816/** 18817 * @ngdoc service 18818 * @name $locale 18819 * 18820 * @description 18821 * $locale service provides localization rules for various Angular components. As of right now the 18822 * only public api is: 18823 * 18824 * * `id` â `{string}` â locale id formatted as `languageId-countryId` (e.g. `en-us`) 18825 */ 18826 18827var PATH_MATCH = /^([^?#]*)(\?([^#]*))?(#(.*))?$/, 18828 DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21}; 18829var $locationMinErr = minErr('$location'); 18830 18831 18832/** 18833 * Encode path using encodeUriSegment, ignoring forward slashes 18834 * 18835 * @param {string} path Path to encode 18836 * @returns {string} 18837 */ 18838function encodePath(path) { 18839 var segments = path.split('/'), 18840 i = segments.length; 18841 18842 while (i--) { 18843 segments[i] = encodeUriSegment(segments[i]); 18844 } 18845 18846 return segments.join('/'); 18847} 18848 18849function parseAbsoluteUrl(absoluteUrl, locationObj) { 18850 var parsedUrl = urlResolve(absoluteUrl); 18851 18852 locationObj.$$protocol = parsedUrl.protocol; 18853 locationObj.$$host = parsedUrl.hostname; 18854 locationObj.$$port = toInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null; 18855} 18856 18857var DOUBLE_SLASH_REGEX = /^\s*[\\/]{2,}/; 18858function parseAppUrl(url, locationObj) { 18859 18860 if (DOUBLE_SLASH_REGEX.test(url)) { 18861 throw $locationMinErr('badpath', 'Invalid url "{0}".', url); 18862 } 18863 18864 var prefixed = (url.charAt(0) !== '/'); 18865 if (prefixed) { 18866 url = '/' + url; 18867 } 18868 var match = urlResolve(url); 18869 locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ? 18870 match.pathname.substring(1) : match.pathname); 18871 locationObj.$$search = parseKeyValue(match.search); 18872 locationObj.$$hash = decodeURIComponent(match.hash); 18873 18874 // make sure path starts with '/'; 18875 if (locationObj.$$path && locationObj.$$path.charAt(0) !== '/') { 18876 locationObj.$$path = '/' + locationObj.$$path; 18877 } 18878} 18879 18880function startsWith(str, search) { 18881 return str.slice(0, search.length) === search; 18882} 18883 18884/** 18885 * 18886 * @param {string} base 18887 * @param {string} url 18888 * @returns {string} returns text from `url` after `base` or `undefined` if it does not begin with 18889 * the expected string. 18890 */ 18891function stripBaseUrl(base, url) { 18892 if (startsWith(url, base)) { 18893 return url.substr(base.length); 18894 } 18895} 18896 18897 18898function stripHash(url) { 18899 var index = url.indexOf('#'); 18900 return index === -1 ? url : url.substr(0, index); 18901} 18902 18903function trimEmptyHash(url) { 18904 return url.replace(/(#.+)|#$/, '$1'); 18905} 18906 18907 18908function stripFile(url) { 18909 return url.substr(0, stripHash(url).lastIndexOf('/') + 1); 18910} 18911 18912/* return the server only (scheme://host:port) */ 18913function serverBase(url) { 18914 return url.substring(0, url.indexOf('/', url.indexOf('//') + 2)); 18915} 18916 18917 18918/** 18919 * LocationHtml5Url represents a URL 18920 * This object is exposed as $location service when HTML5 mode is enabled and supported 18921 * 18922 * @constructor 18923 * @param {string} appBase application base URL 18924 * @param {string} appBaseNoFile application base URL stripped of any filename 18925 * @param {string} basePrefix URL path prefix 18926 */ 18927function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) { 18928 this.$$html5 = true; 18929 basePrefix = basePrefix || ''; 18930 parseAbsoluteUrl(appBase, this); 18931 18932 18933 /** 18934 * Parse given HTML5 (regular) URL string into properties 18935 * @param {string} url HTML5 URL 18936 * @private 18937 */ 18938 this.$$parse = function(url) { 18939 var pathUrl = stripBaseUrl(appBaseNoFile, url); 18940 if (!isString(pathUrl)) { 18941 throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url, 18942 appBaseNoFile); 18943 } 18944 18945 parseAppUrl(pathUrl, this); 18946 18947 if (!this.$$path) { 18948 this.$$path = '/'; 18949 } 18950 18951 this.$$compose(); 18952 }; 18953 18954 /** 18955 * Compose url and update `absUrl` property 18956 * @private 18957 */ 18958 this.$$compose = function() { 18959 var search = toKeyValue(this.$$search), 18960 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 18961 18962 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 18963 this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/' 18964 }; 18965 18966 this.$$parseLinkUrl = function(url, relHref) { 18967 if (relHref && relHref[0] === '#') { 18968 // special case for links to hash fragments: 18969 // keep the old url and only replace the hash fragment 18970 this.hash(relHref.slice(1)); 18971 return true; 18972 } 18973 var appUrl, prevAppUrl; 18974 var rewrittenUrl; 18975 18976 18977 if (isDefined(appUrl = stripBaseUrl(appBase, url))) { 18978 prevAppUrl = appUrl; 18979 if (basePrefix && isDefined(appUrl = stripBaseUrl(basePrefix, appUrl))) { 18980 rewrittenUrl = appBaseNoFile + (stripBaseUrl('/', appUrl) || appUrl); 18981 } else { 18982 rewrittenUrl = appBase + prevAppUrl; 18983 } 18984 } else if (isDefined(appUrl = stripBaseUrl(appBaseNoFile, url))) { 18985 rewrittenUrl = appBaseNoFile + appUrl; 18986 } else if (appBaseNoFile === url + '/') { 18987 rewrittenUrl = appBaseNoFile; 18988 } 18989 if (rewrittenUrl) { 18990 this.$$parse(rewrittenUrl); 18991 } 18992 return !!rewrittenUrl; 18993 }; 18994} 18995 18996 18997/** 18998 * LocationHashbangUrl represents URL 18999 * This object is exposed as $location service when developer doesn't opt into html5 mode. 19000 * It also serves as the base class for html5 mode fallback on legacy browsers. 19001 * 19002 * @constructor 19003 * @param {string} appBase application base URL 19004 * @param {string} appBaseNoFile application base URL stripped of any filename 19005 * @param {string} hashPrefix hashbang prefix 19006 */ 19007function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) { 19008 19009 parseAbsoluteUrl(appBase, this); 19010 19011 19012 /** 19013 * Parse given hashbang URL into properties 19014 * @param {string} url Hashbang URL 19015 * @private 19016 */ 19017 this.$$parse = function(url) { 19018 var withoutBaseUrl = stripBaseUrl(appBase, url) || stripBaseUrl(appBaseNoFile, url); 19019 var withoutHashUrl; 19020 19021 if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === '#') { 19022 19023 // The rest of the URL starts with a hash so we have 19024 // got either a hashbang path or a plain hash fragment 19025 withoutHashUrl = stripBaseUrl(hashPrefix, withoutBaseUrl); 19026 if (isUndefined(withoutHashUrl)) { 19027 // There was no hashbang prefix so we just have a hash fragment 19028 withoutHashUrl = withoutBaseUrl; 19029 } 19030 19031 } else { 19032 // There was no hashbang path nor hash fragment: 19033 // If we are in HTML5 mode we use what is left as the path; 19034 // Otherwise we ignore what is left 19035 if (this.$$html5) { 19036 withoutHashUrl = withoutBaseUrl; 19037 } else { 19038 withoutHashUrl = ''; 19039 if (isUndefined(withoutBaseUrl)) { 19040 appBase = url; 19041 this.replace(); 19042 } 19043 } 19044 } 19045 19046 parseAppUrl(withoutHashUrl, this); 19047 19048 this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase); 19049 19050 this.$$compose(); 19051 19052 /* 19053 * In Windows, on an anchor node on documents loaded from 19054 * the filesystem, the browser will return a pathname 19055 * prefixed with the drive name ('/C:/path') when a 19056 * pathname without a drive is set: 19057 * * a.setAttribute('href', '/foo') 19058 * * a.pathname === '/C:/foo' //true 19059 * 19060 * Inside of Angular, we're always using pathnames that 19061 * do not include drive names for routing. 19062 */ 19063 function removeWindowsDriveName(path, url, base) { 19064 /* 19065 Matches paths for file protocol on windows, 19066 such as /C:/foo/bar, and captures only /foo/bar. 19067 */ 19068 var windowsFilePathExp = /^\/[A-Z]:(\/.*)/; 19069 19070 var firstPathSegmentMatch; 19071 19072 //Get the relative path from the input URL. 19073 if (startsWith(url, base)) { 19074 url = url.replace(base, ''); 19075 } 19076 19077 // The input URL intentionally contains a first path segment that ends with a colon. 19078 if (windowsFilePathExp.exec(url)) { 19079 return path; 19080 } 19081 19082 firstPathSegmentMatch = windowsFilePathExp.exec(path); 19083 return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path; 19084 } 19085 }; 19086 19087 /** 19088 * Compose hashbang URL and update `absUrl` property 19089 * @private 19090 */ 19091 this.$$compose = function() {
19092 var search = toKeyValue(this.$$search), 19093 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 19094 19095 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 19096 this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : ''); 19097 }; 19098 19099 this.$$parseLinkUrl = function(url, relHref) { 19100 if (stripHash(appBase) === stripHash(url)) { 19101 this.$$parse(url); 19102 return true; 19103 } 19104 return false; 19105 }; 19106} 19107 19108 19109/** 19110 * LocationHashbangUrl represents URL 19111 * This object is exposed as $location service when html5 history api is enabled but the browser 19112 * does not support it. 19113 * 19114 * @constructor 19115 * @param {string} appBase application base URL 19116 * @param {string} appBaseNoFile application base URL stripped of any filename 19117 * @param {string} hashPrefix hashbang prefix 19118 */ 19119function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) { 19120 this.$$html5 = true; 19121 LocationHashbangUrl.apply(this, arguments); 19122 19123 this.$$parseLinkUrl = function(url, relHref) { 19124 if (relHref && relHref[0] === '#') { 19125 // special case for links to hash fragments: 19126 // keep the old url and only replace the hash fragment 19127 this.hash(relHref.slice(1)); 19128 return true; 19129 } 19130 19131 var rewrittenUrl; 19132 var appUrl; 19133 19134 if (appBase === stripHash(url)) { 19135 rewrittenUrl = url; 19136 } else if ((appUrl = stripBaseUrl(appBaseNoFile, url))) { 19137 rewrittenUrl = appBase + hashPrefix + appUrl; 19138 } else if (appBaseNoFile === url + '/') { 19139 rewrittenUrl = appBaseNoFile; 19140 } 19141 if (rewrittenUrl) { 19142 this.$$parse(rewrittenUrl); 19143 } 19144 return !!rewrittenUrl; 19145 }; 19146 19147 this.$$compose = function() { 19148 var search = toKeyValue(this.$$search), 19149 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 19150 19151 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 19152 // include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#' 19153 this.$$absUrl = appBase + hashPrefix + this.$$url; 19154 }; 19155 19156} 19157 19158 19159var locationPrototype = { 19160 19161 /** 19162 * Ensure absolute URL is initialized. 19163 * @private 19164 */ 19165 $$absUrl:'', 19166 19167 /** 19168 * Are we in html5 mode? 19169 * @private 19170 */ 19171 $$html5: false, 19172 19173 /** 19174 * Has any change been replacing? 19175 * @private 19176 */ 19177 $$replace: false, 19178 19179 /** 19180 * @ngdoc method 19181 * @name $location#absUrl 19182 * 19183 * @description 19184 * This method is getter only. 19185 * 19186 * Return full URL representation with all segments encoded according to rules specified in 19187 * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt). 19188 * 19189 * 19190 * ```js 19191 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo 19192 * var absUrl = $location.absUrl(); 19193 * // => "http://example.com/#/some/path?foo=bar&baz=xoxo" 19194 * ``` 19195 * 19196 * @return {string} full URL 19197 */ 19198 absUrl: locationGetter('$$absUrl'), 19199 19200 /** 19201 * @ngdoc method 19202 * @name $location#url 19203 * 19204 * @description 19205 * This method is getter / setter. 19206 * 19207 * Return URL (e.g. `/path?a=b#hash`) when called without any parameter. 19208 * 19209 * Change path, search and hash, when called with parameter and return `$location`. 19210 * 19211 * 19212 * ```js 19213 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo 19214 * var url = $location.url(); 19215 * // => "/some/path?foo=bar&baz=xoxo" 19216 * ``` 19217 * 19218 * @param {string=} url New URL without base prefix (e.g. `/path?a=b#hash`) 19219 * @return {string} url 19220 */ 19221 url: function(url) { 19222 if (isUndefined(url)) { 19223 return this.$$url; 19224 } 19225 19226 var match = PATH_MATCH.exec(url); 19227 if (match[1] || url === '') this.path(decodeURIComponent(match[1])); 19228 if (match[2] || match[1] || url === '') this.search(match[3] || ''); 19229 this.hash(match[5] || ''); 19230 19231 return this; 19232 }, 19233 19234 /** 19235 * @ngdoc method 19236 * @name $location#protocol 19237 * 19238 * @description 19239 * This method is getter only. 19240 * 19241 * Return protocol of current URL. 19242 * 19243 * 19244 * ```js 19245 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo 19246 * var protocol = $location.protocol(); 19247 * // => "http" 19248 * ``` 19249 * 19250 * @return {string} protocol of current URL 19251 */ 19252 protocol: locationGetter('$$protocol'), 19253 19254 /** 19255 * @ngdoc method 19256 * @name $location#host 19257 * 19258 * @description 19259 * This method is getter only. 19260 * 19261 * Return host of current URL. 19262 * 19263 * Note: compared to the non-angular version `location.host` which returns `hostname:port`, this returns the `hostname` portion only. 19264 * 19265 * 19266 * ```js
19267 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo 19268 * var host = $location.host(); 19269 * // => "example.com" 19270 * 19271 * // given URL http://user:[email protected]:8080/#/some/path?foo=bar&baz=xoxo 19272 * host = $location.host(); 19273 * // => "example.com" 19274 * host = location.host; 19275 * // => "example.com:8080" 19276 * ``` 19277 * 19278 * @return {string} host of current URL. 19279 */ 19280 host: locationGetter('$$host'), 19281 19282 /** 19283 * @ngdoc method 19284 * @name $location#port 19285 * 19286 * @description 19287 * This method is getter only. 19288 * 19289 * Return port of current URL. 19290 * 19291 * 19292 * ```js 19293 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo 19294 * var port = $location.port(); 19295 * // => 80 19296 * ``` 19297 * 19298 * @return {Number} port 19299 */ 19300 port: locationGetter('$$port'), 19301 19302 /** 19303 * @ngdoc method 19304 * @name $location#path 19305 * 19306 * @description 19307 * This method is getter / setter. 19308 * 19309 * Return path of current URL when called without any parameter. 19310 * 19311 * Change path when called with parameter and return `$location`. 19312 * 19313 * Note: Path should always begin with forward slash (/), this method will add the forward slash 19314 * if it is missing. 19315 * 19316 * 19317 * ```js 19318 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo 19319 * var path = $location.path(); 19320 * // => "/some/path" 19321 * ``` 19322 * 19323 * @param {(string|number)=} path New path 19324 * @return {(string|object)} path if called with no parameters, or `$location` if called with a parameter 19325 */ 19326 path: locationGetterSetter('$$path', function(path) { 19327 path = path !== null ? path.toString() : ''; 19328 return path.charAt(0) === '/' ? path : '/' + path; 19329 }), 19330 19331 /** 19332 * @ngdoc method 19333 * @name $location#search 19334 * 19335 * @description 19336 * This method is getter / setter. 19337 * 19338 * Return search part (as object) of current URL when called without any parameter. 19339 * 19340 * Change search part when called with parameter and return `$location`. 19341 * 19342 * 19343 * ```js 19344 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo 19345 * var searchObject = $location.search(); 19346 * // => {foo: 'bar', baz: 'xoxo'} 19347 * 19348 * // set foo to 'yipee' 19349 * $location.search('foo', 'yipee'); 19350 * // $location.search() => {foo: 'yipee', baz: 'xoxo'} 19351 * ``` 19352 * 19353 * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or 19354 * hash object. 19355 * 19356 * When called with a single argument the method acts as a setter, setting the `search` component 19357 * of `$location` to the specified value. 19358 * 19359 * If the argument is a hash object containing an array of values, these values will be encoded 19360 * as duplicate search parameters in the URL. 19361 * 19362 * @param {(string|Number|Array<string>|boolean)=} paramValue If `search` is a string or number, then `paramValue` 19363 * will override only a single search property. 19364 * 19365 * If `paramValue` is an array, it will override the property of the `search` component of 19366 * `$location` specified via the first argument. 19367 * 19368 * If `paramValue` is `null`, the property specified via the first argument will be deleted. 19369 * 19370 * If `paramValue` is `true`, the property specified via the first argument will be added with no 19371 * value nor trailing equal sign. 19372 * 19373 * @return {Object} If called with no arguments returns the parsed `search` object. If called with 19374 * one or more arguments returns `$location` object itself. 19375 */ 19376 search: function(search, paramValue) { 19377 switch (arguments.length) { 19378 case 0: 19379 return this.$$search; 19380 case 1: 19381 if (isString(search) || isNumber(search)) { 19382 search = search.toString(); 19383 this.$$search = parseKeyValue(search); 19384 } else if (isObject(search)) { 19385 search = copy(search, {}); 19386 // remove object undefined or null properties 19387 forEach(search, function(value, key) { 19388 if (value == null) delete search[key]; 19389 }); 19390 19391 this.$$search = search; 19392 } else { 19393 throw $locationMinErr('isrcharg', 19394 'The first argument of the `$location#search()` call must be a string or an object.'); 19395 } 19396 break; 19397 default: 19398 if (isUndefined(paramValue) || paramValue === null) { 19399 delete this.$$search[search]; 19400 } else { 19401 this.$$search[search] = paramValue; 19402 } 19403 } 19404 19405 this.$$compose(); 19406 return this; 19407 }, 19408 19409 /** 19410 * @ngdoc method 19411 * @name $location#hash 19412 * 19413 * @description 19414 * This method is getter / setter. 19415 * 19416 * Returns the hash fragment when called without any parameters. 19417 * 19418 * Changes the hash fragment when called with a parameter and returns `$location`. 19419 * 19420 * 19421 * ```js
19422 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue 19423 * var hash = $location.hash(); 19424 * // => "hashValue" 19425 * ``` 19426 * 19427 * @param {(string|number)=} hash New hash fragment 19428 * @return {string} hash 19429 */ 19430 hash: locationGetterSetter('$$hash', function(hash) { 19431 return hash !== null ? hash.toString() : ''; 19432 }), 19433 19434 /** 19435 * @ngdoc method 19436 * @name $location#replace 19437 * 19438 * @description 19439 * If called, all changes to $location during the current `$digest` will replace the current history 19440 * record, instead of adding a new one. 19441 */ 19442 replace: function() { 19443 this.$$replace = true; 19444 return this; 19445 } 19446}; 19447 19448forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) { 19449 Location.prototype = Object.create(locationPrototype); 19450 19451 /** 19452 * @ngdoc method 19453 * @name $location#state 19454 * 19455 * @description 19456 * This method is getter / setter. 19457 * 19458 * Return the history state object when called without any parameter. 19459 * 19460 * Change the history state object when called with one parameter and return `$location`. 19461 * The state object is later passed to `pushState` or `replaceState`. 19462 * 19463 * NOTE: This method is supported only in HTML5 mode and only in browsers supporting 19464 * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support 19465 * older browsers (like IE9 or Android < 4.0), don't use this method. 19466 * 19467 * @param {object=} state State object for pushState or replaceState 19468 * @return {object} state 19469 */ 19470 Location.prototype.state = function(state) { 19471 if (!arguments.length) { 19472 return this.$$state; 19473 } 19474 19475 if (Location !== LocationHtml5Url || !this.$$html5) { 19476 throw $locationMinErr('nostate', 'History API state support is available only ' + 19477 'in HTML5 mode and only in browsers supporting HTML5 History API'); 19478 } 19479 // The user might modify `stateObject` after invoking `$location.state(stateObject)` 19480 // but we're changing the $$state reference to $browser.state() during the $digest 19481 // so the modification window is narrow. 19482 this.$$state = isUndefined(state) ? null : state; 19483 19484 return this; 19485 }; 19486}); 19487 19488 19489function locationGetter(property) { 19490 return /** @this */ function() { 19491 return this[property]; 19492 }; 19493} 19494 19495 19496function locationGetterSetter(property, preprocess) { 19497 return /** @this */ function(value) { 19498 if (isUndefined(value)) { 19499 return this[property]; 19500 } 19501 19502 this[property] = preprocess(value); 19503 this.$$compose(); 19504 19505 return this; 19506 }; 19507} 19508 19509 19510/** 19511 * @ngdoc service 19512 * @name $location 19513 * 19514 * @requires $rootElement 19515 * 19516 * @description 19517 * The $location service parses the URL in the browser address bar (based on the 19518 * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL 19519 * available to your application. Changes to the URL in the address bar are reflected into 19520 * $location service and changes to $location are reflected into the browser address bar. 19521 * 19522 * **The $location service:** 19523 * 19524 * - Exposes the current URL in the browser address bar, so you can 19525 * - Watch and observe the URL. 19526 * - Change the URL. 19527 * - Synchronizes the URL with the browser when the user 19528 * - Changes the address bar. 19529 * - Clicks the back or forward button (or clicks a History link). 19530 * - Clicks on a link. 19531 * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). 19532 * 19533 * For more information see {@link guide/$location Developer Guide: Using $location} 19534 */ 19535 19536/** 19537 * @ngdoc provider 19538 * @name $locationProvider 19539 * @this 19540 * 19541 * @description 19542 * Use the `$locationProvider` to configure how the application deep linking paths are stored. 19543 */ 19544function $LocationProvider() { 19545 var hashPrefix = '', 19546 html5Mode = { 19547 enabled: false, 19548 requireBase: true, 19549 rewriteLinks: true 19550 }; 19551 19552 /** 19553 * @ngdoc method 19554 * @name $locationProvider#hashPrefix 19555 * @description 19556 * The default value for the prefix is `''`. 19557 * @param {string=} prefix Prefix for hash part (containing path and search) 19558 * @returns {*} current value if used as getter or itself (chaining) if used as setter 19559 */ 19560 this.hashPrefix = function(prefix) { 19561 if (isDefined(prefix)) { 19562 hashPrefix = prefix; 19563 return this; 19564 } else { 19565 return hashPrefix; 19566 } 19567 }; 19568 19569 /** 19570 * @ngdoc method 19571 * @name $locationProvider#html5Mode 19572 * @description 19573 * @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value. 19574 * If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported 19575 * properties: 19576 * - **enabled** â `{boolean}` â (default: false) If true, will rely on `history.pushState` to 19577 * change urls where supported. Will fall back to hash-prefixed paths in browsers that do not 19578 * support `pushState`. 19579 * - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies 19580 * whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are 19581 * true, and a base tag is not present, an error will be thrown when `$location` is injected. 19582 * See the {@link guide/$location $location guide for more information} 19583 * - **rewriteLinks** - `{boolean|string}` - (default: `true`) When html5Mode is enabled, 19584 * enables/disables URL rewriting for relative links. If set to a string, URL rewriting will 19585 * only happen on links with an attribute that matches the given string. For example, if set 19586 * to `'internal-link'`, then the URL will only be rewritten for `<a internal-link>` links. 19587 * Note that [attribute name normalization](guide/directive#normalization) does not apply 19588 * here, so `'internalLink'` will **not** match `'internal-link'`. 19589 * 19590 * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter 19591 */ 19592 this.html5Mode = function(mode) { 19593 if (isBoolean(mode)) { 19594 html5Mode.enabled = mode; 19595 return this; 19596 } else if (isObject(mode)) { 19597 19598 if (isBoolean(mode.enabled)) { 19599 html5Mode.enabled = mode.enabled; 19600 } 19601 19602 if (isBoolean(mode.requireBase)) { 19603 html5Mode.requireBase = mode.requireBase; 19604 } 19605 19606 if (isBoolean(mode.rewriteLinks) || isString(mode.rewriteLinks)) { 19607 html5Mode.rewriteLinks = mode.rewriteLinks; 19608 } 19609 19610 return this; 19611 } else { 19612 return html5Mode; 19613 } 19614 }; 19615 19616 /** 19617 * @ngdoc event 19618 * @name $location#$locationChangeStart 19619 * @eventType broadcast on root scope 19620 * @description 19621 * Broadcasted before a URL will change. 19622 * 19623 * This change can be prevented by calling 19624 * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more 19625 * details about event object. Upon successful change 19626 * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired. 19627 * 19628 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when 19629 * the browser supports the HTML5 History API. 19630 * 19631 * @param {Object} angularEvent Synthetic event object. 19632 * @param {string} newUrl New URL 19633 * @param {string=} oldUrl URL that was before it was changed. 19634 * @param {string=} newState New history state object 19635 * @param {string=} oldState History state object that was before it was changed. 19636 */ 19637 19638 /** 19639 * @ngdoc event 19640 * @name $location#$locationChangeSuccess 19641 * @eventType broadcast on root scope 19642 * @description 19643 * Broadcasted after a URL was changed. 19644 * 19645 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when 19646 * the browser supports the HTML5 History API. 19647 * 19648 * @param {Object} angularEvent Synthetic event object. 19649 * @param {string} newUrl New URL 19650 * @param {string=} oldUrl URL that was before it was changed. 19651 * @param {string=} newState New history state object 19652 * @param {string=} oldState History state object that was before it was changed. 19653 */ 19654 19655 this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window', 19656 function($rootScope, $browser, $sniffer, $rootElement, $window) { 19657 var $location, 19658 LocationMode, 19659 baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' 19660 initialUrl = $browser.url(), 19661 appBase; 19662 19663 if (html5Mode.enabled) { 19664 if (!baseHref && html5Mode.requireBase) { 19665 throw $locationMinErr('nobase', 19666 '$location in HTML5 mode requires a <base> tag to be present!'); 19667 } 19668 appBase = serverBase(initialUrl) + (baseHref || '/'); 19669 LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url; 19670 } else { 19671 appBase = stripHash(initialUrl); 19672 LocationMode = LocationHashbangUrl; 19673 } 19674 var appBaseNoFile = stripFile(appBase); 19675
vendor: 4,316 bytes, lines 19676-19786
19676 $location = new LocationMode(appBase, appBaseNoFile, '#' + hashPrefix); 19677 $location.$$parseLinkUrl(initialUrl, initialUrl); 19678 19679 $location.$$state = $browser.state(); 19680 19681 var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i; 19682 19683 function setBrowserUrlWithFallback(url, replace, state) { 19684 var oldUrl = $location.url(); 19685 var oldState = $location.$$state; 19686 try { 19687 $browser.url(url, replace, state); 19688 19689 // Make sure $location.state() returns referentially identical (not just deeply equal) 19690 // state object; this makes possible quick checking if the state changed in the digest 19691 // loop. Checking deep equality would be too expensive. 19692 $location.$$state = $browser.state(); 19693 } catch (e) { 19694 // Restore old values if pushState fails 19695 $location.url(oldUrl); 19696 $location.$$state = oldState; 19697 19698 throw e; 19699 } 19700 } 19701 19702 $rootElement.on('click', function(event) { 19703 var rewriteLinks = html5Mode.rewriteLinks; 19704 // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) 19705 // currently we open nice url link and redirect then 19706 19707 if (!rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which === 2 || event.button === 2) return; 19708 19709 var elm = jqLite(event.target); 19710 19711 // traverse the DOM up to find first A tag 19712 while (nodeName_(elm[0]) !== 'a') { 19713 // ignore rewriting if no A tag (reached root element, or no parent - removed from document) 19714 if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return; 19715 } 19716 19717 if (isString(rewriteLinks) && isUndefined(elm.attr(rewriteLinks))) return; 19718 19719 var absHref = elm.prop('href'); 19720 // get the actual href attribute - see 19721 // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx 19722 var relHref = elm.attr('href') || elm.attr('xlink:href'); 19723 19724 if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') { 19725 // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during 19726 // an animation. 19727 absHref = urlResolve(absHref.animVal).href; 19728 } 19729 19730 // Ignore when url is started with javascript: or mailto: 19731 if (IGNORE_URI_REGEXP.test(absHref)) return; 19732 19733 if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) { 19734 if ($location.$$parseLinkUrl(absHref, relHref)) { 19735 // We do a preventDefault for all urls that are part of the angular application, 19736 // in html5mode and also without, so that we are able to abort navigation without 19737 // getting double entries in the location history. 19738 event.preventDefault(); 19739 // update location manually 19740 if ($location.absUrl() !== $browser.url()) { 19741 $rootScope.$apply(); 19742 // hack to work around FF6 bug 684208 when scenario runner clicks on links 19743 $window.angular['ff-684208-preventDefault'] = true; 19744 } 19745 } 19746 } 19747 }); 19748 19749 19750 // rewrite hashbang url <> html5 url 19751 if (trimEmptyHash($location.absUrl()) !== trimEmptyHash(initialUrl)) { 19752 $browser.url($location.absUrl(), true); 19753 } 19754 19755 var initializing = true; 19756 19757 // update $location when $browser url changes 19758 $browser.onUrlChange(function(newUrl, newState) { 19759 19760 if (isUndefined(stripBaseUrl(appBaseNoFile, newUrl))) { 19761 // If we are navigating outside of the app then force a reload 19762 $window.location.href = newUrl; 19763 return; 19764 } 19765 19766 $rootScope.$evalAsync(function() { 19767 var oldUrl = $location.absUrl(); 19768 var oldState = $location.$$state; 19769 var defaultPrevented; 19770 newUrl = trimEmptyHash(newUrl); 19771 $location.$$parse(newUrl); 19772 $location.$$state = newState; 19773 19774 defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, 19775 newState, oldState).defaultPrevented; 19776 19777 // if the location was changed by a `$locationChangeStart` handler then stop 19778 // processing this location change 19779 if ($location.absUrl() !== newUrl) return; 19780 19781 if (defaultPrevented) { 19782 $location.$$parse(oldUrl); 19783 $location.$$state = oldState; 19784 setBrowserUrlWithFallback(oldUrl, false, oldState); 19785 } else { 19786 initializing = false;
19787 afterLocationChange(oldUrl, oldState); 19788 } 19789 }); 19790 if (!$rootScope.$$phase) $rootScope.$digest(); 19791 }); 19792 19793 // update browser 19794 $rootScope.$watch(function $locationWatch() { 19795 var oldUrl = trimEmptyHash($browser.url()); 19796 var newUrl = trimEmptyHash($location.absUrl()); 19797 var oldState = $browser.state(); 19798 var currentReplace = $location.$$replace; 19799 var urlOrStateChanged = oldUrl !== newUrl || 19800 ($location.$$html5 && $sniffer.history && oldState !== $location.$$state); 19801 19802 if (initializing || urlOrStateChanged) { 19803 initializing = false; 19804 19805 $rootScope.$evalAsync(function() { 19806 var newUrl = $location.absUrl(); 19807 var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, 19808 $location.$$state, oldState).defaultPrevented; 19809 19810 // if the location was changed by a `$locationChangeStart` handler then stop 19811 // processing this location change 19812 if ($location.absUrl() !== newUrl) return; 19813 19814 if (defaultPrevented) { 19815 $location.$$parse(oldUrl); 19816 $location.$$state = oldState; 19817 } else { 19818 if (urlOrStateChanged) { 19819 setBrowserUrlWithFallback(newUrl, currentReplace, 19820 oldState === $location.$$state ? null : $location.$$state); 19821 } 19822 afterLocationChange(oldUrl, oldState); 19823 } 19824 }); 19825 } 19826 19827 $location.$$replace = false; 19828 19829 // we don't need to return anything because $evalAsync will make the digest loop dirty when 19830 // there is a change 19831 }); 19832 19833 return $location; 19834 19835 function afterLocationChange(oldUrl, oldState) { 19836 $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl, 19837 $location.$$state, oldState); 19838 } 19839}]; 19840} 19841 19842/** 19843 * @ngdoc service 19844 * @name $log 19845 * @requires $window 19846 * 19847 * @description 19848 * Simple service for logging. Default implementation safely writes the message 19849 * into the browser's console (if present). 19850 * 19851 * The main purpose of this service is to simplify debugging and troubleshooting. 19852 * 19853 * The default is to log `debug` messages. You can use 19854 * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this. 19855 * 19856 * @example 19857 <example module="logExample" name="log-service"> 19858 <file name="script.js"> 19859 angular.module('logExample', []) 19860 .controller('LogController', ['$scope', '$log', function($scope, $log) { 19861 $scope.$log = $log; 19862 $scope.message = 'Hello World!'; 19863 }]); 19864 </file> 19865 <file name="index.html"> 19866 <div ng-controller="LogController"> 19867 <p>Reload this page with open console, enter text and hit the log button...</p> 19868 <label>Message: 19869 <input type="text" ng-model="message" /></label> 19870 <button ng-click="$log.log(message)">log</button> 19871 <button ng-click="$log.warn(message)">warn</button> 19872 <button ng-click="$log.info(message)">info</button> 19873 <button ng-click="$log.error(message)">error</button> 19874 <button ng-click="$log.debug(message)">debug</button> 19875 </div> 19876 </file> 19877 </example> 19878 */ 19879 19880/** 19881 * @ngdoc provider 19882 * @name $logProvider 19883 * @this 19884 * 19885 * @description 19886 * Use the `$logProvider` to configure how the application logs messages 19887 */ 19888function $LogProvider() { 19889 var debug = true, 19890 self = this; 19891 19892 /** 19893 * @ngdoc method 19894 * @name $logProvider#debugEnabled 19895 * @description 19896 * @param {boolean=} flag enable or disable debug level messages 19897 * @returns {*} current value if used as getter or itself (chaining) if used as setter 19898 */ 19899 this.debugEnabled = function(flag) { 19900 if (isDefined(flag)) { 19901 debug = flag; 19902 return this; 19903 } else { 19904 return debug; 19905 } 19906 }; 19907 19908 this.$get = ['$window', function($window) { 19909 return { 19910 /** 19911 * @ngdoc method 19912 * @name $log#log 19913 * 19914 * @description 19915 * Write a log message 19916 */ 19917 log: consoleLog('log'), 19918 19919 /** 19920 * @ngdoc method 19921 * @name $log#info 19922 * 19923 * @description 19924 * Write an information message 19925 */ 19926 info: consoleLog('info'), 19927 19928 /** 19929 * @ngdoc method 19930 * @name $log#warn 19931 *
19932 * @description 19933 * Write a warning message 19934 */ 19935 warn: consoleLog('warn'), 19936 19937 /** 19938 * @ngdoc method 19939 * @name $log#error 19940 * 19941 * @description 19942 * Write an error message 19943 */ 19944 error: consoleLog('error'), 19945 19946 /** 19947 * @ngdoc method 19948 * @name $log#debug 19949 * 19950 * @description 19951 * Write a debug message 19952 */ 19953 debug: (function() { 19954 var fn = consoleLog('debug'); 19955 19956 return function() { 19957 if (debug) { 19958 fn.apply(self, arguments); 19959 } 19960 }; 19961 })() 19962 }; 19963 19964 function formatError(arg) { 19965 if (arg instanceof Error) { 19966 if (arg.stack) { 19967 arg = (arg.message && arg.stack.indexOf(arg.message) === -1) 19968 ? 'Error: ' + arg.message + '\n' + arg.stack 19969 : arg.stack; 19970 } else if (arg.sourceURL) { 19971 arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line; 19972 } 19973 } 19974 return arg; 19975 } 19976 19977 function consoleLog(type) { 19978 var console = $window.console || {}, 19979 logFn = console[type] || console.log || noop, 19980 hasApply = false; 19981 19982 // Note: reading logFn.apply throws an error in IE11 in IE8 document mode. 19983 // The reason behind this is that console.log has type "object" in IE8... 19984 try { 19985 hasApply = !!logFn.apply; 19986 } catch (e) { /* empty */ } 19987 19988 if (hasApply) { 19989 return function() { 19990 var args = []; 19991 forEach(arguments, function(arg) { 19992 args.push(formatError(arg)); 19993 }); 19994 return logFn.apply(console, args); 19995 }; 19996 } 19997 19998 // we are IE which either doesn't have window.console => this is noop and we do nothing, 19999 // or we are IE where console.log doesn't have apply so we log at least first 2 args 20000 return function(arg1, arg2) { 20001 logFn(arg1, arg2 == null ? '' : arg2); 20002 }; 20003 } 20004 }]; 20005} 20006 20007/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 20008 * Any commits to this file should be reviewed with security in mind. * 20009 * Changes to this file can potentially create security vulnerabilities. * 20010 * An approval from 2 Core members with history of modifying * 20011 * this file is required. * 20012 * * 20013 * Does the change somehow allow for arbitrary javascript to be executed? * 20014 * Or allows for someone to change the prototype of built-in objects? * 20015 * Or gives undesired access to variables likes document or window? * 20016 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 20017 20018var $parseMinErr = minErr('$parse'); 20019 20020var ARRAY_CTOR = [].constructor; 20021var BOOLEAN_CTOR = (false).constructor; 20022var FUNCTION_CTOR = Function.constructor; 20023var NUMBER_CTOR = (0).constructor; 20024var OBJECT_CTOR = {}
20024.constructor; 20025var STRING_CTOR = ''.constructor; 20026var ARRAY_CTOR_PROTO = ARRAY_CTOR.prototype; 20027var BOOLEAN_CTOR_PROTO = BOOLEAN_CTOR.prototype; 20028var FUNCTION_CTOR_PROTO = FUNCTION_CTOR.prototype; 20029var NUMBER_CTOR_PROTO = NUMBER_CTOR.prototype; 20030var OBJECT_CTOR_PROTO = OBJECT_CTOR.prototype; 20031var STRING_CTOR_PROTO = STRING_CTOR.prototype; 20032 20033var CALL = FUNCTION_CTOR_PROTO.call; 20034var APPLY = FUNCTION_CTOR_PROTO.apply; 20035var BIND = FUNCTION_CTOR_PROTO.bind; 20036 20037var objectValueOf = OBJECT_CTOR_PROTO.valueOf; 20038 20039// Sandboxing Angular Expressions 20040// ------------------------------ 20041// Angular expressions are generally considered safe because these expressions only have direct 20042// access to `$scope` and locals. However, one can obtain the ability to execute arbitrary JS code by 20043// obtaining a reference to native JS functions such as the Function constructor. 20044// 20045// As an example, consider the following Angular expression: 20046// 20047// {}.toString.constructor('alert("evil JS code")') 20048// 20049// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits 20050// against the expression language, but not to prevent exploits that were enabled by exposing 20051// sensitive JavaScript or browser APIs on Scope. Exposing such objects on a Scope is never a good 20052// practice and therefore we are not even trying to protect against interaction with an object 20053// explicitly exposed in this way. 20054// 20055// In general, it is not possible to access a Window object from an angular expression unless a 20056// window or some DOM object that has a reference to window is published onto a Scope. 20057// Similarly we prevent invocations of function known to be dangerous, as well as assignments to 20058// native objects. 20059// 20060// See https://docs.angularjs.org/guide/security 20061 20062 20063function ensureSafeMemberName(name, fullExpression) { 20064 if (name === '__defineGetter__' || name === '__defineSetter__' 20065 || name === '__lookupGetter__' || name === '__lookupSetter__' 20066 || name === '__proto__') { 20067 throw $parseMinErr('isecfld', 20068 'Attempting to access a disallowed field in Angular expressions! ' 20069 + 'Expression: {0}', fullExpression); 20070 } 20071 return name; 20072} 20073 20074function getStringValue(name) { 20075 // Property names must be strings. This means that non-string objects cannot be used 20076 // as keys in an object. Any non-string object, including a number, is typecasted 20077 // into a string via the toString method. 20078 // -- MDN, https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Property_accessors#Property_names 20079 // 20080 // So, to ensure that we are checking the same `name` that JavaScript would use, we cast it 20081 // to a string. It's not always possible. If `name` is an object and its `toString` method is 20082 // 'broken' (doesn't return a string, isn't a function, etc.), an error will be thrown: 20083 // 20084 // TypeError: Cannot convert object to primitive value 20085 // 20086 // For performance reasons, we don't catch this error here and allow it to propagate up the call 20087 // stack. Note that you'll get the same error in JavaScript if you try to access a property using 20088 // such a 'broken' object as a key. 20089 return name + ''; 20090} 20091 20092function ensureSafeObject(obj, fullExpression) { 20093 // nifty check if obj is Function that is fast and works across iframes and other contexts 20094 if (obj) { 20095 if (obj.constructor === obj) { 20096 throw $parseMinErr('isecfn', 20097 'Referencing Function in Angular expressions is disallowed! Expression: {0}', 20098 fullExpression); 20099 } else if (// isWindow(obj) 20100 obj.window === obj) { 20101 throw $parseMinErr('isecwindow', 20102 'Referencing the Window in Angular expressions is disallowed! Expression: {0}', 20103 fullExpression); 20104 } else if (// isElement(obj) 20105 obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) { 20106 throw $parseMinErr('isecdom', 20107 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}', 20108 fullExpression); 20109 } else if (// block Object so that we can't get hold of dangerous Object.* methods 20110 obj === Object) { 20111 throw $parseMinErr('isecobj', 20112 'Referencing Object in Angular expressions is disallowed! Expression: {0}', 20113 fullExpression); 20114 } 20115 } 20116 return obj; 20117} 20118 20119function ensureSafeFunction(obj, fullExpression) { 20120 if (obj) { 20121 if (obj.constructor === obj) { 20122 throw $parseMinErr('isecfn', 20123 'Referencing Function in Angular expressions is disallowed! Expression: {0}', 20124 fullExpression); 20125 } else if (obj === CALL || obj === APPLY || obj === BIND) { 20126 throw $parseMinErr('isecff', 20127 'Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}', 20128 fullExpression); 20129 } 20130 } 20131} 20132 20133function ensureSafeAssignContext(obj, fullExpression) { 20134 if (obj) { 20135 if (obj === ARRAY_CTOR || 20136 obj === BOOLEAN_CTOR || 20137 obj === FUNCTION_CTOR || 20138 obj === NUMBER_CTOR || 20139 obj === OBJECT_CTOR || 20140 obj === STRING_CTOR || 20141 obj === ARRAY_CTOR_PROTO || 20142 obj === BOOLEAN_CTOR_PROTO || 20143 obj === FUNCTION_CTOR_PROTO || 20144 obj === NUMBER_CTOR_PROTO || 20145 obj === OBJECT_CTOR_PROTO || 20146 obj === STRING_CTOR_PROTO) { 20147 throw $parseMinErr('isecaf', 20148 'Assigning to a constructor or its prototype is disallowed! Expression: {0}', 20149 fullExpression); 20150 } 20151 } 20152}
vendor: 16,057 bytes, lines 20152-20726
20152 20153 20154var OPERATORS = createMap(); 20155forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; }); 20156var ESCAPE = {'n':'\n', 'f':'\f', 'r':'\r', 't':'\t', 'v':'\v', '\'':'\'', '"':'"'}; 20157 20158 20159///////////////////////////////////////// 20160 20161 20162/** 20163 * @constructor 20164 */ 20165var Lexer = function Lexer(options) { 20166 this.options = options; 20167}; 20168 20169Lexer.prototype = { 20170 constructor: Lexer, 20171 20172 lex: function(text) { 20173 this.text = text; 20174 this.index = 0; 20175 this.tokens = []; 20176 20177 while (this.index < this.text.length) { 20178 var ch = this.text.charAt(this.index); 20179 if (ch === '"' || ch === '\'') { 20180 this.readString(ch); 20181 } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) { 20182 this.readNumber(); 20183 } else if (this.isIdentifierStart(this.peekMultichar())) { 20184 this.readIdent(); 20185 } else if (this.is(ch, '(){}[].,;:?')) { 20186 this.tokens.push({index: this.index, text: ch}); 20187 this.index++; 20188 } else if (this.isWhitespace(ch)) { 20189 this.index++; 20190 } else { 20191 var ch2 = ch + this.peek(); 20192 var ch3 = ch2 + this.peek(2); 20193 var op1 = OPERATORS[ch]; 20194 var op2 = OPERATORS[ch2]; 20195 var op3 = OPERATORS[ch3]; 20196 if (op1 || op2 || op3) { 20197 var token = op3 ? ch3 : (op2 ? ch2 : ch); 20198 this.tokens.push({index: this.index, text: token, operator: true}); 20199 this.index += token.length; 20200 } else { 20201 this.throwError('Unexpected next character ', this.index, this.index + 1); 20202 } 20203 } 20204 } 20205 return this.tokens; 20206 }, 20207 20208 is: function(ch, chars) { 20209 return chars.indexOf(ch) !== -1; 20210 }, 20211 20212 peek: function(i) { 20213 var num = i || 1; 20214 return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false; 20215 }, 20216 20217 isNumber: function(ch) { 20218 return ('0' <= ch && ch <= '9') && typeof ch === 'string'; 20219 }, 20220 20221 isWhitespace: function(ch) { 20222 // IE treats non-breaking space as \u00A0 20223 return (ch === ' ' || ch === '\r' || ch === '\t' || 20224 ch === '\n' || ch === '\v' || ch === '\u00A0'); 20225 }, 20226 20227 isIdentifierStart: function(ch) { 20228 return this.options.isIdentifierStart ? 20229 this.options.isIdentifierStart(ch, this.codePointAt(ch)) : 20230 this.isValidIdentifierStart(ch); 20231 }, 20232 20233 isValidIdentifierStart: function(ch) { 20234 return ('a' <= ch && ch <= 'z' || 20235 'A' <= ch && ch <= 'Z' || 20236 '_' === ch || ch === '$'); 20237 }, 20238 20239 isIdentifierContinue: function(ch) { 20240 return this.options.isIdentifierContinue ? 20241 this.options.isIdentifierContinue(ch, this.codePointAt(ch)) : 20242 this.isValidIdentifierContinue(ch); 20243 }, 20244 20245 isValidIdentifierContinue: function(ch, cp) { 20246 return this.isValidIdentifierStart(ch, cp) || this.isNumber(ch); 20247 }, 20248 20249 codePointAt: function(ch) { 20250 if (ch.length === 1) return ch.charCodeAt(0); 20251 // eslint-disable-next-line no-bitwise 20252 return (ch.charCodeAt(0) << 10) + ch.charCodeAt(1) - 0x35FDC00; 20253 }, 20254 20255 peekMultichar: function() { 20256 var ch = this.text.charAt(this.index); 20257 var peek = this.peek(); 20258 if (!peek) { 20259 return ch; 20260 } 20261 var cp1 = ch.charCodeAt(0); 20262 var cp2 = peek.charCodeAt(0); 20263 if (cp1 >= 0xD800 && cp1 <= 0xDBFF && cp2 >= 0xDC00 && cp2 <= 0xDFFF) { 20264 return ch + peek; 20265 } 20266 return ch; 20267 }, 20268 20269 isExpOperator: function(ch) { 20270 return (ch === '-' || ch === '+' || this.isNumber(ch)); 20271 }, 20272 20273 throwError: function(error, start, end) { 20274 end = end || this.index; 20275 var colStr = (isDefined(start) 20276 ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']' 20277 : ' ' + end); 20278 throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].', 20279 error, colStr, this.text); 20280 }, 20281 20282 readNumber: function() { 20283 var number = ''; 20284 var start = this.index; 20285 while (this.index < this.text.length) { 20286 var ch = lowercase(this.text.charAt(this.index)); 20287 if (ch === '.' || this.isNumber(ch)) { 20288 number += ch; 20289 } else { 20290 var peekCh = this.peek(); 20291 if (ch === 'e' && this.isExpOperator(peekCh)) { 20292 number += ch; 20293 } else if (this.isExpOperator(ch) && 20294 peekCh && this.isNumber(peekCh) && 20295 number.charAt(number.length - 1) === 'e') { 20296 number += ch; 20297 } else if (this.isExpOperator(ch) && 20298 (!peekCh || !this.isNumber(peekCh)) && 20299 number.charAt(number.length - 1) === 'e') { 20300 this.throwError('Invalid exponent'); 20301 } else { 20302 break; 20303 } 20304 } 20305 this.index++; 20306 } 20307 this.tokens.push({ 20308 index: start, 20309 text: number, 20310 constant: true, 20311 value: Number(number) 20312 }); 20313 }, 20314 20315 readIdent: function() { 20316 var start = this.index; 20317 this.index += this.peekMultichar().length; 20318 while (this.index < this.text.length) { 20319 var ch = this.peekMultichar(); 20320 if (!this.isIdentifierContinue(ch)) { 20321 break; 20322 } 20323 this.index += ch.length; 20324 } 20325 this.tokens.push({ 20326 index: start, 20327 text: this.text.slice(start, this.index), 20328 identifier: true 20329 }); 20330 }, 20331 20332 readString: function(quote) { 20333 var start = this.index; 20334 this.index++; 20335 var string = ''; 20336 var rawString = quote; 20337 var escape = false; 20338 while (this.index < this.text.length) { 20339 var ch = this.text.charAt(this.index); 20340 rawString += ch; 20341 if (escape) { 20342 if (ch === 'u') { 20343 var hex = this.text.substring(this.index + 1, this.index + 5); 20344 if (!hex.match(/[\da-f]{4}/i)) { 20345 this.throwError('Invalid unicode escape [\\u' + hex + ']'); 20346 } 20347 this.index += 4; 20348 string += String.fromCharCode(parseInt(hex, 16)); 20349 } else { 20350 var rep = ESCAPE[ch]; 20351 string = string + (rep || ch); 20352 } 20353 escape = false; 20354 } else if (ch === '\\') { 20355 escape = true; 20356 } else if (ch === quote) { 20357 this.index++; 20358 this.tokens.push({ 20359 index: start, 20360 text: rawString, 20361 constant: true, 20362 value: string 20363 }); 20364 return; 20365 } else { 20366 string += ch; 20367 } 20368 this.index++; 20369 } 20370 this.throwError('Unterminated quote', start); 20371 } 20372}; 20373 20374var AST = function AST(lexer, options) { 20375 this.lexer = lexer; 20376 this.options = options; 20377}; 20378 20379AST.Program = 'Program'; 20380AST.ExpressionStatement = 'ExpressionStatement'; 20381AST.AssignmentExpression = 'AssignmentExpression'; 20382AST.ConditionalExpression = 'ConditionalExpression'; 20383AST.LogicalExpression = 'LogicalExpression'; 20384AST.BinaryExpression = 'BinaryExpression'; 20385AST.UnaryExpression = 'UnaryExpression'; 20386AST.CallExpression = 'CallExpression'; 20387AST.MemberExpression = 'MemberExpression'; 20388AST.Identifier = 'Identifier'; 20389AST.Literal = 'Literal'; 20390AST.ArrayExpression = 'ArrayExpression'; 20391AST.Property = 'Property'; 20392AST.ObjectExpression = 'ObjectExpression'; 20393AST.ThisExpression = 'ThisExpression'; 20394AST.LocalsExpression = 'LocalsExpression'; 20395 20396// Internal use only 20397AST.NGValueParameter = 'NGValueParameter'; 20398 20399AST.prototype = { 20400 ast: function(text) { 20401 this.text = text; 20402 this.tokens = this.lexer.lex(text); 20403 20404 var value = this.program(); 20405 20406 if (this.tokens.length !== 0) { 20407 this.throwError('is an unexpected token', this.tokens[0]); 20408 } 20409 20410 return value; 20411 }, 20412 20413 program: function() { 20414 var body = []; 20415 while (true) { 20416 if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']')) 20417 body.push(this.expressionStatement()); 20418 if (!this.expect(';')) { 20419 return { type: AST.Program, body: body}; 20420 } 20421 } 20422 }, 20423 20424 expressionStatement: function() { 20425 return { type: AST.ExpressionStatement, expression: this.filterChain() }; 20426 }, 20427 20428 filterChain: function() { 20429 var left = this.expression(); 20430 while (this.expect('|')) { 20431 left = this.filter(left); 20432 } 20433 return left; 20434 }, 20435 20436 expression: function() { 20437 return this.assignment(); 20438 }, 20439 20440 assignment: function() { 20441 var result = this.ternary(); 20442 if (this.expect('=')) { 20443 if (!isAssignable(result)) { 20444 throw $parseMinErr('lval', 'Trying to assign a value to a non l-value'); 20445 } 20446 20447 result = { type: AST.AssignmentExpression, left: result, right: this.assignment(), operator: '='}; 20448 } 20449 return result; 20450 }, 20451 20452 ternary: function() { 20453 var test = this.logicalOR(); 20454 var alternate; 20455 var consequent; 20456 if (this.expect('?')) { 20457 alternate = this.expression(); 20458 if (this.consume(':')) { 20459 consequent = this.expression(); 20460 return { type: AST.ConditionalExpression, test: test, alternate: alternate, consequent: consequent}; 20461 } 20462 } 20463 return test; 20464 }, 20465 20466 logicalOR: function() { 20467 var left = this.logicalAND(); 20468 while (this.expect('||')) { 20469 left = { type: AST.LogicalExpression, operator: '||', left: left, right: this.logicalAND() }; 20470 } 20471 return left; 20472 }, 20473 20474 logicalAND: function() { 20475 var left = this.equality(); 20476 while (this.expect('&&')) { 20477 left = { type: AST.LogicalExpression, operator: '&&', left: left, right: this.equality()}; 20478 } 20479 return left; 20480 }, 20481 20482 equality: function() { 20483 var left = this.relational(); 20484 var token; 20485 while ((token = this.expect('==','!=','===','!=='))) { 20486 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.relational() }; 20487 } 20488 return left; 20489 }, 20490 20491 relational: function() { 20492 var left = this.additive(); 20493 var token; 20494 while ((token = this.expect('<', '>', '<=', '>='))) { 20495 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.additive() }; 20496 } 20497 return left; 20498 }, 20499 20500 additive: function() { 20501 var left = this.multiplicative(); 20502 var token; 20503 while ((token = this.expect('+','-'))) { 20504 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.multiplicative() }; 20505 } 20506 return left; 20507 }, 20508 20509 multiplicative: function() { 20510 var left = this.unary(); 20511 var token; 20512 while ((token = this.expect('*','/','%'))) { 20513 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.unary() }; 20514 } 20515 return left; 20516 }, 20517 20518 unary: function() { 20519 var token; 20520 if ((token = this.expect('+', '-', '!'))) { 20521 return { type: AST.UnaryExpression, operator: token.text, prefix: true, argument: this.unary() }; 20522 } else { 20523 return this.primary(); 20524 } 20525 }, 20526 20527 primary: function() { 20528 var primary; 20529 if (this.expect('(')) { 20530 primary = this.filterChain(); 20531 this.consume(')'); 20532 } else if (this.expect('[')) { 20533 primary = this.arrayDeclaration(); 20534 } else if (this.expect('{')) { 20535 primary = this.object(); 20536 } else if (this.selfReferential.hasOwnProperty(this.peek().text)) { 20537 primary = copy(this.selfReferential[this.consume().text]); 20538 } else if (this.options.literals.hasOwnProperty(this.peek().text)) { 20539 primary = { type: AST.Literal, value: this.options.literals[this.consume().text]}; 20540 } else if (this.peek().identifier) { 20541 primary = this.identifier(); 20542 } else if (this.peek().constant) { 20543 primary = this.constant(); 20544 } else { 20545 this.throwError('not a primary expression', this.peek()); 20546 } 20547 20548 var next; 20549 while ((next = this.expect('(', '[', '.'))) { 20550 if (next.text === '(') { 20551 primary = {type: AST.CallExpression, callee: primary, arguments: this.parseArguments() }; 20552 this.consume(')'); 20553 } else if (next.text === '[') { 20554 primary = { type: AST.MemberExpression, object: primary, property: this.expression(), computed: true }; 20555 this.consume(']'); 20556 } else if (next.text === '.') { 20557 primary = { type: AST.MemberExpression, object: primary, property: this.identifier(), computed: false }; 20558 } else { 20559 this.throwError('IMPOSSIBLE'); 20560 } 20561 } 20562 return primary; 20563 }, 20564 20565 filter: function(baseExpression) { 20566 var args = [baseExpression]; 20567 var result = {type: AST.CallExpression, callee: this.identifier(), arguments: args, filter: true}; 20568 20569 while (this.expect(':')) { 20570 args.push(this.expression()); 20571 } 20572 20573 return result; 20574 }, 20575 20576 parseArguments: function() { 20577 var args = []; 20578 if (this.peekToken().text !== ')') { 20579 do { 20580 args.push(this.filterChain()); 20581 } while (this.expect(',')); 20582 } 20583 return args; 20584 }, 20585 20586 identifier: function() { 20587 var token = this.consume(); 20588 if (!token.identifier) { 20589 this.throwError('is not a valid identifier', token); 20590 } 20591 return { type: AST.Identifier, name: token.text }; 20592 }, 20593 20594 constant: function() { 20595 // TODO check that it is a constant 20596 return { type: AST.Literal, value: this.consume().value }; 20597 }, 20598 20599 arrayDeclaration: function() { 20600 var elements = []; 20601 if (this.peekToken().text !== ']') { 20602 do { 20603 if (this.peek(']')) { 20604 // Support trailing commas per ES5.1. 20605 break; 20606 } 20607 elements.push(this.expression()); 20608 } while (this.expect(',')); 20609 } 20610 this.consume(']'); 20611 20612 return { type: AST.ArrayExpression, elements: elements }; 20613 }, 20614 20615 object: function() { 20616 var properties = [], property; 20617 if (this.peekToken().text !== '}') { 20618 do { 20619 if (this.peek('}')) { 20620 // Support trailing commas per ES5.1. 20621 break; 20622 } 20623 property = {type: AST.Property, kind: 'init'}; 20624 if (this.peek().constant) { 20625 property.key = this.constant(); 20626 property.computed = false; 20627 this.consume(':'); 20628 property.value = this.expression(); 20629 } else if (this.peek().identifier) { 20630 property.key = this.identifier(); 20631 property.computed = false; 20632 if (this.peek(':')) { 20633 this.consume(':'); 20634 property.value = this.expression(); 20635 } else { 20636 property.value = property.key; 20637 } 20638 } else if (this.peek('[')) { 20639 this.consume('['); 20640 property.key = this.expression(); 20641 this.consume(']'); 20642 property.computed = true; 20643 this.consume(':'); 20644 property.value = this.expression(); 20645 } else { 20646 this.throwError('invalid key', this.peek()); 20647 } 20648 properties.push(property); 20649 } while (this.expect(',')); 20650 } 20651 this.consume('}'); 20652 20653 return {type: AST.ObjectExpression, properties: properties }; 20654 }, 20655 20656 throwError: function(msg, token) { 20657 throw $parseMinErr('syntax', 20658 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].', 20659 token.text, msg, (token.index + 1), this.text, this.text.substring(token.index)); 20660 }, 20661 20662 consume: function(e1) { 20663 if (this.tokens.length === 0) { 20664 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); 20665 } 20666 20667 var token = this.expect(e1); 20668 if (!token) { 20669 this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); 20670 } 20671 return token; 20672 }, 20673 20674 peekToken: function() { 20675 if (this.tokens.length === 0) { 20676 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); 20677 } 20678 return this.tokens[0]; 20679 }, 20680 20681 peek: function(e1, e2, e3, e4) { 20682 return this.peekAhead(0, e1, e2, e3, e4); 20683 }, 20684 20685 peekAhead: function(i, e1, e2, e3, e4) { 20686 if (this.tokens.length > i) { 20687 var token = this.tokens[i]; 20688 var t = token.text; 20689 if (t === e1 || t === e2 || t === e3 || t === e4 || 20690 (!e1 && !e2 && !e3 && !e4)) { 20691 return token; 20692 } 20693 } 20694 return false; 20695 }, 20696 20697 expect: function(e1, e2, e3, e4) { 20698 var token = this.peek(e1, e2, e3, e4); 20699 if (token) { 20700 this.tokens.shift(); 20701 return token; 20702 } 20703 return false; 20704 }, 20705 20706 selfReferential: { 20707 'this': {type: AST.ThisExpression }, 20708 '$locals': {type: AST.LocalsExpression } 20709 } 20710}; 20711 20712function ifDefined(v, d) { 20713 return typeof v !== 'undefined' ? v : d; 20714} 20715 20716function plusFn(l, r) { 20717 if (typeof l === 'undefined') return r; 20718 if (typeof r === 'undefined') return l; 20719 return l + r; 20720} 20721 20722function isStateless($filter, filterName) { 20723 var fn = $filter(filterName); 20724 return !fn.$stateful; 20725} 20726
20727function findConstantAndWatchExpressions(ast, $filter) { 20728 var allConstants; 20729 var argsToWatch; 20730 var isStatelessFilter; 20731 switch (ast.type) { 20732 case AST.Program: 20733 allConstants = true; 20734 forEach(ast.body, function(expr) { 20735 findConstantAndWatchExpressions(expr.expression, $filter); 20736 allConstants = allConstants && expr.expression.constant; 20737 }); 20738 ast.constant = allConstants; 20739 break; 20740 case AST.Literal: 20741 ast.constant = true; 20742 ast.toWatch = []; 20743 break; 20744 case AST.UnaryExpression: 20745 findConstantAndWatchExpressions(ast.argument, $filter); 20746 ast.constant = ast.argument.constant; 20747 ast.toWatch = ast.argument.toWatch; 20748 break; 20749 case AST.BinaryExpression: 20750 findConstantAndWatchExpressions(ast.left, $filter); 20751 findConstantAndWatchExpressions(ast.right, $filter); 20752 ast.constant = ast.left.constant && ast.right.constant; 20753 ast.toWatch = ast.left.toWatch.concat(ast.right.toWatch); 20754 break; 20755 case AST.LogicalExpression: 20756 findConstantAndWatchExpressions(ast.left, $filter); 20757 findConstantAndWatchExpressions(ast.right, $filter); 20758 ast.constant = ast.left.constant && ast.right.constant; 20759 ast.toWatch = ast.constant ? [] : [ast]; 20760 break; 20761 case AST.ConditionalExpression: 20762 findConstantAndWatchExpressions(ast.test, $filter); 20763 findConstantAndWatchExpressions(ast.alternate, $filter); 20764 findConstantAndWatchExpressions(ast.consequent, $filter); 20765 ast.constant = ast.test.constant && ast.alternate.constant && ast.consequent.constant; 20766 ast.toWatch = ast.constant ? [] : [ast]; 20767 break; 20768 case AST.Identifier: 20769 ast.constant = false; 20770 ast.toWatch = [ast]; 20771 break; 20772 case AST.MemberExpression: 20773 findConstantAndWatchExpressions(ast.object, $filter); 20774 if (ast.computed) { 20775 findConstantAndWatchExpressions(ast.property, $filter); 20776 } 20777 ast.constant = ast.object.constant && (!ast.computed || ast.property.constant); 20778 ast.toWatch = [ast]; 20779 break; 20780 case AST.CallExpression: 20781 isStatelessFilter = ast.filter ? isStateless($filter, ast.callee.name) : false; 20782 allConstants = isStatelessFilter; 20783 argsToWatch = []; 20784 forEach(ast.arguments, function(expr) { 20785 findConstantAndWatchExpressions(expr, $filter); 20786 allConstants = allConstants && expr.constant; 20787 if (!expr.constant) { 20788 argsToWatch.push.apply(argsToWatch, expr.toWatch); 20789 } 20790 }); 20791 ast.constant = allConstants; 20792 ast.toWatch = isStatelessFilter ? argsToWatch : [ast]; 20793 break; 20794 case AST.AssignmentExpression: 20795 findConstantAndWatchExpressions(ast.left, $filter); 20796 findConstantAndWatchExpressions(ast.right, $filter); 20797 ast.constant = ast.left.constant && ast.right.constant; 20798 ast.toWatch = [ast]; 20799 break; 20800 case AST.ArrayExpression: 20801 allConstants = true; 20802 argsToWatch = []; 20803 forEach(ast.elements, function(expr) { 20804 findConstantAndWatchExpressions(expr, $filter); 20805 allConstants = allConstants && expr.constant; 20806 if (!expr.constant) { 20807 argsToWatch.push.apply(argsToWatch, expr.toWatch); 20808 } 20809 }); 20810 ast.constant = allConstants; 20811 ast.toWatch = argsToWatch; 20812 break; 20813 case AST.ObjectExpression: 20814 allConstants = true; 20815 argsToWatch = []; 20816 forEach(ast.properties, function(property) { 20817 findConstantAndWatchExpressions(property.value, $filter); 20818 allConstants = allConstants && property.value.constant && !property.computed; 20819 if (!property.value.constant) { 20820 argsToWatch.push.apply(argsToWatch, property.value.toWatch); 20821 } 20822 }); 20823 ast.constant = allConstants; 20824 ast.toWatch = argsToWatch; 20825 break; 20826 case AST.ThisExpression: 20827 ast.constant = false; 20828 ast.toWatch = []; 20829 break; 20830 case AST.LocalsExpression: 20831 ast.constant = false;
vendor: 32,119 bytes, lines 20832-21779
20832 ast.toWatch = []; 20833 break; 20834 } 20835} 20836 20837function getInputs(body) { 20838 if (body.length !== 1) return; 20839 var lastExpression = body[0].expression; 20840 var candidate = lastExpression.toWatch; 20841 if (candidate.length !== 1) return candidate; 20842 return candidate[0] !== lastExpression ? candidate : undefined; 20843} 20844 20845function isAssignable(ast) { 20846 return ast.type === AST.Identifier || ast.type === AST.MemberExpression; 20847} 20848 20849function assignableAST(ast) { 20850 if (ast.body.length === 1 && isAssignable(ast.body[0].expression)) { 20851 return {type: AST.AssignmentExpression, left: ast.body[0].expression, right: {type: AST.NGValueParameter}, operator: '='}; 20852 } 20853} 20854 20855function isLiteral(ast) { 20856 return ast.body.length === 0 || 20857 ast.body.length === 1 && ( 20858 ast.body[0].expression.type === AST.Literal || 20859 ast.body[0].expression.type === AST.ArrayExpression || 20860 ast.body[0].expression.type === AST.ObjectExpression); 20861} 20862 20863function isConstant(ast) { 20864 return ast.constant; 20865} 20866 20867function ASTCompiler(astBuilder, $filter) { 20868 this.astBuilder = astBuilder; 20869 this.$filter = $filter; 20870} 20871 20872ASTCompiler.prototype = { 20873 compile: function(expression, expensiveChecks) { 20874 var self = this; 20875 var ast = this.astBuilder.ast(expression); 20876 this.state = { 20877 nextId: 0, 20878 filters: {}, 20879 expensiveChecks: expensiveChecks, 20880 fn: {vars: [], body: [], own: {}}, 20881 assign: {vars: [], body: [], own: {}}, 20882 inputs: [] 20883 }; 20884 findConstantAndWatchExpressions(ast, self.$filter); 20885 var extra = ''; 20886 var assignable; 20887 this.stage = 'assign'; 20888 if ((assignable = assignableAST(ast))) { 20889 this.state.computing = 'assign'; 20890 var result = this.nextId(); 20891 this.recurse(assignable, result); 20892 this.return_(result); 20893 extra = 'fn.assign=' + this.generateFunction('assign', 's,v,l'); 20894 } 20895 var toWatch = getInputs(ast.body); 20896 self.stage = 'inputs'; 20897 forEach(toWatch, function(watch, key) { 20898 var fnKey = 'fn' + key; 20899 self.state[fnKey] = {vars: [], body: [], own: {}}; 20900 self.state.computing = fnKey; 20901 var intoId = self.nextId(); 20902 self.recurse(watch, intoId); 20903 self.return_(intoId); 20904 self.state.inputs.push(fnKey); 20905 watch.watchId = key; 20906 }); 20907 this.state.computing = 'fn'; 20908 this.stage = 'main'; 20909 this.recurse(ast); 20910 var fnString = 20911 // The build and minification steps remove the string "use strict" from the code, but this is done using a regex. 20912 // This is a workaround for this until we do a better job at only removing the prefix only when we should. 20913 '"' + this.USE + ' ' + this.STRICT + '";\n' + 20914 this.filterPrefix() + 20915 'var fn=' + this.generateFunction('fn', 's,l,a,i') + 20916 extra + 20917 this.watchFns() + 20918 'return fn;'; 20919 20920 // eslint-disable-next-line no-new-func 20921 var fn = (new Function('$filter', 20922 'ensureSafeMemberName', 20923 'ensureSafeObject', 20924 'ensureSafeFunction', 20925 'getStringValue', 20926 'ensureSafeAssignContext', 20927 'ifDefined', 20928 'plus', 20929 'text', 20930 fnString))( 20931 this.$filter, 20932 ensureSafeMemberName, 20933 ensureSafeObject, 20934 ensureSafeFunction, 20935 getStringValue, 20936 ensureSafeAssignContext, 20937 ifDefined, 20938 plusFn, 20939 expression); 20940 this.state = this.stage = undefined; 20941 fn.literal = isLiteral(ast); 20942 fn.constant = isConstant(ast); 20943 return fn; 20944 }, 20945 20946 USE: 'use', 20947 20948 STRICT: 'strict', 20949 20950 watchFns: function() { 20951 var result = []; 20952 var fns = this.state.inputs; 20953 var self = this; 20954 forEach(fns, function(name) { 20955 result.push('var ' + name + '=' + self.generateFunction(name, 's')); 20956 }); 20957 if (fns.length) { 20958 result.push('fn.inputs=[' + fns.join(',') + '];'); 20959 } 20960 return result.join(''); 20961 }, 20962 20963 generateFunction: function(name, params) { 20964 return 'function(' + params + '){' + 20965 this.varsPrefix(name) + 20966 this.body(name) + 20967 '};'; 20968 }, 20969 20970 filterPrefix: function() { 20971 var parts = []; 20972 var self = this; 20973 forEach(this.state.filters, function(id, filter) { 20974 parts.push(id + '=$filter(' + self.escape(filter) + ')'); 20975 }); 20976 if (parts.length) return 'var ' + parts.join(',') + ';'; 20977 return ''; 20978 }, 20979 20980 varsPrefix: function(section) { 20981 return this.state[section].vars.length ? 'var ' + this.state[section].vars.join(',') + ';' : ''; 20982 }, 20983 20984 body: function(section) { 20985 return this.state[section].body.join(''); 20986 }, 20987 20988 recurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) { 20989 var left, right, self = this, args, expression, computed; 20990 recursionFn = recursionFn || noop; 20991 if (!skipWatchIdCheck && isDefined(ast.watchId)) { 20992 intoId = intoId || this.nextId(); 20993 this.if_('i', 20994 this.lazyAssign(intoId, this.computedMember('i', ast.watchId)), 20995 this.lazyRecurse(ast, intoId, nameId, recursionFn, create, true) 20996 ); 20997 return; 20998 } 20999 switch (ast.type) { 21000 case AST.Program: 21001 forEach(ast.body, function(expression, pos) { 21002 self.recurse(expression.expression, undefined, undefined, function(expr) { right = expr; }); 21003 if (pos !== ast.body.length - 1) { 21004 self.current().body.push(right, ';'); 21005 } else { 21006 self.return_(right); 21007 } 21008 }); 21009 break; 21010 case AST.Literal: 21011 expression = this.escape(ast.value); 21012 this.assign(intoId, expression); 21013 recursionFn(expression); 21014 break; 21015 case AST.UnaryExpression: 21016 this.recurse(ast.argument, undefined, undefined, function(expr) { right = expr; }); 21017 expression = ast.operator + '(' + this.ifDefined(right, 0) + ')'; 21018 this.assign(intoId, expression); 21019 recursionFn(expression); 21020 break; 21021 case AST.BinaryExpression: 21022 this.recurse(ast.left, undefined, undefined, function(expr) { left = expr; }); 21023 this.recurse(ast.right, undefined, undefined, function(expr) { right = expr; }); 21024 if (ast.operator === '+') { 21025 expression = this.plus(left, right); 21026 } else if (ast.operator === '-') { 21027 expression = this.ifDefined(left, 0) + ast.operator + this.ifDefined(right, 0); 21028 } else { 21029 expression = '(' + left + ')' + ast.operator + '(' + right + ')'; 21030 } 21031 this.assign(intoId, expression); 21032 recursionFn(expression); 21033 break; 21034 case AST.LogicalExpression: 21035 intoId = intoId || this.nextId(); 21036 self.recurse(ast.left, intoId); 21037 self.if_(ast.operator === '&&' ? intoId : self.not(intoId), self.lazyRecurse(ast.right, intoId)); 21038 recursionFn(intoId); 21039 break; 21040 case AST.ConditionalExpression: 21041 intoId = intoId || this.nextId(); 21042 self.recurse(ast.test, intoId); 21043 self.if_(intoId, self.lazyRecurse(ast.alternate, intoId), self.lazyRecurse(ast.consequent, intoId)); 21044 recursionFn(intoId); 21045 break; 21046 case AST.Identifier: 21047 intoId = intoId || this.nextId(); 21048 if (nameId) { 21049 nameId.context = self.stage === 'inputs' ? 's' : this.assign(this.nextId(), this.getHasOwnProperty('l', ast.name) + '?l:s'); 21050 nameId.computed = false; 21051 nameId.name = ast.name; 21052 } 21053 ensureSafeMemberName(ast.name); 21054 self.if_(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)), 21055 function() { 21056 self.if_(self.stage === 'inputs' || 's', function() { 21057 if (create && create !== 1) { 21058 self.if_( 21059 self.not(self.nonComputedMember('s', ast.name)), 21060 self.lazyAssign(self.nonComputedMember('s', ast.name), '{}')); 21061 } 21062 self.assign(intoId, self.nonComputedMember('s', ast.name)); 21063 }); 21064 }, intoId && self.lazyAssign(intoId, self.nonComputedMember('l', ast.name)) 21065 ); 21066 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.name)) { 21067 self.addEnsureSafeObject(intoId); 21068 } 21069 recursionFn(intoId); 21070 break; 21071 case AST.MemberExpression: 21072 left = nameId && (nameId.context = this.nextId()) || this.nextId(); 21073 intoId = intoId || this.nextId(); 21074 self.recurse(ast.object, left, undefined, function() { 21075 self.if_(self.notNull(left), function() { 21076 if (create && create !== 1) { 21077 self.addEnsureSafeAssignContext(left); 21078 } 21079 if (ast.computed) { 21080 right = self.nextId(); 21081 self.recurse(ast.property, right); 21082 self.getStringValue(right); 21083 self.addEnsureSafeMemberName(right); 21084 if (create && create !== 1) { 21085 self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}')); 21086 } 21087 expression = self.ensureSafeObject(self.computedMember(left, right)); 21088 self.assign(intoId, expression); 21089 if (nameId) { 21090 nameId.computed = true; 21091 nameId.name = right; 21092 } 21093 } else { 21094 ensureSafeMemberName(ast.property.name); 21095 if (create && create !== 1) { 21096 self.if_(self.not(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}')); 21097 } 21098 expression = self.nonComputedMember(left, ast.property.name); 21099 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.property.name)) { 21100 expression = self.ensureSafeObject(expression); 21101 } 21102 self.assign(intoId, expression); 21103 if (nameId) { 21104 nameId.computed = false; 21105 nameId.name = ast.property.name; 21106 } 21107 } 21108 }, function() { 21109 self.assign(intoId, 'undefined'); 21110 }); 21111 recursionFn(intoId); 21112 }, !!create); 21113 break; 21114 case AST.CallExpression: 21115 intoId = intoId || this.nextId(); 21116 if (ast.filter) { 21117 right = self.filter(ast.callee.name); 21118 args = []; 21119 forEach(ast.arguments, function(expr) { 21120 var argument = self.nextId(); 21121 self.recurse(expr, argument); 21122 args.push(argument); 21123 }); 21124 expression = right + '(' + args.join(',') + ')'; 21125 self.assign(intoId, expression); 21126 recursionFn(intoId); 21127 } else { 21128 right = self.nextId(); 21129 left = {}; 21130 args = []; 21131 self.recurse(ast.callee, right, left, function() { 21132 self.if_(self.notNull(right), function() { 21133 self.addEnsureSafeFunction(right); 21134 forEach(ast.arguments, function(expr) { 21135 self.recurse(expr, self.nextId(), undefined, function(argument) { 21136 args.push(self.ensureSafeObject(argument)); 21137 }); 21138 }); 21139 if (left.name) { 21140 if (!self.state.expensiveChecks) { 21141 self.addEnsureSafeObject(left.context); 21142 } 21143 expression = self.member(left.context, left.name, left.computed) + '(' + args.join(',') + ')'; 21144 } else { 21145 expression = right + '(' + args.join(',') + ')'; 21146 } 21147 expression = self.ensureSafeObject(expression); 21148 self.assign(intoId, expression); 21149 }, function() { 21150 self.assign(intoId, 'undefined'); 21151 }); 21152 recursionFn(intoId); 21153 }); 21154 } 21155 break; 21156 case AST.AssignmentExpression: 21157 right = this.nextId(); 21158 left = {}; 21159 this.recurse(ast.left, undefined, left, function() { 21160 self.if_(self.notNull(left.context), function() { 21161 self.recurse(ast.right, right); 21162 self.addEnsureSafeObject(self.member(left.context, left.name, left.computed)); 21163 self.addEnsureSafeAssignContext(left.context); 21164 expression = self.member(left.context, left.name, left.computed) + ast.operator + right; 21165 self.assign(intoId, expression); 21166 recursionFn(intoId || expression); 21167 }); 21168 }, 1); 21169 break; 21170 case AST.ArrayExpression: 21171 args = []; 21172 forEach(ast.elements, function(expr) { 21173 self.recurse(expr, self.nextId(), undefined, function(argument) { 21174 args.push(argument); 21175 }); 21176 }); 21177 expression = '[' + args.join(',') + ']'; 21178 this.assign(intoId, expression); 21179 recursionFn(expression); 21180 break; 21181 case AST.ObjectExpression: 21182 args = []; 21183 computed = false; 21184 forEach(ast.properties, function(property) { 21185 if (property.computed) { 21186 computed = true; 21187 } 21188 }); 21189 if (computed) { 21190 intoId = intoId || this.nextId(); 21191 this.assign(intoId, '{}'); 21192 forEach(ast.properties, function(property) { 21193 if (property.computed) { 21194 left = self.nextId(); 21195 self.recurse(property.key, left); 21196 } else { 21197 left = property.key.type === AST.Identifier ? 21198 property.key.name : 21199 ('' + property.key.value); 21200 } 21201 right = self.nextId(); 21202 self.recurse(property.value, right); 21203 self.assign(self.member(intoId, left, property.computed), right); 21204 }); 21205 } else { 21206 forEach(ast.properties, function(property) { 21207 self.recurse(property.value, ast.constant ? undefined : self.nextId(), undefined, function(expr) { 21208 args.push(self.escape( 21209 property.key.type === AST.Identifier ? property.key.name : 21210 ('' + property.key.value)) + 21211 ':' + expr); 21212 }); 21213 }); 21214 expression = '{' + args.join(',') + '}'; 21215 this.assign(intoId, expression); 21216 } 21217 recursionFn(intoId || expression); 21218 break; 21219 case AST.ThisExpression: 21220 this.assign(intoId, 's'); 21221 recursionFn('s'); 21222 break; 21223 case AST.LocalsExpression: 21224 this.assign(intoId, 'l'); 21225 recursionFn('l'); 21226 break; 21227 case AST.NGValueParameter: 21228 this.assign(intoId, 'v'); 21229 recursionFn('v'); 21230 break; 21231 } 21232 }, 21233 21234 getHasOwnProperty: function(element, property) { 21235 var key = element + '.' + property; 21236 var own = this.current().own; 21237 if (!own.hasOwnProperty(key)) { 21238 own[key] = this.nextId(false, element + '&&(' + this.escape(property) + ' in ' + element + ')'); 21239 } 21240 return own[key]; 21241 }, 21242 21243 assign: function(id, value) { 21244 if (!id) return; 21245 this.current().body.push(id, '=', value, ';'); 21246 return id; 21247 }, 21248 21249 filter: function(filterName) { 21250 if (!this.state.filters.hasOwnProperty(filterName)) { 21251 this.state.filters[filterName] = this.nextId(true); 21252 } 21253 return this.state.filters[filterName]; 21254 }, 21255 21256 ifDefined: function(id, defaultValue) { 21257 return 'ifDefined(' + id + ',' + this.escape(defaultValue) + ')'; 21258 }, 21259 21260 plus: function(left, right) { 21261 return 'plus(' + left + ',' + right + ')'; 21262 }, 21263 21264 return_: function(id) { 21265 this.current().body.push('return ', id, ';'); 21266 }, 21267 21268 if_: function(test, alternate, consequent) { 21269 if (test === true) { 21270 alternate(); 21271 } else { 21272 var body = this.current().body; 21273 body.push('if(', test, '){'); 21274 alternate(); 21275 body.push('}'); 21276 if (consequent) { 21277 body.push('else{'); 21278 consequent(); 21279 body.push('}'); 21280 } 21281 } 21282 }, 21283 21284 not: function(expression) { 21285 return '!(' + expression + ')'; 21286 }, 21287 21288 notNull: function(expression) { 21289 return expression + '!=null'; 21290 }, 21291 21292 nonComputedMember: function(left, right) { 21293 var SAFE_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/; 21294 var UNSAFE_CHARACTERS = /[^$_a-zA-Z0-9]/g; 21295 if (SAFE_IDENTIFIER.test(right)) { 21296 return left + '.' + right; 21297 } else { 21298 return left + '["' + right.replace(UNSAFE_CHARACTERS, this.stringEscapeFn) + '"]'; 21299 } 21300 }, 21301 21302 computedMember: function(left, right) { 21303 return left + '[' + right + ']'; 21304 }, 21305 21306 member: function(left, right, computed) { 21307 if (computed) return this.computedMember(left, right); 21308 return this.nonComputedMember(left, right); 21309 }, 21310 21311 addEnsureSafeObject: function(item) { 21312 this.current().body.push(this.ensureSafeObject(item), ';'); 21313 }, 21314 21315 addEnsureSafeMemberName: function(item) { 21316 this.current().body.push(this.ensureSafeMemberName(item), ';'); 21317 }, 21318 21319 addEnsureSafeFunction: function(item) { 21320 this.current().body.push(this.ensureSafeFunction(item), ';'); 21321 }, 21322 21323 addEnsureSafeAssignContext: function(item) { 21324 this.current().body.push(this.ensureSafeAssignContext(item), ';'); 21325 }, 21326 21327 ensureSafeObject: function(item) { 21328 return 'ensureSafeObject(' + item + ',text)'; 21329 }, 21330 21331 ensureSafeMemberName: function(item) { 21332 return 'ensureSafeMemberName(' + item + ',text)'; 21333 }, 21334 21335 ensureSafeFunction: function(item) { 21336 return 'ensureSafeFunction(' + item + ',text)'; 21337 }, 21338 21339 getStringValue: function(item) { 21340 this.assign(item, 'getStringValue(' + item + ')'); 21341 }, 21342 21343 ensureSafeAssignContext: function(item) { 21344 return 'ensureSafeAssignContext(' + item + ',text)'; 21345 }, 21346 21347 lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) { 21348 var self = this; 21349 return function() { 21350 self.recurse(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck); 21351 }; 21352 }, 21353 21354 lazyAssign: function(id, value) { 21355 var self = this; 21356 return function() { 21357 self.assign(id, value); 21358 }; 21359 }, 21360 21361 stringEscapeRegex: /[^ a-zA-Z0-9]/g, 21362 21363 stringEscapeFn: function(c) { 21364 return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4); 21365 }, 21366 21367 escape: function(value) { 21368 if (isString(value)) return '\'' + value.replace(this.stringEscapeRegex, this.stringEscapeFn) + '\''; 21369 if (isNumber(value)) return value.toString(); 21370 if (value === true) return 'true'; 21371 if (value === false) return 'false'; 21372 if (value === null) return 'null'; 21373 if (typeof value === 'undefined') return 'undefined'; 21374 21375 throw $parseMinErr('esc', 'IMPOSSIBLE'); 21376 }, 21377 21378 nextId: function(skip, init) { 21379 var id = 'v' + (this.state.nextId++); 21380 if (!skip) { 21381 this.current().vars.push(id + (init ? '=' + init : '')); 21382 } 21383 return id; 21384 }, 21385 21386 current: function() { 21387 return this.state[this.state.computing]; 21388 } 21389}; 21390 21391 21392function ASTInterpreter(astBuilder, $filter) { 21393 this.astBuilder = astBuilder; 21394 this.$filter = $filter; 21395} 21396 21397ASTInterpreter.prototype = { 21398 compile: function(expression, expensiveChecks) { 21399 var self = this; 21400 var ast = this.astBuilder.ast(expression); 21401 this.expression = expression; 21402 this.expensiveChecks = expensiveChecks; 21403 findConstantAndWatchExpressions(ast, self.$filter); 21404 var assignable; 21405 var assign; 21406 if ((assignable = assignableAST(ast))) { 21407 assign = this.recurse(assignable); 21408 } 21409 var toWatch = getInputs(ast.body); 21410 var inputs; 21411 if (toWatch) { 21412 inputs = []; 21413 forEach(toWatch, function(watch, key) { 21414 var input = self.recurse(watch); 21415 watch.input = input; 21416 inputs.push(input); 21417 watch.watchId = key; 21418 }); 21419 } 21420 var expressions = []; 21421 forEach(ast.body, function(expression) { 21422 expressions.push(self.recurse(expression.expression)); 21423 }); 21424 var fn = ast.body.length === 0 ? noop : 21425 ast.body.length === 1 ? expressions[0] : 21426 function(scope, locals) { 21427 var lastValue; 21428 forEach(expressions, function(exp) { 21429 lastValue = exp(scope, locals); 21430 }); 21431 return lastValue; 21432 }; 21433 if (assign) { 21434 fn.assign = function(scope, value, locals) { 21435 return assign(scope, locals, value); 21436 }; 21437 } 21438 if (inputs) { 21439 fn.inputs = inputs; 21440 } 21441 fn.literal = isLiteral(ast); 21442 fn.constant = isConstant(ast); 21443 return fn; 21444 }, 21445 21446 recurse: function(ast, context, create) { 21447 var left, right, self = this, args; 21448 if (ast.input) { 21449 return this.inputs(ast.input, ast.watchId); 21450 } 21451 switch (ast.type) { 21452 case AST.Literal: 21453 return this.value(ast.value, context); 21454 case AST.UnaryExpression: 21455 right = this.recurse(ast.argument); 21456 return this['unary' + ast.operator](right, context); 21457 case AST.BinaryExpression: 21458 left = this.recurse(ast.left); 21459 right = this.recurse(ast.right); 21460 return this['binary' + ast.operator](left, right, context); 21461 case AST.LogicalExpression: 21462 left = this.recurse(ast.left); 21463 right = this.recurse(ast.right); 21464 return this['binary' + ast.operator](left, right, context); 21465 case AST.ConditionalExpression: 21466 return this['ternary?:']( 21467 this.recurse(ast.test), 21468 this.recurse(ast.alternate), 21469 this.recurse(ast.consequent), 21470 context 21471 ); 21472 case AST.Identifier: 21473 ensureSafeMemberName(ast.name, self.expression); 21474 return self.identifier(ast.name, 21475 self.expensiveChecks || isPossiblyDangerousMemberName(ast.name), 21476 context, create, self.expression); 21477 case AST.MemberExpression: 21478 left = this.recurse(ast.object, false, !!create); 21479 if (!ast.computed) { 21480 ensureSafeMemberName(ast.property.name, self.expression); 21481 right = ast.property.name; 21482 } 21483 if (ast.computed) right = this.recurse(ast.property); 21484 return ast.computed ? 21485 this.computedMember(left, right, context, create, self.expression) : 21486 this.nonComputedMember(left, right, self.expensiveChecks, context, create, self.expression); 21487 case AST.CallExpression: 21488 args = []; 21489 forEach(ast.arguments, function(expr) { 21490 args.push(self.recurse(expr)); 21491 }); 21492 if (ast.filter) right = this.$filter(ast.callee.name); 21493 if (!ast.filter) right = this.recurse(ast.callee, true); 21494 return ast.filter ? 21495 function(scope, locals, assign, inputs) { 21496 var values = []; 21497 for (var i = 0; i < args.length; ++i) { 21498 values.push(args[i](scope, locals, assign, inputs)); 21499 } 21500 var value = right.apply(undefined, values, inputs); 21501 return context ? {context: undefined, name: undefined, value: value} : value; 21502 } : 21503 function(scope, locals, assign, inputs) { 21504 var rhs = right(scope, locals, assign, inputs); 21505 var value; 21506 if (rhs.value != null) { 21507 ensureSafeObject(rhs.context, self.expression); 21508 ensureSafeFunction(rhs.value, self.expression); 21509 var values = []; 21510 for (var i = 0; i < args.length; ++i) { 21511 values.push(ensureSafeObject(args[i](scope, locals, assign, inputs), self.expression)); 21512 } 21513 value = ensureSafeObject(rhs.value.apply(rhs.context, values), self.expression); 21514 } 21515 return context ? {value: value} : value; 21516 }; 21517 case AST.AssignmentExpression: 21518 left = this.recurse(ast.left, true, 1); 21519 right = this.recurse(ast.right); 21520 return function(scope, locals, assign, inputs) { 21521 var lhs = left(scope, locals, assign, inputs); 21522 var rhs = right(scope, locals, assign, inputs); 21523 ensureSafeObject(lhs.value, self.expression); 21524 ensureSafeAssignContext(lhs.context); 21525 lhs.context[lhs.name] = rhs; 21526 return context ? {value: rhs} : rhs; 21527 }; 21528 case AST.ArrayExpression: 21529 args = []; 21530 forEach(ast.elements, function(expr) { 21531 args.push(self.recurse(expr)); 21532 }); 21533 return function(scope, locals, assign, inputs) { 21534 var value = []; 21535 for (var i = 0; i < args.length; ++i) { 21536 value.push(args[i](scope, locals, assign, inputs)); 21537 } 21538 return context ? {value: value} : value; 21539 }; 21540 case AST.ObjectExpression: 21541 args = []; 21542 forEach(ast.properties, function(property) { 21543 if (property.computed) { 21544 args.push({key: self.recurse(property.key), 21545 computed: true, 21546 value: self.recurse(property.value) 21547 }); 21548 } else { 21549 args.push({key: property.key.type === AST.Identifier ? 21550 property.key.name : 21551 ('' + property.key.value), 21552 computed: false, 21553 value: self.recurse(property.value) 21554 }); 21555 } 21556 }); 21557 return function(scope, locals, assign, inputs) { 21558 var value = {}; 21559 for (var i = 0; i < args.length; ++i) { 21560 if (args[i].computed) { 21561 value[args[i].key(scope, locals, assign, inputs)] = args[i].value(scope, locals, assign, inputs); 21562 } else { 21563 value[args[i].key] = args[i].value(scope, locals, assign, inputs); 21564 } 21565 } 21566 return context ? {value: value} : value; 21567 }; 21568 case AST.ThisExpression: 21569 return function(scope) { 21570 return context ? {value: scope} : scope; 21571 }; 21572 case AST.LocalsExpression: 21573 return function(scope, locals) { 21574 return context ? {value: locals} : locals; 21575 }; 21576 case AST.NGValueParameter: 21577 return function(scope, locals, assign) { 21578 return context ? {value: assign} : assign; 21579 }; 21580 } 21581 }, 21582 21583 'unary+': function(argument, context) { 21584 return function(scope, locals, assign, inputs) { 21585 var arg = argument(scope, locals, assign, inputs); 21586 if (isDefined(arg)) { 21587 arg = +arg; 21588 } else { 21589 arg = 0; 21590 } 21591 return context ? {value: arg} : arg; 21592 }; 21593 }, 21594 'unary-': function(argument, context) { 21595 return function(scope, locals, assign, inputs) { 21596 var arg = argument(scope, locals, assign, inputs); 21597 if (isDefined(arg)) { 21598 arg = -arg; 21599 } else { 21600 arg = 0; 21601 } 21602 return context ? {value: arg} : arg; 21603 }; 21604 }, 21605 'unary!': function(argument, context) { 21606 return function(scope, locals, assign, inputs) { 21607 var arg = !argument(scope, locals, assign, inputs); 21608 return context ? {value: arg} : arg; 21609 }; 21610 }, 21611 'binary+': function(left, right, context) { 21612 return function(scope, locals, assign, inputs) { 21613 var lhs = left(scope, locals, assign, inputs); 21614 var rhs = right(scope, locals, assign, inputs); 21615 var arg = plusFn(lhs, rhs); 21616 return context ? {value: arg} : arg; 21617 }; 21618 }, 21619 'binary-': function(left, right, context) { 21620 return function(scope, locals, assign, inputs) { 21621 var lhs = left(scope, locals, assign, inputs); 21622 var rhs = right(scope, locals, assign, inputs); 21623 var arg = (isDefined(lhs) ? lhs : 0) - (isDefined(rhs) ? rhs : 0); 21624 return context ? {value: arg} : arg; 21625 }; 21626 }, 21627 'binary*': function(left, right, context) { 21628 return function(scope, locals, assign, inputs) { 21629 var arg = left(scope, locals, assign, inputs) * right(scope, locals, assign, inputs); 21630 return context ? {value: arg} : arg; 21631 }; 21632 }, 21633 'binary/': function(left, right, context) { 21634 return function(scope, locals, assign, inputs) { 21635 var arg = left(scope, locals, assign, inputs) / right(scope, locals, assign, inputs); 21636 return context ? {value: arg} : arg; 21637 }; 21638 }, 21639 'binary%': function(left, right, context) { 21640 return function(scope, locals, assign, inputs) { 21641 var arg = left(scope, locals, assign, inputs) % right(scope, locals, assign, inputs); 21642 return context ? {value: arg} : arg; 21643 }; 21644 }, 21645 'binary===': function(left, right, context) { 21646 return function(scope, locals, assign, inputs) { 21647 var arg = left(scope, locals, assign, inputs) === right(scope, locals, assign, inputs); 21648 return context ? {value: arg} : arg; 21649 }; 21650 }, 21651 'binary!==': function(left, right, context) { 21652 return function(scope, locals, assign, inputs) { 21653 var arg = left(scope, locals, assign, inputs) !== right(scope, locals, assign, inputs); 21654 return context ? {value: arg} : arg; 21655 }; 21656 }, 21657 'binary==': function(left, right, context) { 21658 return function(scope, locals, assign, inputs) { 21659 // eslint-disable-next-line eqeqeq 21660 var arg = left(scope, locals, assign, inputs) == right(scope, locals, assign, inputs); 21661 return context ? {value: arg} : arg; 21662 }; 21663 }, 21664 'binary!=': function(left, right, context) { 21665 return function(scope, locals, assign, inputs) { 21666 // eslint-disable-next-line eqeqeq 21667 var arg = left(scope, locals, assign, inputs) != right(scope, locals, assign, inputs); 21668 return context ? {value: arg} : arg; 21669 }; 21670 }, 21671 'binary<': function(left, right, context) { 21672 return function(scope, locals, assign, inputs) { 21673 var arg = left(scope, locals, assign, inputs) < right(scope, locals, assign, inputs); 21674 return context ? {value: arg} : arg; 21675 }; 21676 }, 21677 'binary>': function(left, right, context) { 21678 return function(scope, locals, assign, inputs) { 21679 var arg = left(scope, locals, assign, inputs) > right(scope, locals, assign, inputs); 21680 return context ? {value: arg} : arg; 21681 }; 21682 }, 21683 'binary<=': function(left, right, context) { 21684 return function(scope, locals, assign, inputs) { 21685 var arg = left(scope, locals, assign, inputs) <= right(scope, locals, assign, inputs); 21686 return context ? {value: arg} : arg; 21687 }; 21688 }, 21689 'binary>=': function(left, right, context) { 21690 return function(scope, locals, assign, inputs) { 21691 var arg = left(scope, locals, assign, inputs) >= right(scope, locals, assign, inputs); 21692 return context ? {value: arg} : arg; 21693 }; 21694 }, 21695 'binary&&': function(left, right, context) { 21696 return function(scope, locals, assign, inputs) { 21697 var arg = left(scope, locals, assign, inputs) && right(scope, locals, assign, inputs); 21698 return context ? {value: arg} : arg; 21699 }; 21700 }, 21701 'binary||': function(left, right, context) { 21702 return function(scope, locals, assign, inputs) { 21703 var arg = left(scope, locals, assign, inputs) || right(scope, locals, assign, inputs); 21704 return context ? {value: arg} : arg; 21705 }; 21706 }, 21707 'ternary?:': function(test, alternate, consequent, context) { 21708 return function(scope, locals, assign, inputs) { 21709 var arg = test(scope, locals, assign, inputs) ? alternate(scope, locals, assign, inputs) : consequent(scope, locals, assign, inputs); 21710 return context ? {value: arg} : arg; 21711 }; 21712 }, 21713 value: function(value, context) { 21714 return function() { return context ? {context: undefined, name: undefined, value: value} : value; }; 21715 }, 21716 identifier: function(name, expensiveChecks, context, create, expression) { 21717 return function(scope, locals, assign, inputs) { 21718 var base = locals && (name in locals) ? locals : scope; 21719 if (create && create !== 1 && base && !(base[name])) { 21720 base[name] = {}; 21721 } 21722 var value = base ? base[name] : undefined; 21723 if (expensiveChecks) { 21724 ensureSafeObject(value, expression); 21725 } 21726 if (context) { 21727 return {context: base, name: name, value: value}; 21728 } else { 21729 return value; 21730 } 21731 }; 21732 }, 21733 computedMember: function(left, right, context, create, expression) { 21734 return function(scope, locals, assign, inputs) { 21735 var lhs = left(scope, locals, assign, inputs); 21736 var rhs; 21737 var value; 21738 if (lhs != null) { 21739 rhs = right(scope, locals, assign, inputs); 21740 rhs = getStringValue(rhs); 21741 ensureSafeMemberName(rhs, expression); 21742 if (create && create !== 1) { 21743 ensureSafeAssignContext(lhs); 21744 if (lhs && !(lhs[rhs])) { 21745 lhs[rhs] = {}; 21746 } 21747 } 21748 value = lhs[rhs]; 21749 ensureSafeObject(value, expression); 21750 } 21751 if (context) { 21752 return {context: lhs, name: rhs, value: value}; 21753 } else { 21754 return value; 21755 } 21756 }; 21757 }, 21758 nonComputedMember: function(left, right, expensiveChecks, context, create, expression) { 21759 return function(scope, locals, assign, inputs) { 21760 var lhs = left(scope, locals, assign, inputs); 21761 if (create && create !== 1) { 21762 ensureSafeAssignContext(lhs); 21763 if (lhs && !(lhs[right])) { 21764 lhs[right] = {}; 21765 } 21766 } 21767 var value = lhs != null ? lhs[right] : undefined; 21768 if (expensiveChecks || isPossiblyDangerousMemberName(right)) { 21769 ensureSafeObject(value, expression); 21770 } 21771 if (context) { 21772 return {context: lhs, name: right, value: value}; 21773 } else { 21774 return value; 21775 } 21776 }; 21777 }, 21778 inputs: function(input, watchId) { 21779 return function(scope, value, locals, inputs) {
21780 if (inputs) return inputs[watchId]; 21781 return input(scope, value, locals); 21782 }; 21783 } 21784}; 21785 21786/** 21787 * @constructor 21788 */ 21789var Parser = function Parser(lexer, $filter, options) { 21790 this.lexer = lexer; 21791 this.$filter = $filter; 21792 this.options = options; 21793 this.ast = new AST(lexer, options); 21794 this.astCompiler = options.csp ? new ASTInterpreter(this.ast, $filter) : 21795 new ASTCompiler(this.ast, $filter); 21796}; 21797 21798Parser.prototype = { 21799 constructor: Parser, 21800 21801 parse: function(text) { 21802 return this.astCompiler.compile(text, this.options.expensiveChecks); 21803 } 21804}; 21805 21806function isPossiblyDangerousMemberName(name) { 21807 return name === 'constructor'; 21808} 21809 21810function getValueOf(value) { 21811 return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value); 21812} 21813 21814/////////////////////////////////// 21815 21816/** 21817 * @ngdoc service 21818 * @name $parse 21819 * @kind function 21820 * 21821 * @description 21822 * 21823 * Converts Angular {@link guide/expression expression} into a function. 21824 * 21825 * ```js 21826 * var getter = $parse('user.name'); 21827 * var setter = getter.assign; 21828 * var context = {user:{name:'angular'}}; 21829 * var locals = {user:{name:'local'}}; 21830 * 21831 * expect(getter(context)).toEqual('angular'); 21832 * setter(context, 'newValue'); 21833 * expect(context.user.name).toEqual('newValue'); 21834 * expect(getter(context, locals)).toEqual('local'); 21835 * ``` 21836 * 21837 * 21838 * @param {string} expression String expression to compile. 21839 * @returns {function(context, locals)} a function which represents the compiled expression: 21840 * 21841 * * `context` â `{object}` â an object against which any expressions embedded in the strings 21842 * are evaluated against (typically a scope object). 21843 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 21844 * `context`. 21845 * 21846 * The returned function also has the following properties: 21847 * * `literal` â `{boolean}` â whether the expression's top-level node is a JavaScript 21848 * literal. 21849 * * `constant` â `{boolean}` â whether the expression is made entirely of JavaScript 21850 * constant literals. 21851 * * `assign` â `{?function(context, value)}` â if the expression is assignable, this will be 21852 * set to a function to change its value on the given context. 21853 * 21854 */ 21855 21856 21857/** 21858 * @ngdoc provider 21859 * @name $parseProvider 21860 * @this 21861 * 21862 * @description 21863 * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse} 21864 * service. 21865 */ 21866function $ParseProvider() { 21867 var cacheDefault = createMap(); 21868 var cacheExpensive = createMap(); 21869 var literals = { 21870 'true': true, 21871 'false': false, 21872 'null': null, 21873 'undefined': undefined 21874 }; 21875 var identStart, identContinue; 21876 21877 /** 21878 * @ngdoc method 21879 * @name $parseProvider#addLiteral 21880 * @description 21881 * 21882 * Configure $parse service to add literal values that will be present as literal at expressions. 21883 * 21884 * @param {string} literalName Token for the literal value. The literal name value must be a valid literal name. 21885 * @param {*} literalValue Value for this literal. All literal values must be primitives or `undefined`. 21886 * 21887 **/ 21888 this.addLiteral = function(literalName, literalValue) { 21889 literals[literalName] = literalValue; 21890 }; 21891 21892 /** 21893 * @ngdoc method 21894 * @name $parseProvider#setIdentifierFns 21895 * 21896 * @description 21897 * 21898 * Allows defining the set of characters that are allowed in Angular expressions. The function 21899 * `identifierStart` will get called to know if a given character is a valid character to be the 21900 * first character for an identifier. The function `identifierContinue` will get called to know if 21901 * a given character is a valid character to be a follow-up identifier character. The functions 21902 * `identifierStart` and `identifierContinue` will receive as arguments the single character to be 21903 * identifier and the character code point. These arguments will be `string` and `numeric`. Keep in 21904 * mind that the `string` parameter can be two characters long depending on the character 21905 * representation. It is expected for the function to return `true` or `false`, whether that 21906 * character is allowed or not. 21907 * 21908 * Since this function will be called extensively, keep the implementation of these functions fast, 21909 * as the performance of these functions have a direct impact on the expressions parsing speed. 21910 * 21911 * @param {function=} identifierStart The function that will decide whether the given character is 21912 * a valid identifier start character. 21913 * @param {function=} identifierContinue The function that will decide whether the given character is 21914 * a valid identifier continue character. 21915 */ 21916 this.setIdentifierFns = function(identifierStart, identifierContinue) { 21917 identStart = identifierStart; 21918 identContinue = identifierContinue; 21919 return this; 21920 }; 21921 21922 this.$get = ['$filter', function($filter) { 21923 var noUnsafeEval = csp().noUnsafeEval; 21924 var $parseOptions = { 21925 csp: noUnsafeEval,
21926 expensiveChecks: false, 21927 literals: copy(literals), 21928 isIdentifierStart: isFunction(identStart) && identStart, 21929 isIdentifierContinue: isFunction(identContinue) && identContinue 21930 }, 21931 $parseOptionsExpensive = { 21932 csp: noUnsafeEval, 21933 expensiveChecks: true, 21934 literals: copy(literals), 21935 isIdentifierStart: isFunction(identStart) && identStart, 21936 isIdentifierContinue: isFunction(identContinue) && identContinue 21937 }; 21938 var runningChecksEnabled = false; 21939 21940 $parse.$$runningExpensiveChecks = function() { 21941 return runningChecksEnabled; 21942 }; 21943 21944 return $parse; 21945 21946 function $parse(exp, interceptorFn, expensiveChecks) { 21947 var parsedExpression, oneTime, cacheKey; 21948 21949 expensiveChecks = expensiveChecks || runningChecksEnabled; 21950 21951 switch (typeof exp) { 21952 case 'string': 21953 exp = exp.trim(); 21954 cacheKey = exp; 21955 21956 var cache = (expensiveChecks ? cacheExpensive : cacheDefault); 21957 parsedExpression = cache[cacheKey]; 21958 21959 if (!parsedExpression) { 21960 if (exp.charAt(0) === ':' && exp.charAt(1) === ':') { 21961 oneTime = true; 21962 exp = exp.substring(2); 21963 } 21964 var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions; 21965 var lexer = new Lexer(parseOptions); 21966 var parser = new Parser(lexer, $filter, parseOptions); 21967 parsedExpression = parser.parse(exp); 21968 if (parsedExpression.constant) { 21969 parsedExpression.$$watchDelegate = constantWatchDelegate; 21970 } else if (oneTime) { 21971 parsedExpression.$$watchDelegate = parsedExpression.literal ? 21972 oneTimeLiteralWatchDelegate : oneTimeWatchDelegate; 21973 } else if (parsedExpression.inputs) { 21974 parsedExpression.$$watchDelegate = inputsWatchDelegate; 21975 } 21976 if (expensiveChecks) { 21977 parsedExpression = expensiveChecksInterceptor(parsedExpression); 21978 } 21979 cache[cacheKey] = parsedExpression; 21980 } 21981 return addInterceptor(parsedExpression, interceptorFn); 21982 21983 case 'function': 21984 return addInterceptor(exp, interceptorFn); 21985 21986 default: 21987 return addInterceptor(noop, interceptorFn); 21988 } 21989 } 21990 21991 function expensiveChecksInterceptor(fn) { 21992 if (!fn) return fn; 21993 expensiveCheckFn.$$watchDelegate = fn.$$watchDelegate; 21994 expensiveCheckFn.assign = expensiveChecksInterceptor(fn.assign); 21995 expensiveCheckFn.constant = fn.constant; 21996 expensiveCheckFn.literal = fn.literal; 21997 for (var i = 0; fn.inputs && i < fn.inputs.length; ++i) { 21998 fn.inputs[i] = expensiveChecksInterceptor(fn.inputs[i]); 21999 } 22000 expensiveCheckFn.inputs = fn.inputs; 22001 22002 return expensiveCheckFn; 22003 22004 function expensiveCheckFn(scope, locals, assign, inputs) { 22005 var expensiveCheckOldValue = runningChecksEnabled; 22006 runningChecksEnabled = true; 22007 try { 22008 return fn(scope, locals, assign, inputs); 22009 } finally { 22010 runningChecksEnabled = expensiveCheckOldValue; 22011 } 22012 } 22013 } 22014 22015 function expressionInputDirtyCheck(newValue, oldValueOfValue) { 22016 22017 if (newValue == null || oldValueOfValue == null) { // null/undefined 22018 return newValue === oldValueOfValue; 22019 } 22020 22021 if (typeof newValue === 'object') { 22022 22023 // attempt to convert the value to a primitive type 22024 // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can 22025 // be cheaply dirty-checked 22026 newValue = getValueOf(newValue); 22027 22028 if (typeof newValue === 'object') { 22029 // objects/arrays are not supported - deep-watching them would be too expensive 22030 return false; 22031 } 22032 22033 // fall-through to the primitive equality check 22034 } 22035 22036 //Primitive or NaN 22037 // eslint-disable-next-line no-self-compare 22038 return newValue === oldValueOfValue || (newValue !== newValue && oldValueOfValue !== oldValueOfValue); 22039 } 22040 22041 function inputsWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) { 22042 var inputExpressions = parsedExpression.inputs; 22043 var lastResult; 22044 22045 if (inputExpressions.length === 1) { 22046 var oldInputValueOf = expressionInputDirtyCheck; // init to something unique so that equals check fails 22047 inputExpressions = inputExpressions[0]; 22048 return scope.$watch(function expressionInputWatch(scope) { 22049 var newInputValue = inputExpressions(scope); 22050 if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf)) { 22051 lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]); 22052 oldInputValueOf = newInputValue && getValueOf(newInputValue); 22053 } 22054 return lastResult; 22055 }, listener, objectEquality, prettyPrintExpression); 22056 } 22057 22058 var oldInputValueOfValues = []; 22059 var oldInputValues = []; 22060 for (var i = 0, ii = inputExpressions.length; i < ii; i++) { 22061 oldInputValueOfValues[i] = expressionInputDirtyCheck; // init to something unique so that equals check fails 22062 oldInputValues[i] = null; 22063 } 22064 22065 return scope.$watch(function expressionInputsWatch(scope) { 22066 var changed = false;
22067 22068 for (var i = 0, ii = inputExpressions.length; i < ii; i++) { 22069 var newInputValue = inputExpressions[i](scope); 22070 if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i]))) { 22071 oldInputValues[i] = newInputValue; 22072 oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue); 22073 } 22074 } 22075 22076 if (changed) { 22077 lastResult = parsedExpression(scope, undefined, undefined, oldInputValues); 22078 } 22079 22080 return lastResult; 22081 }, listener, objectEquality, prettyPrintExpression); 22082 } 22083 22084 function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression) { 22085 var unwatch, lastValue; 22086 unwatch = scope.$watch(function oneTimeWatch(scope) { 22087 return parsedExpression(scope); 22088 }, /** @this */ function oneTimeListener(value, old, scope) { 22089 lastValue = value; 22090 if (isFunction(listener)) { 22091 listener.apply(this, arguments); 22092 } 22093 if (isDefined(value)) { 22094 scope.$$postDigest(function() { 22095 if (isDefined(lastValue)) { 22096 unwatch(); 22097 } 22098 }); 22099 } 22100 }, objectEquality); 22101 return unwatch; 22102 } 22103 22104 function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) { 22105 var unwatch, lastValue; 22106 unwatch = scope.$watch(function oneTimeWatch(scope) { 22107 return parsedExpression(scope); 22108 }, /** @this */ function oneTimeListener(value, old, scope) { 22109 lastValue = value; 22110 if (isFunction(listener)) { 22111 listener.call(this, value, old, scope); 22112 } 22113 if (isAllDefined(value)) { 22114 scope.$$postDigest(function() { 22115 if (isAllDefined(lastValue)) unwatch(); 22116 }); 22117 } 22118 }, objectEquality); 22119 22120 return unwatch; 22121 22122 function isAllDefined(value) { 22123 var allDefined = true; 22124 forEach(value, function(val) { 22125 if (!isDefined(val)) allDefined = false; 22126 }); 22127 return allDefined; 22128 } 22129 } 22130 22131 function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) { 22132 var unwatch = scope.$watch(function constantWatch(scope) { 22133 unwatch(); 22134 return parsedExpression(scope); 22135 }, listener, objectEquality); 22136 return unwatch; 22137 } 22138 22139 function addInterceptor(parsedExpression, interceptorFn) { 22140 if (!interceptorFn) return parsedExpression; 22141 var watchDelegate = parsedExpression.$$watchDelegate; 22142 var useInputs = false; 22143 22144 var regularWatch = 22145 watchDelegate !== oneTimeLiteralWatchDelegate && 22146 watchDelegate !== oneTimeWatchDelegate; 22147 22148 var fn = regularWatch ? function regularInterceptedExpression(scope, locals, assign, inputs) { 22149 var value = useInputs && inputs ? inputs[0] : parsedExpression(scope, locals, assign, inputs); 22150 return interceptorFn(value, scope, locals); 22151 } : function oneTimeInterceptedExpression(scope, locals, assign, inputs) { 22152 var value = parsedExpression(scope, locals, assign, inputs); 22153 var result = interceptorFn(value, scope, locals); 22154 // we only return the interceptor's result if the 22155 // initial value is defined (for bind-once) 22156 return isDefined(value) ? result : value; 22157 }; 22158 22159 // Propagate $$watchDelegates other then inputsWatchDelegate 22160 if (parsedExpression.$$watchDelegate && 22161 parsedExpression.$$watchDelegate !== inputsWatchDelegate) { 22162 fn.$$watchDelegate = parsedExpression.$$watchDelegate; 22163 } else if (!interceptorFn.$stateful) { 22164 // If there is an interceptor, but no watchDelegate then treat the interceptor like 22165 // we treat filters - it is assumed to be a pure function unless flagged with $stateful 22166 fn.$$watchDelegate = inputsWatchDelegate; 22167 useInputs = !parsedExpression.inputs; 22168 fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression]; 22169 } 22170 22171 return fn; 22172 } 22173 }]; 22174} 22175 22176/** 22177 * @ngdoc service 22178 * @name $q 22179 * @requires $rootScope 22180 * @this 22181 * 22182 * @description 22183 * A service that helps you run functions asynchronously, and use their return values (or exceptions) 22184 * when they are done processing. 22185 * 22186 * This is an implementation of promises/deferred objects inspired by 22187 * [Kris Kowal's Q](https://github.com/kriskowal/q). 22188 * 22189 * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred 22190 * implementations, and the other which resembles ES6 (ES2015) promises to
22190some degree. 22191 * 22192 * # $q constructor 22193 * 22194 * The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver` 22195 * function as the first argument. This is similar to the native Promise implementation from ES6, 22196 * see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). 22197 * 22198 * While the constructor-style use is supported, not all of the supporting methods from ES6 promises are 22199 * available yet. 22200 * 22201 * It can be used like so: 22202 * 22203 * ```js 22204 * // for the purpose of this example let's assume that variables `$q` and `okToGreet` 22205 * // are available in the current lexical scope (they could have been injected or passed in). 22206 * 22207 * function asyncGreet(name) { 22208 * // perform some asynchronous operation, resolve or reject the promise when appropriate. 22209 * return $q(function(resolve, reject) { 22210 * setTimeout(function() { 22211 * if (okToGreet(name)) { 22212 * resolve('Hello, ' + name + '!'); 22213 * } else { 22214 * reject('Greeting ' + name + ' is not allowed.'); 22215 * } 22216 * }, 1000); 22217 * }); 22218 * } 22219 * 22220 * var promise = asyncGreet('Robin Hood'); 22221 * promise.then(function(greeting) { 22222 * alert('Success: ' + greeting); 22223 * }, function(reason) { 22224 * alert('Failed: ' + reason); 22225 * }); 22226 * ``` 22227 * 22228 * Note: progress/notify callbacks are not currently supported via the ES6-style interface. 22229 * 22230 * Note: unlike ES6 behavior, an exception thrown in the constructor function will NOT implicitly reject the promise. 22231 * 22232 * However, the more traditional CommonJS-style usage is still available, and documented below. 22233 * 22234 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an 22235 * interface for interacting with an object that represents the result of an action that is 22236 * performed asynchronously, and may or may not be finished at any given point in time. 22237 * 22238 * From the perspective of dealing with error handling, deferred and promise APIs are to 22239 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. 22240 * 22241 * ```js 22242 * // for the purpose of this example let's assume that variables `$q` and `okToGreet` 22243 * // are available in the current lexical scope (they could have been injected or passed in). 22244 * 22245 * function asyncGreet(name) { 22246 * var deferred = $q.defer(); 22247 * 22248 * setTimeout(function() { 22249 * deferred.notify('About to greet ' + name + '.'); 22250 * 22251 * if (okToGreet(name)) { 22252 * deferred.resolve('Hello, ' + name + '!'); 22253 * } else { 22254 * deferred.reject('Greeting ' + name + ' is not allowed.'); 22255 * } 22256 * }, 1000); 22257 * 22258 * return deferred.promise; 22259 * } 22260 * 22261 * var promise = asyncGreet('Robin Hood'); 22262 * promise.then(function(greeting) { 22263 * alert('Success: ' + greeting); 22264 * }, function(reason) { 22265 * alert('Failed: ' + reason); 22266 * }, function(update) { 22267 * alert('Got notification: ' + update); 22268 * }); 22269 * ``` 22270 * 22271 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff 22272 * comes in the way of guarantees that promise and deferred APIs make, see 22273 * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md. 22274 * 22275 * Additionally the promise api allows for composition that is very hard to do with the 22276 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. 22277 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the 22278 * section on serial or parallel joining of promises. 22279 * 22280 * # The Deferred API 22281 * 22282 * A new instance of deferred is constructed by calling `$q.defer()`. 22283 * 22284 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs 22285 * that can be used for signaling the successful or unsuccessful completion, as well as the status 22286 * of the task. 22287 * 22288 * **Methods** 22289 * 22290 * - `resolve(value)` â resolves the derived promise with the `value`. If the value is a rejection 22291 * constructed via `$q.reject`, the promise will be rejected instead. 22292 * - `reject(reason)` â rejects the derived promise with the `reason`. This is equivalent to 22293 * resolving it with a rejection constructed via `$q.reject`. 22294 * - `notify(value)` - provides updates on the status of the promise's execution. This may be called 22295 * multiple times before the promise is either resolved or rejected. 22296 * 22297 * **Properties** 22298 * 22299 * - promise â `{Promise}` â promise object associated with this deferred. 22300 * 22301 * 22302 * # The Promise API 22303 * 22304 * A new promise instance is created when a deferred instance is created and can be retrieved by 22305 * calling `deferred.promise`. 22306 * 22307 * The purpose of the promise object is to allow for interested parties to get access to the result 22308 * of the deferred task when it completes. 22309 * 22310 * **Methods** 22311 * 22312 * - `then(successCallback, [errorCallback], [notifyCallback])` â regardless of when the promise was or 22313 * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously 22314 * as soon as the result is available. The callbacks are called with a single argument: the result 22315 * or rejection reason. Additionally, the notify callback may be called zero or more times to 22316 * provide a progress indication, before the promise is resolved or rejected. 22317 * 22318 * This method *returns a new promise* which is resolved or rejected via the return value of the 22319 * `successCallback`, `errorCallback` (unless that value is a promise, in which case it is resolved
22320 * with the value which is resolved in that promise using 22321 * [promise chaining](http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promises-queues)). 22322 * It also notifies via the return value of the `notifyCallback` method. The promise cannot be 22323 * resolved or rejected from the notifyCallback method. The errorCallback and notifyCallback 22324 * arguments are optional. 22325 * 22326 * - `catch(errorCallback)` â shorthand for `promise.then(null, errorCallback)` 22327 * 22328 * - `finally(callback, notifyCallback)` â allows you to observe either the fulfillment or rejection of a promise, 22329 * but to do so without modifying the final value. This is useful to release resources or do some 22330 * clean-up that needs to be done whether the promise was rejected or resolved. See the [full 22331 * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for 22332 * more information. 22333 * 22334 * # Chaining promises 22335 * 22336 * Because calling the `then` method of a promise returns a new derived promise, it is easily 22337 * possible to create a chain of promises: 22338 * 22339 * ```js 22340 * promiseB = promiseA.then(function(result) { 22341 * return result + 1; 22342 * }); 22343 * 22344 * // promiseB will be resolved immediately after promiseA is resolved and its value 22345 * // will be the result of promiseA incremented by 1 22346 * ``` 22347 * 22348 * It is possible to create chains of any length and since a promise can be resolved with another 22349 * promise (which will defer its resolution further), it is possible to pause/defer resolution of 22350 * the promises at any point in the chain. This makes it possible to implement powerful APIs like 22351 * $http's response interceptors. 22352 * 22353 * 22354 * # Differences between Kris Kowal's Q and $q 22355 * 22356 * There are two main differences: 22357 * 22358 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation 22359 * mechanism in angular, which means faster propagation of resolution or rejection into your 22360 * models and avoiding unnecessary browser repaints, which would result in flickering UI. 22361 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains
22362 * all the important functionality needed for common async tasks. 22363 * 22364 * # Testing 22365 * 22366 * ```js 22367 * it('should simulate promise', inject(function($q, $rootScope) { 22368 * var deferred = $q.defer(); 22369 * var promise = deferred.promise; 22370 * var resolvedValue; 22371 * 22372 * promise.then(function(value) { resolvedValue = value; }); 22373 * expect(resolvedValue).toBeUndefined(); 22374 * 22375 * // Simulate resolving of promise 22376 * deferred.resolve(123); 22377 * // Note that the 'then' function does not get called synchronously. 22378 * // This is because we want the promise API to always be async, whether or not 22379 * // it got called synchronously or asynchronously. 22380 * expect(resolvedValue).toBeUndefined(); 22381 * 22382 * // Propagate promise resolution to 'then' functions using $apply(). 22383 * $rootScope.$apply(); 22384 * expect(resolvedValue).toEqual(123); 22385 * })); 22386 * ``` 22387 * 22388 * @param {function(function, function)} resolver Function which is responsible for resolving or 22389 * rejecting the newly created promise. The first parameter is a function which resolves the 22390 * promise, the second parameter is a function which rejects the promise. 22391 * 22392 * @returns {Promise} The newly created promise. 22393 */ 22394function $QProvider() { 22395 22396 this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { 22397 return qFactory(function(callback) { 22398 $rootScope.$evalAsync(callback); 22399 }, $exceptionHandler); 22400 }]; 22401} 22402 22403/** @this */ 22404function $$QProvider() { 22405 this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) { 22406 return qFactory(function(callback) { 22407 $browser.defer(callback); 22408 }, $exceptionHandler); 22409 }]; 22410} 22411 22412/** 22413 * Constructs a promise manager. 22414 * 22415 * @param {function(function)} nextTick Function for executing functions in the next turn. 22416 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for 22417 * debugging purposes. 22418 * @returns {object} Promise manager. 22419 */ 22420function qFactory(nextTick, exceptionHandler) { 22421 var $qMinErr = minErr('$q', TypeError); 22422 22423 /** 22424 * @ngdoc method 22425 * @name ng.$q#defer 22426 * @kind function 22427 * 22428 * @description 22429 * Creates a `Deferred` object which represents a task which will finish in the future. 22430 * 22431 * @returns {Deferred} Returns a new instance of deferred. 22432 */ 22433 function defer() { 22434 var d = new Deferred(); 22435 //Necessary to support unbound execution :/ 22436 d.resolve = simpleBind(d, d.resolve); 22437 d.reject = simpleBind(d, d.reject); 22438 d.notify = simpleBind(d, d.notify); 22439 return d; 22440 } 22441 22442 function Promise() { 22443 this.$$state = { status: 0 }; 22444 } 22445 22446 extend(Promise.prototype, { 22447 then: function(onFulfilled, onRejected, progressBack) { 22448 if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) { 22449 return this; 22450 } 22451 var result = new Deferred(); 22452 22453 this.$$state.pending = this.$$state.pending || []; 22454 this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]); 22455 if (this.$$state.status > 0) scheduleProcessQueue(this.$$state); 22456 22457 return result.promise; 22458 }, 22459 22460 'catch': function(callback) { 22461 return this.then(null, callback); 22462 }, 22463 22464 'finally': function(callback, progressBack) { 22465 return this.then(function(value) { 22466 return handleCallback(value, resolve, callback); 22467 }, function(error) { 22468 return handleCallback(error, reject, callback); 22469 }, progressBack); 22470 } 22471 }); 22472 22473 //Faster, more basic than angular.bind http://jsperf.com/angular-bind-vs-custom-vs-native 22474 function simpleBind(context, fn) { 22475 return function(value) { 22476 fn.call(context, value); 22477 }; 22478 } 22479 22480 function processQueue(state) { 22481 var fn, deferred, pending; 22482 22483 pending = state.pending; 22484 state.processScheduled = false; 22485 state.pending = undefined; 22486 for (var i = 0, ii = pending.length; i < ii; ++i) { 22487 deferred = pending[i][0]; 22488 fn = pending[i][state.status]; 22489 try { 22490 if (isFunction(fn)) { 22491 deferred.resolve(fn(state.value)); 22492 } else if (state.status === 1) { 22493 deferred.resolve(state.value); 22494 } else { 22495 deferred.reject(state.value); 22496 } 22497 } catch (e) { 22498 deferred.reject(e); 22499 exceptionHandler(e); 22500 } 22501 } 22502 } 22503 22504 function scheduleProcessQueue(state) { 22505 if (state.processScheduled || !state.pending) return; 22506 state.processScheduled = true; 22507 nextTick(function() { processQueue(state); }); 22508 } 22509 22510 function Deferred() { 22511 this.promise = new Promise(); 22512 } 22513 22514 extend(Deferred.prototype, { 22515 resolve: function(val) { 22516 if (this.promise.$$state.status) return; 22517 if (val === this.promise) { 22518 this.$$reject($qMinErr( 22519 'qcycle', 22520 'Expected promise to be resolved with value other than
22520itself \'{0}\'', 22521 val)); 22522 } else { 22523 this.$$resolve(val); 22524 } 22525 22526 }, 22527 22528 $$resolve: function(val) { 22529 var then; 22530 var that = this; 22531 var done = false; 22532 try { 22533 if ((isObject(val) || isFunction(val))) then = val && val.then; 22534 if (isFunction(then)) { 22535 this.promise.$$state.status = -1; 22536 then.call(val, resolvePromise, rejectPromise, simpleBind(this, this.notify)); 22537 } else { 22538 this.promise.$$state.value = val; 22539 this.promise.$$state.status = 1; 22540 scheduleProcessQueue(this.promise.$$state); 22541 } 22542 } catch (e) { 22543 rejectPromise(e); 22544 exceptionHandler(e); 22545 } 22546 22547 function resolvePromise(val) { 22548 if (done) return; 22549 done = true; 22550 that.$$resolve(val); 22551 } 22552 function rejectPromise(val) { 22553 if (done) return; 22554 done = true; 22555 that.$$reject(val); 22556 } 22557 }, 22558 22559 reject: function(reason) { 22560 if (this.promise.$$state.status) return; 22561 this.$$reject(reason); 22562 }, 22563 22564 $$reject: function(reason) { 22565 this.promise.$$state.value = reason; 22566 this.promise.$$state.status = 2; 22567 scheduleProcessQueue(this.promise.$$state); 22568 }, 22569 22570 notify: function(progress) { 22571 var callbacks = this.promise.$$state.pending; 22572 22573 if ((this.promise.$$state.status <= 0) && callbacks && callbacks.length) { 22574 nextTick(function() { 22575 var callback, result; 22576 for (var i = 0, ii = callbacks.length; i < ii; i++) { 22577 result = callbacks[i][0]; 22578 callback = callbacks[i][3]; 22579 try { 22580 result.notify(isFunction(callback) ? callback(progress) : progress); 22581 } catch (e) { 22582 exceptionHandler(e); 22583 } 22584 } 22585 }); 22586 } 22587 } 22588 }); 22589 22590 /** 22591 * @ngdoc method 22592 * @name $q#reject 22593 * @kind function 22594 * 22595 * @description 22596 * Creates a promise that is resolved as rejected with the specified `reason`. This api should be 22597 * used to forward rejection in a chain of promises. If you are dealing with the last promise in 22598 * a promise chain, you don't need to worry about it. 22599 * 22600 * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of 22601 * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via 22602 * a promise error callback and you want to forward the error to the promise derived from the 22603 * current promise, you have to "rethrow" the error by returning a rejection constructed via 22604 * `reject`. 22605 * 22606 * ```js 22607 * promiseB = promiseA.then(function(result) { 22608 * // success: do something and resolve promiseB 22609 * // with the old or a new result 22610 * return result; 22611 * }, function(reason) { 22612 * // error: handle the error if possible and 22613 * // resolve promiseB with newPromiseOrValue, 22614 * // otherwise forward the rejection to promiseB 22615 * if (canHandle(reason)) { 22616 * // handle the error and recover 22617 * return newPromiseOrValue; 22618 * } 22619 * return $q.reject(reason); 22620 * }); 22621 * ``` 22622 * 22623 * @param {*} reason Constant, message, exception or an object representing the rejection reason. 22624 * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. 22625 */ 22626 function reject(reason) { 22627 var result = new Deferred(); 22628 result.reject(reason); 22629 return result.promise; 22630 } 22631 22632 function handleCallback(value, resolver, callback) { 22633 var callbackOutput = null; 22634 try { 22635 if (isFunction(callback)) callbackOutput = callback(); 22636 } catch (e) { 22637 return reject(e); 22638 } 22639 if (isPromiseLike(callbackOutput)) { 22640 return callbackOutput.then(function() { 22641 return resolver(value); 22642 }, reject); 22643 } else { 22644 return resolver(value); 22645 } 22646 } 22647 22648 /** 22649 * @ngdoc method 22650 * @name $q#when 22651 * @kind function 22652 * 22653 * @description 22654 * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. 22655 * This is useful when you are dealing with an object that might or might not be a promise, or if 22656 * the promise comes from a source that can't be trusted. 22657 * 22658 * @param {*} value Value or a promise 22659 * @param {Function=} successCallback 22660 * @param {Function=} errorCallback 22661 * @param {Function=} progressCallback 22662 * @returns {Promise} Returns a promise of the passed value or promise 22663 */ 22664 22665 22666 function when(value, callback, errback, progressBack) { 22667 var result = new Deferred(); 22668 result.resolve(value); 22669 return result.promise.then(callback, errback, progressBack); 22670 } 22671 22672 /** 22673 * @ngdoc method 22674 * @name $q#resolve 22675 * @kind function 22676 * 22677 * @description 22678 * Alias of {@link ng.$q#when when} to maintain naming consistency with ES6. 22679 * 22680 * @param {*} value Value or a promise 22681 * @param {Function=} successCallback 22682 * @param {Function=} errorCallback 22683 * @param {Function=} progressCallback 22684 * @returns {Promise} Returns a promise of the passed value or promise 22685 */ 22686 var resolve = when; 22687 22688 /** 22689 * @ngdoc method 22690 * @name $q#all 22691 * @kind function 22692 * 22693 * @description 22694 * Combines multiple promises into a single promise that is resolved when all of the input 22695 * promises are resolved. 22696 * 22697 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises. 22698 * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values, 22699 * each value corresponding to the promise at the same index/key in the `promises` array/hash. 22700 * If any of the promises is resolved with a rejection, this resulting promise will be rejected 22701 * with the same rejection value. 22702 */ 22703 22704 function all(promises) { 22705 var deferred = new Deferred(), 22706 counter = 0, 22707 results = isArray(promises) ? [] : {}; 22708
22709 forEach(promises, function(promise, key) { 22710 counter++; 22711 when(promise).then(function(value) { 22712 results[key] = value; 22713 if (!(--counter)) deferred.resolve(results); 22714 }, function(reason) { 22715 deferred.reject(reason); 22716 }); 22717 }); 22718 22719 if (counter === 0) { 22720 deferred.resolve(results); 22721 } 22722 22723 return deferred.promise; 22724 } 22725 22726 /** 22727 * @ngdoc method 22728 * @name $q#race 22729 * @kind function 22730 * 22731 * @description 22732 * Returns a promise that resolves or rejects as soon as one of those promises 22733 * resolves or rejects, with the value or reason from that promise. 22734 * 22735 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises. 22736 * @returns {Promise} a promise that resolves or rejects as soon as one of the `promises` 22737 * resolves or rejects, with the value or reason from that promise. 22738 */ 22739 22740 function race(promises) { 22741 var deferred = defer(); 22742 22743 forEach(promises, function(promise) { 22744 when(promise).then(deferred.resolve, deferred.reject); 22745 }); 22746 22747 return deferred.promise; 22748 } 22749 22750 function $Q(resolver) { 22751 if (!isFunction(resolver)) { 22752 throw $qMinErr('norslvr', 'Expected resolverFn, got \'{0}\'', resolver); 22753 } 22754 22755 var deferred = new Deferred(); 22756 22757 function resolveFn(value) { 22758 deferred.resolve(value); 22759 } 22760 22761 function rejectFn(reason) { 22762 deferred.reject(reason); 22763 } 22764 22765 resolver(resolveFn, rejectFn); 22766 22767 return deferred.promise; 22768 } 22769 22770 // Let's make the instanceof operator work for promises, so that 22771 // `new $q(fn) instanceof $q` would evaluate to true. 22772 $Q.prototype = Promise.prototype; 22773 22774 $Q.defer = defer; 22775 $Q.reject = reject; 22776 $Q.when = when; 22777 $Q.resolve = resolve; 22778 $Q.all = all; 22779 $Q.race = race; 22780 22781 return $Q; 22782} 22783 22784/** @this */ 22785function $$RAFProvider() { //rAF 22786 this.$get = ['$window', '$timeout', function($window, $timeout) { 22787 var requestAnimationFrame = $window.requestAnimationFrame || 22788 $window.webkitRequestAnimationFrame; 22789 22790 var cancelAnimationFrame = $window.cancelAnimationFrame || 22791 $window.webkitCancelAnimationFrame || 22792 $window.webkitCancelRequestAnimationFrame; 22793 22794 var rafSupported = !!requestAnimationFrame; 22795 var raf = rafSupported 22796 ? function(fn) { 22797 var id = requestAnimationFrame(fn); 22798 return function() { 22799 cancelAnimationFrame(id); 22800 }; 22801 } 22802 : function(fn) { 22803 var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666 22804 return function() { 22805 $timeout.cancel(timer); 22806 }; 22807 }; 22808 22809 raf.supported = rafSupported; 22810 22811 return raf; 22812 }]; 22813} 22814 22815/** 22816 * DESIGN NOTES 22817 * 22818 * The design decisions behind the scope are heavily favored for speed and memory consumption. 22819 * 22820 * The typical use of scope is to watch the expressions, which most of the time return the same 22821 * value as last time so we optimize the operation. 22822 * 22823 * Closures construction is expensive in terms of speed as well as memory: 22824 * - No closures, instead use prototypical inheritance for API 22825 * - Internal state needs to be stored on scope directly, which means that private state is 22826 * exposed as $$____ properties 22827 * 22828 * Loop operations are optimized by using while(count--) { ... } 22829 * - This means that in order to keep the same order of execution as addition we have to add 22830 * items to the array at the beginning (unshift) instead of at the end (push) 22831 * 22832 * Child scopes are created and removed often 22833 * - Using an array would be slow since inserts in the middle are expensive; so we use linked lists 22834 * 22835 * There are fewer watches than observers. This is why you don't want the observer to be implemented 22836 * in the same way as watch. Watch requires return of the initialization function which is expensive 22837 * to construct. 22838 */ 22839 22840 22841/** 22842 * @ngdoc provider 22843 * @name $rootScopeProvider 22844 * @description 22845 * 22846 * Provider for the $rootScope service. 22847 */ 22848 22849/** 22850 * @ngdoc method 22851 * @name $rootScopeProvider#digestTtl 22852 * @description 22853 * 22854 * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and 22855 * assuming that the model is unstable. 22856 * 22857 * The current default is 10 iterations. 22858 * 22859 * In complex applications it's possible that the dependencies between `$watch`s will result in 22860 * several digest iterations. However if an application needs more than the default 10 digest 22861 * iterations for its model to stabilize then you should investigate what is causing the model to 22862 * continuously change during the digest. 22863 * 22864 * Increasing the TTL could have performance implications, so you should not change it without 22865 * proper justification. 22866 * 22867 * @param {number} limit The number of digest iterations. 22868 */ 22869 22870 22871/** 22872 * @ngdoc service 22873 * @name $rootScope 22874 * @this 22875 * 22876 * @description 22877 * 22878 * Every application has a single root {@link ng.$rootScope.Scope scope}. 22879 * All other scopes are descendant scopes of the root scope. Scopes provide separation 22880 * between the model and the view, via a mechanism for watching the model for changes. 22881 * They also provide event emission/broadcast and subscription facility. See the 22882 * {@link guide/scope developer guide on scopes}. 22883 */ 22884function $RootScopeProvider() { 22885 var TTL = 10; 22886 var $rootScopeMinErr = minErr('$rootScope'); 22887 var lastDirtyWatch = null; 22888 var applyAsyncId = null; 22889 22890 this.digestTtl = function(value) { 22891 if (arguments.length) { 22892 TTL = value; 22893 } 22894 return TTL; 22895 }; 22896 22897 function createChildScopeClass(parent) { 22898 function ChildScope() {
vendor: 10,821 bytes, lines 22899-23156
22899 this.$$watchers = this.$$nextSibling = 22900 this.$$childHead = this.$$childTail = null; 22901 this.$$listeners = {}; 22902 this.$$listenerCount = {}; 22903 this.$$watchersCount = 0; 22904 this.$id = nextUid(); 22905 this.$$ChildScope = null; 22906 } 22907 ChildScope.prototype = parent; 22908 return ChildScope; 22909 } 22910 22911 this.$get = ['$exceptionHandler', '$parse', '$browser', 22912 function($exceptionHandler, $parse, $browser) { 22913 22914 function destroyChildScope($event) { 22915 $event.currentScope.$$destroyed = true; 22916 } 22917 22918 function cleanUpScope($scope) { 22919 22920 if (msie === 9) { 22921 // There is a memory leak in IE9 if all child scopes are not disconnected 22922 // completely when a scope is destroyed. So this code will recurse up through 22923 // all this scopes children 22924 // 22925 // See issue https://github.com/angular/angular.js/issues/10706 22926 if ($scope.$$childHead) { 22927 cleanUpScope($scope.$$childHead); 22928 } 22929 if ($scope.$$nextSibling) { 22930 cleanUpScope($scope.$$nextSibling); 22931 } 22932 } 22933 22934 // The code below works around IE9 and V8's memory leaks 22935 // 22936 // See: 22937 // - https://code.google.com/p/v8/issues/detail?id=2073#c26 22938 // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909 22939 // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 22940 22941 $scope.$parent = $scope.$$nextSibling = $scope.$$prevSibling = $scope.$$childHead = 22942 $scope.$$childTail = $scope.$root = $scope.$$watchers = null; 22943 } 22944 22945 /** 22946 * @ngdoc type 22947 * @name $rootScope.Scope 22948 * 22949 * @description 22950 * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the 22951 * {@link auto.$injector $injector}. Child scopes are created using the 22952 * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when 22953 * compiled HTML template is executed.) See also the {@link guide/scope Scopes guide} for 22954 * an in-depth introduction and usage examples. 22955 * 22956 * 22957 * # Inheritance 22958 * A scope can inherit from a parent scope, as in this example: 22959 * ```js 22960 var parent = $rootScope; 22961 var child = parent.$new(); 22962 22963 parent.salutation = "Hello"; 22964 expect(child.salutation).toEqual('Hello'); 22965 22966 child.salutation = "Welcome"; 22967 expect(child.salutation).toEqual('Welcome'); 22968 expect(parent.salutation).toEqual('Hello'); 22969 * ``` 22970 * 22971 * When interacting with `Scope` in tests, additional helper methods are available on the 22972 * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional 22973 * details. 22974 * 22975 * 22976 * @param {Object.<string, function()>=} providers Map of service factory which need to be 22977 * provided for the current scope. Defaults to {@link ng}. 22978 * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should 22979 * append/override services provided by `providers`. This is handy 22980 * when unit-testing and having the need to override a default 22981 * service. 22982 * @returns {Object} Newly created scope. 22983 * 22984 */ 22985 function Scope() { 22986 this.$id = nextUid(); 22987 this.$$phase = this.$parent = this.$$watchers = 22988 this.$$nextSibling = this.$$prevSibling = 22989 this.$$childHead = this.$$childTail = null; 22990 this.$root = this; 22991 this.$$destroyed = false; 22992 this.$$listeners = {}; 22993 this.$$listenerCount = {}; 22994 this.$$watchersCount = 0; 22995 this.$$isolateBindings = null; 22996 } 22997 22998 /** 22999 * @ngdoc property 23000 * @name $rootScope.Scope#$id 23001 * 23002 * @description 23003 * Unique scope ID (monotonically increasing) useful for debugging. 23004 */ 23005 23006 /** 23007 * @ngdoc property 23008 * @name $rootScope.Scope#$parent 23009 * 23010 * @description 23011 * Reference to the parent scope. 23012 */ 23013 23014 /** 23015 * @ngdoc property 23016 * @name $rootScope.Scope#$root 23017 * 23018 * @description 23019 * Reference to the root scope. 23020 */ 23021 23022 Scope.prototype = { 23023 constructor: Scope, 23024 /** 23025 * @ngdoc method 23026 * @name $rootScope.Scope#$new 23027 * @kind function 23028 * 23029 * @description 23030 * Creates a new child {@link ng.$rootScope.Scope scope}. 23031 * 23032 * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} event. 23033 * The scope can be removed from the scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}. 23034 * 23035 * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is 23036 * desired for the scope and its child scopes to be permanently detached from the parent and 23037 * thus stop participating in model change detection and listener notification by invoking. 23038 * 23039 * @param {boolean} isolate If true, then the scope does not prototypically inherit from the 23040 * parent scope. The scope is isolated, as it can not see parent scope properties. 23041 * When creating widgets, it is useful for the widget to not accidentally read parent 23042 * state. 23043 * 23044 * @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent` 23045 * of the newly created scope. Defaults to `this` scope if not provided. 23046 * This is used when creating a transclude scope to correctly place it 23047 * in the scope hierarchy while maintaining the correct prototypical 23048 * inheritance. 23049 * 23050 * @returns {Object} The newly created child scope. 23051 * 23052 */ 23053 $new: function(isolate, parent) { 23054 var child; 23055 23056 parent = parent || this; 23057 23058 if (isolate) { 23059 child = new Scope(); 23060 child.$root = this.$root; 23061 } else { 23062 // Only create a child scope class if somebody asks for one, 23063 // but cache it to allow the VM to optimize lookups. 23064 if (!this.$$ChildScope) { 23065 this.$$ChildScope = createChildScopeClass(this); 23066 } 23067 child = new this.$$ChildScope(); 23068 } 23069 child.$parent = parent; 23070 child.$$prevSibling = parent.$$childTail; 23071 if (parent.$$childHead) { 23072 parent.$$childTail.$$nextSibling = child; 23073 parent.$$childTail = child; 23074 } else { 23075 parent.$$childHead = parent.$$childTail = child; 23076 } 23077 23078 // When the new scope is not isolated or we inherit from `this`, and 23079 // the parent scope is destroyed, the property `$$destroyed` is inherited 23080 // prototypically. In all other cases, this property needs to be set 23081 // when the parent scope is destroyed. 23082 // The listener needs to be added after the parent is set 23083 if (isolate || parent !== this) child.$on('$destroy', destroyChildScope); 23084 23085 return child; 23086 }, 23087 23088 /** 23089 * @ngdoc method 23090 * @name $rootScope.Scope#$watch 23091 * @kind function 23092 * 23093 * @description 23094 * Registers a `listener` callback to be executed whenever the `watchExpression` changes. 23095 * 23096 * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest 23097 * $digest()} and should return the value that will be watched. (`watchExpression` should not change 23098 * its value when executed multiple times with the same input because it may be executed multiple 23099 * times by {@link ng.$rootScope.Scope#$digest $digest()}. That is, `watchExpression` should be 23100 * [idempotent](http://en.wikipedia.org/wiki/Idempotence).) 23101 * - The `listener` is called only when the value from the current `watchExpression` and the 23102 * previous call to `watchExpression` are not equal (with the exception of the initial run, 23103 * see below). Inequality is determined according to reference inequality, 23104 * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators) 23105 * via the `!==` Javascript operator, unless `objectEquality == true` 23106 * (see next point) 23107 * - When `objectEquality == true`, inequality of the `watchExpression` is determined 23108 * according to the {@link angular.equals} function. To save the value of the object for 23109 * later comparison, the {@link angular.copy} function is used. This therefore means that 23110 * watching complex objects will have adverse memory and performance implications. 23111 * - The watch `listener` may change the model, which may trigger other `listener`s to fire. 23112 * This is achieved by rerunning the watchers until no changes are detected. The rerun 23113 * iteration limit is 10 to prevent an infinite loop deadlock. 23114 * 23115 * 23116 * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called, 23117 * you can register a `watchExpression` function with no `listener`. (Be prepared for 23118 * multiple calls to your `watchExpression` because it will execute multiple times in a 23119 * single {@link ng.$rootScope.Scope#$digest $digest} cycle if a change is detected.) 23120 * 23121 * After a watcher is registered with the scope, the `listener` fn is called asynchronously 23122 * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the 23123 * watcher. In rare cases, this is undesirable because the listener is called when the result 23124 * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you 23125 * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the 23126 * listener was called due to initialization. 23127 * 23128 * 23129 * 23130 * # Example 23131 * ```js 23132 // let's assume that scope was dependency injected as the $rootScope 23133 var scope = $rootScope; 23134 scope.name = 'misko'; 23135 scope.counter = 0; 23136 23137 expect(scope.counter).toEqual(0); 23138 scope.$watch('name', function(newValue, oldValue) { 23139 scope.counter = scope.counter + 1; 23140 }); 23141 expect(scope.counter).toEqual(0); 23142 23143 scope.$digest(); 23144 // the listener is always called during the first $digest loop after it was registered 23145 expect(scope.counter).toEqual(1); 23146 23147 scope.$digest(); 23148 // but now it will not be called unless the value changes 23149 expect(scope.counter).toEqual(1); 23150 23151 scope.name = 'adam'; 23152 scope.$digest(); 23153 expect(scope.counter).toEqual(2); 23154 23155 23156
23157 // Using a function as a watchExpression 23158 var food; 23159 scope.foodCounter = 0; 23160 expect(scope.foodCounter).toEqual(0); 23161 scope.$watch( 23162 // This function returns the value being watched. It is called for each turn of the $digest loop 23163 function() { return food; }, 23164 // This is the change listener, called when the value returned from the above function changes 23165 function(newValue, oldValue) { 23166 if ( newValue !== oldValue ) { 23167 // Only increment the counter if the value changed 23168 scope.foodCounter = scope.foodCounter + 1; 23169 } 23170 } 23171 ); 23172 // No digest has been run so the counter will be zero 23173 expect(scope.foodCounter).toEqual(0); 23174 23175 // Run the digest but since food has not changed count will still be zero 23176 scope.$digest(); 23177 expect(scope.foodCounter).toEqual(0); 23178 23179 // Update food and run digest. Now the counter will increment 23180 food = 'cheeseburger'; 23181 scope.$digest(); 23182 expect(scope.foodCounter).toEqual(1); 23183 23184 * ``` 23185 * 23186 * 23187 * 23188 * @param {(function()|string)} watchExpression Expression that is evaluated on each 23189 * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers 23190 * a call to the `listener`. 23191 * 23192 * - `string`: Evaluated as {@link guide/expression expression} 23193 * - `function(scope)`: called with current `scope` as a parameter. 23194 * @param {function(newVal, oldVal, scope)} listener Callback called whenever the value 23195 * of `watchExpression` changes. 23196 * 23197 * - `newVal` contains the current value of the `watchExpression` 23198 * - `oldVal` contains the previous value of the `watchExpression` 23199 * - `scope` refers to the current scope 23200 * @param {boolean=} [objectEquality=false] Compare for object equality using {@link angular.equals} instead of 23201 * comparing for reference equality. 23202 * @returns {function()} Returns a deregistration function for this listener. 23203 */ 23204 $watch: function(watchExp, listener, objectEquality, prettyPrintExpression) { 23205 var get = $parse(watchExp); 23206 23207 if (get.$$watchDelegate) { 23208 return get.$$watchDelegate(this, listener, objectEquality, get, watchExp); 23209 } 23210 var scope = this, 23211 array = scope.$$watchers, 23212 watcher = { 23213 fn: listener, 23214 last: initWatchVal, 23215 get: get, 23216 exp: prettyPrintExpression || watchExp, 23217 eq: !!objectEquality 23218 }; 23219 23220 lastDirtyWatch = null; 23221 23222 if (!isFunction(listener)) { 23223 watcher.fn = noop; 23224 } 23225 23226 if (!array) { 23227 array = scope.$$watchers = []; 23228 array.$$digestWatchIndex = -1; 23229 } 23230 // we use unshift since we use a while loop in $digest for speed. 23231 // the while loop reads in reverse order. 23232 array.unshift(watcher); 23233 array.$$digestWatchIndex++; 23234 incrementWatchersCount(this, 1); 23235 23236 return function deregisterWatch() { 23237 var index = arrayRemove(array, watcher); 23238 if (index >= 0) { 23239 incrementWatchersCount(scope, -1); 23240 if (index < array.$$digestWatchIndex) { 23241 array.$$digestWatchIndex--; 23242 } 23243 } 23244 lastDirtyWatch = null; 23245 }; 23246 }, 23247 23248 /** 23249 * @ngdoc method 23250 * @name $rootScope.Scope#$watchGroup 23251 * @kind function 23252 * 23253 * @description 23254 * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`. 23255 * If any one expression in the collection changes the `listener` is executed. 23256 * 23257 * - The items in the `watchExpressions` array are observed via the standard `$watch` operation. Their return 23258 * values are examined for changes on every call to `$digest`. 23259 * - The `listener` is called whenever any expression in the `watchExpressions` array changes. 23260 * 23261 * @param {Array.<string|Function(scope)>} watchExpressions Array of expressions that will be individually 23262 * watched using {@link ng.$rootScope.Scope#$watch $watch()} 23263 * 23264 * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any 23265 * expression in `watchExpressions` changes 23266 * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching 23267 * those of `watchExpression` 23268 * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching 23269 * those of `watchExpression` 23270 * The `scope` refers to the current scope. 23271 * @returns {function()} Returns a de-registration function for all listeners. 23272 */ 23273 $watchGroup: function(watchExpressions, listener) { 23274 var oldValues = new Array(watchExpressions.length); 23275 var newValues = new Array(watchExpressions.length); 23276 var deregisterFns = []; 23277 var self = this; 23278 var changeReactionScheduled = false;
vendor: 4,272 bytes, lines 23279-23388
23279 var firstRun = true; 23280 23281 if (!watchExpressions.length) { 23282 // No expressions means we call the listener ASAP 23283 var shouldCall = true; 23284 self.$evalAsync(function() { 23285 if (shouldCall) listener(newValues, newValues, self); 23286 }); 23287 return function deregisterWatchGroup() { 23288 shouldCall = false; 23289 }; 23290 } 23291 23292 if (watchExpressions.length === 1) { 23293 // Special case size of one 23294 return this.$watch(watchExpressions[0], function watchGroupAction(value, oldValue, scope) { 23295 newValues[0] = value; 23296 oldValues[0] = oldValue; 23297 listener(newValues, (value === oldValue) ? newValues : oldValues, scope); 23298 }); 23299 } 23300 23301 forEach(watchExpressions, function(expr, i) { 23302 var unwatchFn = self.$watch(expr, function watchGroupSubAction(value, oldValue) { 23303 newValues[i] = value; 23304 oldValues[i] = oldValue; 23305 if (!changeReactionScheduled) { 23306 changeReactionScheduled = true; 23307 self.$evalAsync(watchGroupAction); 23308 } 23309 }); 23310 deregisterFns.push(unwatchFn); 23311 }); 23312 23313 function watchGroupAction() { 23314 changeReactionScheduled = false; 23315 23316 if (firstRun) { 23317 firstRun = false; 23318 listener(newValues, newValues, self); 23319 } else { 23320 listener(newValues, oldValues, self); 23321 } 23322 } 23323 23324 return function deregisterWatchGroup() { 23325 while (deregisterFns.length) { 23326 deregisterFns.shift()(); 23327 } 23328 }; 23329 }, 23330 23331 23332 /** 23333 * @ngdoc method 23334 * @name $rootScope.Scope#$watchCollection 23335 * @kind function 23336 * 23337 * @description 23338 * Shallow watches the properties of an object and fires whenever any of the properties change 23339 * (for arrays, this implies watching the array items; for object maps, this implies watching 23340 * the properties). If a change is detected, the `listener` callback is fired. 23341 * 23342 * - The `obj` collection is observed via standard $watch operation and is examined on every 23343 * call to $digest() to see if any items have been added, removed, or moved. 23344 * - The `listener` is called whenever anything within the `obj` has changed. Examples include 23345 * adding, removing, and moving items belonging to an object or array. 23346 * 23347 * 23348 * # Example 23349 * ```js 23350 $scope.names = ['igor', 'matias', 'misko', 'james']; 23351 $scope.dataCount = 4; 23352 23353 $scope.$watchCollection('names', function(newNames, oldNames) { 23354 $scope.dataCount = newNames.length; 23355 }); 23356 23357 expect($scope.dataCount).toEqual(4); 23358 $scope.$digest(); 23359 23360 //still at 4 ... no changes 23361 expect($scope.dataCount).toEqual(4); 23362 23363 $scope.names.pop(); 23364 $scope.$digest(); 23365 23366 //now there's been a change 23367 expect($scope.dataCount).toEqual(3); 23368 * ``` 23369 * 23370 * 23371 * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The 23372 * expression value should evaluate to an object or an array which is observed on each 23373 * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the 23374 * collection will trigger a call to the `listener`. 23375 * 23376 * @param {function(newCollection, oldCollection, scope)} listener a callback function called 23377 * when a change is detected. 23378 * - The `newCollection` object is the newly modified data obtained from the `obj` expression 23379 * - The `oldCollection` object is a copy of the former collection data. 23380 * Due to performance considerations, the`oldCollection` value is computed only if the 23381 * `listener` function declares two or more arguments. 23382 * - The `scope` argument refers to the current scope. 23383 * 23384 * @returns {function()} Returns a de-registration function for this listener. When the 23385 * de-registration function is executed, the internal watch operation is terminated. 23386 */ 23387 $watchCollection: function(obj, listener) { 23388 $watchCollectionInterceptor.$stateful = tru
vendor: 6,149 bytes, lines 23388-23546
23388e; 23389 23390 var self = this; 23391 // the current value, updated on each dirty-check run 23392 var newValue; 23393 // a shallow copy of the newValue from the last dirty-check run, 23394 // updated to match newValue during dirty-check run 23395 var oldValue; 23396 // a shallow copy of the newValue from when the last change happened 23397 var veryOldValue; 23398 // only track veryOldValue if the listener is asking for it 23399 var trackVeryOldValue = (listener.length > 1); 23400 var changeDetected = 0; 23401 var changeDetector = $parse(obj, $watchCollectionInterceptor); 23402 var internalArray = []; 23403 var internalObject = {}; 23404 var initRun = true; 23405 var oldLength = 0; 23406 23407 function $watchCollectionInterceptor(_value) { 23408 newValue = _value; 23409 var newLength, key, bothNaN, newItem, oldItem; 23410 23411 // If the new value is undefined, then return undefined as the watch may be a one-time watch 23412 if (isUndefined(newValue)) return; 23413 23414 if (!isObject(newValue)) { // if primitive 23415 if (oldValue !== newValue) { 23416 oldValue = newValue; 23417 changeDetected++; 23418 } 23419 } else if (isArrayLike(newValue)) { 23420 if (oldValue !== internalArray) { 23421 // we are transitioning from something which was not an array into array. 23422 oldValue = internalArray; 23423 oldLength = oldValue.length = 0; 23424 changeDetected++; 23425 } 23426 23427 newLength = newValue.length; 23428 23429 if (oldLength !== newLength) { 23430 // if lengths do not match we need to trigger change notification 23431 changeDetected++; 23432 oldValue.length = oldLength = newLength; 23433 } 23434 // copy the items to oldValue and look for changes. 23435 for (var i = 0; i < newLength; i++) { 23436 oldItem = oldValue[i]; 23437 newItem = newValue[i]; 23438 23439 // eslint-disable-next-line no-self-compare 23440 bothNaN = (oldItem !== oldItem) && (newItem !== newItem); 23441 if (!bothNaN && (oldItem !== newItem)) { 23442 changeDetected++; 23443 oldValue[i] = newItem; 23444 } 23445 } 23446 } else { 23447 if (oldValue !== internalObject) { 23448 // we are transitioning from something which was not an object into object. 23449 oldValue = internalObject = {}; 23450 oldLength = 0; 23451 changeDetected++; 23452 } 23453 // copy the items to oldValue and look for changes. 23454 newLength = 0; 23455 for (key in newValue) { 23456 if (hasOwnProperty.call(newValue, key)) { 23457 newLength++; 23458 newItem = newValue[key]; 23459 oldItem = oldValue[key]; 23460 23461 if (key in oldValue) { 23462 // eslint-disable-next-line no-self-compare 23463 bothNaN = (oldItem !== oldItem) && (newItem !== newItem); 23464 if (!bothNaN && (oldItem !== newItem)) { 23465 changeDetected++; 23466 oldValue[key] = newItem; 23467 } 23468 } else { 23469 oldLength++; 23470 oldValue[key] = newItem; 23471 changeDetected++; 23472 } 23473 } 23474 } 23475 if (oldLength > newLength) { 23476 // we used to have more keys, need to find them and destroy them. 23477 changeDetected++; 23478 for (key in oldValue) { 23479 if (!hasOwnProperty.call(newValue, key)) { 23480 oldLength--; 23481 delete oldValue[key]; 23482 } 23483 } 23484 } 23485 } 23486 return changeDetected; 23487 } 23488 23489 function $watchCollectionAction() { 23490 if (initRun) { 23491 initRun = false; 23492 listener(newValue, newValue, self); 23493 } else { 23494 listener(newValue, veryOldValue, self); 23495 } 23496 23497 // make a copy for the next time a collection is changed 23498 if (trackVeryOldValue) { 23499 if (!isObject(newValue)) { 23500 //primitive 23501 veryOldValue = newValue; 23502 } else if (isArrayLike(newValue)) { 23503 veryOldValue = new Array(newValue.length); 23504 for (var i = 0; i < newValue.length; i++) { 23505 veryOldValue[i] = newValue[i]; 23506 } 23507 } else { // if object 23508 veryOldValue = {}; 23509 for (var key in newValue) { 23510 if (hasOwnProperty.call(newValue, key)) { 23511 veryOldValue[key] = newValue[key]; 23512 } 23513 } 23514 } 23515 } 23516 } 23517 23518 return this.$watch(changeDetector, $watchCollectionAction); 23519 }, 23520 23521 /** 23522 * @ngdoc method 23523 * @name $rootScope.Scope#$digest 23524 * @kind function 23525 * 23526 * @description 23527 * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and 23528 * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change 23529 * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} 23530 * until no more listeners are firing. This means that it is possible to get into an infinite 23531 * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of 23532 * iterations exceeds 10. 23533 * 23534 * Usually, you don't call `$digest()` directly in 23535 * {@link ng.directive:ngController controllers} or in 23536 * {@link ng.$compileProvider#directive directives}. 23537 * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within 23538 * a {@link ng.$compileProvider#directive directive}), which will force a `$digest()`. 23539 * 23540 * If you want to be notified whenever `$digest()` is called, 23541 * you can register a `watchExpression` function with 23542 * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`. 23543 * 23544 * In unit tests, you may need to call `$digest()` to simulate the scope life cycle. 23545 * 23546 *
vendor: 8,924 bytes, lines 23546-23764
23546# Example 23547 * ```js 23548 var scope = ...; 23549 scope.name = 'misko'; 23550 scope.counter = 0; 23551 23552 expect(scope.counter).toEqual(0); 23553 scope.$watch('name', function(newValue, oldValue) { 23554 scope.counter = scope.counter + 1; 23555 }); 23556 expect(scope.counter).toEqual(0); 23557 23558 scope.$digest(); 23559 // the listener is always called during the first $digest loop after it was registered 23560 expect(scope.counter).toEqual(1); 23561 23562 scope.$digest(); 23563 // but now it will not be called unless the value changes 23564 expect(scope.counter).toEqual(1); 23565 23566 scope.name = 'adam'; 23567 scope.$digest(); 23568 expect(scope.counter).toEqual(2); 23569 * ``` 23570 * 23571 */ 23572 $digest: function() { 23573 var watch, value, last, fn, get, 23574 watchers, 23575 dirty, ttl = TTL, 23576 next, current, target = this, 23577 watchLog = [], 23578 logIdx, asyncTask; 23579 23580 beginPhase('$digest'); 23581 // Check for changes to browser url that happened in sync before the call to $digest 23582 $browser.$$checkUrlChange(); 23583 23584 if (this === $rootScope && applyAsyncId !== null) { 23585 // If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then 23586 // cancel the scheduled $apply and flush the queue of expressions to be evaluated. 23587 $browser.defer.cancel(applyAsyncId); 23588 flushApplyAsync(); 23589 } 23590 23591 lastDirtyWatch = null; 23592 23593 do { // "while dirty" loop 23594 dirty = false; 23595 current = target; 23596 23597 // It's safe for asyncQueuePosition to be a local variable here because this loop can't 23598 // be reentered recursively. Calling $digest from a function passed to $applyAsync would 23599 // lead to a '$digest already in progress' error. 23600 for (var asyncQueuePosition = 0; asyncQueuePosition < asyncQueue.length; asyncQueuePosition++) { 23601 try { 23602 asyncTask = asyncQueue[asyncQueuePosition]; 23603 asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals); 23604 } catch (e) { 23605 $exceptionHandler(e); 23606 } 23607 lastDirtyWatch = null; 23608 } 23609 asyncQueue.length = 0; 23610 23611 traverseScopesLoop: 23612 do { // "traverse the scopes" loop 23613 if ((watchers = current.$$watchers)) { 23614 // process our watches 23615 watchers.$$digestWatchIndex = watchers.length; 23616 while (watchers.$$digestWatchIndex--) { 23617 try { 23618 watch = watchers[watchers.$$digestWatchIndex]; 23619 // Most common watches are on primitives, in which case we can short 23620 // circuit it with === operator, only when === fails do we use .equals 23621 if (watch) { 23622 get = watch.get; 23623 if ((value = get(current)) !== (last = watch.last) && 23624 !(watch.eq 23625 ? equals(value, last) 23626 : (isNumberNaN(value) && isNumberNaN(last)))) { 23627 dirty = true; 23628 lastDirtyWatch = watch; 23629 watch.last = watch.eq ? copy(value, null) : value; 23630 fn = watch.fn; 23631 fn(value, ((last === initWatchVal) ? value : last), current); 23632 if (ttl < 5) { 23633 logIdx = 4 - ttl; 23634 if (!watchLog[logIdx]) watchLog[logIdx] = []; 23635 watchLog[logIdx].push({ 23636 msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp, 23637 newVal: value, 23638 oldVal: last 23639 }); 23640 } 23641 } else if (watch === lastDirtyWatch) { 23642 // If the most recently dirty watcher is now clean, short circuit since the remaining watchers 23643 // have already been tested. 23644 dirty = false; 23645 break traverseScopesLoop; 23646 } 23647 } 23648 } catch (e) { 23649 $exceptionHandler(e); 23650 } 23651 } 23652 } 23653 23654 // Insanity Warning: scope depth-first traversal 23655 // yes, this code is a bit crazy, but it works and we have tests to prove it! 23656 // this piece should be kept in sync with the traversal in $broadcast 23657 if (!(next = ((current.$$watchersCount && current.$$childHead) || 23658 (current !== target && current.$$nextSibling)))) { 23659 while (current !== target && !(next = current.$$nextSibling)) { 23660 current = current.$parent; 23661 } 23662 } 23663 } while ((current = next)); 23664 23665 // `break traverseScopesLoop;` takes us to here 23666 23667 if ((dirty || asyncQueue.length) && !(ttl--)) { 23668 clearPhase(); 23669 throw $rootScopeMinErr('infdig', 23670 '{0} $digest() iterations reached. Aborting!\n' + 23671 'Watchers fired in the last 5 iterations: {1}', 23672 TTL, watchLog); 23673 } 23674 23675 } while (dirty || asyncQueue.length); 23676 23677 clearPhase(); 23678 23679 // postDigestQueuePosition isn't local here because this loop can be reentered recursively. 23680 while (postDigestQueuePosition < postDigestQueue.length) { 23681 try { 23682 postDigestQueue[postDigestQueuePosition++](); 23683 } catch (e) { 23684 $exceptionHandler(e); 23685 } 23686 } 23687 postDigestQueue.length = postDigestQueuePosition = 0; 23688 }, 23689 23690 23691 /** 23692 * @ngdoc event 23693 * @name $rootScope.Scope#$destroy 23694 * @eventType broadcast on scope being destroyed 23695 * 23696 * @description 23697 * Broadcasted when a scope and its children are being destroyed. 23698 * 23699 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 23700 * clean up DOM bindings before an element is removed from the DOM. 23701 */ 23702 23703 /** 23704 * @ngdoc method 23705 * @name $rootScope.Scope#$destroy 23706 * @kind function 23707 * 23708 * @description 23709 * Removes the current scope (and all of its children) from the parent scope. Removal implies 23710 * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer 23711 * propagate to the current scope and its children. Removal also implies that the current 23712 * scope is eligible for garbage collection. 23713 * 23714 * The `$destroy()` is usually used by directives such as 23715 * {@link ng.directive:ngRepeat ngRepeat} for managing the 23716 * unrolling of the loop. 23717 * 23718 * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope. 23719 * Application code can register a `$destroy` event handler that will give it a chance to 23720 * perform any necessary cleanup. 23721 * 23722 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 23723 * clean up DOM bindings before an element is removed from the DOM. 23724 */ 23725 $destroy: function() { 23726 // We can't destroy a scope that has been already destroyed. 23727 if (this.$$destroyed) return; 23728 var parent = this.$parent; 23729 23730 this.$broadcast('$destroy'); 23731 this.$$destroyed = true; 23732 23733 if (this === $rootScope) { 23734 //Remove handlers attached to window when $rootScope is removed 23735 $browser.$$applicationDestroyed(); 23736 } 23737 23738 incrementWatchersCount(this, -this.$$watchersCount); 23739 for (var eventName in this.$$listenerCount) { 23740 decrementListenerCount(this, this.$$listenerCount[eventName], eventName); 23741 } 23742 23743 // sever all the references to parent scopes (after this cleanup, the current scope should 23744 // not be retained by any of our references and should be eligible for garbage collection) 23745 if (parent && parent.$$childHead === this) parent.$$childHead = this.$$nextSibling; 23746 if (parent && parent.$$childTail === this) parent.$$childTail = this.$$prevSibling; 23747 if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; 23748 if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; 23749 23750 // Disable listeners, watchers and apply/digest methods 23751 this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop; 23752 this.$on = this.$watch = this.$watchGroup = function() { return noop; }; 23753 this.$$listeners = {}; 23754 23755 // Disconnect the next sibling to prevent `cleanUpScope` destroying those too 23756 this.$$nextSibling = null; 23757 cleanUpScope(this); 23758 }, 23759 23760 /** 23761 * @ngdoc method 23762 * @name $rootScope.Scope#$eval 23763 * @kind function 23764 *
23765 * @description 23766 * Executes the `expression` on the current scope and returns the result. Any exceptions in 23767 * the expression are propagated (uncaught). This is useful when evaluating Angular 23768 * expressions. 23769 * 23770 * # Example 23771 * ```js 23772 var scope = ng.$rootScope.Scope(); 23773 scope.a = 1; 23774 scope.b = 2; 23775 23776 expect(scope.$eval('a+b')).toEqual(3); 23777 expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3); 23778 * ``` 23779 * 23780 * @param {(string|function())=} expression An angular expression to be executed. 23781 * 23782 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 23783 * - `function(scope)`: execute the function with the current `scope` parameter. 23784 * 23785 * @param {(object)=} locals Local variables object, useful for overriding values in scope. 23786 * @returns {*} The result of evaluating the expression. 23787 */ 23788 $eval: function(expr, locals) { 23789 return $parse(expr)(this, locals); 23790 }, 23791 23792 /** 23793 * @ngdoc method 23794 * @name $rootScope.Scope#$evalAsync 23795 * @kind function 23796 * 23797 * @description 23798 * Executes the expression on the current scope at a later point in time. 23799 * 23800 * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only 23801 * that: 23802 * 23803 * - it will execute after the function that scheduled the evaluation (preferably before DOM 23804 * rendering). 23805 * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after 23806 * `expression` execution. 23807 * 23808 * Any exceptions from the execution of the expression are forwarded to the 23809 * {@link ng.$exceptionHandler $exceptionHandler} service. 23810 * 23811 * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle 23812 * will be scheduled. However, it is encouraged to always call code that changes the model 23813 * from within an `$apply` call. That includes code evaluated via `$evalAsync`. 23814 * 23815 * @param {(string|function())=} expression An angular expression to be executed. 23816 * 23817 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 23818 * - `function(scope)`: execute the function with the current `scope` parameter. 23819 * 23820 * @param {(object)=} locals Local variables object, useful for overriding values in scope. 23821 */ 23822 $evalAsync: function(expr, locals) { 23823 // if we are outside of an $digest loop and this is the first time we are scheduling async 23824 // task also schedule async auto-flush 23825 if (!$rootScope.$$phase && !asyncQueue.length) { 23826 $browser.defer(function() { 23827 if (asyncQueue.length) { 23828 $rootScope.$digest(); 23829 } 23830 }); 23831 } 23832 23833 asyncQueue.push({scope: this, expression: $parse(expr), locals: locals}); 23834 }, 23835 23836 $$postDigest: function(fn) { 23837 postDigestQueue.push(fn); 23838 }, 23839 23840 /** 23841 * @ngdoc method 23842 * @name $rootScope.Scope#$apply 23843 * @kind function 23844 * 23845 * @description 23846 * `$apply()` is used to execute an expression in angular from outside of the angular 23847 * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). 23848 * Because we are calling into the angular framework we need to perform proper scope life 23849 * cycle of {@link ng.$exceptionHandler exception handling}, 23850 * {@link ng.$rootScope.Scope#$digest executing watches}. 23851 * 23852 * ## Life cycle 23853 * 23854 * # Pseudo-Code of `$apply()` 23855 * ```js 23856 function $apply(expr) { 23857 try { 23858 return $eval(expr); 23859 } catch (e) { 23860 $exceptionHandler(e); 23861 } finally { 23862 $root.$digest(); 23863 } 23864 } 23865 * ``` 23866 * 23867 * 23868 * Scope's `$apply()` method transitions through the following stages: 23869 * 23870 * 1. The {@link guide/expression expression} is executed using the 23871 * {@link ng.$rootScope.Scope#$eval $eval()} method. 23872 * 2. Any exceptions from the execution of the expression are forwarded to the 23873 * {@link ng.$exceptionHandler $exceptionHandler} service.
23874 * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the 23875 * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method. 23876 * 23877 * 23878 * @param {(string|function())=} exp An angular expression to be executed. 23879 * 23880 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 23881 * - `function(scope)`: execute the function with current `scope` parameter. 23882 * 23883 * @returns {*} The result of evaluating the expression. 23884 */ 23885 $apply: function(expr) { 23886 try { 23887 beginPhase('$apply'); 23888 try { 23889 return this.$eval(expr); 23890 } finally { 23891 clearPhase(); 23892 } 23893 } catch (e) { 23894 $exceptionHandler(e); 23895 } finally { 23896 try { 23897 $rootScope.$digest(); 23898 } catch (e) { 23899 $exceptionHandler(e); 23900 // eslint-disable-next-line no-unsafe-finally 23901 throw e; 23902 } 23903 } 23904 }, 23905 23906 /** 23907 * @ngdoc method 23908 * @name $rootScope.Scope#$applyAsync 23909 * @kind function 23910 * 23911 * @description 23912 * Schedule the invocation of $apply to occur at a later time. The actual time difference 23913 * varies across browsers, but is typically around ~10 milliseconds. 23914 * 23915 * This can be used to queue up multiple expressions which need to be evaluated in the same 23916 * digest. 23917 * 23918 * @param {(string|function())=} exp An angular expression to be executed. 23919 * 23920 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 23921 * - `function(scope)`: execute the function with current `scope` parameter. 23922 */ 23923 $applyAsync: function(expr) { 23924 var scope = this; 23925 if (expr) { 23926 applyAsyncQueue.push($applyAsyncExpression); 23927 } 23928 expr = $parse(expr); 23929 scheduleApplyAsync(); 23930 23931 function $applyAsyncExpression() { 23932 scope.$eval(expr); 23933 } 23934 }, 23935 23936 /** 23937 * @ngdoc method 23938 * @name $rootScope.Scope#$on 23939 * @kind function 23940 * 23941 * @description 23942 * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for 23943 * discussion of event life cycle. 23944 * 23945 * The event listener function format is: `function(event, args...)`. The `event` object 23946 * passed into the listener has the following attributes: 23947 * 23948 * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or 23949 * `$broadcast`-ed. 23950 * - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the 23951 * event propagates through the scope hierarchy, this property is set to null. 23952 * - `name` - `{string}`: name of the event. 23953 * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel 23954 * further event propagation (available only for events that were `$emit`-ed). 23955 * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag 23956 * to true. 23957 * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called. 23958 * 23959 * @param {string} name Event name to listen on. 23960 * @param {function(event, ...args)} listener Function to call when the event is emitted. 23961 * @returns {function()} Returns a deregistration function for this listener. 23962 */ 23963 $on: function(name, listener) { 23964 var namedListeners = this.$$listeners[name]; 23965 if (!namedListeners) { 23966 this.$$listeners[name] = namedListeners = []; 23967 } 23968 namedListeners.push(listener); 23969 23970 var current = this; 23971 do { 23972 if (!current.$$listenerCount[name]) { 23973 current.$$listenerCount[name] = 0; 23974 } 23975 current.$$listenerCount[name]++; 23976 } while ((current = current.$parent)); 23977 23978 var self = this; 23979 return function() { 23980 var indexOfListener = namedListeners.indexOf(listener); 23981 if (indexOfListener !== -1) { 23982 namedListeners[indexOfListener] = null; 23983 decrementListenerCount(self, 1, name); 23984 } 23985 }; 23986 }, 23987 23988 23989 /** 23990 * @ngdoc method 23991 * @name $rootScope.Scope#$emit 23992 * @kind function 23993 *
23994 * @description 23995 * Dispatches an event `name` upwards through the scope hierarchy notifying the 23996 * registered {@link ng.$rootScope.Scope#$on} listeners. 23997 * 23998 * The event life cycle starts at the scope on which `$emit` was called. All 23999 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 24000 * notified. Afterwards, the event traverses upwards toward the root scope and calls all 24001 * registered listeners along the way. The event will stop propagating if one of the listeners 24002 * cancels it. 24003 * 24004 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 24005 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 24006 * 24007 * @param {string} name Event name to emit. 24008 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 24009 * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}). 24010 */ 24011 $emit: function(name, args) { 24012 var empty = [], 24013 namedListeners, 24014 scope = this, 24015 stopPropagation = false, 24016 event = { 24017 name: name, 24018 targetScope: scope, 24019 stopPropagation: function() {stopPropagation = true;}, 24020 preventDefault: function() { 24021 event.defaultPrevented = true; 24022 }, 24023 defaultPrevented: false 24024 }, 24025 listenerArgs = concat([event], arguments, 1), 24026 i, length; 24027 24028 do { 24029 namedListeners = scope.$$listeners[name] || empty; 24030 event.currentScope = scope; 24031 for (i = 0, length = namedListeners.length; i < length; i++) { 24032 24033 // if listeners were deregistered, defragment the array 24034 if (!namedListeners[i]) { 24035 namedListeners.splice(i, 1); 24036 i--; 24037 length--; 24038 continue; 24039 } 24040 try { 24041 //allow all listeners attached to the current scope to run 24042 namedListeners[i].apply(null, listenerArgs); 24043 } catch (e) { 24044 $exceptionHandler(e); 24045 } 24046 } 24047 //if any listener on the current scope stops propagation, prevent bubbling 24048 if (stopPropagation) { 24049 event.currentScope = null; 24050 return event; 24051 } 24052 //traverse upwards 24053 scope = scope.$parent; 24054 } while (scope); 24055 24056 event.currentScope = null; 24057 24058 return event; 24059 }, 24060 24061 24062 /** 24063 * @ngdoc method 24064 * @name $rootScope.Scope#$broadcast 24065 * @kind function 24066 * 24067 * @description 24068 * Dispatches an event `name` downwards to all child scopes (and their children) notifying the 24069 * registered {@link ng.$rootScope.Scope#$on} listeners. 24070 * 24071 * The event life cycle starts at the scope on which `$broadcast` was called. All 24072 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 24073 * notified. Afterwards, the event propagates to all direct and indirect scopes of the current 24074 * scope and calls all registered listeners along the way. The event cannot be canceled. 24075 * 24076 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 24077 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 24078 * 24079 * @param {string} name Event name to broadcast. 24080 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 24081 * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on} 24082 */ 24083 $broadcast: function(name, args) { 24084 var target = this, 24085 current = target, 24086 next = target, 24087 event = { 24088 name: name, 24089 targetScope: target, 24090 preventDefault: function() { 24091 event.defaultPrevented = true; 24092 }, 24093 defaultPrevented: false 24094 }; 24095 24096 if (!target.$$listenerCount[name]) return event; 24097 24098 var listenerArgs = concat([event], arguments, 1), 24099 listeners, i, length; 24100 24101 //down while you can, then up and next sibling or up and next sibling until back at root 24102 while ((current = next)) { 24103 event.currentScope = current; 24104 listeners = current.$$listeners[name] || []; 24105 for (i = 0, length = listeners.length; i < length; i++) { 24106 // if listeners were deregistered, defragment the array 24107 if (!listeners[i]) { 24108 listeners.splice(i, 1); 24109 i--; 24110 length--; 24111 continue; 24112 } 24113 24114 try { 24115 listeners[i].apply(null, listenerArgs); 24116 } catch (e) { 24117 $exceptionHandler(e); 24118 } 24119 } 24120 24121 // Insanity Warning: scope depth-first traversal 24122 // yes, this code is a bit crazy, but it works and we have tests to prove it! 24123 // this piece should be kept in sync with the traversal in $digest 24124 // (though it differs due to having the extra check for $$listenerCount) 24125 if (!(next = ((current.$$listenerCount[name] && current.$$childHead) || 24126 (current !== target && current.$$nextSibling)))) { 24127 while (current !== target && !(next = current.$$nextSibling)) { 24128 current = current.$parent; 24129 } 24130 } 24131 } 24132 24133 event.currentScope = null; 24134 return event; 24135 } 24136 }; 24137 24138 var $rootScope = new Scope(); 24139 24140 //The internal queues. Expose them on the $rootScope for debugging/testing purposes. 24141 var asyncQueue = $rootScope.$$asyncQueue = []; 24142 var postDigestQueue = $rootScope.$$postDigestQueue = []; 24143 var applyAsyncQueue = $rootScope.$$applyAsyncQueue = []; 24144 24145 var postDigestQueuePosition = 0; 24146 24147 return $rootScope; 24148 24149 24150 function beginPhase(phase) { 24151 if ($rootScope.$$phase) { 24152 throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase); 24153 } 24154 24155 $rootScope.$$phase = phase; 24156 } 24157 24158 function clearPhase() { 24159 $rootScope.$$phase = null; 24160 } 24161 24162 function incrementWatchersCount(current, count) { 24163 do { 24164 current.$$watchersCount += count; 24165 } while ((current = current.$parent)); 24166 } 24167 24168 function decrementListenerCount(current, count, name) { 24169 do { 24170 current.$$listenerCount[name] -= count; 24171 24172 if (current.$$listenerCount[name] === 0) { 24173 delete current.$$listenerCount[name]; 24174 } 24175 } while ((current = current.$parent)); 24176 } 24177 24178 /** 24179 * function used as an initial value for watchers. 24180 * because it's unique we can easily tell it apart from other values 24181 */ 24182 function initWatchVal() {} 24183 24184 function flushApplyAsync() {
vendor: 9,063 bytes, lines 24185-24417
24185 while (applyAsyncQueue.length) { 24186 try { 24187 applyAsyncQueue.shift()(); 24188 } catch (e) { 24189 $exceptionHandler(e); 24190 } 24191 } 24192 applyAsyncId = null; 24193 } 24194 24195 function scheduleApplyAsync() { 24196 if (applyAsyncId === null) { 24197 applyAsyncId = $browser.defer(function() { 24198 $rootScope.$apply(flushApplyAsync); 24199 }); 24200 } 24201 } 24202 }]; 24203} 24204 24205/** 24206 * @ngdoc service 24207 * @name $rootElement 24208 * 24209 * @description 24210 * The root element of Angular application. This is either the element where {@link 24211 * ng.directive:ngApp ngApp} was declared or the element passed into 24212 * {@link angular.bootstrap}. The element represents the root element of application. It is also the 24213 * location where the application's {@link auto.$injector $injector} service gets 24214 * published, and can be retrieved using `$rootElement.injector()`. 24215 */ 24216 24217 24218// the implementation is in angular.bootstrap 24219 24220/** 24221 * @this 24222 * @description 24223 * Private service to sanitize uris for links and images. Used by $compile and $sanitize. 24224 */ 24225function $$SanitizeUriProvider() { 24226 var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/, 24227 imgSrcSanitizationWhitelist = /^\s*((https?|ftp|file|blob):|data:image\/)/; 24228 24229 /** 24230 * @description 24231 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 24232 * urls during a[href] sanitization. 24233 * 24234 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 24235 * 24236 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 24237 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 24238 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 24239 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 24240 * 24241 * @param {RegExp=} regexp New regexp to whitelist urls with. 24242 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 24243 * chaining otherwise. 24244 */ 24245 this.aHrefSanitizationWhitelist = function(regexp) { 24246 if (isDefined(regexp)) { 24247 aHrefSanitizationWhitelist = regexp; 24248 return this; 24249 } 24250 return aHrefSanitizationWhitelist; 24251 }; 24252 24253 24254 /** 24255 * @description 24256 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 24257 * urls during img[src] sanitization. 24258 * 24259 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 24260 * 24261 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 24262 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 24263 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 24264 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 24265 * 24266 * @param {RegExp=} regexp New regexp to whitelist urls with. 24267 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 24268 * chaining otherwise. 24269 */ 24270 this.imgSrcSanitizationWhitelist = function(regexp) { 24271 if (isDefined(regexp)) { 24272 imgSrcSanitizationWhitelist = regexp; 24273 return this; 24274 } 24275 return imgSrcSanitizationWhitelist; 24276 }; 24277 24278 this.$get = function() { 24279 return function sanitizeUri(uri, isImage) { 24280 var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist; 24281 var normalizedVal; 24282 normalizedVal = urlResolve(uri).href; 24283 if (normalizedVal !== '' && !normalizedVal.match(regex)) { 24284 return 'unsafe:' + normalizedVal; 24285 } 24286 return uri; 24287 }; 24288 }; 24289} 24290 24291/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 24292 * Any commits to this file should be reviewed with security in mind. * 24293 * Changes to this file can potentially create security vulnerabilities. * 24294 * An approval from 2 Core members with history of modifying * 24295 * this file is required. * 24296 * * 24297 * Does the change somehow allow for arbitrary javascript to be executed? * 24298 * Or allows for someone to change the prototype of built-in objects? * 24299 * Or gives undesired access to variables likes document or window? * 24300 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 24301 24302/* exported $SceProvider, $SceDelegateProvider */ 24303 24304var $sceMinErr = minErr('$sce'); 24305 24306var SCE_CONTEXTS = { 24307 HTML: 'html', 24308 CSS: 'css', 24309 URL: 'url', 24310 // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a 24311 // url. (e.g. ng-include, script src, templateUrl) 24312 RESOURCE_URL: 'resourceUrl', 24313 JS: 'js' 24314}; 24315 24316// Helper functions follow. 24317 24318function adjustMatcher(matcher) { 24319 if (matcher === 'self') { 24320 return matcher; 24321 } else if (isString(matcher)) { 24322 // Strings match exactly except for 2 wildcards - '*' and '**'. 24323 // '*' matches any character except those from the set ':/.?&'. 24324 // '**' matches any character (like .* in a RegExp). 24325 // More than 2 *'s raises an error as it's ill defined. 24326 if (matcher.indexOf('***') > -1) { 24327 throw $sceMinErr('iwcard', 24328 'Illegal sequence *** in string matcher. String: {0}', matcher); 24329 } 24330 matcher = escapeForRegexp(matcher). 24331 replace(/\\\*\\\*/g, '.*'). 24332 replace(/\\\*/g, '[^:/.?&;]*'); 24333 return new RegExp('^' + matcher + '$'); 24334 } else if (isRegExp(matcher)) { 24335 // The only other type of matcher allowed is a Regexp. 24336 // Match entire URL / disallow partial matches. 24337 // Flags are reset (i.e. no global, ignoreCase or multiline) 24338 return new RegExp('^' + matcher.source + '$'); 24339 } else { 24340 throw $sceMinErr('imatcher', 24341 'Matchers may only be "self", string patterns or RegExp objects'); 24342 } 24343} 24344 24345 24346function adjustMatchers(matchers) { 24347 var adjustedMatchers = []; 24348 if (isDefined(matchers)) { 24349 forEach(matchers, function(matcher) { 24350 adjustedMatchers.push(adjustMatcher(matcher)); 24351 }); 24352 } 24353 return adjustedMatchers; 24354} 24355 24356 24357/** 24358 * @ngdoc service 24359 * @name $sceDelegate 24360 * @kind function 24361 * 24362 * @description 24363 * 24364 * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict 24365 * Contextual Escaping (SCE)} services to AngularJS. 24366 * 24367 * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of 24368 * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is 24369 * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to 24370 * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things 24371 * work because `$sce` delegates to `$sceDelegate` for these operations. 24372 * 24373 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service. 24374 * 24375 * The default instance of `$sceDelegate` should work out of the box with little pain. While you 24376 * can override it completely to change the behavior of `$sce`, the common case would 24377 * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting 24378 * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as 24379 * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist 24380 * $sceDelegateProvider.resourceUrlWhitelist} and {@link 24381 * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 24382 */ 24383 24384/** 24385 * @ngdoc provider 24386 * @name $sceDelegateProvider 24387 * @this 24388 * 24389 * @description 24390 * 24391 * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate 24392 * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure 24393 * that the URLs used for sourcing Angular templates are safe. Refer {@link 24394 * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and 24395 * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 24396 * 24397 * For the general details about this service in Angular, read the main page for {@link ng.$sce 24398 * Strict Contextual Escaping (SCE)}. 24399 * 24400 * **Example**: Consider the following case. <a name="example"></a> 24401 * 24402 * - your app is hosted at url `http://myapp.example.com/` 24403 * - but some of your templates are hosted on other domains you control such as 24404 * `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc. 24405 * - and you have an open redirect at `http://myapp.example.com/clickThru?...`. 24406 * 24407 * Here is what a secure configuration for this scenario might look like: 24408 * 24409 * ``` 24410 * angular.module('myApp', []).config(function($sceDelegateProvider) { 24411 * $sceDelegateProvider.resourceUrlWhitelist([ 24412 * // Allow same origin resource loads. 24413 * 'self', 24414 * // Allow loading from our assets domain. Notice the difference between * and **. 24415 * 'http://srv*.assets.example.com/**' 24416 * ]); 24417 *
24418 * // The blacklist overrides the whitelist so the open redirect here is blocked. 24419 * $sceDelegateProvider.resourceUrlBlacklist([ 24420 * 'http://myapp.example.com/clickThru**' 24421 * ]); 24422 * }); 24423 * ``` 24424 */ 24425 24426function $SceDelegateProvider() { 24427 this.SCE_CONTEXTS = SCE_CONTEXTS; 24428 24429 // Resource URLs can also be trusted by policy. 24430 var resourceUrlWhitelist = ['self'], 24431 resourceUrlBlacklist = []; 24432 24433 /** 24434 * @ngdoc method 24435 * @name $sceDelegateProvider#resourceUrlWhitelist 24436 * @kind function 24437 * 24438 * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value 24439 * provided. This must be an array or null. A snapshot of this array is used so further 24440 * changes to the array are ignored. 24441 * 24442 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 24443 * allowed in this array. 24444 * 24445 * <div class="alert alert-warning"> 24446 * **Note:** an empty whitelist array will block all URLs! 24447 * </div> 24448 * 24449 * @return {Array} the currently set whitelist array. 24450 * 24451 * The **default value** when no whitelist has been explicitly set is `['self']` allowing only 24452 * same origin resource requests. 24453 * 24454 * @description 24455 * Sets/Gets the whitelist of trusted resource URLs. 24456 */ 24457 this.resourceUrlWhitelist = function(value) { 24458 if (arguments.length) { 24459 resourceUrlWhitelist = adjustMatchers(value); 24460 } 24461 return resourceUrlWhitelist; 24462 }; 24463 24464 /** 24465 * @ngdoc method 24466 * @name $sceDelegateProvider#resourceUrlBlacklist 24467 * @kind function 24468 * 24469 * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value 24470 * provided. This must be an array or null. A snapshot of this array is used so further 24471 * changes to the array are ignored. 24472 * 24473 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 24474 * allowed in this array. 24475 * 24476 * The typical usage for the blacklist is to **block 24477 * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as 24478 * these would otherwise be trusted but actually return content from the redirected domain. 24479 * 24480 * Finally, **the blacklist overrides the whitelist** and has the final say. 24481 * 24482 * @return {Array} the currently set blacklist array. 24483 * 24484 * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there 24485 * is no blacklist.) 24486 * 24487 * @description 24488 * Sets/Gets the blacklist of trusted resource URLs. 24489 */ 24490 24491 this.resourceUrlBlacklist = function(value) { 24492 if (arguments.length) { 24493 resourceUrlBlacklist = adjustMatchers(value); 24494 } 24495 return resourceUrlBlacklist; 24496 }; 24497 24498 this.$get = ['$injector', function($injector) { 24499 24500 var htmlSanitizer = function htmlSanitizer(html) { 24501 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 24502 }; 24503 24504 if ($injector.has('$sanitize')) { 24505 htmlSanitizer = $injector.get('$sanitize'); 24506 } 24507 24508 24509 function matchUrl(matcher, parsedUrl) { 24510 if (matcher === 'self') { 24511 return urlIsSameOrigin(parsedUrl); 24512 } else { 24513 // definitely a regex. See adjustMatchers() 24514 return !!matcher.exec(parsedUrl.href); 24515 } 24516 } 24517 24518 function isResourceUrlAllowedByPolicy(url) { 24519 var parsedUrl = urlResolve(url.toString()); 24520 var i, n, allowed = false; 24521 // Ensure that at least one item from the whitelist allows this url. 24522 for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) { 24523 if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) { 24524 allowed = true; 24525 break; 24526 } 24527 } 24528 if (allowed) { 24529 // Ensure that no item from the blacklist blocked this url. 24530 for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) { 24531 if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) { 24532 allowed = false; 24533 break; 24534 } 24535 } 24536 } 24537 return allowed; 24538 } 24539 24540 function generateHolderType(Base) { 24541 var holderType = function TrustedValueHolderType(trustedValue) { 24542 this.$$unwrapTrustedValue = function() { 24543 return trustedValue; 24544 }; 24545 }; 24546 if (Base) { 24547 holderType.prototype = new Base(); 24548 } 24549 holderType.prototype.valueOf = function sceValueOf() { 24550 return this.$$unwrapTrustedValue(); 24551 }; 24552 holderType.prototype.toString = function sceToString() { 24553 return this.$$unwrapTrustedValue().toString(); 24554 }; 24555 return holderType; 24556 } 24557 24558 var trustedValueHolderBase = generateHolderType(), 24559 byType = {}; 24560 24561 byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase); 24562 byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase); 24563 byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase); 24564 byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase); 24565 byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]); 24566 24567 /** 24568 * @ngdoc method 24569 * @name $sceDelegate#trustAs 24570 *
24571 * @description 24572 * Returns an object that is trusted by angular for use in specified strict 24573 * contextual escaping contexts (such as ng-bind-html, ng-include, any src 24574 * attribute interpolation, any dom event binding attribute interpolation 24575 * such as for onclick, etc.) that uses the provided value. 24576 * See {@link ng.$sce $sce} for enabling strict contextual escaping. 24577 * 24578 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 24579 * resourceUrl, html, js and css. 24580 * @param {*} value The value that that should be considered trusted/safe. 24581 * @returns {*} A value that can be used to stand in for the provided `value` in places 24582 * where Angular expects a $sce.trustAs() return value. 24583 */ 24584 function trustAs(type, trustedValue) { 24585 var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 24586 if (!Constructor) { 24587 throw $sceMinErr('icontext', 24588 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}', 24589 type, trustedValue); 24590 } 24591 if (trustedValue === null || isUndefined(trustedValue) || trustedValue === '') { 24592 return trustedValue; 24593 } 24594 // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting 24595 // mutable objects, we ensure here that the value passed in is actually a string. 24596 if (typeof trustedValue !== 'string') { 24597 throw $sceMinErr('itype', 24598 'Attempted to trust a non-string value in a content requiring a string: Context: {0}', 24599 type); 24600 } 24601 return new Constructor(trustedValue); 24602 } 24603 24604 /** 24605 * @ngdoc method 24606 * @name $sceDelegate#valueOf 24607 * 24608 * @description 24609 * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs 24610 * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link 24611 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. 24612 * 24613 * If the passed parameter is not a value that had been returned by {@link 24614 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is. 24615 * 24616 * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} 24617 * call or anything else. 24618 * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs 24619 * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns 24620 * `value` unchanged. 24621 */ 24622 function valueOf(maybeTrusted) { 24623 if (maybeTrusted instanceof trustedValueHolderBase) { 24624 return maybeTrusted.$$unwrapTrustedValue(); 24625 } else { 24626 return maybeTrusted; 24627 } 24628 } 24629 24630 /** 24631 * @ngdoc method 24632 * @name $sceDelegate#getTrusted 24633 * 24634 * @description 24635 * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and 24636 * returns the originally supplied value if the queried context type is a supertype of the 24637 * created type. If this condition isn't satisfied, throws an exception. 24638 * 24639 * <div class="alert alert-danger"> 24640 * Disabling auto-escaping is extremely dangerous, it usually creates a Cross Site Scripting 24641 * (XSS) vulnerability in your application. 24642 * </div> 24643 * 24644 * @param {string} type The kind of context in which this value is to be used. 24645 * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs 24646 * `$sceDelegate.trustAs`} call. 24647 * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs 24648 * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception. 24649 */ 24650 function getTrusted(type, maybeTrusted) { 24651 if (maybeTrusted === null || isUndefined(maybeTrusted) || maybeTrusted === '') { 24652 return maybeTrusted; 24653 } 24654 var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 24655 if (constructor && maybeTrusted instanceof constructor) { 24656 return maybeTrusted.$$unwrapTrustedValue(); 24657 } 24658 // If we get here, then we may only take one of two actions. 24659 // 1. sanitize the value for the requested type, or 24660 // 2. throw an exception. 24661 if (type === SCE_CONTEXTS.RESOURCE_URL) { 24662 if (isResourceUrlAllowedByPolicy(maybeTrusted)) { 24663 return maybeTrusted; 24664 } else { 24665 throw $sceMinErr('insecurl', 24666 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}', 24667 maybeTrusted.toString()); 24668 } 24669 } else if (type === SCE_CONTEXTS.HTML) { 24670 return htmlSanitizer(maybeTrusted); 24671 } 24672 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 24673 } 24674 24675 return { trustAs: trustAs, 24676 getTrusted: getTrusted,
24677 valueOf: valueOf }; 24678 }]; 24679} 24680 24681 24682/** 24683 * @ngdoc provider 24684 * @name $sceProvider 24685 * @this 24686 * 24687 * @description 24688 * 24689 * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service. 24690 * - enable/disable Strict Contextual Escaping (SCE) in a module 24691 * - override the default implementation with a custom delegate 24692 * 24693 * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}. 24694 */ 24695 24696/** 24697 * @ngdoc service 24698 * @name $sce 24699 * @kind function 24700 * 24701 * @description 24702 * 24703 * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS. 24704 * 24705 * # Strict Contextual Escaping 24706 * 24707 * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain 24708 * contexts to result in a value that is marked as safe to use for that context. One example of 24709 * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer 24710 * to these contexts as privileged or SCE contexts. 24711 * 24712 * As of version 1.2, Angular ships with SCE enabled by default. 24713 * 24714 * Note: When enabled (the default), IE<11 in quirks mode is not supported. In this mode, IE<11 allow 24715 * one to execute arbitrary javascript by the use of the expression() syntax. Refer 24716 * <http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx> to learn more about them.
24717 * You can ensure your document is in standards mode and not quirks mode by adding `<!doctype html>` 24718 * to the top of your HTML document. 24719 * 24720 * SCE assists in writing code in a way that (a) is secure by default and (b) makes auditing for 24721 * security vulnerabilities such as XSS, clickjacking, etc. a lot easier. 24722 * 24723 * Here's an example of a binding in a privileged context: 24724 * 24725 * ``` 24726 * <input ng-model="userHtml" aria-label="User input"> 24727 * <div ng-bind-html="userHtml"></div> 24728 * ``` 24729 * 24730 * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE 24731 * disabled, this application allows the user to render arbitrary HTML into the DIV. 24732 * In a more realistic example, one may be rendering user comments, blog articles, etc. via 24733 * bindings. (HTML is just one example of a context where rendering user controlled input creates 24734 * security vulnerabilities.) 24735 * 24736 * For the case of HTML, you might use a library, either on the client side, or on the server side, 24737 * to sanitize unsafe HTML before binding to the value and rendering it in the document. 24738 * 24739 * How would you ensure that every place that used these types of bindings was bound to a value that 24740 * was sanitized by your library (or returned as safe for rendering by your server?) How can you 24741 * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some 24742 * properties/fields and forgot to update the binding to the sanitized value? 24743 * 24744 * To be secure by default, you want to ensure that any such bindings are disallowed unless you can 24745 * determine that something explicitly says it's safe to use a value for binding in that
24746 * context. You can then audit your code (a simple grep would do) to ensure that this is only done 24747 * for those values that you can easily tell are safe - because they were received from your server, 24748 * sanitized by your library, etc. You can organize your codebase to help with this - perhaps 24749 * allowing only the files in a specific directory to do this. Ensuring that the internal API 24750 * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task. 24751 * 24752 * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs} 24753 * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to 24754 * obtain values that will be accepted by SCE / privileged contexts. 24755 * 24756 * 24757 * ## How does it work? 24758 * 24759 * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted 24760 * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link 24761 * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the 24762 * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. 24763 * 24764 * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link 24765 * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly 24766 * simplified): 24767 * 24768 * ``` 24769 * var ngBindHtmlDirective = ['$sce', function($sce) { 24770 * return function(scope, element, attr) { 24771 * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) { 24772 * element.html(value || ''); 24773 * }); 24774 * }; 24775 * }]; 24776 * ``` 24777 * 24778 * ## Impact on loading templates 24779 * 24780 * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as 24781 * `templateUrl`'s specified by {@link guide/directive directives}. 24782 * 24783 * By default, Angular only loads templates from the same domain and protocol as the application 24784 * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl 24785 * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or 24786 * protocols, you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist 24787 * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value. 24788 * 24789 * *Please note*: 24790 * The browser's 24791 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 24792 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 24793 * policy apply in addition to this and may further restrict whether the template is successfully 24794 * loaded. This means that without the right CORS policy, loading templates from a different domain 24795 * won't work on all browsers. Also, loading templates from `file://` URL does not work on some 24796 * browsers. 24797 * 24798 * ## This feels like too much overhead 24799 * 24800 * It's important to remember that SCE only applies to interpolation expressions. 24801 * 24802 * If your expressions are constant literals, they're automatically trusted and you don't need to 24803 * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g. 24804 * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works. 24805 * 24806 * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them 24807 * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here. 24808 * 24809 * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load 24810 * templates in `ng-include` from your application's domain without having to even know about SCE. 24811 * It blocks loading templates from other domains or loading templates over http from an https 24812 * served document. You can change these by setting your own custom {@link 24813 * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link 24814 * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs. 24815 * 24816 * This significantly reduces the overhead. It is far easier to pay the small overhead and have an 24817 * application that's secure and can be audited to verify that with much more ease than bolting 24818 * security onto an application later. 24819 * 24820 * <a name="contexts"></a> 24821 * ## What trusted context types are supported? 24822 * 24823 * | Context | Notes | 24824 * |---------------------|----------------| 24825 * | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. If an unsafe value is encountered and the {@link ngSanitize $sanitize} module is present this will sanitize the value instead of throwing an error. | 24826 * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. | 24827 * | `$sce.URL` | For URLs that are safe to follow as links. Currently unused (`<a href=` and `<img src=` sanitize their urls and don't constitute an SCE context. | 24828 * | `$sce.RESOURCE_URL` | For URLs that are not only safe to follow as links, but whose contents are also safe to include in your application. Examples include `ng-include`, `src` / `ngSrc` bindings for tags other than `IMG`, `VIDEO`, `AUDIO`, `SOURCE`, and `TRACK` (e.g. `IFRAME`, `OBJECT`, etc.) <br><br>Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` does and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` are required. | 24829 * | `$sce.JS` | For JavaScript that is safe to execute in your application's context. Currently unused. Feel free to use it in your own directives. | 24830 * 24831 * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist} <a name="resourceUrlPatternItem"></a> 24832 *
24833 * Each element in these arrays must be one of the following: 24834 * 24835 * - **'self'** 24836 * - The special **string**, `'self'`, can be used to match against all URLs of the **same 24837 * domain** as the application document using the **same protocol**. 24838 * - **String** (except the special value `'self'`) 24839 * - The string is matched against the full *normalized / absolute URL* of the resource 24840 * being tested (substring matches are not good enough.) 24841 * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters 24842 * match themselves. 24843 * - `*`: matches zero or more occurrences of any character other than one of the following 6 24844 * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and '`;`'. It's a useful wildcard for use 24845 * in a whitelist. 24846 * - `**`: matches zero or more occurrences of *any* character. As such, it's not 24847 * appropriate for use in a scheme, domain, etc. as it would match too much. (e.g. 24848 * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might 24849 * not have been the intention.) Its usage at the very end of the path is ok. (e.g. 24850 * http://foo.example.com/templates/**). 24851 * - **RegExp** (*see caveat below*) 24852 * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax 24853 * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to 24854 * accidentally introduce a bug when one updates a complex expression (imho, all regexes should 24855 * have good test coverage). For instance, the use of `.` in the regex is correct only in a 24856 * small number of cases. A `.` character in the regex used when matching the scheme or a 24857 * subdomain could be matched against a `:` or literal `.` that was likely not intended. It 24858 * is highly recommended to use the string patterns and only fall back to regular expressions 24859 * as a last resort. 24860 * - The regular expression must be an instance of RegExp (i.e. not a string.) It is 24861 * matched against the **entire** *normalized / absolute URL* of the resource being tested 24862 * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags 24863 * present on the RegExp (such as multiline, global, ignoreCase) are ignored. 24864 * - If you are generating your JavaScript from some other templating engine (not 24865 * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)), 24866 * remember to escape your regular expression (and be aware that you might need more than 24867 * one level of escaping depending on your templating engine and the way you interpolated 24868 * the value.) Do make use of your platform's escaping mechanism as it might be good 24869 * enough before coding your own. E.g. Ruby has 24870 * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape) 24871 * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape). 24872 * Javascript lacks a similar built in function for escaping. Take a look at Google 24873 * Closure library's [goog.string.regExpEscape(s)]( 24874 * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962). 24875 * 24876 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example. 24877 * 24878 * ## Show me an example using SCE. 24879 * 24880 * <example module="mySceApp" deps="angular-sanitize.js" name="sce-service"> 24881 * <file name="index.html"> 24882 * <div ng-controller="AppController as myCtrl"> 24883 * <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br> 24884 * <b>User comments</b><br> 24885 * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when 24886 * $sanitize is available. If $sanitize isn't available, this results in an error instead of an 24887 * exploit. 24888 * <div class="well"> 24889 * <div ng-repeat="userComment in myCtrl.userComments"> 24890 * <b>{{userComment.name}}</b>: 24891 * <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span> 24892 * <br> 24893 * </div>
24894 * </div> 24895 * </div> 24896 * </file> 24897 * 24898 * <file name="script.js"> 24899 * angular.module('mySceApp', ['ngSanitize']) 24900 * .controller('AppController', ['$http', '$templateCache', '$sce', 24901 * function AppController($http, $templateCache, $sce) { 24902 * var self = this; 24903 * $http.get('test_data.json', {cache: $templateCache}).success(function(userComments) { 24904 * self.userComments = userComments; 24905 * }); 24906 * self.explicitlyTrustedHtml = $sce.trustAsHtml( 24907 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 24908 * 'sanitization."">Hover over this text.</span>'); 24909 * }]); 24910 * </file> 24911 * 24912 * <file name="test_data.json"> 24913 * [ 24914 * { "name": "Alice", 24915 * "htmlComment": 24916 * "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>" 24917 * }, 24918 * { "name": "Bob", 24919 * "htmlComment": "<i>Yes!</i> Am I the only other one?" 24920 * } 24921 * ] 24922 * </file> 24923 * 24924 * <file name="protractor.js" type="protractor"> 24925 * describe('SCE doc demo', function() { 24926 * it('should sanitize untrusted values', function() { 24927 * expect(element.all(by.css('.htmlComment')).first().getAttribute('innerHTML')) 24928 * .toBe('<span>Is <i>anyone</i> reading this?</span>'); 24929 * }); 24930 * 24931 * it('should NOT sanitize explicitly trusted values', function() { 24932 * expect(element(by.id('explicitlyTrustedHtml')).getAttribute('innerHTML')).toBe( 24933 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 24934 * 'sanitization."">Hover over this text.</span>'); 24935 * }); 24936 * }); 24937 * </file> 24938 * </example> 24939 * 24940 * 24941 * 24942 * ## Can I disable SCE completely? 24943 * 24944 * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits 24945 * for little coding overhead. It will be much harder to take an SCE disabled application and 24946 * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE 24947 * for cases where you have a lot of existing code that was written before SCE was introduced and 24948 * you're migrating them a module at a time. 24949 * 24950 * That said, here's how you can completely disable SCE: 24951 * 24952 * ``` 24953 * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) { 24954 * // Completely disable SCE. For demonstration purposes only! 24955 * // Do not use in new projects. 24956 * $sceProvider.enabled(false); 24957 * }); 24958 * ``` 24959 * 24960 */ 24961 24962function $SceProvider() { 24963 var enabled = true; 24964 24965 /** 24966 * @ngdoc method 24967 * @name $sceProvider#enabled 24968 * @kind function 24969 * 24970 * @param {boolean=} value If provided, then enables/disables SCE. 24971 * @return {boolean} true if SCE is enabled, false otherwise. 24972 * 24973 * @description 24974 * Enables/disables SCE and returns the current value. 24975 */ 24976 this.enabled = function(value) { 24977 if (arguments.length) { 24978 enabled = !!value; 24979 } 24980 return enabled; 24981 }; 24982 24983 24984 /* Design notes on the default implementation for SCE. 24985 * 24986 * The API contract for the SCE delegate 24987 * ------------------------------------- 24988 * The SCE delegate object must provide the following 3 methods: 24989 * 24990 * - trustAs(contextEnum, value) 24991 * This method is used to tell the SCE service that the provided value is OK to use in the 24992 * contexts specified by contextEnum. It must return an object that will be accepted by 24993 * getTrusted() for a compatible contextEnum and return this value. 24994 * 24995 * - valueOf(value) 24996 * For values that were not produced by trustAs(), return them as is. For values that were 24997 * produced by trustAs(), return the corresponding input value to trustAs. Basically, if 24998 * trustAs is wrapping the given values into some type, this operation unwraps it when given 24999 * such a value. 25000 * 25001 * - getTrusted(contextEnum, value) 25002 * This function should return the a value that is safe to use in the context specified by 25003 * contextEnum or throw and exception otherwise. 25004 * 25005 * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be 25006 * opaque or wrapped in some holder object. That happens to be an implementation detail. For 25007 * instance, an implementation could maintain a registry of all trusted objects by context. In 25008 * such a case, trustAs() would return the same object that was passed in. getTrusted() would 25009 * return the same object passed in if it was found in the registry under a compatible context or 25010 * throw an exception otherwise. An implementation might only wrap values some of the time based 25011 * on some criteria. getTrusted() might return a value and not throw an exception for special 25012 * constants or objects even if not wrapped. All such implementations fulfill this contract. 25013 * 25014 * 25015 * A note on the inheritance model for SCE contexts 25016 * ------------------------------------------------ 25017 * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This 25018 * is purely an implementation details. 25019 * 25020 * The contract is simply this: 25021 * 25022 * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value) 25023 * will also succeed. 25024 * 25025 * Inheritance happens to capture this in a natural way. In some future, we
25026 * may not use inheritance anymore. That is OK because no code outside of 25027 * sce.js and sceSpecs.js would need to be aware of this detail. 25028 */ 25029 25030 this.$get = ['$parse', '$sceDelegate', function( 25031 $parse, $sceDelegate) { 25032 // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow 25033 // the "expression(javascript expression)" syntax which is insecure. 25034 if (enabled && msie < 8) { 25035 throw $sceMinErr('iequirks', 25036 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' + 25037 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' + 25038 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); 25039 } 25040 25041 var sce = shallowCopy(SCE_CONTEXTS); 25042 25043 /** 25044 * @ngdoc method 25045 * @name $sce#isEnabled 25046 * @kind function 25047 * 25048 * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you 25049 * have to do it at module config time on {@link ng.$sceProvider $sceProvider}. 25050 * 25051 * @description 25052 * Returns a boolean indicating if SCE is enabled. 25053 */ 25054 sce.isEnabled = function() { 25055 return enabled; 25056 }; 25057 sce.trustAs = $sceDelegate.trustAs; 25058 sce.getTrusted = $sceDelegate.getTrusted; 25059 sce.valueOf = $sceDelegate.valueOf; 25060 25061 if (!enabled) { 25062 sce.trustAs = sce.getTrusted = function(type, value) { return value; }; 25063 sce.valueOf = identity; 25064 } 25065 25066 /** 25067 * @ngdoc method 25068 * @name $sce#parseAs 25069 * 25070 * @description 25071 * Converts Angular {@link guide/expression expression} into a function. This is like {@link 25072 * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it 25073 * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*, 25074 * *result*)} 25075 * 25076 * @param {string} type The kind of SCE context in which this result will be used. 25077 * @param {string} expression String expression to compile. 25078 * @returns {function(context, locals)} a function which represents the compiled expression: 25079 * 25080 * * `context` â `{object}` â an object against which any expressions embedded in the strings 25081 * are evaluated against (typically a scope object). 25082 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 25083 * `context`. 25084 */ 25085 sce.parseAs = function sceParseAs(type, expr) { 25086 var parsed = $parse(expr); 25087 if (parsed.literal && parsed.constant) { 25088 return parsed; 25089 } else { 25090 return $parse(expr, function(value) { 25091 return sce.getTrusted(type, value); 25092 }); 25093 } 25094 }; 25095 25096 /** 25097 * @ngdoc method 25098 * @name $sce#trustAs 25099 * 25100 * @description 25101 * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, 25102 * returns an object that is trusted by angular for use in specified strict contextual 25103 * escaping contexts (such as ng-bind-html, ng-include, any src attribute 25104 * interpolation, any dom event binding attribute interpolation such as for onclick, etc.) 25105 * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual 25106 * escaping. 25107 * 25108 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 25109 * resourceUrl, html, js and css. 25110 * @param {*} value The value that that should be considered trusted/safe. 25111 * @returns {*} A value that can be used to stand in for the provided `value` in places 25112 * where Angular expects a $sce.trustAs() return value. 25113 */ 25114 25115 /** 25116 * @ngdoc method 25117 * @name $sce#trustAsHtml 25118 * 25119 * @description 25120 * Shorthand method. `$sce.trustAsHtml(value)` â 25121 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`} 25122 * 25123 * @param {*} value The value to trustAs. 25124 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml 25125 * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives 25126 * only accept expressions that are either literal constants or are the 25127 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 25128 */ 25129 25130 /** 25131 * @ngdoc method 25132 * @name $sce#trustAsUrl 25133 *
25134 * @description 25135 * Shorthand method. `$sce.trustAsUrl(value)` â 25136 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`} 25137 * 25138 * @param {*} value The value to trustAs. 25139 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl 25140 * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives 25141 * only accept expressions that are either literal constants or are the 25142 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 25143 */ 25144 25145 /** 25146 * @ngdoc method 25147 * @name $sce#trustAsResourceUrl 25148 * 25149 * @description 25150 * Shorthand method. `$sce.trustAsResourceUrl(value)` â 25151 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`} 25152 * 25153 * @param {*} value The value to trustAs. 25154 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl 25155 * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives 25156 * only accept expressions that are either literal constants or are the return 25157 * value of {@link ng.$sce#trustAs $sce.trustAs}.) 25158 */ 25159 25160 /** 25161 * @ngdoc method 25162 * @name $sce#trustAsJs 25163 * 25164 * @description 25165 * Shorthand method. `$sce.trustAsJs(value)` â 25166 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`} 25167 * 25168 * @param {*} value The value to trustAs. 25169 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs 25170 * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives 25171 * only accept expressions that are either literal constants or are the 25172 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 25173 */ 25174 25175 /** 25176 * @ngdoc method 25177 * @name $sce#getTrusted 25178 * 25179 * @description 25180 * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such, 25181 * takes the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the 25182 * originally supplied value if the queried context type is a supertype of the created type. 25183 * If this condition isn't satisfied, throws an exception. 25184 * 25185 * @param {string} type The kind of context in which this value is to be used. 25186 * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`} 25187 * call. 25188 * @returns {*} The value the was originally provided to 25189 * {@link ng.$sce#trustAs `$sce.trustAs`} if valid in this context. 25190 * Otherwise, throws an exception. 25191 */ 25192 25193 /** 25194 * @ngdoc method 25195 * @name $sce#getTrustedHtml 25196 * 25197 * @description 25198 * Shorthand method. `$sce.getTrustedHtml(value)` â 25199 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`} 25200 * 25201 * @param {*} value The value to pass to `$sce.getTrusted`. 25202 * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)` 25203 */ 25204 25205 /** 25206 * @ngdoc method 25207 * @name $sce#getTrustedCss 25208 * 25209 * @description 25210 * Shorthand method. `$sce.getTrustedCss(value)` â 25211 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`} 25212 * 25213 * @param {*} value The value to pass to `$sce.getTrusted`. 25214 * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)` 25215 */ 25216 25217 /** 25218 * @ngdoc method 25219 * @name $sce#getTrustedUrl 25220 * 25221 * @description 25222 * Shorthand method. `$sce.getTrustedUrl(value)` â 25223 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`} 25224 * 25225 * @param {*} value The value to pass to `$sce.getTrusted`. 25226 * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)` 25227 */ 25228 25229 /** 25230 * @ngdoc method 25231 * @name $sce#getTrustedResourceUrl 25232 * 25233 * @description 25234 * Shorthand method. `$sce.getTrustedResourceUrl(value)` â 25235 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`} 25236 * 25237 * @param {*} value The value to pass to `$sceDelegate.getTrusted`. 25238 * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)` 25239 */ 25240 25241 /** 25242 * @ngdoc method 25243 * @name $sce#getTrustedJs 25244 *
25245 * @description 25246 * Shorthand method. `$sce.getTrustedJs(value)` â 25247 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`} 25248 * 25249 * @param {*} value The value to pass to `$sce.getTrusted`. 25250 * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)` 25251 */ 25252 25253 /** 25254 * @ngdoc method 25255 * @name $sce#parseAsHtml 25256 * 25257 * @description 25258 * Shorthand method. `$sce.parseAsHtml(expression string)` â 25259 * {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`} 25260 * 25261 * @param {string} expression String expression to compile. 25262 * @returns {function(context, locals)} a function which represents the compiled expression: 25263 * 25264 * * `context` â `{object}` â an object against which any expressions embedded in the strings 25265 * are evaluated against (typically a scope object). 25266 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 25267 * `context`. 25268 */ 25269 25270 /** 25271 * @ngdoc method 25272 * @name $sce#parseAsCss 25273 * 25274 * @description 25275 * Shorthand method. `$sce.parseAsCss(value)` â 25276 * {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`} 25277 * 25278 * @param {string} expression String expression to compile. 25279 * @returns {function(context, locals)} a function which represents the compiled expression: 25280 * 25281 * * `context` â `{object}` â an object against which any expressions embedded in the strings 25282 * are evaluated against (typically a scope object). 25283 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 25284 * `context`. 25285 */ 25286 25287 /** 25288 * @ngdoc method 25289 * @name $sce#parseAsUrl 25290 * 25291 * @description 25292 * Shorthand method. `$sce.parseAsUrl(value)` â 25293 * {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`} 25294 * 25295 * @param {string} expression String expression to compile. 25296 * @returns {function(context, locals)} a function which represents the compiled expression: 25297 * 25298 * * `context` â `{object}` â an object against which any expressions embedded in the strings 25299 * are evaluated against (typically a scope object). 25300 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 25301 * `context`. 25302 */ 25303 25304 /** 25305 * @ngdoc method 25306 * @name $sce#parseAsResourceUrl 25307 * 25308 * @description 25309 * Shorthand method. `$sce.parseAsResourceUrl(value)` â 25310 * {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`} 25311 * 25312 * @param {string} expression String expression to compile. 25313 * @returns {function(context, locals)} a function which represents the compiled expression: 25314 * 25315 * * `context` â `{object}` â an object against which any expressions embedded in the strings 25316 * are evaluated against (typically a scope object). 25317 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 25318 * `context`. 25319 */ 25320 25321 /** 25322 * @ngdoc method 25323 * @name $sce#parseAsJs 25324 * 25325 * @description 25326 * Shorthand method. `$sce.parseAsJs(value)` â 25327 * {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`} 25328 * 25329 * @param {string} expression String expression to compile. 25330 * @returns {function(context, locals)} a function which represents the compiled expression: 25331 * 25332 * * `context` â `{object}` â an object against which any expressions embedded in the strings 25333 * are evaluated against (typically a scope object). 25334 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 25335 * `context`. 25336 */ 25337 25338 // Shorthand delegations. 25339 var parse = sce.parseAs, 25340 getTrusted = sce.getTrusted, 25341 trustAs = sce.trustAs; 25342 25343 forEach(SCE_CONTEXTS, function(enumValue, name) { 25344 var lName = lowercase(name); 25345 sce[camelCase('parse_as_' + lName)] = function(expr) { 25346 return parse(enumValue, expr); 25347 }; 25348 sce[camelCase('get_trusted_' + lName)] = function(value) { 25349 return getTrusted(enumValue, value); 25350 }; 25351 sce[camelCase('trust_as_' + lName)] = function(value) { 25352 return trustAs(enumValue, value); 25353 }; 25354 }); 25355 25356 return sce; 25357 }]; 25358} 25359 25360/* exported $SnifferProvider */ 25361 25362/** 25363 * !!! This is an undocumented "private" service !!! 25364 * 25365 * @name $sniffer 25366 * @requires $window 25367 * @requires $document 25368 * @this 25369 * 25370 * @property {boolean} history Does the browser support html5 history api ? 25371 * @property {boolean} transitions Does the browser support CSS transition events ? 25372 * @property {boolean} animations Does the browser support CSS animation events ? 25373 * 25374 * @description 25375 * This is very simple implementation of testing browser's features. 25376 */ 25377function $SnifferProvider() { 25378 this.$get = ['$window', '$document', function($window, $document) { 25379 var eventSupport = {}, 25380 // Chrome Packaged Apps are not allowed to access `history.pushState`. 25381 // If not sandboxed, they can be detected by the presence of `chrome.app.runtime` 25382 // (see https://developer.chrome.com/apps/api_index). If sandboxed, they can be detected by 25383 // the presence of an extension runtime ID and the absence of other Chrome runtime APIs 25384 // (see https://developer.chrome.com/apps/manifest/sandbox). 25385 isChromePackagedApp = 25386 $window.chrome && 25387 ($window.chrome.app && $window.chrome.app.runtime || 25388 !$window.chrome.app && $window.chrome.runtime && $window.chrome.runtime.id), 25389 hasHistoryPushState = !isChromePackagedApp && $window.history && $window.history.pushState, 25390 android = 25391 toInt((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), 25392 boxee = /Boxee/i.test(($window.navigator || {}).userAgent), 25393 document = $document[0] || {}, 25394 vendorPrefix, 25395 vendorRegex = /^(Moz|webkit|ms)(?=[A-Z])/, 25396 bodyStyle = document.body && document.body.style, 25397 transitions = false, 25398 animations = false, 25399 match; 25400 25401 if (bodyStyle) { 25402 for (var prop in bodyStyle) {
25403 if ((match = vendorRegex.exec(prop))) { 25404 vendorPrefix = match[0]; 25405 vendorPrefix = vendorPrefix[0].toUpperCase() + vendorPrefix.substr(1); 25406 break; 25407 } 25408 } 25409 25410 if (!vendorPrefix) { 25411 vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit'; 25412 } 25413 25414 transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle)); 25415 animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle)); 25416 25417 if (android && (!transitions || !animations)) { 25418 transitions = isString(bodyStyle.webkitTransition); 25419 animations = isString(bodyStyle.webkitAnimation); 25420 } 25421 } 25422 25423 25424 return { 25425 // Android has history.pushState, but it does not update location correctly 25426 // so let's not use the history API at all. 25427 // http://code.google.com/p/android/issues/detail?id=17471 25428 // https://github.com/angular/angular.js/issues/904 25429 25430 // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has 25431 // so let's not use the history API also 25432 // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined 25433 history: !!(hasHistoryPushState && !(android < 4) && !boxee), 25434 hasEvent: function(event) { 25435 // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have 25436 // it. In particular the event is not fired when backspace or delete key are pressed or 25437 // when cut operation is performed. 25438 // IE10+ implements 'input' event but it erroneously fires under various situations, 25439 // e.g. when placeholder changes, or a form is focused. 25440 if (event === 'input' && msie <= 11) return false; 25441 25442 if (isUndefined(eventSupport[event])) { 25443 var divElm = document.createElement('div'); 25444 eventSupport[event] = 'on' + event in divElm; 25445 } 25446 25447 return eventSupport[event]; 25448 }, 25449 csp: csp(), 25450 vendorPrefix: vendorPrefix, 25451 transitions: transitions, 25452 animations: animations, 25453 android: android 25454 }; 25455 }]; 25456} 25457 25458var $templateRequestMinErr = minErr('$compile'); 25459 25460/** 25461 * @ngdoc provider 25462 * @name $templateRequestProvider 25463 * @this 25464 * 25465 * @description 25466 * Used to configure the options passed to the {@link $http} service when making a template request. 25467 * 25468 * For example, it can be used for specifying the "Accept" header that is sent to the server, when 25469 * requesting a template. 25470 */ 25471function $TemplateRequestProvider() { 25472 25473 var httpOptions; 25474 25475 /** 25476 * @ngdoc method 25477 * @name $templateRequestProvider#httpOptions 25478 * @description 25479 * The options to be passed to the {@link $http} service when making the request. 25480 * You can use this to override options such as the "Accept" header for template requests. 25481 * 25482 * The {@link $templateRequest} will set the `cache` and the `transformResponse` properties of the 25483 * options if not overridden here. 25484 * 25485 * @param {string=} value new value for the {@link $http} options. 25486 * @returns {string|self} Returns the {@link $http} options when used as getter and self if used as setter. 25487 */ 25488 this.httpOptions = function(val) { 25489 if (val) { 25490 httpOptions = val; 25491 return this; 25492 } 25493 return httpOptions; 25494 }; 25495 25496 /** 25497 * @ngdoc service 25498 * @name $templateRequest 25499 * 25500 * @description 25501 * The `$templateRequest` service runs security checks then downloads the provided template using 25502 * `$http` and, upon success, stores the contents inside of `$templateCache`. If the HTTP request 25503 * fails or the response data of the HTTP request is empty, a `$compile` error will be thrown (the 25504 * exception can be thwarted by setting the 2nd parameter of the function to true). Note that the 25505 * contents of `$templateCache` are trusted, so the call to `$sce.getTrustedUrl(tpl)` is omitted 25506 * when `tpl` is of type string and `$templateCache` has the matching entry. 25507 * 25508 * If you want to pass custom options to the `$http` service, such as setting the Accept header you 25509 * can configure this via {@link $templateRequestProvider#httpOptions}. 25510 * 25511 * @param {string|TrustedResourceUrl} tpl The HTTP request template URL 25512 * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty 25513 * 25514 * @return {Promise} a promise for the HTTP response data of the given URL. 25515 * 25516 * @property {number} totalPendingRequests total amount of pending template requests being downloaded.
vendor: 4,448 bytes, lines 25517-25644
25517 */ 25518 this.$get = ['$templateCache', '$http', '$q', '$sce', function($templateCache, $http, $q, $sce) { 25519 25520 function handleRequestFn(tpl, ignoreRequestError) { 25521 handleRequestFn.totalPendingRequests++; 25522 25523 // We consider the template cache holds only trusted templates, so 25524 // there's no need to go through whitelisting again for keys that already 25525 // are included in there. This also makes Angular accept any script 25526 // directive, no matter its name. However, we still need to unwrap trusted 25527 // types. 25528 if (!isString(tpl) || isUndefined($templateCache.get(tpl))) { 25529 tpl = $sce.getTrustedResourceUrl(tpl); 25530 } 25531 25532 var transformResponse = $http.defaults && $http.defaults.transformResponse; 25533 25534 if (isArray(transformResponse)) { 25535 transformResponse = transformResponse.filter(function(transformer) { 25536 return transformer !== defaultHttpResponseTransform; 25537 }); 25538 } else if (transformResponse === defaultHttpResponseTransform) { 25539 transformResponse = null; 25540 } 25541 25542 return $http.get(tpl, extend({ 25543 cache: $templateCache, 25544 transformResponse: transformResponse 25545 }, httpOptions) 25546 )['finally'](function() { 25547 handleRequestFn.totalPendingRequests--; 25548 }) 25549 .then(function(response) { 25550 $templateCache.put(tpl, response.data); 25551 return response.data; 25552 }, handleError); 25553 25554 function handleError(resp) { 25555 if (!ignoreRequestError) { 25556 throw $templateRequestMinErr('tpload', 'Failed to load template: {0} (HTTP status: {1} {2})', 25557 tpl, resp.status, resp.statusText); 25558 } 25559 return $q.reject(resp); 25560 } 25561 } 25562 25563 handleRequestFn.totalPendingRequests = 0; 25564 25565 return handleRequestFn; 25566 }]; 25567} 25568 25569/** @this */ 25570function $$TestabilityProvider() { 25571 this.$get = ['$rootScope', '$browser', '$location', 25572 function($rootScope, $browser, $location) { 25573 25574 /** 25575 * @name $testability 25576 * 25577 * @description 25578 * The private $$testability service provides a collection of methods for use when debugging 25579 * or by automated test and debugging tools. 25580 */ 25581 var testability = {}; 25582 25583 /** 25584 * @name $$testability#findBindings 25585 * 25586 * @description 25587 * Returns an array of elements that are bound (via ng-bind or {{}}) 25588 * to expressions matching the input. 25589 * 25590 * @param {Element} element The element root to search from. 25591 * @param {string} expression The binding expression to match. 25592 * @param {boolean} opt_exactMatch If true, only returns exact matches 25593 * for the expression. Filters and whitespace are ignored. 25594 */ 25595 testability.findBindings = function(element, expression, opt_exactMatch) { 25596 var bindings = element.getElementsByClassName('ng-binding'); 25597 var matches = []; 25598 forEach(bindings, function(binding) { 25599 var dataBinding = angular.element(binding).data('$binding'); 25600 if (dataBinding) { 25601 forEach(dataBinding, function(bindingName) { 25602 if (opt_exactMatch) { 25603 var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)'); 25604 if (matcher.test(bindingName)) { 25605 matches.push(binding); 25606 } 25607 } else { 25608 if (bindingName.indexOf(expression) !== -1) { 25609 matches.push(binding); 25610 } 25611 } 25612 }); 25613 } 25614 }); 25615 return matches; 25616 }; 25617 25618 /** 25619 * @name $$testability#findModels 25620 * 25621 * @description 25622 * Returns an array of elements that are two-way found via ng-model to 25623 * expressions matching the input. 25624 * 25625 * @param {Element} element The element root to search from. 25626 * @param {string} expression The model expression to match. 25627 * @param {boolean} opt_exactMatch If true, only returns exact matches 25628 * for the expression. 25629 */ 25630 testability.findModels = function(element, expression, opt_exactMatch) { 25631 var prefixes = ['ng-', 'data-ng-', 'ng\\:']; 25632 for (var p = 0; p < prefixes.length; ++p) { 25633 var attributeEquals = opt_exactMatch ? '=' : '*='; 25634 var selector = '[' + prefixes[p] + 'model' + attributeEquals + '"' + expression + '"]'; 25635 var elements = element.querySelectorAll(selector); 25636 if (elements.length) { 25637 return elements; 25638 } 25639 } 25640 }; 25641 25642 /** 25643 * @name $$testability#getLocation 25644 *
25645 * @description 25646 * Shortcut for getting the location in a browser agnostic way. Returns 25647 * the path, search, and hash. (e.g. /path?a=b#hash) 25648 */ 25649 testability.getLocation = function() { 25650 return $location.url(); 25651 }; 25652 25653 /** 25654 * @name $$testability#setLocation 25655 * 25656 * @description 25657 * Shortcut for navigating to a location without doing a full page reload. 25658 * 25659 * @param {string} url The location url (path, search and hash, 25660 * e.g. /path?a=b#hash) to go to. 25661 */ 25662 testability.setLocation = function(url) { 25663 if (url !== $location.url()) { 25664 $location.url(url); 25665 $rootScope.$digest(); 25666 } 25667 }; 25668 25669 /** 25670 * @name $$testability#whenStable 25671 * 25672 * @description 25673 * Calls the callback when $timeout and $http requests are completed. 25674 * 25675 * @param {function} callback 25676 */ 25677 testability.whenStable = function(callback) { 25678 $browser.notifyWhenNoOutstandingRequests(callback); 25679 }; 25680 25681 return testability; 25682 }]; 25683} 25684 25685/** @this */ 25686function $TimeoutProvider() { 25687 this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler', 25688 function($rootScope, $browser, $q, $$q, $exceptionHandler) { 25689 25690 var deferreds = {}; 25691 25692 25693 /** 25694 * @ngdoc service 25695 * @name $timeout 25696 * 25697 * @description 25698 * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch 25699 * block and delegates any exceptions to 25700 * {@link ng.$exceptionHandler $exceptionHandler} service. 25701 * 25702 * The return value of calling `$timeout` is a promise, which will be resolved when 25703 * the delay has passed and the timeout function, if provided, is executed. 25704 * 25705 * To cancel a timeout request, call `$timeout.cancel(promise)`. 25706 * 25707 * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to 25708 * synchronously flush the queue of deferred functions. 25709 * 25710 * If you only want a promise that will be resolved after some specified delay 25711 * then you can call `$timeout` without the `fn` function. 25712 * 25713 * @param {function()=} fn A function, whose execution should be delayed. 25714 * @param {number=} [delay=0] Delay in milliseconds. 25715 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 25716 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 25717 * @param {...*=} Pass additional parameters to the executed function. 25718 * @returns {Promise} Promise that will be resolved when the timeout is reached. The promise 25719 * will be resolved with the return value of the `fn` function. 25720 * 25721 */ 25722 function timeout(fn, delay, invokeApply) { 25723 if (!isFunction(fn)) { 25724 invokeApply = delay; 25725 delay = fn; 25726 fn = noop; 25727 } 25728 25729 var args = sliceArgs(arguments, 3), 25730 skipApply = (isDefined(invokeApply) && !invokeApply), 25731 deferred = (skipApply ? $$q : $q).defer(), 25732 promise = deferred.promise, 25733 timeoutId; 25734 25735 timeoutId = $browser.defer(function() { 25736 try { 25737 deferred.resolve(fn.apply(null, args)); 25738 } catch (e) { 25739 deferred.reject(e); 25740 $exceptionHandler(e); 25741 } finally { 25742 delete deferreds[promise.$$timeoutId]; 25743 } 25744 25745 if (!skipApply) $rootScope.$apply(); 25746 }, delay); 25747 25748 promise.$$timeoutId = timeoutId; 25749 deferreds[timeoutId] = deferred; 25750 25751 return promise; 25752 } 25753 25754 25755 /** 25756 * @ngdoc method 25757 * @name $timeout#cancel 25758 * 25759 * @description 25760 * Cancels a task associated with the `promise`. As a result of this, the promise will be 25761 * resolved with a rejection. 25762 * 25763 * @param {Promise=} promise Promise returned by the `$timeout` function. 25764 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 25765 * canceled. 25766 */ 25767 timeout.cancel = function(promise) { 25768 if (promise && promise.$$timeoutId in deferreds) { 25769 deferreds[promise.$$timeoutId].reject('canceled'); 25770 delete deferreds[promise.$$timeoutId]; 25771 return $browser.defer.cancel(promise.$$timeoutId); 25772 } 25773 return false;
25774 }; 25775 25776 return timeout; 25777 }]; 25778} 25779 25780// NOTE: The usage of window and document instead of $window and $document here is 25781// deliberate. This service depends on the specific behavior of anchor nodes created by the 25782// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and 25783// cause us to break tests. In addition, when the browser resolves a URL for XHR, it 25784// doesn't know about mocked locations and resolves URLs to the real document - which is 25785// exactly the behavior needed here. There is little value is mocking these out for this 25786// service. 25787var urlParsingNode = window.document.createElement('a'); 25788var originUrl = urlResolve(window.location.href); 25789 25790 25791/** 25792 * 25793 * Implementation Notes for non-IE browsers 25794 * ---------------------------------------- 25795 * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, 25796 * results both in the normalizing and parsing of the URL. Normalizing means that a relative 25797 * URL will be resolved into an absolute URL in the context of the application document. 25798 * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related 25799 * properties are all populated to reflect the normalized URL. This approach has wide 25800 * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See 25801 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 25802 * 25803 * Implementation Notes for IE 25804 * --------------------------- 25805 * IE <= 10 normalizes the URL when assigned to the anchor node similar to the other 25806 * browsers. However, the parsed components will not be set if the URL assigned did not specify 25807 * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We 25808 * work around that by performing the parsing in a 2nd step by taking a previously normalized 25809 * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the 25810 * properties such as protocol, hostname, port, etc. 25811 * 25812 * References: 25813 * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement 25814 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 25815 * http://url.spec.whatwg.org/#urlutils 25816 * https://github.com/angular/angular.js/pull/2902 25817 * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ 25818 * 25819 * @kind function 25820 * @param {string} url The URL to be parsed. 25821 * @description Normalizes and parses a URL. 25822 * @returns {object} Returns the normalized URL as a dictionary. 25823 * 25824 * | member name | Description | 25825 * |---------------|----------------| 25826 * | href | A normalized version of the provided URL if it was not an absolute URL | 25827 * | protocol | The protocol including the trailing colon | 25828 * | host | The host and port (if the port is non-default) of the normalizedUrl | 25829 * | search | The search params, minus the question mark | 25830 * | hash | The hash string, minus the hash symbol 25831 * | hostname | The hostname 25832 * | port | The port, without ":" 25833 * | pathname | The pathname, beginning with "/" 25834 * 25835 */ 25836function urlResolve(url) { 25837 var href = url; 25838 25839 if (msie) { 25840 // Normalize before parse. Refer Implementation Notes on why this is 25841 // done in two steps on IE. 25842 urlParsingNode.setAttribute('href', href); 25843 href = urlParsingNode.href; 25844 } 25845 25846 urlParsingNode.setAttribute('href', href); 25847 25848 // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils 25849 return { 25850 href: urlParsingNode.href, 25851 protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', 25852 host: urlParsingNode.host, 25853 search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', 25854 hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', 25855 hostname: urlParsingNode.hostname, 25856 port: urlParsingNode.port, 25857 pathname: (urlParsingNode.pathname.charAt(0) === '/') 25858 ? urlParsingNode.pathname 25859 : '/' + urlParsingNode.pathname 25860 }; 25861} 25862 25863/** 25864 * Parse a request URL and determine whether this is a same-origin request as the application document. 25865 * 25866 * @param {string|object} requestUrl The url of the request as a string that will be resolved 25867 * or a parsed URL object. 25868 * @returns {boolean} Whether the request is for the same origin as the application document. 25869 */ 25870function urlIsSameOrigin(requestUrl) { 25871 var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl; 25872 return (parsed.protocol === originUrl.protocol && 25873 parsed.host === originUrl.host); 25874} 25875 25876/** 25877 * @ngdoc service 25878 * @name $window 25879 * @this 25880 * 25881 * @description 25882 * A reference to the browser's `window` object. While `window` 25883 * is globally available in JavaScript, it causes testability problems, because 25884 * it is a global variable. In angular we always refer to it through the 25885 * `$window` service, so it may be overridden, removed or mocked for testing. 25886 * 25887 * Expressions, like the one defined for the `ngClick` directive in the example 25888 * below, are evaluated with respect to the current scope. Therefore, there is 25889 * no risk of inadvertently coding in a dependency on a global value in such an 25890 * expression. 25891 * 25892 * @example 25893 <example module="windowExample" name="window-service"> 25894 <file name="index.html"> 25895 <script> 25896 angular.module('windowExample', [])
25897 .controller('ExampleController', ['$scope', '$window', function($scope, $window) { 25898 $scope.greeting = 'Hello, World!'; 25899 $scope.doGreeting = function(greeting) { 25900 $window.alert(greeting); 25901 }; 25902 }]); 25903 </script> 25904 <div ng-controller="ExampleController"> 25905 <input type="text" ng-model="greeting" aria-label="greeting" /> 25906 <button ng-click="doGreeting(greeting)">ALERT</button> 25907 </div> 25908 </file> 25909 <file name="protractor.js" type="protractor"> 25910 it('should display the greeting in the input box', function() { 25911 element(by.model('greeting')).sendKeys('Hello, E2E Tests'); 25912 // If we click the button it will block the test runner 25913 // element(':button').click(); 25914 }); 25915 </file> 25916 </example> 25917 */ 25918function $WindowProvider() { 25919 this.$get = valueFn(window); 25920} 25921 25922/** 25923 * @name $$cookieReader 25924 * @requires $document 25925 * 25926 * @description 25927 * This is a private service for reading cookies used by $http and ngCookies 25928 * 25929 * @return {Object} a key/value map of the current cookies 25930 */ 25931function $$CookieReader($document) { 25932 var rawDocument = $document[0] || {}; 25933 var lastCookies = {}; 25934 var lastCookieString = ''; 25935 25936 function safeGetCookie(rawDocument) { 25937 try { 25938 return rawDocument.cookie || ''; 25939 } catch (e) { 25940 return ''; 25941 } 25942 } 25943 25944 function safeDecodeURIComponent(str) { 25945 try { 25946 return decodeURIComponent(str); 25947 } catch (e) { 25948 return str; 25949 } 25950 } 25951 25952 return function() { 25953 var cookieArray, cookie, i, index, name; 25954 var currentCookieString = safeGetCookie(rawDocument); 25955 25956 if (currentCookieString !== lastCookieString) { 25957 lastCookieString = currentCookieString; 25958 cookieArray = lastCookieString.split('; '); 25959 lastCookies = {}; 25960 25961 for (i = 0; i < cookieArray.length; i++) { 25962 cookie = cookieArray[i]; 25963 index = cookie.indexOf('='); 25964 if (index > 0) { //ignore nameless cookies 25965 name = safeDecodeURIComponent(cookie.substring(0, index)); 25966 // the first value that is seen for a cookie is the most 25967 // specific one. values for the same cookie name that 25968 // follow are for less specific paths. 25969 if (isUndefined(lastCookies[name])) { 25970 lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1)); 25971 } 25972 } 25973 } 25974 } 25975 return lastCookies; 25976 }; 25977} 25978 25979$$CookieReader.$inject = ['$document']; 25980 25981/** @this */ 25982function $$CookieReaderProvider() { 25983 this.$get = $$CookieReader; 25984} 25985 25986/* global currencyFilter: true, 25987 dateFilter: true, 25988 filterFilter: true, 25989 jsonFilter: true, 25990 limitToFilter: true, 25991 lowercaseFilter: true, 25992 numberFilter: true, 25993 orderByFilter: true, 25994 uppercaseFilter: true, 25995 */ 25996 25997/** 25998 * @ngdoc provider 25999 * @name $filterProvider 26000 * @description 26001 * 26002 * Filters are just functions which transform input to an output. However filters need to be 26003 * Dependency Injected. To achieve this a filter definition consists of a factory function which is 26004 * annotated with dependencies and is responsible for creating a filter function. 26005 * 26006 * <div class="alert alert-warning"> 26007 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. 26008 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 26009 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 26010 * (`myapp_subsection_filterx`). 26011 * </div> 26012 * 26013 * ```js 26014 * // Filter registration 26015 * function MyModule($provide, $filterProvider) { 26016 * // create a service to demonstrate injection (not always needed) 26017 * $provide.value('greet', function(name){ 26018 * return 'Hello ' + name + '!'; 26019 * }); 26020 * 26021 * // register a filter factory which uses the 26022 * // greet service to demonstrate DI. 26023 * $filterProvider.register('greet', function(greet){ 26024 * // return the filter function which uses the greet service 26025 * // to generate salutation 26026 * return function(text) { 26027 * // filters need to be forgiving so check input validity 26028 * return text && greet(text) || text; 26029 * }; 26030 * }); 26031 * } 26032 * ``` 26033 * 26034 * The filter function is registered with the `$injector` under the filter name suffix with 26035 * `Filter`. 26036 * 26037 * ```js 26038 * it('should be the same instance', inject( 26039 * function($filterProvider) { 26040 * $filterProvider.register('reverse', function(){ 26041 * return ...; 26042 * }); 26043 * }, 26044 * function($filter, reverseFilter) { 26045 * expect($filter('reverse')).toBe(reverseFilter); 26046 * }); 26047 * ``` 26048 * 26049 * 26050 * For more information about how angular filters work, and how to create your own filters, see 26051 * {@link guide/filter Filters} in the Angular Developer Guide. 26052 */ 26053 26054/** 26055 * @ngdoc service 26056 * @name $filter 26057 * @kind function 26058 * @description 26059 * Filters are used for formatting data displayed to the user. 26060 * 26061 * They can be used in view templates, controllers or services.Angular comes 26062 * with a collection of [built-in filters](api/ng/filter), but it is easy to 26063 * define your own as well. 26064 * 26065 * The general syntax in templates is as follows: 26066 * 26067 * ```html 26068 * {{ expression [| filter_name[:parameter_value] ... ] }} 26069 * ``` 26070 * 26071 * @param {String} name Name of the filter function to retrieve 26072 * @return {Function} the filter function 26073 * @example 26074 <example name="$filter" module="filterExample"> 26075 <file name="index.html"> 26076 <div ng-controller="MainCtrl"> 26077 <h3>{{ originalText }}</h3> 26078 <h3>{{ filteredText }}</h3> 26079 </div> 26080 </file> 26081 26082 <file name="script.js"> 26083 angular.module('filterExample', []) 26084 .controller('MainCtrl', function($scope, $filter) { 26085 $scope.originalText = 'hello'; 26086 $scope.filteredText = $filter('uppercase')($scope.originalText); 26087 }); 26088 </file> 26089 </example> 26090 */ 26091$FilterProvider.$inject = ['$provide']; 26092/** @this */ 26093function $FilterProvider($provide) { 26094 var suffix = 'Filter'; 26095 26096 /** 26097 * @ngdoc method 26098 * @name $filterProvider#register 26099 * @param {string|Object} name Name of the filter function, or an object map of filters where 26100 * the keys are the filter names and the values are the filter factories. 26101 * 26102 * <div class="alert alert-warning"> 26103 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. 26104 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 26105 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 26106 * (`myapp_subsection_filterx`). 26107 * </div> 26108 * @param {Function} factory If the first argument was a string, a factory function for the filter to be registered. 26109 * @returns {Object} Registered filter instance, or if a map of filters was provided then a map 26110 * of the registered filter instances. 26111 */ 26112 function register(name, factory) { 26113 if (isObject(name)) { 26114 var filters = {};
26115 forEach(name, function(filter, key) { 26116 filters[key] = register(key, filter); 26117 }); 26118 return filters; 26119 } else { 26120 return $provide.factory(name + suffix, factory); 26121 } 26122 } 26123 this.register = register; 26124 26125 this.$get = ['$injector', function($injector) { 26126 return function(name) { 26127 return $injector.get(name + suffix); 26128 }; 26129 }]; 26130 26131 //////////////////////////////////////// 26132 26133 /* global 26134 currencyFilter: false, 26135 dateFilter: false, 26136 filterFilter: false, 26137 jsonFilter: false, 26138 limitToFilter: false, 26139 lowercaseFilter: false, 26140 numberFilter: false, 26141 orderByFilter: false, 26142 uppercaseFilter: false 26143 */ 26144 26145 register('currency', currencyFilter); 26146 register('date', dateFilter); 26147 register('filter', filterFilter); 26148 register('json', jsonFilter); 26149 register('limitTo', limitToFilter); 26150 register('lowercase', lowercaseFilter); 26151 register('number', numberFilter); 26152 register('orderBy', orderByFilter); 26153 register('uppercase', uppercaseFilter); 26154} 26155 26156/** 26157 * @ngdoc filter 26158 * @name filter 26159 * @kind function 26160 * 26161 * @description 26162 * Selects a subset of items from `array` and returns it as a new array. 26163 * 26164 * @param {Array} array The source array. 26165 * @param {string|Object|function()} expression The predicate to be used for selecting items from 26166 * `array`. 26167 * 26168 * Can be one of: 26169 * 26170 * - `string`: The string is used for matching against the contents of the `array`. All strings or 26171 * objects with string properties in `array` that match this string will be returned. This also 26172 * applies to nested object properties. 26173 * The predicate can be negated by prefixing the string with `!`. 26174 * 26175 * - `Object`: A pattern object can be used to filter specific properties on objects contained 26176 * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items 26177 * which have property `name` containing "M" and property `phone` containing "1". A special 26178 * property name (`$` by default) can be used (e.g. as in `{$: "text"}`) to accept a match 26179 * against any property of the object or its nested object properties. That's equivalent to the 26180 * simple substring match with a `string` as described above. The special property name can be 26181 * overwritten, using the `anyPropertyKey` parameter. 26182 * The predicate can be negated by prefixing the string with `!`. 26183 * For example `{name: "!M"}` predicate will return an array of items which have property `name` 26184 * not containing "M". 26185 * 26186 * Note that a named property will match properties on the same level only, while the special 26187 * `$` property will match properties on the same level or deeper. E.g. an array item like 26188 * `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but 26189 * **will** be matched by `{$: 'John'}`. 26190 * 26191 * - `function(value, index, array)`: A predicate function can be used to write arbitrary filters. 26192 * The function is called for each element of the array, with the element, its index, and 26193 * the entire array itself as arguments. 26194 * 26195 * The final result is an array of those elements that the predicate returned true for. 26196 * 26197 * @param {function(actual, expected)|true|false} [comparator] Comparator which is used in 26198 * determining if the expected value (from the filter expression) and actual value (from 26199 * the object in the array) should be considered a match. 26200 * 26201 * Can be one of: 26202 * 26203 * - `function(actual, expected)`: 26204 * The function will be given the object value and the predicate value to compare and 26205 * should return true if both values should be considered equal. 26206 * 26207 * - `true`: A shorthand for `function(actual, expected) { return angular.equals(actual, expected)}`. 26208 * This is essentially strict comparison of expected and actual. 26209 * 26210 * - `false`: A short hand for a function which will look for a substring match in a case 26211 * insensitive way. Primitive values are converted to strings. Objects are not compared against 26212 * primitives, unless they have a custom `toString` method (e.g. `Date` objects). 26213 * 26214 * 26215 * Defaults to `false`. 26216 * 26217 * @param {string} [anyPropertyKey] The special property name that matches against any property. 26218 * By default `$`. 26219 * 26220 * @example 26221 <example name="filter-filter"> 26222 <file name="index.html"> 26223 <div ng-init="friends = [{name:'John', phone:'555-1276'}, 26224 {name:'Mary', phone:'800-BIG-MARY'}, 26225 {name:'Mike', phone:'555-4321'}, 26226 {name:'Adam', phone:'555-5678'}, 26227 {name:'Julie', phone:'555-8765'}, 26228 {name:'Juliette', phone:'555-5678'}]"></div> 26229 26230 <label>Search: <input ng-model="searchText"></label> 26231 <table id="searchTextResults"> 26232 <tr><th>
26232Name</th><th>Phone</th></tr> 26233 <tr ng-repeat="friend in friends | filter:searchText"> 26234 <td>{{friend.name}}</td> 26235 <td>{{friend.phone}}</td> 26236 </tr> 26237 </table> 26238 <hr> 26239 <label>Any: <input ng-model="search.$"></label> <br> 26240 <label>Name only <input ng-model="search.name"></label><br> 26241 <label>Phone only <input ng-model="search.phone"></label><br> 26242 <label>Equality <input type="checkbox" ng-model="strict"></label><br> 26243 <table id="searchObjResults"> 26244 <tr><th>Name</th><th>Phone</th></tr> 26245 <tr ng-repeat="friendObj in friends | filter:search:strict"> 26246 <td>{{friendObj.name}}</td> 26247 <td>{{friendObj.phone}}</td> 26248 </tr> 26249 </table> 26250 </file> 26251 <file name="protractor.js" type="protractor"> 26252 var expectFriendNames = function(expectedNames, key) { 26253 element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) { 26254 arr.forEach(function(wd, i) { 26255 expect(wd.getText()).toMatch(expectedNames[i]); 26256 }); 26257 }); 26258 }; 26259 26260 it('should search across all fields when filtering with a string', function() { 26261 var searchText = element(by.model('searchText')); 26262 searchText.clear(); 26263 searchText.sendKeys('m'); 26264 expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend'); 26265 26266 searchText.clear(); 26267 searchText.sendKeys('76'); 26268 expectFriendNames(['John', 'Julie'], 'friend'); 26269 }); 26270 26271 it('should search in specific fields when filtering with a predicate object', function() { 26272 var searchAny = element(by.model('search.$')); 26273 searchAny.clear(); 26274 searchAny.sendKeys('i'); 26275 expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj'); 26276 }); 26277 it('should use a equal comparison when comparator is true', function() { 26278 var searchName = element(by.model('search.name')); 26279 var strict = element(by.model('strict')); 26280 searchName.clear(); 26281 searchName.sendKeys('Julie'); 26282 strict.click(); 26283 expectFriendNames(['Julie'], 'friendObj'); 26284 }); 26285 </file> 26286 </example> 26287 */ 26288 26289function filterFilter() { 26290 return function(array, expression, comparator, anyPropertyKey) { 26291 if (!isArrayLike(array)) { 26292 if (array == null) { 26293 return array; 26294 } else { 26295 throw minErr('filter')('notarray', 'Expected array but received: {0}', array); 26296 } 26297 } 26298 26299 anyPropertyKey = anyPropertyKey || '$'; 26300 var expressionType = getTypeForFilter(expression); 26301 var predicateFn; 26302 var matchAgainstAnyProp; 26303 26304 switch (expressionType) { 26305 case 'function': 26306 predicateFn = expression; 26307 break; 26308 case 'boolean': 26309 case 'null': 26310 case 'number': 26311 case 'string': 26312 matchAgainstAnyProp = true; 26313 // falls through 26314 case 'object': 26315 predicateFn = createPredicateFn(expression, comparator, anyPropertyKey, matchAgainstAnyProp); 26316 break; 26317 default: 26318 return array; 26319 } 26320 26321 return Array.prototype.filter.call(array, predicateFn); 26322 }; 26323} 26324 26325// Helper functions for `filterFilter` 26326function createPredicateFn(expression, comparator, anyPropertyKey, matchAgainstAnyProp) { 26327 var shouldMatchPrimitives = isObject(expression) && (anyPropertyKey in expression); 26328 var predicateFn; 26329 26330 if (comparator === true) { 26331 comparator = equals; 26332 } else if (!isFunction(comparator)) { 26333 comparator = function(actual, expected) { 26334 if (isUndefined(actual)) { 26335 // No substring matching against `undefined` 26336 return false; 26337 } 26338 if ((actual === null) || (expected === null)) { 26339 // No substring matching against `null`; only match against `null` 26340 return actual === expected; 26341 } 26342 if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual))) { 26343 // Should not compare primitives against objects, unless they have custom `toString` method 26344 return false; 26345 } 26346 26347 actual = lowercase('' + actual); 26348 expected = lowercase('' + expected); 26349 return actual.indexOf(expected) !== -1; 26350 }; 26351 } 26352 26353 predicateFn = function(item) { 26354 if (shouldMatchPrimitives && !isObject(item)) { 26355 return deepCompare(item, expression[anyPropertyKey], comparator, anyPropertyKey, false); 26356 } 26357 return deepCompare(item, expression, comparator, anyPropertyKey, matchAgainstAnyProp); 26358 }; 26359 26360 return predicateFn; 26361} 26362 26363function deepCompare(actual, expected, comparator, anyPropertyKey, matchAgainstAnyProp, dontMatchWholeObject) { 26364 var actualType = getTypeForFilter(actual); 26365 var expectedType = getTypeForFilter(expected); 26366 26367 if ((expectedType === 'string') && (expected.charAt(0) === '!')) { 26368 return !deepCompare(actual, expected.substring(1), comparator, anyPropertyKey, matchAgainstAnyProp); 26369 } else if (isArray(actual)) { 26370 // In case `actual` is an array, consider it a match 26371 // if ANY of it's items matches `expected` 26372 return actual.some(function(item) { 26373 return deepCompare(item, expected, comparator, anyPropertyKey, matchAgainstAnyProp); 26374 }); 26375 } 26376 26377 switch (actualType) { 26378 case 'object': 26379 var key; 26380 if (matchAgainstAnyProp) { 26381 for (key in actual) { 26382 if ((key.charAt(0) !== '$') &&
vendor: 2,309 bytes, lines 26382-26447
26382 deepCompare(actual[key], expected, comparator, anyPropertyKey, true)) { 26383 return true; 26384 } 26385 } 26386 return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, anyPropertyKey, false); 26387 } else if (expectedType === 'object') { 26388 for (key in expected) { 26389 var expectedVal = expected[key]; 26390 if (isFunction(expectedVal) || isUndefined(expectedVal)) { 26391 continue; 26392 } 26393 26394 var matchAnyProperty = key === anyPropertyKey; 26395 var actualVal = matchAnyProperty ? actual : actual[key]; 26396 if (!deepCompare(actualVal, expectedVal, comparator, anyPropertyKey, matchAnyProperty, matchAnyProperty)) { 26397 return false; 26398 } 26399 } 26400 return true; 26401 } else { 26402 return comparator(actual, expected); 26403 } 26404 case 'function': 26405 return false; 26406 default: 26407 return comparator(actual, expected); 26408 } 26409} 26410 26411// Used for easily differentiating between `null` and actual `object` 26412function getTypeForFilter(val) { 26413 return (val === null) ? 'null' : typeof val; 26414} 26415 26416var MAX_DIGITS = 22; 26417var DECIMAL_SEP = '.'; 26418var ZERO_CHAR = '0'; 26419 26420/** 26421 * @ngdoc filter 26422 * @name currency 26423 * @kind function 26424 * 26425 * @description 26426 * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default 26427 * symbol for current locale is used. 26428 * 26429 * @param {number} amount Input to filter. 26430 * @param {string=} symbol Currency symbol or identifier to be displayed. 26431 * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale 26432 * @returns {string} Formatted number. 26433 * 26434 * 26435 * @example 26436 <example module="currencyExample" name="currency-filter"> 26437 <file name="index.html"> 26438 <script> 26439 angular.module('currencyExample', []) 26440 .controller('ExampleController', ['$scope', function($scope) { 26441 $scope.amount = 1234.56; 26442 }]); 26443 </script> 26444 <div ng-controller="ExampleController"> 26445 <input type="number" ng-model="amount" aria-label="amount"> <br> 26446 default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br> 26447 custom currency identifier (USD$): <span id="currency-custom">{{amount | currency:"USD$"}}</span>
26447 26448 no fractions (0): <span id="currency-no-fractions">{{amount | currency:"USD$":0}}</span> 26449 </div> 26450 </file> 26451 <file name="protractor.js" type="protractor"> 26452 it('should init with 1234.56', function() { 26453 expect(element(by.id('currency-default')).getText()).toBe('$1,234.56'); 26454 expect(element(by.id('currency-custom')).getText()).toBe('USD$1,234.56'); 26455 expect(element(by.id('currency-no-fractions')).getText()).toBe('USD$1,235'); 26456 }); 26457 it('should update', function() { 26458 if (browser.params.browser === 'safari') { 26459 // Safari does not understand the minus key. See 26460 // https://github.com/angular/protractor/issues/481 26461 return; 26462 } 26463 element(by.model('amount')).clear(); 26464 element(by.model('amount')).sendKeys('-1234'); 26465 expect(element(by.id('currency-default')).getText()).toBe('-$1,234.00'); 26466 expect(element(by.id('currency-custom')).getText()).toBe('-USD$1,234.00'); 26467 expect(element(by.id('currency-no-fractions')).getText()).toBe('-USD$1,234'); 26468 }); 26469 </file> 26470 </example> 26471 */ 26472currencyFilter.$inject = ['$locale']; 26473function currencyFilter($locale) { 26474 var formats = $locale.NUMBER_FORMATS; 26475 return function(amount, currencySymbol, fractionSize) { 26476 if (isUndefined(currencySymbol)) { 26477 currencySymbol = formats.CURRENCY_SYM; 26478 } 26479 26480 if (isUndefined(fractionSize)) { 26481 fractionSize = formats.PATTERNS[1].maxFrac; 26482 } 26483 26484 // if null or undefined pass it through 26485 return (amount == null) 26486 ? amount 26487 : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize). 26488 replace(/\u00A4/g
vendor: 13,487 bytes, lines 26488-26891
26488, currencySymbol); 26489 }; 26490} 26491 26492/** 26493 * @ngdoc filter 26494 * @name number 26495 * @kind function 26496 * 26497 * @description 26498 * Formats a number as text. 26499 * 26500 * If the input is null or undefined, it will just be returned. 26501 * If the input is infinite (Infinity or -Infinity), the Infinity symbol 'â' or '-â' is returned, respectively. 26502 * If the input is not a number an empty string is returned. 26503 * 26504 * 26505 * @param {number|string} number Number to format. 26506 * @param {(number|string)=} fractionSize Number of decimal places to round the number to. 26507 * If this is not provided then the fraction size is computed from the current locale's number 26508 * formatting pattern. In the case of the default locale, it will be 3. 26509 * @returns {string} Number rounded to `fractionSize` appropriately formatted based on the current 26510 * locale (e.g., in the en_US locale it will have "." as the decimal separator and 26511 * include "," group separators after each third digit). 26512 * 26513 * @example 26514 <example module="numberFilterExample" name="number-filter"> 26515 <file name="index.html"> 26516 <script> 26517 angular.module('numberFilterExample', []) 26518 .controller('ExampleController', ['$scope', function($scope) { 26519 $scope.val = 1234.56789; 26520 }]); 26521 </script> 26522 <div ng-controller="ExampleController"> 26523 <label>Enter number: <input ng-model='val'></label><br> 26524 Default formatting: <span id='number-default'>{{val | number}}</span><br> 26525 No fractions: <span>{{val | number:0}}</span><br> 26526 Negative number: <span>{{-val | number:4}}</span> 26527 </div> 26528 </file> 26529 <file name="protractor.js" type="protractor"> 26530 it('should format numbers', function() { 26531 expect(element(by.id('number-default')).getText()).toBe('1,234.568'); 26532 expect(element(by.binding('val | number:0')).getText()).toBe('1,235'); 26533 expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679'); 26534 }); 26535 26536 it('should update', function() { 26537 element(by.model('val')).clear(); 26538 element(by.model('val')).sendKeys('3374.333'); 26539 expect(element(by.id('number-default')).getText()).toBe('3,374.333'); 26540 expect(element(by.binding('val | number:0')).getText()).toBe('3,374'); 26541 expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330'); 26542 }); 26543 </file> 26544 </example> 26545 */ 26546numberFilter.$inject = ['$locale']; 26547function numberFilter($locale) { 26548 var formats = $locale.NUMBER_FORMATS; 26549 return function(number, fractionSize) { 26550 26551 // if null or undefined pass it through 26552 return (number == null) 26553 ? number 26554 : formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP, 26555 fractionSize); 26556 }; 26557} 26558 26559/** 26560 * Parse a number (as a string) into three components that can be used 26561 * for formatting the number. 26562 * 26563 * (Significant bits of this parse algorithm came from https://github.com/MikeMcl/big.js/) 26564 * 26565 * @param {string} numStr The number to parse 26566 * @return {object} An object describing this number, containing the following keys: 26567 * - d : an array of digits containing leading zeros as necessary 26568 * - i : the number of the digits in `d` that are to the left of the decimal point 26569 * - e : the exponent for numbers that would need more than `MAX_DIGITS` digits in `d` 26570 * 26571 */ 26572function parse(numStr) { 26573 var exponent = 0, digits, numberOfIntegerDigits; 26574 var i, j, zeros; 26575 26576 // Decimal point? 26577 if ((numberOfIntegerDigits = numStr.indexOf(DECIMAL_SEP)) > -1) { 26578 numStr = numStr.replace(DECIMAL_SEP, ''); 26579 } 26580 26581 // Exponential form? 26582 if ((i = numStr.search(/e/i)) > 0) { 26583 // Work out the exponent. 26584 if (numberOfIntegerDigits < 0) numberOfIntegerDigits = i; 26585 numberOfIntegerDigits += +numStr.slice(i + 1); 26586 numStr = numStr.substring(0, i); 26587 } else if (numberOfIntegerDigits < 0) { 26588 // There was no decimal point or exponent so it is an integer. 26589 numberOfIntegerDigits = numStr.length; 26590 } 26591 26592 // Count the number of leading zeros. 26593 for (i = 0; numStr.charAt(i) === ZERO_CHAR; i++) { /* empty */ } 26594 26595 if (i === (zeros = numStr.length)) { 26596 // The digits are all zero. 26597 digits = [0]; 26598 numberOfIntegerDigits = 1; 26599 } else { 26600 // Count the number of trailing zeros 26601 zeros--; 26602 while (numStr.charAt(zeros) === ZERO_CHAR) zeros--; 26603 26604 // Trailing zeros are insignificant so ignore them 26605 numberOfIntegerDigits -= i; 26606 digits = []; 26607 // Convert string to array of digits without leading/trailing zeros. 26608 for (j = 0; i <= zeros; i++, j++) { 26609 digits[j] = +numStr.charAt(i); 26610 } 26611 } 26612 26613 // If the number overflows the maximum allowed digits then use an exponent. 26614 if (numberOfIntegerDigits > MAX_DIGITS) { 26615 digits = digits.splice(0, MAX_DIGITS - 1); 26616 exponent = numberOfIntegerDigits - 1; 26617 numberOfIntegerDigits = 1; 26618 } 26619 26620 return { d: digits, e: exponent, i: numberOfIntegerDigits }; 26621} 26622 26623/** 26624 * Round the parsed number to the specified number of decimal places 26625 * This function changed the parsedNumber in-place 26626 */ 26627function roundNumber(parsedNumber, fractionSize, minFrac, maxFrac) { 26628 var digits = parsedNumber.d; 26629 var fractionLen = digits.length - parsedNumber.i; 26630 26631 // determine fractionSize if it is not specified; `+fractionSize` converts it to a number 26632 fractionSize = (isUndefined(fractionSize)) ? Math.min(Math.max(minFrac, fractionLen), maxFrac) : +fractionSize; 26633 26634 // The index of the digit to where rounding is to occur 26635 var roundAt = fractionSize + parsedNumber.i; 26636 var digit = digits[roundAt]; 26637 26638 if (roundAt > 0) { 26639 // Drop fractional digits beyond `roundAt` 26640 digits.splice(Math.max(parsedNumber.i, roundAt)); 26641 26642 // Set non-fractional digits beyond `roundAt` to 0 26643 for (var j = roundAt; j < digits.length; j++) { 26644 digits[j] = 0; 26645 } 26646 } else { 26647 // We rounded to zero so reset the parsedNumber 26648 fractionLen = Math.max(0, fractionLen); 26649 parsedNumber.i = 1; 26650 digits.length = Math.max(1, roundAt = fractionSize + 1); 26651 digits[0] = 0; 26652 for (var i = 1; i < roundAt; i++) digits[i] = 0; 26653 } 26654 26655 if (digit >= 5) { 26656 if (roundAt - 1 < 0) { 26657 for (var k = 0; k > roundAt; k--) { 26658 digits.unshift(0); 26659 parsedNumber.i++; 26660 } 26661 digits.unshift(1); 26662 parsedNumber.i++; 26663 } else { 26664 digits[roundAt - 1]++; 26665 } 26666 } 26667 26668 // Pad out with zeros to get the required fraction length 26669 for (; fractionLen < Math.max(0, fractionSize); fractionLen++) digits.push(0); 26670 26671 26672 // Do any carrying, e.g. a digit was rounded up to 10 26673 var carry = digits.reduceRight(function(carry, d, i, digits) { 26674 d = d + carry; 26675 digits[i] = d % 10; 26676 return Math.floor(d / 10); 26677 }, 0); 26678 if (carry) { 26679 digits.unshift(carry); 26680 parsedNumber.i++; 26681 } 26682} 26683 26684/** 26685 * Format a number into a string 26686 * @param {number} number The number to format 26687 * @param {{ 26688 * minFrac, // the minimum number of digits required in the fraction part of the number 26689 * maxFrac, // the maximum number of digits required in the fraction part of the number 26690 * gSize, // number of digits in each group of separated digits 26691 * lgSize, // number of digits in the last group of digits before the decimal separator 26692 * negPre, // the string to go in front of a negative number (e.g. `-` or `(`)) 26693 * posPre, // the string to go in front of a positive number 26694 * negSuf, // the string to go after a negative number (e.g. `)`) 26695 * posSuf // the string to go after a positive number 26696 * }} pattern 26697 * @param {string} groupSep The string to separate groups of number (e.g. `,`) 26698 * @param {string} decimalSep The string to act as the decimal separator (e.g. `.`) 26699 * @param {[type]} fractionSize The size of the fractional part of the number 26700 * @return {string} The number formatted as a string 26701 */ 26702function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { 26703 26704 if (!(isString(number) || isNumber(number)) || isNaN(number)) return ''; 26705 26706 var isInfinity = !isFinite(number); 26707 var isZero = false; 26708 var numStr = Math.abs(number) + '', 26709 formattedText = '', 26710 parsedNumber; 26711 26712 if (isInfinity) { 26713 formattedText = '\u221e'; 26714 } else { 26715 parsedNumber = parse(numStr); 26716 26717 roundNumber(parsedNumber, fractionSize, pattern.minFrac, pattern.maxFrac); 26718 26719 var digits = parsedNumber.d; 26720 var integerLen = parsedNumber.i; 26721 var exponent = parsedNumber.e; 26722 var decimals = []; 26723 isZero = digits.reduce(function(isZero, d) { return isZero && !d; }, true); 26724 26725 // pad zeros for small numbers 26726 while (integerLen < 0) { 26727 digits.unshift(0); 26728 integerLen++; 26729 } 26730 26731 // extract decimals digits 26732 if (integerLen > 0) { 26733 decimals = digits.splice(integerLen, digits.length); 26734 } else { 26735 decimals = digits; 26736 digits = [0]; 26737 } 26738 26739 // format the integer digits with grouping separators 26740 var groups = []; 26741 if (digits.length >= pattern.lgSize) { 26742 groups.unshift(digits.splice(-pattern.lgSize, digits.length).join('')); 26743 } 26744 while (digits.length > pattern.gSize) { 26745 groups.unshift(digits.splice(-pattern.gSize, digits.length).join('')); 26746 } 26747 if (digits.length) { 26748 groups.unshift(digits.join('')); 26749 } 26750 formattedText = groups.join(groupSep); 26751 26752 // append the decimal digits 26753 if (decimals.length) { 26754 formattedText += decimalSep + decimals.join(''); 26755 } 26756 26757 if (exponent) { 26758 formattedText += 'e+' + exponent; 26759 } 26760 } 26761 if (number < 0 && !isZero) { 26762 return pattern.negPre + formattedText + pattern.negSuf; 26763 } else { 26764 return pattern.posPre + formattedText + pattern.posSuf; 26765 } 26766} 26767 26768function padNumber(num, digits, trim, negWrap) { 26769 var neg = ''; 26770 if (num < 0 || (negWrap && num <= 0)) { 26771 if (negWrap) { 26772 num = -num + 1; 26773 } else { 26774 num = -num; 26775 neg = '-'; 26776 } 26777 } 26778 num = '' + num; 26779 while (num.length < digits) num = ZERO_CHAR + num; 26780 if (trim) { 26781 num = num.substr(num.length - digits); 26782 } 26783 return neg + num; 26784} 26785 26786 26787function dateGetter(name, size, offset, trim, negWrap) { 26788 offset = offset || 0; 26789 return function(date) { 26790 var value = date['get' + name](); 26791 if (offset > 0 || value > -offset) { 26792 value += offset; 26793 } 26794 if (value === 0 && offset === -12) value = 12; 26795 return padNumber(value, size, trim, negWrap); 26796 }; 26797} 26798 26799function dateStrGetter(name, shortForm, standAlone) { 26800 return function(date, formats) { 26801 var value = date['get' + name](); 26802 var propPrefix = (standAlone ? 'STANDALONE' : '') + (shortForm ? 'SHORT' : ''); 26803 var get = uppercase(propPrefix + name); 26804 26805 return formats[get][value]; 26806 }; 26807} 26808 26809function timeZoneGetter(date, formats, offset) { 26810 var zone = -1 * offset; 26811 var paddedZone = (zone >= 0) ? '+' : ''; 26812 26813 paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) + 26814 padNumber(Math.abs(zone % 60), 2); 26815 26816 return paddedZone; 26817} 26818 26819function getFirstThursdayOfYear(year) { 26820 // 0 = index of January 26821 var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay(); 26822 // 4 = index of Thursday (+1 to account for 1st = 5) 26823 // 11 = index of *next* Thursday (+1 account for 1st = 12) 26824 return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst); 26825} 26826 26827function getThursdayThisWeek(datetime) { 26828 return new Date(datetime.getFullYear(), datetime.getMonth(), 26829 // 4 = index of Thursday 26830 datetime.getDate() + (4 - datetime.getDay())); 26831} 26832 26833function weekGetter(size) { 26834 return function(date) { 26835 var firstThurs = getFirstThursdayOfYear(date.getFullYear()), 26836 thisThurs = getThursdayThisWeek(date); 26837 26838 var diff = +thisThurs - +firstThurs, 26839 result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week 26840 26841 return padNumber(result, size); 26842 }; 26843} 26844 26845function ampmGetter(date, formats) { 26846 return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; 26847} 26848 26849function eraGetter(date, formats) { 26850 return date.getFullYear() <= 0 ? formats.ERAS[0] : formats.ERAS[1]; 26851} 26852 26853function longEraGetter(date, formats) { 26854 return date.getFullYear() <= 0 ? formats.ERANAMES[0] : formats.ERANAMES[1]; 26855} 26856 26857var DATE_FORMATS = { 26858 yyyy: dateGetter('FullYear', 4, 0, false, true), 26859 yy: dateGetter('FullYear', 2, 0, true, true), 26860 y: dateGetter('FullYear', 1, 0, false, true), 26861 MMMM: dateStrGetter('Month'), 26862 MMM: dateStrGetter('Month', true), 26863 MM: dateGetter('Month', 2, 1), 26864 M: dateGetter('Month', 1, 1), 26865 LLLL: dateStrGetter('Month', false, true), 26866 dd: dateGetter('Date', 2), 26867 d: dateGetter('Date', 1), 26868 HH: dateGetter('Hours', 2), 26869 H: dateGetter('Hours', 1), 26870 hh: dateGetter('Hours', 2, -12), 26871 h: dateGetter('Hours', 1, -12), 26872 mm: dateGetter('Minutes', 2), 26873 m: dateGetter('Minutes', 1), 26874 ss: dateGetter('Seconds', 2), 26875 s: dateGetter('Seconds', 1), 26876 // while ISO 8601 requires fractions to be prefixed with `.` or `,` 26877 // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions 26878 sss: dateGetter('Milliseconds', 3), 26879 EEEE: dateStrGetter('Day'), 26880 EEE: dateStrGetter('Day', true), 26881 a: ampmGetter, 26882 Z: timeZoneGetter, 26883 ww: weekGetter(2), 26884 w: weekGetter(1), 26885 G: eraGetter, 26886 GG: eraGetter, 26887 GGG: eraGetter, 26888 GGGG: longEraGetter 26889}; 26890 26891var DATE_FORMATS_SPLIT = /((?:[^yMLdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|m+|s+|a|Z|G+|w+))(
26891.*)/, 26892 NUMBER_STRING = /^-?\d+$/; 26893 26894/** 26895 * @ngdoc filter 26896 * @name date 26897 * @kind function 26898 * 26899 * @description 26900 * Formats `date` to a string based on the requested `format`. 26901 * 26902 * `format` string can be composed of the following elements: 26903 * 26904 * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) 26905 * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) 26906 * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) 26907 * * `'MMMM'`: Month in year (January-December) 26908 * * `'MMM'`: Month in year (Jan-Dec) 26909 * * `'MM'`: Month in year, padded (01-12) 26910 * * `'M'`: Month in year (1-12) 26911 * * `'LLLL'`: Stand-alone month in year (January-December) 26912 * * `'dd'`: Day in month, padded (01-31) 26913 * * `'d'`: Day in month (1-31) 26914 * * `'EEEE'`: Day in Week,(Sunday-Saturday) 26915 * * `'EEE'`: Day in Week, (Sun-Sat) 26916 * * `'HH'`: Hour in day, padded (00-23) 26917 * * `'H'`: Hour in day (0-23) 26918 * * `'hh'`: Hour in AM/PM, padded (01-12) 26919 * * `'h'`: Hour in AM/PM, (1-12) 26920 * * `'mm'`: Minute in hour, padded (00-59) 26921 * * `'m'`: Minute in hour (0-59) 26922 * * `'ss'`: Second in minute, padded (00-59) 26923 * * `'s'`: Second in minute (0-59) 26924 * * `'sss'`: Millisecond in second, padded (000-999) 26925 * * `'a'`: AM/PM marker 26926 * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) 26927 * * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year 26928 * * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year 26929 * * `'G'`, `'GG'`, `'GGG'`: The abbreviated form of the era string (e.g. 'AD') 26930 * * `'GGGG'`: The long form of the era string (e.g. 'Anno Domini') 26931 * 26932 * `format` string can also be one of the following predefined 26933 * {@link guide/i18n localizable formats}: 26934 * 26935 * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale 26936 * (e.g. Sep 3, 2010 12:05:08 PM) 26937 * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 PM) 26938 * * `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` for en_US locale 26939 * (e.g. Friday, September 3, 2010) 26940 * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010) 26941 * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010) 26942 * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10) 26943 * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 PM) 26944 * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 PM) 26945 * 26946 * `format` string can contain literal values. These need to be escaped by surrounding with single quotes (e.g. 26947 * `"h 'in the morning'"`). In order to output a single quote, escape it - i.e., two single quotes in a sequence 26948 * (e.g. `"h 'o''clock'"`)
vendor: 6,066 bytes, lines 26948-27112
26948. 26949 * 26950 * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or 26951 * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its 26952 * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is 26953 * specified in the string input, the time is considered to be in the local timezone. 26954 * @param {string=} format Formatting rules (see Description). If not specified, 26955 * `mediumDate` is used. 26956 * @param {string=} timezone Timezone to be used for formatting. It understands UTC/GMT and the 26957 * continental US time zone abbreviations, but for general use, use a time zone offset, for 26958 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian) 26959 * If not specified, the timezone of the browser will be used. 26960 * @returns {string} Formatted string or the input if input is not recognized as date/millis. 26961 * 26962 * @example 26963 <example name="filter-date"> 26964 <file name="index.html"> 26965 <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>: 26966 <span>{{1288323623006 | date:'medium'}}</span><br> 26967 <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>: 26968 <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br> 26969 <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>: 26970 <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br> 26971 <span ng-non-bindable>{{1288323623006 | date:"MM/dd/yyyy 'at' h:mma"}}</span>: 26972 <span>{{'1288323623006' | date:"MM/dd/yyyy 'at' h:mma"}}</span><br> 26973 </file> 26974 <file name="protractor.js" type="protractor"> 26975 it('should format date', function() { 26976 expect(element(by.binding("1288323623006 | date:'medium'")).getText()). 26977 toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/); 26978 expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()). 26979 toMatch(/2010-10-2\d \d{2}:\d{2}:\d{2} (-|\+)?\d{4}/); 26980 expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()). 26981 toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/); 26982 expect(element(by.binding("'1288323623006' | date:\"MM/dd/yyyy 'at' h:mma\"")).getText()). 26983 toMatch(/10\/2\d\/2010 at \d{1,2}:\d{2}(AM|PM)/); 26984 }); 26985 </file> 26986 </example> 26987 */ 26988dateFilter.$inject = ['$locale']; 26989function dateFilter($locale) { 26990 26991 26992 var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; 26993 // 1 2 3 4 5 6 7 8 9 10 11 26994 function jsonStringToDate(string) { 26995 var match; 26996 if ((match = string.match(R_ISO8601_STR))) { 26997 var date = new Date(0), 26998 tzHour = 0, 26999 tzMin = 0, 27000 dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear, 27001 timeSetter = match[8] ? date.setUTCHours : date.setHours; 27002 27003 if (match[9]) { 27004 tzHour = toInt(match[9] + match[10]); 27005 tzMin = toInt(match[9] + match[11]); 27006 } 27007 dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3])); 27008 var h = toInt(match[4] || 0) - tzHour; 27009 var m = toInt(match[5] || 0) - tzMin; 27010 var s = toInt(match[6] || 0); 27011 var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000); 27012 timeSetter.call(date, h, m, s, ms); 27013 return date; 27014 } 27015 return string; 27016 } 27017 27018 27019 return function(date, format, timezone) { 27020 var text = '', 27021 parts = [], 27022 fn, match; 27023 27024 format = format || 'mediumDate'; 27025 format = $locale.DATETIME_FORMATS[format] || format; 27026 if (isString(date)) { 27027 date = NUMBER_STRING.test(date) ? toInt(date) : jsonStringToDate(date); 27028 } 27029 27030 if (isNumber(date)) { 27031 date = new Date(date); 27032 } 27033 27034 if (!isDate(date) || !isFinite(date.getTime())) { 27035 return date; 27036 } 27037 27038 while (format) { 27039 match = DATE_FORMATS_SPLIT.exec(format); 27040 if (match) { 27041 parts = concat(parts, match, 1); 27042 format = parts.pop(); 27043 } else { 27044 parts.push(format); 27045 format = null; 27046 } 27047 } 27048 27049 var dateTimezoneOffset = date.getTimezoneOffset(); 27050 if (timezone) { 27051 dateTimezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset); 27052 date = convertTimezoneToLocal(date, timezone, true); 27053 } 27054 forEach(parts, function(value) { 27055 fn = DATE_FORMATS[value]; 27056 text += fn ? fn(date, $locale.DATETIME_FORMATS, dateTimezoneOffset) 27057 : value === '\'\'' ? '\'' : value.replace(/(^'|'$)/g, '').replace(/''/g, '\''); 27058 }); 27059 27060 return text; 27061 }; 27062} 27063 27064 27065/** 27066 * @ngdoc filter 27067 * @name json 27068 * @kind function 27069 * 27070 * @description 27071 * Allows you to convert a JavaScript object into JSON string. 27072 * 27073 * This filter is mostly useful for debugging. When using the double curly {{value}} notation 27074 * the binding is automatically converted to JSON. 27075 * 27076 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. 27077 * @param {number=} spacing The number of spaces to use per indentation, defaults to 2. 27078 * @returns {string} JSON string. 27079 * 27080 * 27081 * @example 27082 <example name="filter-json"> 27083 <file name="index.html"> 27084 <pre id="default-spacing">{{ {'name':'value'} | json }}</pre> 27085 <pre id="custom-spacing">{{ {'name':'value'} | json:4 }}</pre> 27086 </file> 27087 <file name="protractor.js" type="protractor"> 27088 it('should jsonify filtered objects', function() { 27089 expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n {2}"name": ?"value"\n}/); 27090 expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n {4}"name": ?"value"\n}/); 27091 }); 27092 </file> 27093 </example> 27094 * 27095 */ 27096function jsonFilter() { 27097 return function(object, spacing) { 27098 if (isUndefined(spacing)) { 27099 spacing = 2; 27100 } 27101 return toJson(object, spacing); 27102 }; 27103} 27104 27105 27106/** 27107 * @ngdoc filter 27108 * @name lowercase 27109 * @kind function 27110 * @description 27111 * Converts string to lowercase. 27112 *
27112 @see angular.lowercase 27113 */ 27114var lowercaseFilter = valueFn(lowercase); 27115 27116 27117/** 27118 * @ngdoc filter 27119 * @name uppercase 27120 * @kind function 27121 * @description 27122 * Converts string to uppercase. 27123 * @see angular.uppercase
vendor: 7,356 bytes, lines 27123-27299
27123 27124 */ 27125var uppercaseFilter = valueFn(uppercase); 27126 27127/** 27128 * @ngdoc filter 27129 * @name limitTo 27130 * @kind function 27131 * 27132 * @description 27133 * Creates a new array or string containing only a specified number of elements. The elements are 27134 * taken from either the beginning or the end of the source array, string or number, as specified by 27135 * the value and sign (positive or negative) of `limit`. Other array-like objects are also supported 27136 * (e.g. array subclasses, NodeLists, jqLite/jQuery collections etc). If a number is used as input, 27137 * it is converted to a string. 27138 * 27139 * @param {Array|ArrayLike|string|number} input - Array/array-like, string or number to be limited. 27140 * @param {string|number} limit - The length of the returned array or string. If the `limit` number 27141 * is positive, `limit` number of items from the beginning of the source array/string are copied. 27142 * If the number is negative, `limit` number of items from the end of the source array/string 27143 * are copied. The `limit` will be trimmed if it exceeds `array.length`. If `limit` is undefined, 27144 * the input will be returned unchanged. 27145 * @param {(string|number)=} begin - Index at which to begin limitation. As a negative index, 27146 * `begin` indicates an offset from the end of `input`. Defaults to `0`. 27147 * @returns {Array|string} A new sub-array or substring of length `limit` or less if the input had 27148 * less than `limit` elements. 27149 * 27150 * @example 27151 <example module="limitToExample" name="limit-to-filter"> 27152 <file name="index.html"> 27153 <script> 27154 angular.module('limitToExample', []) 27155 .controller('ExampleController', ['$scope', function($scope) { 27156 $scope.numbers = [1,2,3,4,5,6,7,8,9]; 27157 $scope.letters = "abcdefghi"; 27158 $scope.longNumber = 2345432342; 27159 $scope.numLimit = 3; 27160 $scope.letterLimit = 3; 27161 $scope.longNumberLimit = 3; 27162 }]); 27163 </script> 27164 <div ng-controller="ExampleController"> 27165 <label> 27166 Limit {{numbers}} to: 27167 <input type="number" step="1" ng-model="numLimit"> 27168 </label> 27169 <p>Output numbers: {{ numbers | limitTo:numLimit }}</p> 27170 <label> 27171 Limit {{letters}} to: 27172 <input type="number" step="1" ng-model="letterLimit"> 27173 </label> 27174 <p>Output letters: {{ letters | limitTo:letterLimit }}</p> 27175 <label> 27176 Limit {{longNumber}} to: 27177 <input type="number" step="1" ng-model="longNumberLimit"> 27178 </label> 27179 <p>Output long number: {{ longNumber | limitTo:longNumberLimit }}</p> 27180 </div> 27181 </file> 27182 <file name="protractor.js" type="protractor"> 27183 var numLimitInput = element(by.model('numLimit')); 27184 var letterLimitInput = element(by.model('letterLimit')); 27185 var longNumberLimitInput = element(by.model('longNumberLimit')); 27186 var limitedNumbers = element(by.binding('numbers | limitTo:numLimit')); 27187 var limitedLetters = element(by.binding('letters | limitTo:letterLimit')); 27188 var limitedLongNumber = element(by.binding('longNumber | limitTo:longNumberLimit')); 27189 27190 it('should limit the number array to first three items', function() { 27191 expect(numLimitInput.getAttribute('value')).toBe('3'); 27192 expect(letterLimitInput.getAttribute('value')).toBe('3'); 27193 expect(longNumberLimitInput.getAttribute('value')).toBe('3'); 27194 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]'); 27195 expect(limitedLetters.getText()).toEqual('Output letters: abc'); 27196 expect(limitedLongNumber.getText()).toEqual('Output long number: 234'); 27197 }); 27198 27199 // There is a bug in safari and protractor that doesn't like the minus key 27200 // it('should update the output when -3 is entered', function() { 27201 // numLimitInput.clear(); 27202 // numLimitInput.sendKeys('-3'); 27203 // letterLimitInput.clear(); 27204 // letterLimitInput.sendKeys('-3'); 27205 // longNumberLimitInput.clear(); 27206 // longNumberLimitInput.sendKeys('-3'); 27207 // expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]'); 27208 // expect(limitedLetters.getText()).toEqual('Output letters: ghi'); 27209 // expect(limitedLongNumber.getText()).toEqual('Output long number: 342'); 27210 // }); 27211 27212 it('should not exceed the maximum size of input array', function() { 27213 numLimitInput.clear(); 27214 numLimitInput.sendKeys('100'); 27215 letterLimitInput.clear(); 27216 letterLimitInput.sendKeys('100'); 27217 longNumberLimitInput.clear(); 27218 longNumberLimitInput.sendKeys('100'); 27219 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]'); 27220 expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi'); 27221 expect(limitedLongNumber.getText()).toEqual('Output long number: 2345432342'); 27222 }); 27223 </file> 27224 </example> 27225*/ 27226function limitToFilter() { 27227 return function(input, limit, begin) { 27228 if (Math.abs(Number(limit)) === Infinity) { 27229 limit = Number(limit); 27230 } else { 27231 limit = toInt(limit); 27232 } 27233 if (isNumberNaN(limit)) return input; 27234 27235 if (isNumber(input)) input = input.toString(); 27236 if (!isArrayLike(input)) return input; 27237 27238 begin = (!begin || isNaN(begin)) ? 0 : toInt(begin); 27239 begin = (begin < 0) ? Math.max(0, input.length + begin) : begin; 27240 27241 if (limit >= 0) { 27242 return sliceFn(input, begin, begin + limit); 27243 } else { 27244 if (begin === 0) { 27245 return sliceFn(input, limit, input.length); 27246 } else { 27247 return sliceFn(input, Math.max(0, begin + limit), begin); 27248 } 27249 } 27250 }; 27251} 27252 27253function sliceFn(input, begin, end) { 27254 if (isString(input)) return input.slice(begin, end); 27255 27256 return slice.call(input, begin, end); 27257} 27258 27259/** 27260 * @ngdoc filter 27261 * @name orderBy 27262 * @kind function 27263 * 27264 * @description 27265 * Returns an array containing the items from the specified `collection`, ordered by a `comparator` 27266 * function based on the values computed using the `expression` predicate. 27267 * 27268 * For example, `[{id: 'foo'}, {id: 'bar'}] | orderBy:'id'` would result in 27269 * `[{id: 'bar'}, {id: 'foo'}]`. 27270 * 27271 * The `collection` can be an Array or array-like object (e.g. NodeList, jQuery object, TypedArray, 27272 * String, etc). 27273 * 27274 * The `expression` can be a single predicate, or a list of predicates each serving as a tie-breaker 27275 * for the preceding one. The `expression` is evaluated against each item and the output is used 27276 * for comparing with other items. 27277 * 27278 * You can change the sorting order by setting `reverse` to `true`. By default, items are sorted in 27279 * ascending order. 27280 * 27281 * The comparison is done using the `comparator` function. If none is specified, a default, built-in 27282 * comparator is used (see below for details - in a nutshell, it compares numbers numerically and 27283 * strings alphabetically). 27284 * 27285 * ### Under the hood 27286 * 27287 * Ordering the specified `collection` happens in two phases: 27288 * 27289 * 1. All items are passed through the predicate (or predicates), and the returned values are saved 27290 * along with their type (`string`, `number` etc). For example, an item `{label: 'foo'}`, passed 27291 * through a predicate that extracts the value of the `label` property, would be transformed to: 27292 * ``` 27293 * { 27294 * value: 'foo', 27295 * type: 'string', 27296 * index: ... 27297 * } 27298 * ``` 27299 *
272992. The comparator function is used to sort the items, based on the derived values, types and 27300 * indices. 27301 * 27302 * If you use a custom comparator, it will be called with pairs of objects of the form 27303 * `{value: ..., type: '...', index: ...}` and is expected to return `0` if the objects are equal 27304 * (as far as the comparator is concerned), `-1` if the 1st one should be ranked higher than the 27305 * second, or `1` otherwise. 27306 * 27307 * In order to ensure that the sorting will be deterministic across platforms, if none of the 27308 * specified predicates can distinguish between two items, `orderBy` will automatically introduce a 27309 * dummy predicate that returns the item's index as `value`. 27310 * (If you are using a custom comparator, make sure it can handle this predicate as well.) 27311 * 27312 * Finally, in an attempt to simplify things, if a predicate returns an object as the extracted 27313 * value for an item, `orderBy` will try to convert that object to a primitive value, before passing 27314 * it to the comparator. The following rules govern the conversion: 27315 * 27316 * 1. If the object has a `valueOf()` method that returns a primitive, its return value will be 27317 * used instead.<br /> 27318 * (If the object has a `valueOf()` method that returns another object, then the returned object 27319 * will be used in subsequent steps.) 27320 * 2. If the object has a custom `toString()` method (i.e. not the one inherited from `Object`) that 27321 * returns a primitive, its return value will be used instead.<br /> 27322 * (If the object has a `toString()` method that returns another object, then the returned object 27323 * will be used in subsequent steps.) 27324 * 3. No conversion; the object itself is used. 27325 * 27326 * ### The default comparator 27327 * 27328 * The default, built-in comparator should be sufficient for most usecases. In short, it compares 27329 * numbers numerically, strings alphabetically (and case-insensitively), for objects falls back to 27330 * using their index in the original collection, and sorts values of different types by type. 27331 * 27332 * More specifically, it follows these steps to determine the relative order of items: 27333 * 27334 * 1. If the compared values are of different types, compare the types themselves alphabetically. 27335 * 2. If both values are of type `string`, compare them alphabetically in a case- and 27336 * locale-insensitive way. 27337 * 3. If both values are objects, compare their indices instead. 27338 * 4. Otherwise, return: 27339 * - `0`, if the values are equal (by strict equality comparison, i.e. using `===`). 27340 * - `-1`, if the 1st value is "less than" the 2nd value (compared using the `<` operator). 27341 * - `1`, otherwise. 27342 * 27343 * **Note:** If you notice numbers not being sorted as expected, make sure they are actually being 27344 * saved as numbers and not strings. 27345 * **Note:** For the purpose of sorting, `null` values are treated as the string `'null'` (i.e. 27346 * `type: 'string'`, `value: 'null'`). This may cause unexpected sort order relative to 27347 * other values. 27348 * 27349 * @param {Array|ArrayLike} collection - The collection (array or array-like object) to sort. 27350 * @param {(Function|string|Array.<Function|string>)=} expression - A predicate (or list of 27351 * predicates) to be used by the comparator to determine the order of elements. 27352 * 27353 * Can be one of: 27354 * 27355 * - `Function`: A getter function. This function will be called with each item as argument and 27356 * the return value will be used for sorting. 27357 * - `string`: An Angular
vendor: 19,123 bytes, lines 27357-27858
27357 expression. This expression will be evaluated against each item and the 27358 * result will be used for sorting. For example, use `'label'` to sort by a property called 27359 * `label` or `'label.substring(0, 3)'` to sort by the first 3 characters of the `label` 27360 * property.<br /> 27361 * (The result of a constant expression is interpreted as a property name to be used for 27362 * comparison. For example, use `'"special name"'` (note the extra pair of quotes) to sort by a 27363 * property called `special name`.)<br /> 27364 * An expression can be optionally prefixed with `+` or `-` to control the sorting direction, 27365 * ascending or descending. For example, `'+label'` or `'-label'`. If no property is provided, 27366 * (e.g. `'+'` or `'-'`), the collection element itself is used in comparisons. 27367 * - `Array`: An array of function and/or string predicates. If a predicate cannot determine the 27368 * relative order of two items, the next predicate is used as a tie-breaker. 27369 * 27370 * **Note:** If the predicate is missing or empty then it defaults to `'+'`. 27371 * 27372 * @param {boolean=} reverse - If `true`, reverse the sorting order. 27373 * @param {(Function)=} comparator - The comparator function used to determine the relative order of 27374 * value pairs. If omitted, the built-in comparator will be used. 27375 * 27376 * @returns {Array} - The sorted array. 27377 * 27378 * 27379 * @example 27380 * ### Ordering a table with `ngRepeat` 27381 * 27382 * The example below demonstrates a simple {@link ngRepeat ngRepeat}, where the data is sorted by 27383 * age in descending order (expression is set to `'-age'`). The `comparator` is not set, which means 27384 * it defaults to the built-in comparator. 27385 * 27386 <example name="orderBy-static" module="orderByExample1"> 27387 <file name="index.html"> 27388 <div ng-controller="ExampleController"> 27389 <table class="friends"> 27390 <tr> 27391 <th>Name</th> 27392 <th>Phone Number</th> 27393 <th>Age</th> 27394 </tr> 27395 <tr ng-repeat="friend in friends | orderBy:'-age'"> 27396 <td>{{friend.name}}</td> 27397 <td>{{friend.phone}}</td> 27398 <td>{{friend.age}}</td> 27399 </tr> 27400 </table> 27401 </div> 27402 </file> 27403 <file name="script.js"> 27404 angular.module('orderByExample1', []) 27405 .controller('ExampleController', ['$scope', function($scope) { 27406 $scope.friends = [ 27407 {name: 'John', phone: '555-1212', age: 10}, 27408 {name: 'Mary', phone: '555-9876', age: 19}, 27409 {name: 'Mike', phone: '555-4321', age: 21}, 27410 {name: 'Adam', phone: '555-5678', age: 35}, 27411 {name: 'Julie', phone: '555-8765', age: 29} 27412 ]; 27413 }]); 27414 </file> 27415 <file name="style.css"> 27416 .friends { 27417 border-collapse: collapse; 27418 } 27419 27420 .friends th { 27421 border-bottom: 1px solid; 27422 } 27423 .friends td, .friends th { 27424 border-left: 1px solid; 27425 padding: 5px 10px; 27426 } 27427 .friends td:first-child, .friends th:first-child { 27428 border-left: none; 27429 } 27430 </file> 27431 <file name="protractor.js" type="protractor"> 27432 // Element locators 27433 var names = element.all(by.repeater('friends').column('friend.name')); 27434 27435 it('should sort friends by age in reverse order', function() { 27436 expect(names.get(0).getText()).toBe('Adam'); 27437 expect(names.get(1).getText()).toBe('Julie'); 27438 expect(names.get(2).getText()).toBe('Mike'); 27439 expect(names.get(3).getText()).toBe('Mary'); 27440 expect(names.get(4).getText()).toBe('John'); 27441 }); 27442 </file> 27443 </example> 27444 * <hr /> 27445 * 27446 * @example 27447 * ### Changing parameters dynamically 27448 * 27449 * All parameters can be changed dynamically. The next example shows how you can make the columns of 27450 * a table sortable, by binding the `expression` and `reverse` parameters to scope properties. 27451 * 27452 <example name="orderBy-dynamic" module="orderByExample2"> 27453 <file name="index.html"> 27454 <div ng-controller="ExampleController"> 27455 <pre>Sort by = {{propertyName}}; reverse = {{reverse}}</pre> 27456 <hr/> 27457 <button ng-click="propertyName = null; reverse = false">Set to unsorted</button> 27458 <hr/> 27459 <table class="friends"> 27460 <tr> 27461 <th> 27462 <button ng-click="sortBy('name')">Name</button> 27463 <span class="sortorder" ng-show="propertyName === 'name'" ng-class="{reverse: reverse}"></span> 27464 </th> 27465 <th> 27466 <button ng-click="sortBy('phone')">Phone Number</button> 27467 <span class="sortorder" ng-show="propertyName === 'phone'" ng-class="{reverse: reverse}"></span> 27468 </th> 27469 <th> 27470 <button ng-click="sortBy('age')">Age</button> 27471 <span class="sortorder" ng-show="propertyName === 'age'" ng-class="{reverse: reverse}"></span> 27472 </th> 27473 </tr> 27474 <tr ng-repeat="friend in friends | orderBy:propertyName:reverse"> 27475 <td>{{friend.name}}</td> 27476 <td>{{friend.phone}}</td> 27477 <td>{{friend.age}}</td> 27478 </tr> 27479 </table> 27480 </div> 27481 </file> 27482 <file name="script.js"> 27483 angular.module('orderByExample2', []) 27484 .controller('ExampleController', ['$scope', function($scope) { 27485 var friends = [ 27486 {name: 'John', phone: '555-1212', age: 10}, 27487 {name: 'Mary', phone: '555-9876', age: 19}, 27488 {name: 'Mike', phone: '555-4321', age: 21}, 27489 {name: 'Adam', phone: '555-5678', age: 35}, 27490 {name: 'Julie', phone: '555-8765', age: 29} 27491 ]; 27492 27493 $scope.propertyName = 'age'; 27494 $scope.reverse = true; 27495 $scope.friends = friends; 27496 27497 $scope.sortBy = function(propertyName) { 27498 $scope.reverse = ($scope.propertyName === propertyName) ? !$scope.reverse : false; 27499 $scope.propertyName = propertyName; 27500 }; 27501 }]); 27502 </file> 27503 <file name="style.css"> 27504 .friends { 27505 border-collapse: collapse; 27506 } 27507 27508 .friends th { 27509 border-bottom: 1px solid; 27510 } 27511 .friends td, .friends th { 27512 border-left: 1px solid; 27513 padding: 5px 10px; 27514 } 27515 .friends td:first-child, .friends th:first-child { 27516 border-left: none; 27517 } 27518 27519 .sortorder:after { 27520 content: '\25b2'; // BLACK UP-POINTING TRIANGLE 27521 } 27522 .sortorder.reverse:after { 27523 content: '\25bc'; // BLACK DOWN-POINTING TRIANGLE 27524 } 27525 </file> 27526 <file name="protractor.js" type="protractor"> 27527 // Element locators 27528 var unsortButton = element(by.partialButtonText('unsorted')); 27529 var nameHeader = element(by.partialButtonText('Name')); 27530 var phoneHeader = element(by.partialButtonText('Phone')); 27531 var ageHeader = element(by.partialButtonText('Age')); 27532 var firstName = element(by.repeater('friends').column('friend.name').row(0)); 27533 var lastName = element(by.repeater('friends').column('friend.name').row(4)); 27534 27535 it('should sort friends by some property, when clicking on the column header', function() { 27536 expect(firstName.getText()).toBe('Adam'); 27537 expect(lastName.getText()).toBe('John'); 27538 27539 phoneHeader.click(); 27540 expect(firstName.getText()).toBe('John'); 27541 expect(lastName.getText()).toBe('Mary'); 27542 27543 nameHeader.click(); 27544 expect(firstName.getText()).toBe('Adam'); 27545 expect(lastName.getText()).toBe('Mike'); 27546 27547 ageHeader.click(); 27548 expect(firstName.getText()).toBe('John'); 27549 expect(lastName.getText()).toBe('Adam'); 27550 }); 27551 27552 it('should sort friends in reverse order, when clicking on the same column', function() { 27553 expect(firstName.getText()).toBe('Adam'); 27554 expect(lastName.getText()).toBe('John'); 27555 27556 ageHeader.click(); 27557 expect(firstName.getText()).toBe('John'); 27558 expect(lastName.getText()).toBe('Adam'); 27559 27560 ageHeader.click(); 27561 expect(firstName.getText()).toBe('Adam'); 27562 expect(lastName.getText()).toBe('John'); 27563 }); 27564 27565 it('should restore the original order, when clicking "Set to unsorted"', function() { 27566 expect(firstName.getText()).toBe('Adam'); 27567 expect(lastName.getText()).toBe('John'); 27568 27569 unsortButton.click(); 27570 expect(firstName.getText()).toBe('John'); 27571 expect(lastName.getText()).toBe('Julie'); 27572 }); 27573 </file> 27574 </example> 27575 * <hr /> 27576 * 27577 * @example 27578 * ### Using `orderBy` inside a controller 27579 * 27580 * It is also possible to call the `orderBy` filter manually, by injecting `orderByFilter`, and 27581 * calling it with the desired parameters. (Alternatively, you could inject the `$filter` factory 27582 * and retrieve the `orderBy` filter with `$filter('orderBy')`.) 27583 * 27584 <example name="orderBy-call-manually" module="orderByExample3"> 27585 <file name="index.html"> 27586 <div ng-controller="ExampleController"> 27587 <pre>Sort by = {{propertyName}}; reverse = {{reverse}}</pre> 27588 <hr/> 27589 <button ng-click="sortBy(null)">Set to unsorted</button> 27590 <hr/> 27591 <table class="friends"> 27592 <tr> 27593 <th> 27594 <button ng-click="sortBy('name')">Name</button> 27595 <span class="sortorder" ng-show="propertyName === 'name'" ng-class="{reverse: reverse}"></span> 27596 </th> 27597 <th> 27598 <button ng-click="sortBy('phone')">Phone Number</button> 27599 <span class="sortorder" ng-show="propertyName === 'phone'" ng-class="{reverse: reverse}"></span> 27600 </th> 27601 <th> 27602 <button ng-click="sortBy('age')">Age</button> 27603 <span class="sortorder" ng-show="propertyName === 'age'" ng-class="{reverse: reverse}"></span> 27604 </th> 27605 </tr> 27606 <tr ng-repeat="friend in friends"> 27607 <td>{{friend.name}}</td> 27608 <td>{{friend.phone}}</td> 27609 <td>{{friend.age}}</td> 27610 </tr> 27611 </table> 27612 </div> 27613 </file> 27614 <file name="script.js"> 27615 angular.module('orderByExample3', []) 27616 .controller('ExampleController', ['$scope', 'orderByFilter', function($scope, orderBy) { 27617 var friends = [ 27618 {name: 'John', phone: '555-1212', age: 10}, 27619 {name: 'Mary', phone: '555-9876', age: 19}, 27620 {name: 'Mike', phone: '555-4321', age: 21}, 27621 {name: 'Adam', phone: '555-5678', age: 35}, 27622 {name: 'Julie', phone: '555-8765', age: 29} 27623 ]; 27624 27625 $scope.propertyName = 'age'; 27626 $scope.reverse = true; 27627 $scope.friends = orderBy(friends, $scope.propertyName, $scope.reverse); 27628 27629 $scope.sortBy = function(propertyName) { 27630 $scope.reverse = (propertyName !== null && $scope.propertyName === propertyName) 27631 ? !$scope.reverse : false; 27632 $scope.propertyName = propertyName; 27633 $scope.friends = orderBy(friends, $scope.propertyName, $scope.reverse); 27634 }; 27635 }]); 27636 </file> 27637 <file name="style.css"> 27638 .friends { 27639 border-collapse: collapse; 27640 } 27641 27642 .friends th { 27643 border-bottom: 1px solid; 27644 } 27645 .friends td, .friends th { 27646 border-left: 1px solid; 27647 padding: 5px 10px; 27648 } 27649 .friends td:first-child, .friends th:first-child { 27650 border-left: none; 27651 } 27652 27653 .sortorder:after { 27654 content: '\25b2'; // BLACK UP-POINTING TRIANGLE 27655 } 27656 .sortorder.reverse:after { 27657 content: '\25bc'; // BLACK DOWN-POINTING TRIANGLE 27658 } 27659 </file> 27660 <file name="protractor.js" type="protractor"> 27661 // Element locators 27662 var unsortButton = element(by.partialButtonText('unsorted')); 27663 var nameHeader = element(by.partialButtonText('Name')); 27664 var phoneHeader = element(by.partialButtonText('Phone')); 27665 var ageHeader = element(by.partialButtonText('Age')); 27666 var firstName = element(by.repeater('friends').column('friend.name').row(0)); 27667 var lastName = element(by.repeater('friends').column('friend.name').row(4)); 27668 27669 it('should sort friends by some property, when clicking on the column header', function() { 27670 expect(firstName.getText()).toBe('Adam'); 27671 expect(lastName.getText()).toBe('John'); 27672 27673 phoneHeader.click(); 27674 expect(firstName.getText()).toBe('John'); 27675 expect(lastName.getText()).toBe('Mary'); 27676 27677 nameHeader.click(); 27678 expect(firstName.getText()).toBe('Adam'); 27679 expect(lastName.getText()).toBe('Mike'); 27680 27681 ageHeader.click(); 27682 expect(firstName.getText()).toBe('John'); 27683 expect(lastName.getText()).toBe('Adam'); 27684 }); 27685 27686 it('should sort friends in reverse order, when clicking on the same column', function() { 27687 expect(firstName.getText()).toBe('Adam'); 27688 expect(lastName.getText()).toBe('John'); 27689 27690 ageHeader.click(); 27691 expect(firstName.getText()).toBe('John'); 27692 expect(lastName.getText()).toBe('Adam'); 27693 27694 ageHeader.click(); 27695 expect(firstName.getText()).toBe('Adam'); 27696 expect(lastName.getText()).toBe('John'); 27697 }); 27698 27699 it('should restore the original order, when clicking "Set to unsorted"', function() { 27700 expect(firstName.getText()).toBe('Adam'); 27701 expect(lastName.getText()).toBe('John'); 27702 27703 unsortButton.click(); 27704 expect(firstName.getText()).toBe('John'); 27705 expect(lastName.getText()).toBe('Julie'); 27706 }); 27707 </file> 27708 </example> 27709 * <hr /> 27710 * 27711 * @example 27712 * ### Using a custom comparator 27713 * 27714 * If you have very specific requirements about the way items are sorted, you can pass your own 27715 * comparator function. For example, you might need to compare some strings in a locale-sensitive 27716 * way. (When specifying a custom comparator, you also need to pass a value for the `reverse` 27717 * argument - passing `false` retains the default sorting order, i.e. ascending.) 27718 * 27719 <example name="orderBy-custom-comparator" module="orderByExample4"> 27720 <file name="index.html"> 27721 <div ng-controller="ExampleController"> 27722 <div class="friends-container custom-comparator"> 27723 <h3>Locale-sensitive Comparator</h3> 27724 <table class="friends"> 27725 <tr> 27726 <th>Name</th> 27727 <th>Favorite Letter</th> 27728 </tr> 27729 <tr ng-repeat="friend in friends | orderBy:'favoriteLetter':false:localeSensitiveComparator"> 27730 <td>{{friend.name}}</td> 27731 <td>{{friend.favoriteLetter}}</td> 27732 </tr> 27733 </table> 27734 </div> 27735 <div class="friends-container default-comparator"> 27736 <h3>Default Comparator</h3> 27737 <table class="friends"> 27738 <tr> 27739 <th>Name</th> 27740 <th>Favorite Letter</th> 27741 </tr> 27742 <tr ng-repeat="friend in friends | orderBy:'favoriteLetter'"> 27743 <td>{{friend.name}}</td> 27744 <td>{{friend.favoriteLetter}}</td> 27745 </tr> 27746 </table> 27747 </div> 27748 </div> 27749 </file> 27750 <file name="script.js"> 27751 angular.module('orderByExample4', []) 27752 .controller('ExampleController', ['$scope', function($scope) { 27753 $scope.friends = [ 27754 {name: 'John', favoriteLetter: 'Ã'}, 27755 {name: 'Mary', favoriteLetter: 'Ã'}, 27756 {name: 'Mike', favoriteLetter: 'Ã'}, 27757 {name: 'Adam', favoriteLetter: 'H'}, 27758 {name: 'Julie', favoriteLetter: 'Z'} 27759 ]; 27760 27761 $scope.localeSensitiveComparator = function(v1, v2) { 27762 // If we don't get strings, just compare by index 27763 if (v1.type !== 'string' || v2.type !== 'string') { 27764 return (v1.index < v2.index) ? -1 : 1; 27765 } 27766 27767 // Compare strings alphabetically, taking locale into account 27768 return v1.value.localeCompare(v2.value); 27769 }; 27770 }]); 27771 </file> 27772 <file name="style.css"> 27773 .friends-container { 27774 display: inline-block; 27775 margin: 0 30px; 27776 } 27777 27778 .friends { 27779 border-collapse: collapse; 27780 } 27781 27782 .friends th { 27783 border-bottom: 1px solid; 27784 } 27785 .friends td, .friends th { 27786 border-left: 1px solid; 27787 padding: 5px 10px; 27788 } 27789 .friends td:first-child, .friends th:first-child { 27790 border-left: none; 27791 } 27792 </file> 27793 <file name="protractor.js" type="protractor"> 27794 // Element locators 27795 var container = element(by.css('.custom-comparator')); 27796 var names = container.all(by.repeater('friends').column('friend.name')); 27797 27798 it('should sort friends by favorite letter (in correct alphabetical order)', function() { 27799 expect(names.get(0).getText()).toBe('John'); 27800 expect(names.get(1).getText()).toBe('Adam'); 27801 expect(names.get(2).getText()).toBe('Mike'); 27802 expect(names.get(3).getText()).toBe('Mary'); 27803 expect(names.get(4).getText()).toBe('Julie'); 27804 }); 27805 </file> 27806 </example> 27807 * 27808 */ 27809orderByFilter.$inject = ['$parse']; 27810function orderByFilter($parse) { 27811 return function(array, sortPredicate, reverseOrder, compareFn) { 27812 27813 if (array == null) return array; 27814 if (!isArrayLike(array)) { 27815 throw minErr('orderBy')('notarray', 'Expected array but received: {0}', array); 27816 } 27817 27818 if (!isArray(sortPredicate)) { sortPredicate = [sortPredicate]; } 27819 if (sortPredicate.length === 0) { sortPredicate = ['+']; } 27820 27821 var predicates = processPredicates(sortPredicate); 27822 27823 var descending = reverseOrder ? -1 : 1; 27824 27825 // Define the `compare()` function. Use a default comparator if none is specified. 27826 var compare = isFunction(compareFn) ? compareFn : defaultCompare; 27827 27828 // The next three lines are a version of a Swartzian Transform idiom from Perl 27829 // (sometimes called the Decorate-Sort-Undecorate idiom) 27830 // See https://en.wikipedia.org/wiki/Schwartzian_transform 27831 var compareValues = Array.prototype.map.call(array, getComparisonObject); 27832 compareValues.sort(doComparison); 27833 array = compareValues.map(function(item) { return item.value; }); 27834 27835 return array; 27836 27837 function getComparisonObject(value, index) { 27838 // NOTE: We are adding an extra `tieBreaker` value based on the element's index. 27839 // This will be used to keep the sort stable when none of the input predicates can 27840 // distinguish between two elements. 27841 return { 27842 value: value, 27843 tieBreaker: {value: index, type: 'number', index: index}, 27844 predicateValues: predicates.map(function(predicate) { 27845 return getPredicateValue(predicate.get(value), index); 27846 }) 27847 }; 27848 } 27849 27850 function doComparison(v1, v2) { 27851 for (var i = 0, ii = predicates.length; i < ii; i++) { 27852 var result = compare(v1.predicateValues[i], v2.predicateValues[i]); 27853 if (result) { 27854 return result * predicates[i].descending * descending; 27855 } 27856 } 27857 27858 return
vendor: 5,453 bytes, lines 27858-28037
27858compare(v1.tieBreaker, v2.tieBreaker) * descending; 27859 } 27860 }; 27861 27862 function processPredicates(sortPredicates) { 27863 return sortPredicates.map(function(predicate) { 27864 var descending = 1, get = identity; 27865 27866 if (isFunction(predicate)) { 27867 get = predicate; 27868 } else if (isString(predicate)) { 27869 if ((predicate.charAt(0) === '+' || predicate.charAt(0) === '-')) { 27870 descending = predicate.charAt(0) === '-' ? -1 : 1; 27871 predicate = predicate.substring(1); 27872 } 27873 if (predicate !== '') { 27874 get = $parse(predicate); 27875 if (get.constant) { 27876 var key = get(); 27877 get = function(value) { return value[key]; }; 27878 } 27879 } 27880 } 27881 return {get: get, descending: descending}; 27882 }); 27883 } 27884 27885 function isPrimitive(value) { 27886 switch (typeof value) { 27887 case 'number': /* falls through */ 27888 case 'boolean': /* falls through */ 27889 case 'string': 27890 return true; 27891 default: 27892 return false; 27893 } 27894 } 27895 27896 function objectValue(value) { 27897 // If `valueOf` is a valid function use that 27898 if (isFunction(value.valueOf)) { 27899 value = value.valueOf(); 27900 if (isPrimitive(value)) return value; 27901 } 27902 // If `toString` is a valid function and not the one from `Object.prototype` use that 27903 if (hasCustomToString(value)) { 27904 value = value.toString(); 27905 if (isPrimitive(value)) return value; 27906 } 27907 27908 return value; 27909 } 27910 27911 function getPredicateValue(value, index) { 27912 var type = typeof value; 27913 if (value === null) { 27914 type = 'string'; 27915 value = 'null'; 27916 } else if (type === 'object') { 27917 value = objectValue(value); 27918 } 27919 return {value: value, type: type, index: index}; 27920 } 27921 27922 function defaultCompare(v1, v2) { 27923 var result = 0; 27924 var type1 = v1.type; 27925 var type2 = v2.type; 27926 27927 if (type1 === type2) { 27928 var value1 = v1.value; 27929 var value2 = v2.value; 27930 27931 if (type1 === 'string') { 27932 // Compare strings case-insensitively 27933 value1 = value1.toLowerCase(); 27934 value2 = value2.toLowerCase(); 27935 } else if (type1 === 'object') { 27936 // For basic objects, use the position of the object 27937 // in the collection instead of the value 27938 if (isObject(value1)) value1 = v1.index; 27939 if (isObject(value2)) value2 = v2.index; 27940 } 27941 27942 if (value1 !== value2) { 27943 result = value1 < value2 ? -1 : 1; 27944 } 27945 } else { 27946 result = type1 < type2 ? -1 : 1; 27947 } 27948 27949 return result; 27950 } 27951} 27952 27953function ngDirective(directive) { 27954 if (isFunction(directive)) { 27955 directive = { 27956 link: directive 27957 }; 27958 } 27959 directive.restrict = directive.restrict || 'AC'; 27960 return valueFn(directive); 27961} 27962 27963/** 27964 * @ngdoc directive 27965 * @name a 27966 * @restrict E 27967 * 27968 * @description 27969 * Modifies the default behavior of the html a tag so that the default action is prevented when 27970 * the href attribute is empty. 27971 * 27972 * For dynamically creating `href` attributes for a tags, see the {@link ng.ngHref `ngHref`} directive. 27973 */ 27974var htmlAnchorDirective = valueFn({ 27975 restrict: 'E', 27976 compile: function(element, attr) { 27977 if (!attr.href && !attr.xlinkHref) { 27978 return function(scope, element) { 27979 // If the linked element is not an anchor tag anymore, do nothing 27980 if (element[0].nodeName.toLowerCase() !== 'a') return; 27981 27982 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. 27983 var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? 27984 'xlink:href' : 'href'; 27985 element.on('click', function(event) { 27986 // if we have no href url, then don't navigate anywhere. 27987 if (!element.attr(href)) { 27988 event.preventDefault(); 27989 } 27990 }); 27991 }; 27992 } 27993 } 27994}); 27995 27996/** 27997 * @ngdoc directive 27998 * @name ngHref 27999 * @restrict A 28000 * @priority 99 28001 * 28002 * @description 28003 * Using Angular markup like `{{hash}}` in an href attribute will 28004 * make the link go to the wrong URL if the user clicks it before 28005 * Angular has a chance to replace the `{{hash}}` markup with its 28006 * value. Until Angular replaces the markup the link will be broken 28007 * and will most likely return a 404 error. The `ngHref` directive 28008 * solves this problem. 28009 * 28010 * The wrong way to write it: 28011 * ```html 28012 * <a href="http://www.gravatar.com/avatar/{{hash}}">link1</a> 28013 * ``` 28014 * 28015 * The correct way to write it: 28016 * ```html 28017 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}">link1</a> 28018 * ``` 28019 * 28020 * @element A 28021 * @param {template} ngHref any string which can contain `{{}}` markup. 28022 * 28023 * @example 28024 * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes 28025 * in links and their different behaviors: 28026 <example name="ng-href"> 28027 <file name="index.html"> 28028 <input ng-model="value" /><br /> 28029 <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br /> 28030 <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br /> 28031 <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br /> 28032 <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br /> 28033 <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br /> 28034 <a id="link-6" ng-href="{{value}}">link</a> (link, change location) 28035 </file> 28036 <file name="protractor.js" type="protractor"> 28037 it('should execute ng-click but not reload when href without value', function() {
28038 element(by.id('link-1')).click(); 28039 expect(element(by.model('value')).getAttribute('value')).toEqual('1'); 28040 expect(element(by.id('link-1')).getAttribute('href')).toBe(''); 28041 }); 28042 28043 it('should execute ng-click but not reload when href empty string', function() { 28044 element(by.id('link-2')).click(); 28045 expect(element(by.model('value')).getAttribute('value')).toEqual('2'); 28046 expect(element(by.id('link-2')).getAttribute('href')).toBe(''); 28047 }); 28048 28049 it('should execute ng-click and change url when ng-href specified', function() { 28050 expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/); 28051 28052 element(by.id('link-3')).click(); 28053 28054 // At this point, we navigate away from an Angular page, so we need 28055 // to use browser.driver to get the base webdriver. 28056 28057 browser.wait(function() { 28058 return browser.driver.getCurrentUrl().then(function(url) { 28059 return url.match(/\/123$/); 28060 }); 28061 }, 5000, 'page should navigate to /123'); 28062 }); 28063 28064 it('should execute ng-click but not reload when href empty string and name specified', function() { 28065 element(by.id('link-4')).click(); 28066 expect(element(by.model('value')).getAttribute('value')).toEqual('4'); 28067 expect(element(by.id('link-4')).getAttribute('href')).toBe(''); 28068 }); 28069 28070 it('should execute ng-click but not reload when no href but name specified', function() { 28071 element(by.id('link-5')).click(); 28072 expect(element(by.model('value')).getAttribute('value')).toEqual('5'); 28073 expect(element(by.id('link-5')).getAttribute('href')).toBe(null); 28074 }); 28075 28076 it('should only change url when only ng-href', function() { 28077 element(by.model('value')).clear(); 28078 element(by.model('value')).sendKeys('6'); 28079 expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/); 28080 28081 element(by.id('link-6')).click(); 28082 28083 // At this point, we navigate away from an Angular page, so we need 28084 // to use browser.driver to get the base webdriver. 28085 browser.wait(function() { 28086 return browser.driver.getCurrentUrl().then(function(url) { 28087 return url.match(/\/6$/); 28088 }); 28089 }, 5000, 'page should navigate to /6'); 28090 }); 28091 </file> 28092 </example> 28093 */ 28094 28095/** 28096 * @ngdoc directive 28097 * @name ngSrc 28098 * @restrict A 28099 * @priority 99 28100 * 28101 * @description 28102 * Using Angular markup like `{{hash}}` in a `src` attribute doesn't 28103 * work right: The browser will fetch from the URL with the literal 28104 * text `{{hash}}` until Angular replaces the expression inside 28105 * `{{hash}}`. The `ngSrc` directive solves this problem. 28106 * 28107 * The buggy way to write it: 28108 * ```html 28109 * <img src="http://www.gravatar.com/avatar/{{hash}}" alt="Description"/> 28110 * ``` 28111 * 28112 * The correct way to write it: 28113 * ```html 28114 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}" alt="Description" /> 28115 * ``` 28116 * 28117 * @element IMG 28118 * @param {template} ngSrc any string which can contain `{{}}` markup. 28119 */ 28120 28121/** 28122 * @ngdoc directive 28123 * @name ngSrcset 28124 * @restrict A 28125 * @priority 99 28126 * 28127 * @description 28128 * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't 28129 * work right: The browser will fetch from the URL with the literal 28130 * text `{{hash}}` until Angular replaces the expression inside 28131 * `{{hash}}`. The `ngSrcset` directive solves this problem. 28132 * 28133 * The buggy way to write it: 28134 * ```html 28135 * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description"/> 28136 * ``` 28137 * 28138 * The correct way to write it: 28139 * ```html 28140 * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description" /> 28141 * ``` 28142 * 28143 * @element IMG 28144 * @param {template} ngSrcset any string which can contain `{{}}` markup. 28145 */ 28146 28147/** 28148 * @ngdoc directive 28149 * @name ngDisabled 28150 * @restrict A 28151 * @priority 100 28152 * 28153 * @description 28154 * 28155 * This directive sets the `disabled` attribute on the element if the 28156 * {@link guide/expression expression} inside `ngDisabled` evaluates to truthy. 28157 * 28158 * A special directive is necessary because we cannot use interpolation inside the `disabled` 28159 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 28160 * 28161 * @example 28162 <example name="ng-disabled"> 28163 <file name="index.html"> 28164 <label>Click me to toggle: <input type="checkbox" ng-model="checked"></label><br/> 28165 <button ng-model="button" ng-disabled="checked">Button</button> 28166 </file> 28167 <file name="protractor.js" type="protractor"> 28168 it('should toggle button', function() { 28169 expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy();
28170 element(by.model('checked')).click(); 28171 expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy(); 28172 }); 28173 </file> 28174 </example> 28175 * 28176 * @element INPUT 28177 * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, 28178 * then the `disabled` attribute will be set on the element 28179 */ 28180 28181 28182/** 28183 * @ngdoc directive 28184 * @name ngChecked 28185 * @restrict A 28186 * @priority 100 28187 * 28188 * @description 28189 * Sets the `checked` attribute on the element, if the expression inside `ngChecked` is truthy. 28190 * 28191 * Note that this directive should not be used together with {@link ngModel `ngModel`}, 28192 * as this can lead to unexpected behavior. 28193 * 28194 * A special directive is necessary because we cannot use interpolation inside the `checked` 28195 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 28196 * 28197 * @example 28198 <example name="ng-checked"> 28199 <file name="index.html"> 28200 <label>Check me to check both: <input type="checkbox" ng-model="master"></label><br/> 28201 <input id="checkSlave" type="checkbox" ng-checked="master" aria-label="Slave input"> 28202 </file> 28203 <file name="protractor.js" type="protractor"> 28204 it('should check both checkBoxes', function() { 28205 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy(); 28206 element(by.model('master')).click(); 28207 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy(); 28208 }); 28209 </file> 28210 </example> 28211 * 28212 * @element INPUT 28213 * @param {expression} ngChecked If the {@link guide/expression expression} is truthy, 28214 * then the `checked` attribute will be set on the element 28215 */ 28216 28217 28218/** 28219 * @ngdoc directive 28220 * @name ngReadonly 28221 * @restrict A 28222 * @priority 100 28223 * 28224 * @description 28225 * 28226 * Sets the `readonly` attribute on the element, if the expression inside `ngReadonly` is truthy. 28227 * Note that `readonly` applies only to `input` elements with specific types. [See the input docs on 28228 * MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-readonly) for more information. 28229 * 28230 * A special directive is necessary because we cannot use interpolation inside the `readonly` 28231 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 28232 * 28233 * @example 28234 <example name="ng-readonly"> 28235 <file name="index.html"> 28236 <label>Check me to make text readonly: <input type="checkbox" ng-model="checked"></label><br/> 28237 <input type="text" ng-readonly="checked" value="I'm Angular" aria-label="Readonly field" /> 28238 </file> 28239 <file name="protractor.js" type="protractor"> 28240 it('should toggle readonly attr', function() { 28241 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy(); 28242 element(by.model('checked')).click(); 28243 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy(); 28244 }); 28245 </file> 28246 </example> 28247 * 28248 * @element INPUT 28249 * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy, 28250 * then special attribute "readonly" will be set on the element 28251 */ 28252 28253 28254/** 28255 * @ngdoc directive 28256 * @name ngSelected 28257 * @restrict A 28258 * @priority 100 28259 * 28260 * @description 28261 * 28262 * Sets the `selected` attribute on the element, if the expression inside `ngSelected` is truthy. 28263 * 28264 * A special directive is necessary because we cannot use interpolation inside the `selected` 28265 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 28266 * 28267 * <div class="alert alert-warning"> 28268 * **Note:** `ngSelected` does not interact with the `select` and `ngModel` directives, it only 28269 * sets the `selected` attribute on the element. If you are using `ngModel` on the select, you 28270 * should not use `ngSelected` on the options, as `ngModel` will set the select value and 28271 * selected options. 28272 * </div> 28273 * 28274 * @example 28275 <example name="ng-selected"> 28276 <file name="index.html"> 28277 <label>Check me to select: <input type="checkbox" ng-model="selected"></label><br/> 28278 <select aria-label="ngSelected demo"> 28279 <option>Hello!</option> 28280 <option id="greet" ng-selected="selected">Greetings!</option> 28281 </select> 28282 </file> 28283 <file name="protractor.js" type="protractor"> 28284 it('should select Greetings!', function() { 28285 expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy();
28286 element(by.model('selected')).click(); 28287 expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy(); 28288 }); 28289 </file> 28290 </example> 28291 * 28292 * @element OPTION 28293 * @param {expression} ngSelected If the {@link guide/expression expression} is truthy, 28294 * then special attribute "selected" will be set on the element 28295 */ 28296 28297/** 28298 * @ngdoc directive 28299 * @name ngOpen 28300 * @restrict A 28301 * @priority 100 28302 * 28303 * @description 28304 * 28305 * Sets the `open` attribute on the element, if the expression inside `ngOpen` is truthy. 28306 * 28307 * A special directive is necessary because we cannot use interpolation inside the `open` 28308 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 28309 * 28310 * ## A note about browser compatibility 28311 * 28312 * Edge, Firefox, and Internet Explorer do not support the `details` element, it is 28313 * recommended to use {@link ng.ngShow} and {@link ng.ngHide} instead. 28314 * 28315 * @example 28316 <example name="ng-open"> 28317 <file name="index.html"> 28318 <label>Check me check multiple: <input type="checkbox" ng-model="open"></label><br/> 28319 <details id="details" ng-open="open"> 28320 <summary>Show/Hide me</summary> 28321 </details> 28322 </file> 28323 <file name="protractor.js" type="protractor"> 28324 it('should toggle open', function() { 28325 expect(element(by.id('details')).getAttribute('open')).toBeFalsy(); 28326 element(by.model('open')).click(); 28327 expect(element(by.id('details')).getAttribute('open')).toBeTruthy(); 28328 }); 28329 </file> 28330 </example> 28331 * 28332 * @element DETAILS 28333 * @param {expression} ngOpen If the {@link guide/expression expression} is truthy, 28334 * then special attribute "open" will be set on the element 28335 */ 28336 28337var ngAttributeAliasDirectives = {}; 28338 28339// boolean attrs are evaluated 28340forEach(BOOLEAN_ATTR, function(propName, attrName) { 28341 // binding to multiple is not supported 28342 if (propName === 'multiple') return; 28343 28344 function defaultLinkFn(scope, element, attr) { 28345 scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { 28346 attr.$set(attrName, !!value); 28347 }); 28348 } 28349 28350 var normalized = directiveNormalize('ng-' + attrName); 28351 var linkFn = defaultLinkFn; 28352 28353 if (propName === 'checked') { 28354 linkFn = function(scope, element, attr) { 28355 // ensuring ngChecked doesn't interfere with ngModel when both are set on the same input 28356 if (attr.ngModel !== attr[normalized]) { 28357 defaultLinkFn(scope, element, attr); 28358 } 28359 }; 28360 } 28361 28362 ngAttributeAliasDirectives[normalized] = function() { 28363 return { 28364 restrict: 'A', 28365 priority: 100, 28366 link: linkFn 28367 }; 28368 }; 28369}); 28370 28371// aliased input attrs are evaluated 28372forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) { 28373 ngAttributeAliasDirectives[ngAttr] = function() { 28374 return { 28375 priority: 100, 28376 link: function(scope, element, attr) { 28377 //special case ngPattern when a literal regular expression value 28378 //is used as the expression (this way we don't have to watch anything). 28379 if (ngAttr === 'ngPattern' && attr.ngPattern.charAt(0) === '/') { 28380 var match = attr.ngPattern.match(REGEX_STRING_REGEXP); 28381 if (match) { 28382 attr.$set('ngPattern', new RegExp(match[1], match[2])); 28383 return; 28384 } 28385 } 28386 28387 scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) { 28388 attr.$set(ngAttr, value); 28389 }); 28390 } 28391 }; 28392 }; 28393}); 28394 28395// ng-src, ng-srcset, ng-href are interpolated 28396forEach(['src', 'srcset', 'href'], function(attrName) { 28397 var normalized = directiveNormalize('ng-' + attrName); 28398 ngAttributeAliasDirectives[normalized] = function() { 28399 return { 28400 priority: 99, // it needs to run after the attributes are interpolated 28401 link: function(scope, element, attr) { 28402 var propName = attrName, 28403 name = attrName; 28404 28405 if (attrName === 'href' && 28406 toString.call(element.prop('href')) === '[object SVGAnimatedString]') { 28407 name = 'xlinkHref'; 28408 attr.$attr[name] = 'xlink:href'; 28409 propName = null; 28410 } 28411 28412 attr.$observe(normalized, function(value) { 28413 if (!value) { 28414 if (attrName === 'href') { 28415 attr.$set(name, null); 28416 } 28417 return; 28418 } 28419 28420 attr.$set(name, value); 28421 28422 // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist 28423 // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need 28424 // to set the property as well to achieve the desired effect. 28425 // we use attr[attrName] value since $set can sanitize the url. 28426 if (msie && propName) element.prop(propName, attr[name]); 28427 }); 28428 } 28429 }; 28430 }; 28431}); 28432 28433/* global -nullFormCtrl, -SUBMITTED_CLASS, addSetValidityMethod: true 28434 */ 28435var nullFormCtrl = { 28436 $addControl: noop, 28437 $$renameControl: nullFormRenameControl, 28438 $removeControl: noop, 28439 $setValidity: noop, 28440 $setDirty: noop, 28441 $setPristine: noop, 28442 $setSubmitted: noop 28443}, 28444SUBMITTED_CLASS = 'ng-submitted'; 28445 28446function nullFormRenameControl(control, name) { 28447 control.$name = name; 28448} 28449
28450/** 28451 * @ngdoc type 28452 * @name form.FormController 28453 * 28454 * @property {boolean} $pristine True if user has not interacted with the form yet. 28455 * @property {boolean} $dirty True if user has already interacted with the form. 28456 * @property {boolean} $valid True if all of the containing forms and controls are valid. 28457 * @property {boolean} $invalid True if at least one containing control or form is invalid. 28458 * @property {boolean} $pending True if at least one containing control or form is pending. 28459 * @property {boolean} $submitted True if user has submitted the form even if its invalid. 28460 * 28461 * @property {Object} $error Is an object hash, containing references to controls or 28462 * forms with failing validators, where: 28463 * 28464 * - keys are validation tokens (error names), 28465 * - values are arrays of controls or forms that have a failing validator for given error name. 28466 * 28467 * Built-in validation tokens: 28468 * 28469 * - `email` 28470 * - `max` 28471 * - `maxlength` 28472 * - `min` 28473 * - `minlength` 28474 * - `number` 28475 * - `pattern` 28476 * - `required` 28477 * - `url` 28478 * - `date` 28479 * - `datetimelocal` 28480 * - `time` 28481 * - `week` 28482 * - `month` 28483 * 28484 * @description 28485 * `FormController` keeps track of all its controls and nested forms as well as the state of them, 28486 * such as being valid/invalid or dirty/pristine. 28487 * 28488 * Each {@link ng.directive:form form} directive creates an instance 28489 * of `FormController`. 28490 * 28491 */ 28492//asks for $scope to fool the BC controller module 28493FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate']; 28494function FormController(element, attrs, $scope, $animate, $interpolate) { 28495 var form = this, 28496 controls = []; 28497 28498 // init state 28499 form.$error = {}; 28500 form.$$success = {}; 28501 form.$pending = undefined; 28502 form.$name = $interpolate(attrs.name || attrs.ngForm || '')($scope); 28503 form.$dirty = false; 28504 form.$pristine = true; 28505 form.$valid = true; 28506 form.$invalid = false; 28507 form.$submitted = false; 28508 form.$$parentForm = nullFormCtrl; 28509 28510 /** 28511 * @ngdoc method 28512 * @name form.FormController#$rollbackViewValue 28513 * 28514 * @description 28515 * Rollback all form controls pending updates to the `$modelValue`. 28516 * 28517 * Updates may be pending by a debounced event or because the input is waiting for a some future 28518 * event defined in `ng-model-options`. This method is typically needed by the reset button of 28519 * a form that uses `ng-model-options` to pend updates. 28520 */ 28521 form.$rollbackViewValue = function() { 28522 forEach(controls, function(control) { 28523 control.$rollbackViewValue(); 28524 }); 28525 }; 28526 28527 /** 28528 * @ngdoc method 28529 * @name form.FormController#$commitViewValue 28530 * 28531 * @description 28532 * Commit all form controls pending updates to the `$modelValue`. 28533 * 28534 * Updates may be pending by a debounced event or because the input is waiting for a some future 28535 * event defined in `ng-model-options`. This method is rarely needed as `NgModelController` 28536 * usually handles calling this in response to input events. 28537 */ 28538 form.$commitViewValue = function() { 28539 forEach(controls, function(control) { 28540 control.$commitViewValue(); 28541 }); 28542 }; 28543 28544 /** 28545 * @ngdoc method 28546 * @name form.FormController#$addControl 28547 * @param {object} control control object, either a {@link form.FormController} or an 28548 * {@link ngModel.NgModelController} 28549 * 28550 * @description 28551 * Register a control with the form. Input elements using ngModelController do this automatically 28552 * when they are linked. 28553 * 28554 * Note that the current state of the control will not be reflected on the new parent form. This 28555 * is not an issue with normal use, as freshly compiled and linked controls are in a `$pristine` 28556 * state. 28557 * 28558 * However, if the method is used programmatically, for example by adding dynamically created controls, 28559 * or controls that have been previously removed without destroying their corresponding DOM element, 28560 * it's the developers responsibility to make sure the current state propagates to the parent form. 28561 * 28562 * For example, if an input control is added that is already `$dirty` and has `$error` properties, 28563 * calling `$setDirty()` and `$validate()` afterwards will propagate the state to the parent form. 28564 */ 28565 form.$addControl = function(control) { 28566 // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored 28567 // and not added to the scope. Now we throw an error. 28568 assertNotHasOwnProperty(control.$name, 'input'); 28569 controls.push(control); 28570 28571 if (control.$name) { 28572 form[control.$name] = control; 28573 } 28574 28575 control.$$parentForm = form; 28576 }; 28577 28578 // Private API: rename a form control 28579 form.$$renameControl = function(control, newName) { 28580 var oldName = control.$name; 28581 28582 if (form[oldName] === control) { 28583 delete form[oldName]; 28584 } 28585 form[newName] = control; 28586 control.$name = newName; 28587 }; 28588 28589 /** 28590 * @ngdoc method
28591 * @name form.FormController#$removeControl 28592 * @param {object} control control object, either a {@link form.FormController} or an 28593 * {@link ngModel.NgModelController} 28594 * 28595 * @description 28596 * Deregister a control from the form. 28597 * 28598 * Input elements using ngModelController do this automatically when they are destroyed. 28599 * 28600 * Note that only the removed control's validation state (`$errors`etc.) will be removed from the 28601 * form. `$dirty`, `$submitted` states will not be changed, because the expected behavior can be 28602 * different from case to case. For example, removing the only `$dirty` control from a form may or 28603 * may not mean that the form is still `$dirty`. 28604 */ 28605 form.$removeControl = function(control) { 28606 if (control.$name && form[control.$name] === control) { 28607 delete form[control.$name]; 28608 } 28609 forEach(form.$pending, function(value, name) { 28610 form.$setValidity(name, null, control); 28611 }); 28612 forEach(form.$error, function(value, name) { 28613 form.$setValidity(name, null, control); 28614 }); 28615 forEach(form.$$success, function(value, name) { 28616 form.$setValidity(name, null, control); 28617 }); 28618 28619 arrayRemove(controls, control); 28620 control.$$parentForm = nullFormCtrl; 28621 }; 28622 28623 28624 /** 28625 * @ngdoc method 28626 * @name form.FormController#$setValidity 28627 * 28628 * @description 28629 * Sets the validity of a form control. 28630 * 28631 * This method will also propagate to parent forms. 28632 */ 28633 addSetValidityMethod({ 28634 ctrl: this, 28635 $element: element, 28636 set: function(object, property, controller) { 28637 var list = object[property]; 28638 if (!list) { 28639 object[property] = [controller]; 28640 } else { 28641 var index = list.indexOf(controller); 28642 if (index === -1) { 28643 list.push(controller); 28644 } 28645 } 28646 }, 28647 unset: function(object, property, controller) { 28648 var list = object[property]; 28649 if (!list) { 28650 return; 28651 } 28652 arrayRemove(list, controller); 28653 if (list.length === 0) { 28654 delete object[property]; 28655 } 28656 }, 28657 $animate: $animate 28658 }); 28659 28660 /** 28661 * @ngdoc method 28662 * @name form.FormController#$setDirty 28663 * 28664 * @description 28665 * Sets the form to a dirty state. 28666 * 28667 * This method can be called to add the 'ng-dirty' class and set the form to a dirty 28668 * state (ng-dirty class). This method will also propagate to parent forms. 28669 */ 28670 form.$setDirty = function() { 28671 $animate.removeClass(element, PRISTINE_CLASS); 28672 $animate.addClass(element, DIRTY_CLASS); 28673 form.$dirty = true; 28674 form.$pristine = false; 28675 form.$$parentForm.$setDirty(); 28676 }; 28677 28678 /** 28679 * @ngdoc method 28680 * @name form.FormController#$setPristine 28681 * 28682 * @description 28683 * Sets the form to its pristine state. 28684 * 28685 * This method sets the form's `$pristine` state to true, the `$dirty` state to false, removes 28686 * the `ng-dirty` class and adds the `ng-pristine` class. Additionally, it sets the `$submitted` 28687 * state to false. 28688 * 28689 * This method will also propagate to all the controls contained in this form. 28690 * 28691 * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after 28692 * saving or resetting it. 28693 */ 28694 form.$setPristine = function() { 28695 $animate.setClass(element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS); 28696 form.$dirty = false; 28697 form.$pristine = true; 28698 form.$submitted = false; 28699 forEach(controls, function(control) { 28700 control.$setPristine(); 28701 }); 28702 }; 28703 28704 /** 28705 * @ngdoc method 28706 * @name form.FormController#$setUntouched 28707 * 28708 * @description 28709 * Sets the form to its untouched state. 28710 * 28711 * This method can be called to remove the 'ng-touched' class and set the form controls to their 28712 * untouched state (ng-untouched class). 28713 * 28714 * Setting a form controls back to their untouched state is often useful when setting the form 28715 * back to its pristine state. 28716 */ 28717 form.$setUntouched = function() { 28718 forEach(controls, function(control) { 28719 control.$setUntouched(); 28720 }); 28721 }; 28722 28723 /** 28724 * @ngdoc method 28725 * @name form.FormController#$setSubmitted 28726 * 28727 * @description 28728 * Sets the form to its submitted state. 28729 */ 28730 form.$setSubmitted = function() {
28731 $animate.addClass(element, SUBMITTED_CLASS); 28732 form.$submitted = true; 28733 form.$$parentForm.$setSubmitted(); 28734 }; 28735} 28736 28737/** 28738 * @ngdoc directive 28739 * @name ngForm 28740 * @restrict EAC 28741 * 28742 * @description 28743 * Nestable alias of {@link ng.directive:form `form`} directive. HTML 28744 * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a 28745 * sub-group of controls needs to be determined. 28746 * 28747 * Note: the purpose of `ngForm` is to group controls, 28748 * but not to be a replacement for the `<form>` tag with all of its capabilities 28749 * (e.g. posting to the server, ...). 28750 * 28751 * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into 28752 * related scope, under this name. 28753 * 28754 */ 28755 28756 /** 28757 * @ngdoc directive 28758 * @name form 28759 * @restrict E 28760 * 28761 * @description 28762 * Directive that instantiates 28763 * {@link form.FormController FormController}. 28764 * 28765 * If the `name` attribute is specified, the form controller is published onto the current scope under 28766 * this name. 28767 * 28768 * # Alias: {@link ng.directive:ngForm `ngForm`} 28769 * 28770 * In Angular, forms can be nested. This means that the outer form is valid when all of the child 28771 * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so 28772 * Angular provides the {@link ng.directive:ngForm `ngForm`} directive, which behaves identically to 28773 * `form` but can be nested. Nested forms can be useful, for example, if the validity of a sub-group 28774 * of controls needs to be determined. 28775 * 28776 * # CSS classes 28777 * - `ng-valid` is set if the form is valid. 28778 * - `ng-invalid` is set if the form is invalid. 28779 * - `ng-pending` is set if the form is pending. 28780 * - `ng-pristine` is set if the form is pristine. 28781 * - `ng-dirty` is set if the form is dirty. 28782 * - `ng-submitted` is set if the form was submitted. 28783 * 28784 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 28785 * 28786 * 28787 * # Submitting a form and preventing the default action 28788 * 28789 * Since the role of forms in client-side Angular applications is different than in classical 28790 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full 28791 * page reload that sends the data to the server. Instead some javascript logic should be triggered 28792 * to handle the form submission in an application-specific way. 28793 * 28794 * For this reason, Angular prevents the default action (form submission to the server) unless the 28795 * `<form>` element has an `action` attribute specified. 28796 * 28797 * You can use one of the following two ways to specify what javascript method should be called when 28798 * a form is submitted: 28799 * 28800 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element 28801 * - {@link ng.directive:ngClick ngClick} directive on the first 28802 * button or input field of type submit (input[type=submit]) 28803 * 28804 * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit} 28805 * or {@link ng.directive:ngClick ngClick} directives. 28806 * This is because of the following form submission rules in the HTML specification: 28807 * 28808 * - If a form has only one input field then hitting enter in this field triggers form submit 28809 * (`ngSubmit`) 28810 * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter 28811 * doesn't trigger submit 28812 * - if a form has one or more input fields and one or more buttons or input[type=submit] then 28813 * hitting enter in any of the input fields will trigger the click handler on the *first* button or 28814 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`) 28815 * 28816 * Any pending `ngModelOptions` changes will take place immediately when an enclosing form is 28817 * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` 28818 * to have access to the updated model. 28819 * 28820 * ## Animation Hooks 28821 * 28822 * Animations in ngForm are triggered when any of the associated CSS classes are added and removed. 28823 * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any 28824 * other validations that are performed within the form. Animations in ngForm are similar to how 28825 * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well 28826 * as JS animations. 28827 * 28828 * The following example shows a simple way to utilize CSS transitions to style a form element
28829 * that has been rendered as invalid after it has been validated: 28830 * 28831 * <pre> 28832 * //be sure to include ngAnimate as a module to hook into more 28833 * //advanced animations 28834 * .my-form { 28835 * transition:0.5s linear all; 28836 * background: white; 28837 * } 28838 * .my-form.ng-invalid { 28839 * background: red; 28840 * color:white; 28841 * } 28842 * </pre> 28843 * 28844 * @example 28845 <example name="ng-form" deps="angular-animate.js" animations="true" fixBase="true" module="formExample"> 28846 <file name="index.html"> 28847 <script> 28848 angular.module('formExample', []) 28849 .controller('FormController', ['$scope', function($scope) { 28850 $scope.userType = 'guest'; 28851 }]); 28852 </script> 28853 <style> 28854 .my-form { 28855 transition:all linear 0.5s; 28856 background: transparent; 28857 } 28858 .my-form.ng-invalid { 28859 background: red; 28860 } 28861 </style> 28862 <form name="myForm" ng-controller="FormController" class="my-form"> 28863 userType: <input name="input" ng-model="userType" required> 28864 <span class="error" ng-show="myForm.input.$error.required">Required!</span><br> 28865 <code>userType = {{userType}}</code><br> 28866 <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br> 28867 <code>myForm.input.$error = {{myForm.input.$error}}</code><br> 28868 <code>myForm.$valid = {{myForm.$valid}}</code><br> 28869 <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br> 28870 </form> 28871 </file> 28872 <file name="protractor.js" type="protractor"> 28873 it('should initialize to model', function() { 28874 var userType = element(by.binding('userType')); 28875 var valid = element(by.binding('myForm.input.$valid')); 28876 28877 expect(userType.getText()).toContain('guest'); 28878 expect(valid.getText()).toContain('true'); 28879 }); 28880 28881 it('should be invalid if empty', function() { 28882 var userType = element(by.binding('userType')); 28883 var valid = element(by.binding('myForm.input.$valid')); 28884 var userInput = element(by.model('userType')); 28885 28886 userInput.clear(); 28887 userInput.sendKeys(''); 28888 28889 expect(userType.getText()).toEqual('userType ='); 28890 expect(valid.getText()).toContain('false'); 28891 }); 28892 </file> 28893 </example> 28894 * 28895 * @param {string=} name Name of the form. If specified, the form controller will be published into 28896 * related scope, under this name. 28897 */ 28898var formDirectiveFactory = function(isNgForm) { 28899 return ['$timeout', '$parse', function($timeout, $parse) { 28900 var formDirective = { 28901 name: 'form', 28902 restrict: isNgForm ? 'EAC' : 'E', 28903 require: ['form', '^^?form'], //first is the form's own ctrl, second is an optional parent form 28904 controller: FormController, 28905 compile: function ngFormCompile(formElement, attr) { 28906 // Setup initial state of the control 28907 formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS); 28908 28909 var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false); 28910 28911 return { 28912 pre: function ngFormPreLink(scope, formElement, attr, ctrls) { 28913 var controller = ctrls[0]; 28914 28915 // if `action` attr is not present on the form, prevent the default action (submission) 28916 if (!('action' in attr)) { 28917 // we can't use jq events because if a form is destroyed during submission the default 28918 // action is not prevented. see #1238 28919 // 28920 // IE 9 is not affected because it doesn't fire a submit event and try to do a full 28921 // page reload if the form was destroyed by submission of the form via a click handler 28922 // on a button in the form. Looks like an IE9 specific bug. 28923 var handleFormSubmission = function(event) { 28924 scope.$apply(function() { 28925 controller.$commitViewValue(); 28926 controller.$setSubmitted(); 28927 }); 28928 28929 event.preventDefault(); 28930 }; 28931 28932 addEventListenerFn(formElement[0], 'submit', handleFormSubmission); 28933 28934 // unregister the preventDefault listener so that we don't not leak memory but in a 28935 // way that will achieve the prevention of the default action. 28936 formElement.on('$destroy', function() { 28937 $timeout(function() { 28938 removeEventListenerFn(formElement[0], 'submit', handleFormSubmission); 28939 }, 0, false); 28940 }); 28941 } 28942 28943 var parentFormCtrl = ctrls[1] || controller.$$parentForm; 28944 parentFormCtrl.$addControl(controller); 28945 28946 var setter = nameAttr ? getSetter(controller.$name) : noop; 28947 28948 if (nameAttr) { 28949 setter(scope, controller); 28950 attr.$observe(nameAttr, function(newValue) { 28951 if (controller.$name === newValue) return; 28952 setter(scope, undefined); 28953 controller.$$parentForm.$$renameControl(controller, newValue); 28954 setter = getSetter(controller.$name); 28955 setter(scope, controller); 28956 }); 28957 } 28958 formElement.on('$destroy', function() { 28959 controller.$$parentForm.$removeControl(controller); 28960 setter(scope, undefined); 28961 extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards 28962 }); 28963 } 28964 }; 28965 } 28966 }; 28967 28968 return formDirective; 28969 28970 function getSetter(expression) { 28971 if (expression === '') { 28972 //create an assignable expression, so forms with an empty name can be renamed later 28973 return $parse('this[""]').assign; 28974 } 28975 return $parse(expression).assign || noop; 28976 } 28977 }]; 28978}; 28979 28980var formDirective = formDirectiveFactory(); 28981var ngFormDirective = formDirectiveFactory(true); 28982 28983/* global 28984 VALID_CLASS: false, 28985 INVALID_CLASS: false, 28986 PRISTINE_CLASS: false, 28987 DIRTY_CLASS: false, 28988 ngModelMinErr: false 28989*/ 28990 28991// Regex code was initially obtained from SO prior to modification: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231 28992var ISO_DATE_REGEXP = /^\d{4,}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+(?:[+-][0-2]\d:[0-5]\d|Z)$/;
28993// See valid URLs in RFC3987 (http://tools.ietf.org/html/rfc3987) 28994// Note: We are being more lenient, because browsers are too. 28995// 1. Scheme 28996// 2. Slashes 28997// 3. Username 28998// 4. Password 28999// 5. Hostname 29000// 6. Port 29001// 7. Path 29002// 8. Query 29003// 9. Fragment 29004// 1111111111111111 222 333333 44444 55555555555555555555555 666 77777777 8888888 999 29005var URL_REGEXP = /^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i; 29006// eslint-disable-next-line max-len 29007var EMAIL_REGEXP = /^(?=.{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])?)*$/; 29008var NUMBER_REGEXP = /^\s*(-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/; 29009var DATE_REGEXP = /^(\d{4,})-(\d{2})-(\d{2})$/; 29010var DATETIMELOCAL_REGEXP = /^(\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; 29011var WEEK_REGEXP = /^(\d{4,})-W(\d\d)$/; 29012var MONTH_REGEXP = /^(\d{4,})-(\d\d)$/; 29013var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; 29014 29015var PARTIAL_VALIDATION_EVENTS = 'keydown wheel mousedown'; 29016var PARTIAL_VALIDATION_TYPES = createMap(); 29017forEach('date,datetime-local,month,time,week'.split(','), function(type) { 29018 PARTIAL_VALIDATION_TYPES[type] = true; 29019}); 29020 29021var inputType = { 29022 29023 /** 29024 * @ngdoc input 29025 * @name input[text] 29026 * 29027 * @description 29028 * Standard HTML text input with angular data binding, inherited by most of the `input` elements. 29029 * 29030 * 29031 * @param {string} ngModel Assignable angular expression to data-bind to. 29032 * @param {string=} name Property name of the form under which the control is published. 29033 * @param {string=} required Adds `required` validation error key if the value is not entered. 29034 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 29035 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 29036 * `required` when you want to data-bind to the `required` attribute. 29037 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 29038 * minlength. 29039 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 29040 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 29041 * any length. 29042 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 29043 * that contains the regular expression body that will be converted to a regular expression 29044 * as in the ngPattern directive. 29045 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 29046 * does not match a RegExp found by evaluating the Angular expression given in the attribute value. 29047 * If the expression evaluates to a RegExp object, then this is used directly. 29048 * If the expression evaluates to a string, then it will be converted to a RegExp 29049 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 29050 * `new RegExp('^abc$')`.<br /> 29051 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 29052 * start at the index of the last search's match, thus not taking the whole input value into 29053 * account. 29054 * @param {string=} ngChange Angular expression to be executed when input changes due to user 29055 * interaction with the input element. 29056 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 29057 * This parameter is ignored for input[type=password] controls, which will never trim the 29058 * input. 29059 * 29060 * @example 29061 <example name="text-input-directive" module="textInputExample"> 29062 <file name="index.html"> 29063 <script> 29064 angular.module('textInputExample', []) 29065 .controller('ExampleController', ['$scope', function($scope) { 29066 $scope.example = { 29067 text: 'guest', 29068 word: /^\s*\w*\s*$/ 29069 }; 29070 }]); 29071 </script>
29072 <form name="myForm" ng-controller="ExampleController"> 29073 <label>Single word: 29074 <input type="text" name="input" ng-model="example.text" 29075 ng-pattern="example.word" required ng-trim="false"> 29076 </label> 29077 <div role="alert"> 29078 <span class="error" ng-show="myForm.input.$error.required"> 29079 Required!</span> 29080 <span class="error" ng-show="myForm.input.$error.pattern"> 29081 Single word only!</span> 29082 </div> 29083 <code>text = {{example.text}}</code><br/> 29084 <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br/> 29085 <code>myForm.input.$error = {{myForm.input.$error}}</code><br/> 29086 <code>myForm.$valid = {{myForm.$valid}}</code><br/> 29087 <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br/> 29088 </form> 29089 </file> 29090 <file name="protractor.js" type="protractor"> 29091 var text = element(by.binding('example.text')); 29092 var valid = element(by.binding('myForm.input.$valid')); 29093 var input = element(by.model('example.text')); 29094 29095 it('should initialize to model', function() { 29096 expect(text.getText()).toContain('guest'); 29097 expect(valid.getText()).toContain('true'); 29098 }); 29099 29100 it('should be invalid if empty', function() { 29101 input.clear(); 29102 input.sendKeys(''); 29103 29104 expect(text.getText()).toEqual('text ='); 29105 expect(valid.getText()).toContain('false'); 29106 }); 29107 29108 it('should be invalid if multi word', function() { 29109 input.clear(); 29110 input.sendKeys('hello world'); 29111 29112 expect(valid.getText()).toContain('false'); 29113 }); 29114 </file> 29115 </example> 29116 */ 29117 'text': textInputType, 29118 29119 /** 29120 * @ngdoc input 29121 * @name input[date] 29122 * 29123 * @description 29124 * Input with date validation and transformation. In browsers that do not yet support 29125 * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601 29126 * date format (yyyy-MM-dd), for example: `2009-01-06`. Since many 29127 * modern browsers do not yet support this input type, it is important to provide cues to users on the 29128 * expected input format via a placeholder or label. 29129 * 29130 * The model must always be a Date object, otherwise Angular will throw an error. 29131 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 29132 * 29133 * The timezone to be used to read/write the `Date` instance in the model can be defined using 29134 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 29135 * 29136 * @param {string} ngModel Assignable angular expression to data-bind to. 29137 * @param {string=} name Property name of the form under which the control is published. 29138 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a 29139 * valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute 29140 * (e.g. `min="{{minDate | date:'yyyy-MM-dd'}}"`). Note that `min` will also add native HTML5 29141 * constraint validation. 29142 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be 29143 * a valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute 29144 * (e.g. `max="{{maxDate | date:'yyyy-MM-dd'}}"`). Note that `max` will also add native HTML5 29145 * constraint validation. 29146 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO date string 29147 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 29148 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO date string 29149 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 29150 * @param {string=} required Sets `required` validation error key if the value is not entered. 29151 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 29152 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 29153 * `required` when you want to data-bind to the `required` attribute. 29154 * @param {string=} ngChange Angular expression to be executed when input changes due to user 29155 * interaction with the input element. 29156 * 29157 * @example 29158 <example name="date-input-directive" module="dateInputExample"> 29159 <file name="index.html"> 29160 <script> 29161 angular.module('dateInputExample', []) 29162 .controller('DateController', ['$scope', function($scope) { 29163 $scope.example = { 29164 value: new Date(2013, 9, 22) 29165 }; 29166 }]); 29167 </script> 29168 <form name="myForm" ng-controller="DateController as dateCtrl"> 29169 <label for="exampleInput">Pick a date in 2013:</label> 29170 <input type="date" id="exampleInput" name="input" ng-model="example.value" 29171 placeholder="yyyy-MM-dd" min="2013-01-01" max="2013-12-31" required /> 29172 <div role="alert">
29173 <span class="error" ng-show="myForm.input.$error.required"> 29174 Required!</span> 29175 <span class="error" ng-show="myForm.input.$error.date"> 29176 Not a valid date!</span> 29177 </div> 29178 <tt>value = {{example.value | date: "yyyy-MM-dd"}}</tt><br/> 29179 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 29180 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 29181 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 29182 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 29183 </form> 29184 </file> 29185 <file name="protractor.js" type="protractor"> 29186 var value = element(by.binding('example.value | date: "yyyy-MM-dd"')); 29187 var valid = element(by.binding('myForm.input.$valid')); 29188 29189 // currently protractor/webdriver does not support 29190 // sending keys to all known HTML5 input controls 29191 // for various browsers (see https://github.com/angular/protractor/issues/562). 29192 function setInput(val) { 29193 // set the value of the element and force validation. 29194 var scr = "var ipt = document.getElementById('exampleInput'); " + 29195 "ipt.value = '" + val + "';" + 29196 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 29197 browser.executeScript(scr); 29198 } 29199 29200 it('should initialize to model', function() { 29201 expect(value.getText()).toContain('2013-10-22'); 29202 expect(valid.getText()).toContain('myForm.input.$valid = true'); 29203 }); 29204 29205 it('should be invalid if empty', function() { 29206 setInput(''); 29207 expect(value.getText()).toEqual('value ='); 29208 expect(valid.getText()).toContain('myForm.input.$valid = false'); 29209 }); 29210 29211 it('should be invalid if over max', function() { 29212 setInput('2015-01-01'); 29213 expect(value.getText()).toContain(''); 29214 expect(valid.getText()).toContain('myForm.input.$valid = false'); 29215 }); 29216 </file> 29217 </example> 29218 */ 29219 'date': createDateInputType('date', DATE_REGEXP, 29220 createDateParser(DATE_REGEXP, ['yyyy', 'MM', 'dd']), 29221 'yyyy-MM-dd'), 29222 29223 /** 29224 * @ngdoc input 29225 * @name input[datetime-local] 29226 * 29227 * @description 29228 * Input with datetime validation and transformation. In browsers that do not yet support 29229 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 29230 * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`. 29231 * 29232 * The model must always be a Date object, otherwise Angular will throw an error. 29233 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 29234 * 29235 * The timezone to be used to read/write the `Date` instance in the model can be defined using 29236 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 29237 * 29238 * @param {string} ngModel Assignable angular expression to data-bind to. 29239 * @param {string=} name Property name of the form under which the control is published. 29240 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 29241 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation 29242 * inside this attribute (e.g. `min="{{minDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`). 29243 * Note that `min` will also add native HTML5 constraint validation. 29244 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 29245 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation 29246 * inside this attribute (e.g. `max="{{maxDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`). 29247 * Note that `max` will also add native HTML5 constraint validation. 29248 * @param {(date|string)=} ngMin Sets the `min` validation error key to the Date / ISO datetime string 29249 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 29250 * @param {(date|string)=} ngMax Sets the `max` validation error key to the Date / ISO datetime string 29251 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 29252 * @param {string=} required Sets `required` validation error key if the value is not entered. 29253 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 29254 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 29255 * `required` when you want to data-bind to the `required` attribute. 29256 * @param {string=} ngChange Angular expression to be executed when input changes due to user 29257 * interaction with the input element. 29258 * 29259 * @example 29260 <example name="datetimelocal-input-directive" module="dateExample"> 29261 <file name="index.html"> 29262 <script> 29263 angular.module('dateExample', []) 29264 .controller('DateController', ['$scope', function($scope) { 29265 $scope.example = { 29266 value: new Date(2010, 11, 28, 14, 57) 29267 }; 29268 }]); 29269 </script> 29270 <form name="myForm" ng-controller="DateController as dateCtrl"> 29271 <label for="exampleInput">Pick a date between in 2013:</label> 29272 <input type="datetime-local" id="exampleInput" name="input" ng-model="example.value" 29273 placeholder="yyyy-MM-ddTHH:mm:ss" min="2001-01-01T00:00:00" max="2013-12-31T00:00:00" required /> 29274 <div role="alert">
29275 <span class="error" ng-show="myForm.input.$error.required"> 29276 Required!</span> 29277 <span class="error" ng-show="myForm.input.$error.datetimelocal"> 29278 Not a valid date!</span> 29279 </div> 29280 <tt>value = {{example.value | date: "yyyy-MM-ddTHH:mm:ss"}}</tt><br/> 29281 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 29282 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 29283 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 29284 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 29285 </form> 29286 </file> 29287 <file name="protractor.js" type="protractor"> 29288 var value = element(by.binding('example.value | date: "yyyy-MM-ddTHH:mm:ss"')); 29289 var valid = element(by.binding('myForm.input.$valid')); 29290 29291 // currently protractor/webdriver does not support 29292 // sending keys to all known HTML5 input controls 29293 // for various browsers (https://github.com/angular/protractor/issues/562). 29294 function setInput(val) { 29295 // set the value of the element and force validation. 29296 var scr = "var ipt = document.getElementById('exampleInput'); " + 29297 "ipt.value = '" + val + "';" + 29298 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 29299 browser.executeScript(scr); 29300 } 29301 29302 it('should initialize to model', function() { 29303 expect(value.getText()).toContain('2010-12-28T14:57:00'); 29304 expect(valid.getText()).toContain('myForm.input.$valid = true'); 29305 }); 29306 29307 it('should be invalid if empty', function() { 29308 setInput(''); 29309 expect(value.getText()).toEqual('value ='); 29310 expect(valid.getText()).toContain('myForm.input.$valid = false'); 29311 }); 29312 29313 it('should be invalid if over max', function() { 29314 setInput('2015-01-01T23:59:00'); 29315 expect(value.getText()).toContain(''); 29316 expect(valid.getText()).toContain('myForm.input.$valid = false'); 29317 }); 29318 </file> 29319 </example> 29320 */ 29321 'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP, 29322 createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss', 'sss']), 29323 'yyyy-MM-ddTHH:mm:ss.sss'), 29324 29325 /** 29326 * @ngdoc input 29327 * @name input[time] 29328 * 29329 * @description 29330 * Input with time validation and transformation. In browsers that do not yet support 29331 * the HTML5 time input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 29332 * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a 29333 * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`. 29334 * 29335 * The model must always be a Date object, otherwise Angular will throw an error. 29336 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 29337 * 29338 * The timezone to be used to read/write the `Date` instance in the model can be defined using 29339 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 29340 * 29341 * @param {string} ngModel Assignable angular expression to data-bind to. 29342 * @param {string=} name Property name of the form under which the control is published. 29343 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 29344 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this 29345 * attribute (e.g. `min="{{minTime | date:'HH:mm:ss'}}"`). Note that `min` will also add 29346 * native HTML5 constraint validation. 29347 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 29348 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this 29349 * attribute (e.g. `max="{{maxTime | date:'HH:mm:ss'}}"`). Note that `max` will also add 29350 * native HTML5 constraint validation. 29351 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO time string the 29352 * `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 29353 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO time string the 29354 * `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 29355 * @param {string=} required Sets `required` validation error key if the value is not entered. 29356 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 29357 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 29358 * `required` when you want to data-bind to the `required` attribute. 29359 * @param {string=} ngChange Angular expression to be executed when input changes due to user 29360 * interaction with the input element. 29361 * 29362 * @example 29363 <example name="time-input-directive" module="timeExample"> 29364 <file name="index.html"> 29365 <script> 29366 angular.module('timeExample', []) 29367 .controller('DateController', ['$scope', function($scope) { 29368 $scope.example = { 29369 value: new Date(1970, 0, 1, 14, 57, 0) 29370 }; 29371 }]); 29372 </script> 29373 <form name="myForm" ng-controller="DateController as dateCtrl"> 29374 <label for="exampleInput">Pick a time between 8am and 5pm:</label> 29375 <input type="time" id="exampleInput" name="input" ng-model="example.value" 29376 placeholder="HH:mm:ss" min="08:00:00" max="17:00:00" required /> 29377 <div role="alert">
29378 <span class="error" ng-show="myForm.input.$error.required"> 29379 Required!</span> 29380 <span class="error" ng-show="myForm.input.$error.time"> 29381 Not a valid date!</span> 29382 </div> 29383 <tt>value = {{example.value | date: "HH:mm:ss"}}</tt><br/> 29384 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 29385 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 29386 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 29387 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 29388 </form> 29389 </file> 29390 <file name="protractor.js" type="protractor"> 29391 var value = element(by.binding('example.value | date: "HH:mm:ss"')); 29392 var valid = element(by.binding('myForm.input.$valid')); 29393 29394 // currently protractor/webdriver does not support 29395 // sending keys to all known HTML5 input controls 29396 // for various browsers (https://github.com/angular/protractor/issues/562). 29397 function setInput(val) { 29398 // set the value of the element and force validation. 29399 var scr = "var ipt = document.getElementById('exampleInput'); " + 29400 "ipt.value = '" + val + "';" + 29401 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 29402 browser.executeScript(scr); 29403 } 29404 29405 it('should initialize to model', function() { 29406 expect(value.getText()).toContain('14:57:00'); 29407 expect(valid.getText()).toContain('myForm.input.$valid = true'); 29408 }); 29409 29410 it('should be invalid if empty', function() { 29411 setInput(''); 29412 expect(value.getText()).toEqual('value ='); 29413 expect(valid.getText()).toContain('myForm.input.$valid = false'); 29414 }); 29415 29416 it('should be invalid if over max', function() { 29417 setInput('23:59:00'); 29418 expect(value.getText()).toContain(''); 29419 expect(valid.getText()).toContain('myForm.input.$valid = false'); 29420 }); 29421 </file> 29422 </example> 29423 */ 29424 'time': createDateInputType('time', TIME_REGEXP, 29425 createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss', 'sss']), 29426 'HH:mm:ss.sss'), 29427 29428 /** 29429 * @ngdoc input 29430 * @name input[week] 29431 * 29432 * @description 29433 * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support 29434 * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 29435 * week format (yyyy-W##), for example: `2013-W02`. 29436 * 29437 * The model must always be a Date object, otherwise Angular will throw an error. 29438 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 29439 * 29440 * The timezone to be used to read/write the `Date` instance in the model can be defined using 29441 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 29442 * 29443 * @param {string} ngModel Assignable angular expression to data-bind to. 29444 * @param {string=} name Property name of the form under which the control is published. 29445 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 29446 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this 29447 * attribute (e.g. `min="{{minWeek | date:'yyyy-Www'}}"`). Note that `min` will also add 29448 * native HTML5 constraint validation. 29449 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 29450 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this 29451 * attribute (e.g. `max="{{maxWeek | date:'yyyy-Www'}}"`). Note that `max` will also add 29452 * native HTML5 constraint validation. 29453 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string 29454 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 29455 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string 29456 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 29457 * @param {string=} required Sets `required` validation error key if the value is not entered. 29458 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 29459 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 29460 * `required` when you want to data-bind to the `required` attribute. 29461 * @param {string=} ngChange Angular expression to be executed when input changes due to user 29462 * interaction with the input element. 29463 * 29464 * @example 29465 <example name="week-input-directive" module="weekExample"> 29466 <file name="index.html"> 29467 <script> 29468 angular.module('weekExample', []) 29469 .controller('DateController', ['$scope', function($scope) { 29470 $scope.example = { 29471 value: new Date(2013, 0, 3) 29472 }; 29473 }]); 29474 </script> 29475 <form name="myForm" ng-controller="DateController as dateCtrl"> 29476 <label>Pick a date between in 2013: 29477 <input id="exampleInput" type="week" name="input" ng-model="example.value" 29478 placeholder="YYYY-W##" min="2012-W32" 29479 max="2013-W52" required /> 29480 </label> 29481 <div role="alert">
29482 <span class="error" ng-show="myForm.input.$error.required"> 29483 Required!</span> 29484 <span class="error" ng-show="myForm.input.$error.week"> 29485 Not a valid date!</span> 29486 </div> 29487 <tt>value = {{example.value | date: "yyyy-Www"}}</tt><br/> 29488 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 29489 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 29490 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 29491 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 29492 </form> 29493 </file> 29494 <file name="protractor.js" type="protractor"> 29495 var value = element(by.binding('example.value | date: "yyyy-Www"')); 29496 var valid = element(by.binding('myForm.input.$valid')); 29497 29498 // currently protractor/webdriver does not support 29499 // sending keys to all known HTML5 input controls 29500 // for various browsers (https://github.com/angular/protractor/issues/562). 29501 function setInput(val) { 29502 // set the value of the element and force validation. 29503 var scr = "var ipt = document.getElementById('exampleInput'); " + 29504 "ipt.value = '" + val + "';" + 29505 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 29506 browser.executeScript(scr); 29507 } 29508 29509 it('should initialize to model', function() { 29510 expect(value.getText()).toContain('2013-W01'); 29511 expect(valid.getText()).toContain('myForm.input.$valid = true'); 29512 }); 29513 29514 it('should be invalid if empty', function() { 29515 setInput(''); 29516 expect(value.getText()).toEqual('value ='); 29517 expect(valid.getText()).toContain('myForm.input.$valid = false'); 29518 }); 29519 29520 it('should be invalid if over max', function() { 29521 setInput('2015-W01'); 29522 expect(value.getText()).toContain(''); 29523 expect(valid.getText()).toContain('myForm.input.$valid = false'); 29524 }); 29525 </file> 29526 </example> 29527 */ 29528 'week': createDateInputType('week', WEEK_REGEXP, weekParser, 'yyyy-Www'), 29529 29530 /** 29531 * @ngdoc input 29532 * @name input[month] 29533 * 29534 * @description 29535 * Input with month validation and transformation. In browsers that do not yet support 29536 * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 29537 * month format (yyyy-MM), for example: `2009-01`. 29538 * 29539 * The model must always be a Date object, otherwise Angular will throw an error. 29540 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 29541 * If the model is not set to the first of the month, the next view to model update will set it 29542 * to the first of the month. 29543 * 29544 * The timezone to be used to read/write the `Date` instance in the model can be defined using 29545 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 29546 * 29547 * @param {string} ngModel Assignable angular expression to data-bind to. 29548 * @param {string=} name Property name of the form under which the control is published. 29549 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 29550 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this 29551 * attribute (e.g. `min="{{minMonth | date:'yyyy-MM'}}"`). Note that `min` will also add 29552 * native HTML5 constraint validation. 29553 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 29554 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this 29555 * attribute (e.g. `max="{{maxMonth | date:'yyyy-MM'}}"`). Note that `max` will also add 29556 * native HTML5 constraint validation. 29557 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string 29558 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 29559 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string 29560 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 29561 29562 * @param {string=} required Sets `required` validation error key if the value is not entered. 29563 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 29564 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 29565 * `required` when you want to data-bind to the `required` attribute. 29566 * @param {string=} ngChange Angular expression to be executed when input changes due to user 29567 * interaction with the input element. 29568 * 29569 * @example 29570 <example name="month-input-directive" module="monthExample"> 29571 <file name="index.html"> 29572 <script> 29573 angular.module('monthExample', []) 29574 .controller('DateController', ['$scope', function($scope) { 29575 $scope.example = { 29576 value: new Date(2013, 9, 1) 29577 }; 29578 }]); 29579 </script> 29580 <form name="myForm" ng-controller="DateController as dateCtrl"> 29581 <label for="exampleInput">Pick a month in 2013:</label> 29582 <input id="exampleInput" type="month" name="input" ng-model="example.value" 29583 placeholder="yyyy-MM" min="2013-01" max="2013-12" required /> 29584 <div role="alert">
29585 <span class="error" ng-show="myForm.input.$error.required"> 29586 Required!</span> 29587 <span class="error" ng-show="myForm.input.$error.month"> 29588 Not a valid month!</span> 29589 </div> 29590 <tt>value = {{example.value | date: "yyyy-MM"}}</tt><br/> 29591 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 29592 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 29593 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 29594 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 29595 </form> 29596 </file> 29597 <file name="protractor.js" type="protractor"> 29598 var value = element(by.binding('example.value | date: "yyyy-MM"')); 29599 var valid = element(by.binding('myForm.input.$valid')); 29600 29601 // currently protractor/webdriver does not support 29602 // sending keys to all known HTML5 input controls 29603 // for various browsers (https://github.com/angular/protractor/issues/562). 29604 function setInput(val) { 29605 // set the value of the element and force validation. 29606 var scr = "var ipt = document.getElementById('exampleInput'); " + 29607 "ipt.value = '" + val + "';" + 29608 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 29609 browser.executeScript(scr); 29610 } 29611 29612 it('should initialize to model', function() { 29613 expect(value.getText()).toContain('2013-10'); 29614 expect(valid.getText()).toContain('myForm.input.$valid = true'); 29615 }); 29616 29617 it('should be invalid if empty', function() { 29618 setInput(''); 29619 expect(value.getText()).toEqual('value ='); 29620 expect(valid.getText()).toContain('myForm.input.$valid = false'); 29621 }); 29622 29623 it('should be invalid if over max', function() { 29624 setInput('2015-01'); 29625 expect(value.getText()).toContain(''); 29626 expect(valid.getText()).toContain('myForm.input.$valid = false'); 29627 }); 29628 </file> 29629 </example> 29630 */ 29631 'month': createDateInputType('month', MONTH_REGEXP, 29632 createDateParser(MONTH_REGEXP, ['yyyy', 'MM']), 29633 'yyyy-MM'), 29634 29635 /** 29636 * @ngdoc input 29637 * @name input[number] 29638 * 29639 * @description 29640 * Text input with number validation and transformation. Sets the `number` validation 29641 * error if not a valid number. 29642 * 29643 * <div class="alert alert-warning"> 29644 * The model must always be of type `number` otherwise Angular will throw an error. 29645 * Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt} 29646 * error docs for more information and an example of how to convert your model if necessary. 29647 * </div> 29648 * 29649 * ## Issues with HTML5 constraint validation 29650 * 29651 * In browsers that follow the 29652 * [HTML5 specification](https://html.spec.whatwg.org/multipage/forms.html#number-state-%28type=number%29), 29653 * `input[number]` does not work as expected with {@link ngModelOptions `ngModelOptions.allowInvalid`}. 29654 * If a non-number is entered in the input, the browser will report the value as an empty string, 29655 * which means the view / model values in `ngModel` and subsequently the scope value 29656 * will also be an empty string. 29657 * 29658 * 29659 * @param {string} ngModel Assignable angular expression to data-bind to. 29660 * @param {string=} name Property name of the form under which the control is published. 29661 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 29662 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 29663 * @param {string=} required Sets `required` validation error key if the value is not entered. 29664 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 29665 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 29666 * `required` when you want to data-bind to the `required` attribute. 29667 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 29668 * minlength. 29669 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 29670 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 29671 * any length. 29672 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 29673 * that contains the regular expression body that will be converted to a regular expression 29674 * as in the ngPattern directive. 29675 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 29676 * does not match a RegExp found by evaluating the Angular expression given in the attribute value. 29677 * If the expression evaluates to a RegExp object, then this is used directly. 29678 * If the expression evaluates to a string, then it will be converted to a RegExp 29679 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 29680 * `new RegExp('^abc$')`.<br /> 29681 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 29682 * start at the index of the last search's match, thus not taking the whole input value into 29683 * account. 29684 * @param {string=} ngChange Angular expression to be executed when input changes due to user 29685 * interaction with the input element. 29686 * 29687 * @example 29688 <example name="number-input-directive" module="numberExample"> 29689 <file name="index.html"> 29690 <script> 29691 angular.module('numberExample', [])
29692 .controller('ExampleController', ['$scope', function($scope) { 29693 $scope.example = { 29694 value: 12 29695 }; 29696 }]); 29697 </script> 29698 <form name="myForm" ng-controller="ExampleController"> 29699 <label>Number: 29700 <input type="number" name="input" ng-model="example.value" 29701 min="0" max="99" required> 29702 </label> 29703 <div role="alert"> 29704 <span class="error" ng-show="myForm.input.$error.required"> 29705 Required!</span> 29706 <span class="error" ng-show="myForm.input.$error.number"> 29707 Not valid number!</span> 29708 </div> 29709 <tt>value = {{example.value}}</tt><br/> 29710 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 29711 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 29712 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 29713 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 29714 </form> 29715 </file> 29716 <file name="protractor.js" type="protractor"> 29717 var value = element(by.binding('example.value')); 29718 var valid = element(by.binding('myForm.input.$valid')); 29719 var input = element(by.model('example.value')); 29720 29721 it('should initialize to model', function() { 29722 expect(value.getText()).toContain('12'); 29723 expect(valid.getText()).toContain('true'); 29724 }); 29725 29726 it('should be invalid if empty', function() { 29727 input.clear(); 29728 input.sendKeys(''); 29729 expect(value.getText()).toEqual('value ='); 29730 expect(valid.getText()).toContain('false'); 29731 }); 29732 29733 it('should be invalid if over max', function() { 29734 input.clear(); 29735 input.sendKeys('123'); 29736 expect(value.getText()).toEqual('value ='); 29737 expect(valid.getText()).toContain('false'); 29738 }); 29739 </file> 29740 </example> 29741 */ 29742 'number': numberInputType, 29743 29744 29745 /** 29746 * @ngdoc input 29747 * @name input[url] 29748 * 29749 * @description 29750 * Text input with URL validation. Sets the `url` validation error key if the content is not a 29751 * valid URL. 29752 * 29753 * <div class="alert alert-warning"> 29754 * **Note:** `input[url]` uses a regex to validate urls that is derived from the regex 29755 * used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify 29756 * the built-in validators (see the {@link guide/forms Forms guide}) 29757 * </div> 29758 * 29759 * @param {string} ngModel Assignable angular expression to data-bind to. 29760 * @param {string=} name Property name of the form under which the control is published. 29761 * @param {string=} required Sets `required` validation error key if the value is not entered. 29762 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 29763 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 29764 * `required` when you want to data-bind to the `required` attribute. 29765 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 29766 * minlength. 29767 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 29768 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 29769 * any length. 29770 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 29771 * that contains the regular expression body that will be converted to a regular expression 29772 * as in the ngPattern directive. 29773 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 29774 * does not match a RegExp found by evaluating the Angular expression given in the attribute value. 29775 * If the expression evaluates to a RegExp object, then this is used directly. 29776 * If the expression evaluates to a string, then it will be converted to a RegExp 29777 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 29778 * `new RegExp('^abc$')`.<br /> 29779 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 29780 * start at the index of the last search's match, thus not taking the whole input value into 29781 * account. 29782 * @param {string=} ngChange Angular expression to be executed when input changes due to user 29783 * interaction with the input element. 29784 * 29785 * @example 29786 <example name="url-input-directive" module="urlExample"> 29787 <file name="index.html"> 29788 <script> 29789 angular.module('urlExample', [])
29790 .controller('ExampleController', ['$scope', function($scope) { 29791 $scope.url = { 29792 text: 'http://google.com' 29793 }; 29794 }]); 29795 </script> 29796 <form name="myForm" ng-controller="ExampleController"> 29797 <label>URL: 29798 <input type="url" name="input" ng-model="url.text" required> 29799 <label> 29800 <div role="alert"> 29801 <span class="error" ng-show="myForm.input.$error.required"> 29802 Required!</span> 29803 <span class="error" ng-show="myForm.input.$error.url"> 29804 Not valid url!</span> 29805 </div> 29806 <tt>text = {{url.text}}</tt><br/> 29807 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 29808 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 29809 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 29810 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 29811 <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/> 29812 </form> 29813 </file> 29814 <file name="protractor.js" type="protractor"> 29815 var text = element(by.binding('url.text')); 29816 var valid = element(by.binding('myForm.input.$valid')); 29817 var input = element(by.model('url.text')); 29818 29819 it('should initialize to model', function() { 29820 expect(text.getText()).toContain('http://google.com'); 29821 expect(valid.getText()).toContain('true'); 29822 }); 29823 29824 it('should be invalid if empty', function() { 29825 input.clear(); 29826 input.sendKeys(''); 29827 29828 expect(text.getText()).toEqual('text ='); 29829 expect(valid.getText()).toContain('false'); 29830 }); 29831 29832 it('should be invalid if not url', function() { 29833 input.clear(); 29834 input.sendKeys('box'); 29835 29836 expect(valid.getText()).toContain('false'); 29837 }); 29838 </file> 29839 </example> 29840 */ 29841 'url': urlInputType, 29842 29843 29844 /** 29845 * @ngdoc input 29846 * @name input[email] 29847 * 29848 * @description 29849 * Text input with email validation. Sets the `email` validation error key if not a valid email 29850 * address. 29851 * 29852 * <div class="alert alert-warning"> 29853 * **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex 29854 * used in Chromium. If you need stricter validation (e.g. requiring a top-level domain), you can 29855 * use `ng-pattern` or modify the built-in validators (see the {@link guide/forms Forms guide}) 29856 * </div> 29857 * 29858 * @param {string} ngModel Assignable angular expression to data-bind to. 29859 * @param {string=} name Property name of the form under which the control is published. 29860 * @param {string=} required Sets `required` validation error key if the value is not entered. 29861 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 29862 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 29863 * `required` when you want to data-bind to the `required` attribute. 29864 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 29865 * minlength. 29866 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 29867 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 29868 * any length. 29869 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 29870 * that contains the regular expression body that will be converted to a regular expression 29871 * as in the ngPattern directive. 29872 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 29873 * does not match a RegExp found by evaluating the Angular expression given in the attribute value. 29874 * If the expression evaluates to a RegExp object, then this is used directly. 29875 * If the expression evaluates to a string, then it will be converted to a RegExp 29876 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 29877 * `new RegExp('^abc$')`.<br /> 29878 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 29879 * start at the index of the last search's match, thus not taking the whole input value into 29880 * account. 29881 * @param {string=} ngChange Angular expression to be executed when input changes due to user 29882 * interaction with the input element. 29883 * 29884 * @example 29885 <example name="email-input-directive" module="emailExample"> 29886 <file name="index.html"> 29887 <script> 29888 angular.module('emailExample', [])
29889 .controller('ExampleController', ['$scope', function($scope) { 29890 $scope.email = { 29891 text: '[email protected]' 29892 }; 29893 }]); 29894 </script> 29895 <form name="myForm" ng-controller="ExampleController"> 29896 <label>Email: 29897 <input type="email" name="input" ng-model="email.text" required> 29898 </label> 29899 <div role="alert"> 29900 <span class="error" ng-show="myForm.input.$error.required"> 29901 Required!</span> 29902 <span class="error" ng-show="myForm.input.$error.email"> 29903 Not valid email!</span> 29904 </div> 29905 <tt>text = {{email.text}}</tt><br/> 29906 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 29907 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 29908 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 29909 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 29910 <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/> 29911 </form> 29912 </file> 29913 <file name="protractor.js" type="protractor"> 29914 var text = element(by.binding('email.text')); 29915 var valid = element(by.binding('myForm.input.$valid')); 29916 var input = element(by.model('email.text')); 29917 29918 it('should initialize to model', function() { 29919 expect(text.getText()).toContain('[email protected]'); 29920 expect(valid.getText()).toContain('true'); 29921 }); 29922 29923 it('should be invalid if empty', function() { 29924 input.clear(); 29925 input.sendKeys(''); 29926 expect(text.getText()).toEqual('text ='); 29927 expect(valid.getText()).toContain('false'); 29928 }); 29929 29930 it('should be invalid if not email', function() { 29931 input.clear(); 29932 input.sendKeys('xxx'); 29933 29934 expect(valid.getText()).toContain('false'); 29935 }); 29936 </file> 29937 </example> 29938 */ 29939 'email': emailInputType, 29940 29941 29942 /** 29943 * @ngdoc input 29944 * @name input[radio] 29945 * 29946 * @description 29947 * HTML radio button. 29948 * 29949 * @param {string} ngModel Assignable angular expression to data-bind to. 29950 * @param {string} value The value to which the `ngModel` expression should be set when selected. 29951 * Note that `value` only supports `string` values, i.e. the scope model needs to be a string, 29952 * too. Use `ngValue` if you need complex models (`number`, `object`, ...). 29953 * @param {string=} name Property name of the form under which the control is published. 29954 * @param {string=} ngChange Angular expression to be executed when input changes due to user 29955 * interaction with the input element. 29956 * @param {string} ngValue Angular expression to which `ngModel` will be be set when the radio 29957 * is selected. Should be used instead of the `value` attribute if you need 29958 * a non-string `ngModel` (`boolean`, `array`, ...). 29959 * 29960 * @example 29961 <example name="radio-input-directive" module="radioExample"> 29962 <file name="index.html"> 29963 <script> 29964 angular.module('radioExample', []) 29965 .controller('ExampleController', ['$scope', function($scope) { 29966 $scope.color = { 29967 name: 'blue' 29968 }; 29969 $scope.specialValue = { 29970 "id": "12345", 29971 "value": "green" 29972 }; 29973 }]); 29974 </script>
29975 <form name="myForm" ng-controller="ExampleController"> 29976 <label> 29977 <input type="radio" ng-model="color.name" value="red"> 29978 Red 29979 </label><br/> 29980 <label> 29981 <input type="radio" ng-model="color.name" ng-value="specialValue"> 29982 Green 29983 </label><br/> 29984 <label> 29985 <input type="radio" ng-model="color.name" value="blue"> 29986 Blue 29987 </label><br/> 29988 <tt>color = {{color.name | json}}</tt><br/> 29989 </form> 29990 Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`. 29991 </file> 29992 <file name="protractor.js" type="protractor"> 29993 it('should change state', function() { 29994 var inputs = element.all(by.model('color.name')); 29995 var color = element(by.binding('color.name')); 29996 29997 expect(color.getText()).toContain('blue'); 29998 29999 inputs.get(0).click(); 30000 expect(color.getText()).toContain('red'); 30001 30002 inputs.get(1).click(); 30003 expect(color.getText()).toContain('green'); 30004 }); 30005 </file> 30006 </example> 30007 */ 30008 'radio': radioInputType, 30009 30010 /** 30011 * @ngdoc input 30012 * @name input[range] 30013 * 30014 * @description 30015 * Native range input with validation and transformation. 30016 * 30017 * <div class="alert alert-warning"> 30018 * <p> 30019 * In v1.5.9+, in order to avoid interfering with already existing, custom directives for 30020 * `input[range]`, you need to let Angular know that you want to enable its built-in support. 30021 * You can do this by adding the `ng-input-range` attribute to the input element. E.g.: 30022 * `<input type="range" ng-input-range ... />` 30023 * </p><br /> 30024 * <p> 30025 * Input elements without the `ng-input-range` attibute will continue to be treated the same 30026 * as in previous versions (e.g. their model value will be a string not a number and Angular 30027 * will not take `min`/`max`/`step` attributes and properties into account). 30028 * </p><br /> 30029 * <p> 30030 * **Note:** From v1.6.x onwards, the support for `input[range]` will be always enabled and 30031 * the `ng-input-range` attribute will have no effect. 30032 * </p><br /> 30033 * <p> 30034 * This documentation page refers to elements which have the built-in support enabled; i.e. 30035 * elements _with_ the `ng-input-range` attribute. 30036 * </p> 30037 * </div> 30038 * 30039 * The model for the range input must always be a `Number`. 30040 * 30041 * IE9 and other browsers that do not support the `range` type fall back 30042 * to a text input without any default values for `min`, `max` and `step`. Model binding, 30043 * validation and number parsing are nevertheless supported. 30044 * 30045 * Browsers that support range (latest Chrome, Safari, Firefox, Edge) treat `input[range]` 30046 * in a way that never allows the input to hold an invalid value. That means: 30047 * - any non-numerical value is set to `(max + min) / 2`. 30048 * - any numerical value that is less than the current min val, or greater than the current max val 30049 * is set to the min / max val respectively. 30050 * - additionally, the current `step` is respected, so the nearest value that satisfies a step 30051 * is used. 30052 * 30053 * See the [HTML Spec on input[type=range]](https://www.w3.org/TR/html5/forms.html#range-state-(type=range)) 30054 * for more info. 30055 * 30056 * This has the following consequences for Angular: 30057 * 30058 * Since the element value should always reflect the current model value, a range input 30059 * will set the bound ngModel expression to the value that the browser has set for the 30060 * input element. For example, in the following input `<input type="range" ng-input-range ng-model="model.value">`, 30061 * if the application sets `model.value = null`, the browser will set the input to `'50'`. 30062 * Angular will then set the model to `50`, to prevent input and model value being out of sync. 30063 * 30064 * That means the model for range will immediately be set to `50` after `ngModel` has been 30065 * initialized. It also means a range input can never have the required error. 30066 * 30067 * This does not only affect changes to the model value, but also to the values of the `min`, 30068 * `max`, and `step` attributes. When these change in a way that will cause the browser to modify 30069 * the input value, Angular will also update the model value. 30070 * 30071 * Automatic value adjustment also means that a range input element can never have the `required`, 30072 * `min`, or `max` errors. 30073 * 30074 * However, `step` is currently only fully implemented by Firefox. Other browsers have problems 30075 * when the step value changes dynamically - they do not adjust the element value correctly, but
30076 * instead may set the `stepMismatch` error. If that's the case, the Angular will set the `step` 30077 * error on the input, and set the model to `undefined`. 30078 * 30079 * Note that `input[range]` is not compatible with `ngMax`, `ngMin`, and `ngStep`, because they do 30080 * not set the `min` and `max` attributes, which means that the browser won't automatically adjust 30081 * the input value based on their values, and will always assume min = 0, max = 100, and step = 1. 30082 * 30083 * @param ngInputRange The presense of this attribute enables the built-in support for 30084 * `input[range]`. 30085 * @param {string} ngModel Assignable angular expression to data-bind to. 30086 * @param {string=} name Property name of the form under which the control is published. 30087 * @param {string=} min Sets the `min` validation to ensure that the value entered is greater 30088 * than `min`. Can be interpolated. 30089 * @param {string=} max Sets the `max` validation to ensure that the value entered is less than `max`. 30090 * Can be interpolated. 30091 * @param {string=} step Sets the `step` validation to ensure that the value entered matches the `step` 30092 * Can be interpolated. 30093 * @param {string=} ngChange Angular expression to be executed when the ngModel value changes due 30094 * to user interaction with the input element. 30095 * @param {expression=} ngChecked If the expression is truthy, then the `checked` attribute will be set on the 30096 * element. **Note** : `ngChecked` should not be used alongside `ngModel`. 30097 * Checkout {@link ng.directive:ngChecked ngChecked} for usage. 30098 * 30099 * @example 30100 <example name="range-input-directive" module="rangeExample"> 30101 <file name="index.html"> 30102 <script> 30103 angular.module('rangeExample', []) 30104 .controller('ExampleController', ['$scope', function($scope) { 30105 $scope.value = 75; 30106 $scope.min = 10; 30107 $scope.max = 90; 30108 }]); 30109 </script>
30110 <form name="myForm" ng-controller="ExampleController"> 30111 30112 Model as range: <input type="range" ng-input-range name="range" ng-model="value" min="{{min}}" max="{{max}}"> 30113 <hr> 30114 Model as number: <input type="number" ng-model="value"><br> 30115 Min: <input type="number" ng-model="min"><br> 30116 Max: <input type="number" ng-model="max"><br> 30117 value = <code>{{value}}</code><br/> 30118 myForm.range.$valid = <code>{{myForm.range.$valid}}</code><br/> 30119 myForm.range.$error = <code>{{myForm.range.$error}}</code> 30120 </form> 30121 </file> 30122 </example> 30123 30124 * ## Range Input with ngMin & ngMax attributes 30125 30126 * @example 30127 <example name="range-input-directive-ng" module="rangeExample"> 30128 <file name="index.html"> 30129 <script> 30130 angular.module('rangeExample', []) 30131 .controller('ExampleController', ['$scope', function($scope) { 30132 $scope.value = 75; 30133 $scope.min = 10; 30134 $scope.max = 90; 30135 }]); 30136 </script>
30137 <form name="myForm" ng-controller="ExampleController"> 30138 Model as range: <input type="range" ng-input-range name="range" ng-model="value" ng-min="min" ng-max="max"> 30139 <hr> 30140 Model as number: <input type="number" ng-model="value"><br> 30141 Min: <input type="number" ng-model="min"><br> 30142 Max: <input type="number" ng-model="max"><br> 30143 value = <code>{{value}}</code><br/> 30144 myForm.range.$valid = <code>{{myForm.range.$valid}}</code><br/> 30145 myForm.range.$error = <code>{{myForm.range.$error}}</code> 30146 </form> 30147 </file> 30148 </example> 30149 30150 */ 30151 'range': rangeInputType, 30152 30153 /** 30154 * @ngdoc input 30155 * @name input[checkbox] 30156 * 30157 * @description 30158 * HTML checkbox. 30159 * 30160 * @param {string} ngModel Assignable angular expression to data-bind to. 30161 * @param {string=} name Property name of the form under which the control is published. 30162 * @param {expression=} ngTrueValue The value to which the expression should be set when selected. 30163 * @param {expression=} ngFalseValue The value to which the expression should be set when not selected. 30164 * @param {string=} ngChange Angular expression to be executed when input changes due to user 30165 * interaction with the input element. 30166 * 30167 * @example 30168 <example name="checkbox-input-directive" module="checkboxExample"> 30169 <file name="index.html"> 30170 <script> 30171 angular.module('checkboxExample', []) 30172 .controller('ExampleController', ['$scope', function($scope) {
30173 $scope.checkboxModel = { 30174 value1 : true, 30175 value2 : 'YES' 30176 }; 30177 }]); 30178 </script> 30179 <form name="myForm" ng-controller="ExampleController"> 30180 <label>Value1: 30181 <input type="checkbox" ng-model="checkboxModel.value1"> 30182 </label><br/> 30183 <label>Value2: 30184 <input type="checkbox" ng-model="checkboxModel.value2" 30185 ng-true-value="'YES'" ng-false-value="'NO'"> 30186 </label><br/> 30187 <tt>value1 = {{checkboxModel.value1}}</tt><br/> 30188 <tt>value2 = {{checkboxModel.value2}}</tt><br/> 30189 </form> 30190 </file> 30191 <file name="protractor.js" type="protractor"> 30192 it('should change state', function() { 30193 var value1 = element(by.binding('checkboxModel.value1')); 30194 var value2 = element(by.binding('checkboxModel.value2')); 30195 30196 expect(value1.getText()).toContain('true'); 30197 expect(value2.getText()).toContain('YES'); 30198 30199 element(by.model('checkboxModel.value1')).click(); 30200 element(by.model('checkboxModel.value2')).click(); 30201 30202 expect(value1.getText()).toContain('false'); 30203 expect(value2.getText()).toContain('NO'); 30204 }); 30205 </file> 30206 </example> 30207 */ 30208 'checkbox': checkboxInputType, 30209 30210 'hidden': noop, 30211 'button': noop, 30212 'submit': noop, 30213 'reset': noop, 30214 'file': noop 30215}; 30216 30217function stringBasedInputType(ctrl) { 30218 ctrl.$formatters.push(function(value) { 30219 return ctrl.$isEmpty(value) ? value : value.toString(); 30220 }); 30221} 30222 30223function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { 30224 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 30225 stringBasedInputType(ctrl); 30226} 30227 30228function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) { 30229 var type = lowercase(element[0].type); 30230 30231 // In composition mode, users are still inputting intermediate text buffer, 30232 // hold the listener until composition is done. 30233 // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent 30234 if (!$sniffer.android) { 30235 var composing = false; 30236 30237 element.on('compositionstart', function() { 30238 composing = true; 30239 }); 30240 30241 element.on('compositionend', function() { 30242 composing = false; 30243 listener(); 30244 }); 30245 } 30246 30247 var timeout; 30248 30249 var listener = function(ev) { 30250 if (timeout) { 30251 $browser.defer.cancel(timeout); 30252 timeout = null; 30253 } 30254 if (composing) return; 30255 var value = element.val(), 30256 event = ev && ev.type; 30257 30258 // By default we will trim the value 30259 // If the attribute ng-trim exists we will avoid trimming 30260 // If input type is 'password', the value is never trimmed 30261 if (type !== 'password' && (!attr.ngTrim || attr.ngTrim !== 'false')) { 30262 value = trim(value); 30263 } 30264 30265 // If a control is suffering from bad input (due to native validators), browsers discard its 30266 // value, so it may be necessary to revalidate (by calling $setViewValue again) even if the 30267 // control's value is the same empty value twice in a row. 30268 if (ctrl.$viewValue !== value || (value === '' && ctrl.$$hasNativeValidators)) { 30269 ctrl.$setViewValue(value, event); 30270 } 30271 }; 30272 30273 // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the 30274 // input event on backspace, delete or cut 30275 if ($sniffer.hasEvent('input')) { 30276 element.on('input', listener); 30277 } else { 30278 var deferListener = function(ev, input, origValue) { 30279 if (!timeout) { 30280 timeout = $browser.defer(function() { 30281 timeout = null; 30282 if (!input || input.value !== origValue) { 30283 listener(ev); 30284 } 30285 }); 30286 } 30287 }; 30288 30289 element.on('keydown', /** @this */ function(event) { 30290 var key = event.keyCode; 30291 30292 // ignore 30293 // command modifiers arrows 30294 if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; 30295 30296 deferListener(event, this, this.value); 30297 }); 30298 30299 // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it 30300 if ($sniffer.hasEvent('paste')) { 30301 element.on('paste cut', deferListener); 30302 } 30303 } 30304 30305 // if user paste into input using mouse on older browser 30306 // or form autocomplete on newer browser, we need "change" event to catch it 30307 element.on('change', listener); 30308 30309 // Some native input types (date-family) have the ability to change validity without 30310 // firing any input/change events. 30311 // For these event types, when native validators are present and the browser supports the type, 30312 // check for validity changes on various DOM events. 30313 if (PARTIAL_VALIDATION_TYPES[type] && ctrl.$$hasNativeValidators && type === attr.type) { 30314 element.on(PARTIAL_VALIDATION_EVENTS, /** @this */ function(ev) { 30315 if (!timeout) {
30316 var validity = this[VALIDITY_STATE_PROPERTY]; 30317 var origBadInput = validity.badInput; 30318 var origTypeMismatch = validity.typeMismatch; 30319 timeout = $browser.defer(function() { 30320 timeout = null; 30321 if (validity.badInput !== origBadInput || validity.typeMismatch !== origTypeMismatch) { 30322 listener(ev); 30323 } 30324 }); 30325 } 30326 }); 30327 } 30328 30329 ctrl.$render = function() { 30330 // Workaround for Firefox validation #12102. 30331 var value = ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue; 30332 if (element.val() !== value) { 30333 element.val(value); 30334 } 30335 }; 30336} 30337 30338function weekParser(isoWeek, existingDate) { 30339 if (isDate(isoWeek)) { 30340 return isoWeek; 30341 } 30342 30343 if (isString(isoWeek)) { 30344 WEEK_REGEXP.lastIndex = 0; 30345 var parts = WEEK_REGEXP.exec(isoWeek); 30346 if (parts) { 30347 var year = +parts[1], 30348 week = +parts[2], 30349 hours = 0, 30350 minutes = 0, 30351 seconds = 0, 30352 milliseconds = 0, 30353 firstThurs = getFirstThursdayOfYear(year), 30354 addDays = (week - 1) * 7; 30355 30356 if (existingDate) { 30357 hours = existingDate.getHours(); 30358 minutes = existingDate.getMinutes(); 30359 seconds = existingDate.getSeconds(); 30360 milliseconds = existingDate.getMilliseconds(); 30361 } 30362 30363 return new Date(year, 0, firstThurs.getDate() + addDays, hours, minutes, seconds, milliseconds); 30364 } 30365 } 30366 30367 return NaN; 30368} 30369 30370function createDateParser(regexp, mapping) { 30371 return function(iso, date) { 30372 var parts, map; 30373 30374 if (isDate(iso)) { 30375 return iso; 30376 } 30377 30378 if (isString(iso)) { 30379 // When a date is JSON'ified to wraps itself inside of an extra 30380 // set of double quotes. This makes the date parsing code unable 30381 // to match the date string and parse it as a date. 30382 if (iso.charAt(0) === '"' && iso.charAt(iso.length - 1) === '"') { 30383 iso = iso.substring(1, iso.length - 1); 30384 } 30385 if (ISO_DATE_REGEXP.test(iso)) { 30386 return new Date(iso); 30387 } 30388 regexp.lastIndex = 0; 30389 parts = regexp.exec(iso); 30390 30391 if (parts) { 30392 parts.shift(); 30393 if (date) { 30394 map = { 30395 yyyy: date.getFullYear(), 30396 MM: date.getMonth() + 1, 30397 dd: date.getDate(), 30398 HH: date.getHours(), 30399 mm: date.getMinutes(), 30400 ss: date.getSeconds(), 30401 sss: date.getMilliseconds() / 1000 30402 }; 30403 } else { 30404 map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 }; 30405 } 30406 30407 forEach(parts, function(part, index) { 30408 if (index < mapping.length) { 30409 map[mapping[index]] = +part; 30410 } 30411 }); 30412 return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss * 1000 || 0); 30413 } 30414 } 30415 30416 return NaN; 30417 }; 30418} 30419 30420function createDateInputType(type, regexp, parseDate, format) { 30421 return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) { 30422 badInputChecker(scope, element, attr, ctrl); 30423 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 30424 var timezone = ctrl && ctrl.$options && ctrl.$options.timezone; 30425 var previousDate; 30426 30427 ctrl.$$parserName = type; 30428 ctrl.$parsers.push(function(value) { 30429 if (ctrl.$isEmpty(value)) return null; 30430 if (regexp.test(value)) { 30431 // Note: We cannot read ctrl.$modelValue, as there might be a different 30432 // parser/formatter in the processing chain so that the model 30433 // contains some different data format! 30434 var parsedDate = parseDate(value, previousDate); 30435 if (timezone) { 30436 parsedDate = convertTimezoneToLocal(parsedDate, timezone); 30437 } 30438 return parsedDate; 30439 } 30440 return undefined; 30441 }); 30442 30443 ctrl.$formatters.push(function(value) { 30444 if (value && !isDate(value)) { 30445 throw ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value); 30446 } 30447 if (isValidDate(value)) { 30448 previousDate = value; 30449 if (previousDate && timezone) { 30450 previousDate = convertTimezoneToLocal(previousDate, timezone, true); 30451 } 30452 return $filter('date')(value, format, timezone); 30453 } else { 30454 previousDate = null; 30455 return ''; 30456 } 30457 }); 30458 30459 if (isDefined(attr.min) || attr.ngMin) { 30460 var minVal; 30461 ctrl.$validators.min = function(value) { 30462 return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal; 30463 }; 30464 attr.$observe('min', function(val) { 30465 minVal = parseObservedDateValue(val); 30466 ctrl.$validate(); 30467 }); 30468 } 30469 30470 if (isDefined(attr.max) || attr.ngMax) { 30471 var maxVal; 30472 ctrl.$validators.max = function(value) { 30473 return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal; 30474 }; 30475 attr.$observe('max', function(val) { 30476 maxVal = parseObservedDateValue(val); 30477 ctrl.$validate(); 30478 }); 30479 } 30480 30481 function isValidDate(value) { 30482 // Invalid Date: getTime() returns NaN 30483 return value && !(value.getTime && value.getTime() !== value.getTime()); 30484 } 30485 30486 function parseObservedDateValue(val) { 30487 return isDefined(val) && !isDate(val) ? parseDate(val) || undefined : val; 30488 } 30489 }; 30490} 30491 30492function badInputChecker(scope, element, attr, ctrl) { 30493 var node = element[0]; 30494 var nativeValidation = ctrl.$$hasNativeValidators = isObject(node.validity); 30495 if (nativeValidation) { 30496 ctrl.$parsers.push(function(value) {
30497 var validity = element.prop(VALIDITY_STATE_PROPERTY) || {}; 30498 return validity.badInput || validity.typeMismatch ? undefined : value; 30499 }); 30500 } 30501} 30502 30503function numberFormatterParser(ctrl) { 30504 ctrl.$$parserName = 'number'; 30505 ctrl.$parsers.push(function(value) { 30506 if (ctrl.$isEmpty(value)) return null; 30507 if (NUMBER_REGEXP.test(value)) return parseFloat(value); 30508 return undefined; 30509 }); 30510 30511 ctrl.$formatters.push(function(value) { 30512 if (!ctrl.$isEmpty(value)) { 30513 if (!isNumber(value)) { 30514 throw ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value); 30515 } 30516 value = value.toString(); 30517 } 30518 return value; 30519 }); 30520} 30521 30522function parseNumberAttrVal(val) { 30523 if (isDefined(val) && !isNumber(val)) { 30524 val = parseFloat(val); 30525 } 30526 return !isNumberNaN(val) ? val : undefined; 30527} 30528 30529function isNumberInteger(num) { 30530 // See http://stackoverflow.com/questions/14636536/how-to-check-if-a-variable-is-an-integer-in-javascript#14794066 30531 // (minus the assumption that `num` is a number) 30532 30533 // eslint-disable-next-line no-bitwise 30534 return (num | 0) === num; 30535} 30536 30537function countDecimals(num) { 30538 var numString = num.toString(); 30539 var decimalSymbolIndex = numString.indexOf('.'); 30540 30541 if (decimalSymbolIndex === -1) { 30542 if (-1 < num && num < 1) { 30543 // It may be in the exponential notation format (`1e-X`) 30544 var match = /e-(\d+)$/.exec(numString); 30545 30546 if (match) { 30547 return Number(match[1]); 30548 } 30549 } 30550 30551 return 0; 30552 } 30553 30554 return numString.length - decimalSymbolIndex - 1; 30555} 30556 30557function isValidForStep(viewValue, stepBase, step) { 30558 // At this point `stepBase` and `step` are expected to be non-NaN values 30559 // and `viewValue` is expected to be a valid stringified number. 30560 var value = Number(viewValue); 30561 30562 // Due to limitations in Floating Point Arithmetic (e.g. `0.3 - 0.2 !== 0.1` or 30563 // `0.5 % 0.1 !== 0`), we need to convert all numbers to integers. 30564 if (!isNumberInteger(value) || !isNumberInteger(stepBase) || !isNumberInteger(step)) { 30565 var decimalCount = Math.max(countDecimals(value), countDecimals(stepBase), countDecimals(step)); 30566 var multiplier = Math.pow(10, decimalCount); 30567 30568 value = value * multiplier; 30569 stepBase = stepBase * multiplier; 30570 step = step * multiplier; 30571 } 30572 30573 return (value - stepBase) % step === 0; 30574} 30575 30576function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { 30577 badInputChecker(scope, element, attr, ctrl); 30578 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 30579 numberFormatterParser(ctrl); 30580 30581 var minVal; 30582 var maxVal; 30583 30584 if (isDefined(attr.min) || attr.ngMin) { 30585 ctrl.$validators.min = function(value) { 30586 return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal; 30587 }; 30588 30589 attr.$observe('min', function(val) { 30590 minVal = parseNumberAttrVal(val); 30591 // TODO(matsko): implement validateLater to reduce number of validations 30592 ctrl.$validate(); 30593 }); 30594 } 30595 30596 if (isDefined(attr.max) || attr.ngMax) { 30597 ctrl.$validators.max = function(value) { 30598 return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal; 30599 }; 30600 30601 attr.$observe('max', function(val) { 30602 maxVal = parseNumberAttrVal(val); 30603 // TODO(matsko): implement validateLater to reduce number of validations 30604 ctrl.$validate(); 30605 }); 30606 } 30607} 30608 30609function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) { 30610 badInputChecker(scope, element, attr, ctrl); 30611 numberFormatterParser(ctrl); 30612 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 30613 30614 var supportsRange = ctrl.$$hasNativeValidators && element[0].type === 'range', 30615 minVal = supportsRange ? 0 : undefined, 30616 maxVal = supportsRange ? 100 : undefined, 30617 stepVal = supportsRange ? 1 : undefined, 30618 validity = element[0].validity, 30619 hasMinAttr = isDefined(attr.min), 30620 hasMaxAttr = isDefined(attr.max), 30621 hasStepAttr = isDefined(attr.step); 30622 30623 var originalRender = ctrl.$render; 30624 30625 ctrl.$render = supportsRange && isDefined(validity.rangeUnderflow) && isDefined(validity.rangeOverflow) ? 30626 //Browsers that implement range will set these values automatically, but reading the adjusted values after 30627 //$render would cause the min / max validators to be applied with the wrong value 30628 function rangeRender() { 30629 originalRender(); 30630 ctrl.$setViewValue(element.val()); 30631 } : 30632 originalRender; 30633 30634 if (hasMinAttr) { 30635 ctrl.$validators.min = supportsRange ? 30636 // Since all browsers set the input to a valid value, we don't need to check validity 30637 function noopMinValidator() { return true; } : 30638 // non-support browsers validate the min val 30639 function minValidator(modelValue, viewValue) { 30640 return ctrl.$isEmpty(viewValue) || isUndefined(minVal) || viewValue >= minVal; 30641 }; 30642 30643 setInitialValueAndObserver('min', minChange); 30644 } 30645 30646 if (hasMaxAttr) { 30647 ctrl.$validators.max = supportsRange ? 30648 // Since all browsers set the input to a valid value, we don't need to check validity 30649 function noopMaxValidator() { return true; } : 30650 // non-support browsers validate the max val 30651 function maxValidator(modelValue, viewValue) { 30652 return ctrl.$isEmpty(viewValue) || isUndefined(maxVal) || viewValue <= maxVal; 30653 }; 30654 30655 setInitialValueAndObserver('max', maxChange); 30656 } 30657 30658 if (hasStepAttr) { 30659 ctrl.$validators.step = supportsRange ? 30660 function nativeStepValidator() { 30661 // Currently, only FF implements the spec on step change correctly (i.e. adjusting the 30662 // input element value to a valid value). It's possible that other browsers set the stepMismatch 30663 // validity error instead, so we can at least report an error in that case. 30664 return !validity.stepMismatch; 30665 } : 30666 // ngStep doesn't set the setp attr, so the browser doesn't adjust the input value as setting step would 30667 function stepValidator(modelValue, viewValue) { 30668 return ctrl.$isEmpty(viewValue) || isUndefined(stepVal) || 30669 isValidForStep(viewValue, minVal || 0, stepVal); 30670 }; 30671 30672 setInitialValueAndObserver('step', stepChange); 30673 } 30674
30675 function setInitialValueAndObserver(htmlAttrName, changeFn) { 30676 // interpolated attributes set the attribute value only after a digest, but we need the 30677 // attribute value when the input is first rendered, so that the browser can adjust the 30678 // input value based on the min/max value 30679 element.attr(htmlAttrName, attr[htmlAttrName]); 30680 attr.$observe(htmlAttrName, changeFn); 30681 } 30682 30683 function minChange(val) { 30684 minVal = parseNumberAttrVal(val); 30685 // ignore changes before model is initialized 30686 if (isNumberNaN(ctrl.$modelValue)) { 30687 return; 30688 } 30689 30690 if (supportsRange) { 30691 var elVal = element.val(); 30692 // IE11 doesn't set the el val correctly if the minVal is greater than the element value 30693 if (minVal > elVal) { 30694 elVal = minVal; 30695 element.val(elVal); 30696 } 30697 ctrl.$setViewValue(elVal); 30698 } else { 30699 // TODO(matsko): implement validateLater to reduce number of validations 30700 ctrl.$validate(); 30701 } 30702 } 30703 30704 function maxChange(val) { 30705 maxVal = parseNumberAttrVal(val); 30706 // ignore changes before model is initialized 30707 if (isNumberNaN(ctrl.$modelValue)) { 30708 return; 30709 } 30710 30711 if (supportsRange) { 30712 var elVal = element.val(); 30713 // IE11 doesn't set the el val correctly if the maxVal is less than the element value 30714 if (maxVal < elVal) { 30715 element.val(maxVal); 30716 // IE11 and Chrome don't set the value to the minVal when max < min 30717 elVal = maxVal < minVal ? minVal : maxVal; 30718 } 30719 ctrl.$setViewValue(elVal); 30720 } else { 30721 // TODO(matsko): implement validateLater to reduce number of validations 30722 ctrl.$validate(); 30723 } 30724 } 30725 30726 function stepChange(val) { 30727 stepVal = parseNumberAttrVal(val); 30728 // ignore changes before model is initialized 30729 if (isNumberNaN(ctrl.$modelValue)) { 30730 return; 30731 } 30732 30733 // Some browsers don't adjust the input value correctly, but set the stepMismatch error 30734 if (supportsRange && ctrl.$viewValue !== element.val()) { 30735 ctrl.$setViewValue(element.val()); 30736 } else { 30737 // TODO(matsko): implement validateLater to reduce number of validations 30738 ctrl.$validate(); 30739 } 30740 } 30741} 30742 30743function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { 30744 // Note: no badInputChecker here by purpose as `url` is only a validation 30745 // in browsers, i.e. we can always read out input.value even if it is not valid! 30746 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 30747 stringBasedInputType(ctrl); 30748 30749 ctrl.$$parserName = 'url'; 30750 ctrl.$validators.url = function(modelValue, viewValue) { 30751 var value = modelValue || viewValue; 30752 return ctrl.$isEmpty(value) || URL_REGEXP.test(value); 30753 }; 30754} 30755 30756function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { 30757 // Note: no badInputChecker here by purpose as `url` is only a validation 30758 // in browsers, i.e. we can always read out input.value even if it is not valid! 30759 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 30760 stringBasedInputType(ctrl); 30761 30762 ctrl.$$parserName = 'email'; 30763 ctrl.$validators.email = function(modelValue, viewValue) { 30764 var value = modelValue || viewValue; 30765 return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value); 30766 }; 30767} 30768 30769function radioInputType(scope, element, attr, ctrl) { 30770 // make the name unique, if not defined 30771 if (isUndefined(attr.name)) { 30772 element.attr('name', nextUid()); 30773 } 30774 30775 var listener = function(ev) { 30776 if (element[0].checked) { 30777 ctrl.$setViewValue(attr.value, ev && ev.type); 30778 } 30779 }; 30780 30781 element.on('click', listener); 30782 30783 ctrl.$render = function() { 30784 var value = attr.value; 30785 // We generally use strict comparison. This is behavior we cannot change without a BC. 30786 // eslint-disable-next-line eqeqeq 30787 element[0].checked = (value == ctrl.$viewValue); 30788 }; 30789 30790 attr.$observe('value', ctrl.$render); 30791} 30792 30793function parseConstantExpr($parse, context, name, expression, fallback) { 30794 var parseFn; 30795 if (isDefined(expression)) { 30796 parseFn = $parse(expression); 30797 if (!parseFn.constant) { 30798 throw ngModelMinErr('constexpr', 'Expected constant expression for `{0}`, but saw ' + 30799 '`{1}`.', name, expression); 30800 } 30801 return parseFn(context); 30802 } 30803 return fallback; 30804} 30805 30806function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) { 30807 var trueValue = parseConstantExpr($parse, scope, 'ngTrueValue', attr.ngTrueValue, true); 30808 var falseValue = parseConstantExpr($parse, scope, 'ngFalseValue', attr.ngFalseValue, false); 30809 30810 var listener = function(ev) { 30811 ctrl.$setViewValue(element[0].checked, ev && ev.type); 30812 }; 30813 30814 element.on('click', listener); 30815 30816 ctrl.$render = function() { 30817 element[0].checked = ctrl.$viewValue; 30818 }; 30819
30820 // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false` 30821 // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert 30822 // it to a boolean. 30823 ctrl.$isEmpty = function(value) { 30824 return value === false; 30825 }; 30826 30827 ctrl.$formatters.push(function(value) { 30828 return equals(value, trueValue); 30829 }); 30830 30831 ctrl.$parsers.push(function(value) { 30832 return value ? trueValue : falseValue; 30833 }); 30834} 30835 30836 30837/** 30838 * @ngdoc directive 30839 * @name textarea 30840 * @restrict E 30841 * 30842 * @description 30843 * HTML textarea element control with angular data-binding. The data-binding and validation 30844 * properties of this element are exactly the same as those of the 30845 * {@link ng.directive:input input element}. 30846 * 30847 * @param {string} ngModel Assignable angular expression to data-bind to. 30848 * @param {string=} name Property name of the form under which the control is published. 30849 * @param {string=} required Sets `required` validation error key if the value is not entered. 30850 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 30851 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 30852 * `required` when you want to data-bind to the `required` attribute. 30853 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 30854 * minlength. 30855 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 30856 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any 30857 * length. 30858 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 30859 * does not match a RegExp found by evaluating the Angular expression given in the attribute value. 30860 * If the expression evaluates to a RegExp object, then this is used directly. 30861 * If the expression evaluates to a string, then it will be converted to a RegExp 30862 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 30863 * `new RegExp('^abc$')`.<br /> 30864 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 30865 * start at the index of the last search's match, thus not taking the whole input value into 30866 * account. 30867 * @param {string=} ngChange Angular expression to be executed when input changes due to user 30868 * interaction with the input element. 30869 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 30870 * 30871 * @knownIssue 30872 * 30873 * When specifying the `placeholder` attribute of `<textarea>`, Internet Explorer will temporarily 30874 * insert the placeholder value as the textarea's content. If the placeholder value contains 30875 * interpolation (`{{ ... }}`), an error will be logged in the console when Angular tries to update 30876 * the value of the by-then-removed text node. This doesn't affect the functionality of the 30877 * textarea, but can be undesirable. 30878 * 30879 * You can work around this Internet Explorer issue by using `ng-attr-placeholder` instead of 30880 * `placeholder` on textareas, whenever you need interpolation in the placeholder value. You can 30881 * find more details on `ngAttr` in the 30882 * [Interpolation](guide/interpolation#-ngattr-for-binding-to-arbitrary-attributes) section of the 30883 * Developer Guide. 30884 */ 30885 30886 30887/** 30888 * @ngdoc directive 30889 * @name input 30890 * @restrict E 30891 * 30892 * @description 30893 * HTML input element control. When used together with {@link ngModel `ngModel`}, it provides data-binding, 30894 * input state control, and validation. 30895 * Input control follows HTML5 input types and polyfills the HTML5 validation behavior for older browsers. 30896 * 30897 * <div class="alert alert-warning"> 30898 * **Note:** Not every feature offered is available for all input types. 30899 * Specifically, data binding and event handling via `ng-model` is unsupported for `input[file]`. 30900 * </div> 30901 * 30902 * @param {string} ngModel Assignable angular expression to data-bind to. 30903 * @param {string=} name Property name of the form under which the control is published. 30904 * @param {string=} required Sets `required` validation error key if the value is not entered. 30905 * @param {boolean=} ngRequired Sets `required` attribute if set to true 30906 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 30907 * minlength. 30908 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 30909 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any 30910 * length. 30911 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 30912 * value does not match a RegExp found by evaluating the Angular expression given in the attribute value. 30913 * If the expression evaluates to a RegExp object, then this is used directly. 30914 * If the expression evaluates to a string, then it will be converted to a RegExp 30915 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 30916 * `new RegExp('^abc$')`.<br /> 30917 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 30918 * start at the index of the last search's match, thus not taking the whole input value into 30919 * account. 30920 * @param {string=} ngChange Angular expression to be executed when input changes due to user 30921 * interaction with the input element. 30922 * @param {boolean=} [ngTrim=true] If set to false Angular
vendor: 4,623 bytes, lines 30922-31032
30922 will not automatically trim the input. 30923 * This parameter is ignored for input[type=password] controls, which will never trim the 30924 * input. 30925 * 30926 * @example 30927 <example name="input-directive" module="inputExample"> 30928 <file name="index.html"> 30929 <script> 30930 angular.module('inputExample', []) 30931 .controller('ExampleController', ['$scope', function($scope) { 30932 $scope.user = {name: 'guest', last: 'visitor'}; 30933 }]); 30934 </script> 30935 <div ng-controller="ExampleController"> 30936 <form name="myForm"> 30937 <label> 30938 User name: 30939 <input type="text" name="userName" ng-model="user.name" required> 30940 </label> 30941 <div role="alert"> 30942 <span class="error" ng-show="myForm.userName.$error.required"> 30943 Required!</span> 30944 </div> 30945 <label> 30946 Last name: 30947 <input type="text" name="lastName" ng-model="user.last" 30948 ng-minlength="3" ng-maxlength="10"> 30949 </label> 30950 <div role="alert"> 30951 <span class="error" ng-show="myForm.lastName.$error.minlength"> 30952 Too short!</span> 30953 <span class="error" ng-show="myForm.lastName.$error.maxlength"> 30954 Too long!</span> 30955 </div> 30956 </form> 30957 <hr> 30958 <tt>user = {{user}}</tt><br/> 30959 <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br/> 30960 <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br/> 30961 <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br/> 30962 <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br/> 30963 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 30964 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 30965 <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br/> 30966 <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br/> 30967 </div> 30968 </file> 30969 <file name="protractor.js" type="protractor"> 30970 var user = element(by.exactBinding('user')); 30971 var userNameValid = element(by.binding('myForm.userName.$valid')); 30972 var lastNameValid = element(by.binding('myForm.lastName.$valid')); 30973 var lastNameError = element(by.binding('myForm.lastName.$error')); 30974 var formValid = element(by.binding('myForm.$valid')); 30975 var userNameInput = element(by.model('user.name')); 30976 var userLastInput = element(by.model('user.last')); 30977 30978 it('should initialize to model', function() { 30979 expect(user.getText()).toContain('{"name":"guest","last":"visitor"}'); 30980 expect(userNameValid.getText()).toContain('true'); 30981 expect(formValid.getText()).toContain('true'); 30982 }); 30983 30984 it('should be invalid if empty when required', function() { 30985 userNameInput.clear(); 30986 userNameInput.sendKeys(''); 30987 30988 expect(user.getText()).toContain('{"last":"visitor"}'); 30989 expect(userNameValid.getText()).toContain('false'); 30990 expect(formValid.getText()).toContain('false'); 30991 }); 30992 30993 it('should be valid if empty when min length is set', function() { 30994 userLastInput.clear(); 30995 userLastInput.sendKeys(''); 30996 30997 expect(user.getText()).toContain('{"name":"guest","last":""}'); 30998 expect(lastNameValid.getText()).toContain('true'); 30999 expect(formValid.getText()).toContain('true'); 31000 }); 31001 31002 it('should be invalid if less than required min length', function() { 31003 userLastInput.clear(); 31004 userLastInput.sendKeys('xx'); 31005 31006 expect(user.getText()).toContain('{"name":"guest"}'); 31007 expect(lastNameValid.getText()).toContain('false'); 31008 expect(lastNameError.getText()).toContain('minlength'); 31009 expect(formValid.getText()).toContain('false'); 31010 }); 31011 31012 it('should be invalid if longer than max length', function() { 31013 userLastInput.clear(); 31014 userLastInput.sendKeys('some ridiculously long name'); 31015 31016 expect(user.getText()).toContain('{"name":"guest"}'); 31017 expect(lastNameValid.getText()).toContain('false'); 31018 expect(lastNameError.getText()).toContain('maxlength'); 31019 expect(formValid.getText()).toContain('false'); 31020 }); 31021 </file> 31022 </example> 31023 */ 31024var inputDirective = ['$browser', '$sniffer', '$filter', '$parse', 31025 function($browser, $sniffer, $filter, $parse) { 31026 return { 31027 restrict: 'E', 31028 require: ['?ngModel'], 31029 link: { 31030 pre: function(scope, element, attr, ctrls) { 31031 if (ctrls[0]) { 31032
31032var type = lowercase(attr.type); 31033 if ((type === 'range') && !attr.hasOwnProperty('ngInputRange')) { 31034 type = 'text'; 31035 } 31036 (inputType[type] || inputType.text)(scope, element, attr, ctrls[0], $sniffer, 31037 $browser, $filter, $parse); 31038 } 31039 } 31040 } 31041 }; 31042}]; 31043 31044 31045 31046var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; 31047/** 31048 * @ngdoc directive 31049 * @name ngValue 31050 * 31051 * @description 31052 * Binds the given expression to the value of `<option>` or {@link input[radio] `input[radio]`}, 31053 * so that when the element is selected, the {@link ngModel `ngModel`} of that element is set to 31054 * the bound value. 31055 * 31056 * `ngValue` is useful when dynamically generating lists of radio buttons using 31057 * {@link ngRepeat `ngRepeat`}, as shown below. 31058 * 31059 * Likewise, `ngValue` can be used to generate `<option>` elements for 31060 * the {@link select `select`} element. In that case however, only strings are supported 31061 * for the `value `attribute, so the resulting `ngModel` will always be a string. 31062 * Support for `select` models with non-string values is available via `ngOptions`. 31063 * 31064 * @element input 31065 * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute 31066 * of the `input` element 31067 * 31068 * @example 31069 <example name="ngValue-directive" module="valueExample"> 31070 <file name="index.html"> 31071 <script> 31072 angular.module('valueExample', []) 31073 .controller('ExampleController', ['$scope', function($scope) { 31074 $scope.names = ['pizza', 'unicorns', 'robots']; 31075 $scope.my = { favorite: 'unicorns' }; 31076 }]); 31077 </script>
31078 <form ng-controller="ExampleController"> 31079 <h2>Which is your favorite?</h2> 31080 <label ng-repeat="name in names" for="{{name}}"> 31081 {{name}} 31082 <input type="radio" 31083 ng-model="my.favorite" 31084 ng-value="name" 31085 id="{{name}}" 31086 name="favorite"> 31087 </label> 31088 <div>You chose {{my.favorite}}</div> 31089 </form> 31090 </file> 31091 <file name="protractor.js" type="protractor"> 31092 var favorite = element(by.binding('my.favorite')); 31093 31094 it('should initialize to model', function() { 31095 expect(favorite.getText()).toContain('unicorns'); 31096 }); 31097 it('should bind the values to the inputs', function() { 31098 element.all(by.model('my.favorite')).get(0).click(); 31099 expect(favorite.getText()).toContain('pizza'); 31100 }); 31101 </file> 31102 </example> 31103 */ 31104var ngValueDirective = function() { 31105 return { 31106 restrict: 'A', 31107 priority: 100, 31108 compile: function(tpl, tplAttr) { 31109 if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) { 31110 return function ngValueConstantLink(scope, elm, attr) { 31111 attr.$set('value', scope.$eval(attr.ngValue)); 31112 }; 31113 } else { 31114 return function ngValueLink(scope, elm, attr) { 31115 scope.$watch(attr.ngValue, function valueWatchAction(value) { 31116 attr.$set('value', value); 31117 }); 31118 }; 31119 } 31120 } 31121 }; 31122}; 31123 31124/** 31125 * @ngdoc directive 31126 * @name ngBind 31127 * @restrict AC 31128 * 31129 * @description 31130 * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element 31131 * with the value of a given expression, and to update the text content when the value of that 31132 * expression changes. 31133 * 31134 * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like 31135 * `{{ expression }}` which is similar but less verbose. 31136 * 31137 * It is preferable to use `ngBind` instead of `{{ expression }}` if a template is momentarily 31138 * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an 31139 * element attribute, it makes the bindings invisible to the user while the page is loading. 31140 * 31141 * An alternative solution to this problem would be using the 31142 * {@link ng.directive:ngCloak ngCloak} directive. 31143 * 31144 * 31145 * @element ANY 31146 * @param {expression} ngBind {@link guide/expression Expression} to evaluate. 31147 * 31148 * @example 31149 * Enter a name in the Live Preview text box; the greeting below the text box changes instantly. 31150 <example module="bindExample" name="ng-bind"> 31151 <file name="index.html"> 31152 <script> 31153 angular.module('bindExample', []) 31154 .controller('ExampleController', ['$scope', function($scope) { 31155 $scope.name = 'Whirled'; 31156 }]); 31157 </script>
31158 <div ng-controller="ExampleController"> 31159 <label>Enter name: <input type="text" ng-model="name"></label><br> 31160 Hello <span ng-bind="name"></span>! 31161 </div> 31162 </file> 31163 <file name="protractor.js" type="protractor"> 31164 it('should check ng-bind', function() { 31165 var nameInput = element(by.model('name')); 31166 31167 expect(element(by.binding('name')).getText()).toBe('Whirled'); 31168 nameInput.clear(); 31169 nameInput.sendKeys('world'); 31170 expect(element(by.binding('name')).getText()).toBe('world'); 31171 }); 31172 </file> 31173 </example> 31174 */ 31175var ngBindDirective = ['$compile', function($compile) { 31176 return { 31177 restrict: 'AC', 31178 compile: function ngBindCompile(templateElement) { 31179 $compile.$$addBindingClass(templateElement); 31180 return function ngBindLink(scope, element, attr) { 31181 $compile.$$addBindingInfo(element, attr.ngBind); 31182 element = element[0]; 31183 scope.$watch(attr.ngBind, function ngBindWatchAction(value) { 31184 element.textContent = isUndefined(value) ? '' : value; 31185 }); 31186 }; 31187 } 31188 }; 31189}]; 31190 31191 31192/** 31193 * @ngdoc directive 31194 * @name ngBindTemplate 31195 * 31196 * @description 31197 * The `ngBindTemplate` directive specifies that the element 31198 * text content should be replaced with the interpolation of the template 31199 * in the `ngBindTemplate` attribute. 31200 * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}` 31201 * expressions. This directive is needed since some HTML elements 31202 * (such as TITLE and OPTION) cannot contain SPAN elements. 31203 * 31204 * @element ANY 31205 * @param {string} ngBindTemplate template of form 31206 * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval. 31207 * 31208 * @example 31209 * Try it here: enter text in text box and watch the greeting change. 31210 <example module="bindExample" name="ng-bind-template"> 31211 <file name="index.html"> 31212 <script> 31213 angular.module('bindExample', []) 31214 .controller('ExampleController', ['$scope', function($scope) { 31215 $scope.salutation = 'Hello'; 31216 $scope.name = 'World'; 31217 }]); 31218 </script>
31219 <div ng-controller="ExampleController"> 31220 <label>Salutation: <input type="text" ng-model="salutation"></label><br> 31221 <label>Name: <input type="text" ng-model="name"></label><br> 31222 <pre ng-bind-template="{{salutation}} {{name}}!"></pre> 31223 </div> 31224 </file> 31225 <file name="protractor.js" type="protractor"> 31226 it('should check ng-bind', function() { 31227 var salutationElem = element(by.binding('salutation')); 31228 var salutationInput = element(by.model('salutation')); 31229 var nameInput = element(by.model('name')); 31230 31231 expect(salutationElem.getText()).toBe('Hello World!'); 31232 31233 salutationInput.clear(); 31234 salutationInput.sendKeys('Greetings'); 31235 nameInput.clear(); 31236 nameInput.sendKeys('user'); 31237 31238 expect(salutationElem.getText()).toBe('Greetings user!'); 31239 }); 31240 </file> 31241 </example> 31242 */ 31243var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate, $compile) { 31244 return { 31245 compile: function ngBindTemplateCompile(templateElement) { 31246 $compile.$$addBindingClass(templateElement); 31247 return function ngBindTemplateLink(scope, element, attr) { 31248 var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate)); 31249 $compile.$$addBindingInfo(element, interpolateFn.expressions); 31250 element = element[0]; 31251 attr.$observe('ngBindTemplate', function(value) { 31252 element.textContent = isUndefined(value) ? '' : value; 31253 }); 31254 }; 31255 } 31256 }; 31257}]; 31258 31259 31260/** 31261 * @ngdoc directive 31262 * @name ngBindHtml 31263 * 31264 * @description 31265 * Evaluates the expression and inserts the resulting HTML into the element in a secure way. By default, 31266 * the resulting HTML content will be sanitized using the {@link ngSanitize.$sanitize $sanitize} service. 31267 * To utilize this functionality, ensure that `$sanitize` is available, for example, by including {@link 31268 * ngSanitize} in your module's dependencies (not in core Angular). In order to use {@link ngSanitize} 31269 * in your module's dependencies, you need to include "angular-sanitize.js" in your application. 31270 * 31271 * You may also bypass sanitization for values you know are safe. To do so, bind to 31272 * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example 31273 * under {@link ng.$sce#show-me-an-example-using-sce- Strict Contextual Escaping (SCE)}. 31274 * 31275 * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you 31276 * will have an exception (instead of an exploit.) 31277 * 31278 * @element ANY 31279 * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. 31280 * 31281 * @example 31282 31283 <example module="bindHtmlExample" deps="angular-sanitize.js" name="ng-bind-html"> 31284 <file name="index.html">
31285 <div ng-controller="ExampleController"> 31286 <p ng-bind-html="myHTML"></p> 31287 </div> 31288 </file> 31289 31290 <file name="script.js"> 31291 angular.module('bindHtmlExample', ['ngSanitize']) 31292 .controller('ExampleController', ['$scope', function($scope) { 31293 $scope.myHTML = 31294 'I am an <code>HTML</code>string with ' + 31295 '<a href="#">links!</a> and other <em>stuff</em>'; 31296 }]); 31297 </file> 31298 31299 <file name="protractor.js" type="protractor"> 31300 it('should check ng-bind-html', function() { 31301 expect(element(by.binding('myHTML')).getText()).toBe( 31302 'I am an HTMLstring with links! and other stuff'); 31303 }); 31304 </file> 31305 </example> 31306 */ 31307var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse, $compile) { 31308 return { 31309 restrict: 'A', 31310 compile: function ngBindHtmlCompile(tElement, tAttrs) { 31311 var ngBindHtmlGetter = $parse(tAttrs.ngBindHtml); 31312 var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function sceValueOf(val) { 31313 // Unwrap the value to compare the actual inner safe value, not the wrapper object. 31314 return $sce.valueOf(val); 31315 }); 31316 $compile.$$addBindingClass(tElement); 31317 31318 return function ngBindHtmlLink(scope, element, attr) { 31319 $compile.$$addBindingInfo(element, attr.ngBindHtml); 31320 31321 scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() { 31322 // The watched value is the unwrapped value. To avoid re-escaping, use the direct getter. 31323 var value = ngBindHtmlGetter(scope); 31324 element.html($sce.getTrustedHtml(value) || ''); 31325 }); 31326 }; 31327 } 31328 }; 31329}]; 31330 31331/** 31332 * @ngdoc directive 31333 * @name ngChange 31334 * 31335 * @description 31336 * Evaluate the given expression when the user changes the input. 31337 * The expression is evaluated immediately, unlike the JavaScript onchange event 31338 * which only triggers at the end of a change (usually, when the user leaves the 31339 * form element or presses the return key). 31340 * 31341 * The `ngChange` expression is only evaluated when a change in the input value causes 31342 * a new value to be committed to the model. 31343 * 31344 * It will not be evaluated: 31345 * * if the value returned from the `$parsers` transformation pipeline has not changed 31346 * * if the input has continued to be invalid since the model will stay `null` 31347 * * if the model is changed programmatically and not by a change to the input value 31348 * 31349 * 31350 * Note, this directive requires `ngModel` to be present. 31351 * 31352 * @element input 31353 * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change 31354 * in input value. 31355 * 31356 * @example 31357 * <example name="ngChange-directive" module="changeExample"> 31358 * <file name="index.html"> 31359 * <script> 31360 * angular.module('changeExample', []) 31361 * .controller('ExampleController', ['$scope', function($scope) { 31362 * $scope.counter = 0; 31363 * $scope.change = function() { 31364 * $scope.counter++; 31365 * }; 31366 * }]); 31367 * </script>
31368 * <div ng-controller="ExampleController"> 31369 * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" /> 31370 * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" /> 31371 * <label for="ng-change-example2">Confirmed</label><br /> 31372 * <tt>debug = {{confirmed}}</tt><br/> 31373 * <tt>counter = {{counter}}</tt><br/> 31374 * </div> 31375 * </file> 31376 * <file name="protractor.js" type="protractor"> 31377 * var counter = element(by.binding('counter')); 31378 * var debug = element(by.binding('confirmed')); 31379 * 31380 * it('should evaluate the expression if changing from view', function() { 31381 * expect(counter.getText()).toContain('0'); 31382 * 31383 * element(by.id('ng-change-example1')).click(); 31384 * 31385 * expect(counter.getText()).toContain('1'); 31386 * expect(debug.getText()).toContain('true'); 31387 * }); 31388 * 31389 * it('should not evaluate the expression if changing from model', function() { 31390 * element(by.id('ng-change-example2')).click(); 31391 31392 * expect(counter.getText()).toContain('0'); 31393 * expect(debug.getText()).toContain('true'); 31394 * }); 31395 * </file> 31396 * </example> 31397 */ 31398var ngChangeDirective = valueFn({ 31399 restrict: 'A', 31400 require: 'ngModel', 31401 link: function(scope, element, attr, ctrl) { 31402 ctrl.$viewChangeListeners.push(function() { 31403 scope.$eval(attr.ngChange); 31404 }); 31405 } 31406}); 31407 31408/* exported 31409 ngClassDirective, 31410 ngClassEvenDirective, 31411 ngClassOddDirective 31412*/ 31413 31414function classDirective(name, selector) { 31415 name = 'ngClass' + name; 31416 return ['$animate', function($animate) { 31417 return { 31418 restrict: 'AC', 31419 link: function(scope, element, attr) { 31420 var oldVal; 31421 31422 attr.$observe('class', function(value) { 31423 ngClassWatchAction(scope.$eval(attr[name])); 31424 }); 31425 31426 31427 if (name !== 'ngClass') { 31428 scope.$watch('$index', function($index, old$index) { 31429 /* eslint-disable no-bitwise */ 31430 var mod = $index & 1; 31431 if (mod !== (old$index & 1)) { 31432 var classes = arrayClasses(oldVal); 31433 if (mod === selector) { 31434 addClasses(classes); 31435 } else { 31436 removeClasses(classes); 31437 } 31438 } 31439 /* eslint-enable */ 31440 }); 31441 } 31442 31443 scope.$watch(attr[name], ngClassWatchAction, true); 31444 31445 function addClasses(classes) { 31446 var newClasses = digestClassCounts(classes, 1); 31447 attr.$addClass(newClasses); 31448 } 31449 31450 function removeClasses(classes) { 31451 var newClasses = digestClassCounts(classes, -1); 31452 attr.$removeClass(newClasses); 31453 } 31454 31455 function digestClassCounts(classes, count) { 31456 // Use createMap() to prevent class assumptions involving property 31457 // names in Object.prototype 31458 var classCounts = element.data('$classCounts') || createMap(); 31459 var classesToUpdate = []; 31460 forEach(classes, function(className) { 31461 if (count > 0 || classCounts[className]) { 31462 classCounts[className] = (classCounts[className] || 0) + count; 31463 if (classCounts[className] === +(count > 0)) { 31464 classesToUpdate.push(className); 31465 } 31466 } 31467 }); 31468 element.data('$classCounts', classCounts); 31469 return classesToUpdate.join(' '); 31470 } 31471 31472 function updateClasses(oldClasses, newClasses) { 31473 var toAdd = arrayDifference(newClasses, oldClasses); 31474 var toRemove = arrayDifference(oldClasses, newClasses); 31475 toAdd = digestClassCounts(toAdd, 1); 31476 toRemove = digestClassCounts(toRemove, -1); 31477 if (toAdd && toAdd.length) { 31478 $animate.addClass(element, toAdd); 31479 } 31480 if (toRemove && toRemove.length) { 31481 $animate.removeClass(element, toRemove); 31482 } 31483 } 31484 31485 function ngClassWatchAction(newVal) { 31486 // eslint-disable-next-line no-bitwise 31487 if (selector === true || (scope.$index & 1) === selector) { 31488 var newClasses = arrayClasses(newVal || []); 31489 if (!oldVal) { 31490 addClasses(newClasses); 31491 } else if (!equals(newVal,oldVal)) {
31492 var oldClasses = arrayClasses(oldVal); 31493 updateClasses(oldClasses, newClasses); 31494 } 31495 } 31496 if (isArray(newVal)) { 31497 oldVal = newVal.map(function(v) { return shallowCopy(v); }); 31498 } else { 31499 oldVal = shallowCopy(newVal); 31500 } 31501 } 31502 } 31503 }; 31504 31505 function arrayDifference(tokens1, tokens2) { 31506 var values = []; 31507 31508 outer: 31509 for (var i = 0; i < tokens1.length; i++) { 31510 var token = tokens1[i]; 31511 for (var j = 0; j < tokens2.length; j++) { 31512 if (token === tokens2[j]) continue outer; 31513 } 31514 values.push(token); 31515 } 31516 return values; 31517 } 31518 31519 function arrayClasses(classVal) { 31520 var classes = []; 31521 if (isArray(classVal)) { 31522 forEach(classVal, function(v) { 31523 classes = classes.concat(arrayClasses(v)); 31524 }); 31525 return classes; 31526 } else if (isString(classVal)) { 31527 return classVal.split(' '); 31528 } else if (isObject(classVal)) { 31529 forEach(classVal, function(v, k) { 31530 if (v) { 31531 classes = classes.concat(k.split(' ')); 31532 } 31533 }); 31534 return classes; 31535 } 31536 return classVal; 31537 } 31538 }]; 31539} 31540 31541/** 31542 * @ngdoc directive 31543 * @name ngClass 31544 * @restrict AC 31545 * 31546 * @description 31547 * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding 31548 * an expression that represents all classes to be added. 31549 * 31550 * The directive operates in three different ways, depending on which of three types the expression 31551 * evaluates to: 31552 * 31553 * 1. If the expression evaluates to a string, the string should be one or more space-delimited class 31554 * names. 31555 * 31556 * 2. If the expression evaluates to an object, then for each key-value pair of the 31557 * object with a truthy value the corresponding key is used as a class name. 31558 * 31559 * 3. If the expression evaluates to an array, each element of the array should either be a string as in 31560 * type 1 or an object as in type 2. This means that you can mix strings and objects together in an array 31561 * to give you more control over what CSS classes appear. See the code below for an example of this. 31562 * 31563 * 31564 * The directive won't add duplicate classes if a particular class was already set. 31565 * 31566 * When the expression changes, the previously added classes are removed and only then are the 31567 * new classes added. 31568 * 31569 * @knownIssue 31570 * You should not use {@link guide/interpolation interpolation} in the value of the `class` 31571 * attribute, when using the `ngClass` directive on the same element. 31572 * See {@link guide/interpolation#known-issues here} for more info. 31573 * 31574 * @animations 31575 * | Animation | Occurs | 31576 * |----------------------------------|-------------------------------------| 31577 * | {@link ng.$animate#addClass addClass} | just before the class is applied to the element | 31578 * | {@link ng.$animate#removeClass removeClass} | just before the class is removed from the element | 31579 * 31580 * @element ANY 31581 * @param {expression} ngClass {@link guide/expression Expression} to eval. The result 31582 * of the evaluation can be a string representing space delimited class 31583 * names, an array, or a map of class names to boolean values. In the case of a map, the 31584 * names of the properties whose values are truthy will be added as css classes to the 31585 * element. 31586 * 31587 * @example Example that demonstrates basic bindings via ngClass directive. 31588 <example name="ng-class"> 31589 <file name="index.html"> 31590 <p ng-class="{strike: deleted, bold: important, 'has-error': error}">Map Syntax Example</p> 31591 <label> 31592 <input type="checkbox" ng-model="deleted"> 31593 deleted (apply "strike" class) 31594 </label><br> 31595 <label> 31596 <input type="checkbox" ng-model="important"> 31597 important (apply "bold" class) 31598 </label><br> 31599 <label> 31600 <input type="checkbox" ng-model="error"> 31601 error (apply "has-error" class) 31602 </label> 31603 <hr> 31604 <p ng-class="style">Using String Syntax</p> 31605 <input type="text" ng-model="style" 31606 placeholder="Type: bold strike red" aria-label="Type: bold strike red"> 31607 <hr> 31608 <p ng-class="[style1, style2, style3]">Using Array Syntax</p> 31609 <input ng-model="style1" 31610 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red"><br> 31611 <input ng-model="style2" 31612 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 2"><br> 31613 <input ng-model="style3" 31614 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 3"><br> 31615 <hr> 31616 <p ng-class="[style4, {orange: warning}]">Using Array and Map Syntax</p> 31617 <input ng-model="style4" placeholder="Type: bold, strike" aria-label="Type: bold, strike"><br> 31618 <label><input type="checkbox" ng-model="warning"> warning (apply "orange" class)</label> 31619 </file> 31620 <file name="style.css"> 31621 .strike { 31622 text-decoration: line-through; 31623 } 31624 .bold { 31625 font-weight: bold; 31626 } 31627 .red { 31628 color: red; 31629 } 31630 .has-error { 31631 color: red; 31632 background-color: yellow; 31633 } 31634 .orange { 31635 color: orange; 31636 } 31637 </file> 31638 <file name="protractor.js" type="protractor"> 31639 var ps = element.all(by.css('p')); 31640 31641 it('should let you toggle the class', function() { 31642 31643 expect(ps.first().getAttribute('class')).not.toMatch(/bold/); 31644 expect(ps.first().getAttribute('class')).not.toMatch(/has-error/); 31645
31646 element(by.model('important')).click(); 31647 expect(ps.first().getAttribute('class')).toMatch(/bold/); 31648 31649 element(by.model('error')).click(); 31650 expect(ps.first().getAttribute('class')).toMatch(/has-error/); 31651 }); 31652 31653 it('should let you toggle string example', function() { 31654 expect(ps.get(1).getAttribute('class')).toBe(''); 31655 element(by.model('style')).clear(); 31656 element(by.model('style')).sendKeys('red'); 31657 expect(ps.get(1).getAttribute('class')).toBe('red'); 31658 }); 31659 31660 it('array example should have 3 classes', function() { 31661 expect(ps.get(2).getAttribute('class')).toBe(''); 31662 element(by.model('style1')).sendKeys('bold'); 31663 element(by.model('style2')).sendKeys('strike'); 31664 element(by.model('style3')).sendKeys('red'); 31665 expect(ps.get(2).getAttribute('class')).toBe('bold strike red'); 31666 }); 31667 31668 it('array with map example should have 2 classes', function() { 31669 expect(ps.last().getAttribute('class')).toBe(''); 31670 element(by.model('style4')).sendKeys('bold'); 31671 element(by.model('warning')).click(); 31672 expect(ps.last().getAttribute('class')).toBe('bold orange'); 31673 }); 31674 </file> 31675 </example> 31676 31677 ## Animations 31678 31679 The example below demonstrates how to perform animations using ngClass. 31680 31681 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-class"> 31682 <file name="index.html"> 31683 <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'"> 31684 <input id="clearbtn" type="button" value="clear" ng-click="myVar=''"> 31685 <br> 31686 <span class="base-class" ng-class="myVar">Sample Text</span> 31687 </file> 31688 <file name="style.css"> 31689 .base-class { 31690 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 31691 } 31692 31693 .base-class.my-class { 31694 color: red; 31695 font-size:3em; 31696 } 31697 </file> 31698 <file name="protractor.js" type="protractor"> 31699 it('should check ng-class', function() { 31700 expect(element(by.css('.base-class')).getAttribute('class')).not. 31701 toMatch(/my-class/); 31702 31703 element(by.id('setbtn')).click(); 31704 31705 expect(element(by.css('.base-class')).getAttribute('class')). 31706 toMatch(/my-class/); 31707 31708 element(by.id('clearbtn')).click(); 31709 31710 expect(element(by.css('.base-class')).getAttribute('class')).not. 31711 toMatch(/my-class/); 31712 }); 31713 </file> 31714 </example> 31715 31716 31717 ## ngClass and pre-existing CSS3 Transitions/Animations 31718 The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure. 31719 Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder 31720 any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure 31721 to view the step by step details of {@link $animate#addClass $animate.addClass} and 31722 {@link $animate#removeClass $animate.removeClass}. 31723 */ 31724var ngClassDirective = classDirective('', true); 31725 31726/** 31727 * @ngdoc directive 31728 * @name ngClassOdd 31729 * @restrict AC 31730 * 31731 * @description 31732 * The `ngClassOdd` and `ngClassEven` directives work exactly as 31733 * {@link ng.directive:ngClass ngClass}, except they work in 31734 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 31735 * 31736 * This directive can be applied only within the scope of an 31737 * {@link ng.directive:ngRepeat ngRepeat}. 31738 * 31739 * @element ANY 31740 * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result 31741 * of the evaluation can be a string representing space delimited class names or an array. 31742 * 31743 * @example 31744 <example name="ng-class-odd"> 31745 <file name="index.html"> 31746 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 31747 <li ng-repeat="name in names"> 31748 <span ng-class-odd="'odd'" ng-class-even="'even'"> 31749 {{name}} 31750 </span> 31751 </li> 31752 </ol> 31753 </file> 31754 <file name="style.css"> 31755 .odd { 31756 color: red; 31757 } 31758 .even { 31759 color: blue; 31760 } 31761 </file> 31762 <file name="protractor.js" type="protractor"> 31763 it('should check ng-class-odd and ng-class-even', function() { 31764 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 31765 toMatch(/odd/); 31766 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 31767 toMatch(/even/); 31768 }); 31769 </file> 31770 </example> 31771 */ 31772var ngClassOddDirective = classDirective('Odd', 0); 31773 31774/** 31775 * @ngdoc directive 31776 * @name ngClassEven 31777 * @restrict AC 31778 * 31779 * @description 31780 * The `ngClassOdd` and `ngClassEven` directives work exactly as 31781 * {@link ng.directive:ngClass ngClass}, except they work in 31782 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 31783 * 31784 * This directive can be applied only within the scope of an 31785 * {@link ng.directive:ngRepeat ngRepeat}. 31786 * 31787 * @element ANY 31788 * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The 31789 * result of the evaluation can be a string representing space delimited class names or an array.
31790 * 31791 * @example 31792 <example name="ng-class-even"> 31793 <file name="index.html"> 31794 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 31795 <li ng-repeat="name in names"> 31796 <span ng-class-odd="'odd'" ng-class-even="'even'"> 31797 {{name}} 31798 </span> 31799 </li> 31800 </ol> 31801 </file> 31802 <file name="style.css"> 31803 .odd { 31804 color: red; 31805 } 31806 .even { 31807 color: blue; 31808 } 31809 </file> 31810 <file name="protractor.js" type="protractor"> 31811 it('should check ng-class-odd and ng-class-even', function() { 31812 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 31813 toMatch(/odd/); 31814 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 31815 toMatch(/even/); 31816 }); 31817 </file> 31818 </example> 31819 */ 31820var ngClassEvenDirective = classDirective('Even', 1); 31821 31822/** 31823 * @ngdoc directive 31824 * @name ngCloak 31825 * @restrict AC 31826 * 31827 * @description 31828 * The `ngCloak` directive is used to prevent the Angular html template from being briefly 31829 * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this 31830 * directive to avoid the undesirable flicker effect caused by the html template display. 31831 * 31832 * The directive can be applied to the `<body>` element, but the preferred usage is to apply 31833 * multiple `ngCloak` directives to small portions of the page to permit progressive rendering 31834 * of the browser view. 31835 * 31836 * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and 31837 * `angular.min.js`. 31838 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 31839 * 31840 * ```css 31841 * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { 31842 * display: none !important; 31843 * } 31844 * ``` 31845 * 31846 * When this css rule is loaded by the browser, all html elements (including their children) that 31847 * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive 31848 * during the compilation of the template it deletes the `ngCloak` element attribute, making 31849 * the compiled element visible. 31850 * 31851 * For the best result, the `angular.js` script must be loaded in the head section of the html 31852 * document; alternatively, the css rule above must be included in the external stylesheet of the 31853 * application. 31854 * 31855 * @element ANY 31856 * 31857 * @example 31858 <example name="ng-cloak"> 31859 <file name="index.html"> 31860 <div id="template1" ng-cloak>{{ 'hello' }}</div> 31861 <div id="template2" class="ng-cloak">{{ 'world' }}</div> 31862 </file> 31863 <file name="protractor.js" type="protractor"> 31864 it('should remove the template directive and css class', function() { 31865 expect($('#template1').getAttribute('ng-cloak')). 31866 toBeNull(); 31867 expect($('#template2').getAttribute('ng-cloak')). 31868 toBeNull(); 31869 }); 31870 </file> 31871 </example> 31872 * 31873 */ 31874var ngCloakDirective = ngDirective({ 31875 compile: function(element, attr) { 31876 attr.$set('ngCloak', undefined); 31877 element.removeClass('ng-cloak'); 31878 } 31879}); 31880 31881/** 31882 * @ngdoc directive 31883 * @name ngController 31884 * 31885 * @description 31886 * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular 31887 * supports the principles behind the Model-View-Controller design pattern. 31888 * 31889 * MVC components in angular: 31890 * 31891 * * Model â Models are the properties of a scope; scopes are attached to the DOM where scope properties 31892 * are accessed through bindings. 31893 * * View â The template (HTML with data bindings) that is rendered into the View. 31894 * * Controller â The `ngController` directive specifies a Controller class; the class contains business 31895 * logic behind the application to decorate the scope with functions and values 31896 * 31897 * Note that you can also attach controllers to the DOM by declaring it in a route definition 31898 * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller 31899 * again using `ng-controller` in the template itself. This will cause the controller to be attached 31900 * and executed twice. 31901 * 31902 * @element ANY 31903 * @scope 31904 * @priority 500 31905 * @param {expression} ngController Name of a constructor function registered with the current 31906 * {@link ng.$controllerProvider $controllerProvider} or an {@link guide/expression expression} 31907 * that on the current scope evaluates to a constructor function. 31908 * 31909 * The controller instance can be published into a scope property by specifying 31910 * `ng-controller="as propertyName"`. 31911 * 31912 * If the current `$controllerProvider` is configured to use globals (via 31913 * {@link ng.$controllerProvider#allowGlobals `$controllerProvider.allowGlobals()` }), this may 31914 * also be the name of a globally accessible constructor function (not recommended). 31915 * 31916 * @example 31917 * Here is a simple form for editing user contact information. Adding, remov
31917ing, clearing, and 31918 * greeting are methods declared on the controller (see source tab). These methods can 31919 * easily be called from the angular markup. Any changes to the data are automatically reflected 31920 * in the View without the need for a manual update. 31921 * 31922 * Two different declaration styles are included below: 31923 * 31924 * * one binds methods and properties directly onto the controller using `this`: 31925 * `ng-controller="SettingsController1 as settings"` 31926 * * one injects `$scope` into the controller: 31927 * `ng-controller="SettingsController2"` 31928 * 31929 * The second option is more common in the Angular community, and is generally used in boilerplates 31930 * and in this guide. However, there are advantages to binding properties directly to the controller 31931 * and avoiding scope. 31932 * 31933 * * Using `controller as` makes it obvious which controller you are accessing in the template when 31934 * multiple controllers apply to an element. 31935 * * If you are writing your controllers as classes you have easier access to the properties and 31936 * methods, which will appear on the scope, from inside the controller code. 31937 * * Since there is always a `.` in the bindings, you don't have to worry about prototypal 31938 * inheritance masking primitives. 31939 * 31940 * This example demonstrates the `controller as` syntax. 31941 * 31942 * <example name="ngControllerAs" module="controllerAsExample"> 31943 * <file name="index.html"> 31944 * <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings"> 31945 * <label>Name: <input type="text" ng-model="settings.name"/></label> 31946 * <button ng-click="settings.greet()">greet</button><br/> 31947 * Contact: 31948 * <ul> 31949 * <li ng-repeat="contact in settings.contacts"> 31950 * <select ng-model="contact.type" aria-label="Contact method" id="select_{{$index}}"> 31951 * <option>phone</option> 31952 * <option>email</option> 31953 * </select> 31954 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" /> 31955 * <button ng-click="settings.clearContact(contact)">clear</button> 31956 * <button ng-click="settings.removeContact(contact)" aria-label="Remove">X</button> 31957 * </li> 31958 * <li><button ng-click="settings.addContact()">add</button></li> 31959 * </ul> 31960 * </div> 31961 * </file> 31962 * <file name="app.js"> 31963 * angular.module('controllerAsExample', []) 31964 * .controller('SettingsController1', SettingsController1); 31965 * 31966 * function SettingsController1() { 31967 * this.name = 'John Smith'; 31968 * this.contacts = [ 31969 * {type: 'phone', value: '408 555 1212'}, 31970 * {type: 'email', value: '[email protected]'} 31971 * ]; 31972 * } 31973 * 31974 * SettingsController1.prototype.greet = function() { 31975 * alert(this.name); 31976 * }; 31977 * 31978 * SettingsController1.prototype.addContact = function() { 31979 * this.contacts.push({type: 'email', value: '[email protected]'}); 31980 * }; 31981 * 31982 * SettingsController1.prototype.removeContact = function(contactToRemove) { 31983 * var index = this.contacts.indexOf(contactToRemove); 31984 * this.contacts.splice(index, 1); 31985 * }; 31986 * 31987 * SettingsController1.prototype.clearContact = function(contact) { 31988 * contact.type = 'phone'; 31989 * contact.value = ''; 31990 * }; 31991 * </file> 31992 * <file name="protractor.js" type="protractor"> 31993 * it('should check controller as', function() { 31994 * var container = element(by.id('ctrl-as-exmpl')); 31995 * expect(container.element(by.model('settings.name')) 31996 * .getAttribute('value')).toBe('John Smith'); 31997 * 31998 * var firstRepeat = 31999 * container.element(by.repeater('contact in settings.contacts').row(0)); 32000 * var secondRepeat = 32001 * container.element(by.repeater('contact in settings.contacts').row(1)); 32002 * 32003 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 32004 * .toBe('408 555 1212'); 32005 * 32006 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) 32007 * .toBe('[email protected]'); 32008 * 32009 * firstRepeat.element(by.buttonText('clear')).click(); 32010 * 32011 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 32012 * .toBe(''); 32013 * 32014 * container.element(by.buttonText('add')).click(); 32015 * 32016 * expect(container.element(by.repeater('contact in settings.contacts').row(2)) 32017 * .element(by.model('contact.value')) 32018 * .getAttribute('value')) 32019 * .toBe('[email protected]'); 32020 * }); 32021 * </file> 32022 * </example> 32023 * 32024 * This example demonstrates the "attach to `$scope`" style of controller. 32025 * 32026 * <example name="ngController" module="controllerExample"> 32027 * <file name="index.html"> 32028 * <div id="ctrl-exmpl" ng-controller="SettingsController2"> 32029 * <label>Name: <input type="text" ng-model="name"/></label> 32030 * <button ng-click="greet()">greet</button><br/> 32031 * Contact: 32032 * <ul> 32033 * <li ng-repeat="contact in contacts"> 32034 * <select ng-model="contact.type" id="select_{{$index}}"> 32035 * <option>phone</option> 32036 * <option>email</option> 32037 * </select> 32038 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" /> 32039 * <button ng-click="clearContact(contact)">clear</button> 32040 * <button ng-click="removeContact(contact)">X</button> 32041 * </li> 32042 * <li>[ <button ng-click="addContact()">add</button> ]</li> 32043 * </ul> 32044 * </div> 32045 * </file> 32046 * <file name="app.js">
32047 * angular.module('controllerExample', []) 32048 * .controller('SettingsController2', ['$scope', SettingsController2]); 32049 * 32050 * function SettingsController2($scope) { 32051 * $scope.name = 'John Smith'; 32052 * $scope.contacts = [ 32053 * {type:'phone', value:'408 555 1212'}, 32054 * {type:'email', value:'[email protected]'} 32055 * ]; 32056 * 32057 * $scope.greet = function() { 32058 * alert($scope.name); 32059 * }; 32060 * 32061 * $scope.addContact = function() { 32062 * $scope.contacts.push({type:'email', value:'[email protected]'}); 32063 * }; 32064 * 32065 * $scope.removeContact = function(contactToRemove) { 32066 * var index = $scope.contacts.indexOf(contactToRemove); 32067 * $scope.contacts.splice(index, 1); 32068 * }; 32069 * 32070 * $scope.clearContact = function(contact) { 32071 * contact.type = 'phone'; 32072 * contact.value = ''; 32073 * }; 32074 * } 32075 * </file> 32076 * <file name="protractor.js" type="protractor"> 32077 * it('should check controller', function() { 32078 * var container = element(by.id('ctrl-exmpl')); 32079 * 32080 * expect(container.element(by.model('name')) 32081 * .getAttribute('value')).toBe('John Smith'); 32082 * 32083 * var firstRepeat = 32084 * container.element(by.repeater('contact in contacts').row(0)); 32085 * var secondRepeat = 32086 * container.element(by.repeater('contact in contacts').row(1)); 32087 * 32088 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 32089 * .toBe('408 555 1212'); 32090 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) 32091 * .toBe('[email protected]'); 32092 * 32093 * firstRepeat.element(by.buttonText('clear')).click(); 32094 * 32095 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 32096 * .toBe(''); 32097 * 32098 * container.element(by.buttonText('add')).click(); 32099 * 32100 * expect(container.element(by.repeater('contact in contacts').row(2)) 32101 * .element(by.model('contact.value')) 32102 * .getAttribute('value')) 32103 * .toBe('[email protected]'); 32104 * }); 32105 * </file> 32106 *</example> 32107 32108 */ 32109var ngControllerDirective = [function() { 32110 return { 32111 restrict: 'A', 32112 scope: true, 32113 controller: '@', 32114 priority: 500 32115 }; 32116}]; 32117 32118/** 32119 * @ngdoc directive 32120 * @name ngCsp 32121 * 32122 * @restrict A 32123 * @element ANY 32124 * @description 32125 * 32126 * Angular has some features that can conflict with certain restrictions that are applied when using 32127 * [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) rules. 32128 * 32129 * If you intend to implement CSP with these rules then you must tell Angular not to use these 32130 * features. 32131 * 32132 * This is necessary when developing things like Google Chrome Extensions or Universal Windows Apps. 32133 * 32134 * 32135 * The following default rules in CSP affect Angular: 32136 * 32137 * * The use of `eval()`, `Function(string)` and similar functions to dynamically create and execute 32138 * code from strings is forbidden. Angular makes use of this in the {@link $parse} service to 32139 * provide a 30% increase in the speed of evaluating Angular expressions. (This CSP rule can be 32140 * disabled with the CSP keyword `unsafe-eval`, but it is generally not recommended as it would 32141 * weaken the protections offered by CSP.) 32142 * 32143 * * The use of inline resources, such as inline `<script>` and `<style>` elements, are forbidden. 32144 * This prevents apps from injecting custom styles directly into the document. Angular makes use of 32145 * this to include some CSS rules (e.g. {@link ngCloak} and {@link ngHide}). To make these 32146 * directives work when a CSP rule is blocking inline styles, you must link to the `angular-csp.css` 32147 * in your HTML manually. (This CSP rule can be disabled with the CSP keyword `unsafe-inline`, but 32148 * it is generally not recommended as it would weaken the protections offered by CSP.) 32149 * 32150 * If you do not provide `ngCsp` then Angular tries to autodetect if CSP is blocking dynamic code 32151 * creation from strings (e.g., `unsafe-eval` not specified in CSP header) and automatically 32152 * deactivates this feature in the {@link $parse} service. This autodetection, however, triggers a 32153 * CSP error to be logged in the console: 32154 * 32155 * ``` 32156 * Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of 32157 * script in the following Content Security Policy directive: "default-src 'self'". Note that 32158 * 'script-src' was not explicitly set, so 'default-src' is used as a fallback. 32159 * ``` 32160 * 32161 * This error is harmless but annoying. To prevent the error from showing up, put the `ngCsp` 32162 * directive on an element of the HTML document that appears before the `<script>` tag that loads 32163 * the `angular.js` file. 32164 * 32165 * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.* 32166 * 32167 * You can specify which of the CSP related Angular features should be deactivated by providing 32168 * a value for the `ng-csp` attribute. The options are as follows: 32169 * 32170 * * no-inline-style: this stops Angular from injecting CSS styles into the DOM 32171 *
32172 * * no-unsafe-eval: this stops Angular from optimizing $parse with unsafe eval of strings 32173 * 32174 * You can use these values in the following combinations: 32175 * 32176 * 32177 * * No declaration means that Angular will assume that you can do inline styles, but it will do 32178 * a runtime check for unsafe-eval. E.g. `<body>`. This is backwardly compatible with previous 32179 * versions of Angular. 32180 * 32181 * * A simple `ng-csp` (or `data-ng-csp`) attribute will tell Angular to deactivate both inline 32182 * styles and unsafe eval. E.g. `<body ng-csp>`. This is backwardly compatible with previous 32183 * versions of Angular. 32184 * 32185 * * Specifying only `no-unsafe-eval` tells Angular that we must not use eval, but that we can 32186 * inject inline styles. E.g. `<body ng-csp="no-unsafe-eval">`. 32187 * 32188 * * Specifying only `no-inline-style` tells Angular that we must not inject styles, but that we can 32189 * run eval - no automatic check for unsafe eval will occur. E.g. `<body ng-csp="no-inline-style">` 32190 * 32191 * * Specifying both `no-unsafe-eval` and `no-inline-style` tells Angular that we must not inject 32192 * styles nor use eval, which is the same as an empty: ng-csp. 32193 * E.g.`<body ng-csp="no-inline-style;no-unsafe-eval">` 32194 * 32195 * @example 32196 * This example shows how to apply the `ngCsp` directive to the `html` tag. 32197 ```html 32198 <!doctype html> 32199 <html ng-app ng-csp> 32200 ... 32201 ... 32202 </html> 32203 ``` 32204 * @example 32205 <!-- Note: the `.csp` suffix in the example name triggers CSP mode in our http server! --> 32206 <example name="example.csp" module="cspExample" ng-csp="true"> 32207 <file name="index.html"> 32208 <div ng-controller="MainController as ctrl"> 32209 <div> 32210 <button ng-click="ctrl.inc()" id="inc">Increment</button> 32211 <span id="counter"> 32212 {{ctrl.counter}} 32213 </span> 32214 </div> 32215 32216 <div> 32217 <button ng-click="ctrl.evil()" id="evil">Evil</button> 32218 <span id="evilError"> 32219 {{ctrl.evilError}} 32220 </span> 32221 </div> 32222 </div> 32223 </file> 32224 <file name="script.js"> 32225 angular.module('cspExample', []) 32226 .controller('MainController', function MainController() { 32227 this.counter = 0; 32228 this.inc = function() { 32229 this.counter++; 32230 }; 32231 this.evil = function() { 32232 try { 32233 eval('1+2'); // eslint-disable-line no-eval 32234 } catch (e) { 32235 this.evilError = e.message; 32236 } 32237 }; 32238 }); 32239 </file> 32240 <file name="protractor.js" type="protractor"> 32241 var util, webdriver; 32242 32243 var incBtn = element(by.id('inc')); 32244 var counter = element(by.id('counter')); 32245 var evilBtn = element(by.id('evil')); 32246 var evilError = element(by.id('evilError')); 32247 32248 function getAndClearSevereErrors() { 32249 return browser.manage().logs().get('browser').then(function(browserLog) { 32250 return browserLog.filter(function(logEntry) { 32251 return logEntry.level.value > webdriver.logging.Level.WARNING.value; 32252 }); 32253 }); 32254 } 32255 32256 function clearErrors() { 32257 getAndClearSevereErrors(); 32258 } 32259 32260 function expectNoErrors() { 32261 getAndClearSevereErrors().then(function(filteredLog) { 32262 expect(filteredLog.length).toEqual(0); 32263 if (filteredLog.length) { 32264 console.log('browser console errors: ' + util.inspect(filteredLog)); 32265 } 32266 }); 32267 } 32268 32269 function expectError(regex) { 32270 getAndClearSevereErrors().then(function(filteredLog) { 32271 var found = false; 32272 filteredLog.forEach(function(log) { 32273 if (log.message.match(regex)) { 32274 found = true; 32275 } 32276 }); 32277 if (!found) { 32278 throw new Error('expected an error that matches ' + regex); 32279 } 32280 }); 32281 } 32282 32283 beforeEach(function() { 32284 util = require('util'); 32285 webdriver = require('selenium-webdriver'); 32286 }); 32287 32288 // For now, we only test on Chrome, 32289 // as Safari does not load the page with Protractor's injected scripts, 32290 // and Firefox webdriver always disables content security policy (#6358) 32291 if (browser.params.browser !== 'chrome') { 32292 return; 32293 } 32294 32295 it('should not report errors when the page is loaded', function() { 32296 // clear errors so we are not dependent on previous tests 32297 clearErrors(); 32298 // Need to reload the page as the page is already loaded when 32299 // we come here 32300 browser.driver.getCurrentUrl().then(function(url) { 32301 browser.get(url); 32302 }); 32303 expectNoErrors(); 32304 }); 32305 32306 it('should evaluate expressions', function() { 32307 expect(counter.getText()).toEqual('0'); 32308 incBtn.click(); 32309 expect(counter.getText()).toEqual('1'); 32310 expectNoErrors(); 32311 }); 32312 32313 it('should throw and report an error when using "eval"', function() { 32314 evilBtn.click(); 32315 expect(evilError.getText()).toMatch(/Content Security Policy/); 32316 expectError(/Content Security Policy/); 32317 }); 32318 </file> 32319 </example> 32320 */ 32321 32322// `ngCsp` is not implemented as a proper directive any more, because we need it be processed while 32323// we bootstrap the app (before `$parse` is instantiated). For this reason, we just have the `csp()` 32324// fn that looks for the `ng-csp` attribute anywhere in the current doc. 32325 32326/** 32327 * @ngdoc directive 32328 * @name ngClick 32329 * 32330 * @description 32331 * The ngClick directive allows you to specify custom behavior when 32332 * an element is clicked. 32333 * 32334 * @element ANY 32335 * @priority 0 32336 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon 32337 * click. ({@link guide/expression#-event- Event object is available as `$event`}) 32338 * 32339 * @example 32340 <example name="ng-click"> 32341 <file name="index.html"> 32342 <button ng-click="count = count + 1" ng-init="count=0"> 32343 Increment 32344 </button>
32345 <span> 32346 count: {{count}} 32347 </span> 32348 </file> 32349 <file name="protractor.js" type="protractor"> 32350 it('should check ng-click', function() { 32351 expect(element(by.binding('count')).getText()).toMatch('0'); 32352 element(by.css('button')).click(); 32353 expect(element(by.binding('count')).getText()).toMatch('1'); 32354 }); 32355 </file> 32356 </example> 32357 */ 32358/* 32359 * A collection of directives that allows creation of custom event handlers that are defined as 32360 * angular expressions and are compiled and executed within the current scope. 32361 */ 32362var ngEventDirectives = {}; 32363 32364// For events that might fire synchronously during DOM manipulation 32365// we need to execute their event handlers asynchronously using $evalAsync, 32366// so that they are not executed in an inconsistent state. 32367var forceAsyncEvents = { 32368 'blur': true, 32369 'focus': true 32370}; 32371forEach( 32372 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '), 32373 function(eventName) { 32374 var directiveName = directiveNormalize('ng-' + eventName); 32375 ngEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse, $rootScope) { 32376 return { 32377 restrict: 'A', 32378 compile: function($element, attr) { 32379 // We expose the powerful $event object on the scope that provides access to the Window, 32380 // etc. that isn't protected by the fast paths in $parse. We explicitly request better 32381 // checks at the cost of speed since event handler expressions are not executed as 32382 // frequently as regular change detection. 32383 var fn = $parse(attr[directiveName], /* interceptorFn */ null, /* expensiveChecks */ true); 32384 return function ngEventHandler(scope, element) { 32385 element.on(eventName, function(event) { 32386 var callback = function() { 32387 fn(scope, {$event:event}); 32388 }; 32389 if (forceAsyncEvents[eventName] && $rootScope.$$phase) { 32390 scope.$evalAsync(callback); 32391 } else { 32392 scope.$apply(callback); 32393 } 32394 }); 32395 }; 32396 } 32397 }; 32398 }]; 32399 } 32400); 32401 32402/** 32403 * @ngdoc directive 32404 * @name ngDblclick 32405 * 32406 * @description 32407 * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event. 32408 * 32409 * @element ANY 32410 * @priority 0 32411 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon 32412 * a dblclick. (The Event object is available as `$event`) 32413 * 32414 * @example 32415 <example name="ng-dblclick"> 32416 <file name="index.html"> 32417 <button ng-dblclick="count = count + 1" ng-init="count=0"> 32418 Increment (on double click) 32419 </button> 32420 count: {{count}} 32421 </file> 32422 </example> 32423 */ 32424 32425 32426/** 32427 * @ngdoc directive 32428 * @name ngMousedown 32429 * 32430 * @description 32431 * The ngMousedown directive allows you to specify custom behavior on mousedown event. 32432 * 32433 * @element ANY 32434 * @priority 0 32435 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon 32436 * mousedown. ({@link guide/expression#-event- Event object is available as `$event`}) 32437 * 32438 * @example 32439 <example name="ng-mousedown"> 32440 <file name="index.html"> 32441 <button ng-mousedown="count = count + 1" ng-init="count=0"> 32442 Increment (on mouse down) 32443 </button> 32444 count: {{count}} 32445 </file> 32446 </example> 32447 */ 32448 32449 32450/** 32451 * @ngdoc directive 32452 * @name ngMouseup 32453 * 32454 * @description 32455 * Specify custom behavior on mouseup event. 32456 * 32457 * @element ANY 32458 * @priority 0 32459 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon 32460 * mouseup. ({@link guide/expression#-event- Event object is available as `$event`}) 32461 * 32462 * @example 32463 <example name="ng-mouseup"> 32464 <file name="index.html"> 32465 <button ng-mouseup="count = count + 1" ng-init="count=0"> 32466 Increment (on mouse up) 32467 </button> 32468 count: {{count}} 32469 </file> 32470 </example> 32471 */ 32472 32473/** 32474 * @ngdoc directive 32475 * @name ngMouseover 32476 * 32477 * @description 32478 * Specify custom behavior on mouseover event. 32479 * 32480 * @element ANY 32481 * @priority 0 32482 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon 32483 * mouseover. ({@link guide/expression#-event- Event object is available as `$event`}) 32484 * 32485 * @example 32486 <example name="ng-mouseover"> 32487 <file name="index.html"> 32488 <button ng-mouseover="count = count + 1" ng-init="count=0"> 32489 Increment (when mouse is over) 32490 </button> 32491 count: {{count}} 32492 </file> 32493 </example> 32494 */ 32495 32496 32497/** 32498 * @ngdoc directive 32499 * @name ngMouseenter 32500 * 32501 * @description 32502 * Specify custom behavior on mouseenter event. 32503 * 32504 * @element ANY 32505 * @priority 0 32506 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon 32507 * mouseenter. ({@link guide/expression#-event- Event object is available as `$event`}) 32508 * 32509 * @example 32510 <example name="ng-mouseenter"> 32511 <file name="index.html"> 32512 <button ng-mouseenter="count = count + 1" ng-init="count=0"> 32513 Increment (when mouse enters) 32514 </button> 32515 count: {{count}} 32516 </file> 32517 </example> 32518 */ 32519 32520 32521/** 32522 * @ngdoc directive 32523 * @name ngMouseleave 32524 * 32525 * @description 32526 * Specify custom behavior on mouseleave event. 32527 * 32528 * @element ANY 32529 * @priority 0 32530 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon 32531 * mouseleave. ({@link guide/expression#-event- Event object is available as `$event`}) 32532 * 32533 * @example 32534 <example name="ng-mouseleave"> 32535 <file name="index.html"> 32536 <button ng-mouseleave="count = count + 1" ng-init="count=0"> 32537 Increment (when mouse leaves) 32538 </button> 32539 count: {{count}} 32540 </file> 32541 </example> 32542 */ 32543 32544 32545/** 32546 * @ngdoc directive 32547 * @name ngMousemove 32548 * 32549 * @description 32550 * Specify custom behavior on mousemove event. 32551 * 32552 * @element ANY 32553 * @priority 0 32554 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon 32555 * mousemove. ({@link guide/expression#-event- Event object is available as `$event`}) 32556 * 32557 * @example 32558 <example name="ng-mousemove"> 32559 <file name="index.html"> 32560 <button ng-mousemove="count = count + 1" ng-init="count=0"> 32561 Increment (when mouse moves) 32562 </button> 32563 count: {{count}} 32564 </file> 32565 </example> 32566 */ 32567 32568 32569/** 32570 * @ngdoc directive 32571 * @name ngKeydown 32572 * 32573 * @description 32574 * Specify custom behavior on keydown event. 32575 * 32576 * @element ANY 32577 * @priority 0 32578 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon 32579 * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 32580 * 32581 * @example 32582 <example name="ng-keydown"> 32583 <file name="index.html"> 32584 <input ng-keydown="count = count + 1" ng-init="count=0"> 32585 key down count: {{count}} 32586 </file> 32587 </example> 32588 */ 32589 32590 32591/** 32592 * @ngdoc directive 32593 * @name ngKeyup 32594 * 32595 * @description 32596 * Specify custom behavior on keyup event. 32597 * 32598 * @element ANY 32599 * @priority 0 32600 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon 32601 * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 32602 * 32603 * @example 32604 <example name="ng-keyup"> 32605 <file name="index.html"> 32606 <p>Typing in the input box below updates the key count</p> 32607 <input ng-keyup="count = count + 1" ng-init="count=0"> key up count: {{count}} 32608 32609 <p>Typing in the input box below updates the keycode</p> 32610 <input ng-keyup="event=$event"> 32611 <p>event keyCode: {{ event.keyCode }}</p> 32612 <p>event altKey: {{ event.altKey }}</p> 32613 </file> 32614 </example> 32615 */ 32616 32617 32618/** 32619 * @ngdoc directive 32620 * @name ngKeypress 32621 * 32622 * @description 32623 * Specify custom behavior on keypress event. 32624 * 32625 * @element ANY 32626 * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon 32627 * keypress. ({@link guide/expression#-event- Event object is available as `$event`} 32628 * and can be interrogated for keyCode, altKey, etc.) 32629 * 32630 * @example 32631 <example name="ng-keypress"> 32632 <file name="index.html"> 32633 <input ng-keypress="count = count + 1" ng-init="count=0"> 32634 key press count: {{count}} 32635 </file> 32636 </example> 32637 */ 32638 32639 32640/** 32641 * @ngdoc directive 32642 * @name ngSubmit 32643 * 32644 * @description 32645 * Enables binding angular expressions to onsubmit events. 32646 * 32647 * Additionally it prevents the default action (which for form means
32647sending the request to the 32648 * server and reloading the current page), but only if the form does not contain `action`, 32649 * `data-action`, or `x-action` attributes. 32650 * 32651 * <div class="alert alert-warning"> 32652 * **Warning:** Be careful not to cause "double-submission" by using both the `ngClick` and 32653 * `ngSubmit` handlers together. See the 32654 * {@link form#submitting-a-form-and-preventing-the-default-action `form` directive documentation} 32655 * for a detailed discussion of when `ngSubmit` may be triggered. 32656 * </div> 32657 * 32658 * @element form 32659 * @priority 0 32660 * @param {expression} ngSubmit {@link guide/expression Expression} to eval. 32661 * ({@link guide/expression#-event- Event object is available as `$event`}) 32662 * 32663 * @example 32664 <example module="submitExample" name="ng-submit"> 32665 <file name="index.html"> 32666 <script> 32667 angular.module('submitExample', []) 32668 .controller('ExampleController', ['$scope', function($scope) { 32669 $scope.list = []; 32670 $scope.text = 'hello'; 32671 $scope.submit = function() { 32672 if ($scope.text) { 32673 $scope.list.push(this.text); 32674 $scope.text = ''; 32675 } 32676 }; 32677 }]); 32678 </script>
32679 <form ng-submit="submit()" ng-controller="ExampleController"> 32680 Enter text and hit enter: 32681 <input type="text" ng-model="text" name="text" /> 32682 <input type="submit" id="submit" value="Submit" /> 32683 <pre>list={{list}}</pre> 32684 </form> 32685 </file> 32686 <file name="protractor.js" type="protractor"> 32687 it('should check ng-submit', function() { 32688 expect(element(by.binding('list')).getText()).toBe('list=[]'); 32689 element(by.css('#submit')).click(); 32690 expect(element(by.binding('list')).getText()).toContain('hello'); 32691 expect(element(by.model('text')).getAttribute('value')).toBe(''); 32692 }); 32693 it('should ignore empty strings', function() { 32694 expect(element(by.binding('list')).getText()).toBe('list=[]'); 32695 element(by.css('#submit')).click(); 32696 element(by.css('#submit')).click(); 32697 expect(element(by.binding('list')).getText()).toContain('hello'); 32698 }); 32699 </file> 32700 </example> 32701 */ 32702 32703/** 32704 * @ngdoc directive 32705 * @name ngFocus 32706 * 32707 * @description 32708 * Specify custom behavior on focus event. 32709 * 32710 * Note: As the `focus` event is executed synchronously when calling `input.focus()` 32711 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired 32712 * during an `$apply` to ensure a consistent state. 32713 * 32714 * @element window, input, select, textarea, a 32715 * @priority 0 32716 * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon 32717 * focus. ({@link guide/expression#-event- Event object is available as `$event`}) 32718 * 32719 * @example 32720 * See {@link ng.directive:ngClick ngClick} 32721 */ 32722 32723/** 32724 * @ngdoc directive 32725 * @name ngBlur 32726 * 32727 * @description 32728 * Specify custom behavior on blur event. 32729 * 32730 * A [blur event](https://developer.mozilla.org/en-US/docs/Web/Events/blur) fires when 32731 * an element has lost focus. 32732 * 32733 * Note: As the `blur` event is executed synchronously also during DOM manipulations 32734 * (e.g. removing a focussed input), 32735 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired 32736 * during an `$apply` to ensure a consistent state. 32737 * 32738 * @element window, input, select, textarea, a 32739 * @priority 0 32740 * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon 32741 * blur. ({@link guide/expression#-event- Event object is available as `$event`}) 32742 * 32743 * @example 32744 * See {@link ng.directive:ngClick ngClick} 32745 */ 32746 32747/** 32748 * @ngdoc directive 32749 * @name ngCopy 32750 * 32751 * @description 32752 * Specify custom behavior on copy event. 32753 * 32754 * @element window, input, select, textarea, a 32755 * @priority 0 32756 * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon 32757 * copy. ({@link guide/expression#-event- Event object is available as `$event`}) 32758 * 32759 * @example 32760 <example name="ng-copy"> 32761 <file name="index.html"> 32762 <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value"> 32763 copied: {{copied}} 32764 </file> 32765 </example> 32766 */ 32767 32768/** 32769 * @ngdoc directive 32770 * @name ngCut 32771 * 32772 * @description 32773 * Specify custom behavior on cut event. 32774 * 32775 * @element window, input, select, textarea, a 32776 * @priority 0 32777 * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon 32778 * cut. ({@link guide/expression#-event- Event object is available as `$event`}) 32779 * 32780 * @example 32781 <example name="ng-cut"> 32782 <file name="index.html"> 32783 <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value"> 32784 cut: {{cut}} 32785 </file> 32786 </example> 32787 */ 32788 32789/** 32790 * @ngdoc directive 32791 * @name ngPaste 32792 * 32793 * @description 32794 * Specify custom behavior on paste event. 32795 * 32796 * @element window, input, select, textarea, a 32797 * @priority 0 32798 * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon 32799 * paste. ({@link guide/expression#-event- Event object is available as `$event`}) 32800 * 32801 * @example 32802 <example name="ng-paste"> 32803 <file name="index.html"> 32804 <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'> 32805 pasted: {{paste}} 32806 </file> 32807 </example> 32808 */ 32809 32810/** 32811 * @ngdoc directive 32812 * @name ngIf 32813 * @restrict A 32814 * @multiElement 32815 * 32816 * @description 32817 * The `ngIf` directive removes or recreates a portion of the DOM tree based on an 32818 * {expression}. If the expression assigned to `ngIf` evaluates to a false 32819 * value then the element is removed from the DOM, otherwise a clone of the 32820 * element is reinserted into the DOM. 32821 * 32822 * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the 32823 * element in the DOM rather than changing its visibility via the `display` css property. A common 32824 * case when this difference is significant is when using css selectors that rely on an element's 32825 * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classe
32825s. 32826 * 32827 * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope 32828 * is created when the element is restored. The scope created within `ngIf` inherits from 32829 * its parent scope using 32830 * [prototypal inheritance](https://github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance). 32831 * An important implication of this is if `ngModel` is used within `ngIf` to bind to 32832 * a javascript primitive defined in the parent scope. In this case any modifications made to the 32833 * variable within the child scope will override (hide) the value in the parent scope. 32834 * 32835 * Also, `ngIf` recreates elements using their compiled state. An example of this behavior 32836 * is if an element's class attribute is directly modified after it's compiled, using something like 32837 * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element 32838 * the added class will be lost because the original compiled state is used to regenerate the element. 32839 * 32840 * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter` 32841 * and `leave` effects. 32842 * 32843 * @animations 32844 * | Animation | Occurs | 32845 * |----------------------------------|-------------------------------------| 32846 * | {@link ng.$animate#enter enter} | just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container | 32847 * | {@link ng.$animate#leave leave} | just before the `ngIf` contents are removed from the DOM | 32848 * 32849 * @element ANY 32850 * @scope 32851 * @priority 600 32852 * @param {expression} ngIf If the {@link guide/expression expression} is falsy then 32853 * the element is removed from the DOM tree. If it is truthy a copy of the compiled 32854 * element is added to the DOM tree. 32855 * 32856 * @example 32857 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-if"> 32858 <file name="index.html"> 32859 <label>Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /></label><br/> 32860 Show when checked: 32861 <span ng-if="checked" class="animate-if"> 32862 This is removed when the checkbox is unchecked. 32863 </span> 32864 </file> 32865 <file name="animations.css"> 32866 .animate-if { 32867 background:white; 32868 border:1px solid black; 32869 padding:10px; 32870 } 32871 32872 .animate-if.ng-enter, .animate-if.ng-leave { 32873 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 32874 } 32875 32876 .animate-if.ng-enter, 32877 .animate-if.ng-leave.ng-leave-active { 32878 opacity:0; 32879 } 32880 32881 .animate-if.ng-leave, 32882 .animate-if.ng-enter.ng-enter-active { 32883 opacity:1; 32884 } 32885 </file> 32886 </example> 32887 */ 32888var ngIfDirective = ['$animate', '$compile', function($animate, $compile) { 32889 return { 32890 multiElement: true, 32891 transclude: 'element', 32892 priority: 600, 32893 terminal: true, 32894 restrict: 'A', 32895 $$tlb: true, 32896 link: function($scope, $element, $attr, ctrl, $transclude) { 32897 var block, childScope, previousElements; 32898 $scope.$watch($attr.ngIf, function ngIfWatchAction(value) { 32899 32900 if (value) { 32901 if (!childScope) { 32902 $transclude(function(clone, newScope) { 32903 childScope = newScope; 32904 clone[clone.length++] = $compile.$$createComment('end ngIf', $attr.ngIf); 32905 // Note: We only need the first/last node of the cloned nodes. 32906 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 32907 // by a directive with templateUrl when its template arrives. 32908 block = { 32909 clone: clone 32910 }; 32911 $animate.enter(clone, $element.parent(), $element); 32912 }); 32913 } 32914 } else { 32915 if (previousElements) { 32916 previousElements.remove(); 32917 previousElements = null; 32918 } 32919 if (childScope) { 32920 childScope.$destroy(); 32921 childScope = null; 32922 } 32923 if (block) { 32924 previousElements = getBlockNodes(block.clone); 32925 $animate.leave(previousElements).done(function(response) { 32926 if (response !== false) previousElements = null; 32927 }); 32928 block = null; 32929 } 32930 } 32931 }); 32932 } 32933 }; 32934}]; 32935 32936/** 32937 * @ngdoc directive 32938 * @name ngInclude 32939 * @restrict ECA 32940 * 32941 * @description 32942 * Fetches, compiles and includes an external HTML fragment. 32943 * 32944 * By default, the template URL is restricted to the same domain and protocol as the 32945 * application document. This is done by calling {@link $sce#getTrustedResourceUrl 32946 * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols 32947 * you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or 32948 * {@link $sce#trustAsResourceUrl wrap them} as trusted values. Refer to Angular's {@link 32949 * ng.$sce Strict Contextual Escaping}. 32950 * 32951 * In addition, the browser's 32952 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 32953 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 32954 * policy may further restrict whether the template is successfully loaded. 32955 * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://` 32956 * access on some browsers. 32957 * 32958 * @animations 32959 * | Animation | Occurs | 32960 * |----------------------------------|-------------------------------------| 32961 * | {@link ng.$animate#enter enter} | when the expression changes, on the new include | 32962 * | {@link ng.$animate#leave leave} | when the expression changes, on the old include | 32963 * 32964 * The enter and leave animation occur concurrently. 32965 * 32966 * @scope 32967 * @priority 400 32968 * 32969 * @param {string} ngInclude|src angular
vendor: 9,710 bytes, lines 32969-33246
32969 expression evaluating to URL. If the source is a string constant, 32970 * make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`. 32971 * @param {string=} onload Expression to evaluate when a new partial is loaded. 32972 * <div class="alert alert-warning"> 32973 * **Note:** When using onload on SVG elements in IE11, the browser will try to call 32974 * a function with the name on the window element, which will usually throw a 32975 * "function is undefined" error. To fix this, you can instead use `data-onload` or a 32976 * different form that {@link guide/directive#normalization matches} `onload`. 32977 * </div> 32978 * 32979 * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll 32980 * $anchorScroll} to scroll the viewport after the content is loaded. 32981 * 32982 * - If the attribute is not set, disable scrolling. 32983 * - If the attribute is set without value, enable scrolling. 32984 * - Otherwise enable scrolling only if the expression evaluates to truthy value. 32985 * 32986 * @example 32987 <example module="includeExample" deps="angular-animate.js" animations="true" name="ng-include"> 32988 <file name="index.html"> 32989 <div ng-controller="ExampleController"> 32990 <select ng-model="template" ng-options="t.name for t in templates"> 32991 <option value="">(blank)</option> 32992 </select> 32993 url of the template: <code>{{template.url}}</code> 32994 <hr/> 32995 <div class="slide-animate-container"> 32996 <div class="slide-animate" ng-include="template.url"></div> 32997 </div> 32998 </div> 32999 </file> 33000 <file name="script.js"> 33001 angular.module('includeExample', ['ngAnimate']) 33002 .controller('ExampleController', ['$scope', function($scope) { 33003 $scope.templates = 33004 [{ name: 'template1.html', url: 'template1.html'}, 33005 { name: 'template2.html', url: 'template2.html'}]; 33006 $scope.template = $scope.templates[0]; 33007 }]); 33008 </file> 33009 <file name="template1.html"> 33010 Content of template1.html 33011 </file> 33012 <file name="template2.html"> 33013 Content of template2.html 33014 </file> 33015 <file name="animations.css"> 33016 .slide-animate-container { 33017 position:relative; 33018 background:white; 33019 border:1px solid black; 33020 height:40px; 33021 overflow:hidden; 33022 } 33023 33024 .slide-animate { 33025 padding:10px; 33026 } 33027 33028 .slide-animate.ng-enter, .slide-animate.ng-leave { 33029 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 33030 33031 position:absolute; 33032 top:0; 33033 left:0; 33034 right:0; 33035 bottom:0; 33036 display:block; 33037 padding:10px; 33038 } 33039 33040 .slide-animate.ng-enter { 33041 top:-50px; 33042 } 33043 .slide-animate.ng-enter.ng-enter-active { 33044 top:0; 33045 } 33046 33047 .slide-animate.ng-leave { 33048 top:0; 33049 } 33050 .slide-animate.ng-leave.ng-leave-active { 33051 top:50px; 33052 } 33053 </file> 33054 <file name="protractor.js" type="protractor"> 33055 var templateSelect = element(by.model('template')); 33056 var includeElem = element(by.css('[ng-include]')); 33057 33058 it('should load template1.html', function() { 33059 expect(includeElem.getText()).toMatch(/Content of template1.html/); 33060 }); 33061 33062 it('should load template2.html', function() { 33063 if (browser.params.browser === 'firefox') { 33064 // Firefox can't handle using selects 33065 // See https://github.com/angular/protractor/issues/480 33066 return; 33067 } 33068 templateSelect.click(); 33069 templateSelect.all(by.css('option')).get(2).click(); 33070 expect(includeElem.getText()).toMatch(/Content of template2.html/); 33071 }); 33072 33073 it('should change to blank', function() { 33074 if (browser.params.browser === 'firefox') { 33075 // Firefox can't handle using selects 33076 return; 33077 } 33078 templateSelect.click(); 33079 templateSelect.all(by.css('option')).get(0).click(); 33080 expect(includeElem.isPresent()).toBe(false); 33081 }); 33082 </file> 33083 </example> 33084 */ 33085 33086 33087/** 33088 * @ngdoc event 33089 * @name ngInclude#$includeContentRequested 33090 * @eventType emit on the scope ngInclude was declared in 33091 * @description 33092 * Emitted every time the ngInclude content is requested. 33093 * 33094 * @param {Object} angularEvent Synthetic event object. 33095 * @param {String} src URL of content to load. 33096 */ 33097 33098 33099/** 33100 * @ngdoc event 33101 * @name ngInclude#$includeContentLoaded 33102 * @eventType emit on the current ngInclude scope 33103 * @description 33104 * Emitted every time the ngInclude content is reloaded. 33105 * 33106 * @param {Object} angularEvent Synthetic event object. 33107 * @param {String} src URL of content to load. 33108 */ 33109 33110 33111/** 33112 * @ngdoc event 33113 * @name ngInclude#$includeContentError 33114 * @eventType emit on the scope ngInclude was declared in 33115 * @description 33116 * Emitted when a template HTTP request yields an erroneous response (status < 200 || status > 299) 33117 * 33118 * @param {Object} angularEvent Synthetic event object. 33119 * @param {String} src URL of content to load. 33120 */ 33121var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate', 33122 function($templateRequest, $anchorScroll, $animate) { 33123 return { 33124 restrict: 'ECA', 33125 priority: 400, 33126 terminal: true, 33127 transclude: 'element', 33128 controller: angular.noop, 33129 compile: function(element, attr) { 33130 var srcExp = attr.ngInclude || attr.src, 33131 onloadExp = attr.onload || '', 33132 autoScrollExp = attr.autoscroll; 33133 33134 return function(scope, $element, $attr, ctrl, $transclude) { 33135 var changeCounter = 0, 33136 currentScope, 33137 previousElement, 33138 currentElement; 33139 33140 var cleanupLastIncludeContent = function() { 33141 if (previousElement) { 33142 previousElement.remove(); 33143 previousElement = null; 33144 } 33145 if (currentScope) { 33146 currentScope.$destroy(); 33147 currentScope = null; 33148 } 33149 if (currentElement) { 33150 $animate.leave(currentElement).done(function(response) { 33151 if (response !== false) previousElement = null; 33152 }); 33153 previousElement = currentElement; 33154 currentElement = null; 33155 } 33156 }; 33157 33158 scope.$watch(srcExp, function ngIncludeWatchAction(src) { 33159 var afterAnimation = function(response) { 33160 if (response !== false && isDefined(autoScrollExp) && 33161 (!autoScrollExp || scope.$eval(autoScrollExp))) { 33162 $anchorScroll(); 33163 } 33164 }; 33165 var thisChangeId = ++changeCounter; 33166 33167 if (src) { 33168 //set the 2nd param to true to ignore the template request error so that the inner 33169 //contents and scope can be cleaned up. 33170 $templateRequest(src, true).then(function(response) { 33171 if (scope.$$destroyed) return; 33172 33173 if (thisChangeId !== changeCounter) return; 33174 var newScope = scope.$new(); 33175 ctrl.template = response; 33176 33177 // Note: This will also link all children of ng-include that were contained in the original 33178 // html. If that content contains controllers, ... they could pollute/change the scope. 33179 // However, using ng-include on an element with additional content does not make sense... 33180 // Note: We can't remove them in the cloneAttchFn of $transclude as that 33181 // function is called before linking the content, which would apply child 33182 // directives to non existing elements. 33183 var clone = $transclude(newScope, function(clone) { 33184 cleanupLastIncludeContent(); 33185 $animate.enter(clone, null, $element).done(afterAnimation); 33186 }); 33187 33188 currentScope = newScope; 33189 currentElement = clone; 33190 33191 currentScope.$emit('$includeContentLoaded', src); 33192 scope.$eval(onloadExp); 33193 }, function() { 33194 if (scope.$$destroyed) return; 33195 33196 if (thisChangeId === changeCounter) { 33197 cleanupLastIncludeContent(); 33198 scope.$emit('$includeContentError', src); 33199 } 33200 }); 33201 scope.$emit('$includeContentRequested', src); 33202 } else { 33203 cleanupLastIncludeContent(); 33204 ctrl.template = null; 33205 } 33206 }); 33207 }; 33208 } 33209 }; 33210}]; 33211 33212// This directive is called during the $transclude call of the first `ngInclude` directive. 33213// It will replace and compile the content of the element with the loaded template. 33214// We need this directive so that the element content is already filled when 33215// the link function of another directive on the same element as ngInclude 33216// is called. 33217var ngIncludeFillContentDirective = ['$compile', 33218 function($compile) { 33219 return { 33220 restrict: 'ECA', 33221 priority: -400, 33222 require: 'ngInclude', 33223 link: function(scope, $element, $attr, ctrl) { 33224 if (toString.call($element[0]).match(/SVG/)) { 33225 // WebKit: https://bugs.webkit.org/show_bug.cgi?id=135698 --- SVG elements do not 33226 // support innerHTML, so detect this here and try to generate the contents 33227 // specially. 33228 $element.empty(); 33229 $compile(jqLiteBuildFragment(ctrl.template, window.document).childNodes)(scope, 33230 function namespaceAdaptedClone(clone) { 33231 $element.append(clone); 33232 }, {futureParentElement: $element}); 33233 return; 33234 } 33235 33236 $element.html(ctrl.template); 33237 $compile($element.contents())(scope); 33238 } 33239 }; 33240 }]; 33241 33242/** 33243 * @ngdoc directive 33244 * @name ngInit 33245 * @restrict AC 33246 *
33246 33247 * @description 33248 * The `ngInit` directive allows you to evaluate an expression in the 33249 * current scope. 33250 * 33251 * <div class="alert alert-danger"> 33252 * This directive can be abused to add unnecessary amounts of logic into your templates. 33253 * There are only a few appropriate uses of `ngInit`, such as for aliasing special properties of 33254 * {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below; and for injecting data via 33255 * server side scripting. Besides these few cases, you should use {@link guide/controller controllers} 33256 * rather than `ngInit` to initialize values on a scope. 33257 * </div> 33258 * 33259 * <div class="alert alert-warning"> 33260 * **Note**: If you have assignment in `ngInit` along with a {@link ng.$filter `filter`}, make 33261 * sure you have parentheses to ensure correct operator precedence: 33262 * <pre class="prettyprint"> 33263 * `<div ng-init="test1 = ($index | toString)"></div>` 33264 * </pre> 33265 * </div> 33266 * 33267 * @priority 450 33268 * 33269 * @element ANY 33270 * @param {expression} ngInit {@link guide/expression Expression} to eval. 33271 * 33272 * @example 33273 <example module="initExample" name="ng-init"> 33274 <file name="index.html"> 33275 <script> 33276 angular.module('initExample', []) 33277 .controller('ExampleController', ['$scope', function($scope) { 33278 $scope.list = [['a', 'b'], ['c', 'd']]; 33279 }]); 33280 </script>
33281 <div ng-controller="ExampleController"> 33282 <div ng-repeat="innerList in list" ng-init="outerIndex = $index"> 33283 <div ng-repeat="value in innerList" ng-init="innerIndex = $index"> 33284 <span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span> 33285 </div> 33286 </div> 33287 </div> 33288 </file> 33289 <file name="protractor.js" type="protractor"> 33290 it('should alias index positions', function() { 33291 var elements = element.all(by.css('.example-init')); 33292 expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;'); 33293 expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;'); 33294 expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;'); 33295 expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;'); 33296 }); 33297 </file> 33298 </example> 33299 */ 33300var ngInitDirective = ngDirective({ 33301 priority: 450, 33302 compile: function() { 33303 return { 33304 pre: function(scope, element, attrs) { 33305 scope.$eval(attrs.ngInit); 33306 } 33307 }; 33308 } 33309}); 33310 33311/** 33312 * @ngdoc directive 33313 * @name ngList 33314 * 33315 * @description 33316 * Text input that converts between a delimited string and an array of strings. The default 33317 * delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom 33318 * delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`. 33319 * 33320 * The behaviour of the directive is affected by the use of the `ngTrim` attribute. 33321 * * If `ngTrim` is set to `"false"` then whitespace around both the separator and each 33322 * list item is respected. This implies that the user of the directive is responsible for 33323 * dealing with whitespace but also allows you to use whitespace as a delimiter, such as a 33324 * tab or newline character. 33325 * * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected 33326 * when joining the list items back together) and whitespace around each list item is stripped 33327 * before it is added to the model. 33328 * 33329 * ### Example with Validation 33330 * 33331 * <example name="ngList-directive" module="listExample"> 33332 * <file name="app.js"> 33333 * angular.module('listExample', []) 33334 * .controller('ExampleController', ['$scope', function($scope) { 33335 * $scope.names = ['morpheus', 'neo', 'trinity']; 33336 * }]); 33337 * </file> 33338 * <file name="index.html">
33339 * <form name="myForm" ng-controller="ExampleController"> 33340 * <label>List: <input name="namesInput" ng-model="names" ng-list required></label> 33341 * <span role="alert"> 33342 * <span class="error" ng-show="myForm.namesInput.$error.required"> 33343 * Required!</span> 33344 * </span> 33345 * <br> 33346 * <tt>names = {{names}}</tt><br/> 33347 * <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/> 33348 * <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/> 33349 * <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 33350 * <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 33351 * </form> 33352 * </file> 33353 * <file name="protractor.js" type="protractor"> 33354 * var listInput = element(by.model('names')); 33355 * var names = element(by.exactBinding('names')); 33356 * var valid = element(by.binding('myForm.namesInput.$valid')); 33357 * var error = element(by.css('span.error')); 33358 * 33359 * it('should initialize to model', function() { 33360 * expect(names.getText()).toContain('["morpheus","neo","trinity"]'); 33361 * expect(valid.getText()).toContain('true'); 33362 * expect(error.getCssValue('display')).toBe('none'); 33363 * }); 33364 * 33365 * it('should be invalid if empty', function() { 33366 * listInput.clear(); 33367 * listInput.sendKeys(''); 33368 * 33369 * expect(names.getText()).toContain(''); 33370 * expect(valid.getText()).toContain('false'); 33371 * expect(error.getCssValue('display')).not.toBe('none'); 33372 * }); 33373 * </file> 33374 * </example> 33375 * 33376 * ### Example - splitting on newline 33377 * <example name="ngList-directive-newlines"> 33378 * <file name="index.html"> 33379 * <textarea ng-model="list" ng-list=" " ng-trim="false"></textarea> 33380 * <pre>{{ list | json }}</pre> 33381 * </file> 33382 * <file name="protractor.js" type="protractor"> 33383 * it("should split the text by newlines", function() { 33384 * var listInput = element(by.model('list')); 33385 * var output = element(by.binding('list | json')); 33386 * listInput.sendKeys('abc\ndef\nghi'); 33387 * expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]'); 33388 * }); 33389 * </file> 33390 * </example> 33391 * 33392 * @element input 33393 * @param {string=} ngList optional delimiter that should be used to split the value. 33394 */ 33395var ngListDirective = function() { 33396 return { 33397 restrict: 'A', 33398 priority: 100, 33399 require: 'ngModel', 33400 link: function(scope, element, attr, ctrl) { 33401 // We want to control whitespace trimming so we use this convoluted approach 33402 // to access the ngList attribute, which doesn't pre-trim the attribute 33403 var ngList = element.attr(attr.$attr.ngList) || ', '; 33404 var trimValues = attr.ngTrim !== 'false'; 33405 var separator = trimValues ? trim(ngList) : ngList; 33406 33407 var parse = function(viewValue) { 33408 // If the viewValue is invalid (say required but empty) it will be `undefined` 33409 if (isUndefined(viewValue)) return; 33410 33411 var list = []; 33412 33413 if (viewValue) { 33414 forEach(viewValue.split(separator), function(value) { 33415 if (value) list.push(trimValues ? trim(value) : value); 33416 }); 33417 } 33418 33419 return list; 33420 }; 33421 33422 ctrl.$parsers.push(parse); 33423 ctrl.$formatters.push(function(value) { 33424 if (isArray(value)) { 33425 return value.join(ngList); 33426 } 33427 33428 return undefined; 33429 }); 33430 33431 // Override the standard $isEmpty because an empty array means the input is empty. 33432 ctrl.$isEmpty = function(value) { 33433 return !value || !value.length; 33434 }; 33435 } 33436 }; 33437}; 33438 33439/* global VALID_CLASS: true, 33440 INVALID_CLASS: true, 33441 PRISTINE_CLASS: true, 33442 DIRTY_CLASS: true, 33443 UNTOUCHED_CLASS: true, 33444 TOUCHED_CLASS: true 33445*/ 33446 33447var VALID_CLASS = 'ng-valid', 33448 INVALID_CLASS = 'ng-invalid', 33449 PRISTINE_CLASS = 'ng-pristine', 33450 DIRTY_CLASS = 'ng-dirty', 33451 UNTOUCHED_CLASS = 'ng-untouched', 33452 TOUCHED_CLASS = 'ng-touched', 33453 PENDING_CLASS = 'ng-pending', 33454 EMPTY_CLASS = 'ng-empty', 33455 NOT_EMPTY_CLASS = 'ng-not-empty'; 33456 33457var ngModelMinErr = minErr('ngModel'); 33458 33459/** 33460 * @ngdoc type 33461 * @name ngModel.NgModelController 33462 * 33463 * @property {*} $viewValue The actual value from the control's view. For `input` elements, this is a 33464 * String. See {@link ngModel.NgModelController#$setViewValue} for information about when the $viewValue 33465 * is set. 33466 * @property {*} $modelValue The value in the model that the control is bound to. 33467 * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever 33468 the control reads value from the DOM. The functions are called in array order, each passing 33469 its return value through to the next. The last return value is forwarded to the 33470 {@link ngModel.NgModelController#$validators `$validators`} collection. 33471 33472Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue 33473`$viewValue`}. 33474 33475Returning `undefined` from a parser means a parse error occurred. In that case, 33476no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel` 33477will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`} 33478is set to `true`. The parse error is stored in `ngModel.$error.parse`. 33479 33480 * 33481 * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever 33482 the model value changes. The functions are called in reverse array order, each passing the value through to the 33483 next. The last return value is used as the actual DOM value. 33484 Used to format / convert values for display in the control. 33485 * ```js 33486 * function formatter(value) { 33487 * if (value) { 33488 * return value.toUpperCase(); 33489 * } 33490 * } 33491 * ngModel.$formatters.push(formatter); 33492 * ``` 33493 * 33494 * @property {Object.<string, function>} $validators A collection of validators that are applied 33495 * whenever the model value changes. The key value within the object refers to the name of the 33496 * validator while the function refers to the validation operation. The validation operation is 33497 * provided with the model value as an argument and must return a true or false value depending 33498 * on the response of that validation. 33499 * 33500 * ```js 33501 * ngModel.$validators.validCharacters = function(modelValue, viewValue) { 33502 * var value = modelValue || viewValue; 33503 * return /[0-9]+/.test(value) && 33504 * /[a-z]+/.test(value) && 33505 * /[A-Z]+/.test(value) && 33506 * /\W+/.test(value); 33507 * }; 33508 * ``` 33509 * 33510 * @property {Object.<string, function>} $asyncValidators A collection of validations that are expected to 33511 * perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided 33512 * is expected to return a promise when it is run during the model validation process. Once the promise 33513 * is delivered then the validation status will be set to true when fulfilled and false when rejected. 33514 * When the asynchronous validators are triggered, each of the validators will run in parallel and the model 33515 * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator 33516 * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators 33517 * will only run once all synchronous validators have passed. 33518 * 33519 * Please note that if $http is used then it is important that the server returns a success HTTP response code 33520 * in order to fulfill the validation and a status level of `4xx` in order to reject the validation. 33521 * 33522 * ```js 33523 * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) { 33524 * var value = modelValue || viewValue; 33525 * 33526 * // Lookup user by username 33527 * return $http.get('/api/users/' + value). 33528 * then(function resolved() { 33529 * //username exists, this means validation fails 33530 * return $q.reject('exists'); 33531 * }, function rejected() { 33532 * //username does not exist, therefore this validation passes 33533 * return true; 33534 * }); 33535 * }; 33536 * ``` 33537 * 33538 * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the 33539 * view value has changed. It is called with no arguments, and its return value is ignored. 33540 * This can be used in place of additional $watches against the model value. 33541 * 33542 * @property {Object} $error An object hash with all failing validator ids as keys. 33543 * @property {Object} $pending An object hash with all pending validator ids as keys. 33544 * 33545 * @property {boolean} $untouched True if control has not lost focus yet. 33546 * @property {boolean} $touched True if control has lost focus. 33547 * @property {boolean} $pristine True if user has not interacted with the control yet. 33548 * @property {boolean} $dirty True if user has already interacted with the control. 33549 * @property {boolean} $valid True if there is no error. 33550 * @property {boolean}
33550 $invalid True if at least one error on the control. 33551 * @property {string} $name The name attribute of the control. 33552 * 33553 * @description 33554 * 33555 * `NgModelController` provides API for the {@link ngModel `ngModel`} directive. 33556 * The controller contains services for data-binding, validation, CSS updates, and value formatting 33557 * and parsing. It purposefully does not contain any logic which deals with DOM rendering or 33558 * listening to DOM events. 33559 * Such DOM related logic should be provided by other directives which make use of 33560 * `NgModelController` for data-binding to control elements. 33561 * Angular provides this DOM logic for most {@link input `input`} elements. 33562 * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example 33563 * custom control example} that uses `ngModelController` to bind to `contenteditable` elements. 33564 * 33565 * @example 33566 * ### Custom Control Example 33567 * This example shows how to use `NgModelController` with a custom control to achieve 33568 * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) 33569 * collaborate together to achieve the desired result. 33570 * 33571 * `contenteditable` is an HTML5 attribute, which tells the browser to let the element 33572 * contents be edited in place by the user. 33573 * 33574 * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize} 33575 * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`). 33576 * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks 33577 * that content using the `$sce` service. 33578 * 33579 * <example name="NgModelController" module="customControl" deps="angular-sanitize.js"> 33580 <file name="style.css"> 33581 [contenteditable] { 33582 border: 1px solid black; 33583 background-color: white; 33584 min-height: 20px; 33585 } 33586 33587 .ng-invalid { 33588 border: 1px solid red; 33589 } 33590 33591 </file> 33592 <file name="script.js"> 33593 angular.module('customControl', ['ngSanitize']). 33594 directive('contenteditable', ['$sce', function($sce) { 33595 return { 33596 restrict: 'A', // only activate on element attribute 33597 require: '?ngModel', // get a hold of NgModelController 33598 link: function(scope, element, attrs, ngModel) { 33599 if (!ngModel) return; // do nothing if no ng-model 33600 33601 // Specify how UI should be updated 33602 ngModel.$render = function() { 33603 element.html($sce.getTrustedHtml(ngModel.$viewValue || '')); 33604 }; 33605 33606 // Listen for change events to enable binding 33607 element.on('blur keyup change', function() { 33608 scope.$evalAsync(read); 33609 }); 33610 read(); // initialize 33611 33612 // Write data to the model 33613 function read() { 33614 var html = element.html(); 33615 // When we clear the content editable the browser leaves a <br> behind 33616 // If strip-br attribute is provided then we strip this out 33617 if (attrs.stripBr && html === '<br>') { 33618 html = ''; 33619 } 33620 ngModel.$setViewValue(html); 33621 } 33622 } 33623 }; 33624 }]); 33625 </file> 33626 <file name="index.html"> 33627 <form name="myForm"> 33628 <div contenteditable 33629 name="myWidget" ng-model="userContent" 33630 strip-br="true" 33631 required>Change me!</div> 33632 <span ng-show="myForm.myWidget.$error.required">Required!</span> 33633 <hr> 33634 <textarea ng-model="userContent" aria-label="Dynamic textarea"></textarea> 33635 </form> 33636 </file> 33637 <file name="protractor.js" type="protractor"> 33638 it('should data-bind and become invalid', function() { 33639 if (browser.params.browser === 'safari' || browser.params.browser === 'firefox') { 33640 // SafariDriver can't handle contenteditable 33641 // and Firefox driver can't clear contenteditables very well 33642 return; 33643 } 33644 var contentEditable = element(by.css('[contenteditable]')); 33645 var content = 'Change me!'; 33646 33647 expect(contentEditable.getText()).toEqual(content); 33648 33649 contentEditable.clear(); 33650 contentEditable.sendKeys(protractor.Key.BACK_SPACE); 33651 expect(contentEditable.getText()).toEqual(''); 33652 expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/); 33653 }); 33654 </file> 33655 * </example> 33656 * 33657 * 33658 */ 33659var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$rootScope', '$q', '$interpolate', 33660 /** @this */ function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q, $interpolate) { 33661 this.$viewValue = Number.NaN; 33662 this.$modelValue = Number.NaN; 33663 this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity. 33664 this.$validators = {}; 33665 this.$asyncValidators = {}; 33666 this.$parsers = []; 33667 this.$formatters = []; 33668 this.$viewChangeListeners = []; 33669 this.$untouched = true; 33670 this.$touched = false;
33671 this.$pristine = true; 33672 this.$dirty = false; 33673 this.$valid = true; 33674 this.$invalid = false; 33675 this.$error = {}; // keep invalid keys here 33676 this.$$success = {}; // keep valid keys here 33677 this.$pending = undefined; // keep pending keys here 33678 this.$name = $interpolate($attr.name || '', false)($scope); 33679 this.$$parentForm = nullFormCtrl; 33680 33681 var parsedNgModel = $parse($attr.ngModel), 33682 parsedNgModelAssign = parsedNgModel.assign, 33683 ngModelGet = parsedNgModel, 33684 ngModelSet = parsedNgModelAssign, 33685 pendingDebounce = null, 33686 parserValid, 33687 ctrl = this; 33688 33689 this.$$setOptions = function(options) { 33690 ctrl.$options = options; 33691 if (options && options.getterSetter) { 33692 var invokeModelGetter = $parse($attr.ngModel + '()'), 33693 invokeModelSetter = $parse($attr.ngModel + '($$$p)'); 33694 33695 ngModelGet = function($scope) { 33696 var modelValue = parsedNgModel($scope); 33697 if (isFunction(modelValue)) { 33698 modelValue = invokeModelGetter($scope); 33699 } 33700 return modelValue; 33701 }; 33702 ngModelSet = function($scope, newValue) { 33703 if (isFunction(parsedNgModel($scope))) { 33704 invokeModelSetter($scope, {$$$p: newValue}); 33705 } else { 33706 parsedNgModelAssign($scope, newValue); 33707 } 33708 }; 33709 } else if (!parsedNgModel.assign) { 33710 throw ngModelMinErr('nonassign', 'Expression \'{0}\' is non-assignable. Element: {1}', 33711 $attr.ngModel, startingTag($element)); 33712 } 33713 }; 33714 33715 /** 33716 * @ngdoc method 33717 * @name ngModel.NgModelController#$render 33718 * 33719 * @description 33720 * Called when the view needs to be updated. It is expected that the user of the ng-model 33721 * directive will implement this method. 33722 * 33723 * The `$render()` method is invoked in the following situations: 33724 * 33725 * * `$rollbackViewValue()` is called. If we are rolling back the view value to the last 33726 * committed value then `$render()` is called to update the input control. 33727 * * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and 33728 * the `$viewValue` are different from last time. 33729 * 33730 * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of 33731 * `$modelValue` and `$viewValue` are actually different from their previous values. If `$modelValue` 33732 * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be 33733 * invoked if you only change a property on the objects. 33734 */ 33735 this.$render = noop; 33736 33737 /** 33738 * @ngdoc method 33739 * @name ngModel.NgModelController#$isEmpty 33740 * 33741 * @description 33742 * This is called when we need to determine if the value of an input is empty. 33743 * 33744 * For instance, the required directive does this to work out if the input has data or not. 33745 * 33746 * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`. 33747 * 33748 * You can override this for input directives whose concept of being empty is different from the 33749 * default. The `checkboxInputType` directive does this because in its case a value of `false` 33750 * implies empty. 33751 * 33752 * @param {*} value The value of the input to check for emptiness. 33753 * @returns {boolean} True if `value` is "empty". 33754 */ 33755 this.$isEmpty = function(value) { 33756 // eslint-disable-next-line no-self-compare 33757 return isUndefined(value) || value === '' || value === null || value !== value; 33758 }; 33759 33760 this.$$updateEmptyClasses = function(value) { 33761 if (ctrl.$isEmpty(value)) { 33762 $animate.removeClass($element, NOT_EMPTY_CLASS); 33763 $animate.addClass($element, EMPTY_CLASS); 33764 } else { 33765 $animate.removeClass($element, EMPTY_CLASS); 33766 $animate.addClass($element, NOT_EMPTY_CLASS); 33767 } 33768 }; 33769 33770 33771 var currentValidationRunId = 0; 33772 33773 /** 33774 * @ngdoc method 33775 * @name ngModel.NgModelController#$setValidity 33776 * 33777 * @description 33778 * Change the validity state, and notify the form. 33779 * 33780 * This method can be called within $parsers/$formatters or a custom validation implementation. 33781 * However, in most cases it should be sufficient to use the `ngModel.$validators` and 33782 * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically. 33783 * 33784 * @param {string}
33784 validationErrorKey Name of the validator. The `validationErrorKey` will be assigned 33785 * to either `$error[validationErrorKey]` or `$pending[validationErrorKey]` 33786 * (for unfulfilled `$asyncValidators`), so that it is available for data-binding. 33787 * The `validationErrorKey` should be in camelCase and will get converted into dash-case 33788 * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` 33789 * class and can be bound to as `{{someForm.someControl.$error.myError}}` . 33790 * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined), 33791 * or skipped (null). Pending is used for unfulfilled `$asyncValidators`. 33792 * Skipped is used by Angular when validators do not run because of parse errors and 33793 * when `$asyncValidators` do not run because any of the `$validators` failed. 33794 */ 33795 addSetValidityMethod({ 33796 ctrl: this, 33797 $element: $element, 33798 set: function(object, property) { 33799 object[property] = true; 33800 }, 33801 unset: function(object, property) { 33802 delete object[property]; 33803 }, 33804 $animate: $animate 33805 }); 33806 33807 /** 33808 * @ngdoc method 33809 * @name ngModel.NgModelController#$setPristine 33810 * 33811 * @description 33812 * Sets the control to its pristine state. 33813 * 33814 * This method can be called to remove the `ng-dirty` class and set the control to its pristine 33815 * state (`ng-pristine` class). A model is considered to be pristine when the control 33816 * has not been changed from when first compiled. 33817 */ 33818 this.$setPristine = function() { 33819 ctrl.$dirty = false; 33820 ctrl.$pristine = true; 33821 $animate.removeClass($element, DIRTY_CLASS); 33822 $animate.addClass($element, PRISTINE_CLASS); 33823 }; 33824 33825 /** 33826 * @ngdoc method 33827 * @name ngModel.NgModelController#$setDirty 33828 * 33829 * @description 33830 * Sets the control to its dirty state. 33831 * 33832 * This method can be called to remove the `ng-pristine` class and set the control to its dirty 33833 * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed 33834 * from when first compiled. 33835 */ 33836 this.$setDirty = function() { 33837 ctrl.$dirty = true; 33838 ctrl.$pristine = false; 33839 $animate.removeClass($element, PRISTINE_CLASS); 33840 $animate.addClass($element, DIRTY_CLASS); 33841 ctrl.$$parentForm.$setDirty(); 33842 }; 33843 33844 /** 33845 * @ngdoc method 33846 * @name ngModel.NgModelController#$setUntouched 33847 * 33848 * @description 33849 * Sets the control to its untouched state. 33850 * 33851 * This method can be called to remove the `ng-touched` class and set the control to its 33852 * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched 33853 * by default, however this function can be used to restore that state if the model has 33854 * already been touched by the user. 33855 */ 33856 this.$setUntouched = function() { 33857 ctrl.$touched = false; 33858 ctrl.$untouched = true; 33859 $animate.setClass($element, UNTOUCHED_CLASS, TOUCHED_CLASS); 33860 }; 33861 33862 /** 33863 * @ngdoc method 33864 * @name ngModel.NgModelController#$setTouched 33865 * 33866 * @description 33867 * Sets the control to its touched state. 33868 * 33869 * This method can be called to remove the `ng-untouched` class and set the control to its 33870 * touched state (`ng-touched` class). A model is considered to be touched when the user has 33871 * first focused the control element and then shifted focus away from the control (blur event). 33872 */ 33873 this.$setTouched = function() { 33874 ctrl.$touched = true; 33875 ctrl.$untouched = false; 33876 $animate.setClass($element, TOUCHED_CLASS, UNTOUCHED_CLASS); 33877 }; 33878 33879 /** 33880 * @ngdoc method 33881 * @name ngModel.NgModelController#$rollbackViewValue 33882 * 33883 * @description 33884 * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`, 33885 * which may be caused by a pending debounced event or because the input is waiting for some 33886 * future event. 33887 * 33888 * If you have an input that uses `ng-model-options` to set up debounced updates or updates that 33889 * depend on special events such as `blur`, there can be a period when the `$viewValue` is out of 33890 * sync with the ngModel's `$modelValue`. 33891 * 33892 * In this case, you can use `$rollbackViewValue()` to manually cancel the debounced / future update 33893 * and reset the input to the last committed view value. 33894 * 33895 * It is also possible that you run into difficulties if you try to update the ngModel's `$modelValue` 33896 * programmatically before these debounced/future events have resolved/occurred, because Angular's 33897 * dirty checking mechanism is not able to tell whether the model has actually changed or not. 33898 * 33899 * The `$rollbackViewValue()` method should be called before programmatically changing the model of an 33900 * input which may have such events pending. This is important in order to make sure that the 33901 * input field will be updated with the new model value and any pending operations are cancelled. 33902 * 33903 * <example name="ng-model-cancel-update" module="cancel-update-example"> 33904 * <file name="app.js">
33905 * angular.module('cancel-update-example', []) 33906 * 33907 * .controller('CancelUpdateController', ['$scope', function($scope) { 33908 * $scope.model = {value1: '', value2: ''}; 33909 * 33910 * $scope.setEmpty = function(e, value, rollback) { 33911 * if (e.keyCode === 27) { 33912 * e.preventDefault(); 33913 * if (rollback) { 33914 * $scope.myForm[value].$rollbackViewValue(); 33915 * } 33916 * $scope.model[value] = ''; 33917 * } 33918 * }; 33919 * }]); 33920 * </file> 33921 * <file name="index.html"> 33922 * <div ng-controller="CancelUpdateController"> 33923 * <p>Both of these inputs are only updated if they are blurred. Hitting escape should 33924 * empty them. Follow these steps and observe the difference:</p> 33925 * <ol> 33926 * <li>Type something in the input. You will see that the model is not yet updated</li> 33927 * <li>Press the Escape key. 33928 * <ol> 33929 * <li> In the first example, nothing happens, because the model is already '', and no 33930 * update is detected. If you blur the input, the model will be set to the current view. 33931 * </li> 33932 * <li> In the second example, the pending update is cancelled, and the input is set back 33933 * to the last committed view value (''). Blurring the input does nothing. 33934 * </li> 33935 * </ol> 33936 * </li> 33937 * </ol> 33938 * 33939 * <form name="myForm" ng-model-options="{ updateOn: 'blur' }"> 33940 * <div> 33941 * <p id="inputDescription1">Without $rollbackViewValue():</p> 33942 * <input name="value1" aria-describedby="inputDescription1" ng-model="model.value1" 33943 * ng-keydown="setEmpty($event, 'value1')"> 33944 * value1: "{{ model.value1 }}" 33945 * </div> 33946 * 33947 * <div> 33948 * <p id="inputDescription2">With $rollbackViewValue():</p> 33949 * <input name="value2" aria-describedby="inputDescription2" ng-model="model.value2" 33950 * ng-keydown="setEmpty($event, 'value2', true)"> 33951 * value2: "{{ model.value2 }}" 33952 * </div> 33953 * </form> 33954 * </div> 33955 * </file> 33956 <file name="style.css"> 33957 div { 33958 display: table-cell; 33959 } 33960 div:nth-child(1) { 33961 padding-right: 30px; 33962 } 33963 33964 </file> 33965 * </example> 33966 */ 33967 this.$rollbackViewValue = function() { 33968 $timeout.cancel(pendingDebounce); 33969 ctrl.$viewValue = ctrl.$$lastCommittedViewValue; 33970 ctrl.$render(); 33971 }; 33972 33973 /** 33974 * @ngdoc method 33975 * @name ngModel.NgModelController#$validate 33976 * 33977 * @description 33978 * Runs each of the registered validators (first synchronous validators and then 33979 * asynchronous validators). 33980 * If the validity changes to invalid, the model will be set to `undefined`, 33981 * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`. 33982 * If the validity changes to valid, it will set the model to the last available valid 33983 * `$modelValue`, i.e. either the last parsed value or the last value set from the scope. 33984 */ 33985 this.$validate = function() { 33986 // ignore $validate before model is initialized 33987 if (isNumberNaN(ctrl.$modelValue)) { 33988 return; 33989 } 33990 33991 var viewValue = ctrl.$$lastCommittedViewValue; 33992 // Note: we use the $$rawModelValue as $modelValue might have been 33993 // set to undefined during a view -> model update that found validation 33994 // errors. We can't parse the view here, since that could change 33995 // the model although neither viewValue nor the model on the scope changed 33996 var modelValue = ctrl.$$rawModelValue; 33997 33998 var prevValid = ctrl.$valid; 33999 var prevModelValue = ctrl.$modelValue; 34000 34001 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid; 34002 34003 ctrl.$$runValidators(modelValue, viewValue, function(allValid) { 34004 // If there was no change in validity, don't update the model 34005 // This prevents changing an invalid modelValue to undefined 34006 if (!allowInvalid && prevValid !== allValid) { 34007 // Note: Don't check ctrl.$valid here, as we could have 34008 // external validators (e.g. calculated on the server), 34009 // that just call $setValidity and need the model value 34010 // to calculate their validity. 34011 ctrl.$modelValue = allValid ? modelValue : undefined; 34012 34013 if (ctrl.$modelValue !== prevModelValue) { 34014 ctrl.$$writeModelToScope(); 34015 } 34016 } 34017 }); 34018 34019 }; 34020 34021 this.$$runValidators = function(modelValue, viewValue, doneCallback) { 34022 currentValidationRunId++; 34023 var localValidationRunId = currentValidationRunId; 34024 34025 // check parser error 34026 if (!processParseErrors()) { 34027 validationDone(false); 34028 return; 34029 } 34030 if (!processSyncValidators()) { 34031 validationDone(false); 34032 return; 34033 } 34034 processAsyncValidators(); 34035 34036 function processParseErrors() { 34037 var errorKey = ctrl.$$parserName || 'parse'; 34038 if (isUndefined(parserValid)) {
34039 setValidity(errorKey, null); 34040 } else { 34041 if (!parserValid) { 34042 forEach(ctrl.$validators, function(v, name) { 34043 setValidity(name, null); 34044 }); 34045 forEach(ctrl.$asyncValidators, function(v, name) { 34046 setValidity(name, null); 34047 }); 34048 } 34049 // Set the parse error last, to prevent unsetting it, should a $validators key == parserName 34050 setValidity(errorKey, parserValid); 34051 return parserValid; 34052 } 34053 return true; 34054 } 34055 34056 function processSyncValidators() { 34057 var syncValidatorsValid = true; 34058 forEach(ctrl.$validators, function(validator, name) { 34059 var result = validator(modelValue, viewValue); 34060 syncValidatorsValid = syncValidatorsValid && result; 34061 setValidity(name, result); 34062 }); 34063 if (!syncValidatorsValid) { 34064 forEach(ctrl.$asyncValidators, function(v, name) { 34065 setValidity(name, null); 34066 }); 34067 return false; 34068 } 34069 return true; 34070 } 34071 34072 function processAsyncValidators() { 34073 var validatorPromises = []; 34074 var allValid = true; 34075 forEach(ctrl.$asyncValidators, function(validator, name) { 34076 var promise = validator(modelValue, viewValue); 34077 if (!isPromiseLike(promise)) { 34078 throw ngModelMinErr('nopromise', 34079 'Expected asynchronous validator to return a promise but got \'{0}\' instead.', promise); 34080 } 34081 setValidity(name, undefined); 34082 validatorPromises.push(promise.then(function() { 34083 setValidity(name, true); 34084 }, function() { 34085 allValid = false; 34086 setValidity(name, false); 34087 })); 34088 }); 34089 if (!validatorPromises.length) { 34090 validationDone(true); 34091 } else { 34092 $q.all(validatorPromises).then(function() { 34093 validationDone(allValid); 34094 }, noop); 34095 } 34096 } 34097 34098 function setValidity(name, isValid) { 34099 if (localValidationRunId === currentValidationRunId) { 34100 ctrl.$setValidity(name, isValid); 34101 } 34102 } 34103 34104 function validationDone(allValid) { 34105 if (localValidationRunId === currentValidationRunId) { 34106 34107 doneCallback(allValid); 34108 } 34109 } 34110 }; 34111 34112 /** 34113 * @ngdoc method 34114 * @name ngModel.NgModelController#$commitViewValue 34115 * 34116 * @description 34117 * Commit a pending update to the `$modelValue`. 34118 * 34119 * Updates may be pending by a debounced event or because the input is waiting for a some future 34120 * event defined in `ng-model-options`. this method is rarely needed as `NgModelController` 34121 * usually handles calling this in response to input events. 34122 */ 34123 this.$commitViewValue = function() { 34124 var viewValue = ctrl.$viewValue; 34125 34126 $timeout.cancel(pendingDebounce); 34127 34128 // If the view value has not changed then we should just exit, except in the case where there is 34129 // a native validator on the element. In this case the validation state may have changed even though 34130 // the viewValue has stayed empty. 34131 if (ctrl.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !ctrl.$$hasNativeValidators)) { 34132 return; 34133 } 34134 ctrl.$$updateEmptyClasses(viewValue); 34135 ctrl.$$lastCommittedViewValue = viewValue; 34136 34137 // change to dirty 34138 if (ctrl.$pristine) { 34139 this.$setDirty(); 34140 } 34141 this.$$parseAndValidate(); 34142 }; 34143 34144 this.$$parseAndValidate = function() { 34145 var viewValue = ctrl.$$lastCommittedViewValue; 34146 var modelValue = viewValue; 34147 parserValid = isUndefined(modelValue) ? undefined : true; 34148 34149 if (parserValid) { 34150 for (var i = 0; i < ctrl.$parsers.length; i++) { 34151 modelValue = ctrl.$parsers[i](modelValue); 34152 if (isUndefined(modelValue)) { 34153 parserValid = false; 34154 break; 34155 } 34156 } 34157 } 34158 if (isNumberNaN(ctrl.$modelValue)) { 34159 // ctrl.$modelValue has not been touched yet... 34160 ctrl.$modelValue = ngModelGet($scope); 34161 } 34162 var prevModelValue = ctrl.$modelValue; 34163 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid; 34164 ctrl.$$rawModelValue = modelValue; 34165 34166 if (allowInvalid) { 34167 ctrl.$modelValue = modelValue; 34168 writeToModelIfNeeded(); 34169 } 34170 34171 // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date. 34172 // This can happen if e.g. $setViewValue is called from inside a parser 34173 ctrl.$$runValidators(modelValue, ctrl.$$lastCommittedViewValue, function(allValid) { 34174 if (!allowInvalid) { 34175 // Note: Don't check ctrl.$valid here, as we could have 34176 // external validators (e.g. calculated on the server), 34177 // that just call $setValidity and need the model value 34178 // to calculate their validity. 34179 ctrl.$modelValue = allValid ? modelValue : undefined; 34180 writeToModelIfNeeded(); 34181 } 34182 }); 34183 34184 function writeToModelIfNeeded() { 34185 if (ctrl.$modelValue !== prevModelValue) { 34186 ctrl.$$writeModelToScope(); 34187 } 34188 } 34189 }; 34190 34191 this.$$writeModelToScope = function() { 34192 ngModelSet($scope, ctrl.$modelValue);
34193 forEach(ctrl.$viewChangeListeners, function(listener) { 34194 try { 34195 listener(); 34196 } catch (e) { 34197 $exceptionHandler(e); 34198 } 34199 }); 34200 }; 34201 34202 /** 34203 * @ngdoc method 34204 * @name ngModel.NgModelController#$setViewValue 34205 * 34206 * @description 34207 * Update the view value. 34208 * 34209 * This method should be called when a control wants to change the view value; typically, 34210 * this is done from within a DOM event handler. For example, the {@link ng.directive:input input} 34211 * directive calls it when the value of the input changes and {@link ng.directive:select select} 34212 * calls it when an option is selected. 34213 * 34214 * When `$setViewValue` is called, the new `value` will be staged for committing through the `$parsers` 34215 * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged 34216 * value sent directly for processing, finally to be applied to `$modelValue` and then the 34217 * **expression** specified in the `ng-model` attribute. Lastly, all the registered change listeners, 34218 * in the `$viewChangeListeners` list, are called. 34219 * 34220 * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn` 34221 * and the `default` trigger is not listed, all those actions will remain pending until one of the 34222 * `updateOn` events is triggered on the DOM element. 34223 * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions} 34224 * directive is used with a custom debounce for this particular event. 34225 * Note that a `$digest` is only triggered once the `updateOn` events are fired, or if `debounce` 34226 * is specified, once the timer runs out. 34227 * 34228 * When used with standard inputs, the view value will always be a string (which is in some cases 34229 * parsed into another type, such as a `Date` object for `input[date]`.) 34230 * However, custom controls might also pass objects to this method. In this case, we should make 34231 * a copy of the object before passing it to `$setViewValue`. This is because `ngModel` does not 34232 * perform a deep watch of objects, it only looks for a change of identity. If you only change 34233 * the property of the object then ngModel will not realize that the object has changed and 34234 * will not invoke the `$parsers` and `$validators` pipelines. For this reason, you should 34235 * not change properties of the copy once it has been passed to `$setViewValue`. 34236 * Otherwise you may cause the model value on the scope to change incorrectly. 34237 * 34238 * <div class="alert alert-info"> 34239 * In any case, the value passed to the method should always reflect the current value 34240 * of the control. For example, if you are calling `$setViewValue` for an input element, 34241 * you should pass the input DOM value. Otherwise, the control and the scope model become 34242 * out of sync. It's also important to note that `$setViewValue` does not call `$render` or change 34243 * the control's DOM value in any way. If we want to change the control's DOM value 34244 * programmatically, we should update the `ngModel` scope expression. Its new value will be 34245 * picked up by the model controller, which will run it through the `$formatters`, `$render` it 34246 * to update the DOM, and finally call `$validate` on it. 34247 * </div> 34248 * 34249 * @param {*} value value from the view. 34250 * @param {string} trigger Event that triggered the update. 34251 */ 34252 this.$setViewValue = function(value, trigger) { 34253 ctrl.$viewValue = value; 34254 if (!ctrl.$options || ctrl.$options.updateOnDefault) { 34255 ctrl.$$debounceViewValueCommit(trigger); 34256 } 34257 }; 34258 34259 this.$$debounceViewValueCommit = function(trigger) { 34260 var debounceDelay = 0, 34261 options = ctrl.$options, 34262 debounce; 34263 34264 if (options && isDefined(options.debounce)) { 34265 debounce = options.debounce; 34266 if (isNumber(debounce)) { 34267 debounceDelay = debounce; 34268 } else if (isNumber(debounce[trigger])) { 34269 debounceDelay = debounce[trigger]; 34270 } else if (isNumber(debounce['default'])) { 34271 debounceDelay = debounce['default']; 34272 } 34273 } 34274 34275 $timeout.cancel(pendingDebounce); 34276 if (debounceDelay) { 34277 pendingDebounce = $timeout(function() { 34278 ctrl.$commitViewValue(); 34279 }, debounceDelay); 34280 } else if ($rootScope.$$phase) { 34281 ctrl.$commitViewValue(); 34282 } else { 34283 $scope.$apply(function() { 34284 ctrl.$commitViewValue(); 34285 }); 34286 } 34287 }; 34288 34289 // model -> value 34290 // Note: we cannot use a normal scope.$watch as we want to detect the following: 34291 // 1. scope value is 'a' 34292 // 2. user enters 'b' 34293 // 3. ng-change kicks in and reverts scope value to 'a' 34294 // ->
34294 scope value did not change since the last digest as 34295 // ng-change executes in apply phase 34296 // 4. view should be changed back to 'a' 34297 $scope.$watch(function ngModelWatch() { 34298 var modelValue = ngModelGet($scope); 34299 34300 // if scope model value and ngModel value are out of sync 34301 // TODO(perf): why not move this to the action fn? 34302 if (modelValue !== ctrl.$modelValue && 34303 // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator 34304 // eslint-disable-next-line no-self-compare 34305 (ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue) 34306 ) { 34307 ctrl.$modelValue = ctrl.$$rawModelValue = modelValue; 34308 parserValid = undefined; 34309 34310 var formatters = ctrl.$formatters, 34311 idx = formatters.length; 34312 34313 var viewValue = modelValue; 34314 while (idx--) { 34315 viewValue = formatters[idx](viewValue); 34316 } 34317 if (ctrl.$viewValue !== viewValue) { 34318 ctrl.$$updateEmptyClasses(viewValue); 34319 ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue; 34320 ctrl.$render(); 34321 34322 // It is possible that model and view value have been updated during render 34323 ctrl.$$runValidators(ctrl.$modelValue, ctrl.$viewValue, noop); 34324 } 34325 } 34326 34327 return modelValue; 34328 }); 34329}]; 34330 34331 34332/** 34333 * @ngdoc directive 34334 * @name ngModel 34335 * 34336 * @element input 34337 * @priority 1 34338 * 34339 * @description 34340 * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a 34341 * property on the scope using {@link ngModel.NgModelController NgModelController}, 34342 * which is created and exposed by this directive. 34343 * 34344 * `ngModel` is responsible for: 34345 * 34346 * - Binding the view into the model, which other directives such as `input`, `textarea` or `select` 34347 * require. 34348 * - Providing validation behavior (i.e. required, number, email, url). 34349 * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors). 34350 * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, 34351 * `ng-untouched`, `ng-empty`, `ng-not-empty`) including animations. 34352 * - Registering the control with its parent {@link ng.directive:form form}. 34353 * 34354 * Note: `ngModel` will try to bind to the property given by evaluating the expression on the 34355 * current scope. If the property doesn't already exist on this scope, it will be created 34356 * implicitly and added to the scope. 34357 * 34358 * For best practices on using `ngModel`, see: 34359 * 34360 * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes) 34361 * 34362 * For basic examples, how to use `ngModel`, see: 34363 * 34364 * - {@link ng.directive:input input} 34365 * - {@link input[text] text} 34366 * - {@link input[checkbox] checkbox} 34367 * - {@link input[radio] radio} 34368 * - {@link input[number] number} 34369 * - {@link input[email] email} 34370 * - {@link input[url] url} 34371 * - {@link input[date] date} 34372 * - {@link input[datetime-local] datetime-local} 34373 * - {@link input[time] time} 34374 * - {@link input[month] month} 34375 * - {@link input[week] week} 34376 * - {@link ng.directive:select select} 34377 * - {@link ng.directive:textarea textarea} 34378 * 34379 * # Complex Models (objects or collections) 34380 * 34381 * By default, `ngModel` watches the model by reference, not value. This is important to know when 34382 * binding inputs to models that are objects (e.g. `Date`) or collections (e.g. arrays). If only properties of the 34383 * object or collection change, `ngModel` will not be notified and so the input will not be re-rendered. 34384 * 34385 * The model must be assigned an entirely new object or collection before a re-rendering will occur. 34386 * 34387 * Some directives have options that will cause them to use a custom `$watchCollection` on the model expression 34388 * - for example, `ngOptions` will do so when a `track by` clause is included in the comprehension expression or 34389 * if the select is given the `multiple` attribute. 34390 * 34391 * The `$watchCollection()` method only does a shallow comparison, meaning that changing properties deeper than the 34392 * first level of the object (or only changing the properties of an item in the collection if it's an array) will still 34393 * not trigger a re-rendering of the model. 34394 * 34395 * # CSS classes 34396 * The following CSS classes are added and removed on the associated input/select/textarea element 34397 * depending on the validity of the model. 34398 * 34399 * - `ng-valid`: the model is valid
34400 * - `ng-invalid`: the model is invalid 34401 * - `ng-valid-[key]`: for each valid key added by `$setValidity` 34402 * - `ng-invalid-[key]`: for each invalid key added by `$setValidity` 34403 * - `ng-pristine`: the control hasn't been interacted with yet 34404 * - `ng-dirty`: the control has been interacted with 34405 * - `ng-touched`: the control has been blurred 34406 * - `ng-untouched`: the control hasn't been blurred 34407 * - `ng-pending`: any `$asyncValidators` are unfulfilled 34408 * - `ng-empty`: the view does not contain a value or the value is deemed "empty", as defined 34409 * by the {@link ngModel.NgModelController#$isEmpty} method 34410 * - `ng-not-empty`: the view contains a non-empty value 34411 * 34412 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 34413 * 34414 * ## Animation Hooks 34415 * 34416 * Animations within models are triggered when any of the associated CSS classes are added and removed 34417 * on the input element which is attached to the model. These classes include: `.ng-pristine`, `.ng-dirty`, 34418 * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself. 34419 * The animations that are triggered within ngModel are similar to how they work in ngClass and 34420 * animations can be hooked into using CSS transitions, keyframes as well as JS animations. 34421 * 34422 * The following example shows a simple way to utilize CSS transitions to style an input element 34423 * that has been rendered as invalid after it has been validated: 34424 * 34425 * <pre> 34426 * //be sure to include ngAnimate as a module to hook into more 34427 * //advanced animations 34428 * .my-input { 34429 * transition:0.5s linear all; 34430 * background: white; 34431 * } 34432 * .my-input.ng-invalid { 34433 * background: red; 34434 * color:white; 34435 * } 34436 * </pre> 34437 * 34438 * @example 34439 * <example deps="angular-animate.js" animations="true" fixBase="true" module="inputExample" name="ng-model"> 34440 <file name="index.html"> 34441 <script> 34442 angular.module('inputExample', []) 34443 .controller('ExampleController', ['$scope', function($scope) { 34444 $scope.val = '1'; 34445 }]); 34446 </script> 34447 <style> 34448 .my-input { 34449 transition:all linear 0.5s; 34450 background: transparent; 34451 } 34452 .my-input.ng-invalid { 34453 color:white; 34454 background: red; 34455 } 34456 </style> 34457 <p id="inputDescription"> 34458 Update input to see transitions when valid/invalid. 34459 Integer is a valid value. 34460 </p>
34461 <form name="testForm" ng-controller="ExampleController"> 34462 <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input" 34463 aria-describedby="inputDescription" /> 34464 </form> 34465 </file> 34466 * </example> 34467 * 34468 * ## Binding to a getter/setter 34469 * 34470 * Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a 34471 * function that returns a representation of the model when called with zero arguments, and sets 34472 * the internal state of a model when called with an argument. It's sometimes useful to use this 34473 * for models that have an internal representation that's different from what the model exposes 34474 * to the view. 34475 * 34476 * <div class="alert alert-success"> 34477 * **Best Practice:** It's best to keep getters fast because Angular is likely to call them more 34478 * frequently than other parts of your code. 34479 * </div> 34480 * 34481 * You use this behavior by adding `ng-model-options="{ getterSetter: true }"` to an element that 34482 * has `ng-model` attached to it. You can also add `ng-model-options="{ getterSetter: true }"` to 34483 * a `<form>`, which will enable this behavior for all `<input>`s within it. See 34484 * {@link ng.directive:ngModelOptions `ngModelOptions`} for more. 34485 * 34486 * The following example shows how to use `ngModel` with a getter/setter: 34487 * 34488 * @example 34489 * <example name="ngModel-getter-setter" module="getterSetterExample"> 34490 <file name="index.html">
34491 <div ng-controller="ExampleController"> 34492 <form name="userForm"> 34493 <label>Name: 34494 <input type="text" name="userName" 34495 ng-model="user.name" 34496 ng-model-options="{ getterSetter: true }" /> 34497 </label> 34498 </form> 34499 <pre>user.name = <span ng-bind="user.name()"></span></pre> 34500 </div> 34501 </file> 34502 <file name="app.js"> 34503 angular.module('getterSetterExample', []) 34504 .controller('ExampleController', ['$scope', function($scope) { 34505 var _name = 'Brian'; 34506 $scope.user = { 34507 name: function(newName) { 34508 // Note that newName can be undefined for two reasons: 34509 // 1. Because it is called as a getter and thus called with no arguments 34510 // 2. Because the property should actually be set to undefined. This happens e.g. if the 34511 // input is invalid 34512 return arguments.length ? (_name = newName) : _name; 34513 } 34514 }; 34515 }]); 34516 </file> 34517 * </example> 34518 */ 34519var ngModelDirective = ['$rootScope', function($rootScope) { 34520 return { 34521 restrict: 'A', 34522 require: ['ngModel', '^?form', '^?ngModelOptions'], 34523 controller: NgModelController, 34524 // Prelink needs to run before any input directive 34525 // so that we can set the NgModelOptions in NgModelController 34526 // before anyone else uses it. 34527 priority: 1, 34528 compile: function ngModelCompile(element) { 34529 // Setup initial state of the control 34530 element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS); 34531 34532 return { 34533 pre: function ngModelPreLink(scope, element, attr, ctrls) { 34534 var modelCtrl = ctrls[0], 34535 formCtrl = ctrls[1] || modelCtrl.$$parentForm; 34536 34537 modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options); 34538 34539 // notify others, especially parent forms 34540 formCtrl.$addControl(modelCtrl); 34541 34542 attr.$observe('name', function(newValue) { 34543 if (modelCtrl.$name !== newValue) { 34544 modelCtrl.$$parentForm.$$renameControl(modelCtrl, newValue); 34545 } 34546 }); 34547 34548 scope.$on('$destroy', function() { 34549 modelCtrl.$$parentForm.$removeControl(modelCtrl); 34550 }); 34551 }, 34552 post: function ngModelPostLink(scope, element, attr, ctrls) { 34553 var modelCtrl = ctrls[0]; 34554 if (modelCtrl.$options && modelCtrl.$options.updateOn) { 34555 element.on(modelCtrl.$options.updateOn, function(ev) { 34556 modelCtrl.$$debounceViewValueCommit(ev && ev.type); 34557 }); 34558 } 34559 34560 element.on('blur', function() { 34561 if (modelCtrl.$touched) return; 34562 34563 if ($rootScope.$$phase) { 34564 scope.$evalAsync(modelCtrl.$setTouched); 34565 } else { 34566 scope.$apply(modelCtrl.$setTouched); 34567 } 34568 }); 34569 } 34570 }; 34571 } 34572 }; 34573}]; 34574 34575 34576 34577var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/; 34578 34579/** 34580 * @ngdoc directive 34581 * @name ngModelOptions 34582 * 34583 * @description 34584 * Allows tuning how model updates are done. Using `ngModelOptions` you can specify a custom list of 34585 * events that will trigger a model update and/or a debouncing delay so that the actual update only 34586 * takes place when a timer expires; this timer will be reset after another change takes place. 34587 * 34588 * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might 34589 * be different from the value in the actual model. This means that if you update the model you 34590 * should also invoke {@link ngModel.NgModelController `$rollbackViewValue`} on the relevant input field in 34591 * order to make sure it is synchronized with the model and that any debounced action is canceled. 34592 * 34593 * The easiest way to reference the control's {@link ngModel.NgModelController `$rollbackViewValue`} 34594 * method is by making sure the input is placed inside a form that has a `name` attribute. This is 34595 * important because `form` controllers are published to the related scope under the name in their 34596 * `name` attribute. 34597 * 34598 * Any pending changes will take place immediately when an enclosing form is submitted via the 34599 * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` 34600 * to have access to the updated model. 34601 * 34602 * `ngModelOptions` has an effect on the element it's declared on and its desce
34602ndants. 34603 * 34604 * @param {Object} ngModelOptions options to apply to the current model. Valid keys are: 34605 * - `updateOn`: string specifying which event should the input be bound to. You can set several 34606 * events using an space delimited list. There is a special event called `default` that 34607 * matches the default events belonging of the control. 34608 * - `debounce`: integer value which contains the debounce model update value in milliseconds. A 34609 * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a 34610 * custom value for each event. For example: 34611 * `ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 500, 'blur': 0 } }"` 34612 * - `allowInvalid`: boolean value which indicates that the model can be set with values that did 34613 * not validate correctly instead of the default behavior of setting the model to undefined. 34614 * - `getterSetter`: boolean value which determines whether or not to treat functions bound to 34615 `ngModel` as getters/setters. 34616 * - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for 34617 * `<input type="date">`, `<input type="time">`, ... . It understands UTC/GMT and the 34618 * continental US time zone abbreviations, but for general use, use a time zone offset, for 34619 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian) 34620 * If not specified, the timezone of the browser will be used. 34621 * 34622 * @example 34623 34624 The following example shows how to override immediate updates. Changes on the inputs within the 34625 form will update the model only when the control loses focus (blur event). If `escape` key is 34626 pressed while the input field is focused, the value is reset to the value in the current model. 34627 34628 <example name="ngModelOptions-directive-blur" module="optionsExample"> 34629 <file name="index.html">
34630 <div ng-controller="ExampleController"> 34631 <form name="userForm"> 34632 <label>Name: 34633 <input type="text" name="userName" 34634 ng-model="user.name" 34635 ng-model-options="{ updateOn: 'blur' }" 34636 ng-keyup="cancel($event)" /> 34637 </label><br /> 34638 <label>Other data: 34639 <input type="text" ng-model="user.data" /> 34640 </label><br /> 34641 </form> 34642 <pre>user.name = <span ng-bind="user.name"></span></pre> 34643 <pre>user.data = <span ng-bind="user.data"></span></pre> 34644 </div> 34645 </file> 34646 <file name="app.js"> 34647 angular.module('optionsExample', []) 34648 .controller('ExampleController', ['$scope', function($scope) { 34649 $scope.user = { name: 'John', data: '' }; 34650 34651 $scope.cancel = function(e) { 34652 if (e.keyCode === 27) { 34653 $scope.userForm.userName.$rollbackViewValue(); 34654 } 34655 }; 34656 }]); 34657 </file> 34658 <file name="protractor.js" type="protractor"> 34659 var model = element(by.binding('user.name')); 34660 var input = element(by.model('user.name')); 34661 var other = element(by.model('user.data')); 34662 34663 it('should allow custom events', function() { 34664 input.sendKeys(' Doe'); 34665 input.click(); 34666 expect(model.getText()).toEqual('John'); 34667 other.click(); 34668 expect(model.getText()).toEqual('John Doe'); 34669 }); 34670 34671 it('should $rollbackViewValue when model changes', function() { 34672 input.sendKeys(' Doe'); 34673 expect(input.getAttribute('value')).toEqual('John Doe'); 34674 input.sendKeys(protractor.Key.ESCAPE); 34675 expect(input.getAttribute('value')).toEqual('John'); 34676 other.click(); 34677 expect(model.getText()).toEqual('John'); 34678 }); 34679 </file> 34680 </example> 34681 34682 This one shows how to debounce model changes. Model will be updated only 1 sec after last change. 34683 If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty. 34684 34685 <example name="ngModelOptions-directive-debounce" module="optionsExample"> 34686 <file name="index.html">
34687 <div ng-controller="ExampleController"> 34688 <form name="userForm"> 34689 <label>Name: 34690 <input type="text" name="userName" 34691 ng-model="user.name" 34692 ng-model-options="{ debounce: 1000 }" /> 34693 </label> 34694 <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button> 34695 <br /> 34696 </form> 34697 <pre>user.name = <span ng-bind="user.name"></span></pre> 34698 </div> 34699 </file> 34700 <file name="app.js"> 34701 angular.module('optionsExample', []) 34702 .controller('ExampleController', ['$scope', function($scope) { 34703 $scope.user = { name: 'Igor' }; 34704 }]); 34705 </file> 34706 </example> 34707 34708 This one shows how to bind to getter/setters: 34709 34710 <example name="ngModelOptions-directive-getter-setter" module="getterSetterExample"> 34711 <file name="index.html"> 34712 <div ng-controller="ExampleController"> 34713 <form name="userForm"> 34714 <label>Name: 34715 <input type="text" name="userName" 34716 ng-model="user.name" 34717 ng-model-options="{ getterSetter: true }" /> 34718 </label> 34719 </form> 34720 <pre>user.name = <span ng-bind="user.name()"></span></pre> 34721 </div> 34722 </file> 34723 <file name="app.js"> 34724 angular.module('getterSetterExample', []) 34725 .controller('ExampleController', ['$scope', function($scope) { 34726 var _name = 'Brian'; 34727 $scope.user = { 34728 name: function(newName) { 34729 // Note that newName can be undefined for two reasons: 34730 // 1. Because it is called as a getter and thus called with no arguments 34731 // 2. Because the property should actually be set to undefined. This happens e.g. if the 34732 // input is invalid 34733 return arguments.length ? (_name = newName) : _name; 34734 } 34735 }; 34736 }]); 34737 </file> 34738 </example> 34739 */ 34740var ngModelOptionsDirective = function() { 34741 return { 34742 restrict: 'A', 34743 controller: ['$scope', '$attrs', function NgModelOptionsController($scope, $attrs) { 34744 var that = this; 34745 this.$options = copy($scope.$eval($attrs.ngModelOptions)); 34746 // Allow adding/overriding bound events 34747 if (isDefined(this.$options.updateOn)) { 34748 this.$options.updateOnDefault = false;
34749 // extract "default" pseudo-event from list of events that can trigger a model update 34750 this.$options.updateOn = trim(this.$options.updateOn.replace(DEFAULT_REGEXP, function() { 34751 that.$options.updateOnDefault = true; 34752 return ' '; 34753 })); 34754 } else { 34755 this.$options.updateOnDefault = true; 34756 } 34757 }] 34758 }; 34759}; 34760 34761 34762 34763// helper methods 34764function addSetValidityMethod(context) { 34765 var ctrl = context.ctrl, 34766 $element = context.$element, 34767 classCache = {}, 34768 set = context.set, 34769 unset = context.unset, 34770 $animate = context.$animate; 34771 34772 classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS)); 34773 34774 ctrl.$setValidity = setValidity; 34775 34776 function setValidity(validationErrorKey, state, controller) { 34777 if (isUndefined(state)) { 34778 createAndSet('$pending', validationErrorKey, controller); 34779 } else { 34780 unsetAndCleanup('$pending', validationErrorKey, controller); 34781 } 34782 if (!isBoolean(state)) { 34783 unset(ctrl.$error, validationErrorKey, controller); 34784 unset(ctrl.$$success, validationErrorKey, controller); 34785 } else { 34786 if (state) { 34787 unset(ctrl.$error, validationErrorKey, controller); 34788 set(ctrl.$$success, validationErrorKey, controller); 34789 } else { 34790 set(ctrl.$error, validationErrorKey, controller); 34791 unset(ctrl.$$success, validationErrorKey, controller); 34792 } 34793 } 34794 if (ctrl.$pending) { 34795 cachedToggleClass(PENDING_CLASS, true); 34796 ctrl.$valid = ctrl.$invalid = undefined; 34797 toggleValidationCss('', null); 34798 } else { 34799 cachedToggleClass(PENDING_CLASS, false); 34800 ctrl.$valid = isObjectEmpty(ctrl.$error); 34801 ctrl.$invalid = !ctrl.$valid; 34802 toggleValidationCss('', ctrl.$valid); 34803 } 34804 34805 // re-read the state as the set/unset methods could have 34806 // combined state in ctrl.$error[validationError] (used for forms), 34807 // where setting/unsetting only increments/decrements the value, 34808 // and does not replace it. 34809 var combinedState; 34810 if (ctrl.$pending && ctrl.$pending[validationErrorKey]) { 34811 combinedState = undefined; 34812 } else if (ctrl.$error[validationErrorKey]) { 34813 combinedState = false; 34814 } else if (ctrl.$$success[validationErrorKey]) { 34815 combinedState = true; 34816 } else { 34817 combinedState = null; 34818 } 34819
34820 toggleValidationCss(validationErrorKey, combinedState); 34821 ctrl.$$parentForm.$setValidity(validationErrorKey, combinedState, ctrl); 34822 } 34823 34824 function createAndSet(name, value, controller) { 34825 if (!ctrl[name]) { 34826 ctrl[name] = {}; 34827 } 34828 set(ctrl[name], value, controller); 34829 } 34830 34831 function unsetAndCleanup(name, value, controller) { 34832 if (ctrl[name]) { 34833 unset(ctrl[name], value, controller); 34834 } 34835 if (isObjectEmpty(ctrl[name])) { 34836 ctrl[name] = undefined; 34837 } 34838 } 34839 34840 function cachedToggleClass(className, switchValue) { 34841 if (switchValue && !classCache[className]) { 34842 $animate.addClass($element, className); 34843 classCache[className] = true; 34844 } else if (!switchValue && classCache[className]) { 34845 $animate.removeClass($element, className); 34846 classCache[className] = false; 34847 } 34848 } 34849 34850 function toggleValidationCss(validationErrorKey, isValid) { 34851 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; 34852 34853 cachedToggleClass(VALID_CLASS + validationErrorKey, isValid === true); 34854 cachedToggleClass(INVALID_CLASS + validationErrorKey, isValid === false); 34855 } 34856} 34857 34858function isObjectEmpty(obj) { 34859 if (obj) { 34860 for (var prop in obj) { 34861 if (obj.hasOwnProperty(prop)) { 34862 return false; 34863 } 34864 } 34865 } 34866 return true; 34867} 34868 34869/** 34870 * @ngdoc directive 34871 * @name ngNonBindable 34872 * @restrict AC 34873 * @priority 1000 34874 * 34875 * @description 34876 * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current 34877 * DOM element. This is useful if the element contains what appears to be Angular directives and 34878 * bindings but which should be ignored by Angular. This could be the case if you have a site that 34879 * displays snippets of code, for instance. 34880 * 34881 * @element ANY 34882 * 34883 * @example 34884 * In this example there are two locations where a simple interpolation binding (`{{}}`) is present, 34885 * but the one wrapped in `ngNonBindable` is left alone. 34886 * 34887 * @example 34888 <example name="ng-non-bindable"> 34889 <file name="index.html"> 34890 <div>Normal: {{1 + 2}}</div> 34891 <div ng-non-bindable>Ignored: {{1 + 2}}</div> 34892 </file> 34893 <file name="protractor.js" type="protractor"> 34894 it('should check ng-non-bindable', function() { 34895 expect(element(by.binding('1 + 2')).getText()).toContain('3'); 34896 expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/); 34897 }); 34898 </file> 34899 </example> 34900 */ 34901var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); 34902 34903/* exported ngOptionsDirective */ 34904 34905/* global jqLiteRemove */ 34906 34907var ngOptionsMinErr = minErr('ngOptions'); 34908 34909/** 34910 * @ngdoc directive 34911 * @name ngOptions 34912 * @restrict A 34913 * 34914 * @description 34915 * 34916 * The `ngOptions` attribute can be used to dynamically generate a list of `<option>` 34917 * elements for the `<select>` element using the array or object obtained by evaluating the 34918 * `ngOptions` comprehension expression. 34919 * 34920 * In many cases, {@link ng.directive:ngRepeat ngRepeat} can be used on `<option>` elements 34921 * instead of `ngOptions` to achieve a similar result. 34922 * However, `ngOptions` provides some benefits such as reducing memory and 34923 * increasing speed by not creating a new scope for each repeated instance, as well as providing 34924 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the 34925 * comprehension expression. `ngOptions` should be used when the `<select>` model needs to be bound 34926 * to a non-string value. This is because an option element can only be bound to string values at 34927 * present. 34928 * 34929 * When an item in the `<select>` menu is selected, the array element or object property 34930 * represented by the selected option will be bound to the model identified by the `ngModel` 34931 * directive. 34932 * 34933 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can 34934 * be nested into the `<select>` element. This element will then represent the `null` or "not selected" 34935 * option. See example below for demonstration. 34936 * 34937 * ## Complex Models (objects or collections) 34938 * 34939 * By default, `ngModel` watches the model by reference, not value. This is important to know when 34940 * binding the select to a model that is an object or a collection. 34941 * 34942 * One issue occurs if you want to preselect an option. For example, if you set 34943 * the model to an object that is equal to an object in your collection, `ngOptions` won't be able to set the selection, 34944 * because the objects are not identical. So by default, you should always reference the item in y
34944our collection 34945 * for preselections, e.g.: `$scope.selected = $scope.collection[3]`. 34946 * 34947 * Another solution is to use a `track by` clause, because then `ngOptions` will track the identity 34948 * of the item not by reference, but by the result of the `track by` expression. For example, if your 34949 * collection items have an id property, you would `track by item.id`. 34950 * 34951 * A different issue with objects or collections is that ngModel won't detect if an object property or 34952 * a collection item changes. For that reason, `ngOptions` additionally watches the model using 34953 * `$watchCollection`, when the expression contains a `track by` clause or the the select has the `multiple` attribute. 34954 * This allows ngOptions to trigger a re-rendering of the options even if the actual object/collection 34955 * has not changed identity, but only a property on the object or an item in the collection changes. 34956 * 34957 * Note that `$watchCollection` does a shallow comparison of the properties of the object (or the items in the collection 34958 * if the model is an array). This means that changing a property deeper than the first level inside the 34959 * object/collection will not trigger a re-rendering. 34960 * 34961 * ## `select` **`as`** 34962 * 34963 * Using `select` **`as`** will bind the result of the `select` expression to the model, but 34964 * the value of the `<select>` and `<option>` html elements will be either the index (for array data sources) 34965 * or property name (for object data sources) of the value within the collection. If a **`track by`** expression 34966 * is used, the result of that expression will be set as the value of the `option` and `select` elements. 34967 * 34968 * 34969 * ### `select` **`as`** and **`track by`** 34970 * 34971 * <div class="alert alert-warning"> 34972 * Be careful when using `select` **`as`** and **`track by`** in the same expression. 34973 * </div> 34974 * 34975 * Given this array of items on the $scope: 34976 * 34977 * ```js 34978 * $scope.items = [{ 34979 * id: 1, 34980 * label: 'aLabel', 34981 * subItem: { name: 'aSubItem' } 34982 * }, { 34983 * id: 2, 34984 * label: 'bLabel', 34985 * subItem: { name: 'bSubItem' } 34986 * }]; 34987 * ``` 34988 * 34989 * This will work: 34990 * 34991 * ```html 34992 * <select ng-options="item as item.label for item in items track by item.id" ng-model="selected"></select> 34993 * ``` 34994 * ```js 34995 * $scope.selected = $scope.items[0]; 34996 * ``` 34997 * 34998 * but this will not work: 34999 * 35000 * ```html 35001 * <select ng-options="item.subItem as item.label for item in items track by item.id" ng-model="selected"></select> 35002 * ``` 35003 * ```js 35004 * $scope.selected = $scope.items[0].subItem; 35005 * ``` 35006 * 35007 * In both examples, the **`track by`** expression is applied successfully to each `item` in the 35008 * `items` array. Because the selected option has been set programmatically in the controller, the 35009 * **`track by`** expression is also applied to the `ngModel` value. In the first example, the 35010 * `ngModel` value is `items[0]` and the **`track by`** expression evaluates to `items[0].id` with 35011 * no issue. In the second example, the `ngModel` value is `items[0].subItem` and the **`track by`** 35012 * expression evaluates to `items[0].subItem.id` (which is undefined). As a result, the model value 35013 * is not matched against any `<option>` and the `<select>` appears as having no selected value. 35014 * 35015 * 35016 * @param {string} ngModel Assignable angular expression to data-bind to. 35017 * @param {string=} name Property name of the form under which the control is published. 35018 * @param {string=} required The control is considered valid only if value is entered. 35019 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 35020 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 35021 * `required` when you want to data-bind to the `required` attribute. 35022 * @param {comprehension_expression=} ngOptions in one of the following forms: 35023 * 35024 * * for array data sources: 35025 * * `label` **`for`** `value` **`in`** `array` 35026 * * `select` **`as`** `label` **`for`** `value` **`in`** `array` 35027 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` 35028 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` 35029 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` 35030 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` 35031 * * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr` 35032 * (for including a filter with `track by`) 35033 * * for object data sources:
35034 * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 35035 * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 35036 * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object` 35037 * * `label` **`disable when`** `disable` **`for (`**`key`**`,`** `value`**`) in`** `object` 35038 * * `select` **`as`** `label` **`group by`** `group` 35039 * **`for` `(`**`key`**`,`** `value`**`) in`** `object` 35040 * * `select` **`as`** `label` **`disable when`** `disable` 35041 * **`for` `(`**`key`**`,`** `value`**`) in`** `object` 35042 * 35043 * Where: 35044 * 35045 * * `array` / `object`: an expression which evaluates to an array / object to iterate over. 35046 * * `value`: local variable which will refer to each item in the `array` or each property value 35047 * of `object` during iteration. 35048 * * `key`: local variable which will refer to a property name in `object` during iteration. 35049 * * `label`: The result of this expression will be the label for `<option>` element. The 35050 * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`). 35051 * * `select`: The result of this expression will be bound to the model of the parent `<select>` 35052 * element. If not specified, `select` expression will default to `value`. 35053 * * `group`: The result of this expression will be used to group options using the `<optgroup>` 35054 * DOM element. 35055 * * `disable`: The result of this expression will be used to disable the rendered `<option>` 35056 * element. Return `true` to disable. 35057 * * `trackexpr`: Used when working with an array of objects. The result of this expression will be 35058 * used to identify the objects in the array. The `trackexpr` will most likely refer to the 35059 * `value` variable (e.g. `value.propertyName`). With this the selection is preserved 35060 * even when the options are recreated (e.g. reloaded from the server)
vendor: 10,367 bytes, lines 35060-35310
35060. 35061 * 35062 * @example 35063 <example module="selectExample" name="select"> 35064 <file name="index.html"> 35065 <script> 35066 angular.module('selectExample', []) 35067 .controller('ExampleController', ['$scope', function($scope) { 35068 $scope.colors = [ 35069 {name:'black', shade:'dark'}, 35070 {name:'white', shade:'light', notAnOption: true}, 35071 {name:'red', shade:'dark'}, 35072 {name:'blue', shade:'dark', notAnOption: true}, 35073 {name:'yellow', shade:'light', notAnOption: false} 35074 ]; 35075 $scope.myColor = $scope.colors[2]; // red 35076 }]); 35077 </script> 35078 <div ng-controller="ExampleController"> 35079 <ul> 35080 <li ng-repeat="color in colors"> 35081 <label>Name: <input ng-model="color.name"></label> 35082 <label><input type="checkbox" ng-model="color.notAnOption"> Disabled?</label> 35083 <button ng-click="colors.splice($index, 1)" aria-label="Remove">X</button> 35084 </li> 35085 <li> 35086 <button ng-click="colors.push({})">add</button> 35087 </li> 35088 </ul> 35089 <hr/> 35090 <label>Color (null not allowed): 35091 <select ng-model="myColor" ng-options="color.name for color in colors"></select> 35092 </label><br/> 35093 <label>Color (null allowed): 35094 <span class="nullable"> 35095 <select ng-model="myColor" ng-options="color.name for color in colors"> 35096 <option value="">-- choose color --</option> 35097 </select> 35098 </span></label><br/> 35099 35100 <label>Color grouped by shade: 35101 <select ng-model="myColor" ng-options="color.name group by color.shade for color in colors"> 35102 </select> 35103 </label><br/> 35104 35105 <label>Color grouped by shade, with some disabled: 35106 <select ng-model="myColor" 35107 ng-options="color.name group by color.shade disable when color.notAnOption for color in colors"> 35108 </select> 35109 </label><br/> 35110 35111 35112 35113 Select <button ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</button>. 35114 <br/> 35115 <hr/> 35116 Currently selected: {{ {selected_color:myColor} }} 35117 <div style="border:solid 1px black; height:20px" 35118 ng-style="{'background-color':myColor.name}"> 35119 </div> 35120 </div> 35121 </file> 35122 <file name="protractor.js" type="protractor"> 35123 it('should check ng-options', function() { 35124 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red'); 35125 element.all(by.model('myColor')).first().click(); 35126 element.all(by.css('select[ng-model="myColor"] option')).first().click(); 35127 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black'); 35128 element(by.css('.nullable select[ng-model="myColor"]')).click(); 35129 element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click(); 35130 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null'); 35131 }); 35132 </file> 35133 </example> 35134 */ 35135 35136/* eslint-disable max-len */ 35137// //00001111111111000000000002222222222000000000000000000000333333333300000000000000000000000004444444444400000000000005555555555555000000000666666666666600000007777777777777000000000000000888888888800000000000000000009999999999 35138var NG_OPTIONS_REGEXP = /^\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]+?))?$/; 35139 // 1: value expression (valueFn) 35140 // 2: label expression (displayFn) 35141 // 3: group by expression (groupByFn) 35142 // 4: disable when expression (disableWhenFn) 35143 // 5: array item variable name 35144 // 6: object item key variable name 35145 // 7: object item value variable name 35146 // 8: collection expression 35147 // 9: track by expression 35148/* eslint-enable */ 35149 35150 35151var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile, $document, $parse) { 35152 35153 function parseOptionsExpression(optionsExp, selectElement, scope) { 35154 35155 var match = optionsExp.match(NG_OPTIONS_REGEXP); 35156 if (!(match)) { 35157 throw ngOptionsMinErr('iexp', 35158 'Expected expression in form of ' + 35159 '\'_select_ (as _label_)? for (_key_,)?_value_ in _collection_\'' + 35160 ' but got \'{0}\'. Element: {1}', 35161 optionsExp, startingTag(selectElement)); 35162 } 35163 35164 // Extract the parts from the ngOptions expression 35165 35166 // The variable name for the value of the item in the collection 35167 var valueName = match[5] || match[7]; 35168 // The variable name for the key of the item in the collection 35169 var keyName = match[6]; 35170 35171 // An expression that generates the viewValue for an option if there is a label expression 35172 var selectAs = / as /.test(match[0]) && match[1]; 35173 // An expression that is used to track the id of each object in the options collection 35174 var trackBy = match[9]; 35175 // An expression that generates the viewValue for an option if there is no label expression 35176 var valueFn = $parse(match[2] ? match[1] : valueName); 35177 var selectAsFn = selectAs && $parse(selectAs); 35178 var viewValueFn = selectAsFn || valueFn; 35179 var trackByFn = trackBy && $parse(trackBy); 35180 35181 // Get the value by which we are going to track the option 35182 // if we have a trackFn then use that (passing scope and locals) 35183 // otherwise just hash the given viewValue 35184 var getTrackByValueFn = trackBy ? 35185 function(value, locals) { return trackByFn(scope, locals); } : 35186 function getHashOfValue(value) { return hashKey(value); }; 35187 var getTrackByValue = function(value, key) { 35188 return getTrackByValueFn(value, getLocals(value, key)); 35189 }; 35190 35191 var displayFn = $parse(match[2] || match[1]); 35192 var groupByFn = $parse(match[3] || ''); 35193 var disableWhenFn = $parse(match[4] || ''); 35194 var valuesFn = $parse(match[8]); 35195 35196 var locals = {}; 35197 var getLocals = keyName ? function(value, key) { 35198 locals[keyName] = key; 35199 locals[valueName] = value; 35200 return locals; 35201 } : function(value) { 35202 locals[valueName] = value; 35203 return locals; 35204 }; 35205 35206 35207 function Option(selectValue, viewValue, label, group, disabled) { 35208 this.selectValue = selectValue; 35209 this.viewValue = viewValue; 35210 this.label = label; 35211 this.group = group; 35212 this.disabled = disabled; 35213 } 35214 35215 function getOptionValuesKeys(optionValues) { 35216 var optionValuesKeys; 35217 35218 if (!keyName && isArrayLike(optionValues)) { 35219 optionValuesKeys = optionValues; 35220 } else { 35221 // if object, extract keys, in enumeration order, unsorted 35222 optionValuesKeys = []; 35223 for (var itemKey in optionValues) { 35224 if (optionValues.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') { 35225 optionValuesKeys.push(itemKey); 35226 } 35227 } 35228 } 35229 return optionValuesKeys; 35230 } 35231 35232 return { 35233 trackBy: trackBy, 35234 getTrackByValue: getTrackByValue, 35235 getWatchables: $parse(valuesFn, function(optionValues) { 35236 // Create a collection of things that we would like to watch (watchedArray) 35237 // so that they can all be watched using a single $watchCollection 35238 // that only runs the handler once if anything changes 35239 var watchedArray = []; 35240 optionValues = optionValues || []; 35241 35242 var optionValuesKeys = getOptionValuesKeys(optionValues); 35243 var optionValuesLength = optionValuesKeys.length; 35244 for (var index = 0; index < optionValuesLength; index++) { 35245 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index]; 35246 var value = optionValues[key]; 35247 35248 var locals = getLocals(value, key); 35249 var selectValue = getTrackByValueFn(value, locals); 35250 watchedArray.push(selectValue); 35251 35252 // Only need to watch the displayFn if there is a specific label expression 35253 if (match[2] || match[1]) { 35254 var label = displayFn(scope, locals); 35255 watchedArray.push(label); 35256 } 35257 35258 // Only need to watch the disableWhenFn if there is a specific disable expression 35259 if (match[4]) { 35260 var disableWhen = disableWhenFn(scope, locals); 35261 watchedArray.push(disableWhen); 35262 } 35263 } 35264 return watchedArray; 35265 }), 35266 35267 getOptions: function() { 35268 35269 var optionItems = []; 35270 var selectValueMap = {}; 35271 35272 // The option values were already computed in the `getWatchables` fn, 35273 // which must have been called to trigger `getOptions` 35274 var optionValues = valuesFn(scope) || []; 35275 var optionValuesKeys = getOptionValuesKeys(optionValues); 35276 var optionValuesLength = optionValuesKeys.length; 35277 35278 for (var index = 0; index < optionValuesLength; index++) { 35279 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index]; 35280 var value = optionValues[key]; 35281 var locals = getLocals(value, key); 35282 var viewValue = viewValueFn(scope, locals); 35283 var selectValue = getTrackByValueFn(viewValue, locals); 35284 var label = displayFn(scope, locals); 35285 var group = groupByFn(scope, locals); 35286 var disabled = disableWhenFn(scope, locals); 35287 var optionItem = new Option(selectValue, viewValue, label, group, disabled); 35288 35289 optionItems.push(optionItem); 35290 selectValueMap[selectValue] = optionItem; 35291 } 35292 35293 return { 35294 items: optionItems, 35295 selectValueMap: selectValueMap, 35296 getOptionFromViewValue: function(value) { 35297 return selectValueMap[getTrackByValue(value)]; 35298 }, 35299 getViewValueFromOption: function(option) { 35300 // If the viewValue could be an object that may be mutated by the application, 35301 // we need to make a copy and not return the reference to the value on the option. 35302 return trackBy ? copy(option.viewValue) : option.viewValue; 35303 } 35304 }; 35305 } 35306 }; 35307 } 35308 35309 35310 //
35310we can't just jqLite('<option>') since jqLite is not smart enough 35311 // to create it in <select> and IE barfs otherwise. 35312 var optionTemplate = window.document.createElement('option'), 35313 optGroupTemplate = window.document.createElement('optgroup'); 35314 35315 function ngOptionsPostLink(scope, selectElement, attr, ctrls) { 35316 35317 var selectCtrl = ctrls[0]; 35318 var ngModelCtrl = ctrls[1]; 35319 var multiple = attr.multiple; 35320 35321 // The emptyOption allows the application developer to provide their own custom "empty" 35322 // option when the viewValue does not match any of the option values. 35323 var emptyOption; 35324 for (var i = 0, children = selectElement.children(), ii = children.length; i < ii; i++) { 35325 if (children[i].value === '') { 35326 emptyOption = children.eq(i); 35327 break; 35328 } 35329 } 35330 35331 var providedEmptyOption = !!emptyOption; 35332 var emptyOptionRendered = false; 35333 35334 var unknownOption = jqLite(optionTemplate.cloneNode(false)); 35335 unknownOption.val('?'); 35336 35337 var options; 35338 var ngOptions = parseOptionsExpression(attr.ngOptions, selectElement, scope); 35339 // This stores the newly created options before they are appended to the select. 35340 // Since the contents are removed from the fragment when it is appended, 35341 // we only need to create it once. 35342 var listFragment = $document[0].createDocumentFragment(); 35343 35344 var renderEmptyOption = function() { 35345 if (!providedEmptyOption) { 35346 selectElement.prepend(emptyOption); 35347 } 35348 selectElement.val(''); 35349 if (emptyOptionRendered) { 35350 emptyOption.prop('selected', true); // needed for IE 35351 emptyOption.attr('selected', true); 35352 } 35353 }; 35354 35355 var removeEmptyOption = function() { 35356 if (!providedEmptyOption) { 35357 emptyOption.remove(); 35358 } else if (emptyOptionRendered) { 35359 emptyOption.removeAttr('selected'); 35360 } 35361 }; 35362 35363 var renderUnknownOption = function() { 35364 selectElement.prepend(unknownOption); 35365 selectElement.val('?'); 35366 unknownOption.prop('selected', true); // needed for IE 35367 unknownOption.attr('selected', true); 35368 }; 35369 35370 var removeUnknownOption = function() { 35371 unknownOption.remove(); 35372 }; 35373 35374 // Update the controller methods for multiple selectable options 35375 if (!multiple) { 35376 35377 selectCtrl.writeValue = function writeNgOptionsValue(value) { 35378 var selectedOption = options.selectValueMap[selectElement.val()]; 35379 var option = options.getOptionFromViewValue(value); 35380 35381 // Make sure to remove the selected attribute from the previously selected option 35382 // Otherwise, screen readers might get confused 35383 if (selectedOption) selectedOption.element.removeAttribute('selected'); 35384 35385 if (option) { 35386 // Don't update the option when it is already selected. 35387 // For example, the browser will select the first option by default. In that case, 35388 // most properties are set automatically - except the `selected` attribute, which we 35389 // set always 35390 35391 if (selectElement[0].value !== option.selectValue) { 35392 removeUnknownOption(); 35393 removeEmptyOption(); 35394 35395 selectElement[0].value = option.selectValue; 35396 option.element.selected = true; 35397 } 35398 35399 option.element.setAttribute('selected', 'selected'); 35400 } else { 35401 if (value === null || providedEmptyOption) { 35402 removeUnknownOption(); 35403 renderEmptyOption(); 35404 } else { 35405 removeEmptyOption(); 35406 renderUnknownOption(); 35407 } 35408 } 35409 }; 35410 35411 selectCtrl.readValue = function readNgOptionsValue() { 35412 35413 var selectedOption = options.selectValueMap[selectElement.val()]; 35414 35415 if (selectedOption && !selectedOption.disabled) { 35416 removeEmptyOption(); 35417 removeUnknownOption(); 35418 return options.getViewValueFromOption(selectedOption); 35419 } 35420 return null; 35421 }; 35422 35423 // If we are using `track by` then we must watch the tracked value on the model 35424 // since ngModel only watches for object identity change
35425 // FIXME: When a user selects an option, this watch will fire needlessly 35426 if (ngOptions.trackBy) { 35427 scope.$watch( 35428 function() { return ngOptions.getTrackByValue(ngModelCtrl.$viewValue); }, 35429 function() { ngModelCtrl.$render(); } 35430 ); 35431 } 35432 35433 } else { 35434 35435 ngModelCtrl.$isEmpty = function(value) { 35436 return !value || value.length === 0; 35437 }; 35438 35439 35440 selectCtrl.writeValue = function writeNgOptionsMultiple(value) { 35441 options.items.forEach(function(option) { 35442 option.element.selected = false; 35443 }); 35444 35445 if (value) { 35446 value.forEach(function(item) { 35447 var option = options.getOptionFromViewValue(item); 35448 if (option) option.element.selected = true; 35449 }); 35450 } 35451 }; 35452 35453 35454 selectCtrl.readValue = function readNgOptionsMultiple() { 35455 var selectedValues = selectElement.val() || [], 35456 selections = []; 35457
35458 forEach(selectedValues, function(value) { 35459 var option = options.selectValueMap[value]; 35460 if (option && !option.disabled) selections.push(options.getViewValueFromOption(option)); 35461 }); 35462 35463 return selections; 35464 }; 35465 35466 // If we are using `track by` then we must watch these tracked values on the model 35467 // since ngModel only watches for object identity change 35468 if (ngOptions.trackBy) { 35469 35470 scope.$watchCollection(function() { 35471 if (isArray(ngModelCtrl.$viewValue)) { 35472 return ngModelCtrl.$viewValue.map(function(value) { 35473 return ngOptions.getTrackByValue(value); 35474 }); 35475 } 35476 }, function() { 35477 ngModelCtrl.$render(); 35478 }); 35479 35480 } 35481 } 35482 35483 35484 if (providedEmptyOption) { 35485 35486 // we need to remove it before calling selectElement.empty() because otherwise IE will 35487 // remove the label from the element. wtf? 35488 emptyOption.remove(); 35489 35490 // compile the element since there might be bindings in it 35491 $compile(emptyOption)(scope); 35492 35493 if (emptyOption[0].nodeType === NODE_TYPE_COMMENT) { 35494 // This means the empty option has currently no actual DOM node, probably because 35495 // it has been modified by a transclusion directive. 35496 35497 emptyOptionRendered = false; 35498 35499 // Redefine the registerOption function, which will catch 35500 // options that are added by ngIf etc. (rendering of the node is async because of 35501 // lazy transclusion) 35502 selectCtrl.registerOption = function(optionScope, optionEl) { 35503 if (optionEl.val() === '') { 35504 emptyOptionRendered = true; 35505 emptyOption = optionEl; 35506 emptyOption.removeClass('ng-scope'); 35507 // This ensures the new empty option is selected if previously no option was selected 35508 ngModelCtrl.$render(); 35509 35510 optionEl.on('$destroy', function() { 35511 emptyOption = undefined; 35512 emptyOptionRendered = false; 35513 }); 35514 } 35515 }; 35516 35517 } else { 35518 emptyOption.removeClass('ng-scope'); 35519 emptyOptionRendered = true; 35520 } 35521 35522 } else { 35523 emptyOption = jqLite(optionTemplate.cloneNode(false)); 35524 } 35525 35526 selectElement.empty(); 35527 35528 // We need to do this here to ensure that the options object is defined 35529 // when we first hit it in writeNgOptionsValue 35530 updateOptions(); 35531 35532 // We will re-render the option elements if the option values or labels change 35533 scope.$watchCollection(ngOptions.getWatchables, updateOptions); 35534 35535 // ------------------------------------------------------------------ // 35536 35537 function addOptionElement(option, parent) { 35538 var optionElement = optionTemplate.cloneNode(false); 35539 parent.appendChild(optionElement); 35540 updateOptionElement(option, optionElement); 35541 } 35542 35543 35544 function updateOptionElement(option, element) { 35545 option.element = element; 35546 element.disabled = option.disabled; 35547 // NOTE: The label must be set before the value, otherwise IE10/11/EDGE create unresponsive 35548 // selects in certain circumstances when multiple selects are next to each other and display 35549 // the option list in listbox style, i.e. the select is [multiple], or specifies a [size]. 35550 // See https://github.com/angular/angular.js/issues/11314 for more info. 35551 // This is unfortunately untestable with unit / e2e tests 35552 if (option.label !== element.label) { 35553 element.label = option.label; 35554 element.textContent = option.label; 35555 } 35556 element.value = option.selectValue; 35557 } 35558 35559 function updateOptions() { 35560 var previousValue = options && selectCtrl.readValue(); 35561 35562 // We must remove all current options, but cannot simply set innerHTML = null 35563 // since the providedEmptyOption might have an ngIf on it that inserts comments which we 35564 // must preserve. 35565 // Instead, iterate over the current option elements and remove them or their optgroup 35566 // parents 35567 if (options) { 35568 35569 for (var i = options.items.length - 1; i >= 0; i--) { 35570 var option = options.items[i]; 35571 if (isDefined(option.group)) { 35572 jqLiteRemove(option.element.parentNode); 35573 } else { 35574 jqLiteRemove(option.element); 35575 } 35576 } 35577 } 35578 35579 options = ngOptions.getOptions(); 35580 35581 var groupElementMap = {}; 35582 35583 // Ensure that the empty option is always there if it was explicitly provided 35584 if (providedEmptyOption) {
vendor: 4,592 bytes, lines 35585-35703
35585 selectElement.prepend(emptyOption); 35586 } 35587 35588 options.items.forEach(function addOption(option) { 35589 var groupElement; 35590 35591 if (isDefined(option.group)) { 35592 35593 // This option is to live in a group 35594 // See if we have already created this group 35595 groupElement = groupElementMap[option.group]; 35596 35597 if (!groupElement) { 35598 35599 groupElement = optGroupTemplate.cloneNode(false); 35600 listFragment.appendChild(groupElement); 35601 35602 // Update the label on the group element 35603 // "null" is special cased because of Safari 35604 groupElement.label = option.group === null ? 'null' : option.group; 35605 35606 // Store it for use later 35607 groupElementMap[option.group] = groupElement; 35608 } 35609 35610 addOptionElement(option, groupElement); 35611 35612 } else { 35613 35614 // This option is not in a group 35615 addOptionElement(option, listFragment); 35616 } 35617 }); 35618 35619 selectElement[0].appendChild(listFragment); 35620 35621 ngModelCtrl.$render(); 35622 35623 // Check to see if the value has changed due to the update to the options 35624 if (!ngModelCtrl.$isEmpty(previousValue)) { 35625 var nextValue = selectCtrl.readValue(); 35626 var isNotPrimitive = ngOptions.trackBy || multiple; 35627 if (isNotPrimitive ? !equals(previousValue, nextValue) : previousValue !== nextValue) { 35628 ngModelCtrl.$setViewValue(nextValue); 35629 ngModelCtrl.$render(); 35630 } 35631 } 35632 35633 } 35634 } 35635 35636 return { 35637 restrict: 'A', 35638 terminal: true, 35639 require: ['select', 'ngModel'], 35640 link: { 35641 pre: function ngOptionsPreLink(scope, selectElement, attr, ctrls) { 35642 // Deactivate the SelectController.register method to prevent 35643 // option directives from accidentally registering themselves 35644 // (and unwanted $destroy handlers etc.) 35645 ctrls[0].registerOption = noop; 35646 }, 35647 post: ngOptionsPostLink 35648 } 35649 }; 35650}]; 35651 35652/** 35653 * @ngdoc directive 35654 * @name ngPluralize 35655 * @restrict EA 35656 * 35657 * @description 35658 * `ngPluralize` is a directive that displays messages according to en-US localization rules. 35659 * These rules are bundled with angular.js, but can be overridden 35660 * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive 35661 * by specifying the mappings between 35662 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 35663 * and the strings to be displayed. 35664 * 35665 * # Plural categories and explicit number rules 35666 * There are two 35667 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 35668 * in Angular's default en-US locale: "one" and "other". 35669 * 35670 * While a plural category may match many numbers (for example, in en-US locale, "other" can match 35671 * any number that is not 1), an explicit number rule can only match one number. For example, the 35672 * explicit number rule for "3" matches the number 3. There are examples of plural categories 35673 * and explicit number rules throughout the rest of this documentation. 35674 * 35675 * # Configuring ngPluralize 35676 * You configure ngPluralize by providing 2 attributes: `count` and `when`. 35677 * You can also provide an optional attribute, `offset`. 35678 * 35679 * The value of the `count` attribute can be either a string or an {@link guide/expression 35680 * Angular expression}; these are evaluated on the current scope for its bound value. 35681 * 35682 * The `when` attribute specifies the mappings between plural categories and the actual 35683 * string to be displayed. The value of the attribute should be a JSON object. 35684 * 35685 * The following example shows how to configure ngPluralize: 35686 * 35687 * ```html 35688 * <ng-pluralize count="personCount" 35689 when="{'0': 'Nobody is viewing.', 35690 * 'one': '1 person is viewing.', 35691 * 'other': '{} people are viewing.'}"> 35692 * </ng-pluralize> 35693 *``` 35694 * 35695 * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not 35696 * specify this rule, 0 would be matched to the "other" category and "0 people are viewing" 35697 * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for 35698 * other numbers, for example 12, so that instead of showing "12 people are viewing", you can 35699 * show "a dozen people are viewing". 35700 * 35701 * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted 35702 * into pluralized strings. In the previous example, Angular will replace `{}` with 35703 * <span ng-non-bindable>`{{personCount}}
35703`</span>. The closed braces `{}` is a placeholder 35704 * for <span ng-non-bindable>{{numberExpression}}</span>. 35705 * 35706 * If no rule is defined for a category, then an empty string is displayed and a warning is generated. 35707 * Note that some locales define more categories than `one` and `other`. For example, fr-fr defines `few` and `many`. 35708 * 35709 * # Configuring ngPluralize with offset 35710 * The `offset` attribute allows further customization of pluralized text, which can result in 35711 * a better user experience. For example, instead of the message "4 people are viewing this document", 35712 * you might display "John, Kate and 2 others are viewing this document". 35713 * The offset attribute allows you to offset a number by any desired value. 35714 * Let's take a look at an example: 35715 * 35716 * ```html 35717 * <ng-pluralize count="personCount" offset=2 35718 * when="{'0': 'Nobody is viewing.', 35719 * '1': '{{person1}} is viewing.', 35720 * '2': '{{person1}} and {{person2}} are viewing.', 35721 * 'one': '{{person1}}, {{person2}} and one other person are viewing.', 35722 * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 35723 * </ng-pluralize> 35724 * ``` 35725 * 35726 * Notice that we are still using two plural categories(one, other), but we added 35727 * three explicit number rules 0, 1 and 2. 35728 * When one person, perhaps John, views the document, "John is viewing" will be shown. 35729 * When three people view the document, no explicit number rule is found, so 35730 * an offset of 2 is taken off 3, and Angular
vendor: 6,786 bytes, lines 35730-35891
35730 uses 1 to decide the plural category. 35731 * In this case, plural category 'one' is matched and "John, Mary and one other person are viewing" 35732 * is shown. 35733 * 35734 * Note that when you specify offsets, you must provide explicit number rules for 35735 * numbers from 0 up to and including the offset. If you use an offset of 3, for example, 35736 * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for 35737 * plural categories "one" and "other". 35738 * 35739 * @param {string|expression} count The variable to be bound to. 35740 * @param {string} when The mapping between plural category to its corresponding strings. 35741 * @param {number=} offset Offset to deduct from the total number. 35742 * 35743 * @example 35744 <example module="pluralizeExample" name="ng-pluralize"> 35745 <file name="index.html"> 35746 <script> 35747 angular.module('pluralizeExample', []) 35748 .controller('ExampleController', ['$scope', function($scope) { 35749 $scope.person1 = 'Igor'; 35750 $scope.person2 = 'Misko'; 35751 $scope.personCount = 1; 35752 }]); 35753 </script> 35754 <div ng-controller="ExampleController"> 35755 <label>Person 1:<input type="text" ng-model="person1" value="Igor" /></label><br/> 35756 <label>Person 2:<input type="text" ng-model="person2" value="Misko" /></label><br/> 35757 <label>Number of People:<input type="text" ng-model="personCount" value="1" /></label><br/> 35758 35759 <!--- Example with simple pluralization rules for en locale ---> 35760 Without Offset: 35761 <ng-pluralize count="personCount" 35762 when="{'0': 'Nobody is viewing.', 35763 'one': '1 person is viewing.', 35764 'other': '{} people are viewing.'}"> 35765 </ng-pluralize><br> 35766 35767 <!--- Example with offset ---> 35768 With Offset(2): 35769 <ng-pluralize count="personCount" offset=2 35770 when="{'0': 'Nobody is viewing.', 35771 '1': '{{person1}} is viewing.', 35772 '2': '{{person1}} and {{person2}} are viewing.', 35773 'one': '{{person1}}, {{person2}} and one other person are viewing.', 35774 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 35775 </ng-pluralize> 35776 </div> 35777 </file> 35778 <file name="protractor.js" type="protractor"> 35779 it('should show correct pluralized string', function() { 35780 var withoutOffset = element.all(by.css('ng-pluralize')).get(0); 35781 var withOffset = element.all(by.css('ng-pluralize')).get(1); 35782 var countInput = element(by.model('personCount')); 35783 35784 expect(withoutOffset.getText()).toEqual('1 person is viewing.'); 35785 expect(withOffset.getText()).toEqual('Igor is viewing.'); 35786 35787 countInput.clear(); 35788 countInput.sendKeys('0'); 35789 35790 expect(withoutOffset.getText()).toEqual('Nobody is viewing.'); 35791 expect(withOffset.getText()).toEqual('Nobody is viewing.'); 35792 35793 countInput.clear(); 35794 countInput.sendKeys('2'); 35795 35796 expect(withoutOffset.getText()).toEqual('2 people are viewing.'); 35797 expect(withOffset.getText()).toEqual('Igor and Misko are viewing.'); 35798 35799 countInput.clear(); 35800 countInput.sendKeys('3'); 35801 35802 expect(withoutOffset.getText()).toEqual('3 people are viewing.'); 35803 expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.'); 35804 35805 countInput.clear(); 35806 countInput.sendKeys('4'); 35807 35808 expect(withoutOffset.getText()).toEqual('4 people are viewing.'); 35809 expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.'); 35810 }); 35811 it('should show data-bound names', function() { 35812 var withOffset = element.all(by.css('ng-pluralize')).get(1); 35813 var personCount = element(by.model('personCount')); 35814 var person1 = element(by.model('person1')); 35815 var person2 = element(by.model('person2')); 35816 personCount.clear(); 35817 personCount.sendKeys('4'); 35818 person1.clear(); 35819 person1.sendKeys('Di'); 35820 person2.clear(); 35821 person2.sendKeys('Vojta'); 35822 expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.'); 35823 }); 35824 </file> 35825 </example> 35826 */ 35827var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, $interpolate, $log) { 35828 var BRACE = /{}/g, 35829 IS_WHEN = /^when(Minus)?(.+)$/; 35830 35831 return { 35832 link: function(scope, element, attr) { 35833 var numberExp = attr.count, 35834 whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs 35835 offset = attr.offset || 0, 35836 whens = scope.$eval(whenExp) || {}, 35837 whensExpFns = {}, 35838 startSymbol = $interpolate.startSymbol(), 35839 endSymbol = $interpolate.endSymbol(), 35840 braceReplacement = startSymbol + numberExp + '-' + offset + endSymbol, 35841 watchRemover = angular.noop, 35842 lastCount; 35843 35844 forEach(attr, function(expression, attributeName) { 35845 var tmpMatch = IS_WHEN.exec(attributeName); 35846 if (tmpMatch) { 35847 var whenKey = (tmpMatch[1] ? '-' : '') + lowercase(tmpMatch[2]); 35848 whens[whenKey] = element.attr(attr.$attr[attributeName]); 35849 } 35850 }); 35851 forEach(whens, function(expression, key) { 35852 whensExpFns[key] = $interpolate(expression.replace(BRACE, braceReplacement)); 35853 35854 }); 35855 35856 scope.$watch(numberExp, function ngPluralizeWatchAction(newVal) { 35857 var count = parseFloat(newVal); 35858 var countIsNaN = isNumberNaN(count); 35859 35860 if (!countIsNaN && !(count in whens)) { 35861 // If an explicit number rule such as 1, 2, 3... is defined, just use it. 35862 // Otherwise, check it against pluralization rules in $locale service. 35863 count = $locale.pluralCat(count - offset); 35864 } 35865 35866 // If both `count` and `lastCount` are NaN, we don't need to re-register a watch. 35867 // In JS `NaN !== NaN`, so we have to explicitly check. 35868 if ((count !== lastCount) && !(countIsNaN && isNumberNaN(lastCount))) { 35869 watchRemover(); 35870 var whenExpFn = whensExpFns[count]; 35871 if (isUndefined(whenExpFn)) { 35872 if (newVal != null) { 35873 $log.debug('ngPluralize: no rule defined for \'' + count + '\' in ' + whenExp); 35874 } 35875 watchRemover = noop; 35876 updateElementText(); 35877 } else { 35878 watchRemover = scope.$watch(whenExpFn, updateElementText); 35879 } 35880 lastCount = count; 35881 } 35882 }); 35883 35884 function updateElementText(newText) { 35885 element.text(newText || ''); 35886 } 35887 } 35888 }; 35889}]; 35890 35891/*
35891 exported ngRepeatDirective */ 35892 35893/** 35894 * @ngdoc directive 35895 * @name ngRepeat 35896 * @multiElement 35897 * 35898 * @description 35899 * The `ngRepeat` directive instantiates a template once per item from a collection. Each template 35900 * instance gets its own scope, where the given loop variable is set to the current collection item, 35901 * and `$index` is set to the item index or key. 35902 * 35903 * Special properties are exposed on the local scope of each template instance, including: 35904 * 35905 * | Variable | Type | Details | 35906 * |-----------|-----------------|-----------------------------------------------------------------------------| 35907 * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) | 35908 * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. | 35909 * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. | 35910 * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. | 35911 * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). | 35912 * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). | 35913 * 35914 * <div class="alert alert-info"> 35915 * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}. 35916 * This may be useful when, for instance, nesting ngRepeats. 35917 * </div> 35918 * 35919 * 35920 * # Iterating over object properties 35921 * 35922 * It is possible to get `ngRepeat` to iterate over the properties of an object using the following 35923 * syntax: 35924 * 35925 * ```js 35926 * <div ng-repeat="(key, value) in myObj"> ... </div> 35927 * ``` 35928 * 35929 * However, there are a few limitations compared to array iteration: 35930 * 35931 * - The JavaScript specification does not define the order of keys 35932 * returned for an object, so Angular relies on the order returned by the browser 35933 * when running `for key in myObj`. Browsers generally follow the strategy of providing 35934 * keys in the order in which they were defined, although there are exceptions when keys are deleted 35935 * and reinstated. See the 35936 * [MDN page on `delete` for more info](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_notes). 35937 * 35938 * - `ngRepeat` will silently *ignore* object keys starting with `$`, because 35939 * it's a prefix used by Angular for public (`$`) and private (`$$`) properties. 35940 * 35941 * - The built-in filters {@link ng.orderBy orderBy} and {@link ng.filter filter} do not work with 35942 * objects, and will throw an error if used with one. 35943 * 35944 * If you are hitting any of these limitations, the recommended workaround is to convert your object into an array 35945 * that is sorted into the order that you prefer before providing it to `ngRepeat`. You could 35946 * do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter) 35947 * or implement a `$watch` on the object yourself. 35948 * 35949 * 35950 * # Tracking and Duplicates 35951 * 35952 * `ngRepeat` uses {@link $rootScope.Scope#$watchCollection $watchCollection} to detect changes in 35953 * the collection. When a change happens, `ngRepeat` then makes the corresponding changes to the DOM: 35954 * 35955 * * When an item is added, a new instance of the template is added to the DOM. 35956 * * When an item is removed, its template instance is removed from the DOM. 35957 * * When items are reordered, their respective templates are reordered in the DOM. 35958 * 35959 * To minimize creation of DOM elements, `ngRepeat` uses a function 35960 * to "keep track" of all items in the collection and their corresponding DOM elements. 35961 * For example, if an item is added to the collection, `ngRepeat` will know that all other items 35962 * already have DOM elements, and will not re-render them. 35963 * 35964 * The default tracking function (which tracks items by their identity) does not allow 35965 * duplicate items in arrays. This is because when there are duplicates, it is not possible 35966 * to maintain a one-to-one mapping between collection items and DOM elements. 35967 * 35968 * If you do need to repeat duplicate items, you can substitute the default tracking behavior 35969 * with your own using the `track by` expression. 35970 * 35971 * For example, you may track items by the index of each item in the collection, using the 35972 * special scope property `$index`: 35973 * ```html 35974 * <div ng-repeat="n in [42, 42, 43, 43] track by $index"> 35975 * {{n}} 35976 * </div> 35977 * ``` 35978 * 35979 * You may also use arbitrary expressions in `track by`, including references to custom functions 35980 * on the scope: 35981 * ```html 35982 * <div ng-repeat="n in [42, 42, 43, 43] track by myTrackingFunction(n)"> 35983 * {{n}} 35984 * </div> 35985 * ``` 35986 * 35987 * <div class="alert alert-success"> 35988 * If you are working with objects that have a unique identifier property, you should track 35989 * by this identifier instead of the object instance. Should you reload your data later, `ngRepeat` 35990 * will not have to rebuild the DOM elements for items it has already rendered, even if the 35991 * JavaScript objects in the collection have been substituted for new ones. For large collections, 35992 * this significantly improves rendering performance. If you don't have a unique identifier, 35993 * `track by $index` can also provide a performance boost. 35994 * </div> 35995 * 35996 * ```html 35997 * <div ng-repeat="model in collection track by model.id"> 35998 * {{model.name}} 35999 * </div> 36000 * ``` 36001 * 36002 * <br /> 36003 * <div class="alert alert-warning"> 36004 * Avoid using `track by $index` when the repeated template contains 36005 * {@link guide/expression#one-time-binding one-time bindings}. In such cases, the `nth` DOM 36006 * element will always be matched with the `nth` item of the array, so the bindings on that element 36007 * will not be updated even when the corresponding item changes, essentially causing the view to get 36008 * out-of-sync with the underlying data. 36009 * </div> 36010 * 36011 * When no `track by` expression is provided, it is equivalent to tracking by the built-in 36012 * `$id` function, which tracks items by their identity: 36013 * ```html 36014 * <div ng-repeat="obj in collection track by $id(obj)"> 36015 * {{obj.prop}} 36016 * </div> 36017 * ``` 36018 * 36019 * <br /> 36020 * <div class="alert alert-warning"> 36021 * **Note:** `track by` must always be the last expression: 36022 * </div> 36023 * ``` 36024 * <div ng-repeat="model in collection | orderBy: 'id' as filtered_result track by model.id"> 36025 * {{model.name}} 36026 * </div> 36027 * ``` 36028 * 36029 * 36030 * # Special repeat start and end points 36031 * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending 36032 * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively. 36033 * The **ng-repeat-start** directive works the same as **ng-repeat**, but will repeat all the HTML code (including the tag it's defined on) 36034 * up to and including the ending HTML tag where **ng-repeat-end** is placed. 36035 * 36036 * The example below makes use of this feature: 36037 * ```html 36038 * <header ng-repeat-start="item in items"> 36039 * Header {{ item }} 36040 * </header> 36041 * <div class="body"> 36042 * Body {{ item }} 36043 * </div> 36044 * <footer ng-repeat-end> 36045 * Footer {{ item }} 36046 * </footer> 36047 * ``` 36048 * 36049 * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to: 36050 * ```html 36051 * <header> 36052 * Header A 36053 * </header> 36054 * <div class="body"> 36055 * Body A 36056 * </div> 36057 * <footer> 36058 * Footer A 36059 * </footer> 36060 * <header> 36061 * Header B 36062 * </header> 36063 * <div class="body"> 36064 * Body B 36065 * </div> 36066 * <footer> 36067 * Footer B 36068 * </footer> 36069 * ``` 36070 * 36071 * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such 36072 * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**). 36073 * 36074 * @animations 36075 * | Animation | Occurs | 36076 * |----------------------------------|-------------------------------------| 36077 * | {@link ng.$animate#enter enter} | when a new item is added to the list or when an item is revealed after a filter | 36078 * | {@link ng.$animate#leave leave} | when an item is removed from the list or when an item is filtered out | 36079 * | {@link ng.$animate#move move } | when an adjacent item is filtered out causing a reorder or when the item contents are reordered | 36080 *
36081 * See the example below for defining CSS animations with ngRepeat. 36082 * 36083 * @element ANY 36084 * @scope 36085 * @priority 1000 36086 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These 36087 * formats are currently supported: 36088 * 36089 * * `variable in expression` â where variable is the user defined loop variable and `expression` 36090 * is a scope expression giving the collection to enumerate. 36091 * 36092 * For example: `album in artist.albums`. 36093 * 36094 * * `(key, value) in expression` â where `key` and `value` can be any user defined identifiers, 36095 * and `expression` is the scope expression giving the collection to enumerate. 36096 * 36097 * For example: `(name, age) in {'adam':10, 'amalie':12}`. 36098 * 36099 * * `variable in expression track by tracking_expression` â You can also provide an optional tracking expression 36100 * which can be used to associate the objects in the collection with the DOM elements. If no tracking expression 36101 * is specified, ng-repeat associates elements by identity. It is an error to have 36102 * more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are 36103 * mapped to the same DOM element, which is not possible.) 36104 * 36105 * Note that the tracking expression must come last, after any filters, and the alias expression. 36106 * 36107 * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements 36108 * will be associated by item identity in the array. 36109 * 36110 * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique 36111 * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements 36112 * with the corresponding item in the array by identity. Moving the same object in array would move the DOM 36113 * element in the same way in the DOM. 36114 * 36115 * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this 36116 * case the object identity does not matter. Two objects are considered equivalent as long as their `id` 36117 * property is same. 36118 * 36119 * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter 36120 * to items in conjunction with a tracking expression. 36121 * 36122 * * `variable in expression as alias_expression` â You can also provide an optional alias expression which will then store the 36123 * intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message 36124 * when a filter is active on the repeater, but the filtered result set is empty. 36125 * 36126 * For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after 36127 * the items have been processed through the filter. 36128 * 36129 * Please note that `as [variable name] is not an operator but rather a part of ngRepeat micro-syntax so it can be used only at the end 36130 * (and not as operator, inside an expression). 36131 * 36132 * For example: `item in items | filter : x | orderBy : order | limitTo : limit as results` . 36133 * 36134 * @example 36135 * This example uses `ngRepeat` to display a list of people. A filter is used to restrict the displayed 36136 * results by name or by age. New (entering) and removed (leaving) items are animated. 36137 <example module="ngRepeat" name="ngRepeat" deps="angular-animate.js" animations="true" name="ng-repeat"> 36138 <file name="index.html"> 36139 <div ng-controller="repeatController"> 36140 I have {{friends.length}} friends. They are: 36141 <input type="search" ng-model="q" placeholder="filter friends..." aria-label="filter friends" /> 36142 <ul class="example-animate-container"> 36143 <li class="animate-repeat" ng-repeat="friend in friends | filter:q as results"> 36144 [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. 36145 </li> 36146 <li class="animate-repeat" ng-if="results.length === 0"> 36147 <strong>No results found...</strong> 36148 </li> 36149 </ul> 36150 </div> 36151 </file> 36152 <file name="script.js"> 36153 angular.module('ngRepeat', ['ngAnimate']).controller('repeatController', function($scope) { 36154 $scope.friends = [ 36155 {name:'John', age:25, gender:'boy'}, 36156 {name:'Jessie', age:30, gender:'girl'}, 36157 {name:'Johanna', age:28, gender:'girl'}, 36158 {name:'Joy', age:15, gender:'girl'}, 36159 {name:'Mary', age:28, gender:'girl'}, 36160 {name:'Peter', age:95, gender:'boy'}, 36161 {name:'Sebastian', age:50, gender:'boy'}, 36162 {name:'Erika', age:27, gender:'girl'}, 36163 {name:'Patrick', age:40, gender:'boy'}, 36164 {name:'Samantha', age:60, gender:'girl'} 36165 ]; 36166 }); 36167 </file> 36168 <file name="animations.css"> 36169 .example-animate-container { 36170 background:white; 36171 border:1px solid black; 36172 list-style:none; 36173 margin:0; 36174 padding:0 10px; 36175 } 36176 36177 .animate-repeat { 36178 line-height:30px; 36179 list-style:none; 36180 box-sizing:border-box; 36181 } 36182 36183 .animate-repeat.ng-move, 36184 .animate-repeat.ng-enter, 36185 .animate-repeat.ng-leave { 36186 transition:all linear 0.5s; 36187 } 36188 36189 .animate-repeat.ng-leave.ng-leave-active, 36190 .animate-repeat.ng-move, 36191 .animate-repeat.ng-enter { 36192 opacity:0; 36193 max-height:0; 36194 } 36195 36196 .animate-repeat.ng-leave,
36197 .animate-repeat.ng-move.ng-move-active, 36198 .animate-repeat.ng-enter.ng-enter-active { 36199 opacity:1; 36200 max-height:30px; 36201 } 36202 </file> 36203 <file name="protractor.js" type="protractor"> 36204 var friends = element.all(by.repeater('friend in friends')); 36205 36206 it('should render initial data set', function() { 36207 expect(friends.count()).toBe(10); 36208 expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.'); 36209 expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.'); 36210 expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.'); 36211 expect(element(by.binding('friends.length')).getText()) 36212 .toMatch("I have 10 friends. They are:"); 36213 }); 36214 36215 it('should update repeater when filter predicate changes', function() { 36216 expect(friends.count()).toBe(10); 36217 36218 element(by.model('q')).sendKeys('ma'); 36219 36220 expect(friends.count()).toBe(2); 36221 expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.'); 36222 expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.'); 36223 }); 36224 </file> 36225 </example> 36226 */ 36227var ngRepeatDirective = ['$parse', '$animate', '$compile', function($parse, $animate, $compile) { 36228 var NG_REMOVED = '$$NG_REMOVED'; 36229 var ngRepeatMinErr = minErr('ngRepeat'); 36230 36231 var updateScope = function(scope, index, valueIdentifier, value, keyIdentifier, key, arrayLength) { 36232 // TODO(perf): generate setters to shave off ~40ms or 1-1.5% 36233 scope[valueIdentifier] = value; 36234 if (keyIdentifier) scope[keyIdentifier] = key; 36235 scope.$index = index; 36236 scope.$first = (index === 0); 36237 scope.$last = (index === (arrayLength - 1)); 36238 scope.$middle = !(scope.$first || scope.$last); 36239 // eslint-disable-next-line no-bitwise 36240 scope.$odd = !(scope.$even = (index & 1) === 0); 36241 }; 36242 36243 var getBlockStart = function(block) { 36244 return block.clone[0]; 36245 }; 36246 36247 var getBlockEnd = function(block) { 36248 return block.clone[block.clone.length - 1]; 36249 }; 36250 36251 36252 return { 36253 restrict: 'A', 36254 multiElement: true, 36255 transclude: 'element', 36256 priority: 1000, 36257 terminal: true, 36258 $$tlb: true, 36259 compile: function ngRepeatCompile($element, $attr) { 36260 var expression = $attr.ngRepeat; 36261 var ngRepeatEndComment = $compile.$$createComment('end ngRepeat', expression); 36262 36263 var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/); 36264 36265 if (!match) { 36266 throw ngRepeatMinErr('iexp', 'Expected expression in form of \'_item_ in _collection_[ track by _id_]\' but got \'{0}\'.', 36267 expression); 36268 } 36269 36270 var lhs = match[1]; 36271 var rhs = match[2]; 36272 var aliasAs = match[3]; 36273 var trackByExp = match[4]; 36274 36275 match = lhs.match(/^(?:(\s*[$\w]+)|\(\s*([$\w]+)\s*,\s*([$\w]+)\s*\))$/); 36276 36277 if (!match) { 36278 throw ngRepeatMinErr('iidexp', '\'_item_\' in \'_item_ in _collection_\' should be an identifier or \'(_key_, _value_)\' expression, but got \'{0}\'.', 36279 lhs); 36280 } 36281 var valueIdentifier = match[3] || match[1]; 36282 var keyIdentifier = match[2]; 36283 36284 if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) || 36285 /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) { 36286 throw ngRepeatMinErr('badident', 'alias \'{0}\' is invalid --- must be a valid JS identifier which is not a reserved name.', 36287 aliasAs); 36288 } 36289 36290 var trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn; 36291 var hashFnLocals = {$id: hashKey}; 36292 36293 if (trackByExp) { 36294 trackByExpGetter = $parse(trackByExp); 36295 } else { 36296 trackByIdArrayFn = function(key, value) { 36297 return hashKey(value); 36298 }; 36299 trackByIdObjFn = function(key) { 36300 return key; 36301 }; 36302 } 36303 36304 return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) { 36305 36306 if (trackByExpGetter) { 36307 trackByIdExpFn = function(key, value, index) {
vendor: 14,350 bytes, lines 36308-36650
36308 // assign key, value, and $index to the locals so that they can be used in hash functions 36309 if (keyIdentifier) hashFnLocals[keyIdentifier] = key; 36310 hashFnLocals[valueIdentifier] = value; 36311 hashFnLocals.$index = index; 36312 return trackByExpGetter($scope, hashFnLocals); 36313 }; 36314 } 36315 36316 // Store a list of elements from previous run. This is a hash where key is the item from the 36317 // iterator, and the value is objects with following properties. 36318 // - scope: bound scope 36319 // - element: previous element. 36320 // - index: position 36321 // 36322 // We are using no-proto object so that we don't need to guard against inherited props via 36323 // hasOwnProperty. 36324 var lastBlockMap = createMap(); 36325 36326 //watch props 36327 $scope.$watchCollection(rhs, function ngRepeatAction(collection) { 36328 var index, length, 36329 previousNode = $element[0], // node that cloned nodes should be inserted after 36330 // initialized to the comment node anchor 36331 nextNode, 36332 // Same as lastBlockMap but it has the current state. It will become the 36333 // lastBlockMap on the next iteration. 36334 nextBlockMap = createMap(), 36335 collectionLength, 36336 key, value, // key/value of iteration 36337 trackById, 36338 trackByIdFn, 36339 collectionKeys, 36340 block, // last object information {scope, element, id} 36341 nextBlockOrder, 36342 elementsToRemove; 36343 36344 if (aliasAs) { 36345 $scope[aliasAs] = collection; 36346 } 36347 36348 if (isArrayLike(collection)) { 36349 collectionKeys = collection; 36350 trackByIdFn = trackByIdExpFn || trackByIdArrayFn; 36351 } else { 36352 trackByIdFn = trackByIdExpFn || trackByIdObjFn; 36353 // if object, extract keys, in enumeration order, unsorted 36354 collectionKeys = []; 36355 for (var itemKey in collection) { 36356 if (hasOwnProperty.call(collection, itemKey) && itemKey.charAt(0) !== '$') { 36357 collectionKeys.push(itemKey); 36358 } 36359 } 36360 } 36361 36362 collectionLength = collectionKeys.length; 36363 nextBlockOrder = new Array(collectionLength); 36364 36365 // locate existing items 36366 for (index = 0; index < collectionLength; index++) { 36367 key = (collection === collectionKeys) ? index : collectionKeys[index]; 36368 value = collection[key]; 36369 trackById = trackByIdFn(key, value, index); 36370 if (lastBlockMap[trackById]) { 36371 // found previously seen block 36372 block = lastBlockMap[trackById]; 36373 delete lastBlockMap[trackById]; 36374 nextBlockMap[trackById] = block; 36375 nextBlockOrder[index] = block; 36376 } else if (nextBlockMap[trackById]) { 36377 // if collision detected. restore lastBlockMap and throw an error 36378 forEach(nextBlockOrder, function(block) { 36379 if (block && block.scope) lastBlockMap[block.id] = block; 36380 }); 36381 throw ngRepeatMinErr('dupes', 36382 'Duplicates in a repeater are not allowed. Use \'track by\' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}', 36383 expression, trackById, value); 36384 } else { 36385 // new never before seen block 36386 nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined}; 36387 nextBlockMap[trackById] = true; 36388 } 36389 } 36390 36391 // remove leftover items 36392 for (var blockKey in lastBlockMap) { 36393 block = lastBlockMap[blockKey]; 36394 elementsToRemove = getBlockNodes(block.clone); 36395 $animate.leave(elementsToRemove); 36396 if (elementsToRemove[0].parentNode) { 36397 // if the element was not removed yet because of pending animation, mark it as deleted 36398 // so that we can ignore it later 36399 for (index = 0, length = elementsToRemove.length; index < length; index++) { 36400 elementsToRemove[index][NG_REMOVED] = true; 36401 } 36402 } 36403 block.scope.$destroy(); 36404 } 36405 36406 // we are not using forEach for perf reasons (trying to avoid #call) 36407 for (index = 0; index < collectionLength; index++) { 36408 key = (collection === collectionKeys) ? index : collectionKeys[index]; 36409 value = collection[key]; 36410 block = nextBlockOrder[index]; 36411 36412 if (block.scope) { 36413 // if we have already seen this object, then we need to reuse the 36414 // associated scope/element 36415 36416 nextNode = previousNode; 36417 36418 // skip nodes that are already pending removal via leave animation 36419 do { 36420 nextNode = nextNode.nextSibling; 36421 } while (nextNode && nextNode[NG_REMOVED]); 36422 36423 if (getBlockStart(block) !== nextNode) { 36424 // existing item which got moved 36425 $animate.move(getBlockNodes(block.clone), null, previousNode); 36426 } 36427 previousNode = getBlockEnd(block); 36428 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); 36429 } else { 36430 // new item which we don't know about 36431 $transclude(function ngRepeatTransclude(clone, scope) { 36432 block.scope = scope; 36433 // http://jsperf.com/clone-vs-createcomment 36434 var endNode = ngRepeatEndComment.cloneNode(false); 36435 clone[clone.length++] = endNode; 36436 36437 $animate.enter(clone, null, previousNode); 36438 previousNode = endNode; 36439 // Note: We only need the first/last node of the cloned nodes. 36440 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 36441 // by a directive with templateUrl when its template arrives. 36442 block.clone = clone; 36443 nextBlockMap[block.id] = block; 36444 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); 36445 }); 36446 } 36447 } 36448 lastBlockMap = nextBlockMap; 36449 }); 36450 }; 36451 } 36452 }; 36453}]; 36454 36455var NG_HIDE_CLASS = 'ng-hide'; 36456var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate'; 36457/** 36458 * @ngdoc directive 36459 * @name ngShow 36460 * @multiElement 36461 * 36462 * @description 36463 * The `ngShow` directive shows or hides the given HTML element based on the expression provided to 36464 * the `ngShow` attribute. 36465 * 36466 * The element is shown or hidden by removing or adding the `.ng-hide` CSS class onto the element. 36467 * The `.ng-hide` CSS class is predefined in AngularJS and sets the display style to none (using an 36468 * `!important` flag). For CSP mode please add `angular-csp.css` to your HTML file (see 36469 * {@link ng.directive:ngCsp ngCsp}). 36470 * 36471 * ```html 36472 * <!-- when $scope.myValue is truthy (element is visible) --> 36473 * <div ng-show="myValue"></div> 36474 * 36475 * <!-- when $scope.myValue is falsy (element is hidden) --> 36476 * <div ng-show="myValue" class="ng-hide"></div> 36477 * ``` 36478 * 36479 * When the `ngShow` expression evaluates to a falsy value then the `.ng-hide` CSS class is added 36480 * to the class attribute on the element causing it to become hidden. When truthy, the `.ng-hide` 36481 * CSS class is removed from the element causing the element not to appear hidden. 36482 * 36483 * ## Why is `!important` used? 36484 * 36485 * You may be wondering why `!important` is used for the `.ng-hide` CSS class. This is because the 36486 * `.ng-hide` selector can be easily overridden by heavier selectors. For example, something as 36487 * simple as changing the display style on a HTML list item would make hidden elements appear 36488 * visible. This also becomes a bigger issue when dealing with CSS frameworks. 36489 * 36490 * By using `!important`, the show and hide behavior will work as expected despite any clash between 36491 * CSS selector specificity (when `!important` isn't used with any conflicting styles). If a 36492 * developer chooses to override the styling to change how to hide an element then it is just a 36493 * matter of using `!important` in their own CSS code. 36494 * 36495 * ### Overriding `.ng-hide` 36496 * 36497 * By default, the `.ng-hide` class will style the element with `display: none !important`. If you 36498 * wish to change the hide behavior with `ngShow`/`ngHide`, you can simply overwrite the styles for 36499 * the `.ng-hide` CSS class. Note that the selector that needs to be used is actually 36500 * `.ng-hide:not(.ng-hide-animate)` to cope with extra animation classes that can be added. 36501 * 36502 * ```css 36503 * .ng-hide:not(.ng-hide-animate) { 36504 * /* These are just alternative ways of hiding an element */ 36505 * display: block!important; 36506 * position: absolute; 36507 * top: -9999px; 36508 * left: -9999px; 36509 * } 36510 * ``` 36511 * 36512 * By default you don't need to override anything in CSS and the animations will work around the 36513 * display style. 36514 * 36515 * ## A note about animations with `ngShow` 36516 * 36517 * Animations in `ngShow`/`ngHide` work with the show and hide events that are triggered when the 36518 * directive expression is true and false. This system works like the animation system present with 36519 * `ngClass` except that you must also include the `!important` flag to override the display 36520 * property so that the elements are not actually hidden during the animation. 36521 * 36522 * ```css 36523 * /* A working example can be found at the bottom of this page. */ 36524 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 36525 * transition: all 0.5s linear; 36526 * } 36527 * 36528 * .my-element.ng-hide-add { ... } 36529 * .my-element.ng-hide-add.ng-hide-add-active { ... } 36530 * .my-element.ng-hide-remove { ... } 36531 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 36532 * ``` 36533 * 36534 * Keep in mind that, as of AngularJS version 1.3, there is no need to change the display property 36535 * to block during animation states - ngAnimate will automatically handle the style toggling for you. 36536 * 36537 * @animations 36538 * | Animation | Occurs | 36539 * |-----------------------------------------------------|---------------------------------------------------------------------------------------------------------------| 36540 * | {@link $animate#addClass addClass} `.ng-hide` | After the `ngShow` expression evaluates to a non truthy value and just before the contents are set to hidden. | 36541 * | {@link $animate#removeClass removeClass} `.ng-hide` | After the `ngShow` expression evaluates to a truthy value and just before contents are set to visible. | 36542 * 36543 * @element ANY 36544 * @param {expression} ngShow If the {@link guide/expression expression} is truthy/falsy then the 36545 * element is shown/hidden respectively. 36546 * 36547 * @example 36548 * A simple example, animating the element's opacity: 36549 * 36550 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-show-simple"> 36551 <file name="index.html"> 36552 Show: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br /> 36553 <div class="check-element animate-show-hide" ng-show="checked"> 36554 I show up when your checkbox is checked. 36555 </div> 36556 </file> 36557 <file name="animations.css"> 36558 .animate-show-hide.ng-hide { 36559 opacity: 0; 36560 } 36561 36562 .animate-show-hide.ng-hide-add, 36563 .animate-show-hide.ng-hide-remove { 36564 transition: all linear 0.5s; 36565 } 36566 36567 .check-element { 36568 border: 1px solid black; 36569 opacity: 1; 36570 padding: 10px; 36571 } 36572 </file> 36573 <file name="protractor.js" type="protractor"> 36574 it('should check ngShow', function() { 36575 var checkbox = element(by.model('checked')); 36576 var checkElem = element(by.css('.check-element')); 36577 36578 expect(checkElem.isDisplayed()).toBe(false); 36579 checkbox.click(); 36580 expect(checkElem.isDisplayed()).toBe(true); 36581 }); 36582 </file> 36583 </example> 36584 * 36585 * <hr /> 36586 * @example 36587 * A more complex example, featuring different show/hide animations: 36588 * 36589 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-show-complex"> 36590 <file name="index.html"> 36591 Show: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br /> 36592 <div class="check-element funky-show-hide" ng-show="checked"> 36593 I show up when your checkbox is checked. 36594 </div> 36595 </file> 36596 <file name="animations.css"> 36597 body { 36598 overflow: hidden; 36599 perspective: 1000px; 36600 } 36601 36602 .funky-show-hide.ng-hide-add { 36603 transform: rotateZ(0); 36604 transform-origin: right; 36605 transition: all 0.5s ease-in-out; 36606 } 36607 36608 .funky-show-hide.ng-hide-add.ng-hide-add-active { 36609 transform: rotateZ(-135deg); 36610 } 36611 36612 .funky-show-hide.ng-hide-remove { 36613 transform: rotateY(90deg); 36614 transform-origin: left; 36615 transition: all 0.5s ease; 36616 } 36617 36618 .funky-show-hide.ng-hide-remove.ng-hide-remove-active { 36619 transform: rotateY(0); 36620 } 36621 36622 .check-element { 36623 border: 1px solid black; 36624 opacity: 1; 36625 padding: 10px; 36626 } 36627 </file> 36628 <file name="protractor.js" type="protractor"> 36629 it('should check ngShow', function() { 36630 var checkbox = element(by.model('checked')); 36631 var checkElem = element(by.css('.check-element')); 36632 36633 expect(checkElem.isDisplayed()).toBe(false); 36634 checkbox.click(); 36635 expect(checkElem.isDisplayed()).toBe(true); 36636 }); 36637 </file> 36638 </example> 36639 */ 36640var ngShowDirective = ['$animate', function($animate) { 36641 return { 36642 restrict: 'A', 36643 multiElement: true, 36644 link: function(scope, element, attr) { 36645 scope.$watch(attr.ngShow, function ngShowWatchAction(value) { 36646 // we're adding a temporary, animation-specific class for ng-hide since this way 36647 // we can control when the element is actually displayed on screen without having 36648 // to have a global/greedy CSS selector that breaks when other animations are run. 36649 // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845 36650 $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, {
36651 tempClasses: NG_HIDE_IN_PROGRESS_CLASS 36652 }); 36653 }); 36654 } 36655 }; 36656}]; 36657 36658 36659/** 36660 * @ngdoc directive 36661 * @name ngHide 36662 * @multiElement 36663 * 36664 * @description 36665 * The `ngHide` directive shows or hides the given HTML element based on the expression provided to 36666 * the `ngHide` attribute. 36667 * 36668 * The element is shown or hidden by removing or adding the `.ng-hide` CSS class onto the element. 36669 * The `.ng-hide` CSS class is predefined in AngularJS and sets the display style to none (using an 36670 * `!important` flag). For CSP mode please add `angular-csp.css` to your HTML file (see 36671 * {@link ng.directive:ngCsp ngCsp}). 36672 * 36673 * ```html 36674 * <!-- when $scope.myValue is truthy (element is hidden) --> 36675 * <div ng-hide="myValue" class="ng-hide"></div> 36676 * 36677 * <!-- when $scope.myValue is falsy (element is visible) --> 36678 * <div ng-hide="myValue"></div> 36679 * ``` 36680 * 36681 * When the `ngHide` expression evaluates to a truthy value then the `.ng-hide` CSS class is added 36682 * to the class attribute on the element causing it to become hidden. When falsy, the `.ng-hide` 36683 * CSS class is removed from the element causing the element not to appear hidden. 36684 * 36685 * ## Why is `!important` used? 36686 * 36687 * You may be wondering why `!important` is used for the `.ng-hide` CSS class. This is because the 36688 * `.ng-hide` selector can be easily overridden by heavier selectors. For example, something as 36689 * simple as changing the display style on a HTML list item would make hidden elements appear 36690 * visible. This also becomes a bigger issue when dealing with CSS frameworks. 36691 * 36692 * By using `!important`, the show and hide behavior will work as expected despite any clash between 36693 * CSS selector specificity (when `!important` isn't used with any conflicting styles). If a 36694 * developer chooses to override the styling to change how to hide an element then it is just a 36695 * matter of using `!important` in their own CSS code. 36696 * 36697 * ### Overriding `.ng-hide` 36698 * 36699 * By default, the `.ng-hide` class will style the element with `display: none !important`. If you 36700 * wish to change the hide behavior with `ngShow`/`ngHide`, you can simply overwrite the styles for 36701 * the `.ng-hide` CSS class. Note that the selector that needs to be used is actually 36702 * `.ng-hide:not(.ng-hide-animate)` to cope with extra animation classes that can be added. 36703 * 36704 * ```css 36705 * .ng-hide:not(.ng-hide-animate) { 36706 * /* These are just alternative ways of hiding an element */ 36707 * display: block!important; 36708 * position: absolute; 36709 * top: -9999px; 36710 * left: -9999px; 36711 * } 36712 * ``` 36713 * 36714 * By default you don't need to override in CSS anything and the animations will work around the 36715 * display style. 36716 * 36717 * ## A note about animations with `ngHide` 36718 * 36719 * Animations in `ngShow`/`ngHide` work with the show and hide events that are triggered when the 36720 * directive expression is true and false. This system works like the animation system present with 36721 * `ngClass` except that you must also include the `!important` flag to override the display 36722 * property so that the elements are not actually hidden during the animation. 36723 * 36724 * ```css 36725 * /* A working example can be found at the bottom of this page. */ 36726 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 36727 * transition: all 0.5s linear; 36728 * } 36729 * 36730 * .my-element.ng-hide-add { ... } 36731 * .my-element.ng-hide-add.ng-hide-add-active { ... } 36732 * .my-element.ng-hide-remove { ... } 36733 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 36734 * ``` 36735 * 36736 * Keep in mind that, as of AngularJS version 1.3, there is no need to change the display property 36737 * to block during animation states - ngAnimate will automatically handle the style toggling for you. 36738 * 36739 * @animations 36740 * | Animation | Occurs | 36741 * |-----------------------------------------------------|------------------------------------------------------------------------------------------------------------| 36742 * | {@link $animate#addClass addClass} `.ng-hide` | After the `ngHide` expression evaluates to a truthy value and just before the contents are set to hidden. | 36743 * | {@link $animate#removeClass removeClass} `.ng-hide` | After the `ngHide` expression evaluates to a non truthy value and just before contents are set to visible. | 36744 * 36745 * 36746 * @element ANY 36747 * @param {expression} ngHide If the {@link guide/expression expression} is truthy/falsy then the 36748 * element is hidden/shown respectively. 36749 * 36750 * @example 36751 * A simple example, animating the element's opacity: 36752 * 36753 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-hide-simple"> 36754 <file name="index.html"> 36755 Hide: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br /> 36756 <div class="check-element animate-show-hide" ng-hide="checked"> 36757 I hide when your checkbox is checked. 36758 </div> 36759 </file> 36760 <file name="animations.css"> 36761 .animate-show-hide.ng-hide { 36762 opacity: 0; 36763 } 36764 36765 .animate-show-hide.ng-hide-add, 36766 .animate-show-hide.ng-hide-remove { 36767 transition: all linear 0.5s; 36768 } 36769 36770 .check-element { 36771 border: 1px solid black; 36772 opacity: 1; 36773 padding: 10px; 36774 } 36775 </file> 36776 <file name="protractor.js" type="protractor"> 36777 it('should check ngHide', function() { 36778 var checkbox = element(by.model('checked')); 36779 var checkElem = element(by.css('.check-element')); 36780 36781 expect(checkElem.isDisplayed()).toBe(true); 36782 checkbox.click(); 36783 expect(checkElem.isDisplayed()).toBe(false); 36784 }); 36785 </file> 36786 </example> 36787 * 36788 * <hr /> 36789 * @example 36790 * A more complex example, featuring different show/hide animations: 36791 * 36792 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-hide-complex"> 36793 <file name="index.html"> 36794 Hide: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br /> 36795 <div class="check-element funky-show-hide" ng-hide="checked"> 36796 I hide when your checkbox is checked. 36797 </div> 36798 </file> 36799 <file name="animations.css"> 36800 body { 36801 overflow: hidden; 36802 perspective: 1000px; 36803 } 36804 36805 .funky-show-hide.ng-hide-add { 36806 transform: rotateZ(0); 36807 transform-origin: right; 36808 transition: all 0.5s ease-in-out; 36809 } 36810 36811 .funky-show-hide.ng-hide-add.ng-hide-add-active { 36812 transform: rotateZ(-135deg); 36813 } 36814 36815 .funky-show-hide.ng-hide-remove { 36816 transform: rotateY(90deg); 36817 transform-origin: left; 36818 transition: all 0.5s ease; 36819 } 36820 36821 .funky-show-hide.ng-hide-remove.ng-hide-remove-active { 36822 transform: rotateY(0); 36823 } 36824 36825 .check-element { 36826 border: 1px solid black; 36827 opacity: 1; 36828 padding: 10px; 36829 } 36830 </file> 36831 <file name="protractor.js" type="protractor"> 36832 it('should check ngHide', function() { 36833 var checkbox = element(by.model('checked')); 36834 var checkElem = element(by.css('.check-element')); 36835 36836 expect(checkElem.isDisplayed()).toBe(true); 36837 checkbox.click(); 36838 expect(checkElem.isDisplayed()).toBe(false); 36839 }); 36840 </file> 36841 </example> 36842 */ 36843var ngHideDirective = ['$animate', function($animate) { 36844 return { 36845 restrict: 'A', 36846 multiElement: true, 36847 link: function(scope, element, attr) { 36848 scope.$watch(attr.ngHide, function ngHideWatchAction(value) { 36849 // The comment inside of the ngShowDirective explains why we add and 36850 // remove a temporary class for the show/hide animation 36851 $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, {
36852 tempClasses: NG_HIDE_IN_PROGRESS_CLASS 36853 }); 36854 }); 36855 } 36856 }; 36857}]; 36858 36859/** 36860 * @ngdoc directive 36861 * @name ngStyle 36862 * @restrict AC 36863 * 36864 * @description 36865 * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally. 36866 * 36867 * @knownIssue 36868 * You should not use {@link guide/interpolation interpolation} in the value of the `style` 36869 * attribute, when using the `ngStyle` directive on the same element. 36870 * See {@link guide/interpolation#known-issues here} for more info. 36871 * 36872 * @element ANY 36873 * @param {expression} ngStyle 36874 * 36875 * {@link guide/expression Expression} which evals to an 36876 * object whose keys are CSS style names and values are corresponding values for those CSS 36877 * keys. 36878 * 36879 * Since some CSS style names are not valid keys for an object, they must be quoted. 36880 * See the 'background-color' style in the example below. 36881 * 36882 * @example 36883 <example name="ng-style"> 36884 <file name="index.html"> 36885 <input type="button" value="set color" ng-click="myStyle={color:'red'}"> 36886 <input type="button" value="set background" ng-click="myStyle={'background-color':'blue'}"> 36887 <input type="button" value="clear" ng-click="myStyle={}"> 36888 <br/> 36889 <span ng-style="myStyle">Sample Text</span> 36890 <pre>myStyle={{myStyle}}</pre> 36891 </file> 36892 <file name="style.css"> 36893 span { 36894 color: black; 36895 } 36896 </file> 36897 <file name="protractor.js" type="protractor"> 36898 var colorSpan = element(by.css('span')); 36899 36900 it('should check ng-style', function() { 36901 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); 36902 element(by.css('input[value=\'set color\']')).click(); 36903 expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)'); 36904 element(by.css('input[value=clear]')).click(); 36905 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); 36906 }); 36907 </file> 36908 </example> 36909 */ 36910var ngStyleDirective = ngDirective(function(scope, element, attr) { 36911 scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) { 36912 if (oldStyles && (newStyles !== oldStyles)) { 36913 forEach(oldStyles, function(val, style) { element.css(style, '');}); 36914 } 36915 if (newStyles) element.css(newStyles); 36916 }, true
vendor: 9,215 bytes, lines 36916-37156
36916); 36917}); 36918 36919/** 36920 * @ngdoc directive 36921 * @name ngSwitch 36922 * @restrict EA 36923 * 36924 * @description 36925 * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression. 36926 * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location 36927 * as specified in the template. 36928 * 36929 * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it 36930 * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element 36931 * matches the value obtained from the evaluated expression. In other words, you define a container element 36932 * (where you place the directive), place an expression on the **`on="..."` attribute** 36933 * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place 36934 * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on 36935 * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default 36936 * attribute is displayed. 36937 * 36938 * <div class="alert alert-info"> 36939 * Be aware that the attribute values to match against cannot be expressions. They are interpreted 36940 * as literal string values to match against. 36941 * For example, **`ng-switch-when="someVal"`** will match against the string `"someVal"` not against the 36942 * value of the expression `$scope.someVal`. 36943 * </div> 36944 36945 * @animations 36946 * | Animation | Occurs | 36947 * |----------------------------------|-------------------------------------| 36948 * | {@link ng.$animate#enter enter} | after the ngSwitch contents change and the matched child element is placed inside the container | 36949 * | {@link ng.$animate#leave leave} | after the ngSwitch contents change and just before the former contents are removed from the DOM | 36950 * 36951 * @usage 36952 * 36953 * ``` 36954 * <ANY ng-switch="expression"> 36955 * <ANY ng-switch-when="matchValue1">...</ANY> 36956 * <ANY ng-switch-when="matchValue2">...</ANY> 36957 * <ANY ng-switch-default>...</ANY> 36958 * </ANY> 36959 * ``` 36960 * 36961 * 36962 * @scope 36963 * @priority 1200 36964 * @param {*} ngSwitch|on expression to match against <code>ng-switch-when</code>. 36965 * On child elements add: 36966 * 36967 * * `ngSwitchWhen`: the case statement to match against. If match then this 36968 * case will be displayed. If the same match appears multiple times, all the 36969 * elements will be displayed. It is possible to associate multiple values to 36970 * the same `ngSwitchWhen` by defining the optional attribute 36971 * `ngSwitchWhenSeparator`. The separator will be used to split the value of 36972 * the `ngSwitchWhen` attribute into multiple tokens, and the element will show 36973 * if any of the `ngSwitch` evaluates to any of these tokens. 36974 * * `ngSwitchDefault`: the default case when no other case match. If there 36975 * are multiple default cases, all of them will be displayed when no other 36976 * case match. 36977 * 36978 * 36979 * @example 36980 <example module="switchExample" deps="angular-animate.js" animations="true" name="ng-switch"> 36981 <file name="index.html"> 36982 <div ng-controller="ExampleController"> 36983 <select ng-model="selection" ng-options="item for item in items"> 36984 </select> 36985 <code>selection={{selection}}</code> 36986 <hr/> 36987 <div class="animate-switch-container" 36988 ng-switch on="selection"> 36989 <div class="animate-switch" ng-switch-when="settings|options" ng-switch-when-separator="|">Settings Div</div> 36990 <div class="animate-switch" ng-switch-when="home">Home Span</div> 36991 <div class="animate-switch" ng-switch-default>default</div> 36992 </div> 36993 </div> 36994 </file> 36995 <file name="script.js"> 36996 angular.module('switchExample', ['ngAnimate']) 36997 .controller('ExampleController', ['$scope', function($scope) { 36998 $scope.items = ['settings', 'home', 'options', 'other']; 36999 $scope.selection = $scope.items[0]; 37000 }]); 37001 </file> 37002 <file name="animations.css"> 37003 .animate-switch-container { 37004 position:relative; 37005 background:white; 37006 border:1px solid black; 37007 height:40px; 37008 overflow:hidden; 37009 } 37010 37011 .animate-switch { 37012 padding:10px; 37013 } 37014 37015 .animate-switch.ng-animate { 37016 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 37017 37018 position:absolute; 37019 top:0; 37020 left:0; 37021 right:0; 37022 bottom:0; 37023 } 37024 37025 .animate-switch.ng-leave.ng-leave-active, 37026 .animate-switch.ng-enter { 37027 top:-50px; 37028 } 37029 .animate-switch.ng-leave, 37030 .animate-switch.ng-enter.ng-enter-active { 37031 top:0; 37032 } 37033 </file> 37034 <file name="protractor.js" type="protractor"> 37035 var switchElem = element(by.css('[ng-switch]')); 37036 var select = element(by.model('selection')); 37037 37038 it('should start in settings', function() { 37039 expect(switchElem.getText()).toMatch(/Settings Div/); 37040 }); 37041 it('should change to home', function() { 37042 select.all(by.css('option')).get(1).click(); 37043 expect(switchElem.getText()).toMatch(/Home Span/); 37044 }); 37045 it('should change to settings via "options"', function() { 37046 select.all(by.css('option')).get(2).click(); 37047 expect(switchElem.getText()).toMatch(/Settings Div/); 37048 }); 37049 it('should select default', function() { 37050 select.all(by.css('option')).get(3).click(); 37051 expect(switchElem.getText()).toMatch(/default/); 37052 }); 37053 </file> 37054 </example> 37055 */ 37056var ngSwitchDirective = ['$animate', '$compile', function($animate, $compile) { 37057 return { 37058 require: 'ngSwitch', 37059 37060 // asks for $scope to fool the BC controller module 37061 controller: ['$scope', function NgSwitchController() { 37062 this.cases = {}; 37063 }], 37064 link: function(scope, element, attr, ngSwitchController) { 37065 var watchExpr = attr.ngSwitch || attr.on, 37066 selectedTranscludes = [], 37067 selectedElements = [], 37068 previousLeaveAnimations = [], 37069 selectedScopes = []; 37070 37071 var spliceFactory = function(array, index) { 37072 return function(response) { 37073 if (response !== false) array.splice(index, 1); 37074 }; 37075 }; 37076 37077 scope.$watch(watchExpr, function ngSwitchWatchAction(value) { 37078 var i, ii; 37079 37080 // Start with the last, in case the array is modified during the loop 37081 while (previousLeaveAnimations.length) { 37082 $animate.cancel(previousLeaveAnimations.pop()); 37083 } 37084 37085 for (i = 0, ii = selectedScopes.length; i < ii; ++i) { 37086 var selected = getBlockNodes(selectedElements[i].clone); 37087 selectedScopes[i].$destroy(); 37088 var runner = previousLeaveAnimations[i] = $animate.leave(selected); 37089 runner.done(spliceFactory(previousLeaveAnimations, i)); 37090 } 37091 37092 selectedElements.length = 0; 37093 selectedScopes.length = 0; 37094 37095 if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) { 37096 forEach(selectedTranscludes, function(selectedTransclude) { 37097 selectedTransclude.transclude(function(caseElement, selectedScope) { 37098 selectedScopes.push(selectedScope); 37099 var anchor = selectedTransclude.element; 37100 caseElement[caseElement.length++] = $compile.$$createComment('end ngSwitchWhen'); 37101 var block = { clone: caseElement }; 37102 37103 selectedElements.push(block); 37104 $animate.enter(caseElement, anchor.parent(), anchor); 37105 }); 37106 }); 37107 } 37108 }); 37109 } 37110 }; 37111}]; 37112 37113var ngSwitchWhenDirective = ngDirective({ 37114 transclude: 'element', 37115 priority: 1200, 37116 require: '^ngSwitch', 37117 multiElement: true, 37118 link: function(scope, element, attrs, ctrl, $transclude) { 37119 37120 var cases = attrs.ngSwitchWhen.split(attrs.ngSwitchWhenSeparator).sort().filter( 37121 // Filter duplicate cases 37122 function(element, index, array) { return array[index - 1] !== element; } 37123 ); 37124 37125 forEach(cases, function(whenCase) { 37126 ctrl.cases['!' + whenCase] = (ctrl.cases['!' + whenCase] || []); 37127 ctrl.cases['!' + whenCase].push({ transclude: $transclude, element: element }); 37128 }); 37129 } 37130}); 37131 37132var ngSwitchDefaultDirective = ngDirective({ 37133 transclude: 'element', 37134 priority: 1200, 37135 require: '^ngSwitch', 37136 multiElement: true, 37137 link: function(scope, element, attr, ctrl, $transclude) { 37138 ctrl.cases['?'] = (ctrl.cases['?'] || []); 37139 ctrl.cases['?'].push({ transclude: $transclude, element: element }); 37140 } 37141}); 37142 37143/** 37144 * @ngdoc directive 37145 * @name ngTransclude 37146 * @restrict EAC 37147 * 37148 * @description 37149 * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion. 37150 * 37151 * You can specify that you want to insert a named transclusion slot, instead of the default slot, by providing the slot name 37152 * as the value of the `ng-transclude` or `ng-transclude-slot` attribute. 37153 * 37154 * If the transcluded content is not empty (i.e. contains one or more DOM nodes, including whitespace text nodes), any existing 37155 * content of this element will be removed before the transcluded content is inserted. 37156 * If the transcluded content is empty
37156, the existing content is left intact. This lets you provide fallback content in the case 37157 * that no transcluded content is provided. 37158 * 37159 * @element ANY 37160 * 37161 * @param {string} ngTransclude|ngTranscludeSlot the name of the slot to insert at this point. If this is not provided, is empty 37162 * or its value is the same as the name of the attribute then the default slot is used. 37163 * 37164 * @example 37165 * ### Basic transclusion 37166 * This example demonstrates basic transclusion of content into a component directive. 37167 * <example name="simpleTranscludeExample" module="transcludeExample"> 37168 * <file name="index.html"> 37169 * <script> 37170 * angular.module('transcludeExample', []) 37171 * .directive('pane', function(){ 37172 * return { 37173 * restrict: 'E', 37174 * transclude: true, 37175 * scope: { title:'@' }, 37176 * template: '<div style="border: 1px solid black;">' + 37177 * '<div style="background-color: gray">{{title}}</div>' + 37178 * '<ng-transclude></ng-transclude>' + 37179 * '</div>' 37180 * }; 37181 * }) 37182 * .controller('ExampleController', ['$scope', function($scope) { 37183 * $scope.title = 'Lorem Ipsum'; 37184 * $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; 37185 * }]); 37186 * </script>
37187 * <div ng-controller="ExampleController"> 37188 * <input ng-model="title" aria-label="title"> <br/> 37189 * <textarea ng-model="text" aria-label="text"></textarea> <br/> 37190 * <pane title="{{title}}">{{text}}</pane> 37191 * </div> 37192 * </file> 37193 * <file name="protractor.js" type="protractor"> 37194 * it('should have transcluded', function() { 37195 * var titleElement = element(by.model('title')); 37196 * titleElement.clear(); 37197 * titleElement.sendKeys('TITLE'); 37198 * var textElement = element(by.model('text')); 37199 * textElement.clear(); 37200 * textElement.sendKeys('TEXT'); 37201 * expect(element(by.binding('title')).getText()).toEqual('TITLE'); 37202 * expect(element(by.binding('text')).getText()).toEqual('TEXT'); 37203 * }); 37204 * </file> 37205 * </example> 37206 * 37207 * @example 37208 * ### Transclude fallback content 37209 * This example shows how to use `NgTransclude` with fallback content, that 37210 * is displayed if no transcluded content is provided. 37211 * 37212 * <example module="transcludeFallbackContentExample" name="ng-transclude"> 37213 * <file name="index.html"> 37214 * <script> 37215 * angular.module('transcludeFallbackContentExample', []) 37216 * .directive('myButton', function(){ 37217 * return { 37218 * restrict: 'E', 37219 * transclude: true, 37220 * scope: true, 37221 * template: '<button style="cursor: pointer;">' + 37222 * '<ng-transclude>' + 37223 * '<b style="color: red;">Button1</b>' + 37224 * '</ng-transclude>' + 37225 * '</button>' 37226 * }; 37227 * }); 37228 * </script> 37229 * <!-- fallback button content --> 37230 * <my-button id="fallback"></my-button> 37231 * <!-- modified button content --> 37232 * <my-button id="modified"> 37233 * <i style="color: green;">Button2</i> 37234 * </my-button> 37235 * </file> 37236 * <file name="protractor.js" type="protractor"> 37237 * it('should have different transclude element content', function() { 37238 * expect(element(by.id('fallback')).getText()).toBe('Button1'); 37239 * expect(element(by.id('modified')).getText()).toBe('Button2'); 37240 * }); 37241 * </file> 37242 * </example> 37243 * 37244 * @example 37245 * ### Multi-slot transclusion 37246 * This example demonstrates using multi-slot transclusion in a component directive. 37247 * <example name="multiSlotTranscludeExample" module="multiSlotTranscludeExample"> 37248 * <file name="index.html"> 37249 * <style> 37250 * .title, .footer { 37251 * background-color: gray 37252 * } 37253 * </style>
37254 * <div ng-controller="ExampleController"> 37255 * <input ng-model="title" aria-label="title"> <br/> 37256 * <textarea ng-model="text" aria-label="text"></textarea> <br/> 37257 * <pane> 37258 * <pane-title><a ng-href="{{link}}">{{title}}</a></pane-title> 37259 * <pane-body><p>{{text}}</p></pane-body> 37260 * </pane> 37261 * </div> 37262 * </file> 37263 * <file name="app.js"> 37264 * angular.module('multiSlotTranscludeExample', []) 37265 * .directive('pane', function() { 37266 * return { 37267 * restrict: 'E', 37268 * transclude: { 37269 * 'title': '?paneTitle', 37270 * 'body': 'paneBody', 37271 * 'footer': '?paneFooter' 37272 * }, 37273 * template: '<div style="border: 1px solid black;">' + 37274 * '<div class="title" ng-transclude="title">Fallback Title</div>' + 37275 * '<div ng-transclude="body"></div>' + 37276 * '<div class="footer" ng-transclude="footer">Fallback Footer</div>' + 37277 * '</div>' 37278 * }; 37279 * }) 37280 * .controller('ExampleController', ['$scope', function($scope) { 37281 * $scope.title = 'Lorem Ipsum'; 37282 * $scope.link = 'https://google.com'; 37283 * $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; 37284 * }]); 37285 * </file> 37286 * <file name="protractor.js" type="protractor"> 37287 * it('should have transcluded the title and the body', function() { 37288 * var titleElement = element(by.model('title')); 37289 * titleElement.clear(); 37290 * titleElement.sendKeys('TITLE'); 37291 * var textElement = element(by.model('text')); 37292 * textElement.clear(); 37293 * textElement.sendKeys('TEXT'); 37294 * expect(element(by.css('.title')).getText()).toEqual('TITLE'); 37295 * expect(element(by.binding('text')).getText()).toEqual('TEXT'); 37296 * expect(element(by.css('.footer')).getText()).toEqual('Fallback Footer'); 37297 * }); 37298 * </file> 37299 * </example> 37300 */ 37301var ngTranscludeMinErr = minErr('ngTransclude'); 37302var ngTranscludeDirective = ['$compile', function($compile) { 37303 return { 37304 restrict: 'EAC', 37305 terminal: true, 37306 compile: function ngTranscludeCompile(tElement) { 37307 37308 // Remove and cache any original content to act as a fallback 37309 var fallbackLinkFn = $compile(tElement.contents()); 37310 tElement.empty(); 37311 37312 return function ngTranscludePostLink($scope, $element, $attrs, controller, $transclude) { 37313 37314 if (!$transclude) { 37315 throw ngTranscludeMinErr('orphan', 37316 'Illegal use of ngTransclude directive in the template! ' + 37317 'No parent directive that requires a transclusion found. ' + 37318 'Element: {0}', 37319 startingTag($element)); 37320 } 37321 37322 37323 // If the attribute is of the form: `ng-transclude="ng-transclude"` then treat it like the default 37324 if ($attrs.ngTransclude === $attrs.$attr.ngTransclude) { 37325 $attrs.ngTransclude = ''; 37326 } 37327 var slotName = $attrs.ngTransclude || $attrs.ngTranscludeSlot; 37328 37329 // If the slot is required and no transclusion content is provided then this call will throw an error 37330 $transclude(ngTranscludeCloneAttachFn, null, slotName); 37331
37332 // If the slot is optional and no transclusion content is provided then use the fallback content 37333 if (slotName && !$transclude.isSlotFilled(slotName)) { 37334 useFallbackContent(); 37335 } 37336 37337 function ngTranscludeCloneAttachFn(clone, transcludedScope) { 37338 if (clone.length) { 37339 $element.append(clone); 37340 } else { 37341 useFallbackContent(); 37342 // There is nothing linked against the transcluded scope since no content was available, 37343 // so it should be safe to clean up the generated scope. 37344 transcludedScope.$destroy(); 37345 } 37346 } 37347 37348 function useFallbackContent() { 37349 // Since this is the fallback content rather than the transcluded content, 37350 // we link against the scope of this directive rather than the transcluded scope 37351 fallbackLinkFn($scope, function(clone) { 37352 $element.append(clone); 37353 }); 37354 } 37355 }; 37356 } 37357 }; 37358}]; 37359 37360/** 37361 * @ngdoc directive 37362 * @name script 37363 * @restrict E 37364 * 37365 * @description 37366 * Load the content of a `<script>` element into {@link ng.$templateCache `$templateCache`}, so that the 37367 * template can be used by {@link ng.directive:ngInclude `ngInclude`}, 37368 * {@link ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the 37369 * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be 37370 * assigned through the element's `id`, which can then be used as a directive's `templateUrl`. 37371 * 37372 * @param {string} type Must be set to `'text/ng-template'`. 37373 * @param {string} id Cache name of the template. 37374 * 37375 * @example 37376 <example name="script-tag"> 37377 <file name="index.html"> 37378 <script type="text/ng-template" id="/tpl.html"> 37379 Content of the template. 37380 </script> 37381 37382 <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a> 37383 <div id="tpl-content" ng-include src="currentTpl"></div> 37384 </file> 37385 <file name="protractor.js" type="protractor"> 37386 it('should load template defined inside script tag', function() { 37387 element(by.css('#tpl-link')).click(); 37388 expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/); 37389 }); 37390 </file> 37391 </example> 37392 */ 37393var scriptDirective = ['$templateCache', function($templateCache) { 37394 return { 37395 restrict: 'E', 37396 terminal: true, 37397 compile: function(element, attr) { 37398 if (attr.type === 'text/ng-template') { 37399 var templateUrl = attr.id, 37400 text = element[0].text; 37401 37402 $templateCache.put(templateUrl, text); 37403 } 37404 } 37405 }; 37406}]; 37407 37408/* exported selectDirective, optionDirective */ 37409 37410var noopNgModelController = { $setViewValue: noop, $render: noop }; 37411 37412function chromeHack(optionElement) { 37413 // Workaround for https://code.google.com/p/chromium/issues/detail?id=381459 37414 // Adding an <option selected="selected"> element to a <select required="required"> should 37415 // automatically select the new element 37416 if (optionElement[0].hasAttribute('selected')) { 37417 optionElement[0].selected = true; 37418 } 37419} 37420 37421/** 37422 * @ngdoc type 37423 * @name select.SelectController 37424 * @description 37425 * The controller for the `<select>` directive. This provides support for reading 37426 * and writing the selected value(s) of the control and also coordinates dynamically 37427 * added `<option>` elements, perhaps by an `ngRepeat` directive. 37428 */ 37429var SelectController = 37430 ['$element', '$scope', /** @this */ function($element, $scope) { 37431 37432 var self = this, 37433 optionsMap = new HashMap(); 37434 37435 // If the ngModel doesn't get provided then provide a dummy noop version to prevent errors 37436 self.ngModelCtrl = noopNgModelController; 37437 37438 // The "unknown" option is one that is prepended to the list if the viewValue 37439 // does not match any of the options. When it is rendered the value of the unknown 37440 // option is '? XXX ?' where XXX is the hashKey of the value that is not known. 37441 // 37442 // We can't just jqLite('<option>') since jqLite is not smart enough 37443 // to create it in <select> and IE barfs otherwise. 37444 self.unknownOption = jqLite(window.document.createElement('option')); 37445 self.renderUnknownOption = function(val) { 37446 var unknownVal = '? ' + hashKey(val) + ' ?'; 37447 self.unknownOption.val(unknownVal);
37448 $element.prepend(self.unknownOption); 37449 $element.val(unknownVal); 37450 }; 37451 37452 $scope.$on('$destroy', function() { 37453 // disable unknown option so that we don't do work when the whole select is being destroyed 37454 self.renderUnknownOption = noop; 37455 }); 37456 37457 self.removeUnknownOption = function() { 37458 if (self.unknownOption.parent()) self.unknownOption.remove(); 37459 }; 37460 37461 37462 // Read the value of the select control, the implementation of this changes depending 37463 // upon whether the select can have multiple values and whether ngOptions is at work. 37464 self.readValue = function readSingleValue() { 37465 self.removeUnknownOption(); 37466 return $element.val(); 37467 }; 37468 37469 37470 // Write the value to the select control, the implementation of this changes depending 37471 // upon whether the select can have multiple values and whether ngOptions is at work. 37472 self.writeValue = function writeSingleValue(value) { 37473 if (self.hasOption(value)) { 37474 self.removeUnknownOption(); 37475 $element.val(value); 37476 if (value === '') self.emptyOption.prop('selected', true); // to make IE9 happy 37477 } else { 37478 if (value == null && self.emptyOption) { 37479 self.removeUnknownOption(); 37480 $element.val(''); 37481 } else { 37482 self.renderUnknownOption(value); 37483 } 37484 } 37485 }; 37486 37487 37488 // Tell the select control that an option, with the given value, has been added 37489 self.addOption = function(value, element) { 37490 // Skip comment nodes, as they only pollute the `optionsMap` 37491 if (element[0].nodeType === NODE_TYPE_COMMENT) return; 37492 37493 assertNotHasOwnProperty(value, '"option value"'); 37494 if (value === '') { 37495 self.emptyOption = element; 37496 } 37497 var count = optionsMap.get(value) || 0; 37498 optionsMap.put(value, count + 1); 37499 self.ngModelCtrl.$render(); 37500 chromeHack(element); 37501 }; 37502 37503 // Tell the select control that an option, with the given value, has been removed 37504 self.removeOption = function(value) { 37505 var count = optionsMap.get(value); 37506 if (count) { 37507 if (count === 1) { 37508 optionsMap.remove(value); 37509 if (value === '') { 37510 self.emptyOption = undefined; 37511 } 37512 } else { 37513 optionsMap.put(value, count - 1); 37514 } 37515 } 37516 }; 37517 37518 // Check whether the select control has an option matching the given value 37519 self.hasOption = function(value) { 37520 return !!optionsMap.get(value); 37521 }; 37522 37523 37524 self.registerOption = function(optionScope, optionElement, optionAttrs, hasDynamicValueAttr, interpolateTextFn) { 37525 37526 if (hasDynamicValueAttr) { 37527 // either "value" is interpolated directly, or set by ngValue 37528 var oldVal; 37529 optionAttrs.$observe('value', function valueAttributeObserveAction(newVal) { 37530 if (isDefined(oldVal)) { 37531 self.removeOption(oldVal); 37532 } 37533 oldVal = newVal; 37534 self.addOption(newVal, optionElement); 37535 }); 37536 } else if (interpolateTextFn) { 37537 // The text content is interpolated 37538 optionScope.$watch(interpolateTextFn, function interpolateWatchAction(newVal, oldVal) { 37539 optionAttrs.$set('value', newVal); 37540 if (oldVal !== newVal) { 37541 self.removeOption(oldVal); 37542 } 37543 self.addOption(newVal, optionElement); 37544 }); 37545 } else { 37546 // The value attribute is static 37547 self.addOption(optionAttrs.value, optionElement); 37548 } 37549 37550 optionElement.on('$destroy', function() { 37551 self.removeOption(optionAttrs.value); 37552 self.ngModelCtrl.$render(); 37553 }); 37554 }; 37555}]; 37556 37557/** 37558 * @ngdoc directive 37559 * @name select 37560 * @restrict E 37561 * 37562 * @description 37563 * HTML `SELECT` element with angular data-binding. 37564 * 37565 * The `select` directive is used together with {@link ngModel `ngModel`} to provide data-binding 37566 * between the scope and the `<select>` control (including setting default values). 37567 * It also handles dynamic `<option>` elements, which can be added using the {@link ngRepeat `ngRepeat}` or 37568 * {@link ngOptions `ngOptions`} directives. 37569 * 37570 * When an item in the `<select>` menu is selected, the value of the selected option will be bound 37571 * to the model identified by the `ngModel` directive. With static or repeated options, this is 37572 * the content of the `value` attribute or the textContent of the `<option>`, if the value attribute is missing. 37573 * For dynamic options, use interpolation inside the `value` attribute or the `textContent`. Using 37574 * {@link ngValue ngValue} is also possible (as it sets the `value` attribute), and will take 37575 * precedence over `value` and `textContent`. 37576 * 37577 * <div class="alert alert-warning"> 37578 * Note that the value of a `select` directive used without `ngOptions` is always a string. 37579 * When the model needs to be bound to a non-string value, you must either explicitly convert it 37580 * using a directive (see example below) or use `ngOptions` to specify the set of options. 37581 * This is because an option element can only be bound to string values at present. 37582 * </div> 37583 * 37584 * If the viewValue of `ngModel` does not match any of the options, then the control 37585 * will automatically add an "unknown" option, which it then removes when the mismatch is resolved. 37586 * 37587 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can 37588 * be nested into the `<select>` element. This element will then represent the `null` or "not selected" 37589 * option. See example below for demonstration. 37590 * 37591 * <div class="alert alert-info"> 37592 * In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions 37593 * ngOptions} to achieve a similar result. However, `ngOptions` provides some benef
37593its, such as 37594 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the 37595 * comprehension expression, and additionally in reducing memory and increasing speed by not creating 37596 * a new scope for each repeated instance. 37597 * </div> 37598 * 37599 * 37600 * @param {string} ngModel Assignable angular expression to data-bind to. 37601 * @param {string=} name Property name of the form under which the control is published. 37602 * @param {string=} multiple Allows multiple options to be selected. The selected values will be 37603 * bound to the model as an array. 37604 * @param {string=} required Sets `required` validation error key if the value is not entered. 37605 * @param {string=} ngRequired Adds required attribute and required validation constraint to 37606 * the element when the ngRequired expression evaluates to true. Use ngRequired instead of required 37607 * when you want to data-bind to the required attribute. 37608 * @param {string=} ngChange Angular expression to be executed when selected option(s) changes due to user 37609 * interaction with the select element. 37610 * @param {string=} ngOptions sets the options that the select is populated with and defines what is 37611 * set on the model on selection. See {@link ngOptions `ngOptions`}. 37612 * 37613 * @example 37614 * ### Simple `select` elements with static options 37615 * 37616 * <example name="static-select" module="staticSelect"> 37617 * <file name="index.html">
37618 * <div ng-controller="ExampleController"> 37619 * <form name="myForm"> 37620 * <label for="singleSelect"> Single select: </label><br> 37621 * <select name="singleSelect" ng-model="data.singleSelect"> 37622 * <option value="option-1">Option 1</option> 37623 * <option value="option-2">Option 2</option> 37624 * </select><br> 37625 * 37626 * <label for="singleSelect"> Single select with "not selected" option and dynamic option values: </label><br> 37627 * <select name="singleSelect" id="singleSelect" ng-model="data.singleSelect"> 37628 * <option value="">---Please select---</option> <!-- not selected / blank option --> 37629 * <option value="{{data.option1}}">Option 1</option> <!-- interpolation --> 37630 * <option value="option-2">Option 2</option> 37631 * </select><br> 37632 * <button ng-click="forceUnknownOption()">Force unknown option</button><br> 37633 * <tt>singleSelect = {{data.singleSelect}}</tt> 37634 * 37635 * <hr> 37636 * <label for="multipleSelect"> Multiple select: </label><br> 37637 * <select name="multipleSelect" id="multipleSelect" ng-model="data.multipleSelect" multiple> 37638 * <option value="option-1">Option 1</option> 37639 * <option value="option-2">Option 2</option> 37640 * <option value="option-3">Option 3</option> 37641 * </select><br> 37642 * <tt>multipleSelect = {{data.multipleSelect}}</tt><br/> 37643 * </form> 37644 * </div> 37645 * </file> 37646 * <file name="app.js"> 37647 * angular.module('staticSelect', []) 37648 * .controller('ExampleController', ['$scope', function($scope) { 37649 * $scope.data = { 37650 * singleSelect: null, 37651 * multipleSelect: [], 37652 * option1: 'option-1' 37653 * }; 37654 * 37655 * $scope.forceUnknownOption = function() { 37656 * $scope.data.singleSelect = 'nonsense'; 37657 * }; 37658 * }]); 37659 * </file> 37660 *</example> 37661 * 37662 * ### Using `ngRepeat` to generate `select` options 37663 * <example name="ngrepeat-select" module="ngrepeatSelect"> 37664 * <file name="index.html">
37665 * <div ng-controller="ExampleController"> 37666 * <form name="myForm"> 37667 * <label for="repeatSelect"> Repeat select: </label> 37668 * <select name="repeatSelect" id="repeatSelect" ng-model="data.repeatSelect"> 37669 * <option ng-repeat="option in data.availableOptions" value="{{option.id}}">{{option.name}}</option> 37670 * </select> 37671 * </form> 37672 * <hr> 37673 * <tt>repeatSelect = {{data.repeatSelect}}</tt><br/> 37674 * </div> 37675 * </file> 37676 * <file name="app.js"> 37677 * angular.module('ngrepeatSelect', []) 37678 * .controller('ExampleController', ['$scope', function($scope) { 37679 * $scope.data = { 37680 * repeatSelect: null, 37681 * availableOptions: [ 37682 * {id: '1', name: 'Option A'}, 37683 * {id: '2', name: 'Option B'}, 37684 * {id: '3', name: 'Option C'} 37685 * ] 37686 * }; 37687 * }]); 37688 * </file> 37689 *</example> 37690 * 37691 * 37692 * ### Using `select` with `ngOptions` and setting a default value 37693 * See the {@link ngOptions ngOptions documentation} for more `ngOptions` usage examples. 37694 * 37695 * <example name="select-with-default-values" module="defaultValueSelect"> 37696 * <file name="index.html">
37697 * <div ng-controller="ExampleController"> 37698 * <form name="myForm"> 37699 * <label for="mySelect">Make a choice:</label> 37700 * <select name="mySelect" id="mySelect" 37701 * ng-options="option.name for option in data.availableOptions track by option.id" 37702 * ng-model="data.selectedOption"></select> 37703 * </form> 37704 * <hr> 37705 * <tt>option = {{data.selectedOption}}</tt><br/> 37706 * </div> 37707 * </file> 37708 * <file name="app.js"> 37709 * angular.module('defaultValueSelect', []) 37710 * .controller('ExampleController', ['$scope', function($scope) { 37711 * $scope.data = { 37712 * availableOptions: [ 37713 * {id: '1', name: 'Option A'}, 37714 * {id: '2', name: 'Option B'}, 37715 * {id: '3', name: 'Option C'} 37716 * ], 37717 * selectedOption: {id: '3', name: 'Option C'} //This sets the default value of the select in the ui 37718 * }; 37719 * }]); 37720 * </file> 37721 *</example> 37722 * 37723 * 37724 * ### Binding `select` to a non-string value via `ngModel` parsing / formatting 37725 * 37726 * <example name="select-with-non-string-options" module="nonStringSelect"> 37727 * <file name="index.html"> 37728 * <select ng-model="model.id" convert-to-number> 37729 * <option value="0">Zero</option> 37730 * <option value="1">One</option> 37731 * <option value="2">Two</option> 37732 * </select> 37733 * {{ model }} 37734 * </file> 37735 * <file name="app.js"> 37736 * angular.module('nonStringSelect', []) 37737 * .run(function($rootScope) { 37738 * $rootScope.model = { id: 2 }; 37739 * }) 37740 * .directive('convertToNumber', function() { 37741 * return { 37742 * require: 'ngModel', 37743 * link: function(scope, element, attrs, ngModel) { 37744 * ngModel.$parsers.push(function(val) { 37745 * return parseInt(val, 10); 37746 * }); 37747 * ngModel.$formatters.push(function(val) { 37748 * return '' + val; 37749 * }); 37750 * } 37751 * }; 37752 * }); 37753 * </file> 37754 * <file name="protractor.js" type="protractor"> 37755 * it('should initialize to model', function() { 37756 * expect(element(by.model('model.id')).$('option:checked').getText()).toEqual('Two'); 37757 * }); 37758 * </file> 37759 * </example> 37760 * 37761 */ 37762var selectDirective = function() { 37763 37764 return { 37765 restrict: 'E', 37766 require: ['select', '?ngModel'], 37767 controller: SelectController, 37768 priority: 1, 37769 link: { 37770 pre: selectPreLink, 37771 post: selectPostLink 37772 } 37773 }; 37774 37775 function selectPreLink(scope, element, attr, ctrls) { 37776 37777 // if ngModel is not defined, we don't need to do anything 37778 var ngModelCtrl = ctrls[1]; 37779 if (!ngModelCtrl) return; 37780 37781 var selectCtrl = ctrls[0]; 37782 37783 selectCtrl.ngModelCtrl = ngModelCtrl; 37784 37785 // When the selected item(s) changes we delegate getting the value of the select control 37786 // to the `readValue` method, which can be changed if the select can have multiple 37787 // selected values or if the options are being generated by `ngOptions` 37788 element.on('change', function() { 37789 scope.$apply(function() { 37790 ngModelCtrl.$setViewValue(selectCtrl.readValue()); 37791 }); 37792 }); 37793 37794 // If the select allows multiple values then we need to modify how we read and write 37795 // values from and to the control; also what it means for the value to be empty and 37796 // we have to add an extra watch since ngModel doesn't work well with arrays - it 37797 // doesn't trigger rendering if only an item in the array changes. 37798 if (attr.multiple) { 37799 37800 // Read value now needs to check each option to see if it is selected 37801 selectCtrl.readValue = function readMultipleValue() { 37802 var array = []; 37803 forEach(element.find('option'), function(option) { 37804 if (option.selected) { 37805 array.push(option.value); 37806 } 37807 }); 37808 return array; 37809 }; 37810 37811 // Write value now needs to set the selected property of each matching option 37812 selectCtrl.writeValue = function writeMultipleValue(value) { 37813 var items = new HashMap(value); 37814 forEach(element.find('option'), function(option) { 37815 option.selected = isDefined(items.get(option.value)); 37816 }); 37817 }; 37818 37819 // we have to do it on each watch since ngModel watches reference, but 37820 // we need to work of an array, so we need to see if anything was inserted/removed 37821 var lastView, lastViewRef = NaN; 37822 scope.$watch(function selectMultipleWatch() { 37823 if (lastViewRef === ngModelCtrl.$viewValue && !equals(lastView, ngModelCtrl.$viewValue)) { 37824 lastView = shallowCopy(ngModelCtrl.$viewValue); 37825 ngModelCtrl.$render(); 37826 } 37827 lastViewRef = ngModelCtrl.$viewValue; 37828 }); 37829 37830 // If we are a multiple select then value is now a collection 37831 // so the meaning of $isEmpty changes 37832 ngModelCtrl.$isEmpty = function(value) { 37833 return !value || value.length === 0; 37834 }; 37835 37836 } 37837 } 37838 37839 function selectPostLink(scope, element, attrs, ctrls) { 37840 // if ngModel is not defined, we don't need to do anything 37841 var ngModelCtrl = ctrls[1]; 37842 if (!ngModelCtrl) return; 37843 37844 var selectCtrl = ctrls[0]; 37845 37846 // We delegate rendering to the `writeValue` method, which can be changed 37847 // if the select can have multiple selected values or if the options are being 37848 // generated by `ngOptions`. 37849 // This must be done in the postLink fn to prevent $render to be called before 37850 // all nodes have been linked correctly. 37851 ngModelCtrl.$render = function() { 37852 selectCtrl.writeValue(ngModelCtrl.$viewValue); 37853 }; 37854 } 37855}; 37856 37857 37858// The option directive is purely designed to communicate the existence (or lack of) 37859// of dynamically created (and destroyed) option elements to their containing select
vendor: 3,069 bytes, lines 37860-37930
37860// directive via its controller. 37861var optionDirective = ['$interpolate', function($interpolate) { 37862 return { 37863 restrict: 'E', 37864 priority: 100, 37865 compile: function(element, attr) { 37866 var hasDynamicValueAttr, interpolateTextFn; 37867 37868 if (isDefined(attr.ngValue)) { 37869 // If ngValue is defined, then the value attr will be set to the result of the expression, 37870 // and the selectCtrl must set up an observer 37871 hasDynamicValueAttr = true; 37872 } else if (isDefined(attr.value)) { 37873 // If the value attr contains an interpolation, the selectCtrl must set up an observer 37874 hasDynamicValueAttr = $interpolate(attr.value, true); 37875 } else { 37876 // If the value attribute is not defined then we fall back to the 37877 // text content of the option element, which may be interpolated 37878 interpolateTextFn = $interpolate(element.text(), true); 37879 if (!interpolateTextFn) { 37880 attr.$set('value', element.text()); 37881 } 37882 } 37883 37884 return function(scope, element, attr) { 37885 // This is an optimization over using ^^ since we don't want to have to search 37886 // all the way to the root of the DOM for every single option element 37887 var selectCtrlName = '$selectController', 37888 parent = element.parent(), 37889 selectCtrl = parent.data(selectCtrlName) || 37890 parent.parent().data(selectCtrlName); // in case we are in optgroup 37891 37892 if (selectCtrl) { 37893 selectCtrl.registerOption(scope, element, attr, hasDynamicValueAttr, interpolateTextFn); 37894 } 37895 }; 37896 } 37897 }; 37898}]; 37899 37900/** 37901 * @ngdoc directive 37902 * @name ngRequired 37903 * @restrict A 37904 * 37905 * @description 37906 * 37907 * ngRequired adds the required {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. 37908 * It is most often used for {@link input `input`} and {@link select `select`} controls, but can also be 37909 * applied to custom controls. 37910 * 37911 * The directive sets the `required` attribute on the element if the Angular expression inside 37912 * `ngRequired` evaluates to true. A special directive for setting `required` is necessary because we 37913 * cannot use interpolation inside `required`. See the {@link guide/interpolation interpolation guide} 37914 * for more info. 37915 * 37916 * The validator will set the `required` error key to true if the `required` attribute is set and 37917 * calling {@link ngModel.NgModelController#$isEmpty `NgModelController.$isEmpty`} with the 37918 * {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} returns `true`. For example, the 37919 * `$isEmpty()` implementation for `input[text]` checks the length of the `$viewValue`. When developing 37920 * custom controls, `$isEmpty()` can be overwritten to account for a $viewValue that is not string-based. 37921 * 37922 * @example 37923 * <example name="ngRequiredDirective" module="ngRequiredExample"> 37924 * <file name="index.html"> 37925 * <script> 37926 * angular.module('ngRequiredExample', []) 37927 * .controller('ExampleController', ['$scope', function($scope) { 37928 * $scope.required = true; 37929 * }]); 37930 * </script>
37931 * <div ng-controller="ExampleController"> 37932 * <form name="form"> 37933 * <label for="required">Toggle required: </label> 37934 * <input type="checkbox" ng-model="required" id="required" /> 37935 * <br> 37936 * <label for="input">This input must be filled if `required` is true: </label> 37937 * <input type="text" ng-model="model" id="input" name="input" ng-required="required" /><br> 37938 * <hr> 37939 * required error set? = <code>{{form.input.$error.required}}</code><br> 37940 * model = <code>{{model}}</code> 37941 * </form> 37942 * </div> 37943 * </file> 37944 * <file name="protractor.js" type="protractor"> 37945 var required = element(by.binding('form.input.$error.required')); 37946 var model = element(by.binding('model')); 37947 var input = element(by.id('input')); 37948 37949 it('should set the required error', function() { 37950 expect(required.getText()).toContain('true'); 37951 37952 input.sendKeys('123'); 37953 expect(required.getText()).not.toContain('true'); 37954 expect(model.getText()).toContain('123'); 37955 }); 37956 * </file> 37957 * </example> 37958 */ 37959var requiredDirective = function() { 37960 return { 37961 restrict: 'A', 37962 require: '?ngModel', 37963 link: function(scope, elm, attr, ctrl) { 37964 if (!ctrl) return; 37965 attr.required = true; // force truthy in case we are on non input element 37966 37967 ctrl.$validators.required = function(modelValue, viewValue) { 37968 return !attr.required || !ctrl.$isEmpty(viewValue); 37969 }; 37970 37971 attr.$observe('required', function() { 37972 ctrl.$validate(); 37973 }); 37974 } 37975 }; 37976}; 37977 37978/** 37979 * @ngdoc directive 37980 * @name ngPattern 37981 * 37982 * @description 37983 * 37984 * ngPattern adds the pattern {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. 37985 * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls. 37986 * 37987 * The validator sets the `pattern` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} 37988 * does not match a RegExp which is obtained by evaluating the Angular expression given in the 37989 * `ngPattern` attribute value: 37990 * * If the expression evaluates to a RegExp object, then this is used directly. 37991 * * If the expression evaluates to a string, then it will be converted to a RegExp after wrapping it 37992 * in `^` and `$` characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`. 37993 * 37994 * <div class="alert alert-info"> 37995 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 37996 * start at the index of the last search's match, thus not taking the whole input value into 37997 * account. 37998 * </div> 37999 * 38000 * <div class="alert alert-info"> 38001 * **Note:** This directive is also added when the plain `pattern` attribute is used, with two 38002 * differences: 38003 * <ol> 38004 * <li> 38005 * `ngPattern` does not set the `pattern` attribute and therefore HTML5 constraint validation is 38006 * not available. 38007 * </li> 38008 * <li> 38009 * The `ngPattern` attribute must be an expression, while the `pattern` value must be 38010 * interpolated. 38011 * </li> 38012 * </ol> 38013 * </div> 38014 * 38015 * @example 38016 * <example name="ngPatternDirective" module="ngPatternExample"> 38017 * <file name="index.html"> 38018 * <script> 38019 * angular.module('ngPatternExample', []) 38020 * .controller('ExampleController', ['$scope', function($scope) { 38021 * $scope.regex = '\\d+'; 38022 * }]); 38023 * </script>
38024 * <div ng-controller="ExampleController"> 38025 * <form name="form"> 38026 * <label for="regex">Set a pattern (regex string): </label> 38027 * <input type="text" ng-model="regex" id="regex" /> 38028 * <br> 38029 * <label for="input">This input is restricted by the current pattern: </label> 38030 * <input type="text" ng-model="model" id="input" name="input" ng-pattern="regex" /><br> 38031 * <hr> 38032 * input valid? = <code>{{form.input.$valid}}</code><br> 38033 * model = <code>{{model}}</code> 38034 * </form> 38035 * </div> 38036 * </file> 38037 * <file name="protractor.js" type="protractor"> 38038 var model = element(by.binding('model')); 38039 var input = element(by.id('input')); 38040 38041 it('should validate the input with the default pattern', function() { 38042 input.sendKeys('aaa'); 38043 expect(model.getText()).not.toContain('aaa'); 38044 38045 input.clear().then(function() { 38046 input.sendKeys('123'); 38047 expect(model.getText()).toContain('123'); 38048 }); 38049 }); 38050 * </file> 38051 * </example> 38052 */ 38053var patternDirective = function() { 38054 return { 38055 restrict: 'A', 38056 require: '?ngModel', 38057 link: function(scope, elm, attr, ctrl) { 38058 if (!ctrl) return; 38059 38060 var regexp, patternExp = attr.ngPattern || attr.pattern; 38061 attr.$observe('pattern', function(regex) { 38062 if (isString(regex) && regex.length > 0) { 38063 regex = new RegExp('^' + regex + '$'); 38064 } 38065 38066 if (regex && !regex.test) { 38067 throw minErr('ngPattern')('noregexp', 38068 'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp, 38069 regex, startingTag(elm)); 38070 } 38071 38072 regexp = regex || undefined; 38073 ctrl.$validate(); 38074 }); 38075 38076 ctrl.$validators.pattern = function(modelValue, viewValue) { 38077 // HTML5 pattern constraint validates the input value, so we validate the viewValue 38078 return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue); 38079 }; 38080 } 38081 }; 38082}; 38083 38084/** 38085 * @ngdoc directive 38086 * @name ngMaxlength 38087 * 38088 * @description 38089 * 38090 * ngMaxlength adds the maxlength {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. 38091 * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls. 38092 * 38093 * The validator sets the `maxlength` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} 38094 * is longer than the integer obtained by evaluating the Angular expression given in the 38095 * `ngMaxlength` attribute value. 38096 * 38097 * <div class="alert alert-info"> 38098 * **Note:** This directive is also added when the plain `maxlength` attribute is used, with two 38099 * differences: 38100 * <ol> 38101 * <li> 38102 * `ngMaxlength` does not set the `maxlength` attribute and therefore HTML5 constraint 38103 * validation is not available. 38104 * </li> 38105 * <li> 38106 * The `ngMaxlength` attribute must be an expression, while the `maxlength` value must be 38107 * interpolated. 38108 * </li> 38109 * </ol> 38110 * </div> 38111 * 38112 * @example 38113 * <example name="ngMaxlengthDirective" module="ngMaxlengthExample"> 38114 * <file name="index.html"> 38115 * <script> 38116 * angular.module('ngMaxlengthExample', []) 38117 * .controller('ExampleController', ['$scope', function($scope) { 38118 * $scope.maxlength = 5; 38119 * }]); 38120 * </script>
38121 * <div ng-controller="ExampleController"> 38122 * <form name="form"> 38123 * <label for="maxlength">Set a maxlength: </label> 38124 * <input type="number" ng-model="maxlength" id="maxlength" /> 38125 * <br> 38126 * <label for="input">This input is restricted by the current maxlength: </label> 38127 * <input type="text" ng-model="model" id="input" name="input" ng-maxlength="maxlength" /><br> 38128 * <hr> 38129 * input valid? = <code>{{form.input.$valid}}</code><br> 38130 * model = <code>{{model}}</code> 38131 * </form> 38132 * </div> 38133 * </file> 38134 * <file name="protractor.js" type="protractor"> 38135 var model = element(by.binding('model')); 38136 var input = element(by.id('input')); 38137 38138 it('should validate the input with the default maxlength', function() { 38139 input.sendKeys('abcdef'); 38140 expect(model.getText()).not.toContain('abcdef'); 38141 38142 input.clear().then(function() { 38143 input.sendKeys('abcde'); 38144 expect(model.getText()).toContain('abcde'); 38145 }); 38146 }); 38147 * </file> 38148 * </example> 38149 */ 38150var maxlengthDirective = function() { 38151 return { 38152 restrict: 'A', 38153 require: '?ngModel', 38154 link: function(scope, elm, attr, ctrl) { 38155 if (!ctrl) return; 38156 38157 var maxlength = -1; 38158 attr.$observe('maxlength', function(value) { 38159 var intVal = toInt(value); 38160 maxlength = isNumberNaN(intVal) ? -1 : intVal; 38161 ctrl.$validate(); 38162 }); 38163 ctrl.$validators.maxlength = function(modelValue, viewValue) { 38164 return (maxlength < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlength); 38165 }; 38166 } 38167 }; 38168}; 38169 38170/** 38171 * @ngdoc directive 38172 * @name ngMinlength 38173 * 38174 * @description 38175 * 38176 * ngMinlength adds the minlength {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. 38177 * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls. 38178 * 38179 * The validator sets the `minlength` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} 38180 * is shorter than the integer obtained by evaluating the Angular expression given in the 38181 * `ngMinlength` attribute value. 38182 * 38183 * <div class="alert alert-info"> 38184 * **Note:** This directive is also added when the plain `minlength` attribute is used, with two 38185 * differences: 38186 * <ol> 38187 * <li> 38188 * `ngMinlength` does not set the `minlength` attribute and therefore HTML5 constraint 38189 * validation is not available. 38190 * </li> 38191 * <li> 38192 * The `ngMinlength` value must be an expression, while the `minlength` value must be 38193 * interpolated. 38194 * </li> 38195 * </ol> 38196 * </div> 38197 * 38198 * @example 38199 * <example name="ngMinlengthDirective" module="ngMinlengthExample"> 38200 * <file name="index.html"> 38201 * <script> 38202 * angular.module('ngMinlengthExample', []) 38203 * .controller('ExampleController', ['$scope', function($scope) { 38204 * $scope.minlength = 3; 38205 * }]); 38206 * </script>
38207 * <div ng-controller="ExampleController"> 38208 * <form name="form"> 38209 * <label for="minlength">Set a minlength: </label> 38210 * <input type="number" ng-model="minlength" id="minlength" /> 38211 * <br> 38212 * <label for="input">This input is restricted by the current minlength: </label> 38213 * <input type="text" ng-model="model" id="input" name="input" ng-minlength="minlength" /><br> 38214 * <hr> 38215 * input valid? = <code>{{form.input.$valid}}</code><br> 38216 * model = <code>{{model}}</code> 38217 * </form> 38218 * </div> 38219 * </file> 38220 * <file name="protractor.js" type="protractor"> 38221 var model = element(by.binding('model')); 38222 var input = element(by.id('input')); 38223 38224 it('should validate the input with the default minlength', function() { 38225 input.sendKeys('ab'); 38226 expect(model.getText()).not.toContain('ab'); 38227 38228 input.sendKeys('abc'); 38229 expect(model.getText()).toContain('abc'); 38230 }); 38231 * </file> 38232 * </example> 38233 */ 38234var minlengthDirective = function() { 38235 return { 38236 restrict: 'A', 38237 require: '?ngModel', 38238 link: function(scope, elm, attr, ctrl) { 38239 if (!ctrl) return; 38240 38241 var minlength = 0; 38242 attr.$observe('minlength', function(value) { 38243 minlength = toInt(value) || 0; 38244 ctrl.$validate(); 38245 }); 38246 ctrl.$validators.minlength = function(modelValue, viewValue) { 38247 return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength; 38248 }; 38249 } 38250 }; 38251}; 38252 38253if (window.angular.bootstrap) { 38254 //AngularJS is already loaded, so we can return here... 38255 if (window.console) { 38256 console.log('WARNING: Tried to load angular more than once.'); 38257 } 38258 return; 38259} 38260 38261//try to bind to jquery now so that one can write jqLite(document).ready() 38262//but we will rebind on bootstrap again. 38263bindJQuery(); 38264 38265publishExternalAPI(angular); 38266 38267angular.module("ngLocale", [], ["$provide", function($provide) { 38268var PLURAL_CATEGORY = {ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"}; 38269function getDecimals(n) { 38270 n = n + ''; 38271 var i = n.indexOf('.'); 38272 return (i == -1) ? 0 : n.length - i - 1; 38273} 38274 38275function getVF(n, opt_precision) { 38276 var v = opt_precision; 38277 38278 if (undefined === v) { 38279 v = Math.min(getDecimals(n), 3); 38280 } 38281 38282 var base = Math.pow(10, v); 38283 var f = ((n * base) | 0) % base; 38284 return {v: v, f: f}; 38285} 38286 38287$provide.value("$locale", { 38288 "DATETIME_FORMATS": { 38289 "AMPMS": [ 38290 "AM", 38291 "PM" 38292 ], 38293 "DAY": [ 38294 "Sunday", 38295 "Monday", 38296 "Tuesday", 38297 "Wednesday", 38298 "Thursday", 38299 "Friday", 38300 "Saturday" 38301 ], 38302 "ERANAMES": [ 38303 "Before Christ", 38304 "Anno Domini" 38305 ], 38306 "ERAS": [ 38307 "BC", 38308 "AD" 38309 ], 38310 "FIRSTDAYOFWEEK": 6, 38311 "MONTH": [ 38312 "January", 38313 "February", 38314 "March", 38315 "April", 38316 "May", 38317 "June", 38318 "July", 38319 "August", 38320 "September", 38321 "October", 38322 "November", 38323 "December" 38324 ], 38325 "SHORTDAY": [ 38326 "Sun", 38327 "Mon", 38328 "Tue", 38329 "Wed", 38330 "Thu", 38331 "Fri", 38332 "Sat" 38333 ], 38334 "SHORTMONTH": [ 38335 "Jan", 38336 "Feb", 38337 "Mar", 38338 "Apr", 38339 "May", 38340 "Jun", 38341 "Jul", 38342 "Aug", 38343 "Sep", 38344 "Oct", 38345 "Nov", 38346 "Dec" 38347 ], 38348 "STANDALONEMONTH": [ 38349 "January", 38350 "February", 38351 "March", 38352 "April", 38353 "May", 38354 "June", 38355 "July", 38356 "August", 38357 "September", 38358 "October", 38359 "November", 38360 "December" 38361 ], 38362 "WEEKENDRANGE": [ 38363 5, 38364 6 38365 ], 38366 "fullDate": "EEEE, MMMM d, y", 38367 "longDate": "MMMM d, y", 38368 "medium": "MMM d, y h:mm:ss a", 38369 "mediumDate": "MMM d, y", 38370 "mediumTime": "h:mm:ss a", 38371 "short": "M/d/yy h:mm a", 38372 "shortDate": "M/d/yy", 38373 "shortTime": "h:mm a" 38374 }, 38375 "NUMBER_FORMATS": { 38376 "CURRENCY_SYM": "$", 38377 "DECIMAL_SEP": ".", 38378 "GROUP_SEP": ",", 38379 "PATTERNS": [ 38380 { 38381 "gSize": 3, 38382 "lgSize": 3, 38383 "maxFrac": 3, 38384 "minFrac": 0, 38385 "minInt": 1, 38386 "negPre": "-", 38387 "negSuf": "", 38388 "posPre": "", 38389 "posSuf": "" 38390 }, 38391 { 38392 "gSize": 3, 38393 "lgSize": 3, 38394 "maxFrac": 2, 38395 "minFrac": 2, 38396 "minInt": 1, 38397 "negPre": "-\u00a4", 38398 "negSuf": "", 38399 "posPre": "\u00a4", 38400 "posSuf": "" 38401 } 38402 ] 38403 }, 38404 "id": "en-us", 38405 "localeID": "en_US", 38406 "pluralCat": function(n, opt_precision) { var i = n | 0; var vf = getVF(n, opt_precision); if (i == 1 && vf.v == 0) { return PLURAL_CATEGORY.ONE; } return PLURAL_CATEGORY.OTHER;} 38407}); 38408}]); 38409 38410 jqLite(window.document).ready(function() { 38411 angularInit(window.document, bootstrap); 38412 }); 38413 38414})(window); 38415 38416!window.angular.$$csp().noInlineStyle && window.angular.element(document.head).prepend('<style type="text/css">@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;}</style>'); 38417/** 38418 * @license AngularJS v1.5.11 38419 * (c) 2010-2017 Google, Inc. http://angularjs.org 38420 * License: MIT 38421 */ 38422(function(window, angular) {'use strict'; 38423 38424var ELEMENT_NODE = 1; 38425var COMMENT_NODE = 8; 38426 38427var ADD_CLASS_SUFFIX = '-add'; 38428var REMOVE_CLASS_SUFFIX = '-remove'; 38429var EVENT_CLASS_PREFIX = 'ng-'; 38430var ACTIVE_CLASS_SUFFIX = '-active'; 38431var PREPARE_CLASS_SUFFIX = '-prepare'; 38432 38433var NG_ANIMATE_CLASSNAME = 'ng-animate'; 38434var NG_ANIMATE_CHILDREN_DATA = '$$ngAnimateChildren'; 38435 38436// Detect proper transitionend/animationend event names. 38437var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMATIONEND_EVENT; 38438 38439// If unprefixed events are not supported but webkit-prefixed are, use the latter.
vendor: 13,270 bytes, lines 38440-38902
38440// Otherwise, just use W3C names, browsers not supporting them at all will just ignore them. 38441// Note: Chrome implements `window.onwebkitanimationend` and doesn't implement `window.onanimationend` 38442// but at the same time dispatches the `animationend` event and not `webkitAnimationEnd`. 38443// Register both events in case `window.onanimationend` is not supported because of that, 38444// do the same for `transitionend` as Safari is likely to exhibit similar behavior. 38445// Also, the only modern browser that uses vendor prefixes for transitions/keyframes is webkit 38446// therefore there is no reason to test anymore for other vendor prefixes: 38447// http://caniuse.com/#search=transition 38448if ((window.ontransitionend === undefined) && (window.onwebkittransitionend !== undefined)) { 38449 CSS_PREFIX = '-webkit-'; 38450 TRANSITION_PROP = 'WebkitTransition'; 38451 TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend'; 38452} else { 38453 TRANSITION_PROP = 'transition'; 38454 TRANSITIONEND_EVENT = 'transitionend'; 38455} 38456 38457if ((window.onanimationend === undefined) && (window.onwebkitanimationend !== undefined)) { 38458 CSS_PREFIX = '-webkit-'; 38459 ANIMATION_PROP = 'WebkitAnimation'; 38460 ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend'; 38461} else { 38462 ANIMATION_PROP = 'animation'; 38463 ANIMATIONEND_EVENT = 'animationend'; 38464} 38465 38466var DURATION_KEY = 'Duration'; 38467var PROPERTY_KEY = 'Property'; 38468var DELAY_KEY = 'Delay'; 38469var TIMING_KEY = 'TimingFunction'; 38470var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount'; 38471var ANIMATION_PLAYSTATE_KEY = 'PlayState'; 38472var SAFE_FAST_FORWARD_DURATION_VALUE = 9999; 38473 38474var ANIMATION_DELAY_PROP = ANIMATION_PROP + DELAY_KEY; 38475var ANIMATION_DURATION_PROP = ANIMATION_PROP + DURATION_KEY; 38476var TRANSITION_DELAY_PROP = TRANSITION_PROP + DELAY_KEY; 38477var TRANSITION_DURATION_PROP = TRANSITION_PROP + DURATION_KEY; 38478 38479var ngMinErr = angular.$$minErr('ng'); 38480function assertArg(arg, name, reason) { 38481 if (!arg) { 38482 throw ngMinErr('areq', 'Argument \'{0}\' is {1}', (name || '?'), (reason || 'required')); 38483 } 38484 return arg; 38485} 38486 38487function mergeClasses(a,b) { 38488 if (!a && !b) return ''; 38489 if (!a) return b; 38490 if (!b) return a; 38491 if (isArray(a)) a = a.join(' '); 38492 if (isArray(b)) b = b.join(' '); 38493 return a + ' ' + b; 38494} 38495 38496function packageStyles(options) { 38497 var styles = {}; 38498 if (options && (options.to || options.from)) { 38499 styles.to = options.to; 38500 styles.from = options.from; 38501 } 38502 return styles; 38503} 38504 38505function pendClasses(classes, fix, isPrefix) { 38506 var className = ''; 38507 classes = isArray(classes) 38508 ? classes 38509 : classes && isString(classes) && classes.length 38510 ? classes.split(/\s+/) 38511 : []; 38512 forEach(classes, function(klass, i) { 38513 if (klass && klass.length > 0) { 38514 className += (i > 0) ? ' ' : ''; 38515 className += isPrefix ? fix + klass 38516 : klass + fix; 38517 } 38518 }); 38519 return className; 38520} 38521 38522function removeFromArray(arr, val) { 38523 var index = arr.indexOf(val); 38524 if (val >= 0) { 38525 arr.splice(index, 1); 38526 } 38527} 38528 38529function stripCommentsFromElement(element) { 38530 if (element instanceof jqLite) { 38531 switch (element.length) { 38532 case 0: 38533 return element; 38534 38535 case 1: 38536 // there is no point of stripping anything if the element 38537 // is the only element within the jqLite wrapper. 38538 // (it's important that we retain the element instance.) 38539 if (element[0].nodeType === ELEMENT_NODE) { 38540 return element; 38541 } 38542 break; 38543 38544 default: 38545 return jqLite(extractElementNode(element)); 38546 } 38547 } 38548 38549 if (element.nodeType === ELEMENT_NODE) { 38550 return jqLite(element); 38551 } 38552} 38553 38554function extractElementNode(element) { 38555 if (!element[0]) return element; 38556 for (var i = 0; i < element.length; i++) { 38557 var elm = element[i]; 38558 if (elm.nodeType === ELEMENT_NODE) { 38559 return elm; 38560 } 38561 } 38562} 38563 38564function $$addClass($$jqLite, element, className) { 38565 forEach(element, function(elm) { 38566 $$jqLite.addClass(elm, className); 38567 }); 38568} 38569 38570function $$removeClass($$jqLite, element, className) { 38571 forEach(element, function(elm) { 38572 $$jqLite.removeClass(elm, className); 38573 }); 38574} 38575 38576function applyAnimationClassesFactory($$jqLite) { 38577 return function(element, options) { 38578 if (options.addClass) { 38579 $$addClass($$jqLite, element, options.addClass); 38580 options.addClass = null; 38581 } 38582 if (options.removeClass) { 38583 $$removeClass($$jqLite, element, options.removeClass); 38584 options.removeClass = null; 38585 } 38586 }; 38587} 38588 38589function prepareAnimationOptions(options) { 38590 options = options || {}; 38591 if (!options.$$prepared) { 38592 var domOperation = options.domOperation || noop; 38593 options.domOperation = function() { 38594 options.$$domOperationFired = true; 38595 domOperation(); 38596 domOperation = noop; 38597 }; 38598 options.$$prepared = true; 38599 } 38600 return options; 38601} 38602 38603function applyAnimationStyles(element, options) { 38604 applyAnimationFromStyles(element, options); 38605 applyAnimationToStyles(element, options); 38606} 38607 38608function applyAnimationFromStyles(element, options) { 38609 if (options.from) { 38610 element.css(options.from); 38611 options.from = null; 38612 } 38613} 38614 38615function applyAnimationToStyles(element, options) { 38616 if (options.to) { 38617 element.css(options.to); 38618 options.to = null; 38619 } 38620} 38621 38622function mergeAnimationDetails(element, oldAnimation, newAnimation) { 38623 var target = oldAnimation.options || {}; 38624 var newOptions = newAnimation.options || {}; 38625 38626 var toAdd = (target.addClass || '') + ' ' + (newOptions.addClass || ''); 38627 var toRemove = (target.removeClass || '') + ' ' + (newOptions.removeClass || ''); 38628 var classes = resolveElementClasses(element.attr('class'), toAdd, toRemove); 38629 38630 if (newOptions.preparationClasses) { 38631 target.preparationClasses = concatWithSpace(newOptions.preparationClasses, target.preparationClasses); 38632 delete newOptions.preparationClasses; 38633 } 38634 38635 // noop is basically when there is no callback; otherwise something has been set 38636 var realDomOperation = target.domOperation !== noop ? target.domOperation : null; 38637 38638 extend(target, newOptions); 38639 38640 // TODO(matsko or sreeramu): proper fix is to maintain all animation callback in array and call at last,but now only leave has the callback so no issue with this. 38641 if (realDomOperation) { 38642 target.domOperation = realDomOperation; 38643 } 38644 38645 if (classes.addClass) { 38646 target.addClass = classes.addClass; 38647 } else { 38648 target.addClass = null; 38649 } 38650 38651 if (classes.removeClass) { 38652 target.removeClass = classes.removeClass; 38653 } else { 38654 target.removeClass = null; 38655 } 38656 38657 oldAnimation.addClass = target.addClass; 38658 oldAnimation.removeClass = target.removeClass; 38659 38660 return target; 38661} 38662 38663function resolveElementClasses(existing, toAdd, toRemove) { 38664 var ADD_CLASS = 1; 38665 var REMOVE_CLASS = -1; 38666 38667 var flags = {}; 38668 existing = splitClassesToLookup(existing); 38669 38670 toAdd = splitClassesToLookup(toAdd); 38671 forEach(toAdd, function(value, key) { 38672 flags[key] = ADD_CLASS; 38673 }); 38674 38675 toRemove = splitClassesToLookup(toRemove); 38676 forEach(toRemove, function(value, key) { 38677 flags[key] = flags[key] === ADD_CLASS ? null : REMOVE_CLASS; 38678 }); 38679 38680 var classes = { 38681 addClass: '', 38682 removeClass: '' 38683 }; 38684 38685 forEach(flags, function(val, klass) { 38686 var prop, allow; 38687 if (val === ADD_CLASS) { 38688 prop = 'addClass'; 38689 allow = !existing[klass] || existing[klass + REMOVE_CLASS_SUFFIX]; 38690 } else if (val === REMOVE_CLASS) { 38691 prop = 'removeClass'; 38692 allow = existing[klass] || existing[klass + ADD_CLASS_SUFFIX]; 38693 } 38694 if (allow) { 38695 if (classes[prop].length) { 38696 classes[prop] += ' '; 38697 } 38698 classes[prop] += klass; 38699 } 38700 }); 38701 38702 function splitClassesToLookup(classes) { 38703 if (isString(classes)) { 38704 classes = classes.split(' '); 38705 } 38706 38707 var obj = {}; 38708 forEach(classes, function(klass) { 38709 // sometimes the split leaves empty string values 38710 // incase extra spaces were applied to the options 38711 if (klass.length) { 38712 obj[klass] = true; 38713 } 38714 }); 38715 return obj; 38716 } 38717 38718 return classes; 38719} 38720 38721function getDomNode(element) { 38722 return (element instanceof jqLite) ? element[0] : element; 38723} 38724 38725function applyGeneratedPreparationClasses(element, event, options) { 38726 var classes = ''; 38727 if (event) { 38728 classes = pendClasses(event, EVENT_CLASS_PREFIX, true); 38729 } 38730 if (options.addClass) { 38731 classes = concatWithSpace(classes, pendClasses(options.addClass, ADD_CLASS_SUFFIX)); 38732 } 38733 if (options.removeClass) { 38734 classes = concatWithSpace(classes, pendClasses(options.removeClass, REMOVE_CLASS_SUFFIX)); 38735 } 38736 if (classes.length) { 38737 options.preparationClasses = classes; 38738 element.addClass(classes); 38739 } 38740} 38741 38742function clearGeneratedClasses(element, options) { 38743 if (options.preparationClasses) { 38744 element.removeClass(options.preparationClasses); 38745 options.preparationClasses = null; 38746 } 38747 if (options.activeClasses) { 38748 element.removeClass(options.activeClasses); 38749 options.activeClasses = null; 38750 } 38751} 38752 38753function blockTransitions(node, duration) { 38754 // we use a negative delay value since it performs blocking 38755 // yet it doesn't kill any existing transitions running on the 38756 // same element which makes this safe for class-based animations 38757 var value = duration ? '-' + duration + 's' : ''; 38758 applyInlineStyle(node, [TRANSITION_DELAY_PROP, value]); 38759 return [TRANSITION_DELAY_PROP, value]; 38760} 38761 38762function blockKeyframeAnimations(node, applyBlock) { 38763 var value = applyBlock ? 'paused' : ''; 38764 var key = ANIMATION_PROP + ANIMATION_PLAYSTATE_KEY; 38765 applyInlineStyle(node, [key, value]); 38766 return [key, value]; 38767} 38768 38769function applyInlineStyle(node, styleTuple) { 38770 var prop = styleTuple[0]; 38771 var value = styleTuple[1]; 38772 node.style[prop] = value; 38773} 38774 38775function concatWithSpace(a,b) { 38776 if (!a) return b; 38777 if (!b) return a; 38778 return a + ' ' + b; 38779} 38780 38781var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) { 38782 var queue, cancelFn; 38783 38784 function scheduler(tasks) { 38785 // we make a copy since RAFScheduler mutates the state 38786 // of the passed in array variable and this would be difficult 38787 // to track down on the outside code 38788 queue = queue.concat(tasks); 38789 nextTick(); 38790 } 38791 38792 queue = scheduler.queue = []; 38793 38794 /* waitUntilQuiet does two things: 38795 * 1. It will run the FINAL `fn` value only when an uncanceled RAF has passed through 38796 * 2. It will delay the next wave of tasks from running until the quiet `fn` has run. 38797 * 38798 * The motivation here is that animation code can request more time from the scheduler 38799 * before the next wave runs. This allows for certain DOM properties such as classes to 38800 * be resolved in time for the next animation to run. 38801 */ 38802 scheduler.waitUntilQuiet = function(fn) { 38803 if (cancelFn) cancelFn(); 38804 38805 cancelFn = $$rAF(function() { 38806 cancelFn = null; 38807 fn(); 38808 nextTick(); 38809 }); 38810 }; 38811 38812 return scheduler; 38813 38814 function nextTick() { 38815 if (!queue.length) return; 38816 38817 var items = queue.shift(); 38818 for (var i = 0; i < items.length; i++) { 38819 items[i](); 38820 } 38821 38822 if (!cancelFn) { 38823 $$rAF(function() { 38824 if (!cancelFn) nextTick(); 38825 }); 38826 } 38827 } 38828}]; 38829 38830/** 38831 * @ngdoc directive 38832 * @name ngAnimateChildren 38833 * @restrict AE 38834 * @element ANY 38835 * 38836 * @description 38837 * 38838 * ngAnimateChildren allows you to specify that children of this element should animate even if any 38839 * of the children's parents are currently animating. By default, when an element has an active `enter`, `leave`, or `move` 38840 * (structural) animation, child elements that also have an active structural animation are not animated. 38841 * 38842 * Note that even if `ngAnimateChildren` is set, no child animations will run when the parent element is removed from the DOM (`leave` animation). 38843 * 38844 * 38845 * @param {string} ngAnimateChildren If the value is empty, `true` or `on`, 38846 * then child animations are allowed. If the value is `false`, child animations are not allowed. 38847 * 38848 * @example 38849 * <example module="ngAnimateChildren" name="ngAnimateChildren" deps="angular-animate.js" animations="true"> 38850 <file name="index.html"> 38851 <div ng-controller="MainController as main"> 38852 <label>Show container? <input type="checkbox" ng-model="main.enterElement" /></label> 38853 <label>Animate children? <input type="checkbox" ng-model="main.animateChildren" /></label> 38854 <hr> 38855 <div ng-animate-children="{{main.animateChildren}}"> 38856 <div ng-if="main.enterElement" class="container"> 38857 List of items: 38858 <div ng-repeat="item in [0, 1, 2, 3]" class="item">Item {{item}}</div> 38859 </div> 38860 </div> 38861 </div> 38862 </file> 38863 <file name="animations.css"> 38864 38865 .container.ng-enter, 38866 .container.ng-leave { 38867 transition: all ease 1.5s; 38868 } 38869 38870 .container.ng-enter, 38871 .container.ng-leave-active { 38872 opacity: 0; 38873 } 38874 38875 .container.ng-leave, 38876 .container.ng-enter-active { 38877 opacity: 1; 38878 } 38879 38880 .item { 38881 background: firebrick; 38882 color: #FFF; 38883 margin-bottom: 10px; 38884 } 38885 38886 .item.ng-enter, 38887 .item.ng-leave { 38888 transition: transform 1.5s ease; 38889 } 38890 38891 .item.ng-enter { 38892 transform: translateX(50px); 38893 } 38894 38895 .item.ng-enter-active { 38896 transform: translateX(0); 38897 } 38898 </file> 38899 <file name="script.js"> 38900 angular.module('ngAnimateChildren', ['ngAnimate']) 38901 .controller('MainController', function MainController() { 38902 this.animateChildren = false;
38903 this.enterElement = false; 38904 }); 38905 </file> 38906 </example> 38907 */ 38908var $$AnimateChildrenDirective = ['$interpolate', function($interpolate) { 38909 return { 38910 link: function(scope, element, attrs) { 38911 var val = attrs.ngAnimateChildren; 38912 if (isString(val) && val.length === 0) { //empty attribute 38913 element.data(NG_ANIMATE_CHILDREN_DATA, true); 38914 } else { 38915 // Interpolate and set the value, so that it is available to 38916 // animations that run right after compilation 38917 setData($interpolate(val)(scope)); 38918 attrs.$observe('ngAnimateChildren', setData); 38919 } 38920 38921 function setData(value) { 38922 value = value === 'on' || value === 'true'; 38923 element.data(NG_ANIMATE_CHILDREN_DATA, value); 38924 } 38925 } 38926 }; 38927}]; 38928 38929/* exported $AnimateCssProvider */ 38930 38931var ANIMATE_TIMER_KEY = '$$animateCss'; 38932 38933/** 38934 * @ngdoc service 38935 * @name $animateCss 38936 * @kind object 38937 * 38938 * @description 38939 * The `$animateCss` service is a useful utility to trigger customized CSS-based transitions/keyframes 38940 * from a JavaScript-based animation or directly from a directive. The purpose of `$animateCss` is NOT 38941 * to side-step how `$animate` and ngAnimate work, but the goal is to allow pre-existing animations or 38942 * directives to create more complex animations that can be purely driven using CSS code. 38943 * 38944 * Note that only browsers that support CSS transitions and/or keyframe animations are capable of 38945 * rendering animations triggered via `$animateCss` (bad news for IE9 and lower). 38946 * 38947 * ## Usage 38948 * Once again, `$animateCss` is designed to be used inside of a registered JavaScript animation that 38949 * is powered by ngAnimate. It is possible to use `$animateCss` directly inside of a directive, however, 38950 * any automatic control over cancelling animations and/or preventing animations from being run on 38951 * child elements will not be handled by Angular
vendor: 13,421 bytes, lines 38951-39232
38951. For this to work as expected, please use `$animate` to 38952 * trigger the animation and then setup a JavaScript animation that injects `$animateCss` to trigger 38953 * the CSS animation. 38954 * 38955 * The example below shows how we can create a folding animation on an element using `ng-if`: 38956 * 38957 * ```html 38958 * <!-- notice the `fold-animation` CSS class --> 38959 * <div ng-if="onOff" class="fold-animation"> 38960 * This element will go BOOM 38961 * </div> 38962 * <button ng-click="onOff=true">Fold In</button> 38963 * ``` 38964 * 38965 * Now we create the **JavaScript animation** that will trigger the CSS transition: 38966 * 38967 * ```js 38968 * ngModule.animation('.fold-animation', ['$animateCss', function($animateCss) { 38969 * return { 38970 * enter: function(element, doneFn) { 38971 * var height = element[0].offsetHeight; 38972 * return $animateCss(element, { 38973 * from: { height:'0px' }, 38974 * to: { height:height + 'px' }, 38975 * duration: 1 // one second 38976 * }); 38977 * } 38978 * } 38979 * }]); 38980 * ``` 38981 * 38982 * ## More Advanced Uses 38983 * 38984 * `$animateCss` is the underlying code that ngAnimate uses to power **CSS-based animations** behind the scenes. Therefore CSS hooks 38985 * like `.ng-EVENT`, `.ng-EVENT-active`, `.ng-EVENT-stagger` are all features that can be triggered using `$animateCss` via JavaScript code. 38986 * 38987 * This also means that just about any combination of adding classes, removing classes, setting styles, dynamically setting a keyframe animation, 38988 * applying a hardcoded duration or delay value, changing the animation easing or applying a stagger animation are all options that work with 38989 * `$animateCss`. The service itself is smart enough to figure out the combination of options and examine the element styling properties in order 38990 * to provide a working animation that will run in CSS. 38991 * 38992 * The example below showcases a more advanced version of the `.fold-animation` from the example above: 38993 * 38994 * ```js 38995 * ngModule.animation('.fold-animation', ['$animateCss', function($animateCss) { 38996 * return { 38997 * enter: function(element, doneFn) { 38998 * var height = element[0].offsetHeight; 38999 * return $animateCss(element, { 39000 * addClass: 'red large-text pulse-twice', 39001 * easing: 'ease-out', 39002 * from: { height:'0px' }, 39003 * to: { height:height + 'px' }, 39004 * duration: 1 // one second 39005 * }); 39006 * } 39007 * } 39008 * }]); 39009 * ``` 39010 * 39011 * Since we're adding/removing CSS classes then the CSS transition will also pick those up: 39012 * 39013 * ```css 39014 * /* since a hardcoded duration value of 1 was provided in the JavaScript animation code, 39015 * the CSS classes below will be transitioned despite them being defined as regular CSS classes */ 39016 * .red { background:red; } 39017 * .large-text { font-size:20px; } 39018 * 39019 * /* we can also use a keyframe animation and $animateCss will make it work alongside the transition */ 39020 * .pulse-twice { 39021 * animation: 0.5s pulse linear 2; 39022 * -webkit-animation: 0.5s pulse linear 2; 39023 * } 39024 * 39025 * @keyframes pulse { 39026 * from { transform: scale(0.5); } 39027 * to { transform: scale(1.5); } 39028 * } 39029 * 39030 * @-webkit-keyframes pulse { 39031 * from { -webkit-transform: scale(0.5); } 39032 * to { -webkit-transform: scale(1.5); } 39033 * } 39034 * ``` 39035 * 39036 * Given this complex combination of CSS classes, styles and options, `$animateCss` will figure everything out and make the animation happen. 39037 * 39038 * ## How the Options are handled 39039 * 39040 * `$animateCss` is very versatile and intelligent when it comes to figuring out what configurations to apply to the element to ensure the animation 39041 * works with the options provided. Say for example we were adding a class that contained a keyframe value and we wanted to also animate some inline 39042 * styles using the `from` and `to` properties. 39043 * 39044 * ```js 39045 * var animator = $animateCss(element, { 39046 * from: { background:'red' }, 39047 * to: { background:'blue' } 39048 * }); 39049 * animator.start(); 39050 * ``` 39051 * 39052 * ```css 39053 * .rotating-animation { 39054 * animation:0.5s rotate linear; 39055 * -webkit-animation:0.5s rotate linear; 39056 * } 39057 * 39058 * @keyframes rotate { 39059 * from { transform: rotate(0deg); } 39060 * to { transform: rotate(360deg); } 39061 * } 39062 * 39063 * @-webkit-keyframes rotate { 39064 * from { -webkit-transform: rotate(0deg); } 39065 * to { -webkit-transform: rotate(360deg); } 39066 * } 39067 * ``` 39068 * 39069 * The missing pieces here are that we do not have a transition set (within the CSS code nor within the `$animateCss` options) and the duration of the animation is 39070 * going to be detected from what the keyframe styles on the CSS class are. In this event, `$animateCss` will automatically create an inline transition 39071 * style matching the duration detected from the keyframe style (which is present in the CSS class that is being added) and then prepare both the transition 39072 * and keyframe animations to run in parallel on the element. Then when the animation is underway the provided `from` and `to` CSS styles will be applied 39073 * and spread across the transition and keyframe animation. 39074 * 39075 * ## What is returned 39076 * 39077 * `$animateCss` works in two stages: a preparation phase and an animation phase. Therefore when `$animateCss` is first called it will NOT actually 39078 * start the animation. All that is going on here is that the element is being prepared for the animation (which means that the generated CSS classes are 39079 * added and removed on the element). Once `$animateCss` is called it will return an object with the following properties: 39080 * 39081 * ```js 39082 * var animator = $animateCss(element, { ... }); 39083 * ``` 39084 * 39085 * Now what do the contents of our `animator` variable look like: 39086 * 39087 * ```js 39088 * { 39089 * // starts the animation 39090 * start: Function, 39091 * 39092 * // ends (aborts) the animation 39093 * end: Function 39094 * } 39095 * ``` 39096 * 39097 * To actually start the animation we need to run `animation.start()` which will then return a promise that we can hook into to detect when the animation ends. 39098 * If we choose not to run the animation then we MUST run `animation.end()` to perform a cleanup on the element (since some CSS classes and styles may have been 39099 * applied to the element during the preparation phase). Note that all other properties such as duration, delay, transitions and keyframes are just properties 39100 * and that changing them will not reconfigure the parameters of the animation. 39101 * 39102 * ### runner.done() vs runner.then() 39103 * It is documented that `animation.start()` will return a promise object and this is true, however, there is also an additional method available on the 39104 * runner called `.done(callbackFn)`. The done method works the same as `.finally(callbackFn)`, however, it does **not trigger a digest to occur**. 39105 * Therefore, for performance reasons, it's always best to use `runner.done(callback)` instead of `runner.then()`, `runner.catch()` or `runner.finally()` 39106 * unless you really need a digest to kick off afterwards. 39107 * 39108 * Keep in mind that, to make this easier, ngAnimate has tweaked the JS animations API to recognize when a runner instance is returned from $animateCss 39109 * (so there is no need to call `runner.done(doneFn)` inside of your JavaScript animation code). 39110 * Check the {@link ngAnimate.$animateCss#usage animation code above} to see how this works. 39111 * 39112 * @param {DOMElement} element the element that will be animated 39113 * @param {object} options the animation-related options that will be applied during the animation 39114 * 39115 * * `event` - The DOM event (e.g. enter, leave, move). When used, a generated CSS class of `ng-EVENT` and `ng-EVENT-active` will be applied 39116 * to the element during the animation. Multiple events can be provided when spaces are used as a separator. (Note that this will not perform any DOM operation.) 39117 * * `structural` - Indicates that the `ng-` prefix will be added to the event class. Setting to `false` or omitting will turn `ng-EVENT` and 39118 * `ng-EVENT-active` in `EVENT` and `EVENT-active`. Unused if `event` is omitted. 39119 * * `easing` - The CSS easing value that will be applied to the transition or keyframe animation (or both). 39120 * * `transitionStyle` - The raw CSS transition style that will be used (e.g. `1s linear all`). 39121 * * `keyframeStyle` - The raw CSS keyframe animation style that will be used (e.g. `1s my_animation linear`). 39122 * * `from` - The starting CSS styles (a key/value object) that will be applied at the start of the animation. 39123 * * `to` - The ending CSS styles (a key/value object) that will be applied across the animation via a CSS transition. 39124 * * `addClass` - A space separated list of CSS classes that will be added to the element and spread across the animation. 39125 * * `removeClass` - A space separated list of CSS classes that will be removed from the element and spread across the animation. 39126 * * `duration` - A number value representing the total duration of the transition and/or keyframe (note that a value of 1 is 1000ms). If a value of `0` 39127 * is provided then the animation will be skipped entirely. 39128 * * `delay` - A number value representing the total delay of the transition and/or keyframe (note that a value of 1 is 1000ms). If a value of `true` is 39129 * used then whatever delay value is detected from the CSS classes will be mirrored on the elements styles (e.g. by setting delay true then the style value 39130 * of the element will be `transition-delay: DETECTED_VALUE`). Using `true` is useful when you want the CSS classes and inline styles to all share the same 39131 * CSS delay value. 39132 * * `stagger` - A numeric time value representing the delay between successively animated elements 39133 * ({@link ngAnimate#css-staggering-animations Click here to learn how CSS-based staggering works in ngAnimate.}) 39134 * * `staggerIndex` - The numeric index representing the stagger item (e.g. a value of 5 is equal to the sixth item in the stagger; therefore when a 39135 * `stagger` option value of `0.1` is used then there will be a stagger delay of `600ms`) 39136 * * `applyClassesEarly` - Whether or not the classes being added or removed will be used when detecting the animation. This is set by `$animate` when enter/leave/move animations are fired to ensure that the CSS classes are resolved in time. (Note that this will prevent any transitions from occurring on the classes being added and removed.) 39137 * * `cleanupStyles` - Whether or not the provided `from` and `to` styles will be removed once 39138 * the animation is closed. This is useful for when the styles are used purely for the sake of 39139 * the animation and do not have a lasting visual effect on the element (e.g. a collapse and open animation). 39140 * By default this value is set to `false`. 39141 * 39142 * @return {object} an object with start and end methods and details about the animation. 39143 * 39144 * * `start` - The method to start the animation. This will return a `Promise` when called. 39145 * * `end` - This method will cancel the animation and remove all applied CSS classes and styles. 39146 */ 39147var ONE_SECOND = 1000; 39148 39149var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3; 39150var CLOSING_TIME_BUFFER = 1.5; 39151 39152var DETECT_CSS_PROPERTIES = { 39153 transitionDuration: TRANSITION_DURATION_PROP, 39154 transitionDelay: TRANSITION_DELAY_PROP, 39155 transitionProperty: TRANSITION_PROP + PROPERTY_KEY, 39156 animationDuration: ANIMATION_DURATION_PROP, 39157 animationDelay: ANIMATION_DELAY_PROP, 39158 animationIterationCount: ANIMATION_PROP + ANIMATION_ITERATION_COUNT_KEY 39159}; 39160 39161var DETECT_STAGGER_CSS_PROPERTIES = { 39162 transitionDuration: TRANSITION_DURATION_PROP, 39163 transitionDelay: TRANSITION_DELAY_PROP, 39164 animationDuration: ANIMATION_DURATION_PROP, 39165 animationDelay: ANIMATION_DELAY_PROP 39166}; 39167 39168function getCssKeyframeDurationStyle(duration) { 39169 return [ANIMATION_DURATION_PROP, duration + 's']; 39170} 39171 39172function getCssDelayStyle(delay, isKeyframeAnimation) { 39173 var prop = isKeyframeAnimation ? ANIMATION_DELAY_PROP : TRANSITION_DELAY_PROP; 39174 return [prop, delay + 's']; 39175} 39176 39177function computeCssStyles($window, element, properties) { 39178 var styles = Object.create(null); 39179 var detectedStyles = $window.getComputedStyle(element) || {}; 39180 forEach(properties, function(formalStyleName, actualStyleName) { 39181 var val = detectedStyles[formalStyleName]; 39182 if (val) { 39183 var c = val.charAt(0); 39184 39185 // only numerical-based values have a negative sign or digit as the first value 39186 if (c === '-' || c === '+' || c >= 0) { 39187 val = parseMaxTime(val); 39188 } 39189 39190 // by setting this to null in the event that the delay is not set or is set directly as 0 39191 // then we can still allow for negative values to be used later on and not mistake this 39192 // value for being greater than any other negative value. 39193 if (val === 0) { 39194 val = null; 39195 } 39196 styles[actualStyleName] = val; 39197 } 39198 }); 39199 39200 return styles; 39201} 39202 39203function parseMaxTime(str) { 39204 var maxValue = 0; 39205 var values = str.split(/\s*,\s*/); 39206 forEach(values, function(value) { 39207 // it's always safe to consider only second values and omit `ms` values since 39208 // getComputedStyle will always handle the conversion for us 39209 if (value.charAt(value.length - 1) === 's') { 39210 value = value.substring(0, value.length - 1); 39211 } 39212 value = parseFloat(value) || 0; 39213 maxValue = maxValue ? Math.max(value, maxValue) : value; 39214 }); 39215 return maxValue; 39216} 39217 39218function truthyTimingValue(val) { 39219 return val === 0 || val != null; 39220} 39221 39222function getCssTransitionDurationStyle(duration, applyOnlyDuration) { 39223 var style = TRANSITION_PROP; 39224 var value = duration + 's'; 39225 if (applyOnlyDuration) { 39226 style += DURATION_KEY; 39227 } else { 39228 value += ' linear all'; 39229 } 39230 return [style, value]; 39231} 39232
39233function createLocalCacheLookup() { 39234 var cache = Object.create(null); 39235 return { 39236 flush: function() { 39237 cache = Object.create(null); 39238 }, 39239 39240 count: function(key) { 39241 var entry = cache[key]; 39242 return entry ? entry.total : 0; 39243 }, 39244 39245 get: function(key) { 39246 var entry = cache[key]; 39247 return entry && entry.value; 39248 }, 39249 39250 put: function(key, value) { 39251 if (!cache[key]) { 39252 cache[key] = { total: 1, value: value }; 39253 } else { 39254 cache[key].total++; 39255 } 39256 } 39257 }; 39258} 39259 39260// we do not reassign an already present style value since 39261// if we detect the style property value again we may be 39262// detecting styles that were added via the `from` styles. 39263// We make use of `isDefined` here since an empty string 39264// or null value (which is what getPropertyValue will return 39265// for a non-existing style) will still be marked as a valid 39266// value for the style (a falsy value implies that the style 39267// is to be removed at the end of the animation). If we had a simple 39268// "OR" statement then it would not be enough to catch that. 39269function registerRestorableStyles(backup, node, properties) { 39270 forEach(properties, function(prop) { 39271 backup[prop] = isDefined(backup[prop]) 39272 ? backup[prop] 39273 : node.style.getPropertyValue(prop); 39274 }); 39275} 39276 39277var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animateProvider) { 39278 var gcsLookup = createLocalCacheLookup(); 39279 var gcsStaggerLookup = createLocalCacheLookup(); 39280 39281 this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout', 39282 '$$forceReflow', '$sniffer', '$$rAFScheduler', '$$animateQueue', 39283 function($window, $$jqLite, $$AnimateRunner, $timeout, 39284 $$forceReflow, $sniffer, $$rAFScheduler, $$animateQueue) { 39285 39286 var applyAnimationClasses = applyAnimationClassesFactory($$jqLite); 39287 39288 var parentCounter = 0;
vendor: 20,666 bytes, lines 39289-39829
39289 function gcsHashFn(node, extraClasses) { 39290 var KEY = '$$ngAnimateParentKey'; 39291 var parentNode = node.parentNode; 39292 var parentID = parentNode[KEY] || (parentNode[KEY] = ++parentCounter); 39293 return parentID + '-' + node.getAttribute('class') + '-' + extraClasses; 39294 } 39295 39296 function computeCachedCssStyles(node, className, cacheKey, properties) { 39297 var timings = gcsLookup.get(cacheKey); 39298 39299 if (!timings) { 39300 timings = computeCssStyles($window, node, properties); 39301 if (timings.animationIterationCount === 'infinite') { 39302 timings.animationIterationCount = 1; 39303 } 39304 } 39305 39306 // we keep putting this in multiple times even though the value and the cacheKey are the same 39307 // because we're keeping an internal tally of how many duplicate animations are detected. 39308 gcsLookup.put(cacheKey, timings); 39309 return timings; 39310 } 39311 39312 function computeCachedCssStaggerStyles(node, className, cacheKey, properties) { 39313 var stagger; 39314 39315 // if we have one or more existing matches of matching elements 39316 // containing the same parent + CSS styles (which is how cacheKey works) 39317 // then staggering is possible 39318 if (gcsLookup.count(cacheKey) > 0) { 39319 stagger = gcsStaggerLookup.get(cacheKey); 39320 39321 if (!stagger) { 39322 var staggerClassName = pendClasses(className, '-stagger'); 39323 39324 $$jqLite.addClass(node, staggerClassName); 39325 39326 stagger = computeCssStyles($window, node, properties); 39327 39328 // force the conversion of a null value to zero incase not set 39329 stagger.animationDuration = Math.max(stagger.animationDuration, 0); 39330 stagger.transitionDuration = Math.max(stagger.transitionDuration, 0); 39331 39332 $$jqLite.removeClass(node, staggerClassName); 39333 39334 gcsStaggerLookup.put(cacheKey, stagger); 39335 } 39336 } 39337 39338 return stagger || {}; 39339 } 39340 39341 var rafWaitQueue = []; 39342 function waitUntilQuiet(callback) { 39343 rafWaitQueue.push(callback); 39344 $$rAFScheduler.waitUntilQuiet(function() { 39345 gcsLookup.flush(); 39346 gcsStaggerLookup.flush(); 39347 39348 // DO NOT REMOVE THIS LINE OR REFACTOR OUT THE `pageWidth` variable. 39349 // PLEASE EXAMINE THE `$$forceReflow` service to understand why. 39350 var pageWidth = $$forceReflow(); 39351 39352 // we use a for loop to ensure that if the queue is changed 39353 // during this looping then it will consider new requests 39354 for (var i = 0; i < rafWaitQueue.length; i++) { 39355 rafWaitQueue[i](pageWidth); 39356 } 39357 rafWaitQueue.length = 0; 39358 }); 39359 } 39360 39361 function computeTimings(node, className, cacheKey) { 39362 var timings = computeCachedCssStyles(node, className, cacheKey, DETECT_CSS_PROPERTIES); 39363 var aD = timings.animationDelay; 39364 var tD = timings.transitionDelay; 39365 timings.maxDelay = aD && tD 39366 ? Math.max(aD, tD) 39367 : (aD || tD); 39368 timings.maxDuration = Math.max( 39369 timings.animationDuration * timings.animationIterationCount, 39370 timings.transitionDuration); 39371 39372 return timings; 39373 } 39374 39375 return function init(element, initialOptions) { 39376 // all of the animation functions should create 39377 // a copy of the options data, however, if a 39378 // parent service has already created a copy then 39379 // we should stick to using that 39380 var options = initialOptions || {}; 39381 if (!options.$$prepared) { 39382 options = prepareAnimationOptions(copy(options)); 39383 } 39384 39385 var restoreStyles = {}; 39386 var node = getDomNode(element); 39387 if (!node 39388 || !node.parentNode 39389 || !$$animateQueue.enabled()) { 39390 return closeAndReturnNoopAnimator(); 39391 } 39392 39393 var temporaryStyles = []; 39394 var classes = element.attr('class'); 39395 var styles = packageStyles(options); 39396 var animationClosed; 39397 var animationPaused; 39398 var animationCompleted; 39399 var runner; 39400 var runnerHost; 39401 var maxDelay; 39402 var maxDelayTime; 39403 var maxDuration; 39404 var maxDurationTime; 39405 var startTime; 39406 var events = []; 39407 39408 if (options.duration === 0 || (!$sniffer.animations && !$sniffer.transitions)) { 39409 return closeAndReturnNoopAnimator(); 39410 } 39411 39412 var method = options.event && isArray(options.event) 39413 ? options.event.join(' ') 39414 : options.event; 39415 39416 var isStructural = method && options.structural; 39417 var structuralClassName = ''; 39418 var addRemoveClassName = ''; 39419 39420 if (isStructural) { 39421 structuralClassName = pendClasses(method, EVENT_CLASS_PREFIX, true); 39422 } else if (method) { 39423 structuralClassName = method; 39424 } 39425 39426 if (options.addClass) { 39427 addRemoveClassName += pendClasses(options.addClass, ADD_CLASS_SUFFIX); 39428 } 39429 39430 if (options.removeClass) { 39431 if (addRemoveClassName.length) { 39432 addRemoveClassName += ' '; 39433 } 39434 addRemoveClassName += pendClasses(options.removeClass, REMOVE_CLASS_SUFFIX); 39435 } 39436 39437 // there may be a situation where a structural animation is combined together 39438 // with CSS classes that need to resolve before the animation is computed. 39439 // However this means that there is no explicit CSS code to block the animation 39440 // from happening (by setting 0s none in the class name). If this is the case 39441 // we need to apply the classes before the first rAF so we know to continue if 39442 // there actually is a detected transition or keyframe animation 39443 if (options.applyClassesEarly && addRemoveClassName.length) { 39444 applyAnimationClasses(element, options); 39445 } 39446 39447 var preparationClasses = [structuralClassName, addRemoveClassName].join(' ').trim(); 39448 var fullClassName = classes + ' ' + preparationClasses; 39449 var activeClasses = pendClasses(preparationClasses, ACTIVE_CLASS_SUFFIX); 39450 var hasToStyles = styles.to && Object.keys(styles.to).length > 0; 39451 var containsKeyframeAnimation = (options.keyframeStyle || '').length > 0; 39452 39453 // there is no way we can trigger an animation if no styles and 39454 // no classes are being applied which would then trigger a transition, 39455 // unless there a is raw keyframe value that is applied to the element. 39456 if (!containsKeyframeAnimation 39457 && !hasToStyles 39458 && !preparationClasses) { 39459 return closeAndReturnNoopAnimator(); 39460 } 39461 39462 var cacheKey, stagger; 39463 if (options.stagger > 0) { 39464 var staggerVal = parseFloat(options.stagger); 39465 stagger = { 39466 transitionDelay: staggerVal, 39467 animationDelay: staggerVal, 39468 transitionDuration: 0, 39469 animationDuration: 0 39470 }; 39471 } else { 39472 cacheKey = gcsHashFn(node, fullClassName); 39473 stagger = computeCachedCssStaggerStyles(node, preparationClasses, cacheKey, DETECT_STAGGER_CSS_PROPERTIES); 39474 } 39475 39476 if (!options.$$skipPreparationClasses) { 39477 $$jqLite.addClass(element, preparationClasses); 39478 } 39479 39480 var applyOnlyDuration; 39481 39482 if (options.transitionStyle) { 39483 var transitionStyle = [TRANSITION_PROP, options.transitionStyle]; 39484 applyInlineStyle(node, transitionStyle); 39485 temporaryStyles.push(transitionStyle); 39486 } 39487 39488 if (options.duration >= 0) { 39489 applyOnlyDuration = node.style[TRANSITION_PROP].length > 0; 39490 var durationStyle = getCssTransitionDurationStyle(options.duration, applyOnlyDuration); 39491 39492 // we set the duration so that it will be picked up by getComputedStyle later 39493 applyInlineStyle(node, durationStyle); 39494 temporaryStyles.push(durationStyle); 39495 } 39496 39497 if (options.keyframeStyle) { 39498 var keyframeStyle = [ANIMATION_PROP, options.keyframeStyle]; 39499 applyInlineStyle(node, keyframeStyle); 39500 temporaryStyles.push(keyframeStyle); 39501 } 39502 39503 var itemIndex = stagger 39504 ? options.staggerIndex >= 0 39505 ? options.staggerIndex 39506 : gcsLookup.count(cacheKey) 39507 : 0; 39508 39509 var isFirst = itemIndex === 0; 39510 39511 // this is a pre-emptive way of forcing the setup classes to be added and applied INSTANTLY 39512 // without causing any combination of transitions to kick in. By adding a negative delay value 39513 // it forces the setup class' transition to end immediately. We later then remove the negative 39514 // transition delay to allow for the transition to naturally do it's thing. The beauty here is 39515 // that if there is no transition defined then nothing will happen and this will also allow 39516 // other transitions to be stacked on top of each other without any chopping them out. 39517 if (isFirst && !options.skipBlocking) { 39518 blockTransitions(node, SAFE_FAST_FORWARD_DURATION_VALUE); 39519 } 39520 39521 var timings = computeTimings(node, fullClassName, cacheKey); 39522 var relativeDelay = timings.maxDelay; 39523 maxDelay = Math.max(relativeDelay, 0); 39524 maxDuration = timings.maxDuration; 39525 39526 var flags = {}; 39527 flags.hasTransitions = timings.transitionDuration > 0; 39528 flags.hasAnimations = timings.animationDuration > 0; 39529 flags.hasTransitionAll = flags.hasTransitions && timings.transitionProperty === 'all'; 39530 flags.applyTransitionDuration = hasToStyles && ( 39531 (flags.hasTransitions && !flags.hasTransitionAll) 39532 || (flags.hasAnimations && !flags.hasTransitions)); 39533 flags.applyAnimationDuration = options.duration && flags.hasAnimations; 39534 flags.applyTransitionDelay = truthyTimingValue(options.delay) && (flags.applyTransitionDuration || flags.hasTransitions); 39535 flags.applyAnimationDelay = truthyTimingValue(options.delay) && flags.hasAnimations; 39536 flags.recalculateTimingStyles = addRemoveClassName.length > 0; 39537 39538 if (flags.applyTransitionDuration || flags.applyAnimationDuration) { 39539 maxDuration = options.duration ? parseFloat(options.duration) : maxDuration; 39540 39541 if (flags.applyTransitionDuration) { 39542 flags.hasTransitions = true; 39543 timings.transitionDuration = maxDuration; 39544 applyOnlyDuration = node.style[TRANSITION_PROP + PROPERTY_KEY].length > 0; 39545 temporaryStyles.push(getCssTransitionDurationStyle(maxDuration, applyOnlyDuration)); 39546 } 39547 39548 if (flags.applyAnimationDuration) { 39549 flags.hasAnimations = true; 39550 timings.animationDuration = maxDuration; 39551 temporaryStyles.push(getCssKeyframeDurationStyle(maxDuration)); 39552 } 39553 } 39554 39555 if (maxDuration === 0 && !flags.recalculateTimingStyles) { 39556 return closeAndReturnNoopAnimator(); 39557 } 39558 39559 if (options.delay != null) { 39560 var delayStyle; 39561 if (typeof options.delay !== 'boolean') { 39562 delayStyle = parseFloat(options.delay); 39563 // number in options.delay means we have to recalculate the delay for the closing timeout 39564 maxDelay = Math.max(delayStyle, 0); 39565 } 39566 39567 if (flags.applyTransitionDelay) { 39568 temporaryStyles.push(getCssDelayStyle(delayStyle)); 39569 } 39570 39571 if (flags.applyAnimationDelay) { 39572 temporaryStyles.push(getCssDelayStyle(delayStyle, true)); 39573 } 39574 } 39575 39576 // we need to recalculate the delay value since we used a pre-emptive negative 39577 // delay value and the delay value is required for the final event checking. This 39578 // property will ensure that this will happen after the RAF phase has passed. 39579 if (options.duration == null && timings.transitionDuration > 0) { 39580 flags.recalculateTimingStyles = flags.recalculateTimingStyles || isFirst; 39581 } 39582 39583 maxDelayTime = maxDelay * ONE_SECOND; 39584 maxDurationTime = maxDuration * ONE_SECOND; 39585 if (!options.skipBlocking) { 39586 flags.blockTransition = timings.transitionDuration > 0; 39587 flags.blockKeyframeAnimation = timings.animationDuration > 0 && 39588 stagger.animationDelay > 0 && 39589 stagger.animationDuration === 0; 39590 } 39591 39592 if (options.from) { 39593 if (options.cleanupStyles) { 39594 registerRestorableStyles(restoreStyles, node, Object.keys(options.from)); 39595 } 39596 applyAnimationFromStyles(element, options); 39597 } 39598 39599 if (flags.blockTransition || flags.blockKeyframeAnimation) { 39600 applyBlocking(maxDuration); 39601 } else if (!options.skipBlocking) { 39602 blockTransitions(node, false); 39603 } 39604 39605 // TODO(matsko): for 1.5 change this code to have an animator object for better debugging 39606 return { 39607 $$willAnimate: true, 39608 end: endFn, 39609 start: function() { 39610 if (animationClosed) return; 39611 39612 runnerHost = { 39613 end: endFn, 39614 cancel: cancelFn, 39615 resume: null, //this will be set during the start() phase 39616 pause: null 39617 }; 39618 39619 runner = new $$AnimateRunner(runnerHost); 39620 39621 waitUntilQuiet(start); 39622 39623 // we don't have access to pause/resume the animation 39624 // since it hasn't run yet. AnimateRunner will therefore 39625 // set noop functions for resume and pause and they will 39626 // later be overridden once the animation is triggered 39627 return runner; 39628 } 39629 }; 39630 39631 function endFn() { 39632 close(); 39633 } 39634 39635 function cancelFn() { 39636 close(true); 39637 } 39638 39639 function close(rejected) { 39640 // if the promise has been called already then we shouldn't close 39641 // the animation again 39642 if (animationClosed || (animationCompleted && animationPaused)) return; 39643 animationClosed = true; 39644 animationPaused = false; 39645 39646 if (!options.$$skipPreparationClasses) { 39647 $$jqLite.removeClass(element, preparationClasses); 39648 } 39649 $$jqLite.removeClass(element, activeClasses); 39650 39651 blockKeyframeAnimations(node, false); 39652 blockTransitions(node, false); 39653 39654 forEach(temporaryStyles, function(entry) { 39655 // There is only one way to remove inline style properties entirely from elements. 39656 // By using `removeProperty` this works, but we need to convert camel-cased CSS 39657 // styles down to hyphenated values. 39658 node.style[entry[0]] = ''; 39659 }); 39660 39661 applyAnimationClasses(element, options); 39662 applyAnimationStyles(element, options); 39663 39664 if (Object.keys(restoreStyles).length) { 39665 forEach(restoreStyles, function(value, prop) { 39666 if (value) { 39667 node.style.setProperty(prop, value); 39668 } else { 39669 node.style.removeProperty(prop); 39670 } 39671 }); 39672 } 39673 39674 // the reason why we have this option is to allow a synchronous closing callback 39675 // that is fired as SOON as the animation ends (when the CSS is removed) or if 39676 // the animation never takes off at all. A good example is a leave animation since 39677 // the element must be removed just after the animation is over or else the element 39678 // will appear on screen for one animation frame causing an overbearing flicker. 39679 if (options.onDone) { 39680 options.onDone(); 39681 } 39682 39683 if (events && events.length) { 39684 // Remove the transitionend / animationend listener(s) 39685 element.off(events.join(' '), onAnimationProgress); 39686 } 39687 39688 //Cancel the fallback closing timeout and remove the timer data 39689 var animationTimerData = element.data(ANIMATE_TIMER_KEY); 39690 if (animationTimerData) { 39691 $timeout.cancel(animationTimerData[0].timer); 39692 element.removeData(ANIMATE_TIMER_KEY); 39693 } 39694 39695 // if the preparation function fails then the promise is not setup 39696 if (runner) { 39697 runner.complete(!rejected); 39698 } 39699 } 39700 39701 function applyBlocking(duration) { 39702 if (flags.blockTransition) { 39703 blockTransitions(node, duration); 39704 } 39705 39706 if (flags.blockKeyframeAnimation) { 39707 blockKeyframeAnimations(node, !!duration); 39708 } 39709 } 39710 39711 function closeAndReturnNoopAnimator() { 39712 runner = new $$AnimateRunner({ 39713 end: endFn, 39714 cancel: cancelFn 39715 }); 39716 39717 // should flush the cache animation 39718 waitUntilQuiet(noop); 39719 close(); 39720 39721 return { 39722 $$willAnimate: false, 39723 start: function() { 39724 return runner; 39725 }, 39726 end: endFn 39727 }; 39728 } 39729 39730 function onAnimationProgress(event) { 39731 event.stopPropagation(); 39732 var ev = event.originalEvent || event; 39733 39734 // we now always use `Date.now()` due to the recent changes with 39735 // event.timeStamp in Firefox, Webkit and Chrome (see #13494 for more info) 39736 var timeStamp = ev.$manualTimeStamp || Date.now(); 39737 39738 /* Firefox (or possibly just Gecko) likes to not round values up 39739 * when a ms measurement is used for the animation */ 39740 var elapsedTime = parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES)); 39741 39742 /* $manualTimeStamp is a mocked timeStamp value which is set 39743 * within browserTrigger(). This is only here so that tests can 39744 * mock animations properly. Real events fallback to event.timeStamp, 39745 * or, if they don't, then a timeStamp is automatically created for them. 39746 * We're checking to see if the timeStamp surpasses the expected delay, 39747 * but we're using elapsedTime instead of the timeStamp on the 2nd 39748 * pre-condition since animationPauseds sometimes close off early */ 39749 if (Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) { 39750 // we set this flag to ensure that if the transition is paused then, when resumed, 39751 // the animation will automatically close itself since transitions cannot be paused. 39752 animationCompleted = true; 39753 close(); 39754 } 39755 } 39756 39757 function start() { 39758 if (animationClosed) return; 39759 if (!node.parentNode) { 39760 close(); 39761 return; 39762 } 39763 39764 // even though we only pause keyframe animations here the pause flag 39765 // will still happen when transitions are used. Only the transition will 39766 // not be paused since that is not possible. If the animation ends when 39767 // paused then it will not complete until unpaused or cancelled. 39768 var playPause = function(playAnimation) { 39769 if (!animationCompleted) { 39770 animationPaused = !playAnimation; 39771 if (timings.animationDuration) { 39772 var value = blockKeyframeAnimations(node, animationPaused); 39773 if (animationPaused) { 39774 temporaryStyles.push(value); 39775 } else { 39776 removeFromArray(temporaryStyles, value); 39777 } 39778 } 39779 } else if (animationPaused && playAnimation) { 39780 animationPaused = false; 39781 close(); 39782 } 39783 }; 39784 39785 // checking the stagger duration prevents an accidentally cascade of the CSS delay style 39786 // being inherited from the parent. If the transition duration is zero then we can safely 39787 // rely that the delay value is an intentional stagger delay style. 39788 var maxStagger = itemIndex > 0 39789 && ((timings.transitionDuration && stagger.transitionDuration === 0) || 39790 (timings.animationDuration && stagger.animationDuration === 0)) 39791 && Math.max(stagger.animationDelay, stagger.transitionDelay); 39792 if (maxStagger) { 39793 $timeout(triggerAnimationStart, 39794 Math.floor(maxStagger * itemIndex * ONE_SECOND), 39795 false); 39796 } else { 39797 triggerAnimationStart(); 39798 } 39799 39800 // this will decorate the existing promise runner with pause/resume methods 39801 runnerHost.resume = function() { 39802 playPause(true); 39803 }; 39804 39805 runnerHost.pause = function() { 39806 playPause(false); 39807 }; 39808 39809 function triggerAnimationStart() { 39810 // just incase a stagger animation kicks in when the animation 39811 // itself was cancelled entirely 39812 if (animationClosed) return; 39813 39814 applyBlocking(false); 39815 39816 forEach(temporaryStyles, function(entry) { 39817 var key = entry[0]; 39818 var value = entry[1]; 39819 node.style[key] = value; 39820 }); 39821 39822 applyAnimationClasses(element, options); 39823 $$jqLite.addClass(element, activeClasses); 39824 39825 if (flags.recalculateTimingStyles) { 39826 fullClassName = node.className + ' ' + preparationClasses; 39827 cacheKey = gcsHashFn(node, fullClassName); 39828 39829 timings = computeTimings(node, fullClassName, cacheKey
vendor: 23,535 bytes, lines 39829-40566
39829); 39830 relativeDelay = timings.maxDelay; 39831 maxDelay = Math.max(relativeDelay, 0); 39832 maxDuration = timings.maxDuration; 39833 39834 if (maxDuration === 0) { 39835 close(); 39836 return; 39837 } 39838 39839 flags.hasTransitions = timings.transitionDuration > 0; 39840 flags.hasAnimations = timings.animationDuration > 0; 39841 } 39842 39843 if (flags.applyAnimationDelay) { 39844 relativeDelay = typeof options.delay !== 'boolean' && truthyTimingValue(options.delay) 39845 ? parseFloat(options.delay) 39846 : relativeDelay; 39847 39848 maxDelay = Math.max(relativeDelay, 0); 39849 timings.animationDelay = relativeDelay; 39850 delayStyle = getCssDelayStyle(relativeDelay, true); 39851 temporaryStyles.push(delayStyle); 39852 node.style[delayStyle[0]] = delayStyle[1]; 39853 } 39854 39855 maxDelayTime = maxDelay * ONE_SECOND; 39856 maxDurationTime = maxDuration * ONE_SECOND; 39857 39858 if (options.easing) { 39859 var easeProp, easeVal = options.easing; 39860 if (flags.hasTransitions) { 39861 easeProp = TRANSITION_PROP + TIMING_KEY; 39862 temporaryStyles.push([easeProp, easeVal]); 39863 node.style[easeProp] = easeVal; 39864 } 39865 if (flags.hasAnimations) { 39866 easeProp = ANIMATION_PROP + TIMING_KEY; 39867 temporaryStyles.push([easeProp, easeVal]); 39868 node.style[easeProp] = easeVal; 39869 } 39870 } 39871 39872 if (timings.transitionDuration) { 39873 events.push(TRANSITIONEND_EVENT); 39874 } 39875 39876 if (timings.animationDuration) { 39877 events.push(ANIMATIONEND_EVENT); 39878 } 39879 39880 startTime = Date.now(); 39881 var timerTime = maxDelayTime + CLOSING_TIME_BUFFER * maxDurationTime; 39882 var endTime = startTime + timerTime; 39883 39884 var animationsData = element.data(ANIMATE_TIMER_KEY) || []; 39885 var setupFallbackTimer = true; 39886 if (animationsData.length) { 39887 var currentTimerData = animationsData[0]; 39888 setupFallbackTimer = endTime > currentTimerData.expectedEndTime; 39889 if (setupFallbackTimer) { 39890 $timeout.cancel(currentTimerData.timer); 39891 } else { 39892 animationsData.push(close); 39893 } 39894 } 39895 39896 if (setupFallbackTimer) { 39897 var timer = $timeout(onAnimationExpired, timerTime, false); 39898 animationsData[0] = { 39899 timer: timer, 39900 expectedEndTime: endTime 39901 }; 39902 animationsData.push(close); 39903 element.data(ANIMATE_TIMER_KEY, animationsData); 39904 } 39905 39906 if (events.length) { 39907 element.on(events.join(' '), onAnimationProgress); 39908 } 39909 39910 if (options.to) { 39911 if (options.cleanupStyles) { 39912 registerRestorableStyles(restoreStyles, node, Object.keys(options.to)); 39913 } 39914 applyAnimationToStyles(element, options); 39915 } 39916 } 39917 39918 function onAnimationExpired() { 39919 var animationsData = element.data(ANIMATE_TIMER_KEY); 39920 39921 // this will be false in the event that the element was 39922 // removed from the DOM (via a leave animation or something 39923 // similar) 39924 if (animationsData) { 39925 for (var i = 1; i < animationsData.length; i++) { 39926 animationsData[i](); 39927 } 39928 element.removeData(ANIMATE_TIMER_KEY); 39929 } 39930 } 39931 } 39932 }; 39933 }]; 39934}]; 39935 39936var $$AnimateCssDriverProvider = ['$$animationProvider', /** @this */ function($$animationProvider) { 39937 $$animationProvider.drivers.push('$$animateCssDriver'); 39938 39939 var NG_ANIMATE_SHIM_CLASS_NAME = 'ng-animate-shim'; 39940 var NG_ANIMATE_ANCHOR_CLASS_NAME = 'ng-anchor'; 39941 39942 var NG_OUT_ANCHOR_CLASS_NAME = 'ng-anchor-out'; 39943 var NG_IN_ANCHOR_CLASS_NAME = 'ng-anchor-in'; 39944 39945 function isDocumentFragment(node) { 39946 return node.parentNode && node.parentNode.nodeType === 11; 39947 } 39948 39949 this.$get = ['$animateCss', '$rootScope', '$$AnimateRunner', '$rootElement', '$sniffer', '$$jqLite', '$document', 39950 function($animateCss, $rootScope, $$AnimateRunner, $rootElement, $sniffer, $$jqLite, $document) { 39951 39952 // only browsers that support these properties can render animations 39953 if (!$sniffer.animations && !$sniffer.transitions) return noop; 39954 39955 var bodyNode = $document[0].body; 39956 var rootNode = getDomNode($rootElement); 39957 39958 var rootBodyElement = jqLite( 39959 // this is to avoid using something that exists outside of the body 39960 // we also special case the doc fragment case because our unit test code 39961 // appends the $rootElement to the body after the app has been bootstrapped 39962 isDocumentFragment(rootNode) || bodyNode.contains(rootNode) ? rootNode : bodyNode 39963 ); 39964 39965 return function initDriverFn(animationDetails) { 39966 return animationDetails.from && animationDetails.to 39967 ? prepareFromToAnchorAnimation(animationDetails.from, 39968 animationDetails.to, 39969 animationDetails.classes, 39970 animationDetails.anchors) 39971 : prepareRegularAnimation(animationDetails); 39972 }; 39973 39974 function filterCssClasses(classes) { 39975 //remove all the `ng-` stuff 39976 return classes.replace(/\bng-\S+\b/g, ''); 39977 } 39978 39979 function getUniqueValues(a, b) { 39980 if (isString(a)) a = a.split(' '); 39981 if (isString(b)) b = b.split(' '); 39982 return a.filter(function(val) { 39983 return b.indexOf(val) === -1; 39984 }).join(' '); 39985 } 39986 39987 function prepareAnchoredAnimation(classes, outAnchor, inAnchor) { 39988 var clone = jqLite(getDomNode(outAnchor).cloneNode(true)); 39989 var startingClasses = filterCssClasses(getClassVal(clone)); 39990 39991 outAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME); 39992 inAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME); 39993 39994 clone.addClass(NG_ANIMATE_ANCHOR_CLASS_NAME); 39995 39996 rootBodyElement.append(clone); 39997 39998 var animatorIn, animatorOut = prepareOutAnimation(); 39999 40000 // the user may not end up using the `out` animation and 40001 // only making use of the `in` animation or vice-versa. 40002 // In either case we should allow this and not assume the 40003 // animation is over unless both animations are not used. 40004 if (!animatorOut) { 40005 animatorIn = prepareInAnimation(); 40006 if (!animatorIn) { 40007 return end(); 40008 } 40009 } 40010 40011 var startingAnimator = animatorOut || animatorIn; 40012 40013 return { 40014 start: function() { 40015 var runner; 40016 40017 var currentAnimation = startingAnimator.start(); 40018 currentAnimation.done(function() { 40019 currentAnimation = null; 40020 if (!animatorIn) { 40021 animatorIn = prepareInAnimation(); 40022 if (animatorIn) { 40023 currentAnimation = animatorIn.start(); 40024 currentAnimation.done(function() { 40025 currentAnimation = null; 40026 end(); 40027 runner.complete(); 40028 }); 40029 return currentAnimation; 40030 } 40031 } 40032 // in the event that there is no `in` animation 40033 end(); 40034 runner.complete(); 40035 }); 40036 40037 runner = new $$AnimateRunner({ 40038 end: endFn, 40039 cancel: endFn 40040 }); 40041 40042 return runner; 40043 40044 function endFn() { 40045 if (currentAnimation) { 40046 currentAnimation.end(); 40047 } 40048 } 40049 } 40050 }; 40051 40052 function calculateAnchorStyles(anchor) { 40053 var styles = {}; 40054 40055 var coords = getDomNode(anchor).getBoundingClientRect(); 40056 40057 // we iterate directly since safari messes up and doesn't return 40058 // all the keys for the coords object when iterated 40059 forEach(['width','height','top','left'], function(key) { 40060 var value = coords[key]; 40061 switch (key) { 40062 case 'top': 40063 value += bodyNode.scrollTop; 40064 break; 40065 case 'left': 40066 value += bodyNode.scrollLeft; 40067 break; 40068 } 40069 styles[key] = Math.floor(value) + 'px'; 40070 }); 40071 return styles; 40072 } 40073 40074 function prepareOutAnimation() { 40075 var animator = $animateCss(clone, { 40076 addClass: NG_OUT_ANCHOR_CLASS_NAME, 40077 delay: true, 40078 from: calculateAnchorStyles(outAnchor) 40079 }); 40080 40081 // read the comment within `prepareRegularAnimation` to understand 40082 // why this check is necessary 40083 return animator.$$willAnimate ? animator : null; 40084 } 40085 40086 function getClassVal(element) { 40087 return element.attr('class') || ''; 40088 } 40089 40090 function prepareInAnimation() { 40091 var endingClasses = filterCssClasses(getClassVal(inAnchor)); 40092 var toAdd = getUniqueValues(endingClasses, startingClasses); 40093 var toRemove = getUniqueValues(startingClasses, endingClasses); 40094 40095 var animator = $animateCss(clone, { 40096 to: calculateAnchorStyles(inAnchor), 40097 addClass: NG_IN_ANCHOR_CLASS_NAME + ' ' + toAdd, 40098 removeClass: NG_OUT_ANCHOR_CLASS_NAME + ' ' + toRemove, 40099 delay: true 40100 }); 40101 40102 // read the comment within `prepareRegularAnimation` to understand 40103 // why this check is necessary 40104 return animator.$$willAnimate ? animator : null; 40105 } 40106 40107 function end() { 40108 clone.remove(); 40109 outAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME); 40110 inAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME); 40111 } 40112 } 40113 40114 function prepareFromToAnchorAnimation(from, to, classes, anchors) { 40115 var fromAnimation = prepareRegularAnimation(from, noop); 40116 var toAnimation = prepareRegularAnimation(to, noop); 40117 40118 var anchorAnimations = []; 40119 forEach(anchors, function(anchor) { 40120 var outElement = anchor['out']; 40121 var inElement = anchor['in']; 40122 var animator = prepareAnchoredAnimation(classes, outElement, inElement); 40123 if (animator) { 40124 anchorAnimations.push(animator); 40125 } 40126 }); 40127 40128 // no point in doing anything when there are no elements to animate 40129 if (!fromAnimation && !toAnimation && anchorAnimations.length === 0) return; 40130 40131 return { 40132 start: function() { 40133 var animationRunners = []; 40134 40135 if (fromAnimation) { 40136 animationRunners.push(fromAnimation.start()); 40137 } 40138 40139 if (toAnimation) { 40140 animationRunners.push(toAnimation.start()); 40141 } 40142 40143 forEach(anchorAnimations, function(animation) { 40144 animationRunners.push(animation.start()); 40145 }); 40146 40147 var runner = new $$AnimateRunner({ 40148 end: endFn, 40149 cancel: endFn // CSS-driven animations cannot be cancelled, only ended 40150 }); 40151 40152 $$AnimateRunner.all(animationRunners, function(status) { 40153 runner.complete(status); 40154 }); 40155 40156 return runner; 40157 40158 function endFn() { 40159 forEach(animationRunners, function(runner) { 40160 runner.end(); 40161 }); 40162 } 40163 } 40164 }; 40165 } 40166 40167 function prepareRegularAnimation(animationDetails) { 40168 var element = animationDetails.element; 40169 var options = animationDetails.options || {}; 40170 40171 if (animationDetails.structural) { 40172 options.event = animationDetails.event; 40173 options.structural = true; 40174 options.applyClassesEarly = true; 40175 40176 // we special case the leave animation since we want to ensure that 40177 // the element is removed as soon as the animation is over. Otherwise 40178 // a flicker might appear or the element may not be removed at all 40179 if (animationDetails.event === 'leave') { 40180 options.onDone = options.domOperation; 40181 } 40182 } 40183 40184 // We assign the preparationClasses as the actual animation event since 40185 // the internals of $animateCss will just suffix the event token values 40186 // with `-active` to trigger the animation. 40187 if (options.preparationClasses) { 40188 options.event = concatWithSpace(options.event, options.preparationClasses); 40189 } 40190 40191 var animator = $animateCss(element, options); 40192 40193 // the driver lookup code inside of $$animation attempts to spawn a 40194 // driver one by one until a driver returns a.$$willAnimate animator object. 40195 // $animateCss will always return an object, however, it will pass in 40196 // a flag as a hint as to whether an animation was detected or not 40197 return animator.$$willAnimate ? animator : null; 40198 } 40199 }]; 40200}]; 40201 40202// TODO(matsko): use caching here to speed things up for detection 40203// TODO(matsko): add documentation 40204// by the time... 40205 40206var $$AnimateJsProvider = ['$animateProvider', /** @this */ function($animateProvider) { 40207 this.$get = ['$injector', '$$AnimateRunner', '$$jqLite', 40208 function($injector, $$AnimateRunner, $$jqLite) { 40209 40210 var applyAnimationClasses = applyAnimationClassesFactory($$jqLite); 40211 // $animateJs(element, 'enter'); 40212 return function(element, event, classes, options) { 40213 var animationClosed = false; 40214 40215 // the `classes` argument is optional and if it is not used 40216 // then the classes will be resolved from the element's className 40217 // property as well as options.addClass/options.removeClass. 40218 if (arguments.length === 3 && isObject(classes)) { 40219 options = classes; 40220 classes = null; 40221 } 40222 40223 options = prepareAnimationOptions(options); 40224 if (!classes) { 40225 classes = element.attr('class') || ''; 40226 if (options.addClass) { 40227 classes += ' ' + options.addClass; 40228 } 40229 if (options.removeClass) { 40230 classes += ' ' + options.removeClass; 40231 } 40232 } 40233 40234 var classesToAdd = options.addClass; 40235 var classesToRemove = options.removeClass; 40236 40237 // the lookupAnimations function returns a series of animation objects that are 40238 // matched up with one or more of the CSS classes. These animation objects are 40239 // defined via the module.animation factory function. If nothing is detected then 40240 // we don't return anything which then makes $animation query the next driver. 40241 var animations = lookupAnimations(classes); 40242 var before, after; 40243 if (animations.length) { 40244 var afterFn, beforeFn; 40245 if (event === 'leave') { 40246 beforeFn = 'leave'; 40247 afterFn = 'afterLeave'; // TODO(matsko): get rid of this 40248 } else { 40249 beforeFn = 'before' + event.charAt(0).toUpperCase() + event.substr(1); 40250 afterFn = event; 40251 } 40252 40253 if (event !== 'enter' && event !== 'move') { 40254 before = packageAnimations(element, event, options, animations, beforeFn); 40255 } 40256 after = packageAnimations(element, event, options, animations, afterFn); 40257 } 40258 40259 // no matching animations 40260 if (!before && !after) return; 40261 40262 function applyOptions() { 40263 options.domOperation(); 40264 applyAnimationClasses(element, options); 40265 } 40266 40267 function close() { 40268 animationClosed = true; 40269 applyOptions(); 40270 applyAnimationStyles(element, options); 40271 } 40272 40273 var runner; 40274 40275 return { 40276 $$willAnimate: true, 40277 end: function() { 40278 if (runner) { 40279 runner.end(); 40280 } else { 40281 close(); 40282 runner = new $$AnimateRunner(); 40283 runner.complete(true); 40284 } 40285 return runner; 40286 }, 40287 start: function() { 40288 if (runner) { 40289 return runner; 40290 } 40291 40292 runner = new $$AnimateRunner(); 40293 var closeActiveAnimations; 40294 var chain = []; 40295 40296 if (before) { 40297 chain.push(function(fn) { 40298 closeActiveAnimations = before(fn); 40299 }); 40300 } 40301 40302 if (chain.length) { 40303 chain.push(function(fn) { 40304 applyOptions(); 40305 fn(true); 40306 }); 40307 } else { 40308 applyOptions(); 40309 } 40310 40311 if (after) { 40312 chain.push(function(fn) { 40313 closeActiveAnimations = after(fn); 40314 }); 40315 } 40316 40317 runner.setHost({ 40318 end: function() { 40319 endAnimations(); 40320 }, 40321 cancel: function() { 40322 endAnimations(true); 40323 } 40324 }); 40325 40326 $$AnimateRunner.chain(chain, onComplete); 40327 return runner; 40328 40329 function onComplete(success) { 40330 close(success); 40331 runner.complete(success); 40332 } 40333 40334 function endAnimations(cancelled) { 40335 if (!animationClosed) { 40336 (closeActiveAnimations || noop)(cancelled); 40337 onComplete(cancelled); 40338 } 40339 } 40340 } 40341 }; 40342 40343 function executeAnimationFn(fn, element, event, options, onDone) { 40344 var args; 40345 switch (event) { 40346 case 'animate': 40347 args = [element, options.from, options.to, onDone]; 40348 break; 40349 40350 case 'setClass': 40351 args = [element, classesToAdd, classesToRemove, onDone]; 40352 break; 40353 40354 case 'addClass': 40355 args = [element, classesToAdd, onDone]; 40356 break; 40357 40358 case 'removeClass': 40359 args = [element, classesToRemove, onDone]; 40360 break; 40361 40362 default: 40363 args = [element, onDone]; 40364 break; 40365 } 40366 40367 args.push(options); 40368 40369 var value = fn.apply(fn, args); 40370 if (value) { 40371 if (isFunction(value.start)) { 40372 value = value.start(); 40373 } 40374 40375 if (value instanceof $$AnimateRunner) { 40376 value.done(onDone); 40377 } else if (isFunction(value)) { 40378 // optional onEnd / onCancel callback 40379 return value; 40380 } 40381 } 40382 40383 return noop; 40384 } 40385 40386 function groupEventedAnimations(element, event, options, animations, fnName) { 40387 var operations = []; 40388 forEach(animations, function(ani) { 40389 var animation = ani[fnName]; 40390 if (!animation) return; 40391 40392 // note that all of these animations will run in parallel 40393 operations.push(function() { 40394 var runner; 40395 var endProgressCb; 40396 40397 var resolved = false; 40398 var onAnimationComplete = function(rejected) { 40399 if (!resolved) { 40400 resolved = true; 40401 (endProgressCb || noop)(rejected); 40402 runner.complete(!rejected); 40403 } 40404 }; 40405 40406 runner = new $$AnimateRunner({ 40407 end: function() { 40408 onAnimationComplete(); 40409 }, 40410 cancel: function() { 40411 onAnimationComplete(true); 40412 } 40413 }); 40414 40415 endProgressCb = executeAnimationFn(animation, element, event, options, function(result) { 40416 var cancelled = result === false; 40417 onAnimationComplete(cancelled); 40418 }); 40419 40420 return runner; 40421 }); 40422 }); 40423 40424 return operations; 40425 } 40426 40427 function packageAnimations(element, event, options, animations, fnName) { 40428 var operations = groupEventedAnimations(element, event, options, animations, fnName); 40429 if (operations.length === 0) { 40430 var a, b; 40431 if (fnName === 'beforeSetClass') { 40432 a = groupEventedAnimations(element, 'removeClass', options, animations, 'beforeRemoveClass'); 40433 b = groupEventedAnimations(element, 'addClass', options, animations, 'beforeAddClass'); 40434 } else if (fnName === 'setClass') { 40435 a = groupEventedAnimations(element, 'removeClass', options, animations, 'removeClass'); 40436 b = groupEventedAnimations(element, 'addClass', options, animations, 'addClass'); 40437 } 40438 40439 if (a) { 40440 operations = operations.concat(a); 40441 } 40442 if (b) { 40443 operations = operations.concat(b); 40444 } 40445 } 40446 40447 if (operations.length === 0) return; 40448 40449 // TODO(matsko): add documentation 40450 return function startAnimation(callback) { 40451 var runners = []; 40452 if (operations.length) { 40453 forEach(operations, function(animateFn) { 40454 runners.push(animateFn()); 40455 }); 40456 } 40457 40458 if (runners.length) { 40459 $$AnimateRunner.all(runners, callback); 40460 } else { 40461 callback(); 40462 } 40463 40464 return function endFn(reject) { 40465 forEach(runners, function(runner) { 40466 if (reject) { 40467 runner.cancel(); 40468 } else { 40469 runner.end(); 40470 } 40471 }); 40472 }; 40473 }; 40474 } 40475 }; 40476 40477 function lookupAnimations(classes) { 40478 classes = isArray(classes) ? classes : classes.split(' '); 40479 var matches = [], flagMap = {}; 40480 for (var i = 0; i < classes.length; i++) { 40481 var klass = classes[i], 40482 animationFactory = $animateProvider.$$registeredAnimations[klass]; 40483 if (animationFactory && !flagMap[klass]) { 40484 matches.push($injector.get(animationFactory)); 40485 flagMap[klass] = true; 40486 } 40487 } 40488 return matches; 40489 } 40490 }]; 40491}]; 40492 40493var $$AnimateJsDriverProvider = ['$$animationProvider', /** @this */ function($$animationProvider) { 40494 $$animationProvider.drivers.push('$$animateJsDriver'); 40495 this.$get = ['$$animateJs', '$$AnimateRunner', function($$animateJs, $$AnimateRunner) { 40496 return function initDriverFn(animationDetails) { 40497 if (animationDetails.from && animationDetails.to) { 40498 var fromAnimation = prepareAnimation(animationDetails.from); 40499 var toAnimation = prepareAnimation(animationDetails.to); 40500 if (!fromAnimation && !toAnimation) return; 40501 40502 return { 40503 start: function() { 40504 var animationRunners = []; 40505 40506 if (fromAnimation) { 40507 animationRunners.push(fromAnimation.start()); 40508 } 40509 40510 if (toAnimation) { 40511 animationRunners.push(toAnimation.start()); 40512 } 40513 40514 $$AnimateRunner.all(animationRunners, done); 40515 40516 var runner = new $$AnimateRunner({ 40517 end: endFnFactory(), 40518 cancel: endFnFactory() 40519 }); 40520 40521 return runner; 40522 40523 function endFnFactory() { 40524 return function() { 40525 forEach(animationRunners, function(runner) { 40526 // at this point we cannot cancel animations for groups just yet. 1.5+ 40527 runner.end(); 40528 }); 40529 }; 40530 } 40531 40532 function done(status) { 40533 runner.complete(status); 40534 } 40535 } 40536 }; 40537 } else { 40538 return prepareAnimation(animationDetails); 40539 } 40540 }; 40541 40542 function prepareAnimation(animationDetails) { 40543 // TODO(matsko): make sure to check for grouped animations and delegate down to normal animations 40544 var element = animationDetails.element; 40545 var event = animationDetails.event; 40546 var options = animationDetails.options; 40547 var classes = animationDetails.classes; 40548 return $$animateJs(element, event, classes, options); 40549 } 40550 }]; 40551}]; 40552 40553var NG_ANIMATE_ATTR_NAME = 'data-ng-animate'; 40554var NG_ANIMATE_PIN_DATA = '$ngAnimatePin'; 40555var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animateProvider) { 40556 var PRE_DIGEST_STATE = 1; 40557 var RUNNING_STATE = 2; 40558 var ONE_SPACE = ' '; 40559 40560 var rules = this.rules = { 40561 skip: [], 40562 cancel: [], 40563 join: [] 40564 }; 40565 40566 function
40566makeTruthyCssClassMap(classString) { 40567 if (!classString) { 40568 return null; 40569 } 40570 40571 var keys = classString.split(ONE_SPACE); 40572 var map = Object.create(null); 40573 40574 forEach(keys, function(key) { 40575 map[key] = true; 40576 }); 40577 return map; 40578 } 40579 40580 function hasMatchingClasses(newClassString, currentClassString) { 40581 if (newClassString && currentClassString) { 40582 var currentClassMap = makeTruthyCssClassMap(currentClassString); 40583 return newClassString.split(ONE_SPACE).some(function(className) { 40584 return currentClassMap[className]; 40585 }); 40586 } 40587 } 40588 40589 function isAllowed(ruleType, element, currentAnimation, previousAnimation) { 40590 return rules[ruleType].some(function(fn) { 40591 return fn(element, currentAnimation, previousAnimation); 40592 }); 40593 } 40594 40595 function hasAnimationClasses(animation, and) { 40596 var a = (animation.addClass || '').length > 0; 40597 var b = (animation.removeClass || '').length > 0; 40598 return and ? a && b : a || b; 40599 } 40600 40601 rules.join.push(function(element, newAnimation, currentAnimation) { 40602 // if the new animation is class-based then we can just tack that on
vendor: 11,131 bytes, lines 40603-40902
40603 return !newAnimation.structural && hasAnimationClasses(newAnimation); 40604 }); 40605 40606 rules.skip.push(function(element, newAnimation, currentAnimation) { 40607 // there is no need to animate anything if no classes are being added and 40608 // there is no structural animation that will be triggered 40609 return !newAnimation.structural && !hasAnimationClasses(newAnimation); 40610 }); 40611 40612 rules.skip.push(function(element, newAnimation, currentAnimation) { 40613 // why should we trigger a new structural animation if the element will 40614 // be removed from the DOM anyway? 40615 return currentAnimation.event === 'leave' && newAnimation.structural; 40616 }); 40617 40618 rules.skip.push(function(element, newAnimation, currentAnimation) { 40619 // if there is an ongoing current animation then don't even bother running the class-based animation 40620 return currentAnimation.structural && currentAnimation.state === RUNNING_STATE && !newAnimation.structural; 40621 }); 40622 40623 rules.cancel.push(function(element, newAnimation, currentAnimation) { 40624 // there can never be two structural animations running at the same time 40625 return currentAnimation.structural && newAnimation.structural; 40626 }); 40627 40628 rules.cancel.push(function(element, newAnimation, currentAnimation) { 40629 // if the previous animation is already running, but the new animation will 40630 // be triggered, but the new animation is structural 40631 return currentAnimation.state === RUNNING_STATE && newAnimation.structural; 40632 }); 40633 40634 rules.cancel.push(function(element, newAnimation, currentAnimation) { 40635 // cancel the animation if classes added / removed in both animation cancel each other out, 40636 // but only if the current animation isn't structural 40637 40638 if (currentAnimation.structural) return false; 40639 40640 var nA = newAnimation.addClass; 40641 var nR = newAnimation.removeClass; 40642 var cA = currentAnimation.addClass; 40643 var cR = currentAnimation.removeClass; 40644 40645 // early detection to save the global CPU shortage :) 40646 if ((isUndefined(nA) && isUndefined(nR)) || (isUndefined(cA) && isUndefined(cR))) { 40647 return false; 40648 } 40649 40650 return hasMatchingClasses(nA, cR) || hasMatchingClasses(nR, cA); 40651 }); 40652 40653 this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$HashMap', 40654 '$$animation', '$$AnimateRunner', '$templateRequest', '$$jqLite', '$$forceReflow', 40655 function($$rAF, $rootScope, $rootElement, $document, $$HashMap, 40656 $$animation, $$AnimateRunner, $templateRequest, $$jqLite, $$forceReflow) { 40657 40658 var activeAnimationsLookup = new $$HashMap(); 40659 var disabledElementsLookup = new $$HashMap(); 40660 var animationsEnabled = null; 40661 40662 function postDigestTaskFactory() { 40663 var postDigestCalled = false; 40664 return function(fn) { 40665 // we only issue a call to postDigest before 40666 // it has first passed. This prevents any callbacks 40667 // from not firing once the animation has completed 40668 // since it will be out of the digest cycle. 40669 if (postDigestCalled) { 40670 fn(); 40671 } else { 40672 $rootScope.$$postDigest(function() { 40673 postDigestCalled = true; 40674 fn(); 40675 }); 40676 } 40677 }; 40678 } 40679 40680 // Wait until all directive and route-related templates are downloaded and 40681 // compiled. The $templateRequest.totalPendingRequests variable keeps track of 40682 // all of the remote templates being currently downloaded. If there are no 40683 // templates currently downloading then the watcher will still fire anyway. 40684 var deregisterWatch = $rootScope.$watch( 40685 function() { return $templateRequest.totalPendingRequests === 0; }, 40686 function(isEmpty) { 40687 if (!isEmpty) return; 40688 deregisterWatch(); 40689 40690 // Now that all templates have been downloaded, $animate will wait until 40691 // the post digest queue is empty before enabling animations. By having two 40692 // calls to $postDigest calls we can ensure that the flag is enabled at the 40693 // very end of the post digest queue. Since all of the animations in $animate 40694 // use $postDigest, it's important that the code below executes at the end. 40695 // This basically means that the page is fully downloaded and compiled before 40696 // any animations are triggered. 40697 $rootScope.$$postDigest(function() { 40698 $rootScope.$$postDigest(function() { 40699 // we check for null directly in the event that the application already called 40700 // .enabled() with whatever arguments that it provided it with 40701 if (animationsEnabled === null) { 40702 animationsEnabled = true; 40703 } 40704 }); 40705 }); 40706 } 40707 ); 40708 40709 var callbackRegistry = Object.create(null); 40710 40711 // remember that the classNameFilter is set during the provider/config 40712 // stage therefore we can optimize here and setup a helper function 40713 var classNameFilter = $animateProvider.classNameFilter(); 40714 var isAnimatableClassName = !classNameFilter 40715 ? function() { return true; } 40716 : function(className) { 40717 return classNameFilter.test(className); 40718 }; 40719 40720 var applyAnimationClasses = applyAnimationClassesFactory($$jqLite); 40721 40722 function normalizeAnimationDetails(element, animation) { 40723 return mergeAnimationDetails(element, animation, {}); 40724 } 40725 40726 // IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259. 40727 var contains = window.Node.prototype.contains || /** @this */ function(arg) { 40728 // eslint-disable-next-line no-bitwise 40729 return this === arg || !!(this.compareDocumentPosition(arg) & 16); 40730 }; 40731 40732 function findCallbacks(parent, element, event) { 40733 var targetNode = getDomNode(element); 40734 var targetParentNode = getDomNode(parent); 40735 40736 var matches = []; 40737 var entries = callbackRegistry[event]; 40738 if (entries) { 40739 forEach(entries, function(entry) { 40740 if (contains.call(entry.node, targetNode)) { 40741 matches.push(entry.callback); 40742 } else if (event === 'leave' && contains.call(entry.node, targetParentNode)) { 40743 matches.push(entry.callback); 40744 } 40745 }); 40746 } 40747 40748 return matches; 40749 } 40750 40751 function filterFromRegistry(list, matchContainer, matchCallback) { 40752 var containerNode = extractElementNode(matchContainer); 40753 return list.filter(function(entry) { 40754 var isMatch = entry.node === containerNode && 40755 (!matchCallback || entry.callback === matchCallback); 40756 return !isMatch; 40757 }); 40758 } 40759 40760 function cleanupEventListeners(phase, element) { 40761 if (phase === 'close' && !element[0].parentNode) { 40762 // If the element is not attached to a parentNode, it has been removed by 40763 // the domOperation, and we can safely remove the event callbacks 40764 $animate.off(element); 40765 } 40766 } 40767 40768 var $animate = { 40769 on: function(event, container, callback) { 40770 var node = extractElementNode(container); 40771 callbackRegistry[event] = callbackRegistry[event] || []; 40772 callbackRegistry[event].push({ 40773 node: node, 40774 callback: callback 40775 }); 40776 40777 // Remove the callback when the element is removed from the DOM 40778 jqLite(container).on('$destroy', function() { 40779 var animationDetails = activeAnimationsLookup.get(node); 40780 40781 if (!animationDetails) { 40782 // If there's an animation ongoing, the callback calling code will remove 40783 // the event listeners. If we'd remove here, the callbacks would be removed 40784 // before the animation ends 40785 $animate.off(event, container, callback); 40786 } 40787 }); 40788 }, 40789 40790 off: function(event, container, callback) { 40791 if (arguments.length === 1 && !isString(arguments[0])) { 40792 container = arguments[0]; 40793 for (var eventType in callbackRegistry) { 40794 callbackRegistry[eventType] = filterFromRegistry(callbackRegistry[eventType], container); 40795 } 40796 40797 return; 40798 } 40799 40800 var entries = callbackRegistry[event]; 40801 if (!entries) return; 40802 40803 callbackRegistry[event] = arguments.length === 1 40804 ? null 40805 : filterFromRegistry(entries, container, callback); 40806 }, 40807 40808 pin: function(element, parentElement) { 40809 assertArg(isElement(element), 'element', 'not an element'); 40810 assertArg(isElement(parentElement), 'parentElement', 'not an element'); 40811 element.data(NG_ANIMATE_PIN_DATA, parentElement); 40812 }, 40813 40814 push: function(element, event, options, domOperation) { 40815 options = options || {}; 40816 options.domOperation = domOperation; 40817 return queueAnimation(element, event, options); 40818 }, 40819 40820 // this method has four signatures: 40821 // () - global getter 40822 // (bool) - global setter 40823 // (element) - element getter 40824 // (element, bool) - element setter<F37> 40825 enabled: function(element, bool) { 40826 var argCount = arguments.length; 40827 40828 if (argCount === 0) { 40829 // () - Global getter 40830 bool = !!animationsEnabled; 40831 } else { 40832 var hasElement = isElement(element); 40833 40834 if (!hasElement) { 40835 // (bool) - Global setter 40836 bool = animationsEnabled = !!element; 40837 } else { 40838 var node = getDomNode(element); 40839 40840 if (argCount === 1) { 40841 // (element) - Element getter 40842 bool = !disabledElementsLookup.get(node); 40843 } else { 40844 // (element, bool) - Element setter 40845 disabledElementsLookup.put(node, !bool); 40846 } 40847 } 40848 } 40849 40850 return bool; 40851 } 40852 }; 40853 40854 return $animate; 40855 40856 function queueAnimation(element, event, initialOptions) { 40857 // we always make a copy of the options since 40858 // there should never be any side effects on 40859 // the input data when running `$animateCss`. 40860 var options = copy(initialOptions); 40861 40862 var node, parent; 40863 element = stripCommentsFromElement(element); 40864 if (element) { 40865 node = getDomNode(element); 40866 parent = element.parent(); 40867 } 40868 40869 options = prepareAnimationOptions(options); 40870 40871 // we create a fake runner with a working promise. 40872 // These methods will become available after the digest has passed 40873 var runner = new $$AnimateRunner(); 40874 40875 // this is used to trigger callbacks in postDigest mode 40876 var runInNextPostDigestOrNow = postDigestTaskFactory(); 40877 40878 if (isArray(options.addClass)) { 40879 options.addClass = options.addClass.join(' '); 40880 } 40881 40882 if (options.addClass && !isString(options.addClass)) { 40883 options.addClass = null; 40884 } 40885 40886 if (isArray(options.removeClass)) { 40887 options.removeClass = options.removeClass.join(' '); 40888 } 40889 40890 if (options.removeClass && !isString(options.removeClass)) { 40891 options.removeClass = null; 40892 } 40893 40894 if (options.from && !isObject(options.from)) { 40895 options.from = null; 40896 } 40897 40898 if (options.to && !isObject(options.to)) { 40899 options.to = null; 40900 } 40901 40902 // there are situations where a directive issues an animation for
40903 // a jqLite wrapper that contains only comment nodes... If this 40904 // happens then there is no way we can perform an animation 40905 if (!node) { 40906 close(); 40907 return runner; 40908 } 40909 40910 var className = [node.className, options.addClass, options.removeClass].join(' '); 40911 if (!isAnimatableClassName(className)) { 40912 close(); 40913 return runner; 40914 } 40915 40916 var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0; 40917 40918 var documentHidden = $document[0].hidden; 40919 40920 // this is a hard disable of all animations for the application or on 40921 // the element itself, therefore there is no need to continue further 40922 // past this point if not enabled 40923 // Animations are also disabled if the document is currently hidden (page is not visible 40924 // to the user), because browsers slow down or do not flush calls to requestAnimationFrame 40925 var skipAnimations = !animationsEnabled || documentHidden || disabledElementsLookup.get(node); 40926 var existingAnimation = (!skipAnimations && activeAnimationsLookup.get(node)) || {}; 40927 var hasExistingAnimation = !!existingAnimation.state; 40928 40929 // there is no point in traversing the same collection of parent ancestors if a followup 40930 // animation will be run on the same element that already did all that checking work 40931 if (!skipAnimations && (!hasExistingAnimation || existingAnimation.state !== PRE_DIGEST_STATE)) { 40932 skipAnimations = !areAnimationsAllowed(element, parent, event); 40933 } 40934 40935 if (skipAnimations) { 40936 // Callbacks should fire even if the document is hidden (regression fix for issue #14120) 40937 if (documentHidden) notifyProgress(runner, event, 'start'); 40938 close(); 40939 if (documentHidden) notifyProgress(runner, event, 'close'); 40940 return runner; 40941 } 40942 40943 if (isStructural) { 40944 closeChildAnimations(element); 40945 } 40946 40947 var newAnimation = { 40948 structural: isStructural, 40949 element: element, 40950 event: event, 40951 addClass: options.addClass, 40952 removeClass: options.removeClass, 40953 close: close, 40954 options: options, 40955 runner: runner 40956 }; 40957 40958 if (hasExistingAnimation) { 40959 var skipAnimationFlag = isAllowed('skip', element, newAnimation, existingAnimation); 40960 if (skipAnimationFlag) { 40961 if (existingAnimation.state === RUNNING_STATE) { 40962 close(); 40963 return runner; 40964 } else { 40965 mergeAnimationDetails(element, existingAnimation, newAnimation); 40966 return existingAnimation.runner; 40967 } 40968 } 40969 var cancelAnimationFlag = isAllowed('cancel', element, newAnimation, existingAnimation); 40970 if (cancelAnimationFlag) { 40971 if (existingAnimation.state === RUNNING_STATE) { 40972 // this will end the animation right away and it is safe 40973 // to do so since the animation is already running and the 40974 // runner callback code will run in async 40975 existingAnimation.runner.end(); 40976 } else if (existingAnimation.structural) { 40977 // this means that the animation is queued into a digest, but 40978 // hasn't started yet. Therefore it is safe to run the close 40979 // method which will call the runner methods in async. 40980 existingAnimation.close(); 40981 } else { 40982 // this will merge the new animation options into existing animation options 40983 mergeAnimationDetails(element, existingAnimation, newAnimation); 40984 40985 return existingAnimation.runner; 40986 } 40987 } else { 40988 // a joined animation means that this animation will take over the existing one 40989 // so an example would involve a leave animation taking over an enter. Then when 40990 // the postDigest kicks in the enter will be ignored. 40991 var joinAnimationFlag = isAllowed('join', element, newAnimation, existingAnimation); 40992 if (joinAnimationFlag) { 40993 if (existingAnimation.state === RUNNING_STATE) { 40994 normalizeAnimationDetails(element, newAnimation); 40995 } else {
vendor: 4,089 bytes, lines 40996-41081
40996 applyGeneratedPreparationClasses(element, isStructural ? event : null, options); 40997 40998 event = newAnimation.event = existingAnimation.event; 40999 options = mergeAnimationDetails(element, existingAnimation, newAnimation); 41000 41001 //we return the same runner since only the option values of this animation will 41002 //be fed into the `existingAnimation`. 41003 return existingAnimation.runner; 41004 } 41005 } 41006 } 41007 } else { 41008 // normalization in this case means that it removes redundant CSS classes that 41009 // already exist (addClass) or do not exist (removeClass) on the element 41010 normalizeAnimationDetails(element, newAnimation); 41011 } 41012 41013 // when the options are merged and cleaned up we may end up not having to do 41014 // an animation at all, therefore we should check this before issuing a post 41015 // digest callback. Structural animations will always run no matter what. 41016 var isValidAnimation = newAnimation.structural; 41017 if (!isValidAnimation) { 41018 // animate (from/to) can be quickly checked first, otherwise we check if any classes are present 41019 isValidAnimation = (newAnimation.event === 'animate' && Object.keys(newAnimation.options.to || {}).length > 0) 41020 || hasAnimationClasses(newAnimation); 41021 } 41022 41023 if (!isValidAnimation) { 41024 close(); 41025 clearElementAnimationState(element); 41026 return runner; 41027 } 41028 41029 // the counter keeps track of cancelled animations 41030 var counter = (existingAnimation.counter || 0) + 1; 41031 newAnimation.counter = counter; 41032 41033 markElementAnimationState(element, PRE_DIGEST_STATE, newAnimation); 41034 41035 $rootScope.$$postDigest(function() { 41036 var animationDetails = activeAnimationsLookup.get(node); 41037 var animationCancelled = !animationDetails; 41038 animationDetails = animationDetails || {}; 41039 41040 // if addClass/removeClass is called before something like enter then the 41041 // registered parent element may not be present. The code below will ensure 41042 // that a final value for parent element is obtained 41043 var parentElement = element.parent() || []; 41044 41045 // animate/structural/class-based animations all have requirements. Otherwise there 41046 // is no point in performing an animation. The parent node must also be set. 41047 var isValidAnimation = parentElement.length > 0 41048 && (animationDetails.event === 'animate' 41049 || animationDetails.structural 41050 || hasAnimationClasses(animationDetails)); 41051 41052 // this means that the previous animation was cancelled 41053 // even if the follow-up animation is the same event 41054 if (animationCancelled || animationDetails.counter !== counter || !isValidAnimation) { 41055 // if another animation did not take over then we need 41056 // to make sure that the domOperation and options are 41057 // handled accordingly 41058 if (animationCancelled) { 41059 applyAnimationClasses(element, options); 41060 applyAnimationStyles(element, options); 41061 } 41062 41063 // if the event changed from something like enter to leave then we do 41064 // it, otherwise if it's the same then the end result will be the same too 41065 if (animationCancelled || (isStructural && animationDetails.event !== event)) { 41066 options.domOperation(); 41067 runner.end(); 41068 } 41069 41070 // in the event that the element animation was not cancelled or a follow-up animation 41071 // isn't allowed to animate from here then we need to clear the state of the element 41072 // so that any future animations won't read the expired animation data. 41073 if (!isValidAnimation) { 41074 clearElementAnimationState(element); 41075 } 41076 41077 return; 41078 } 41079 41080 // this combined multiple class to addClass / removeClass into a setClass event 41081 // so long as a structural event did not take over the animation
41082 event = !animationDetails.structural && hasAnimationClasses(animationDetails, true) 41083 ? 'setClass' 41084 : animationDetails.event; 41085 41086 markElementAnimationState(element, RUNNING_STATE); 41087 var realRunner = $$animation(element, event, animationDetails.options); 41088 41089 // this will update the runner's flow-control events based on 41090 // the `realRunner` object. 41091 runner.setHost(realRunner); 41092 notifyProgress(runner, event, 'start', {}); 41093 41094 realRunner.done(function(status) { 41095 close(!status); 41096 var animationDetails = activeAnimationsLookup.get(node); 41097 if (animationDetails && animationDetails.counter === counter) { 41098 clearElementAnimationState(getDomNode(element)); 41099 } 41100 notifyProgress(runner, event, 'close', {}); 41101 }); 41102 }); 41103 41104 return runner; 41105 41106 function notifyProgress(runner, event, phase, data) { 41107 runInNextPostDigestOrNow(function() { 41108 var callbacks = findCallbacks(parent, element, event); 41109 if (callbacks.length) { 41110 // do not optimize this call here to RAF because 41111 // we don't know how heavy the callback code here will 41112 // be and if this code is buffered then this can 41113 // lead to a performance regression. 41114 $$rAF(function() { 41115 forEach(callbacks, function(callback) { 41116 callback(element, phase, data); 41117 }); 41118 cleanupEventListeners(phase, element); 41119 }); 41120 } else { 41121 cleanupEventListeners(phase, element); 41122 } 41123 }); 41124 runner.progress(event, phase, data); 41125 } 41126 41127 function close(reject) { 41128 clearGeneratedClasses(element, options); 41129 applyAnimationClasses(element, options); 41130 applyAnimationStyles(element, options); 41131 options.domOperation(); 41132 runner.complete(!reject); 41133 } 41134 } 41135 41136 function closeChildAnimations(element) { 41137 var node = getDomNode(element); 41138 var children = node.querySelectorAll('[' + NG_ANIMATE_ATTR_NAME + ']'); 41139 forEach(children, function(child) { 41140 var state = parseInt(child.getAttribute(NG_ANIMATE_ATTR_NAME), 10); 41141 var animationDetails = activeAnimationsLookup.get(child); 41142 if (animationDetails) { 41143 switch (state) { 41144 case RUNNING_STATE: 41145 animationDetails.runner.end(); 41146 /* falls through */ 41147 case PRE_DIGEST_STATE: 41148 activeAnimationsLookup.remove(child); 41149 break; 41150 } 41151 } 41152 }); 41153 } 41154 41155 function clearElementAnimationState(element) { 41156 var node = getDomNode(element); 41157 node.removeAttribute(NG_ANIMATE_ATTR_NAME); 41158 activeAnimationsLookup.remove(node); 41159 } 41160 41161 function isMatchingElement(nodeOrElmA, nodeOrElmB) { 41162 return getDomNode(nodeOrElmA) === getDomNode(nodeOrElmB); 41163 } 41164 41165 /** 41166 * This fn returns false if any of the following is true: 41167 * a) animations on any parent element are disabled, and animations on the element aren't explicitly allowed 41168 * b) a parent element has an ongoing structural animation, and animateChildren is false 41169 * c) the element is not a child of the body 41170 * d) the element is not a child of the $rootElement 41171 */ 41172 function areAnimationsAllowed(element, parentElement, event) { 41173 var bodyElement = jqLite($document[0].body); 41174 var bodyElementDetected = isMatchingElement(element, bodyElement) || element[0].nodeName === 'HTML'; 41175 var rootElementDetected = isMatchingElement(element, $rootElement); 41176 var parentAnimationDetected = false; 41177 var animateChildren; 41178 var elementDisabled = disabledElementsLookup.get(getDomNode(element)); 41179 41180 var parentHost = jqLite.data(element[0], NG_ANIMATE_PIN_DATA); 41181 if (parentHost) { 41182 parentElement = parentHost; 41183 } 41184 41185 parentElement = getDomNode(parentElement); 41186 41187 while (parentElement) { 41188 if (!rootElementDetected) { 41189 // angular doesn't want to attempt to animate elements outside of the application 41190 // therefore we need to ensure that the rootElement is an ancestor of the current element 41191 rootElementDetected = isMatchingElement(parentElement, $rootElement); 41192 } 41193 41194 if (parentElement.nodeType !== ELEMENT_NODE) { 41195 // no point in inspecting the #document element 41196 break; 41197 } 41198 41199 var details = activeAnimationsLookup.get(parentElement) || {}; 41200 // either an enter, leave or move animation will commence 41201 // therefore we can't allow any animations to take place 41202 // but if a parent animation is class-based then that's ok 41203 if (!parentAnimationDetected) { 41204 var parentElementDisabled = disabledElementsLookup.get(parentElement); 41205 41206 if (parentElementDisabled === true && elementDisabled !== false) { 41207 // disable animations if the user hasn't explicitly enabled animations on the 41208 // current element 41209 elementDisabled = true; 41210 // element is disabled via parent element, no need to check anything else 41211 break; 41212 } else if (parentElementDisabled === false) { 41213 elementDisabled = false;
vendor: 4,789 bytes, lines 41214-41366
41214 } 41215 parentAnimationDetected = details.structural; 41216 } 41217 41218 if (isUndefined(animateChildren) || animateChildren === true) { 41219 var value = jqLite.data(parentElement, NG_ANIMATE_CHILDREN_DATA); 41220 if (isDefined(value)) { 41221 animateChildren = value; 41222 } 41223 } 41224 41225 // there is no need to continue traversing at this point 41226 if (parentAnimationDetected && animateChildren === false) break; 41227 41228 if (!bodyElementDetected) { 41229 // we also need to ensure that the element is or will be a part of the body element 41230 // otherwise it is pointless to even issue an animation to be rendered 41231 bodyElementDetected = isMatchingElement(parentElement, bodyElement); 41232 } 41233 41234 if (bodyElementDetected && rootElementDetected) { 41235 // If both body and root have been found, any other checks are pointless, 41236 // as no animation data should live outside the application 41237 break; 41238 } 41239 41240 if (!rootElementDetected) { 41241 // If no rootElement is detected, check if the parentElement is pinned to another element 41242 parentHost = jqLite.data(parentElement, NG_ANIMATE_PIN_DATA); 41243 if (parentHost) { 41244 // The pin target element becomes the next parent element 41245 parentElement = getDomNode(parentHost); 41246 continue; 41247 } 41248 } 41249 41250 parentElement = parentElement.parentNode; 41251 } 41252 41253 var allowAnimation = (!parentAnimationDetected || animateChildren) && elementDisabled !== true; 41254 return allowAnimation && rootElementDetected && bodyElementDetected; 41255 } 41256 41257 function markElementAnimationState(element, state, details) { 41258 details = details || {}; 41259 details.state = state; 41260 41261 var node = getDomNode(element); 41262 node.setAttribute(NG_ANIMATE_ATTR_NAME, state); 41263 41264 var oldValue = activeAnimationsLookup.get(node); 41265 var newValue = oldValue 41266 ? extend(oldValue, details) 41267 : details; 41268 activeAnimationsLookup.put(node, newValue); 41269 } 41270 }]; 41271}]; 41272 41273/* exported $$AnimationProvider */ 41274 41275var $$AnimationProvider = ['$animateProvider', /** @this */ function($animateProvider) { 41276 var NG_ANIMATE_REF_ATTR = 'ng-animate-ref'; 41277 41278 var drivers = this.drivers = []; 41279 41280 var RUNNER_STORAGE_KEY = '$$animationRunner'; 41281 41282 function setRunner(element, runner) { 41283 element.data(RUNNER_STORAGE_KEY, runner); 41284 } 41285 41286 function removeRunner(element) { 41287 element.removeData(RUNNER_STORAGE_KEY); 41288 } 41289 41290 function getRunner(element) { 41291 return element.data(RUNNER_STORAGE_KEY); 41292 } 41293 41294 this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$HashMap', '$$rAFScheduler', 41295 function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$HashMap, $$rAFScheduler) { 41296 41297 var animationQueue = []; 41298 var applyAnimationClasses = applyAnimationClassesFactory($$jqLite); 41299 41300 function sortAnimations(animations) { 41301 var tree = { children: [] }; 41302 var i, lookup = new $$HashMap(); 41303 41304 // this is done first beforehand so that the hashmap 41305 // is filled with a list of the elements that will be animated 41306 for (i = 0; i < animations.length; i++) { 41307 var animation = animations[i]; 41308 lookup.put(animation.domNode, animations[i] = { 41309 domNode: animation.domNode, 41310 fn: animation.fn, 41311 children: [] 41312 }); 41313 } 41314 41315 for (i = 0; i < animations.length; i++) { 41316 processNode(animations[i]); 41317 } 41318 41319 return flatten(tree); 41320 41321 function processNode(entry) { 41322 if (entry.processed) return entry; 41323 entry.processed = true; 41324 41325 var elementNode = entry.domNode; 41326 var parentNode = elementNode.parentNode; 41327 lookup.put(elementNode, entry); 41328 41329 var parentEntry; 41330 while (parentNode) { 41331 parentEntry = lookup.get(parentNode); 41332 if (parentEntry) { 41333 if (!parentEntry.processed) { 41334 parentEntry = processNode(parentEntry); 41335 } 41336 break; 41337 } 41338 parentNode = parentNode.parentNode; 41339 } 41340 41341 (parentEntry || tree).children.push(entry); 41342 return entry; 41343 } 41344 41345 function flatten(tree) { 41346 var result = []; 41347 var queue = []; 41348 var i; 41349 41350 for (i = 0; i < tree.children.length; i++) { 41351 queue.push(tree.children[i]); 41352 } 41353 41354 var remainingLevelEntries = queue.length; 41355 var nextLevelEntries = 0; 41356 var row = []; 41357 41358 for (i = 0; i < queue.length; i++) { 41359 var entry = queue[i]; 41360 if (remainingLevelEntries <= 0) { 41361 remainingLevelEntries = nextLevelEntries; 41362 nextLevelEntries = 0; 41363 result.push(row); 41364 row = []; 41365 } 41366 row.push(entry.fn);
41367 entry.children.forEach(function(childEntry) { 41368 nextLevelEntries++; 41369 queue.push(childEntry); 41370 }); 41371 remainingLevelEntries--; 41372 } 41373 41374 if (row.length) { 41375 result.push(row); 41376 } 41377 41378 return result; 41379 } 41380 } 41381 41382 // TODO(matsko): document the signature in a better way 41383 return function(element, event, options) { 41384 options = prepareAnimationOptions(options); 41385 var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0; 41386 41387 // there is no animation at the current moment, however 41388 // these runner methods will get later updated with the 41389 // methods leading into the driver's end/cancel methods 41390 // for now they just stop the animation from starting 41391 var runner = new $$AnimateRunner({ 41392 end: function() { close(); }, 41393 cancel: function() { close(true); } 41394 }); 41395 41396 if (!drivers.length) { 41397 close(); 41398 return runner; 41399 } 41400 41401 setRunner(element, runner); 41402 41403 var classes = mergeClasses(element.attr('class'), mergeClasses(options.addClass, options.removeClass)); 41404 var tempClasses = options.tempClasses; 41405 if (tempClasses) { 41406 classes += ' ' + tempClasses; 41407 options.tempClasses = null; 41408 } 41409 41410 var prepareClassName; 41411 if (isStructural) { 41412 prepareClassName = 'ng-' + event + PREPARE_CLASS_SUFFIX; 41413 $$jqLite.addClass(element, prepareClassName); 41414 } 41415 41416 animationQueue.push({ 41417 // this data is used by the postDigest code and passed into 41418 // the driver step function 41419 element: element, 41420 classes: classes, 41421 event: event, 41422 structural: isStructural, 41423 options: options, 41424 beforeStart: beforeStart, 41425 close: close 41426 }); 41427 41428 element.on('$destroy', handleDestroyedElement); 41429 41430 // we only want there to be one function called within the post digest 41431 // block. This way we can group animations for all the animations that 41432 // were apart of the same postDigest flush call. 41433 if (animationQueue.length > 1) return runner; 41434 41435 $rootScope.$$postDigest(function() { 41436 var animations = []; 41437 forEach(animationQueue, function(entry) { 41438 // the element was destroyed early on which removed the runner 41439 // form its storage. This means we can't animate this element 41440 // at all and it already has been closed due to destruction. 41441 if (getRunner(entry.element)) { 41442 animations.push(entry); 41443 } else { 41444 entry.close(); 41445 } 41446 }); 41447 41448 // now any future animations will be in another postDigest 41449 animationQueue.length = 0; 41450 41451 var groupedAnimations = groupAnimations(animations); 41452 var toBeSortedAnimations = []; 41453 41454 forEach(groupedAnimations, function(animationEntry) { 41455 toBeSortedAnimations.push({ 41456 domNode: getDomNode(animationEntry.from ? animationEntry.from.element : animationEntry.element), 41457 fn: function triggerAnimationStart() { 41458 // it's important that we apply the `ng-animate` CSS class and the 41459 // temporary classes before we do any driver invoking since these 41460 // CSS classes may be required for proper CSS detection. 41461 animationEntry.beforeStart(); 41462 41463 var startAnimationFn, closeFn = animationEntry.close; 41464 41465 // in the event that the element was removed before the digest runs or 41466 // during the RAF sequencing then we should not trigger the animation. 41467 var targetElement = animationEntry.anchors 41468 ? (animationEntry.from.element || animationEntry.to.element) 41469 : animationEntry.element; 41470 41471 if (getRunner(targetElement)) { 41472 var operation = invokeFirstDriver(animationEntry); 41473 if (operation) { 41474 startAnimationFn = operation.start; 41475 } 41476 } 41477 41478 if (!startAnimationFn) { 41479 closeFn(); 41480 } else { 41481 var animationRunner = startAnimationFn(); 41482 animationRunner.done(function(status) { 41483 closeFn(!status); 41484 }); 41485 updateAnimationRunners(animationEntry, animationRunner); 41486 } 41487 } 41488 }); 41489 }); 41490 41491 // we need to sort each of the animations in order of parent to child 41492 // relationships. This ensures that the child classes are applied at the 41493 // right time. 41494 $$rAFScheduler(sortAnimations(toBeSortedAnimations)
vendor: 4,673 bytes, lines 41494-41632
41494); 41495 }); 41496 41497 return runner; 41498 41499 // TODO(matsko): change to reference nodes 41500 function getAnchorNodes(node) { 41501 var SELECTOR = '[' + NG_ANIMATE_REF_ATTR + ']'; 41502 var items = node.hasAttribute(NG_ANIMATE_REF_ATTR) 41503 ? [node] 41504 : node.querySelectorAll(SELECTOR); 41505 var anchors = []; 41506 forEach(items, function(node) { 41507 var attr = node.getAttribute(NG_ANIMATE_REF_ATTR); 41508 if (attr && attr.length) { 41509 anchors.push(node); 41510 } 41511 }); 41512 return anchors; 41513 } 41514 41515 function groupAnimations(animations) { 41516 var preparedAnimations = []; 41517 var refLookup = {}; 41518 forEach(animations, function(animation, index) { 41519 var element = animation.element; 41520 var node = getDomNode(element); 41521 var event = animation.event; 41522 var enterOrMove = ['enter', 'move'].indexOf(event) >= 0; 41523 var anchorNodes = animation.structural ? getAnchorNodes(node) : []; 41524 41525 if (anchorNodes.length) { 41526 var direction = enterOrMove ? 'to' : 'from'; 41527 41528 forEach(anchorNodes, function(anchor) { 41529 var key = anchor.getAttribute(NG_ANIMATE_REF_ATTR); 41530 refLookup[key] = refLookup[key] || {}; 41531 refLookup[key][direction] = { 41532 animationID: index, 41533 element: jqLite(anchor) 41534 }; 41535 }); 41536 } else { 41537 preparedAnimations.push(animation); 41538 } 41539 }); 41540 41541 var usedIndicesLookup = {}; 41542 var anchorGroups = {}; 41543 forEach(refLookup, function(operations, key) { 41544 var from = operations.from; 41545 var to = operations.to; 41546 41547 if (!from || !to) { 41548 // only one of these is set therefore we can't have an 41549 // anchor animation since all three pieces are required 41550 var index = from ? from.animationID : to.animationID; 41551 var indexKey = index.toString(); 41552 if (!usedIndicesLookup[indexKey]) { 41553 usedIndicesLookup[indexKey] = true; 41554 preparedAnimations.push(animations[index]); 41555 } 41556 return; 41557 } 41558 41559 var fromAnimation = animations[from.animationID]; 41560 var toAnimation = animations[to.animationID]; 41561 var lookupKey = from.animationID.toString(); 41562 if (!anchorGroups[lookupKey]) { 41563 var group = anchorGroups[lookupKey] = { 41564 structural: true, 41565 beforeStart: function() { 41566 fromAnimation.beforeStart(); 41567 toAnimation.beforeStart(); 41568 }, 41569 close: function() { 41570 fromAnimation.close(); 41571 toAnimation.close(); 41572 }, 41573 classes: cssClassesIntersection(fromAnimation.classes, toAnimation.classes), 41574 from: fromAnimation, 41575 to: toAnimation, 41576 anchors: [] // TODO(matsko): change to reference nodes 41577 }; 41578 41579 // the anchor animations require that the from and to elements both have at least 41580 // one shared CSS class which effectively marries the two elements together to use 41581 // the same animation driver and to properly sequence the anchor animation. 41582 if (group.classes.length) { 41583 preparedAnimations.push(group); 41584 } else { 41585 preparedAnimations.push(fromAnimation); 41586 preparedAnimations.push(toAnimation); 41587 } 41588 } 41589 41590 anchorGroups[lookupKey].anchors.push({ 41591 'out': from.element, 'in': to.element 41592 }); 41593 }); 41594 41595 return preparedAnimations; 41596 } 41597 41598 function cssClassesIntersection(a,b) { 41599 a = a.split(' '); 41600 b = b.split(' '); 41601 var matches = []; 41602 41603 for (var i = 0; i < a.length; i++) { 41604 var aa = a[i]; 41605 if (aa.substring(0,3) === 'ng-') continue; 41606 41607 for (var j = 0; j < b.length; j++) { 41608 if (aa === b[j]) { 41609 matches.push(aa); 41610 break; 41611 } 41612 } 41613 } 41614 41615 return matches.join(' '); 41616 } 41617 41618 function invokeFirstDriver(animationDetails) { 41619 // we loop in reverse order since the more general drivers (like CSS and JS) 41620 // may attempt more elements, but custom drivers are more particular 41621 for (var i = drivers.length - 1; i >= 0; i--) { 41622 var driverName = drivers[i]; 41623 var factory = $injector.get(driverName); 41624 var driver = factory(animationDetails); 41625 if (driver) { 41626 return driver; 41627 } 41628 } 41629 } 41630 41631 function beforeStart() { 41632
vendor: 7,723 bytes, lines 41632-41825
41632element.addClass(NG_ANIMATE_CLASSNAME); 41633 if (tempClasses) { 41634 $$jqLite.addClass(element, tempClasses); 41635 } 41636 if (prepareClassName) { 41637 $$jqLite.removeClass(element, prepareClassName); 41638 prepareClassName = null; 41639 } 41640 } 41641 41642 function updateAnimationRunners(animation, newRunner) { 41643 if (animation.from && animation.to) { 41644 update(animation.from.element); 41645 update(animation.to.element); 41646 } else { 41647 update(animation.element); 41648 } 41649 41650 function update(element) { 41651 var runner = getRunner(element); 41652 if (runner) runner.setHost(newRunner); 41653 } 41654 } 41655 41656 function handleDestroyedElement() { 41657 var runner = getRunner(element); 41658 if (runner && (event !== 'leave' || !options.$$domOperationFired)) { 41659 runner.end(); 41660 } 41661 } 41662 41663 function close(rejected) { 41664 element.off('$destroy', handleDestroyedElement); 41665 removeRunner(element); 41666 41667 applyAnimationClasses(element, options); 41668 applyAnimationStyles(element, options); 41669 options.domOperation(); 41670 41671 if (tempClasses) { 41672 $$jqLite.removeClass(element, tempClasses); 41673 } 41674 41675 element.removeClass(NG_ANIMATE_CLASSNAME); 41676 runner.complete(!rejected); 41677 } 41678 }; 41679 }]; 41680}]; 41681 41682/** 41683 * @ngdoc directive 41684 * @name ngAnimateSwap 41685 * @restrict A 41686 * @scope 41687 * 41688 * @description 41689 * 41690 * ngAnimateSwap is a animation-oriented directive that allows for the container to 41691 * be removed and entered in whenever the associated expression changes. A 41692 * common usecase for this directive is a rotating banner or slider component which 41693 * contains one image being present at a time. When the active image changes 41694 * then the old image will perform a `leave` animation and the new element 41695 * will be inserted via an `enter` animation. 41696 * 41697 * @animations 41698 * | Animation | Occurs | 41699 * |----------------------------------|--------------------------------------| 41700 * | {@link ng.$animate#enter enter} | when the new element is inserted to the DOM | 41701 * | {@link ng.$animate#leave leave} | when the old element is removed from the DOM | 41702 * 41703 * @example 41704 * <example name="ngAnimateSwap-directive" module="ngAnimateSwapExample" 41705 * deps="angular-animate.js" 41706 * animations="true" fixBase="true"> 41707 * <file name="index.html"> 41708 * <div class="container" ng-controller="AppCtrl"> 41709 * <div ng-animate-swap="number" class="cell swap-animation" ng-class="colorClass(number)"> 41710 * {{ number }} 41711 * </div> 41712 * </div> 41713 * </file> 41714 * <file name="script.js"> 41715 * angular.module('ngAnimateSwapExample', ['ngAnimate']) 41716 * .controller('AppCtrl', ['$scope', '$interval', function($scope, $interval) { 41717 * $scope.number = 0; 41718 * $interval(function() { 41719 * $scope.number++; 41720 * }, 1000); 41721 * 41722 * var colors = ['red','blue','green','yellow','orange']; 41723 * $scope.colorClass = function(number) { 41724 * return colors[number % colors.length]; 41725 * }; 41726 * }]); 41727 * </file> 41728 * <file name="animations.css"> 41729 * .container { 41730 * height:250px; 41731 * width:250px; 41732 * position:relative; 41733 * overflow:hidden; 41734 * border:2px solid black; 41735 * } 41736 * .container .cell { 41737 * font-size:150px; 41738 * text-align:center; 41739 * line-height:250px; 41740 * position:absolute; 41741 * top:0; 41742 * left:0; 41743 * right:0; 41744 * border-bottom:2px solid black; 41745 * } 41746 * .swap-animation.ng-enter, .swap-animation.ng-leave { 41747 * transition:0.5s linear all; 41748 * } 41749 * .swap-animation.ng-enter { 41750 * top:-250px; 41751 * } 41752 * .swap-animation.ng-enter-active { 41753 * top:0px; 41754 * } 41755 * .swap-animation.ng-leave { 41756 * top:0px; 41757 * } 41758 * .swap-animation.ng-leave-active { 41759 * top:250px; 41760 * } 41761 * .red { background:red; } 41762 * .green { background:green; } 41763 * .blue { background:blue; } 41764 * .yellow { background:yellow; } 41765 * .orange { background:orange; } 41766 * </file> 41767 * </example> 41768 */ 41769var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $rootScope) { 41770 return { 41771 restrict: 'A', 41772 transclude: 'element', 41773 terminal: true, 41774 priority: 600, // we use 600 here to ensure that the directive is caught before others 41775 link: function(scope, $element, attrs, ctrl, $transclude) { 41776 var previousElement, previousScope; 41777 scope.$watchCollection(attrs.ngAnimateSwap || attrs['for'], function(value) { 41778 if (previousElement) { 41779 $animate.leave(previousElement); 41780 } 41781 if (previousScope) { 41782 previousScope.$destroy(); 41783 previousScope = null; 41784 } 41785 if (value || value === 0) { 41786 previousScope = scope.$new(); 41787 $transclude(previousScope, function(element) { 41788 previousElement = element; 41789 $animate.enter(element, null, $element); 41790 }); 41791 } 41792 }); 41793 } 41794 }; 41795}]; 41796 41797/** 41798 * @ngdoc module 41799 * @name ngAnimate 41800 * @description 41801 * 41802 * The `ngAnimate` module provides support for CSS-based animations (keyframes and transitions) as well as JavaScript-based animations via 41803 * callback hooks. Animations are not enabled by default, however, by including `ngAnimate` the animation hooks are enabled for an Angular app. 41804 * 41805 * <div doc-module-components="ngAnimate"></div> 41806 * 41807 * # Usage 41808 * Simply put, there are two ways to make use of animations when ngAnimate is used: by using **CSS** and **JavaScript**. The former works purely based 41809 * using CSS (by using matching CSS selectors/styles) and the latter triggers animations that are registered via `module.animation()`. For 41810 * both CSS and JS animations the sole requirement is to have a matching `CSS class` that exists both in the registered animation and within 41811 * the HTML element that the animation will be triggered on. 41812 * 41813 * ## Directive Support 41814 * The following directives are "animation aware": 41815 * 41816 * | Directive | Supported Animations | 41817 * |----------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------| 41818 * | {@link ng.directive:ngRepeat#animations ngRepeat} | enter, leave and move | 41819 * | {@link ngRoute.directive:ngView#animations ngView} | enter and leave | 41820 * | {@link ng.directive:ngInclude#animations ngInclude} | enter and leave | 41821 * | {@link ng.directive:ngSwitch#animations ngSwitch} | enter and leave | 41822 * | {@link ng.directive:ngIf#animations ngIf} | enter and leave | 41823 * | {@link ng.directive:ngClass#animations ngClass} | add and remove (the CSS class(es) present) | 41824 * | {@link ng.directive:ngShow#animations ngShow} & {@link ng.directive:ngHide#animations ngHide} | add and remove (the ng-hide class value) | 41825 * | {@link ng.directive:form#animation-hooks form} & {@link ng.directive:ngModel#animation-hooks ngModel}
41825 | add and remove (dirty, pristine, valid, invalid & all other validations) | 41826 * | {@link module:ngMessages#animations ngMessages} | add and remove (ng-active & ng-inactive) | 41827 * | {@link module:ngMessages#animations ngMessage} | enter and leave | 41828 * 41829 * (More information can be found by visiting each the documentation associated with each directive.) 41830 * 41831 * ## CSS-based Animations 41832 * 41833 * CSS-based animations with ngAnimate are unique since they require no JavaScript code at all. By using a CSS class that we reference between our HTML 41834 * and CSS code we can create an animation that will be picked up by Angular when an underlying directive performs an operation. 41835 * 41836 * The example below shows how an `enter` animation can be made possible on an element using `ng-if`: 41837 * 41838 * ```html 41839 * <div ng-if="bool" class="fade"> 41840 * Fade me in out 41841 * </div> 41842 * <button ng-click="bool=true">Fade In!</button> 41843 * <button ng-click="bool=false">Fade Out!</button> 41844 * ``` 41845 * 41846 * Notice the CSS class **fade**? We can now create the CSS transition code that references this class: 41847 * 41848 * ```css 41849 * /* The starting CSS styles for the enter animation */ 41850 * .fade.ng-enter { 41851 * transition:0.5s linear all; 41852 * opacity:0; 41853 * } 41854 * 41855 * /* The finishing CSS styles for the enter animation */ 41856 * .fade.ng-enter.ng-enter-active { 41857 * opacity:1; 41858 * } 41859 * ``` 41860 * 41861 * The key thing to remember here is that, depending on the animation event (which each of the directives above trigger depending on what's going on) two 41862 * generated CSS classes will be applied to the element; in the example above we have `.ng-enter` and `.ng-enter-active`. For CSS transitions, the transition 41863 * code **must** be defined within the starting CSS class (in this case `.ng-enter`). The destination class is what the transition will animate towards. 41864 * 41865 * If for example we wanted to create animations for `leave` and `move` (ngRepeat triggers move) then we can do so using the same CSS naming conventions: 41866 * 41867 * ```css 41868 * /* now the element will fade out before it is removed from the DOM */ 41869 * .fade.ng-leave { 41870 * transition:0.5s linear all; 41871 * opacity:1; 41872 * } 41873 * .fade.ng-leave.ng-leave-active { 41874 * opacity:0; 41875 * } 41876 * ``` 41877 * 41878 * We can also make use of **CSS Keyframes** by referencing the keyframe animation within the starting CSS class: 41879 * 41880 * ```css 41881 * /* there is no need to define anything inside of the destination 41882 * CSS class since the keyframe will take charge of the animation */ 41883 * .fade.ng-leave { 41884 * animation: my_fade_animation 0.5s linear; 41885 * -webkit-animation: my_fade_animation 0.5s linear; 41886 * } 41887 * 41888 * @keyframes my_fade_animation { 41889 * from { opacity:1; } 41890 * to { opacity:0; } 41891 * } 41892 * 41893 * @-webkit-keyframes my_fade_animation { 41894 * from { opacity:1; } 41895 * to { opacity:0; } 41896 * } 41897 * ``` 41898 * 41899 * Feel free also mix transitions and keyframes together as well as any other CSS classes on the same element. 41900 * 41901 * ### CSS Class-based Animations 41902 * 41903 * Class-based animations (animations that are triggered via `ngClass`, `ngShow`, `ngHide` and some other directives) have a slightly different 41904 * naming convention. Class-based animations are basic enough that a standard transition or keyframe can be referenced on the class being added 41905 * and removed. 41906 * 41907 * For example if we wanted to do a CSS animation for `ngHide` then we place an animation on the `.ng-hide` CSS class: 41908 * 41909 * ```html 41910 * <div ng-show="bool" class="fade"> 41911 * Show and hide me 41912 * </div> 41913 * <button ng-click="bool=!bool">Toggle</button> 41914 * 41915 * <style> 41916 * .fade.ng-hide { 41917 * transition:0.5s linear all; 41918 * opacity:0; 41919 * } 41920 * </style> 41921 * ``` 41922 * 41923 * All that is going on here with ngShow/ngHide behind the scenes is the `.ng-hide` class is added/removed (when the hidden state is valid). Since 41924 * ngShow and ngHide are animation aware then we can match up a transition and ngAnimate handles the rest. 41925 * 41926 * In addition the addition and removal of the CSS class, ngAnimate also provides two helper methods that we can use to further decorate the animation 41927 * with CSS styles. 41928 * 41929 * ```html 41930 * <div ng-class="{on:onOff}" class="highlight"> 41931 * Highlight this box 41932 * </div> 41933 * <button ng-click="onOff=!onOff">Toggle</button> 41934 * 41935 * <style> 41936 * .highlight { 41937 * transition:0.5s linear all; 41938 * } 41939 * .highlight.on-add { 41940 * background:white; 41941 * } 41942 * .highlight.on { 41943 * background:yellow; 41944 * } 41945 * .highlight.on-remove { 41946 * background:black; 41947 * } 41948 * </style> 41949 * ``` 41950 * 41951 * We can also make use of CSS keyframes by placing them within the CSS classes. 41952 * 41953 * 41954 * ### CSS Staggering Animations 41955 * A Staggering animation is a collection of animations that are issued with a slight delay in between each successive operation resulting in a 41956 * curtain-like effect. The ngAnimate module (versions >
41956=1.2) supports staggering animations and the stagger effect can be 41957 * performed by creating a **ng-EVENT-stagger** CSS class and attaching that class to the base CSS class used for 41958 * the animation. The style property expected within the stagger class can either be a **transition-delay** or an 41959 * **animation-delay** property (or both if your animation contains both transitions and keyframe animations). 41960 * 41961 * ```css 41962 * .my-animation.ng-enter { 41963 * /* standard transition code */ 41964 * transition: 1s linear all; 41965 * opacity:0; 41966 * } 41967 * .my-animation.ng-enter-stagger { 41968 * /* this will have a 100ms delay between each successive leave animation */ 41969 * transition-delay: 0.1s; 41970 * 41971 * /* As of 1.4.4, this must always be set: it signals ngAnimate 41972 * to not accidentally inherit a delay property from another CSS class */ 41973 * transition-duration: 0s; 41974 * } 41975 * .my-animation.ng-enter.ng-enter-active { 41976 * /* standard transition styles */ 41977 * opacity:1; 41978 * } 41979 * ``` 41980 * 41981 * Staggering animations work by default in ngRepeat (so long as the CSS class is defined). Outside of ngRepeat, to use staggering animations 41982 * on your own, they can be triggered by firing multiple calls to the same event on $animate. However, the restrictions surrounding this 41983 * are that each of the elements must have the same CSS className value as well as the same parent element. A stagger operation 41984 * will also be reset if one or more animation frames have passed since the multiple calls to `$animate` were fired. 41985 * 41986 * The following code will issue the **ng-leave-stagger** event on the element provided: 41987 * 41988 * ```js 41989 * var kids = parent.children(); 41990 * 41991 * $animate.leave(kids[0]); //stagger index=0 41992 * $animate.leave(kids[1]); //stagger index=1 41993 * $animate.leave(kids[2]); //stagger index=2 41994 * $animate.leave(kids[3]); //stagger index=3 41995 * $animate.leave(kids[4]); //stagger index=4 41996 * 41997 * window.requestAnimationFrame(function() { 41998 * //stagger has reset itself 41999 * $animate.leave(kids[5]); //stagger index=0 42000 * $animate.leave(kids[6]); //stagger index=1 42001 * 42002 * $scope.$digest(); 42003 * }); 42004 * ``` 42005 * 42006 * Stagger animations are currently only supported within CSS-defined animations. 42007 * 42008 * ### The `ng-animate` CSS class 42009 * 42010 * When ngAnimate is animating an element it will apply the `ng-animate` CSS class to the element for the duration of the animation. 42011 * This is a temporary CSS class and it will be removed once the animation is over (for both JavaScript and CSS-based animations). 42012 * 42013 * Therefore, animations can be applied to an element using this temporary class directly via CSS. 42014 * 42015 * ```css 42016 * .zipper.ng-animate { 42017 * transition:0.5s linear all; 42018 * } 42019 * .zipper.ng-enter { 42020 * opacity:0; 42021 * } 42022 * .zipper.ng-enter.ng-enter-active { 42023 * opacity:1; 42024 * } 42025 * .zipper.ng-leave { 42026 * opacity:1; 42027 * } 42028 * .zipper.ng-leave.ng-leave-active { 42029 * opacity:0; 42030 * } 42031 * ``` 42032 * 42033 * (Note that the `ng-animate` CSS class is reserved and it cannot be applied on an element directly since ngAnimate will always remove 42034 * the CSS class once an animation has completed.) 42035 * 42036 * 42037 * ### The `ng-[event]-prepare` class 42038 * 42039 * This is a special class that can be used to prevent unwanted flickering / flash of content before 42040 * the actual animation starts. The class is added as soon as an animation is initialized, but removed 42041 * before the actual animation starts (after waiting for a $digest). 42042 * It is also only added for *structural* animations (`enter`, `move`, and `leave`). 42043 * 42044 * In practice, flickering can appear when nesting elements with structural animations such as `ngIf` 42045 * into elements that have class-based animations such as `ngClass`. 42046 * 42047 * ```html 42048 * <div ng-class="{red: myProp}"> 42049 * <div ng-class="{blue: myProp}"> 42050 * <div class="message" ng-if="myProp"></div> 42051 * </div> 42052 * </div> 42053 * ``` 42054 * 42055 * It is possible that during the `enter` animation, the `.message` div will be briefly visible before it starts animating. 42056 * In that case, you can add styles to the CSS that make sure the element stays hidden before the animation starts: 42057 * 42058 * ```css 42059 * .message.ng-enter-prepare { 42060 * opacity: 0; 42061 * } 42062 * 42063 * ``` 42064 * 42065 * ## JavaScript-based Animations 42066 * 42067 * ngAnimate also allows for animations to be consumed by JavaScript code. The approach is similar to CSS-based animations (where there is a shared 42068 * CSS class that is referenced in our HTML code) but in addition we need to register the JavaScript animation on the module. By making use of the 42069 * `module.animation()` module function we can register the animation. 42070 * 42071 * Let's see an example of a enter/leave animation using `ngRepeat`: 42072 * 42073 * ```html 42074 * <div ng-repeat="item in items" class="slide"> 42075 * {{ item }} 42076 * </div> 42077 * ``` 42078 * 42079 * See the **slide** CSS class? Let's use that class to define an animation that we'll structure in our module code by using `module.animation`: 42080 * 42081 * ```js 42082 * myModule.animation('.slide', [function() { 42083 * return { 42084 * // make note that other events (like addClass/removeClass) 42085 * // have different function input parameters 42086 * enter: function(element, doneFn) { 42087 * jQuery(element).fadeIn(1000, doneFn); 42088 * 42089 * // remember to call doneFn so that angular 42090 * // knows that the animation has concluded 42091 * }, 42092 * 42093 * move: function(element, doneFn) { 42094 * jQuery(element).fadeIn(1000, doneFn); 42095 * }, 42096 * 42097 * leave: function(element, doneFn) { 42098 * jQuery(element).fadeOut(1000, doneFn); 42099 * } 42100 * } 42101 * }]); 42102 * ``` 42103 * 42104 * The nice thing about JS-based animations is that we can inject other services and make use of advanced animation libraries such as 42105 * greensock.js and velocity.js. 42106 * 42107 * If our animation code class-based (meaning that something like `ngClass`, `ngHide` and `ngShow` triggers it) then we can still define 42108 * our animations inside of the same registered animation, however, the function input arguments are a bit different: 42109 * 42110 * ```html 42111 * <div ng-class="color" class="colorful"> 42112 * this box is moody 42113 * </div> 42114 * <button ng-click="color='red'">Change to red</button> 42115 * <button ng-click="color='blue'">Change to blue</button> 42116 * <button ng-click="color='green'">Change to green</button> 42117 * ``` 42118 * 42119 * ```js 42120 * myModule.animation('.colorful', [function() { 42121 * return { 42122 * addClass: function(element, className, doneFn) {
42123 * // do some cool animation and call the doneFn 42124 * }, 42125 * removeClass: function(element, className, doneFn) { 42126 * // do some cool animation and call the doneFn 42127 * }, 42128 * setClass: function(element, addedClass, removedClass, doneFn) { 42129 * // do some cool animation and call the doneFn 42130 * } 42131 * } 42132 * }]); 42133 * ``` 42134 * 42135 * ## CSS + JS Animations Together 42136 * 42137 * AngularJS 1.4 and higher has taken steps to make the amalgamation of CSS and JS animations more flexible. However, unlike earlier versions of Angular, 42138 * defining CSS and JS animations to work off of the same CSS class will not work anymore. Therefore the example below will only result in **JS animations taking 42139 * charge of the animation**: 42140 * 42141 * ```html 42142 * <div ng-if="bool" class="slide"> 42143 * Slide in and out 42144 * </div> 42145 * ``` 42146 * 42147 * ```js 42148 * myModule.animation('.slide', [function() { 42149 * return { 42150 * enter: function(element, doneFn) { 42151 * jQuery(element).slideIn(1000, doneFn); 42152 * } 42153 * } 42154 * }]); 42155 * ``` 42156 * 42157 * ```css 42158 * .slide.ng-enter { 42159 * transition:0.5s linear all; 42160 * transform:translateY(-100px); 42161 * } 42162 * .slide.ng-enter.ng-enter-active { 42163 * transform:translateY(0); 42164 * } 42165 * ``` 42166 * 42167 * Does this mean that CSS and JS animations cannot be used together? Do JS-based animations always have higher priority? We can make up for the 42168 * lack of CSS animations by using the `$animateCss` service to trigger our own tweaked-out, CSS-based animations directly from 42169 * our own JS-based animation code: 42170 * 42171 * ```js 42172 * myModule.animation('.slide', ['$animateCss', function($animateCss) { 42173 * return { 42174 * enter: function(element) { 42175* // this will trigger `.slide.ng-enter` and `.slide.ng-enter-active`. 42176 * return $animateCss(element, { 42177 * event: 'enter', 42178 * structural: true 42179 * }); 42180 * } 42181 * } 42182 * }]); 42183 * ``` 42184 * 42185 * The nice thing here is that we can save bandwidth by sticking to our CSS-based animation code and we don't need to rely on a 3rd-party animation framework. 42186 * 42187 * The `$animateCss` service is very powerful since we can feed in all kinds of extra properties that will be evaluated and fed into a CSS transition or 42188 * keyframe animation. For example if we wanted to animate the height of an element while adding and removing classes then we can do so by providing that 42189 * data into `$animateCss` directly: 42190 * 42191 * ```js 42192 * myModule.animation('.slide', ['$animateCss', function($animateCss) { 42193 * return { 42194 * enter: function(element) { 42195 * return $animateCss(element, { 42196 * event: 'enter', 42197 * structural: true, 42198 * addClass: 'maroon-setting', 42199 * from: { height:0 }, 42200 * to: { height: 200 } 42201 * }); 42202 * } 42203 * } 42204 * }]); 42205 * ``` 42206 * 42207 * Now we can fill in the rest via our transition CSS code: 42208 * 42209 * ```css 42210 * /* the transition tells ngAnimate to make the animation happen */ 42211 * .slide.ng-enter { transition:0.5s linear all; } 42212 * 42213 * /* this extra CSS class will be absorbed into the transition 42214 * since the $animateCss code is adding the class */ 42215 * .maroon-setting { background:red; } 42216 * ``` 42217 * 42218 * And `$animateCss` will figure out the rest. Just make sure to have the `done()` callback fire the `doneFn` function to signal when the animation is over. 42219 * 42220 * To learn more about what's possible be sure to visit the {@link ngAnimate.$animateCss $animateCss service}. 42221 * 42222 * ## Animation Anchoring (via `ng-animate-ref`) 42223 * 42224 * ngAnimate in AngularJS 1.4 comes packed with the ability to cross-animate elements between 42225 * structural areas of an application (like views) by pairing up elements using an attribute 42226 * called `ng-animate-ref`. 42227 * 42228 * Let's say for example we have two views that are managed by `ng-view` and we want to show 42229 * that there is a relationship between two components situated in within these views. By using the 42230 * `ng-animate-ref` attribute we can identify that the two components are paired together and we 42231 * can then attach an animation, which is triggered when the view changes. 42232 * 42233 * Say for example we have the following template code: 42234 * 42235 * ```html 42236 * <!-- index.html --> 42237 * <div ng-view class="view-animation"> 42238 * </div> 42239 * 42240 * <!-- home.html --> 42241 * <a href="#/banner-page"> 42242 * <img src="./banner.jpg" class="banner" ng-animate-ref="banner"> 42243 * </a> 42244 * 42245 * <!-- banner-page.html --> 42246 * <img src="./banner.jpg" class="banner" ng-animate-ref="banner"> 42247 * ``` 42248 *
42249 * Now, when the view changes (once the link is clicked), ngAnimate will examine the 42250 * HTML contents to see if there is a match reference between any components in the view 42251 * that is leaving and the view that is entering. It will scan both the view which is being 42252 * removed (leave) and inserted (enter) to see if there are any paired DOM elements that 42253 * contain a matching ref value. 42254 * 42255 * The two images match since they share the same ref value. ngAnimate will now create a 42256 * transport element (which is a clone of the first image element) and it will then attempt 42257 * to animate to the position of the second image element in the next view. For the animation to 42258 * work a special CSS class called `ng-anchor` will be added to the transported element. 42259 * 42260 * We can now attach a transition onto the `.banner.ng-anchor` CSS class and then 42261 * ngAnimate will handle the entire transition for us as well as the addition and removal of 42262 * any changes of CSS classes between the elements: 42263 * 42264 * ```css 42265 * .banner.ng-anchor { 42266 * /* this animation will last for 1 second since there are 42267 * two phases to the animation (an `in` and an `out` phase) */ 42268 * transition:0.5s linear all; 42269 * } 42270 * ``` 42271 * 42272 * We also **must** include animations for the views that are being entered and removed 42273 * (otherwise anchoring wouldn't be possible since the new view would be inserted right away). 42274 * 42275 * ```css 42276 * .view-animation.ng-enter, .view-animation.ng-leave { 42277 * transition:0.5s linear all; 42278 * position:fixed; 42279 * left:0; 42280 * top:0; 42281 * width:100%; 42282 * } 42283 * .view-animation.ng-enter { 42284 * transform:translateX(100%); 42285 * } 42286 * .view-animation.ng-leave, 42287 * .view-animation.ng-enter.ng-enter-active { 42288 * transform:translateX(0%); 42289 * } 42290 * .view-animation.ng-leave.ng-leave-active { 42291 * transform:translateX(-100%); 42292 * } 42293 * ``` 42294 * 42295 * Now we can jump back to the anchor animation. When the animation happens, there are two stages that occur: 42296 * an `out` and an `in` stage. The `out` stage happens first and that is when the element is animated away 42297 * from its origin. Once that animation is over then the `in` stage occurs which animates the 42298 * element to its destination. The reason why there are two animations is to give enough time 42299 * for the enter animation on the new element to be ready. 42300 * 42301 * The example above sets up a transition for both the in and out phases, but we can also target the out or 42302 * in phases directly via `ng-anchor-out` and `ng-anchor-in`. 42303 * 42304 * ```css 42305 * .banner.ng-anchor-out { 42306 * transition: 0.5s linear all; 42307 * 42308 * /* the scale will be applied during the out animation, 42309 * but will be animated away when the in animation runs */ 42310 * transform: scale(1.2); 42311 * } 42312 * 42313 * .banner.ng-anchor-in { 42314 * transition: 1s linear all; 42315 * } 42316 * ``` 42317 * 42318 * 42319 * 42320 * 42321 * ### Anchoring Demo 42322 * 42323 <example module="anchoringExample" 42324 name="anchoringExample" 42325 id="anchoringExample" 42326 deps="angular-animate.js;angular-route.js" 42327 animations="true"> 42328 <file name="index.html"> 42329 <a href="#/">Home</a> 42330 <hr /> 42331 <div class="view-container"> 42332 <div ng-view class="view"></div> 42333 </div> 42334 </file> 42335 <file name="script.js"> 42336 angular.module('anchoringExample', ['ngAnimate', 'ngRoute']) 42337 .config(['$routeProvider', function($routeProvider) { 42338 $routeProvider.when('/', { 42339 templateUrl: 'home.html', 42340 controller: 'HomeController as home' 42341 }); 42342 $routeProvider.when('/profile/:id', { 42343 templateUrl: 'profile.html', 42344 controller: 'ProfileController as profile' 42345 }); 42346 }]) 42347 .run(['$rootScope', function($rootScope) { 42348 $rootScope.records = [ 42349 { id: 1, title: 'Miss Beulah Roob' }, 42350 { id: 2, title: 'Trent Morissette' }, 42351 { id: 3, title: 'Miss Ava Pouros' }, 42352 { id: 4, title: 'Rod Pouros' }, 42353 { id: 5, title: 'Abdul Rice' }, 42354 { id: 6, title: 'Laurie Rutherford Sr.' }, 42355 { id: 7, title: 'Nakia McLaughlin' }, 42356 { id: 8, title: 'Jordon Blanda DVM' }, 42357 { id: 9, title: 'Rhoda Hand' }, 42358 { id: 10, title: 'Alexandrea Sauer' } 42359 ]; 42360 }]) 42361 .controller('HomeController', [function() { 42362 //empty 42363 }]) 42364 .controller('ProfileController', ['$rootScope', '$routeParam
42364s', 42365 function ProfileController($rootScope, $routeParams) { 42366 var index = parseInt($routeParams.id, 10); 42367 var record = $rootScope.records[index - 1]; 42368 42369 this.title = record.title; 42370 this.id = record.id; 42371 }]); 42372 </file> 42373 <file name="home.html"> 42374 <h2>Welcome to the home page</h1> 42375 <p>Please click on an element</p> 42376 <a class="record" 42377 ng-href="#/profile/{{ record.id }}" 42378 ng-animate-ref="{{ record.id }}" 42379 ng-repeat="record in records"> 42380 {{ record.title }} 42381 </a> 42382 </file> 42383 <file name="profile.html"> 42384 <div class="profile record" ng-animate-ref="{{ profile.id }}"> 42385 {{ profile.title }} 42386 </div> 42387 </file> 42388 <file name="animations.css"> 42389 .record { 42390 display:block; 42391 font-size:20px; 42392 } 42393 .profile { 42394 background:black; 42395 color:white; 42396 font-size:100px; 42397 } 42398 .view-container { 42399 position:relative; 42400 } 42401 .view-container > .view.ng-animate { 42402 position:absolute; 42403 top:0; 42404 left:0; 42405 width:100%; 42406 min-height:500px; 42407 } 42408 .view.ng-enter, .view.ng-leave, 42409 .record.ng-anchor { 42410 transition:0.5s linear all; 42411 } 42412 .view.ng-enter { 42413 transform:translateX(100%); 42414 } 42415 .view.ng-enter.ng-enter-active, .view.ng-leave { 42416 transform:translateX(0%); 42417 } 42418 .view.ng-leave.ng-leave-active { 42419 transform:translateX(-100%); 42420 } 42421 .record.ng-anchor-out { 42422 background:red; 42423 } 42424 </file> 42425 </example> 42426 * 42427 * ### How is the element transported? 42428 * 42429 * When an anchor animation occurs, ngAnimate will clone the starting element and position it exactly where the starting 42430 * element is located on screen via absolute positioning. The cloned element will be placed inside of the root element 42431 * of the application (where ng-app was defined) and all of the CSS classes of the starting element will be applied. The 42432 * element will then animate into the `out` and `in` animations and will eventually reach the coordinates and match 42433 * the dimensions of the destination element. During the entire animation a CSS class of `.ng-animate-shim` will be applied 42434 * to both the starting and destination elements in order to hide them from being visible (the CSS styling for the class 42435 * is: `visibility:hidden`). Once the anchor reaches its destination then it will be removed and the destination element 42436 * will become visible since the shim class will be removed. 42437 * 42438 * ### How is the morphing handled? 42439 * 42440 * CSS Anchoring relies on transitions and keyframes and the internal code is intelligent enough to figure out 42441 * what CSS classes differ between the starting element and the destination element. These different CSS classes 42442 * will be added/removed on the anchor element and a transition will be applied (the transition that is provided 42443 * in the anchor class). Long story short, ngAnimate will figure out what classes to add and remove which will 42444 * make the transition of the element as smooth and automatic as possible. Be sure to use simple CSS classes that 42445 * do not rely on DOM nesting structure so that the anchor element appears the same as the starting element (since 42446 * the cloned element is placed inside of root element which is likely close to the body element). 42447 * 42448 * Note that if the root element is on the `<html>` element then the cloned node will be placed inside of body. 42449 * 42450 * 42451 * ## Using $animate in your directive code 42452 * 42453 * So far we've explored how to feed in animations into an Angular application, but how do we trigger animations within our own directives in our application? 42454 * By injecting the `$animate` service into our directive code, we can trigger structural and class-based hooks which can then be c
42454onsumed by animations. Let's 42455 * imagine we have a greeting box that shows and hides itself when the data changes 42456 * 42457 * ```html 42458 * <greeting-box active="onOrOff">Hi there</greeting-box> 42459 * ``` 42460 * 42461 * ```js 42462 * ngModule.directive('greetingBox', ['$animate', function($animate) { 42463 * return function(scope, element, attrs) { 42464 * attrs.$observe('active', function(value) { 42465 * value ? $animate.addClass(element, 'on') : $animate.removeClass(element, 'on'); 42466 * }); 42467 * }); 42468 * }]); 42469 * ``` 42470 * 42471 * Now the `on` CSS class is added and removed on the greeting box component. Now if we add a CSS class on top of the greeting box element 42472 * in our HTML code then we can trigger a CSS or JS animation to happen. 42473 * 42474 * ```css 42475 * /* normally we would create a CSS class to reference on the element */ 42476 * greeting-box.on { transition:0.5s linear all; background:green; color:white; } 42477 * ``` 42478 * 42479 * The `$animate` service contains a variety of other methods like `enter`, `leave`, `animate` and `setClass`. To learn more about what's 42480 * possible be sure to visit the {@link ng.$animate $animate service API page}. 42481 * 42482 * 42483 * ## Callbacks and Promises 42484 * 42485 * When `$animate` is called it returns a promise that can be used to capture when the animation has ended. Therefore if we were to trigger 42486 * an animation (within our directive code) then we can continue performing directive and scope related activities after the animation has 42487 * ended by chaining onto the returned promise that animation method returns. 42488 * 42489 * ```js 42490 * // somewhere within the depths of the directive 42491 * $animate.enter(element, parent).then(function() { 42492 * //the animation has completed 42493 * }); 42494 * ``` 42495 * 42496 * (Note that earlier versions of Angular prior to v1.4 required the promise code to be wrapped using `$scope.$apply(...)`. This is not the case 42497 * anymore.) 42498 * 42499 * In addition to the animation promise, we can also make use of animation-related callbacks within our directives and controller code by registering 42500 * an event listener using the `$animate` service. Let's say for example that an animation was triggered on our view 42501 * routing controller to hook into that: 42502 * 42503 * ```js 42504 * ngModule.controller('HomePageController', ['$animate', function($animate) { 42505 * $animate.on('enter', ngViewElement, function(element) { 42506 * // the animation for this route has completed 42507 * }]); 42508 * }]) 42509 * ``` 42510 * 42511 * (Note that you will need to trigger a digest within the callback to get angular to notice any scope-related changes.) 42512 */ 42513 42514var copy; 42515var extend; 42516var forEach; 42517var isArray; 42518var isDefined; 42519var isElement; 42520var isFunction; 42521var isObject; 42522var isString; 42523var isUndefined; 42524var jqLite; 42525var noop; 42526 42527/** 42528 * @ngdoc service 42529 * @name $animate 42530 * @kind object 42531 * 42532 * @description 42533 * The ngAnimate `$animate` service documentation is the same for the core `$animate` service. 42534 * 42535 * Click here {@link ng.$animate to learn more about animations with `$animate`}. 42536 */ 42537angular.module('ngAnimate', [], function initAngularHelpers() { 42538 // Access helpers from angular core. 42539 // Do it inside a `config` block to ensure `window.angular` is available. 42540 noop = angular.noop; 42541 copy = angular.copy; 42542 extend = angular.extend; 42543 jqLite = angular.element; 42544 forEach = angular.forEach; 42545 isArray = angular.isArray; 42546 isString = angular.isString; 42547 isObject = angular.isObject; 42548 isUndefined = angular.isUndefined; 42549 isDefined = angular.isDefined; 42550 isFunction = angular.isFunction; 42551 isElement = angular.isElement; 42552}) 42553 .directive('ngAnimateSwap', ngAnimateSwapDirective) 42554 42555 .directive('ngAnimateChildren', $$AnimateChildrenDirective) 42556 .factory('$$rAFScheduler', $$rAFSchedulerFactory) 42557 42558 .provider('$$animateQueue', $$AnimateQueueProvider) 42559 .provider('$$animation', $$AnimationProvider) 42560 42561 .provider('$animateCss', $AnimateCssProvider) 42562 .provider('$$animateCssDriver', $$AnimateCssDriverProvider) 42563 42564 .provider('$$animateJs', $$AnimateJsProvider) 42565 .provider('$$animateJsDriver', $$AnimateJsDriverProvider); 42566 42567 42568})(window, window.angular); 42569 42570/** 42571 * @license AngularJS v1.5.11 42572 * (c) 2010-2017 Google, Inc. http://angularjs.org 42573 * License: MIT 42574 */ 42575(function(window, angular) {'use strict'; 42576 42577/** 42578 * @ngdoc module 42579 * @name ngCookies 42580 * @description 42581 * 42582 * # ngCookies 42583 * 42584 * The `ngCookies` module provides a convenient wrapper for reading and writing browser cookies. 42585 * 42586 * 42587 * <div doc-module-components="ngCookies"></div> 42588 * 42589 * See {@link ngCookies.$cookies `$cookies`} for usage. 42590 */ 42591 42592 42593angular.module('ngCookies', ['ng']). 42594 /** 42595 * @ngdoc provider 42596 * @name $cookiesProvider 42597 * @description 42598 * Use `$cookiesProvider` to change the default behavior of the {@link ngCookies.$cookies $cookies} service. 42599 * */ 42600 provider('$cookies', [/** @this */function $CookiesProvider() { 42601 /** 42602 * @ngdoc property 42603 * @name $cookiesProvider#defaults
42604 * @description 42605 * 42606 * Object containing default options to pass when setting cookies. 42607 * 42608 * The object may have following properties: 42609 * 42610 * - **path** - `{string}` - The cookie will be available only for this path and its 42611 * sub-paths. By default, this is the URL that appears in your `<base>` tag. 42612 * - **domain** - `{string}` - The cookie will be available only for this domain and 42613 * its sub-domains. For security reasons the user agent will not accept the cookie 42614 * if the current domain is not a sub-domain of this domain or equal to it. 42615 * - **expires** - `{string|Date}` - String of the form "Wdy, DD Mon YYYY HH:MM:SS GMT" 42616 * or a Date object indicating the exact date/time this cookie will expire. 42617 * - **secure** - `{boolean}` - If `true`, then the cookie will only be available through a 42618 * secured connection. 42619 * 42620 * Note: By default, the address that appears in your `<base>` tag will be used as the path. 42621 * This is important so that cookies will be visible for all routes when html5mode is enabled. 42622 * 42623 * @example 42624 * 42625 * ```js 42626 * angular.module('cookiesProviderExample', ['ngCookies']) 42627 * .config(['$cookiesProvider', function($cookiesProvider) { 42628 * // Setting default options 42629 * $cookiesProvider.defaults.domain = 'foo.com'; 42630 * $cookiesProvider.defaults.secure = true; 42631 * }]); 42632 * ``` 42633 **/ 42634 var defaults = this.defaults = {}; 42635 42636 function calcOptions(options) { 42637 return options ? angular.extend({}, defaults, options) : defaults; 42638 } 42639 42640 /** 42641 * @ngdoc service 42642 * @name $cookies 42643 * 42644 * @description 42645 * Provides read/write access to browser's cookies. 42646 * 42647 * <div class="alert alert-info"> 42648 * Up until Angular 1.3, `$cookies` exposed properties that represented the 42649 * current browser cookie values. In version 1.4, this behavior has changed, and 42650 * `$cookies` now provides a standard api of getters, setters etc. 42651 * </div> 42652 * 42653 * Requires the {@link ngCookies `ngCookies`} module to be installed. 42654 * 42655 * @example 42656 * 42657 * ```js 42658 * angular.module('cookiesExample', ['ngCookies']) 42659 * .controller('ExampleController', ['$cookies', function($cookies) { 42660 * // Retrieving a cookie 42661 * var favoriteCookie = $cookies.get('myFavorite'); 42662 * // Setting a cookie 42663 * $cookies.put('myFavorite', 'oatmeal'); 42664 * }]); 42665 * ``` 42666 */ 42667 this.$get = ['$$cookieReader', '$$cookieWriter', function($$cookieReader, $$cookieWriter) { 42668 return { 42669 /** 42670 * @ngdoc method 42671 * @name $cookies#get 42672 * 42673 * @description 42674 * Returns the value of given cookie key 42675 * 42676 * @param {string} key Id to use for lookup. 42677 * @returns {string} Raw cookie value. 42678 */ 42679 get: function(key) { 42680 return $$cookieReader()[key]; 42681 }, 42682 42683 /** 42684 * @ngdoc method 42685 * @name $cookies#getObject 42686 * 42687 * @description 42688 * Returns the deserialized value of given cookie key 42689 * 42690 * @param {string} key Id to use for lookup. 42691 * @returns {Object} Deserialized cookie value. 42692 */ 42693 getObject: function(key) { 42694 var value = this.get(key); 42695 return value ? angular.fromJson(value) : value; 42696 }, 42697 42698 /** 42699 * @ngdoc method 42700 * @name $cookies#getAll 42701 * 42702 * @description 42703 * Returns a key value object with all the cookies 42704 * 42705 * @returns {Object} All cookies 42706 */ 42707 getAll: function() { 42708 return $$cookieReader(); 42709 }, 42710 42711 /** 42712 * @ngdoc method 42713 * @name $cookies#put 42714 * 42715 * @description 42716 * Sets a value for given cookie key 42717 * 42718 * @param {string} key Id for the `value`. 42719 * @param {string} value Raw value to be stored. 42720 * @param {Object=} options Options object. 42721 * See {@link ngCookies.$cookiesProvider#defaults $cookiesProvider.defaults} 42722 */ 42723 put: function(key, value, options) { 42724 $$cookieWriter(key, value, calcOptions(options)); 42725 }, 42726 42727 /** 42728 * @ngdoc method 42729 * @name $cookies#putObject 42730 *
42731 * @description 42732 * Serializes and sets a value for given cookie key 42733 * 42734 * @param {string} key Id for the `value`. 42735 * @param {Object} value Value to be stored. 42736 * @param {Object=} options Options object. 42737 * See {@link ngCookies.$cookiesProvider#defaults $cookiesProvider.defaults} 42738 */ 42739 putObject: function(key, value, options) { 42740 this.put(key, angular.toJson(value), options); 42741 }, 42742 42743 /** 42744 * @ngdoc method 42745 * @name $cookies#remove 42746 * 42747 * @description 42748 * Remove given cookie 42749 * 42750 * @param {string} key Id of the key-value pair to delete. 42751 * @param {Object=} options Options object. 42752 * See {@link ngCookies.$cookiesProvider#defaults $cookiesProvider.defaults} 42753 */ 42754 remove: function(key, options) { 42755 $$cookieWriter(key, undefined, calcOptions(options)); 42756 } 42757 }; 42758 }]; 42759 }]); 42760 42761angular.module('ngCookies'). 42762/** 42763 * @ngdoc service 42764 * @name $cookieStore 42765 * @deprecated 42766 * sinceVersion="v1.4.0" 42767 * Please use the {@link ngCookies.$cookies `$cookies`} service instead. 42768 * 42769 * @requires $cookies 42770 * 42771 * @description 42772 * Provides a key-value (string-object) storage, that is backed by session cookies. 42773 * Objects put or retrieved from this storage are automatically serialized or 42774 * deserialized by angular's toJson/fromJson. 42775 * 42776 * Requires the {@link ngCookies `ngCookies`} module to be installed. 42777 * 42778 * @example 42779 * 42780 * ```js 42781 * angular.module('cookieStoreExample', ['ngCookies']) 42782 * .controller('ExampleController', ['$cookieStore', function($cookieStore) { 42783 * // Put cookie 42784 * $cookieStore.put('myFavorite','oatmeal'); 42785 * // Get cookie 42786 * var favoriteCookie = $cookieStore.get('myFavorite'); 42787 * // Removing a cookie 42788 * $cookieStore.remove('myFavorite'); 42789 * }]); 42790 * ``` 42791 */ 42792 factory('$cookieStore', ['$cookies', function($cookies) { 42793 42794 return { 42795 /** 42796 * @ngdoc method 42797 * @name $cookieStore#get 42798 * 42799 * @description 42800 * Returns the value of given cookie key 42801 * 42802 * @param {string} key Id to use for lookup. 42803 * @returns {Object} Deserialized cookie value, undefined if the cookie does not exist. 42804 */ 42805 get: function(key) { 42806 return $cookies.getObject(key); 42807 }, 42808 42809 /** 42810 * @ngdoc method 42811 * @name $cookieStore#put 42812 * 42813 * @description 42814 * Sets a value for given cookie key 42815 * 42816 * @param {string} key Id for the `value`. 42817 * @param {Object} value Value to be stored. 42818 */ 42819 put: function(key, value) { 42820 $cookies.putObject(key, value); 42821 }, 42822 42823 /** 42824 * @ngdoc method 42825 * @name $cookieStore#remove 42826 * 42827 * @description 42828 * Remove given cookie 42829 * 42830 * @param {string} key Id of the key-value pair to delete. 42831 */ 42832 remove: function(key) { 42833 $cookies.remove(key); 42834 } 42835 }; 42836 42837 }]); 42838 42839/** 42840 * @name $$cookieWriter 42841 * @requires $document 42842 * 42843 * @description 42844 * This is a private service for writing cookies 42845 * 42846 * @param {string} name Cookie name 42847 * @param {string=} value Cookie value (if undefined, cookie will be deleted) 42848 * @param {Object=} options Object with options that need to be stored for the cookie. 42849 */ 42850function $$CookieWriter($document, $log, $browser) { 42851 var cookiePath = $browser.baseHref(); 42852 var rawDocument = $document[0]; 42853 42854 function buildCookieString(name, value, options) { 42855 var path, expires; 42856 options = options || {}; 42857 expires = options.expires; 42858 path = angular.isDefined(options.path) ? options.path : cookiePath; 42859 if (angular.isUndefined(value)) { 42860 expires = 'Thu, 01 Jan 1970 00:00:00 GMT'; 42861 value = ''; 42862 } 42863 if (angular.isString(expires)) { 42864 expires = new Date(expires); 42865 } 42866 42867 var str = encodeURIComponent(name) + '=' + encodeURIComponent(value); 42868 str += path ? ';path=' + path : ''; 42869 str += options.domain ? ';domain=' + options.domain : ''; 42870 str += expires ? ';expires=' + expires.toUTCString() : ''; 42871 str += options.secure ? ';secure' : ''; 42872 42873 // per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum: 42874 // - 300 cookies 42875 // - 20 cookies per unique domain 42876 // - 4096 bytes per cookie 42877 var cookieLength = str.length + 1; 42878 if (cookieLength > 4096) {
42879 $log.warn('Cookie \'' + name + 42880 '\' possibly not set or overflowed because it was too large (' + 42881 cookieLength + ' > 4096 bytes)!'); 42882 } 42883 42884 return str; 42885 } 42886 42887 return function(name, value, options) { 42888 rawDocument.cookie = buildCookieString(name, value, options); 42889 }; 42890} 42891 42892$$CookieWriter.$inject = ['$document', '$log', '$browser']; 42893 42894angular.module('ngCookies').provider('$$cookieWriter', /** @this */ function $$CookieWriterProvider() { 42895 this.$get = $$CookieWriter; 42896}); 42897 42898 42899})(window, window.angular); 42900 42901/** 42902 * @license AngularJS v1.5.11 42903 * (c) 2010-2017 Google, Inc. http://angularjs.org 42904 * License: MIT 42905 */ 42906(function(window, angular) {'use strict'; 42907 42908/* global ngTouchClickDirectiveFactory: false */ 42909 42910/** 42911 * @ngdoc module 42912 * @name ngTouch 42913 * @description 42914 * 42915 * # ngTouch 42916 * 42917 * The `ngTouch` module provides touch events and other helpers for touch-enabled devices. 42918 * The implementation is based on jQuery Mobile touch event handling 42919 * ([jquerymobile.com](http://jquerymobile.com/)). 42920 * 42921 * 42922 * See {@link ngTouch.$swipe `$swipe`} for usage. 42923 * 42924 * <div doc-module-components="ngTouch"></div> 42925 * 42926 */ 42927 42928// define ngTouch module 42929/* global -ngTouch */ 42930var ngTouch = angular.module('ngTouch', []); 42931 42932ngTouch.provider('$touch', $TouchProvider); 42933 42934function nodeName_(element) { 42935 return angular.lowercase(element.nodeName || (element[0] && element[0].nodeName)); 42936} 42937 42938/** 42939 * @ngdoc provider 42940 * @name $touchProvider 42941 * 42942 * @description 42943 * The `$touchProvider` allows enabling / disabling {@link ngTouch.ngClick ngTouch's ngClick directive}. 42944 */ 42945$TouchProvider.$inject = ['$provide', '$compileProvider']; 42946function $TouchProvider($provide, $compileProvider) { 42947 42948 /** 42949 * @ngdoc method 42950 * @name $touchProvider#ngClickOverrideEnabled 42951 * 42952 * @param {boolean=} enabled update the ngClickOverrideEnabled state if provided, otherwise just return the 42953 * current ngClickOverrideEnabled state 42954 * @returns {*} current value if used as getter or itself (chaining) if used as setter 42955 * 42956 * @kind function 42957 * 42958 * @description 42959 * Call this method to enable/disable {@link ngTouch.ngClick ngTouch's ngClick directive}. If enabled, 42960 * the default ngClick directive will be replaced by a version that eliminates the 300ms delay for 42961 * click events on browser for touch-devices. 42962 * 42963 * The default is `false`. 42964 * 42965 */ 42966 var ngClickOverrideEnabled = false; 42967 var ngClickDirectiveAdded = false; 42968 // eslint-disable-next-line no-invalid-this 42969 this.ngClickOverrideEnabled = function(enabled) { 42970 if (angular.isDefined(enabled)) { 42971 42972 if (enabled && !ngClickDirectiveAdded) { 42973 ngClickDirectiveAdded = true; 42974 42975 // Use this to identify the correct directive in the delegate 42976 ngTouchClickDirectiveFactory.$$moduleName = 'ngTouch'; 42977 $compileProvider.directive('ngClick', ngTouchClickDirectiveFactory); 42978 42979 $provide.decorator('ngClickDirective', ['$delegate', function($delegate) { 42980 if (ngClickOverrideEnabled) { 42981 // drop the default ngClick directive 42982 $delegate.shift(); 42983 } else { 42984 // drop the ngTouch ngClick directive if the override has been re-disabled (because 42985 // we cannot de-register added directives) 42986 var i = $delegate.length - 1; 42987 while (i >= 0) { 42988 if ($delegate[i].$$moduleName === 'ngTouch') { 42989 $delegate.splice(i, 1); 42990 break; 42991 } 42992 i--; 42993 } 42994 } 42995 42996 return $delegate; 42997 }]); 42998 } 42999 43000 ngClickOverrideEnabled = enabled; 43001 return this; 43002 } 43003 43004 return ngClickOverrideEnabled; 43005 }; 43006 43007 /** 43008 * @ngdoc service 43009 * @name $touch 43010 * @kind object 43011 * 43012 * @description 43013 * Provides the {@link ngTouch.$touch#ngClickOverrideEnabled `ngClickOverrideEnabled`} method. 43014 * 43015 */ 43016 // eslint-disable-next-line no-invalid-this 43017 this.$get = function() { 43018 return { 43019 /** 43020 * @ngdoc method 43021 * @name $touch#ngClickOverrideEnabled 43022 * 43023 * @returns {*} current value of `ngClickOverrideEnabled` set in the {@link ngTouch.$touchProvider $touchProvi
43023der}, 43024 * i.e. if {@link ngTouch.ngClick ngTouch's ngClick} directive is enabled. 43025 * 43026 * @kind function 43027 */ 43028 ngClickOverrideEnabled: function() { 43029 return ngClickOverrideEnabled; 43030 } 43031 }; 43032 }; 43033 43034} 43035 43036/* global ngTouch: false */ 43037 43038 /** 43039 * @ngdoc service 43040 * @name $swipe 43041 * 43042 * @description 43043 * The `$swipe` service is a service that abstracts the messier details of hold-and-drag swipe 43044 * behavior, to make implementing swipe-related directives more convenient. 43045 * 43046 * Requires the {@link ngTouch `ngTouch`} module to be installed. 43047 * 43048 * `$swipe` is used by the `ngSwipeLeft` and `ngSwipeRight` directives in `ngTouch`. 43049 * 43050 * # Usage 43051 * The `$swipe` service is an object with a single method: `bind`. `bind` takes an element 43052 * which is to be watched for swipes, and an object with four handler functions. See the 43053 * documentation for `bind` below. 43054 */ 43055 43056ngTouch.factory('$swipe', [function() { 43057 // The total distance in any direction before we make the call on swipe vs. scroll. 43058 var MOVE_BUFFER_RADIUS = 10; 43059 43060 var POINTER_EVENTS = { 43061 'mouse': { 43062 start: 'mousedown', 43063 move: 'mousemove', 43064 end: 'mouseup' 43065 }, 43066 'touch': { 43067 start: 'touchstart', 43068 move: 'touchmove', 43069 end: 'touchend', 43070 cancel: 'touchcancel' 43071 }, 43072 'pointer': { 43073 start: 'pointerdown', 43074 move: 'pointermove', 43075 end: 'pointerup', 43076 cancel: 'pointercancel' 43077 } 43078 }; 43079 43080 function getCoordinates(event) { 43081 var originalEvent = event.originalEvent || event; 43082 var touches = originalEvent.touches && originalEvent.touches.length ? originalEvent.touches : [originalEvent]; 43083 var e = (originalEvent.changedTouches && originalEvent.changedTouches[0]) || touches[0]; 43084 43085 return { 43086 x: e.clientX, 43087 y: e.clientY 43088 }; 43089 } 43090 43091 function getEvents(pointerTypes, eventType) { 43092 var res = []; 43093 angular.forEach(pointerTypes, function(pointerType) { 43094 var eventName = POINTER_EVENTS[pointerType][eventType]; 43095 if (eventName) { 43096 res.push(eventName); 43097 } 43098 }); 43099 return res.join(' '); 43100 } 43101 43102 return { 43103 /** 43104 * @ngdoc method 43105 * @name $swipe#bind 43106 * 43107 * @description 43108 * The main method of `$swipe`. It takes an element to be watched for swipe motions, and an 43109 * object containing event handlers. 43110 * The pointer types that should be used can be specified via the optional 43111 * third argument, which is an array of strings `'mouse'`, `'touch'` and `'pointer'`. By default, 43112 * `$swipe` will listen for `mouse`, `touch` and `pointer` events. 43113 * 43114 * The four events are `start`, `move`, `end`, and `cancel`. `start`, `move`, and `end` 43115 * receive as a parameter a coordinates object of the form `{ x: 150, y: 310 }` and the raw 43116 * `event`. `cancel` receives the raw `event` as its single parameter. 43117 * 43118 * `start` is called on either `mousedown`, `touchstart` or `pointerdown`. After this event, `$swipe` is 43119 * watching for `touchmove`, `mousemove` or `pointermove` events. These events are ignored until the total 43120 * distance moved in either dimension exceeds a small threshold. 43121 * 43122 * Once this threshold is exceeded, either the horizontal or vertical delta is greater. 43123 * - If the horizontal distance is greater, this is a swipe and `move` and `end` events follow. 43124 * - If the vertical distance is greater, this is a scroll, and we let the browser take over. 43125 * A `cancel` event is sent. 43126 * 43127 * `move` is called on `mousemove`, `touchmove` and `pointermove` after the above logic has determined that 43128 * a swipe is in progress. 43129 * 43130 * `end` is called when a swipe is successfully completed with a `touchend`, `mouseup` or `pointerup`. 43131 * 43132 * `cancel` is called either on a `touchcancel` or `pointercancel` from the browser, or when we begin scrolling 43133 * as described above. 43134 * 43135 */ 43136 bind: function(element, eventHandlers, pointerTypes) { 43137 // Absolute total movement, used to control swipe vs. scroll. 43138 var totalX, totalY; 43139 // Coordinates of the start position. 43140 var startCoords; 43141 // Last event's position. 43142 var lastPos; 43143 // Whether a swipe is active. 43144 var active = false;
vendor: 4,584 bytes, lines 43145-43269
43145 43146 pointerTypes = pointerTypes || ['mouse', 'touch', 'pointer']; 43147 element.on(getEvents(pointerTypes, 'start'), function(event) { 43148 startCoords = getCoordinates(event); 43149 active = true; 43150 totalX = 0; 43151 totalY = 0; 43152 lastPos = startCoords; 43153 if (eventHandlers['start']) { 43154 eventHandlers['start'](startCoords, event); 43155 } 43156 }); 43157 var events = getEvents(pointerTypes, 'cancel'); 43158 if (events) { 43159 element.on(events, function(event) { 43160 active = false; 43161 if (eventHandlers['cancel']) { 43162 eventHandlers['cancel'](event); 43163 } 43164 }); 43165 } 43166 43167 element.on(getEvents(pointerTypes, 'move'), function(event) { 43168 if (!active) return; 43169 43170 // Android will send a touchcancel if it thinks we're starting to scroll. 43171 // So when the total distance (+ or - or both) exceeds 10px in either direction, 43172 // we either: 43173 // - On totalX > totalY, we send preventDefault() and treat this as a swipe. 43174 // - On totalY > totalX, we let the browser handle it as a scroll. 43175 43176 if (!startCoords) return; 43177 var coords = getCoordinates(event); 43178 43179 totalX += Math.abs(coords.x - lastPos.x); 43180 totalY += Math.abs(coords.y - lastPos.y); 43181 43182 lastPos = coords; 43183 43184 if (totalX < MOVE_BUFFER_RADIUS && totalY < MOVE_BUFFER_RADIUS) { 43185 return; 43186 } 43187 43188 // One of totalX or totalY has exceeded the buffer, so decide on swipe vs. scroll. 43189 if (totalY > totalX) { 43190 // Allow native scrolling to take over. 43191 active = false; 43192 if (eventHandlers['cancel']) { 43193 eventHandlers['cancel'](event); 43194 } 43195 return; 43196 } else { 43197 // Prevent the browser from scrolling. 43198 event.preventDefault(); 43199 if (eventHandlers['move']) { 43200 eventHandlers['move'](coords, event); 43201 } 43202 } 43203 }); 43204 43205 element.on(getEvents(pointerTypes, 'end'), function(event) { 43206 if (!active) return; 43207 active = false; 43208 if (eventHandlers['end']) { 43209 eventHandlers['end'](getCoordinates(event), event); 43210 } 43211 }); 43212 } 43213 }; 43214}]); 43215 43216/* global ngTouch: false, 43217 nodeName_: false 43218*/ 43219 43220/** 43221 * @ngdoc directive 43222 * @name ngClick 43223 * @deprecated 43224 * sinceVersion="v1.5.0" 43225 * This directive is deprecated and **disabled** by default. 43226 * The directive will receive no further support and might be removed from future releases. 43227 * If you need the directive, you can enable it with the {@link ngTouch.$touchProvider $touchProvider#ngClickOverrideEnabled} 43228 * function. We also recommend that you migrate to [FastClick](https://github.com/ftlabs/fastclick). 43229 * To learn more about the 300ms delay, this [Telerik article](http://developer.telerik.com/featured/300-ms-click-delay-ios-8/) 43230 * gives a good overview. 43231 * 43232 * @description 43233 * A more powerful replacement for the default ngClick designed to be used on touchscreen 43234 * devices. Most mobile browsers wait about 300ms after a tap-and-release before sending 43235 * the click event. This version handles them immediately, and then prevents the 43236 * following click event from propagating. 43237 * 43238 * Requires the {@link ngTouch `ngTouch`} module to be installed. 43239 * 43240 * This directive can fall back to using an ordinary click event, and so works on desktop 43241 * browsers as well as mobile. 43242 * 43243 * This directive also sets the CSS class `ng-click-active` while the element is being held 43244 * down (by a mouse click or touch) so you can restyle the depressed element if you wish. 43245 * 43246 * @element ANY 43247 * @param {expression} ngClick {@link guide/expression Expression} to evaluate 43248 * upon tap. (Event object is available as `$event`) 43249 * 43250 * @example 43251 <example module="ngClickExample" deps="angular-touch.js" name="ng-touch-ng-click"> 43252 <file name="index.html"> 43253 <button ng-click="count = count + 1" ng-init="count=0"> 43254 Increment 43255 </button> 43256 count: {{ count }} 43257 </file> 43258 <file name="script.js"> 43259 angular.module('ngClickExample', ['ngTouch']); 43260 </file> 43261 </example> 43262 */ 43263 43264var ngTouchClickDirectiveFactory = ['$parse', '$timeout', '$rootElement', 43265 function($parse, $timeout, $rootElement) { 43266 var TAP_DURATION = 750; // Shorter than 750ms is a tap, longer is a taphold or drag. 43267 var MOVE_TOLERANCE = 12; // 12px seems to work in most mobile browsers. 43268 var PREVENT_DURATION = 2500; // 2.5 seconds maximum from preventGhostClick call to click 43269 var CLICKBUSTER_THRESHOLD = 25; // 25 pixels in any dimension is the limit for busting clicks.
43270 43271 var ACTIVE_CLASS_NAME = 'ng-click-active'; 43272 var lastPreventedTime; 43273 var touchCoordinates; 43274 var lastLabelClickCoordinates; 43275 43276 43277 // TAP EVENTS AND GHOST CLICKS 43278 // 43279 // Why tap events? 43280 // Mobile browsers detect a tap, then wait a moment (usually ~300ms) to see if you're 43281 // double-tapping, and then fire a click event. 43282 // 43283 // This delay sucks and makes mobile apps feel unresponsive. 43284 // So we detect touchstart, touchcancel and touchend ourselves and determine when 43285 // the user has tapped on something. 43286 // 43287 // What happens when the browser then generates a click event? 43288 // The browser, of course, also detects the tap and fires a click after a delay. This results in 43289 // tapping/clicking twice. We do "clickbusting" to prevent it. 43290 // 43291 // How does it work? 43292 // We attach global touchstart and click handlers, that run during the capture (early) phase. 43293 // So the sequence for a tap is: 43294 // - global touchstart: Sets an "allowable region" at the point touched. 43295 // - element's touchstart: Starts a touch 43296 // (- touchcancel ends the touch, no click follows) 43297 // - element's touchend: Determines if the tap is valid (didn't move too far away, didn't hold 43298 // too long) and fires the user's tap handler. The touchend also calls preventGhostClick(). 43299 // - preventGhostClick() removes the allowable region the global touchstart created. 43300 // - The browser generates a click event. 43301 // - The global click handler catches the click, and checks whether it was in an allowable region. 43302 // - If preventGhostClick was called, the region will have been removed, the click is busted. 43303 // - If the region is still there, the click proceeds normally. Therefore clicks on links and 43304 // other elements without ngTap on them work normally. 43305 // 43306 // This is an ugly, terrible hack! 43307 // Yeah, tell me about it. The alternatives are using the slow click events, or making our users 43308 // deal with the ghost clicks, so I consider this the least of evils. Fortunately Angular 43309 // encapsulates this ugly logic away from the user. 43310 // 43311 // Why not just put click handlers on the element? 43312 // We do that too, just to be sure. If the tap event caused the DOM to change, 43313 // it is possible another element is now in that position. To take account for these possibly 43314 // distinct elements, the handlers are global and care only about coordinates. 43315 43316 // Checks if the coordinates are close enough to be within the region. 43317 function hit(x1, y1, x2, y2) { 43318 return Math.abs(x1 - x2) < CLICKBUSTER_THRESHOLD && Math.abs(y1 - y2) < CLICKBUSTER_THRESHOLD; 43319 } 43320 43321 // Checks a list of allowable regions against a click location. 43322 // Returns true if the click should be allowed. 43323 // Splices out the allowable region from the list after it has been used. 43324 function checkAllowableRegions(touchCoordinates, x, y) { 43325 for (var i = 0; i < touchCoordinates.length; i += 2) { 43326 if (hit(touchCoordinates[i], touchCoordinates[i + 1], x, y)) { 43327 touchCoordinates.splice(i, i + 2); 43328 return true; // allowable region 43329 } 43330 } 43331 return false; // No allowable region; bust it. 43332 } 43333 43334 // Global click handler that prevents the click if it's in a bustable zone and preventGhostClick 43335 // was called recently. 43336 function onClick(event) { 43337 if (Date.now() - lastPreventedTime > PREVENT_DURATION) { 43338 return; // Too old. 43339 } 43340 43341 var touches = event.touches && event.touches.length ? event.touches : [event]; 43342 var x = touches[0].clientX; 43343 var y = touches[0].clientY; 43344 // Work around desktop Webkit quirk where clicking a label will fire two clicks (on the label 43345 // and on the input element). Depending on the exact browser, this second click we don't want 43346 // to bust has either (0,0), negative coordinates, or coordinates equal to triggering label 43347 // click event 43348 if (x < 1 && y < 1) { 43349 return; // offscreen 43350 } 43351 if (lastLabelClickCoordinates && 43352 lastLabelClickCoordinates[0] === x && lastLabelClickCoordinates[1] === y) { 43353 return; // input click triggered by label click 43354 } 43355 // reset label click coordinates on first subsequent click 43356 if (lastLabelClickCoordinates) { 43357 lastLabelClickCoordinates = null; 43358 } 43359 // remember label click coordinates to prevent click busting of trigger click event on input 43360 if (nodeName_(event.target) === 'label') { 43361 lastLabelClickCoordinates = [x, y]; 43362 } 43363 43364 // Look for an allowable region containing this click. 43365 // If we find one, that means it was created by touchstart and n
43365ot removed by 43366 // preventGhostClick, so we don't bust it. 43367 if (checkAllowableRegions(touchCoordinates, x, y)) { 43368 return; 43369 } 43370 43371 // If we didn't find an allowable region, bust the click. 43372 event.stopPropagation(); 43373 event.preventDefault(); 43374 43375 // Blur focused form elements 43376 if (event.target && event.target.blur) { 43377 event.target.blur(); 43378 } 43379 } 43380 43381 43382 // Global touchstart handler that creates an allowable region for a click event. 43383 // This allowable region can be removed by preventGhostClick if we want to bust it. 43384 function onTouchStart(event) { 43385 var touches = event.touches && event.touches.length ? event.touches : [event]; 43386 var x = touches[0].clientX; 43387 var y = touches[0].clientY; 43388 touchCoordinates.push(x, y); 43389 43390 $timeout(function() { 43391 // Remove the allowable region. 43392 for (var i = 0; i < touchCoordinates.length; i += 2) { 43393 if (touchCoordinates[i] === x && touchCoordinates[i + 1] === y) { 43394 touchCoordinates.splice(i, i + 2); 43395 return; 43396 } 43397 } 43398 }, PREVENT_DURATION, false); 43399 } 43400 43401 // On the first call, attaches some event handlers. Then whenever it gets called, it creates a 43402 // zone around the touchstart where clicks will get busted. 43403 function preventGhostClick(x, y) { 43404 if (!touchCoordinates) { 43405 $rootElement[0].addEventListener('click', onClick, true); 43406 $rootElement[0].addEventListener('touchstart', onTouchStart, true); 43407 touchCoordinates = []; 43408 } 43409 43410 lastPreventedTime = Date.now(); 43411 43412 checkAllowableRegions(touchCoordinates, x, y); 43413 } 43414 43415 // Actual linking function. 43416 return function(scope, element, attr) { 43417 var clickHandler = $parse(attr.ngClick), 43418 tapping = false, 43419 tapElement, // Used to blur the element after a tap. 43420 startTime, // Used to check if the tap was held too long. 43421 touchStartX, 43422 touchStartY; 43423 43424 function resetState() { 43425 tapping = false; 43426 element.removeClass(ACTIVE_CLASS_NAME); 43427 } 43428 43429 element.on('touchstart', function(event) { 43430 tapping = true; 43431 tapElement = event.target ? event.target : event.srcElement; // IE uses srcElement. 43432 // Hack for Safari, which can target text nodes instead of containers. 43433 if (tapElement.nodeType === 3) { 43434 tapElement = tapElement.parentNode; 43435 } 43436 43437 element.addClass(ACTIVE_CLASS_NAME); 43438 43439 startTime = Date.now(); 43440 43441 // Use jQuery originalEvent 43442 var originalEvent = event.originalEvent || event; 43443 var touches = originalEvent.touches && originalEvent.touches.length ? originalEvent.touches : [originalEvent]; 43444 var e = touches[0]; 43445 touchStartX = e.clientX; 43446 touchStartY = e.clientY; 43447 }); 43448 43449 element.on('touchcancel', function(event) { 43450 resetState(); 43451 }); 43452 43453 element.on('touchend', function(event) { 43454 var diff = Date.now() - startTime; 43455 43456 // Use jQuery originalEvent 43457 var originalEvent = event.originalEvent || event; 43458 var touches = (originalEvent.changedTouches && originalEvent.changedTouches.length) ? 43459 originalEvent.changedTouches : 43460 ((originalEvent.touches && originalEvent.touches.length) ? originalEvent.touches : [originalEvent]); 43461 var e = touches[0]; 43462 var x = e.clientX; 43463 var y = e.clientY; 43464 var dist = Math.sqrt(Math.pow(x - touchStartX, 2) + Math.pow(y - touchStartY, 2)); 43465 43466 if (tapping && diff < TAP_DURATION && dist < MOVE_TOLERANCE) { 43467 // Call preventGhostClick so the clickbuster will catch the corresponding click. 43468 preventGhostClick(x, y); 43469 43470 // Blur the focused element (the button, probably) before firing the callback. 43471 // This doesn't work perfectly on Android Chrome, but seems to work elsewhere. 43472 // I couldn't get anything to work reliably on Android Chrome. 43473 if (tapElement) { 43474 tapElement.blur(); 43475 } 43476 43477 if (!angular.isDefined(attr.disabled) || attr.disabled === false) { 43478 element.triggerHandler('click', [event]); 43479 } 43480 } 43481 43482 resetState(); 43483 }); 43484 43485 // Hack for iOS Safari's benefit. It goes searching for onclick handlers and is liable to click 43486 // something else nearby. 43487 element.onclick = function(event) { }; 43488 43489 // Actual click handler. 43490 // There are three different kinds of clicks, only two of which reach this point. 43491 // - On desktop browsers without touch events, their clicks will always come here. 43492 // - On mobile browsers, the simulated "fast" click will call this. 43493 // - But the browser's follow-up slow click will be "busted" before it reaches this handler.
43494 // Therefore it's safe to use this directive on both mobile and desktop. 43495 element.on('click', function(event, touchend) { 43496 scope.$apply(function() { 43497 clickHandler(scope, {$event: (touchend || event)}); 43498 }); 43499 }); 43500 43501 element.on('mousedown', function(event) { 43502 element.addClass(ACTIVE_CLASS_NAME); 43503 }); 43504 43505 element.on('mousemove mouseup', function(event) { 43506 element.removeClass(ACTIVE_CLASS_NAME); 43507 }); 43508 43509 }; 43510}]; 43511 43512/* global ngTouch: false */ 43513 43514/** 43515 * @ngdoc directive 43516 * @name ngSwipeLeft 43517 * 43518 * @description 43519 * Specify custom behavior when an element is swiped to the left on a touchscreen device. 43520 * A leftward swipe is a quick, right-to-left slide of the finger. 43521 * Though ngSwipeLeft is designed for touch-based devices, it will work with a mouse click and drag 43522 * too. 43523 * 43524 * To disable the mouse click and drag functionality, add `ng-swipe-disable-mouse` to 43525 * the `ng-swipe-left` or `ng-swipe-right` DOM Element. 43526 * 43527 * Requires the {@link ngTouch `ngTouch`} module to be installed. 43528 * 43529 * @element ANY 43530 * @param {expression} ngSwipeLeft {@link guide/expression Expression} to evaluate 43531 * upon left swipe. (Event object is available as `$event`) 43532 * 43533 * @example 43534 <example module="ngSwipeLeftExample" deps="angular-touch.js" name="ng-swipe-left"> 43535 <file name="index.html"> 43536 <div ng-show="!showActions" ng-swipe-left="showActions = true"> 43537 Some list content, like an email in the inbox 43538 </div> 43539 <div ng-show="showActions" ng-swipe-right="showActions = false"> 43540 <button ng-click="reply()">Reply</button> 43541 <button ng-click="delete()">Delete</button> 43542 </div> 43543 </file> 43544 <file name="script.js"> 43545 angular.module('ngSwipeLeftExample', ['ngTouch']); 43546 </file> 43547 </example> 43548 */ 43549 43550/** 43551 * @ngdoc directive 43552 * @name ngSwipeRight 43553 * 43554 * @description 43555 * Specify custom behavior when an element is swiped to the right on a touchscreen device. 43556 * A rightward swipe is a quick, left-to-right slide of the finger. 43557 * Though ngSwipeRight is designed for touch-based devices, it will work with a mouse click and drag 43558 * too. 43559 * 43560 * Requires the {@link ngTouch `ngTouch`} module to be installed. 43561 * 43562 * @element ANY 43563 * @param {expression} ngSwipeRight {@link guide/expression Expression} to evaluate 43564 * upon right swipe. (Event object is available as `$event`) 43565 * 43566 * @example 43567 <example module="ngSwipeRightExample" deps="angular-touch.js" name="ng-swipe-right"> 43568 <file name="index.html"> 43569 <div ng-show="!showActions" ng-swipe-left="showActions = true"> 43570 Some list content, like an email in the inbox 43571 </div> 43572 <div ng-show="showActions" ng-swipe-right="showActions = false"> 43573 <button ng-click="reply()">Reply</button> 43574 <button ng-click="delete()">Delete</button> 43575 </div> 43576 </file> 43577 <file name="script.js"> 43578 angular.module('ngSwipeRightExample', ['ngTouch']); 43579 </file> 43580 </example> 43581 */ 43582 43583function makeSwipeDirective(directiveName, direction, eventName) { 43584 ngTouch.directive(directiveName, ['$parse', '$swipe', function($parse, $swipe) { 43585 // The maximum vertical delta for a swipe should be less than 75px. 43586 var MAX_VERTICAL_DISTANCE = 75; 43587 // Vertical distance should not be more than a fraction of the horizontal distance. 43588 var MAX_VERTICAL_RATIO = 0.3; 43589 // At least a 30px lateral motion is necessary for a swipe. 43590 var MIN_HORIZONTAL_DISTANCE = 30; 43591 43592 return function(scope, element, attr) { 43593 var swipeHandler = $parse(attr[directiveName]); 43594 43595 var startCoords, valid; 43596 43597 function validSwipe(coords) { 43598 // Check that it's within the coordinates. 43599 // Absolute vertical distance must be within tolerances. 43600 // Horizontal distance, we take the current X - the starting X. 43601 // This is negative for leftward swipes and positive for rightward swipes. 43602 // After multiplying by the direction (-1 for left, +1 for right), legal swipes 43603 // (ie. same direction as the directive wants) will have a positive delta and 43604 // illegal ones a negative delta. 43605 // Therefore this delta must be positive, and larger than the minimum. 43606 if (!startCoords) return false;
vendor: 4,010 bytes, lines 43607-43727
43607 var deltaY = Math.abs(coords.y - startCoords.y); 43608 var deltaX = (coords.x - startCoords.x) * direction; 43609 return valid && // Short circuit for already-invalidated swipes. 43610 deltaY < MAX_VERTICAL_DISTANCE && 43611 deltaX > 0 && 43612 deltaX > MIN_HORIZONTAL_DISTANCE && 43613 deltaY / deltaX < MAX_VERTICAL_RATIO; 43614 } 43615 43616 var pointerTypes = ['touch']; 43617 if (!angular.isDefined(attr['ngSwipeDisableMouse'])) { 43618 pointerTypes.push('mouse'); 43619 } 43620 $swipe.bind(element, { 43621 'start': function(coords, event) { 43622 startCoords = coords; 43623 valid = true; 43624 }, 43625 'cancel': function(event) { 43626 valid = false; 43627 }, 43628 'end': function(coords, event) { 43629 if (validSwipe(coords)) { 43630 scope.$apply(function() { 43631 element.triggerHandler(eventName); 43632 swipeHandler(scope, {$event: event}); 43633 }); 43634 } 43635 } 43636 }, pointerTypes); 43637 }; 43638 }]); 43639} 43640 43641// Left is negative X-coordinate, right is positive. 43642makeSwipeDirective('ngSwipeLeft', -1, 'swipeleft'); 43643makeSwipeDirective('ngSwipeRight', 1, 'swiperight'); 43644 43645 43646 43647})(window, window.angular); 43648 43649/** 43650 * @license AngularJS v1.5.11 43651 * (c) 2010-2017 Google, Inc. http://angularjs.org 43652 * License: MIT 43653 */ 43654(function(window, angular) {'use strict'; 43655 43656/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 43657 * Any commits to this file should be reviewed with security in mind. * 43658 * Changes to this file can potentially create security vulnerabilities. * 43659 * An approval from 2 Core members with history of modifying * 43660 * this file is required. * 43661 * * 43662 * Does the change somehow allow for arbitrary javascript to be executed? * 43663 * Or allows for someone to change the prototype of built-in objects? * 43664 * Or gives undesired access to variables likes document or window? * 43665 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 43666 43667var $sanitizeMinErr = angular.$$minErr('$sanitize'); 43668var bind; 43669var extend; 43670var forEach; 43671var isDefined; 43672var lowercase; 43673var noop; 43674var htmlParser; 43675var htmlSanitizeWriter; 43676 43677/** 43678 * @ngdoc module 43679 * @name ngSanitize 43680 * @description 43681 * 43682 * # ngSanitize 43683 * 43684 * The `ngSanitize` module provides functionality to sanitize HTML. 43685 * 43686 * 43687 * <div doc-module-components="ngSanitize"></div> 43688 * 43689 * See {@link ngSanitize.$sanitize `$sanitize`} for usage. 43690 */ 43691 43692/** 43693 * @ngdoc service 43694 * @name $sanitize 43695 * @kind function 43696 * 43697 * @description 43698 * Sanitizes an html string by stripping all potentially dangerous tokens. 43699 * 43700 * The input is sanitized by parsing the HTML into tokens. All safe tokens (from a whitelist) are 43701 * then serialized back to properly escaped html string. This means that no unsafe input can make 43702 * it into the returned string. 43703 * 43704 * The whitelist for URL sanitization of attribute values is configured using the functions 43705 * `aHrefSanitizationWhitelist` and `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider 43706 * `$compileProvider`}. 43707 * 43708 * The input may also contain SVG markup if this is enabled via {@link $sanitizeProvider}. 43709 * 43710 * @param {string} html HTML input. 43711 * @returns {string} Sanitized HTML. 43712 * 43713 * @example 43714 <example module="sanitizeExample" deps="angular-sanitize.js" name="sanitize-service"> 43715 <file name="index.html"> 43716 <script> 43717 angular.module('sanitizeExample', ['ngSanitize']) 43718 .controller('ExampleController', ['$scope', '$sce', function($scope, $sce) { 43719 $scope.snippet = 43720 '<p style="color:blue">an html\n' + 43721 '<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' + 43722 'snippet</p>'; 43723 $scope.deliberatelyTrustDangerousSnippet = function() { 43724 return $sce.trustAsHtml($scope.snippet); 43725 }; 43726 }]); 43727 </script>
43728 <div ng-controller="ExampleController"> 43729 Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea> 43730 <table> 43731 <tr> 43732 <td>Directive</td> 43733 <td>How</td> 43734 <td>Source</td> 43735 <td>Rendered</td> 43736 </tr> 43737 <tr id="bind-html-with-sanitize"> 43738 <td>ng-bind-html</td> 43739 <td>Automatically uses $sanitize</td> 43740 <td><pre><div ng-bind-html="snippet"><br/></div></pre></td> 43741 <td><div ng-bind-html="snippet"></div></td> 43742 </tr> 43743 <tr id="bind-html-with-trust"> 43744 <td>ng-bind-html</td> 43745 <td>Bypass $sanitize by explicitly trusting the dangerous value</td> 43746 <td> 43747 <pre><div ng-bind-html="deliberatelyTrustDangerousSnippet()"> 43748</div></pre> 43749 </td> 43750 <td><div ng-bind-html="deliberatelyTrustDangerousSnippet()"></div></td> 43751 </tr> 43752 <tr id="bind-default"> 43753 <td>ng-bind</td> 43754 <td>Automatically escapes</td> 43755 <td><pre><div ng-bind="snippet"><br/></div></pre></td> 43756 <td><div ng-bind="snippet"></div></td> 43757 </tr> 43758 </table> 43759 </div> 43760 </file> 43761 <file name="protractor.js" type="protractor"> 43762 it('should sanitize the html snippet by default', function() { 43763 expect(element(by.css('#bind-html-with-sanitize div')).getAttribute('innerHTML')). 43764 toBe('<p>an html\n<em>click here</em>\nsnippet</p>'); 43765 }); 43766 43767 it('should inline raw snippet if bound to a trusted value', function() { 43768 expect(element(by.css('#bind-html-with-trust div')).getAttribute('innerHTML')). 43769 toBe("<p style=\"color:blue\">an html\n" + 43770 "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + 43771 "snippet</p>"); 43772 }); 43773 43774 it('should escape snippet without any filter', function() { 43775 expect(element(by.css('#bind-default div')).getAttribute('innerHTML')). 43776 toBe("<p style=\"color:blue\">an html\n" + 43777 "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + 43778 "snippet</p>"); 43779 }); 43780 43781 it('should update', function() { 43782 element(by.model('snippet')).clear(); 43783 element(by.model('snippet')).sendKeys('new <b onclick="alert(1)">text</b>'); 43784 expect(element(by.css('#bind-html-with-sanitize div')).getAttribute('innerHTML')). 43785 toBe('new <b>text</b>'); 43786 expect(element(by.css('#bind-html-with-trust div')).getAttribute('innerHTML')).toBe( 43787 'new <b onclick="alert(1)">text</b>'); 43788 expect(element(by.css('#bind-default div')).getAttribute('innerHTML')).toBe( 43789 "new <b onclick=\"alert(1)\">text</b>"); 43790 }); 43791 </file> 43792 </example> 43793 */ 43794 43795 43796/** 43797 * @ngdoc provider 43798 * @name $sanitizeProvider 43799 * @this 43800 * 43801 * @description 43802 * Creates and configures {@link $sanitize} instance. 43803 */ 43804function $SanitizeProvider() { 43805 var svgEnabled = false; 43806 43807 this.$get = ['$$sanitizeUri', function($$sanitizeUri) { 43808 if (svgEnabled) { 43809 extend(validElements, svgElements); 43810 } 43811 return function(html) { 43812 var buf = []; 43813 htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) { 43814 return !/^unsafe:/.test($$sanitizeUri(uri, isImage)); 43815 })); 43816 return buf.join(''); 43817 }; 43818 }]; 43819 43820 43821 /** 43822 * @ngdoc method 43823 * @name $sanitizeProvider#enableSvg 43824 * @kind function 43825 * 43826 * @description 43827 * Enables a subset of svg to be supported by the sanitizer. 43828 * 43829 * <div class="alert alert-warning"> 43830 * <p>By enabling this setting without taking other precautions, you might expose your 43831 * application to click-hijacking attacks. In these attacks, sanitized svg elements could be positioned 43832 * outside of the containing element and be rendered over other elements on the page (e.g. a login 43833 * link). Such behavior can then result in phishing incidents.</p> 43834 * 43835 * <p>To protect against these, explicitly setup `overflow: hidden` css rule for all potential svg 43836 * tags within the sanitized content:</p> 43837 * 43838 * <br> 43839 * 43840 * <pre><code> 43841 * .rootOfTheIncludedContent svg { 43842 * overflow: hidden !important; 43843 * } 43844 * </code></pre> 43845 * </div> 43846 * 43847 * @param {boolean=} flag Enable or disable SVG support in the sanitizer. 43848 * @returns {boolean|ng.$sanitizeProvider} Returns the currently configured value if called 43849 * without an argument or self for chaining otherwise. 43850 */ 43851 this.enableSvg = function(enableSvg) { 43852 if (isDefined(enableSvg)) { 43853 svgEnabled = enableSvg; 43854 return this; 43855 } else { 43856 return svgEnabled; 43857 } 43858 }; 43859 43860 ////////////////////////////////////////////////////////////////////////////////////////////////// 43861 // Private stuff 43862 ////////////////////////////////////////////////////////////////////////////////////////////////// 43863 43864 bind = angular.bind; 43865 extend = angular.extend; 43866 forEach = angular.forEach; 43867 isDefined = angular.isDefined; 43868 lowercase = angular.lowercase; 43869 noop = angular.noop; 43870 43871 htmlParser = htmlParserImpl; 43872 htmlSanitizeWriter = htmlSanitizeWriterImpl; 43873 43874 // Regular Expressions for parsing tags and attributes 43875 var SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g, 43876 // Match everything outside of normal chars and " (quote character) 43877 NON_ALPHANUMERIC_REGEXP = /([^#-~ |!])/g; 43878 43879 43880 // Good source of info about elements and attributes 43881 // http://dev.w3.org/html5/spec/Overview.html#semantics 43882 // http://simon.html5.org/html-elements 43883 43884 // Safe Void Elements - HTML5 43885 // http://dev.w3.org/html5/spec/Overview.html#void-elements 43886 var voidElements = toMap('area,br,col,hr,img,wbr'); 43887 43888 // Elements that you can, intentionally, leave open (and which close themselves) 43889 // http://dev.w3.org/html5/spec/Overview.html#optional-tags 43890 var optionalEndTagBlockElements = toMap('colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr'), 43891 optionalEndTagInlineElements = toMap('rp,rt'), 43892 optionalEndTagElements = extend({}, 43893 optionalEndTagInlineElements, 43894 optionalEndTagBlockElements); 43895 43896 // Safe Block Elements - HTML5 43897 var blockElements = extend({}, optionalEndTagBlockElements, toMap('address,article,' + 43898 'aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,' + 43899 'h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,section,table,ul')); 43900 43901 // Inline Elements - HTML5 43902 var inlineElements = extend({}, optionalEndTagInlineElements, toMap('a,abbr,acronym,b,' + 43903 'bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,' +
43904 'samp,small,span,strike,strong,sub,sup,time,tt,u,var')); 43905 43906 // SVG Elements 43907 // https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Elements 43908 // Note: the elements animate,animateColor,animateMotion,animateTransform,set are intentionally omitted. 43909 // They can potentially allow for arbitrary javascript to be executed. See #11290 43910 var svgElements = toMap('circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph,' + 43911 'hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline,' + 43912 'radialGradient,rect,stop,svg,switch,text,title,tspan'); 43913 43914 // Blocked Elements (will be stripped) 43915 var blockedElements = toMap('script,style'); 43916 43917 var validElements = extend({}, 43918 voidElements, 43919 blockElements, 43920 inlineElements, 43921 optionalEndTagElements); 43922 43923 //Attributes that have href and hence need to be sanitized 43924 var uriAttrs = toMap('background,cite,href,longdesc,src,xlink:href'); 43925 43926 var htmlAttrs = toMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' + 43927 'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,' + 43928 'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,' + 43929 'scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,' + 43930 'valign,value,vspace,width'); 43931 43932 // SVG attributes (without "id" and "name" attributes) 43933 // https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Attributes 43934 var svgAttrs = toMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,' + 43935 'baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,' + 43936 'cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,' + 43937 'font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,' + 43938 'height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,keySplines,keyTimes,lang,' + 43939 'marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mathematical,' + 43940 'max,min,offset,opacity,orient,origin,overline-position,overline-thickness,panose-1,' + 43941 'path,pathLength,points,preserveAspectRatio,r,refX,refY,repeatCount,repeatDur,' + 43942 'requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,stemv,stop-color,' + 43943 'stop-opacity,strikethrough-position,strikethrough-thickness,stroke,stroke-dasharray,' + 43944 'stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,' + 43945 'stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,underline-position,' +
43946 'underline-thickness,unicode,unicode-range,units-per-em,values,version,viewBox,visibility,' + 43947 'width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,xlink:show,xlink:title,' + 43948 'xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,zoomAndPan', true); 43949 43950 var validAttrs = extend({}, 43951 uriAttrs, 43952 svgAttrs, 43953 htmlAttrs); 43954 43955 function toMap(str, lowercaseKeys) { 43956 var obj = {}, items = str.split(','), i; 43957 for (i = 0; i < items.length; i++) { 43958 obj[lowercaseKeys ? lowercase(items[i]) : items[i]] = true; 43959 } 43960 return obj; 43961 } 43962 43963 var inertBodyElement; 43964 (function(window) { 43965 var doc; 43966 if (window.document && window.document.implementation) { 43967 doc = window.document.implementation.createHTMLDocument('inert'); 43968 } else { 43969 throw $sanitizeMinErr('noinert', 'Can\'t create an inert html document'); 43970 } 43971 var docElement = doc.documentElement || doc.getDocumentElement(); 43972 var bodyElements = docElement.getElementsByTagName('body'); 43973 43974 // usually there should be only one body element in the document, but IE doesn't have any, so we need to create one 43975 if (bodyElements.length === 1) { 43976 inertBodyElement = bodyElements[0]; 43977 } else { 43978 var html = doc.createElement('html'); 43979 inertBodyElement = doc.createElement('body'); 43980 html.appendChild(inertBodyElement); 43981 doc.appendChild(html); 43982 } 43983 })(window); 43984 43985 /** 43986 * @example 43987 * htmlParser(htmlString, { 43988 * start: function(tag, attrs) {}, 43989 * end: function(tag) {}, 43990 * chars: function(text) {}, 43991 * comment: function(text) {} 43992 * }); 43993 * 43994 * @param {string} html string 43995 * @param {object} handler 43996 */ 43997 function htmlParserImpl(html, handler) { 43998 if (html === null || html === undefined) { 43999 html = ''; 44000 } else if (typeof html !== 'string') { 44001 html = '' + html; 44002 } 44003 inertBodyElement.innerHTML = html; 44004 44005 //mXSS protection 44006 var mXSSAttempts = 5; 44007 do { 44008 if (mXSSAttempts === 0) { 44009 throw $sanitizeMinErr('uinput', 'Failed to sanitize html because the input is unstable'); 44010 } 44011 mXSSAttempts--; 44012 44013 // strip custom-namespaced attributes on IE<=11 44014 if (window.document.documentMode) { 44015 stripCustomNsAttrs(inertBodyElement); 44016 } 44017 html = inertBodyElement.innerHTML; //trigger mXSS 44018 inertBodyElement.innerHTML = html; 44019 } while (html !== inertBodyElement.innerHTML); 44020 44021 var node = inertBodyElement.firstChild; 44022 while (node) { 44023 switch (node.nodeType) { 44024 case 1: // ELEMENT_NODE 44025 handler.start(node.nodeName.toLowerCase(), attrToMap(node.attributes)); 44026 break; 44027 case 3: // TEXT NODE 44028 handler.chars(node.textContent); 44029 break; 44030 } 44031 44032 var nextNode; 44033 if (!(nextNode = node.firstChild)) { 44034 if (node.nodeType === 1) { 44035 handler.end(node.nodeName.toLowerCase()); 44036 } 44037 nextNode = node.nextSibling; 44038 if (!nextNode) { 44039 while (nextNode == null) { 44040 node = node.parentNode; 44041 if (node === inertBodyElement) break; 44042 nextNode = node.nextSibling; 44043 if (node.nodeType === 1) { 44044 handler.end(node.nodeName.toLowerCase()); 44045 } 44046 } 44047 } 44048 } 44049 node = nextNode; 44050 } 44051 44052 while ((node = inertBodyElement.firstChild)) { 44053 inertBodyElement.removeChild(node); 44054 } 44055 } 44056 44057 function attrToMap(attrs) { 44058 var map = {}; 44059 for (var i = 0, ii = attrs.length; i < ii; i++) { 44060 var attr = attrs[i]; 44061 map[attr.name] = attr.value; 44062 } 44063 return map; 44064 } 44065 44066 44067 /** 44068 * Escapes all potentially dangerous characters, so that the 44069 * resulting string can be safely inserted into attribute or 44070 * element text. 44071 * @param value 44072 * @returns {string} escaped text 44073 */ 44074 function encodeEntities(value) { 44075 return value. 44076 replace(/&/g, '&'). 44077 replace(SURROGATE_PAIR_REGEXP, function(value) { 44078 var hi = value.charCodeAt(0); 44079 var low = value.charCodeAt(1); 44080 return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';'; 44081 }). 44082 replace(NON_ALPHANUMERIC_REGEXP, function(value) { 44083 return '&#' + value.charCodeAt(0) + ';'; 44084 }). 44085 replace(/</g, '<'). 44086 replace(/>/g, '>'); 44087 } 44088 44089 /** 44090 * create an HTML/XML writer which writes to buffer 44091 * @param {Array} buf use buf.join('') to get out sanitized html string 44092 * @returns {object} in the form of { 44093 * start: function(tag, attrs) {}, 44094 * end: function(tag) {}, 44095 * chars: function(text) {}, 44096 * comment: function(text) {} 44097 * } 44098 */ 44099 function htmlSanitizeWriterImpl(buf, uriValidator) { 44100 var ignoreCurrentElement = false;
vendor: 3,676 bytes, lines 44101-44221
44101 var out = bind(buf, buf.push); 44102 return { 44103 start: function(tag, attrs) { 44104 tag = lowercase(tag); 44105 if (!ignoreCurrentElement && blockedElements[tag]) { 44106 ignoreCurrentElement = tag; 44107 } 44108 if (!ignoreCurrentElement && validElements[tag] === true) { 44109 out('<'); 44110 out(tag); 44111 forEach(attrs, function(value, key) { 44112 var lkey = lowercase(key); 44113 var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background'); 44114 if (validAttrs[lkey] === true && 44115 (uriAttrs[lkey] !== true || uriValidator(value, isImage))) { 44116 out(' '); 44117 out(key); 44118 out('="'); 44119 out(encodeEntities(value)); 44120 out('"'); 44121 } 44122 }); 44123 out('>'); 44124 } 44125 }, 44126 end: function(tag) { 44127 tag = lowercase(tag); 44128 if (!ignoreCurrentElement && validElements[tag] === true && voidElements[tag] !== true) { 44129 out('</'); 44130 out(tag); 44131 out('>'); 44132 } 44133 // eslint-disable-next-line eqeqeq 44134 if (tag == ignoreCurrentElement) { 44135 ignoreCurrentElement = false; 44136 } 44137 }, 44138 chars: function(chars) { 44139 if (!ignoreCurrentElement) { 44140 out(encodeEntities(chars)); 44141 } 44142 } 44143 }; 44144 } 44145 44146 44147 /** 44148 * When IE9-11 comes across an unknown namespaced attribute e.g. 'xlink:foo' it adds 'xmlns:ns1' attribute to declare 44149 * ns1 namespace and prefixes the attribute with 'ns1' (e.g. 'ns1:xlink:foo'). This is undesirable since we don't want 44150 * to allow any of these custom attributes. This method strips them all. 44151 * 44152 * @param node Root element to process 44153 */ 44154 function stripCustomNsAttrs(node) { 44155 while (node) { 44156 if (node.nodeType === window.Node.ELEMENT_NODE) { 44157 var attrs = node.attributes; 44158 for (var i = 0, l = attrs.length; i < l; i++) { 44159 var attrNode = attrs[i]; 44160 var attrName = attrNode.name.toLowerCase(); 44161 if (attrName === 'xmlns:ns1' || attrName.lastIndexOf('ns1:', 0) === 0) { 44162 node.removeAttributeNode(attrNode); 44163 i--; 44164 l--; 44165 } 44166 } 44167 } 44168 44169 var nextNode = node.firstChild; 44170 if (nextNode) { 44171 stripCustomNsAttrs(nextNode); 44172 } 44173 44174 node = node.nextSibling; 44175 } 44176 } 44177} 44178 44179function sanitizeText(chars) { 44180 var buf = []; 44181 var writer = htmlSanitizeWriter(buf, noop); 44182 writer.chars(chars); 44183 return buf.join(''); 44184} 44185 44186 44187// define ngSanitize module and register $sanitize service 44188angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider); 44189 44190/** 44191 * @ngdoc filter 44192 * @name linky 44193 * @kind function 44194 * 44195 * @description 44196 * Finds links in text input and turns them into html links. Supports `http/https/ftp/mailto` and 44197 * plain email address links. 44198 * 44199 * Requires the {@link ngSanitize `ngSanitize`} module to be installed. 44200 * 44201 * @param {string} text Input text. 44202 * @param {string} target Window (`_blank|_self|_parent|_top`) or named frame to open links in. 44203 * @param {object|function(url)} [attributes] Add custom attributes to the link element. 44204 * 44205 * Can be one of: 44206 * 44207 * - `object`: A map of attributes 44208 * - `function`: Takes the url as a parameter and returns a map of attributes 44209 * 44210 * If the map of attributes contains a value for `target`, it overrides the value of 44211 * the target parameter. 44212 * 44213 * 44214 * @returns {string} Html-linkified and {@link $sanitize sanitized} text. 44215 * 44216 * @usage 44217 <span ng-bind-html="linky_expression | linky"></span> 44218 * 44219 * @example 44220 <example module="linkyExample" deps="angular-sanitize.js" name="linky-filter"> 44221 <file name="index.html">
44222 <div ng-controller="ExampleController"> 44223 Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea> 44224 <table> 44225 <tr> 44226 <th>Filter</th> 44227 <th>Source</th> 44228 <th>Rendered</th> 44229 </tr> 44230 <tr id="linky-filter"> 44231 <td>linky filter</td> 44232 <td> 44233 <pre><div ng-bind-html="snippet | linky"><br></div></pre> 44234 </td> 44235 <td> 44236 <div ng-bind-html="snippet | linky"></div> 44237 </td> 44238 </tr> 44239 <tr id="linky-target"> 44240 <td>linky target</td> 44241 <td> 44242 <pre><div ng-bind-html="snippetWithSingleURL | linky:'_blank'"><br></div></pre> 44243 </td> 44244 <td> 44245 <div ng-bind-html="snippetWithSingleURL | linky:'_blank'"></div> 44246 </td> 44247 </tr> 44248 <tr id="linky-custom-attributes"> 44249 <td>linky custom attributes</td> 44250 <td> 44251 <pre><div ng-bind-html="snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}"><br></div></pre> 44252 </td> 44253 <td> 44254 <div ng-bind-html="snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}"></div> 44255 </td> 44256 </tr> 44257 <tr id="escaped-html"> 44258 <td>no filter</td> 44259 <td><pre><div ng-bind="snippet"><br></div></pre></td> 44260 <td><div ng-bind="snippet"></div></td> 44261 </tr> 44262 </table> 44263 </file> 44264 <file name="script.js"> 44265 angular.module('linkyExample', ['ngSanitize']) 44266 .controller('ExampleController', ['$scope', function($scope) { 44267 $scope.snippet = 44268 'Pretty text with some links:\n' + 44269 'http://angularjs.org/,\n' + 44270 'mailto:[email protected],\n' + 44271 '[email protected],\n' + 44272 'and one more: ftp://127.0.0.1/.'; 44273 $scope.snippetWithSingleURL = 'http://angularjs.org/'; 44274 }]); 44275 </file> 44276 <file name="protractor.js" type="protractor"> 44277 it('should linkify the snippet with urls', function() { 44278 expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()). 44279 toBe('Pretty text with some links: http://angularjs.org/, [email protected], ' + 44280 '[email protected], and one more: ftp://127.0.0.1/.'); 44281 expect(element.all(by.css('#linky-filter a')).count()).toEqual(4); 44282 }); 44283 44284 it('should not linkify snippet without the linky filter', function() { 44285 expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()). 44286 toBe('Pretty text with some links: http://angularjs.org/, mailto:[email protected], ' + 44287 '[email protected], and one more: ftp://127.0.0.1/.'); 44288 expect(element.all(by.css('#escaped-html a')).count()).toEqual(0); 44289 }); 44290 44291 it('should update', function() { 44292 element(by.model('snippet')).clear(); 44293 element(by.model('snippet')).sendKeys('new http://link.'); 44294 expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()). 44295 toBe('new http://link.'); 44296 expect(element.all(by.css('#linky-filter a')).count()).toEqual(1); 44297 expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()) 44298 .toBe('new http://link.'); 44299 }); 44300 44301 it('should work with the target property', function() { 44302 expect(element(by.id('linky-target')). 44303 element(by.binding("snippetWithSingleURL | linky:'_blank'")).getText()). 44304 toBe('http://angularjs.org/'); 44305 expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank'); 44306 }); 44307 44308 it('should optionally add custom attributes', function() { 44309 expect(element(by.id('linky-custom-attributes')). 44310 element(by.binding("snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}")).getText()). 44311 toBe('http://angularjs.org/'); 44312 expect(element(by.css('#linky-custom-attributes a')).getAttribute('rel')).toEqual('nofollow'); 44313 }); 44314 </file> 44315 </example> 44316 */ 44317angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) { 44318 var LINKY_URL_REGEXP = 44319 /((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"\u201d\u2019]/i, 44320 MAILTO_REGEXP = /^mailto:/i; 44321 44322 var linkyMinErr = angular.$$minErr('linky'); 44323 var isDefined = angular.isDefined; 44324 var isFunction = angular.isFunction; 44325 var isObject = angular.isObject; 44326 var isString = angular.isString; 44327 44328 return function(text, target, attributes) { 44329 if (text == null || text === '') return text; 44330 if (!isString(text)) throw linkyMinErr('notstring', 'Expected string but received: {0}', text); 44331 44332 var attributesFn = 44333 isFunction(attributes) ? attributes : 44334 isObject(attributes) ? function getAttributesObject() {return attributes;} : 44335 function getEmptyAttributesObject() {return {};}; 44336 44337 var match; 44338 var raw = text; 44339 var html = []; 44340 var url; 44341 var i; 44342 while ((match = raw.match(LINKY_URL_REGEXP))) { 44343 // We can not end in these as they are sometimes found at the end of the sentence 44344 url = match[0]; 44345 // if we did not match ftp/http/www/mailto then assume mailto 44346 if (!match[2] && !match[4]) { 44347 url = (match[3] ? 'http://' : 'mailto:') + url; 44348 } 44349 i = match.index;
vendor: 4,083 bytes, lines 44350-44449
44350 addText(raw.substr(0, i)); 44351 addLink(url, match[0].replace(MAILTO_REGEXP, '')); 44352 raw = raw.substring(i + match[0].length); 44353 } 44354 addText(raw); 44355 return $sanitize(html.join('')); 44356 44357 function addText(text) { 44358 if (!text) { 44359 return; 44360 } 44361 html.push(sanitizeText(text)); 44362 } 44363 44364 function addLink(url, text) { 44365 var key, linkAttributes = attributesFn(url); 44366 html.push('<a '); 44367 44368 for (key in linkAttributes) { 44369 html.push(key + '="' + linkAttributes[key] + '" '); 44370 } 44371 44372 if (isDefined(target) && !('target' in linkAttributes)) { 44373 html.push('target="', 44374 target, 44375 '" '); 44376 } 44377 html.push('href="', 44378 url.replace(/"/g, '"'), 44379 '">'); 44380 addText(text); 44381 html.push('</a>'); 44382 } 44383 }; 44384}]); 44385 44386 44387})(window, window.angular); 44388 44389/** 44390 * @license AngularJS v1.5.11 44391 * (c) 2010-2017 Google, Inc. http://angularjs.org 44392 * License: MIT 44393 */ 44394(function(window, angular) {'use strict'; 44395 44396/** 44397 * @ngdoc module 44398 * @name ngAria 44399 * @description 44400 * 44401 * The `ngAria` module provides support for common 44402 * [<abbr title="Accessible Rich Internet Applications">ARIA</abbr>](http://www.w3.org/TR/wai-aria/) 44403 * attributes that convey state or semantic information about the application for users 44404 * of assistive technologies, such as screen readers. 44405 * 44406 * <div doc-module-components="ngAria"></div> 44407 * 44408 * ## Usage 44409 * 44410 * For ngAria to do its magic, simply include the module `ngAria` as a dependency. The following 44411 * directives are supported: 44412 * `ngModel`, `ngChecked`, `ngReadonly`, `ngRequired`, `ngValue`, `ngDisabled`, `ngShow`, `ngHide`, `ngClick`, 44413 * `ngDblClick`, and `ngMessages`. 44414 * 44415 * Below is a more detailed breakdown of the attributes handled by ngAria: 44416 * 44417 * | Directive | Supported Attributes | 44418 * |---------------------------------------------|----------------------------------------------------------------------------------------| 44419 * | {@link ng.directive:ngModel ngModel} | aria-checked, aria-valuemin, aria-valuemax, aria-valuenow, aria-invalid, aria-required, input roles | 44420 * | {@link ng.directive:ngDisabled ngDisabled} | aria-disabled | 44421 * | {@link ng.directive:ngRequired ngRequired} | aria-required 44422 * | {@link ng.directive:ngChecked ngChecked} | aria-checked 44423 * | {@link ng.directive:ngReadonly ngReadonly} | aria-readonly | 44424 * | {@link ng.directive:ngValue ngValue} | aria-checked | 44425 * | {@link ng.directive:ngShow ngShow} | aria-hidden | 44426 * | {@link ng.directive:ngHide ngHide} | aria-hidden | 44427 * | {@link ng.directive:ngDblclick ngDblclick} | tabindex | 44428 * | {@link module:ngMessages ngMessages} | aria-live | 44429 * | {@link ng.directive:ngClick ngClick} | tabindex, keypress event, button role | 44430 * 44431 * Find out more information about each directive by reading the 44432 * {@link guide/accessibility ngAria Developer Guide}. 44433 * 44434 * ## Example 44435 * Using ngDisabled with ngAria: 44436 * ```html 44437 * <md-checkbox ng-disabled="disabled"> 44438 * ``` 44439 * Becomes: 44440 * ```html 44441 * <md-checkbox ng-disabled="disabled" aria-disabled="true"> 44442 * ``` 44443 * 44444 * ## Disabling Attributes 44445 * It's possible to disable individual attributes added by ngAria with the 44446 * {@link ngAria.$ariaProvider#config config} method. For more details, see the 44447 * {@link guide/accessibility Developer Guide}. 44448 */ 44449var ngAriaModule = angular.module('ngAria', ['ng']).
vendor: 4,119 bytes, lines 44450-44563
44450 provider('$aria', $AriaProvider); 44451 44452/** 44453* Internal Utilities 44454*/ 44455var nodeBlackList = ['BUTTON', 'A', 'INPUT', 'TEXTAREA', 'SELECT', 'DETAILS', 'SUMMARY']; 44456 44457var isNodeOneOf = function(elem, nodeTypeArray) { 44458 if (nodeTypeArray.indexOf(elem[0].nodeName) !== -1) { 44459 return true; 44460 } 44461}; 44462/** 44463 * @ngdoc provider 44464 * @name $ariaProvider 44465 * @this 44466 * 44467 * @description 44468 * 44469 * Used for configuring the ARIA attributes injected and managed by ngAria. 44470 * 44471 * ```js 44472 * angular.module('myApp', ['ngAria'], function config($ariaProvider) { 44473 * $ariaProvider.config({ 44474 * ariaValue: true, 44475 * tabindex: false 44476 * }); 44477 * }); 44478 *``` 44479 * 44480 * ## Dependencies 44481 * Requires the {@link ngAria} module to be installed. 44482 * 44483 */ 44484function $AriaProvider() { 44485 var config = { 44486 ariaHidden: true, 44487 ariaChecked: true, 44488 ariaReadonly: true, 44489 ariaDisabled: true, 44490 ariaRequired: true, 44491 ariaInvalid: true, 44492 ariaValue: true, 44493 tabindex: true, 44494 bindKeypress: true, 44495 bindRoleForClick: true 44496 }; 44497 44498 /** 44499 * @ngdoc method 44500 * @name $ariaProvider#config 44501 * 44502 * @param {object} config object to enable/disable specific ARIA attributes 44503 * 44504 * - **ariaHidden** â `{boolean}` â Enables/disables aria-hidden tags 44505 * - **ariaChecked** â `{boolean}` â Enables/disables aria-checked tags 44506 * - **ariaReadonly** â `{boolean}` â Enables/disables aria-readonly tags 44507 * - **ariaDisabled** â `{boolean}` â Enables/disables aria-disabled tags 44508 * - **ariaRequired** â `{boolean}` â Enables/disables aria-required tags 44509 * - **ariaInvalid** â `{boolean}` â Enables/disables aria-invalid tags 44510 * - **ariaValue** â `{boolean}` â Enables/disables aria-valuemin, aria-valuemax and aria-valuenow tags 44511 * - **tabindex** â `{boolean}` â Enables/disables tabindex tags 44512 * - **bindKeypress** â `{boolean}` â Enables/disables keypress event binding on `div` and 44513 * `li` elements with ng-click 44514 * - **bindRoleForClick** â `{boolean}` â Adds role=button to non-interactive elements like `div` 44515 * using ng-click, making them more accessible to users of assistive technologies 44516 * 44517 * @description 44518 * Enables/disables various ARIA attributes 44519 */ 44520 this.config = function(newConfig) { 44521 config = angular.extend(config, newConfig); 44522 }; 44523 44524 function watchExpr(attrName, ariaAttr, nodeBlackList, negate) { 44525 return function(scope, elem, attr) { 44526 var ariaCamelName = attr.$normalize(ariaAttr); 44527 if (config[ariaCamelName] && !isNodeOneOf(elem, nodeBlackList) && !attr[ariaCamelName]) { 44528 scope.$watch(attr[attrName], function(boolVal) { 44529 // ensure boolean value 44530 boolVal = negate ? !boolVal : !!boolVal; 44531 elem.attr(ariaAttr, boolVal); 44532 }); 44533 } 44534 }; 44535 } 44536 /** 44537 * @ngdoc service 44538 * @name $aria 44539 * 44540 * @description 44541 * @priority 200 44542 * 44543 * The $aria service contains helper methods for applying common 44544 * [ARIA](http://www.w3.org/TR/wai-aria/) attributes to HTML directives. 44545 * 44546 * ngAria injects common accessibility attributes that tell assistive technologies when HTML 44547 * elements are enabled, selected, hidden, and more. To see how this is performed with ngAria, 44548 * let's review a code snippet from ngAria itself: 44549 * 44550 *```js 44551 * ngAriaModule.directive('ngDisabled', ['$aria', function($aria) { 44552 * return $aria.$$watchExpr('ngDisabled', 'aria-disabled', nodeBlackList, false); 44553 * }]) 44554 *``` 44555 * Shown above, the ngAria module creates a directive with the same signature as the 44556 * traditional `ng-disabled` directive. But this ngAria version is dedicated to 44557 * solely managing accessibility attributes on custom elements. The internal `$aria` service is 44558 * used to watch the boolean attribute `ngDisabled`. If it has not been explicitly set by the 44559 * developer, `aria-disabled` is injected as an attribute with its value synchronized to the 44560 * value in `ngDisabled`. 44561 * 44562 * Because ngAria hooks into the `ng-disabled` directive, developers do not have to do 44563 * anything to enable this feature. The `aria-disabled` attribute is automatically managed
44564 * simply as a silent side-effect of using `ng-disabled` with the ngAria module. 44565 * 44566 * The full list of directives that interface with ngAria: 44567 * * **ngModel** 44568 * * **ngChecked** 44569 * * **ngReadonly** 44570 * * **ngRequired** 44571 * * **ngDisabled** 44572 * * **ngValue** 44573 * * **ngShow** 44574 * * **ngHide** 44575 * * **ngClick** 44576 * * **ngDblclick** 44577 * * **ngMessages** 44578 * 44579 * Read the {@link guide/accessibility ngAria Developer Guide} for a thorough explanation of each 44580 * directive. 44581 * 44582 * 44583 * ## Dependencies 44584 * Requires the {@link ngAria} module to be installed. 44585 */ 44586 this.$get = function() { 44587 return { 44588 config: function(key) { 44589 return config[key]; 44590 }, 44591 $$watchExpr: watchExpr 44592 }; 44593 }; 44594} 44595 44596 44597ngAriaModule.directive('ngShow', ['$aria', function($aria) { 44598 return $aria.$$watchExpr('ngShow', 'aria-hidden', [], true); 44599}]) 44600.directive('ngHide', ['$aria', function($aria) { 44601 return $aria.$$watchExpr('ngHide', 'aria-hidden', [], false); 44602}]) 44603.directive('ngValue', ['$aria', function($aria) { 44604 return $aria.$$watchExpr('ngValue', 'aria-checked', nodeBlackList, false); 44605}]) 44606.directive('ngChecked', ['$aria', function($aria) { 44607 return $aria.$$watchExpr('ngChecked', 'aria-checked', nodeBlackList, false); 44608}]) 44609.directive('ngReadonly', ['$aria', function($aria) { 44610 return $aria.$$watchExpr('ngReadonly', 'aria-readonly', nodeBlackList, false); 44611}]) 44612.directive('ngRequired', ['$aria', function($aria) { 44613 return $aria.$$watchExpr('ngRequired', 'aria-required', nodeBlackList, false); 44614}]) 44615.directive('ngModel', ['$aria', function($aria) { 44616 44617 function shouldAttachAttr(attr, normalizedAttr, elem, allowBlacklistEls) { 44618 return $aria.config(normalizedAttr) && !elem.attr(attr) && (allowBlacklistEls || !isNodeOneOf(elem, nodeBlackList)); 44619 } 44620 44621 function shouldAttachRole(role, elem) { 44622 // if element does not have role attribute 44623 // AND element type is equal to role (if custom element has a type equaling shape) <-- remove? 44624 // AND element is not INPUT 44625 return !elem.attr('role') && (elem.attr('type') === role) && (elem[0].nodeName !== 'INPUT'); 44626 } 44627 44628 function getShape(attr, elem) { 44629 var type = attr.type, 44630 role = attr.role; 44631 44632 return ((type || role) === 'checkbox' || role === 'menuitemcheckbox') ? 'checkbox' : 44633 ((type || role) === 'radio' || role === 'menuitemradio') ? 'radio' : 44634 (type === 'range' || role === 'progressbar' || role === 'slider') ? 'range' : ''; 44635 } 44636 44637 return { 44638 restrict: 'A', 44639 require: 'ngModel', 44640 priority: 200, //Make sure watches are fired after any other directives that affect the ngModel value 44641 compile: function(elem, attr) { 44642 var shape = getShape(attr, elem); 44643 44644 return { 44645 pre: function(scope, elem, attr, ngModel) { 44646 if (shape === 'checkbox') { 44647 //Use the input[checkbox] $isEmpty implementation for elements with checkbox roles 44648 ngModel.$isEmpty = function(value) { 44649 return value === false; 44650 }; 44651 } 44652 }, 44653 post: function(scope, elem, attr, ngModel) { 44654 var needsTabIndex = shouldAttachAttr('tabindex', 'tabindex', elem, false); 44655 44656 function ngAriaWatchModelValue() { 44657 return ngModel.$modelValue; 44658 } 44659 44660 function getRadioReaction(newVal) { 44661 // Strict comparison would cause a BC 44662 // eslint-disable-next-line eqeqeq 44663 var boolVal = (attr.value == ngModel.$viewValue); 44664 elem.attr('aria-checked', boolVal); 44665 } 44666 44667 function getCheckboxReaction() { 44668 elem.attr('aria-checked', !ngModel.$isEmpty(ngModel.$viewValue)); 44669 } 44670 44671 switch (shape) { 44672 case 'radio': 44673 case 'checkbox': 44674 if (shouldAttachRole(shape, elem)) { 44675 elem.attr('role', shape); 44676 } 44677 if (shouldAttachAttr('aria-checked', 'ariaChecked', elem, false)) { 44678 scope.$watch(ngAriaWatchModelValue, shape === 'radio' ? 44679 getRadioReaction : getCheckboxReaction); 44680 } 44681 if (needsTabIndex) { 44682 elem.attr('tabindex', 0); 44683 } 44684 break; 44685 case 'range': 44686 if (shouldAttachRole(shape, elem)) { 44687 elem.attr('role', 'slider'); 44688 } 44689 if ($aria.config('ariaValue')) { 44690 var needsAriaValuemin = !elem.attr('aria-valuemin') && 44691 (attr.hasOwnProperty('min') || attr.hasOwnProperty('ngMin')); 44692 var needsAriaValuemax = !elem.attr('aria-valuemax') && 44693 (attr.hasOwnProperty('max') || attr.hasOwnProperty('ngMax')); 44694 var needsAriaValuenow = !elem.attr('aria-valuenow'); 44695 44696 if (needsAriaValuemin) { 44697 attr.$observe('min', function ngAriaValueMinReaction(newVal) { 44698 elem.attr('aria-valuemin', newVal); 44699 }); 44700 } 44701 if (needsAriaValuemax) { 44702 attr.$observe('max', function ngAriaValueMinReaction(newVal) { 44703 elem.attr('aria-valuemax', newVal); 44704 }); 44705 } 44706 if (needsAriaValuenow) { 44707 scope.$watch(ngAriaWatchModelValue, function ngAriaValueNowReaction(newVal) { 44708 elem.attr('aria-valuenow', newVal); 44709 }); 44710 } 44711 } 44712 if (needsTabIndex) { 44713 elem.attr('tabindex', 0); 44714 } 44715 break; 44716 } 44717 44718 if (!attr.hasOwnProperty('ngRequired') && ngModel.$validators.required 44719 && shouldAttachAttr('aria-required', 'ariaRequired', elem, false)) { 44720 // ngModel.$error.required is undefined on custom controls 44721 attr.$observe('required', function() { 44722 elem.attr('aria-required', !!attr['required']); 44723 }); 44724 } 44725 44726 if (shouldAttachAttr('aria-invalid', 'ariaInvalid', elem, true)) { 44727 scope.$watch(function ngAriaInvalidWatch() {
44728 return ngModel.$invalid; 44729 }, function ngAriaInvalidReaction(newVal) { 44730 elem.attr('aria-invalid', !!newVal); 44731 }); 44732 } 44733 } 44734 }; 44735 } 44736 }; 44737}]) 44738.directive('ngDisabled', ['$aria', function($aria) { 44739 return $aria.$$watchExpr('ngDisabled', 'aria-disabled', nodeBlackList, false); 44740}]) 44741.directive('ngMessages', function() { 44742 return { 44743 restrict: 'A', 44744 require: '?ngMessages', 44745 link: function(scope, elem, attr, ngMessages) { 44746 if (!elem.attr('aria-live')) { 44747 elem.attr('aria-live', 'assertive'); 44748 } 44749 } 44750 }; 44751}) 44752.directive('ngClick',['$aria', '$parse', function($aria, $parse) { 44753 return { 44754 restrict: 'A', 44755 compile: function(elem, attr) { 44756 var fn = $parse(attr.ngClick, /* interceptorFn */ null, /* expensiveChecks */ true); 44757 return function(scope, elem, attr) { 44758 44759 if (!isNodeOneOf(elem, nodeBlackList)) { 44760 44761 if ($aria.config('bindRoleForClick') && !elem.attr('role')) { 44762 elem.attr('role', 'button'); 44763 } 44764 44765 if ($aria.config('tabindex') && !elem.attr('tabindex')) { 44766 elem.attr('tabindex', 0); 44767 } 44768 44769 if ($aria.config('bindKeypress') && !attr.ngKeypress) { 44770 elem.on('keypress', function(event) { 44771 var keyCode = event.which || event.keyCode; 44772 if (keyCode === 32 || keyCode === 13) { 44773 scope.$apply(callback); 44774 } 44775 44776 function callback() { 44777 fn(scope, { $event: event }); 44778 } 44779 }); 44780 } 44781 } 44782 }; 44783 } 44784 }; 44785}]) 44786.directive('ngDblclick', ['$aria', function($aria) { 44787 return function(scope, elem, attr) { 44788 if ($aria.config('tabindex') && !elem.attr('tabindex') && !isNodeOneOf(elem, nodeBlackList)) { 44789 elem.attr('tabindex', 0); 44790 } 44791 }; 44792}]); 44793 44794 44795})(window, window.angular); 44796 44797/** 44798 * @license AngularJS v1.5.11 44799 * (c) 2010-2017 Google, Inc. http://angularjs.org 44800 * License: MIT 44801 */ 44802(function(window, angular) {'use strict'; 44803 44804var $resourceMinErr = angular.$$minErr('$resource'); 44805 44806// Helper functions and regex to lookup a dotted path on an object 44807// stopping at undefined/null. The path must be composed of ASCII 44808// identifiers (just like $parse) 44809var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$@][0-9a-zA-Z_$@]*)+$/; 44810 44811function isValidDottedPath(path) { 44812 return (path != null && path !== '' && path !== 'hasOwnProperty' && 44813 MEMBER_NAME_REGEX.test('.' + path)); 44814} 44815 44816function lookupDottedPath(obj, path) { 44817 if (!isValidDottedPath(path)) { 44818 throw $resourceMinErr('badmember', 'Dotted member path "@{0}" is invalid.', path); 44819 } 44820 var keys = path.split('.'); 44821 for (var i = 0, ii = keys.length; i < ii && angular.isDefined(obj); i++) { 44822 var key = keys[i]; 44823 obj = (obj !== null) ? obj[key] : undefined; 44824 } 44825 return obj; 44826} 44827 44828/** 44829 * Create a shallow copy of an object and clear other fields from the destination 44830 */ 44831function shallowClearAndCopy(src, dst) { 44832 dst = dst || {}; 44833 44834 angular.forEach(dst, function(value, key) { 44835 delete dst[key]; 44836 }); 44837 44838 for (var key in src) { 44839 if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) { 44840 dst[key] = src[key]; 44841 } 44842 } 44843 44844 return dst; 44845} 44846 44847/** 44848 * @ngdoc module 44849 * @name ngResource 44850 * @description 44851 * 44852 * # ngResource 44853 * 44854 * The `ngResource` module provides interaction support with RESTful services 44855 * via the $resource service. 44856 * 44857 * 44858 * <div doc-module-components="ngResource"></div> 44859 * 44860 * See {@link ngResource.$resourceProvider} and {@link ngResource.$resource} for usage. 44861 */ 44862 44863/** 44864 * @ngdoc provider 44865 * @name $resourceProvider 44866 * 44867 * @description 44868 * 44869 * Use `$resourceProvider` to change the default behavior of the {@link ngResource.$resource} 44870 * service. 44871 * 44872 * ## Dependencies 44873 * Requires the {@link ngResource } module to be installed. 44874 * 44875 */ 44876 44877/** 44878 * @ngdoc service 44879 * @name $resource 44880 * @requires $http 44881 * @requires ng.$log 44882 * @requires $q 44883 * @requires ng.$timeout 44884 * 44885 * @description 44886 * A factory which creates a resource object that lets you interact with 44887 * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources. 44888 * 44889 * The returned resource object has action methods which provide high-level behaviors without 44890 * the need to interact with the low level {@link ng.$http $http} service. 44891 * 44892 * Requires the {@link ngResource `ngResource`} module to be installed. 44893 * 44894 * By default, trailing slashes will be stripped from the calculated URLs, 44895 * which can pose problems with server backends that do not expect that 44896 * behavior. This can be disabled by configuring the `$resourceProvider` like 44897 * this: 44898 * 44899 * ```js 44900 app.config(['$resourceProvider', function($resourceProvider) { 44901 // Don't strip trailing slashes from calculated URLs 44902 $resourceProvider.defaults.stripTrailingSlashes = false;
44903 }]); 44904 * ``` 44905 * 44906 * @param {string} url A parameterized URL template with parameters prefixed by `:` as in 44907 * `/user/:username`. If you are using a URL with a port number (e.g. 44908 * `http://example.com:8080/api`), it will be respected. 44909 * 44910 * If you are using a url with a suffix, just add the suffix, like this: 44911 * `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')` 44912 * or even `$resource('http://example.com/resource/:resource_id.:format')` 44913 * If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be 44914 * collapsed down to a single `.`. If you need this sequence to appear and not collapse then you 44915 * can escape it with `/\.`. 44916 * 44917 * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in 44918 * `actions` methods. If a parameter value is a function, it will be called every time 44919 * a param value needs to be obtained for a request (unless the param was overridden). The function 44920 * will be passed the current data value as an argument. 44921 * 44922 * Each key value in the parameter object is first bound to url template if present and then any 44923 * excess keys are appended to the url search query after the `?`. 44924 * 44925 * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in 44926 * URL `/path/greet?salutation=Hello`. 44927 * 44928 * If the parameter value is prefixed with `@`, then the value for that parameter will be 44929 * extracted from the corresponding property on the `data` object (provided when calling a 44930 * "non-GET" action method). 44931 * For example, if the `defaultParam` object is `{someParam: '@someProp'}` then the value of 44932 * `someParam` will be `data.someProp`. 44933 * Note that the parameter will be ignored, when calling a "GET" action method (i.e. an action 44934 * method that does not accept a request body) 44935 * 44936 * @param {Object.<Object>=} actions Hash with declaration of custom actions that should extend 44937 * the default set of resource actions. The declaration should be created in the format of {@link 44938 * ng.$http#usage $http.config}: 44939 * 44940 * {action1: {method:?, params:?, isArray:?, headers:?, ...}, 44941 * action2: {method:?, params:?, isArray:?, headers:?, ...}, 44942 * ...} 44943 * 44944 * Where: 44945 * 44946 * - **`action`** â {string} â The name of action. This name becomes the name of the method on 44947 * your resource object. 44948 * - **`method`** â {string} â Case insensitive HTTP method (e.g. `GET`, `POST`, `PUT`, 44949 * `DELETE`, `JSONP`, etc). 44950 * - **`params`** â {Object=} â Optional set of pre-bound parameters for this action. If any of 44951 * the parameter value is a function, it will be called every time when a param value needs to 44952 * be obtained for a request (unless the param was overridden). The function will be passed the 44953 * current data value as an argument. 44954 * - **`url`** â {string} â action specific `url` override. The url templating is supported just 44955 * like for the resource-level urls. 44956 * - **`isArray`** â {boolean=} â If true then the returned object for this action is an array, 44957 * see `returns` section. 44958 * - **`transformRequest`** â 44959 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` â 44960 * transform function or an array of such functions. The transform function takes the http 44961 * request body and headers and returns its transformed (typically serialized) version. 44962 * By default, transformRequest will contain one function that checks if the request data is 44963 * an object and serializes it using `angular.toJson`. To prevent this behavior, set 44964 * `transformRequest` to an empty array: `transformRequest: []` 44965 * - **`transformResponse`** â 44966 * `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}` â 44967 * transform function or an array of such functions. The transform function takes the http 44968 * response body, headers and status and returns its transformed (typically deserialized) 44969 * version.
44970 * By default, transformResponse will contain one function that checks if the response looks 44971 * like a JSON string and deserializes it using `angular.fromJson`. To prevent this behavior, 44972 * set `transformResponse` to an empty array: `transformResponse: []` 44973 * - **`cache`** â `{boolean|Cache}` â If true, a default $http cache will be used to cache the 44974 * GET request, otherwise if a cache instance built with 44975 * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for 44976 * caching. 44977 * - **`timeout`** â `{number}` â timeout in milliseconds.<br /> 44978 * **Note:** In contrast to {@link ng.$http#usage $http.config}, {@link ng.$q promises} are 44979 * **not** supported in $resource, because the same value would be used for multiple requests. 44980 * If you are looking for a way to cancel requests, you should use the `cancellable` option. 44981 * - **`cancellable`** â `{boolean}` â if set to true, the request made by a "non-instance" call 44982 * will be cancelled (if not already completed) by calling `$cancelRequest()` on the call's 44983 * return value. Calling `$cancelRequest()` for a non-cancellable or an already 44984 * completed/cancelled request will have no effect.<br /> 44985 * - **`withCredentials`** - `{boolean}` - whether to set the `withCredentials` flag on the 44986 * XHR object. See 44987 * [requests with credentials](https://developer.mozilla.org/en/http_access_control#section_5) 44988 * for more information. 44989 * - **`responseType`** - `{string}` - see 44990 * [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType). 44991 * - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods - 44992 * `response` and `responseError`. Both `response` and `responseError` interceptors get called 44993 * with `http response` object. See {@link ng.$http $http interceptors}. 44994 * 44995 * @param {Object} options Hash with custom settings that should extend the 44996 * default `$resourceProvider` behavior. The supported options are: 44997 * 44998 * - **`stripTrailingSlashes`** â {boolean} â If true then the trailing 44999 * slashes from any calculated URL will be stripped. (Defaults to true.) 45000 * - **`cancellable`** â {boolean} â If true, the request made by a "non-instance" call will be 45001 * cancelled (if not already completed) by calling `$cancelRequest()` on the call's return value. 45002 * This can be overwritten per action. (Defaults to false.) 45003 * 45004 * @returns {Object} A resource "class" object with methods for the default set of resource actions 45005 * optionally extended with custom `actions`. The default set contains these actions: 45006 * ```js 45007 * { 'get': {method:'GET'}, 45008 * 'save': {method:'POST'}, 45009 * 'query': {method:'GET', isArray:true}, 45010 * 'remove': {method:'DELETE'}, 45011 * 'delete': {method:'DELETE'} }; 45012 * ``` 45013 * 45014 * Calling these methods invoke an {@link ng.$http} with the specified http method, 45015 * destination and parameters. When the data is returned from the server then the object is an 45016 * instance of the resource class. The actions `save`, `remove` and `delete` are available on it 45017 * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create, 45018 * read, update, delete) on server-side data like this: 45019 * ```js 45020 * var User = $resource('/user/:userId', {userId:'@id'}); 45021 * var user = User.get({userId:123}, function() { 45022 * user.abc = true; 45023 * user.$save(); 45024 * }); 45025 * ``` 45026 * 45027 * It is important to realize that invoking a $resource object method immediately returns an 45028 * empty reference (object or array depending on `isArray`). Once the data is returned from the 45029 * server the existing reference is populated with the actual data. This is a useful trick since 45030 * usually the resource is assigned to a model which is then rendered by the view. Having an empty 45031 * object results in no rendering, once the data arrives from the server then the object is 45032 * populated with the data and the view automatically re-renders itself showing the new data. This 45033 * means that in most cases one never has to write a callback function for the action methods. 45034 * 45035 * The action methods on the class object or instance object can be invoked with the following 45036 * parameters: 45037 * 45038 * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])` 45039 * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])` 45040 * - non-GET instance actions: `instance.$action([parameters], [success], [error])` 45041 * 45042 * 45043 * Success callback is called with (value (Object|Array), responseHeaders (Function), 45044 * status (number), statusText (string)) arguments, where the value is the populated resource 45045 * instance or collection object. The error callback is called with (httpResponse) argument. 45046 * 45047 * Class actions return empty instance (with additional properties below). 45048 * Instance actions return promise of the action. 45049 * 45050 * The Resource instances and collections have these additional properties: 45051 * 45052 * - `$promise`: the {@link ng.$q promise} of the original server interaction that created this 45053 * instance or collection. 45054 * 45055 * On success, the promise is resolved with the same resource instance or collection object, 45056 * updated with data from server. This makes it easy to use in 45057 * {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view 45058 * rendering until the resource(s) are loaded. 45059 * 45060 * On failure, the promise is rejected with the {@link ng.$http http response} object, without 45061 * the `resource` property. 45062 * 45063 * If an interceptor object was provided, the promise will instead be resolved with the value 45064 * returned by the interceptor. 45065 * 45066 * - `$resolved`: `true` after first server interaction is completed (either with success or 45067 * rejection), `false` before that. Knowing if the Resource has been resolved
45067is useful in 45068 * data-binding. 45069 * 45070 * The Resource instances and collections have these additional methods: 45071 * 45072 * - `$cancelRequest`: If there is a cancellable, pending request related to the instance or 45073 * collection, calling this method will abort the request. 45074 * 45075 * The Resource instances have these additional methods: 45076 * 45077 * - `toJSON`: It returns a simple object without any of the extra properties added as part of 45078 * the Resource API. This object can be serialized through {@link angular.toJson} safely 45079 * without attaching Angular-specific fields. Notice that `JSON.stringify` (and 45080 * `angular.toJson`) automatically use this method when serializing a Resource instance 45081 * (see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#toJSON()_behavior)). 45082 * 45083 * @example 45084 * 45085 * # Credit card resource 45086 * 45087 * ```js 45088 // Define CreditCard class 45089 var CreditCard = $resource('/user/:userId/card/:cardId', 45090 {userId:123, cardId:'@id'}, { 45091 charge: {method:'POST', params:{charge:true}} 45092 }); 45093 45094 // We can retrieve a collection from the server 45095 var cards = CreditCard.query(function() { 45096 // GET: /user/123/card 45097 // server returns: [ {id:456, number:'1234', name:'Smith'} ]; 45098 45099 var card = cards[0]; 45100 // each item is an instance of CreditCard 45101 expect(card instanceof CreditCard).toEqual(true); 45102 card.name = "J. Smith"; 45103 // non GET methods are mapped onto the instances 45104 card.$save(); 45105 // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'} 45106 // server returns: {id:456, number:'1234', name: 'J. Smith'}; 45107 45108 // our custom method is mapped as well. 45109 card.$charge({amount:9.99}); 45110 // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'} 45111 }); 45112 45113 // we can create an instance as well 45114 var newCard = new CreditCard({number:'0123'}); 45115 newCard.name = "Mike Smith"; 45116 newCard.$save(); 45117 // POST: /user/123/card {number:'0123', name:'Mike Smith'} 45118 // server returns: {id:789, number:'0123', name: 'Mike Smith'}; 45119 expect(newCard.id).toEqual(789); 45120 * ``` 45121 * 45122 * The object returned from this function execution is a resource "class" which has "static" method 45123 * for each action in the definition. 45124 * 45125 * Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and 45126 * `headers`. 45127 * 45128 * @example 45129 * 45130 * # User resource 45131 * 45132 * When the data is returned from the server then the object is an instance of the resource type and 45133 * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD 45134 * operations (create, read, update, delete) on server-side data. 45135 45136 ```js 45137 var User = $resource('/user/:userId', {userId:'@id'}); 45138 User.get({userId:123}, function(user) { 45139 user.abc = true; 45140 user.$save(); 45141 }); 45142 ``` 45143 * 45144 * It's worth noting that the success callback for `get`, `query` and other methods gets passed 45145 * in the response that came from the server as well as $http header getter function, so one 45146 * could rewrite the above example and get access to http headers as: 45147 * 45148 ```js 45149 var User = $resource('/user/:userId', {userId:'@id'}); 45150 User.get({userId:123}, function(user, getResponseHeaders){ 45151 user.abc = true; 45152 user.$save(function(user, putResponseHeaders) { 45153 //user => saved user object 45154 //putResponseHeaders => $http header getter 45155 }); 45156 }); 45157 ``` 45158 * 45159 * You can also access the raw `$http` promise via the `$promise` property on the object returned 45160 * 45161 ``` 45162 var User = $resource('/user/:userId', {userId:'@id'}); 45163 User.get({userId:123}) 45164 .$promise.then(function(user) { 45165 $scope.user = user; 45166 }); 45167 ``` 45168 * 45169 * @example 45170 * 45171 * # Creating a custom 'PUT' request 45172 * 45173 * In this example we create a custom method on our resource to make a PUT request 45174 * ```js 45175 * var app = angular.module('app', ['ngResource', 'ngRoute']); 45176 * 45177 * // Some APIs expect a PUT request in the format URL/object/ID 45178 * // Here we are creating an 'update' method 45179 * app.factory('Notes', ['$resource', function($resource) { 45180 * return $resource('/notes/:id', null, 45181 * { 45182 * 'update': { method:'PUT' } 45183 * }); 45184 * }]); 45185 * 45186 * // In our controller we get the ID from the URL using ngRoute and $routeParam
45186s 45187 * // We pass in $routeParams and our Notes factory along with $scope 45188 * app.controller('NotesCtrl', ['$scope', '$routeParams', 'Notes', 45189 function($scope, $routeParams, Notes) { 45190 * // First get a note object from the factory 45191 * var note = Notes.get({ id:$routeParams.id }); 45192 * $id = note.id; 45193 * 45194 * // Now call update passing in the ID first then the object you are updating 45195 * Notes.update({ id:$id }, note); 45196 * 45197 * // This will PUT /notes/ID with the note object in the request payload 45198 * }]); 45199 * ``` 45200 * 45201 * @example 45202 * 45203 * # Cancelling requests 45204 * 45205 * If an action's configuration specifies that it is cancellable, you can cancel the request related 45206 * to an instance or collection (as long as it is a result of a "non-instance" call): 45207 * 45208 ```js 45209 // ...defining the `Hotel` resource... 45210 var Hotel = $resource('/api/hotel/:id', {id: '@id'}, { 45211 // Let's make the `query()` method cancellable 45212 query: {method: 'get', isArray: true, cancellable: true} 45213 }); 45214 45215 // ...somewhere in the PlanVacationController... 45216 ... 45217 this.onDestinationChanged = function onDestinationChanged(destination) { 45218 // We don't care about any pending request for hotels 45219 // in a different destination any more 45220 this.availableHotels.$cancelRequest(); 45221 45222 // Let's query for hotels in '<destination>' 45223 // (calls: /api/hotel?location=<destination>) 45224 this.availableHotels = Hotel.query({location: destination}); 45225 }; 45226 ``` 45227 * 45228 */ 45229angular.module('ngResource', ['ng']). 45230 provider('$resource', function ResourceProvider() { 45231 var PROTOCOL_AND_DOMAIN_REGEX = /^https?:\/\/[^/]*/; 45232 45233 var provider = this; 45234 45235 /** 45236 * @ngdoc property 45237 * @name $resourceProvider#defaults 45238 * @description 45239 * Object containing default options used when creating `$resource` instances. 45240 * 45241 * The default values satisfy a wide range of usecases, but you may choose to overwrite any of 45242 * them to further customize your instances. The available properties are: 45243 * 45244 * - **stripTrailingSlashes** â `{boolean}` â If true, then the trailing slashes from any 45245 * calculated URL will be stripped.<br /> 45246 * (Defaults to true.) 45247 * - **cancellable** â `{boolean}` â If true, the request made by a "non-instance" call will be 45248 * cancelled (if not already completed) by calling `$cancelRequest()` on the call's return 45249 * value. For more details, see {@link ngResource.$resource}. This can be overwritten per 45250 * resource class or action.<br /> 45251 * (Defaults to false.) 45252 * - **actions** - `{Object.<Object>}` - A hash with default actions declarations. Actions are 45253 * high-level methods corresponding to RESTful actions/methods on resources. An action may 45254 * specify what HTTP method to use, what URL to hit, if the return value will be a single 45255 * object or a collection (array) of objects etc. For more details, see 45256 * {@link ngResource.$resource}. The actions can also be enhanced or overwritten per resource 45257 * class.<br /> 45258 * The default actions are: 45259 * ```js 45260 * { 45261 * get: {method: 'GET'}, 45262 * save: {method: 'POST'}, 45263 * query: {method: 'GET', isArray: true}, 45264 * remove: {method: 'DELETE'}, 45265 * delete: {method: 'DELETE'} 45266 * } 45267 * ``` 45268 * 45269 * #### Example 45270 * 45271 * For example, you can specify a new `update` action that uses the `PUT` HTTP verb: 45272 * 45273 * ```js 45274 * angular. 45275 * module('myApp'). 45276 * config(['$resourceProvider', function ($resourceProvider) { 45277 * $resourceProvider.defaults.actions.update = { 45278 * method: 'PUT' 45279 * }; 45280 * }); 45281 * ``` 45282 * 45283 * Or you can even overwrite the whole `actions` list and specify your own: 45284 * 45285 * ```js 45286 * angular. 45287 * module('myApp'). 45288 * config(['$resourceProvider', function ($resourceProvider) { 45289 * $resourceProvider.defaults.actions = { 45290 * create: {method: 'POST'}, 45291 * get: {method: 'GET'}, 45292 * getAll: {method: 'GET', isArray:true}, 45293 * update: {method: 'PUT'}, 45294 * delete: {method: 'DELETE'} 45295 * }; 45296 * }); 45297 * ``` 45298 * 45299 */ 45300 this.defaults = { 45301 // Strip slashes by default
45302 stripTrailingSlashes: true, 45303 45304 // Make non-instance requests cancellable (via `$cancelRequest()`) 45305 cancellable: false, 45306 45307 // Default actions configuration 45308 actions: { 45309 'get': {method: 'GET'}, 45310 'save': {method: 'POST'}, 45311 'query': {method: 'GET', isArray: true}, 45312 'remove': {method: 'DELETE'}, 45313 'delete': {method: 'DELETE'} 45314 } 45315 }; 45316 45317 this.$get = ['$http', '$log', '$q', '$timeout', function($http, $log, $q, $timeout) { 45318 45319 var noop = angular.noop, 45320 forEach = angular.forEach, 45321 extend = angular.extend, 45322 copy = angular.copy, 45323 isArray = angular.isArray, 45324 isDefined = angular.isDefined, 45325 isFunction = angular.isFunction, 45326 isNumber = angular.isNumber; 45327 45328 /** 45329 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow 45330 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set 45331 * (pchar) allowed in path segments: 45332 * segment = *pchar 45333 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 45334 * pct-encoded = "%" HEXDIG HEXDIG 45335 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 45336 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 45337 * / "*" / "+" / "," / ";" / "=" 45338 */ 45339 function encodeUriSegment(val) { 45340 return encodeUriQuery(val, true). 45341 replace(/%26/gi, '&'). 45342 replace(/%3D/gi, '='). 45343 replace(/%2B/gi, '+'); 45344 } 45345 45346 45347 /** 45348 * This method is intended for encoding *key* or *value* parts of query component. We need a 45349 * custom method because encodeURIComponent is too aggressive and encodes stuff that doesn't 45350 * have to be encoded per http://tools.ietf.org/html/rfc3986: 45351 * query = *( pchar / "/" / "?" ) 45352 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 45353 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 45354 * pct-encoded = "%" HEXDIG HEXDIG 45355 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 45356 * / "*" / "+" / "," / ";" / "=" 45357 */ 45358 function encodeUriQuery(val, pctEncodeSpaces) { 45359 return encodeURIComponent(val). 45360 replace(/%40/gi, '@'). 45361 replace(/%3A/gi, ':'). 45362 replace(/%24/g, '$'). 45363 replace(/%2C/gi, ','). 45364 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); 45365 } 45366 45367 function Route(template, defaults) { 45368 this.template = template; 45369 this.defaults = extend({}, provider.defaults, defaults); 45370 this.urlParams = {}; 45371 } 45372 45373 Route.prototype = { 45374 setUrlParams: function(config, params, actionUrl) { 45375 var self = this, 45376 url = actionUrl || self.template, 45377 val, 45378 encodedVal, 45379 protocolAndDomain = ''; 45380 45381 var urlParams = self.urlParams = {}; 45382 forEach(url.split(/\W/), function(param) { 45383 if (param === 'hasOwnProperty') { 45384 throw $resourceMinErr('badname', 'hasOwnProperty is not a valid parameter name.'); 45385 } 45386 if (!(new RegExp('^\\d+$').test(param)) && param && 45387 (new RegExp('(^|[^\\\\]):' + param + '(\\W|$)').test(url))) { 45388 urlParams[param] = { 45389 isQueryParamValue: (new RegExp('\\?.*=:' + param + '(?:\\W|$)')).test(url) 45390 }; 45391 } 45392 }); 45393 url = url.replace(/\\:/g, ':'); 45394 url = url.replace(PROTOCOL_AND_DOMAIN_REGEX, function(match) { 45395 protocolAndDomain = match; 45396 return ''; 45397 }); 45398 45399 params = params || {}; 45400 forEach(self.urlParams, function(paramInfo, urlParam) { 45401 val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam]; 45402 if (isDefined(val) && val !== null) { 45403 if (paramInfo.isQueryParamValue) { 45404 encodedVal = encodeUriQuery(val, true); 45405 } else { 45406 encodedVal = encodeUriSegment(val); 45407 } 45408 url = url.replace(new RegExp(':' + urlParam + '(\\W|$)', 'g'), function(match, p1) { 45409 return encodedVal + p1; 45410 }); 45411 } else { 45412 url = url.replace(new RegExp('(/?):' + urlParam + '(\\W|$)', 'g'), function(match, 45413 leadingSlashes, tail) { 45414 if (tail.charAt(0) === '/') { 45415 return tail; 45416 } else { 45417 return leadingSlashes + tail; 45418 } 45419 }); 45420 } 45421 }); 45422 45423 // strip trailing slashes and set the url (unless this behavior is specifically disabled) 45424 if (self.defaults.stripTrailingSlashes) { 45425 url = url.replace(/\/+$/, '') || '/'; 45426 } 45427 45428 // then replace collapse `/.` if found in the last URL path segment before the query 45429 // E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x` 45430 url = url.replace(/\/\.(?=\w+($|\?))/, '.'); 45431 // replace escaped `/\.` with `/.` 45432 config.url = protocolAndDomain + url.replace(/\/\\\./, '/.'); 45433 45434 45435 // set params - delegate param encoding to $http
45436 forEach(params, function(value, key) { 45437 if (!self.urlParams[key]) { 45438 config.params = config.params || {}; 45439 config.params[key] = value; 45440 } 45441 }); 45442 } 45443 }; 45444 45445 45446 function resourceFactory(url, paramDefaults, actions, options) { 45447 var route = new Route(url, options); 45448 45449 actions = extend({}, provider.defaults.actions, actions); 45450 45451 function extractParams(data, actionParams) { 45452 var ids = {}; 45453 actionParams = extend({}, paramDefaults, actionParams); 45454 forEach(actionParams, function(value, key) { 45455 if (isFunction(value)) { value = value(data); } 45456 ids[key] = value && value.charAt && value.charAt(0) === '@' ? 45457 lookupDottedPath(data, value.substr(1)) : value; 45458 }); 45459 return ids; 45460 } 45461 45462 function defaultResponseInterceptor(response) { 45463 return response.resource; 45464 } 45465 45466 function Resource(value) { 45467 shallowClearAndCopy(value || {}, this); 45468 } 45469 45470 Resource.prototype.toJSON = function() { 45471 var data = extend({}, this); 45472 delete data.$promise; 45473 delete data.$resolved; 45474 delete data.$cancelRequest; 45475 return data; 45476 }; 45477 45478 forEach(actions, function(action, name) { 45479 var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method); 45480 var numericTimeout = action.timeout; 45481 var cancellable = isDefined(action.cancellable) ? 45482 action.cancellable : route.defaults.cancellable; 45483 45484 if (numericTimeout && !isNumber(numericTimeout)) { 45485 $log.debug('ngResource:\n' + 45486 ' Only numeric values are allowed as `timeout`.\n' + 45487 ' Promises are not supported in $resource, because the same value would ' + 45488 'be used for multiple requests. If you are looking for a way to cancel ' + 45489 'requests, you should use the `cancellable` option.'); 45490 delete action.timeout; 45491 numericTimeout = null; 45492 } 45493 45494 Resource[name] = function(a1, a2, a3, a4) { 45495 var params = {}, data, success, error; 45496 45497 switch (arguments.length) { 45498 case 4: 45499 error = a4; 45500 success = a3; 45501 // falls through 45502 case 3: 45503 case 2: 45504 if (isFunction(a2)) { 45505 if (isFunction(a1)) { 45506 success = a1; 45507 error = a2; 45508 break; 45509 } 45510 45511 success = a2; 45512 error = a3; 45513 // falls through 45514 } else { 45515 params = a1; 45516 data = a2; 45517 success = a3; 45518 break; 45519 } 45520 // falls through 45521 case 1: 45522 if (isFunction(a1)) success = a1; 45523 else if (hasBody) data = a1; 45524 else params = a1; 45525 break; 45526 case 0: break; 45527 default: 45528 throw $resourceMinErr('badargs', 45529 'Expected up to 4 arguments [params, data, success, error], got {0} arguments', 45530 arguments.length); 45531 } 45532 45533 var isInstanceCall = this instanceof Resource; 45534 var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data)); 45535 var httpConfig = {}; 45536 var responseInterceptor = action.interceptor && action.interceptor.response || 45537 defaultResponseInterceptor; 45538 var responseErrorInterceptor = action.interceptor && action.interceptor.responseError || 45539 undefined; 45540 var timeoutDeferred; 45541 var numericTimeoutPromise; 45542 45543 forEach(action, function(value, key) { 45544 switch (key) { 45545 default: 45546 httpConfig[key] = copy(value); 45547 break; 45548 case 'params': 45549 case 'isArray': 45550 case 'interceptor': 45551 case 'cancellable': 45552 break; 45553 } 45554 }); 45555 45556 if (!isInstanceCall && cancellable) { 45557 timeoutDeferred = $q.defer(); 45558 httpConfig.timeout = timeoutDeferred.promise; 45559 45560 if (numericTimeout) { 45561 numericTimeoutPromise = $timeout(timeoutDeferred.resolve, numericTimeout); 45562 } 45563 } 45564 45565 if (hasBody) httpConfig.data = data; 45566 route.setUrlParams(httpConfig, 45567 extend({}, extractParams(data, action.params || {}), params), 45568 action.url); 45569 45570 var promise = $http(httpConfig).then(function(response) { 45571 var data = response.data; 45572 45573 if (data) { 45574 // Need to convert action.isArray to boolean in case it is undefined 45575 if (isArray(data) !== (!!action.isArray)) { 45576 throw $resourceMinErr('badcfg', 45577 'Error in resource configuration for action `{0}`. Expected response to ' + 45578 'contain an {1} but got an {2} (Request: {3} {4})', name, action.isArray ? 'array' : 'object', 45579 isArray(data) ? 'array' : 'object', httpConfig.method, httpConfig.url); 45580 } 45581 if (action.isArray) { 45582 value.length = 0;
45583 forEach(data, function(item) { 45584 if (typeof item === 'object') { 45585 value.push(new Resource(item)); 45586 } else { 45587 // Valid JSON values may be string literals, and these should not be converted 45588 // into objects. These items will not have access to the Resource prototype 45589 // methods, but unfortunately there 45590 value.push(item); 45591 } 45592 }); 45593 } else { 45594 var promise = value.$promise; // Save the promise 45595 shallowClearAndCopy(data, value); 45596 value.$promise = promise; // Restore the promise 45597 } 45598 } 45599 response.resource = value; 45600 45601 return response; 45602 }, function(response) { 45603 (error || noop)(response); 45604 return $q.reject(response); 45605 }); 45606 45607 promise['finally'](function() { 45608 value.$resolved = true; 45609 if (!isInstanceCall && cancellable) { 45610 value.$cancelRequest = noop; 45611 $timeout.cancel(numericTimeoutPromise); 45612 timeoutDeferred = numericTimeoutPromise = httpConfig.timeout = null; 45613 } 45614 }); 45615 45616 promise = promise.then( 45617 function(response) { 45618 var value = responseInterceptor(response); 45619 (success || noop)(value, response.headers, response.status, response.statusText); 45620 return value; 45621 }, 45622 responseErrorInterceptor); 45623 45624 if (!isInstanceCall) { 45625 // we are creating instance / collection 45626 // - set the initial promise 45627 // - return the instance / collection 45628 value.$promise = promise; 45629 value.$resolved = false; 45630 if (cancellable) value.$cancelRequest = timeoutDeferred.resolve; 45631 45632 return value; 45633 } 45634 45635 // instance call 45636 return promise; 45637 }; 45638 45639 45640 Resource.prototype['$' + name] = function(params, success, error) { 45641 if (isFunction(params)) { 45642 error = success; success = params; params = {}; 45643 } 45644 var result = Resource[name].call(this, params, this, success, error); 45645 return result.$promise || result; 45646 }; 45647 }); 45648 45649 Resource.bind = function(additionalParamDefaults) { 45650 var extendedParamDefaults = extend({}, paramDefaults, additionalParamDefaults); 45651 return resourceFactory(url, extendedParamDefaults, actions, options); 45652 }; 45653 45654 return Resource; 45655 } 45656 45657 return resourceFactory; 45658 }]; 45659 }); 45660 45661 45662})(window, window.angular); 45663 45664/** 45665 * @license AngularJS v1.5.11 45666 * (c) 2010-2017 Google, Inc. http://angularjs.org 45667 * License: MIT 45668 */ 45669(function(window, angular) {'use strict'; 45670 45671var forEach; 45672var isArray; 45673var isString; 45674var jqLite; 45675 45676/** 45677 * @ngdoc module 45678 * @name ngMessages 45679 * @description 45680 * 45681 * The `ngMessages` module provides enhanced support for displaying messages within templates 45682 * (typically within forms or when rendering message objects that return key/value data). 45683 * Instead of relying on JavaScript code and/or complex ng-if statements within your form template to 45684 * show and hide error messages specific to the state of an input field, the `ngMessages` and 45685 * `ngMessage` directives are designed to handle the complexity, inheritance and priority 45686 * sequencing based on the order of how the messages are defined in the template. 45687 * 45688 * Currently, the ngMessages module only contains the code for the `ngMessages`, `ngMessagesInclude` 45689 * `ngMessage` and `ngMessageExp` directives. 45690 * 45691 * # Usage 45692 * The `ngMessages` directive allows keys in a key/value collection to be associated with a child element 45693 * (or 'message') that will show or hide based on the truthiness of that key's value in the collection. A common use 45694 * case for `ngMessages` is to display error messages for inputs using the `$error` object exposed by the 45695 * {@link ngModel ngModel} directive. 45696 * 45697 * The child elements of the `ngMessages` directive are matched to the collection keys by a `ngMessage` or 45698 * `ngMessageExp` directive. The value of these attributes must match a key in the collection that is provided by 45699 * the `ngMessages` directive. 45700 * 45701 * Consider the following example, which illustrates a typical use case of `ngMessages`. Within the form `myForm` we 45702 * have a text input named `myField` which is bound to the scope variable `field` using the {@link ngModel ngModel} 45703 * directive. 45704 * 45705 * The `myField` field is a required input of type `email` with a maximum length of 15 characters. 45706 * 45707 * ```html 45708 * <form name="myForm"> 45709 * <label> 45710 * Enter text: 45711 * <input type="email" ng-model="field" name="myField" required maxlength="15" /> 45712 * </label> 45713 * <div ng-messages="myForm.myField.$error" role="alert"> 45714 * <div ng-message="required">Please enter a value for this field.</div> 45715 * <div ng-message="email">This field must be a valid email address.</div> 45716 * <div ng-message="maxlength">This field can be at most 15 characters long.</div> 45717 * </div> 45718 * </form> 45719 * ``` 45720 * 45721 * In order to show error messages corresponding to `myField` we first create an element with an `ngMessages` attribute 45722 * set to the `$error` object owned by the `myField` input in our `myForm` form. 45723 * 45724 * Within this element we then create separate elements for each of the possible errors that `myField` could have. 45725 * The `ngMessage` attribute is used to declare which element(s) will appear for which error - for example, 45726 * setting `ng-message="required"` specifies that this particular element should be displayed when there 45727 * is no value present for the required field `myField` (because the key `required` will be `true` in the object 45728 * `myForm.myField.$error`). 45729 * 45730 * ### Message order 45731 * 45732 * By default, `ngMessages` will only display one message for a particular key/value collection at any time. If more 45733 * than one message (or error) key is currently true, then which message is shown is determined by the order of messages 45734 * in the HTML template code (messages declared first are prioritised). This mechanism means the developer does not have 45735 * to prioritize messages using custom JavaScript code. 45736 * 45737 * Given the following error object for our example (which informs us that the field `myField` currently has both the 45738 * `required` and `email` errors): 45739 * 45740 * ```javascript 45741 * <!-- keep in mind that ngModel automatically sets these error flags --> 45742 * myField.$error = { required : true, email: true, maxlength: false }; 45743 * ``` 45744 * The `required` message will be displayed to the user since it appears before the `email` message in the DOM. 45745 * Once the user types a single character, the `required` message will disappear (since the field now has a value) 45746 * but the `email` message will be visible because it is still applicable. 45747 * 45748 * ### Displaying multiple messages at the same time 45749 * 45750 * While `ngMessages` will by default only display one error element at a time, the `ng-messages-multiple` attribute can 45751 * be applied to the `ngMessages` container element to cause it to display all applicable error messages at once: 45752 * 45753 * ```html 45754 * <!-- attribute-style usage --> 45755 * <div ng-messages="myForm.myField.$error" ng-messages-multiple>...</div> 45756 * 45757 * <!-- element-style usage --> 45758 * <ng-messages for="myForm.myField.$error" multiple>...</ng-messages> 45759 * ``` 45760 * 45761 * ## Reusing and Overriding Messages 45762 * In addition to prioritization, ngMessages also allows for including messages from a remote or an inline 45763 * template. This allows for generic collection of messages to be reused across multiple parts of an 45764 * application. 45765 * 45766 * ```html 45767 * <script type="text/ng-template" id="error-messages"> 45768 * <div ng-message="required">This field is required</div> 45769 * <div ng-message="minlength">This field is too short</div> 45770 * </script> 45771 * 45772 * <div ng-messages="myForm.myField.$error" role="alert"> 45773 * <div ng-messages-include="error-messages"></div> 45774 * </div> 45775 * ``` 45776 * 45777 * However, including generic messages may not be useful enough to match all input fields, therefore, 45778 * `ngMessages` provides the ability to override messages defined in the remote template by redefining 45779 * them within the directive container. 45780 * 45781 * ```html 45782 * <!-- a generic template of error messages known as "my-custom-messages" --> 45783 * <script type="text/ng-template" id="my-custom-messages"> 45784 * <div ng-message="required">This field is required</div> 45785 * <div ng-message="minlength">This field is too short</div> 45786 * </script> 45787 * 45788 * <form name="myForm"> 45789 * <label> 45790 * Email address 45791 * <input type="email" 45792 * id="email" 45793 * name="myEmail" 45794 * ng-model="email" 45795 * minlength="5" 45796 * required /> 45797 * </label> 45798 * <!-- any ng-message elements that appear BEFORE the ng-messages-include will 45799 * override the messages present in the ng-messages-include template --> 45800 * <div ng-messages="myForm.myEmail.$error" role="alert"> 45801 * <!-- this required message has overridden the template message --> 45802 * <div ng-message="required">You did not enter your email address</div> 45803 * 45804 * <!-- this is a brand new message and will appear last in the prioritization --> 45805 * <div ng-message="email">
45805Your email address is invalid</div> 45806 * 45807 * <!-- and here are the generic error messages --> 45808 * <div ng-messages-include="my-custom-messages"></div> 45809 * </div> 45810 * </form> 45811 * ``` 45812 * 45813 * In the example HTML code above the message that is set on required will override the corresponding 45814 * required message defined within the remote template. Therefore, with particular input fields (such 45815 * email addresses, date fields, autocomplete inputs, etc...), specialized error messages can be applied 45816 * while more generic messages can be used to handle other, more general input errors. 45817 * 45818 * ## Dynamic Messaging 45819 * ngMessages also supports using expressions to dynamically change key values. Using arrays and 45820 * repeaters to list messages is also supported. This means that the code below will be able to 45821 * fully adapt itself and display the appropriate message when any of the expression data changes: 45822 * 45823 * ```html 45824 * <form name="myForm"> 45825 * <label> 45826 * Email address 45827 * <input type="email" 45828 * name="myEmail" 45829 * ng-model="email" 45830 * minlength="5" 45831 * required /> 45832 * </label> 45833 * <div ng-messages="myForm.myEmail.$error" role="alert"> 45834 * <div ng-message="required">You did not enter your email address</div> 45835 * <div ng-repeat="errorMessage in errorMessages"> 45836 * <!-- use ng-message-exp for a message whose key is given by an expression --> 45837 * <div ng-message-exp="errorMessage.type">{{ errorMessage.text }}</div> 45838 * </div> 45839 * </div> 45840 * </form> 45841 * ``` 45842 * 45843 * The `errorMessage.type` expression can be a string value or it can be an array so 45844 * that multiple errors can be associated with a single error message: 45845 * 45846 * ```html 45847 * <label> 45848 * Email address 45849 * <input type="email" 45850 * ng-model="data.email" 45851 * name="myEmail" 45852 * ng-minlength="5" 45853 * ng-maxlength="100" 45854 * required /> 45855 * </label> 45856 * <div ng-messages="myForm.myEmail.$error" role="alert"> 45857 * <div ng-message-exp="'required'">You did not enter your email address</div> 45858 * <div ng-message-exp="['minlength', 'maxlength']"> 45859 * Your email must be between 5 and 100 characters long 45860 * </div> 45861 * </div> 45862 * ``` 45863 * 45864 * Feel free to use other structural directives such as ng-if and ng-switch to further control 45865 * what messages are active and when. Be careful, if you place ng-message on the same element 45866 * as these structural directives, Angular may not be able to determine if a message is active 45867 * or not. Therefore it is best to place the ng-message on a child element of the structural 45868 * directive. 45869 * 45870 * ```html 45871 * <div ng-messages="myForm.myEmail.$error" role="alert"> 45872 * <div ng-if="showRequiredError"> 45873 * <div ng-message="required">Please enter something</div> 45874 * </div> 45875 * </div> 45876 * ``` 45877 * 45878 * ## Animations 45879 * If the `ngAnimate` module is active within the application then the `ngMessages`, `ngMessage` and 45880 * `ngMessageExp` directives will trigger animations whenever any messages are added and removed from 45881 * the DOM by the `ngMessages` directive. 45882 * 45883 * Whenever the `ngMessages` directive contains one or more visible messages then the `.ng-active` CSS 45884 * class will be added to the element. The `.ng-inactive` CSS class will be applied when there are no 45885 * messages present. Therefore, CSS transitions and keyframes as well as JavaScript animations can 45886 * hook into the animations whenever these classes are added/removed. 45887 * 45888 * Let's say that our HTML code for our messages container looks like so: 45889 * 45890 * ```html 45891 * <div ng-messages="myMessages" class="my-messages" role="alert"> 45892 * <div ng-message="alert" class="some-message">...</div> 45893 * <div ng-message="fail" class="some-message">...</div> 45894 * </div> 45895 * ``` 45896 * 45897 * Then the CSS animation code for the message container looks like so: 45898 * 45899 * ```css 45900 * .my-messages { 45901 * transition:1s linear all; 45902 * } 45903 * .my-messages.ng-active { 45904 * // messages are visible 45905 * } 45906 * .my-messages.ng-inactive { 45907 * // messages are hidden 45908 * } 45909 * ``` 45910 * 45911 * Whenever an inner message is attached (becomes visible) or removed (becomes hidden) then the enter 45912 * and leave animation is triggered for each particular element bound to the `ngMessage` directive. 45913 * 45914 * Therefore, the CSS code for the inner messages looks like so: 45915 * 45916 * ```css 45917 * .some-message { 45918 * transition:1s linear all; 45919 * } 45920 * 45921 * .some-message.ng-enter {} 45922 * .some-message.ng-enter.ng-enter-active {} 45923 * 45924 * .some-message.ng-leave {} 45925 * .some-message.ng-leave.ng-leave-active {} 45926 * ``` 45927 * 45928 * {@link ngAnimate Click here} to learn how to use JavaScript animations or to learn more about ngAnimate. 45929 */ 45930angular.module('ngMessages', [], function initAngularHelpers() { 45931 // Access helpers from angular core. 45932 // Do it inside a `config` block to ensure `window.angular` is available. 45933 forEach = angular.forEach; 45934 isArray = angular.isArray; 45935 isString = angular.isString; 45936 jqLite = angular.element; 45937}) 45938 45939 /** 45940 * @ngdoc directive 45941 * @module ngMessages 45942 * @name ngMessages 45943 * @restrict AE 45944 * 45945 * @description 45946 * `ngMessages` is a directive that is designed to show and hide messages based on the state 45947 * of a key/value object that it listens on. The directive itself complements error message 45948 * reporting with the `ngModel` $error object (which stores a key/value state of validation errors). 45949 * 45950 * `ngMessages` manages the state of internal messages within its container element. The internal 45951 * messages use the `ngMessage` directive and will be inserted/removed from the page depending 45952 * on if they're present within the key/value object. By default, only one message will be displayed 45953 * at a time and this depends on the prioritization of the messages within the template. (This can 45954 * be changed by using the `ng-messages-multiple` or `multiple` attribute on the directive container.) 45955 * 45956 * A remote template can also be used to promote message reusability and messages can also be 45957 * overridden. 45958 * 45959 * {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`. 45960 * 45961 * @usage 45962 * ```html 45963 * <!-- using attribute directives --> 45964 * <ANY ng-messages="expression" role="alert"> 45965 * <ANY ng-message="stringValue">...</ANY> 45966 * <ANY ng-message="stringValue1, stringValue2, ...">...</ANY> 45967 * <ANY ng-message-exp="expressionValue">...</ANY> 45968 * </ANY> 45969 * 45970 * <!-- or by using element directives --> 45971 * <ng-messages for="expression" role="alert"> 45972 * <ng-message when="stringValue">...</ng-message> 45973 * <ng-message when="stringValue1, stringValue2, ...">...</ng-message> 45974 * <ng-message when-exp="expressionValue">...</ng-message> 45975 * </ng-messages> 45976 * ``` 45977 * 45978 * @param {string} ngMessages an angular expression evaluating to a key/value object 45979 * (this is typically the $error object on an ngModel instance). 45980 * @param {string=} ngMessagesMultiple|multiple when set, all messages will be displayed with true 45981 * 45982 * @example 45983 * <example name="ngMessages-directive" module="ngMessagesExample" 45984 * deps="angular-messages.js" 45985 * animations="true" fixBase="true"> 45986 * <file name="index.html"> 45987 * <form name="myForm"> 45988 * <label> 45989 * Enter your name: 45990 * <input type="text" 45991 * name="myName" 45992 * ng-model="name" 45993 * ng-minlength="5" 45994 * ng-maxlength="20" 45995 * required /> 45996 * </label> 45997 * <pre>myForm.myName.$error = {{ myForm.myName.$error | json }}</pre> 45998 * 45999 * <div ng-messages="myForm.myName.$error" style="color:maroon" role="alert"> 46000 * <div ng-message="required">You did not enter a field</div> 46001 * <div ng-message="minlength">Your field is too short</div> 46002 * <div ng-message="maxlength">Your field is too long</div> 46003 * </div> 46004 * </form> 46005 * </file> 46006 * <file name="script.js"> 46007 * angular.module('ngMessagesExample', ['ngMessages']); 46008 * </file> 46009 * </example> 46010 */ 46011 .directive('ngMessages', ['$animate', function($animate) { 46012 var ACTIVE_CLASS = 'ng-active'; 46013 var INACTIVE_CLASS = 'ng-inactive'; 46014 46015 return { 46016 require: 'ngMessages', 46017 restrict: 'AE', 46018 controller: ['$element', '$scope', '$attrs', function NgMessagesCtrl($element, $scope, $attrs) { 46019 var ctrl = this; 46020 var latestKey = 0; 46021 var nextAttachId = 0; 46022 46023 this.getAttachId = function getAttachId() { return nextAttachId++; }; 46024 46025 var messages = this.messages = {}; 46026 var renderLater, cachedCollection; 46027 46028 this.render = function(collection) { 46029 collection = collection || {}; 46030 46031 renderLater = false;
46032 cachedCollection = collection; 46033 46034 // this is true if the attribute is empty or if the attribute value is truthy 46035 var multiple = isAttrTruthy($scope, $attrs.ngMessagesMultiple) || 46036 isAttrTruthy($scope, $attrs.multiple); 46037 46038 var unmatchedMessages = []; 46039 var matchedKeys = {}; 46040 var messageItem = ctrl.head; 46041 var messageFound = false; 46042 var totalMessages = 0; 46043 46044 // we use != instead of !== to allow for both undefined and null values 46045 while (messageItem != null) { 46046 totalMessages++; 46047 var messageCtrl = messageItem.message; 46048 46049 var messageUsed = false; 46050 if (!messageFound) { 46051 forEach(collection, function(value, key) { 46052 if (!messageUsed && truthy(value) && messageCtrl.test(key)) { 46053 // this is to prevent the same error name from showing up twice 46054 if (matchedKeys[key]) return; 46055 matchedKeys[key] = true; 46056 46057 messageUsed = true; 46058 messageCtrl.attach(); 46059 } 46060 }); 46061 } 46062 46063 if (messageUsed) { 46064 // unless we want to display multiple messages then we should 46065 // set a flag here to avoid displaying the next message in the list 46066 messageFound = !multiple; 46067 } else { 46068 unmatchedMessages.push(messageCtrl); 46069 } 46070 46071 messageItem = messageItem.next; 46072 } 46073 46074 forEach(unmatchedMessages, function(messageCtrl) { 46075 messageCtrl.detach(); 46076 }); 46077 46078 if (unmatchedMessages.length !== totalMessages) { 46079 $animate.setClass($element, ACTIVE_CLASS, INACTIVE_CLASS); 46080 } else { 46081 $animate.setClass($element, INACTIVE_CLASS, ACTIVE_CLASS); 46082 } 46083 }; 46084 46085 $scope.$watchCollection($attrs.ngMessages || $attrs['for'], ctrl.render); 46086 46087 // If the element is destroyed, proactively destroy all the currently visible messages 46088 $element.on('$destroy', function() { 46089 forEach(messages, function(item) { 46090 item.message.detach(); 46091 }); 46092 }); 46093 46094 this.reRender = function() { 46095 if (!renderLater) { 46096 renderLater = true; 46097 $scope.$evalAsync(function() { 46098 if (renderLater && cachedCollection) { 46099 ctrl.render(cachedCollection); 46100 } 46101 }); 46102 } 46103 }; 46104 46105 this.register = function(comment, messageCtrl) { 46106 var nextKey = latestKey.toString(); 46107 messages[nextKey] = { 46108 message: messageCtrl 46109 }; 46110 insertMessageNode($element[0], comment, nextKey); 46111 comment.$$ngMessageNode = nextKey; 46112 latestKey++; 46113 46114 ctrl.reRender(); 46115 }; 46116 46117 this.deregister = function(comment) { 46118 var key = comment.$$ngMessageNode; 46119 delete comment.$$ngMessageNode; 46120 removeMessageNode($element[0], comment, key); 46121 delete messages[key]; 46122 ctrl.reRender(); 46123 }; 46124 46125 function findPreviousMessage(parent, comment) { 46126 var prevNode = comment; 46127 var parentLookup = []; 46128 46129 while (prevNode && prevNode !== parent) { 46130 var prevKey = prevNode.$$ngMessageNode; 46131 if (prevKey && prevKey.length) { 46132 return messages[prevKey]; 46133 } 46134 46135 // dive deeper into the DOM and examine its children for any ngMessage 46136 // comments that may be in an element that appears deeper in the list 46137 if (prevNode.childNodes.length && parentLookup.indexOf(prevNode) === -1) { 46138 parentLookup.push(prevNode); 46139 prevNode = prevNode.childNodes[prevNode.childNodes.length - 1]; 46140 } else if (prevNode.previousSibling) { 46141 prevNode = prevNode.previousSibling; 46142 } else { 46143 prevNode = prevNode.parentNode; 46144 parentLookup.push(prevNode); 46145 } 46146 } 46147 } 46148 46149 function insertMessageNode(parent, comment, key) { 46150 var messageNode = messages[key]; 46151 if (!ctrl.head) { 46152 ctrl.head = messageNode; 46153 } else { 46154 var match = findPreviousMessage(parent, comment); 46155 if (match) { 46156 messageNode.next = match.next; 46157 match.next = messageNode; 46158 } else { 46159 messageNode.next = ctrl.head; 46160 ctrl.head = messageNode; 46161 } 46162 } 46163 } 46164 46165 function removeMessageNode(parent, comment, key) { 46166 var messageNode = messages[key]; 46167 46168 var match = findPreviousMessage(parent, comment); 46169 if (match) { 46170 match.next = messageNode.next; 46171 } else { 46172 ctrl.head = messageNode.next; 46173 } 46174 } 46175 }] 46176 }; 46177 46178 function isAttrTruthy(scope, attr) { 46179 return (isString(attr) && attr.length === 0) || //empty attribute 46180 truthy(scope.$eval(attr)); 46181 } 46182 46183 function truthy(val) { 46184 return isString(val) ? val.length : !!val; 46185 } 46186 }]) 46187 46188 /** 46189 * @ngdoc directive 46190 * @name ngMessagesInclude 46191 * @restrict AE 46192 * @scope 46193 * 46194 * @description 46195 * `ngMessagesInclude` is a directive with the purpose to import existing ngMessage template 46196 * code from a remote template and place the downloaded template code into the exact spot 46197 * that the ngMessagesInclude directive is placed within the ngMessages container. This allows 46198 * for a series of pre-defined messages to be reused and also allows for the developer to 46199 * determine what messages are overridden due to the placement of the ngMessagesInclude directive. 46200 * 46201 * @usage 46202 * ```html 46203 * <!-- using attribute directives --> 46204 * <ANY ng-messages="expression" role="alert"> 46205 * <ANY ng-messages-include="remoteTplString">...</ANY> 46206 * </ANY> 46207 * 46208 * <!-- or by using element directives --> 46209 * <ng-messages for="expression" role="alert"> 46210 * <ng-messages-include src="expressionValue1">...</ng-messages-include> 46211 * </ng-messages> 46212 * ``` 46213 * 46214 * {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`. 46215 * 46216 * @param {string} ngMessagesInclude|src a string value corresponding to the remote template. 46217 */ 46218 .directive('ngMessagesInclude', 46219 ['$templateRequest', '$document', '$compile', function($templateRequest, $document, $compile) { 46220 46221 return { 46222 restrict: 'AE', 46223 require: '^^ngMessages', // we only require this for validation sake 46224 link: function($scope, element, attrs) { 46225 var src = attrs.ngMessagesInclude || attrs.src; 46226 $templateRequest(src).then(function(html) { 46227 if ($scope.$$destroyed) return; 46228 46229 if (isString(html) && !html.trim()) { 46230 // Empty template - nothing to compile 46231 replaceElementWithMarker(element, src); 46232 } else { 46233 // Non-empty template - compile and link 46234 $compile(html)($scope, function(contents) { 46235 element.after(contents); 46236 replaceElementWithMarker(element, src); 46237 }); 46238 } 46239 }); 46240 } 46241 }; 46242 46243 // Helpers 46244 function replaceElementWithMarker(element, src) { 46245 // A comment marker is placed for debugging purposes 46246 var comment = $compile.$$createComment ? 46247 $compile.$$createComment('ngMessagesInclude', src) : 46248 $document[0].createComment(' ngMessagesInclude: ' + src + ' '); 46249 var marker = jqLite(comment); 46250 element.after(marker); 46251 46252 // Don't pollute the DOM anymore by keeping an empty directive element 46253 element.remove(); 46254 } 46255 }]) 46256 46257 /** 46258 * @ngdoc directive 46259 * @name ngMessage 46260 * @restrict AE 46261 * @scope 46262 * 46263 * @description 46264 * `ngMessage` is a directive with the purpose to show and hide a particular message. 46265 * For `ngMessage` to operate, a parent `ngMessages` directive on a parent DOM element 46266 * must be situated since it determines which messages are visible based on the state 46267 * of the provided key/value map that `ngMessages` listens on. 46268 * 46269 * More information about using `ngMessage` can be found in the 46270 * {@link module:ngMessages `ngMessages` module documentation}. 46271 * 46272 * @usage 46273 * ```html 46274 * <!-- using attribute directives --> 46275 * <ANY ng-messages="expression" role="alert"> 46276 * <ANY ng-message="stringValue">...</ANY> 46277 * <ANY ng-message="stringValue1, stringValue2, ...">...</ANY> 46278 * </ANY> 46279 * 46280 * <!-- or by using element directives --> 46281 * <ng-messages for="expression" role="alert"> 46282 * <ng-message when="stringValue">...</ng-message> 46283 * <ng-message when="stringValue1, stringValue2, ...">...</ng-message> 46284 * </ng-messages> 46285 * ``` 46286 * 46287 * @param {expression}
46287 ngMessage|when a string value corresponding to the message key. 46288 */ 46289 .directive('ngMessage', ngMessageDirectiveFactory()) 46290 46291 46292 /** 46293 * @ngdoc directive 46294 * @name ngMessageExp 46295 * @restrict AE 46296 * @priority 1 46297 * @scope 46298 * 46299 * @description 46300 * `ngMessageExp` is the same as {@link directive:ngMessage `ngMessage`}, but instead of a static 46301 * value, it accepts an expression to be evaluated for the message key. 46302 * 46303 * @usage 46304 * ```html 46305 * <!-- using attribute directives --> 46306 * <ANY ng-messages="expression"> 46307 * <ANY ng-message-exp="expressionValue">...</ANY> 46308 * </ANY> 46309 * 46310 * <!-- or by using element directives --> 46311 * <ng-messages for="expression"> 46312 * <ng-message when-exp="expressionValue">...</ng-message> 46313 * </ng-messages> 46314 * ``` 46315 * 46316 * {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`. 46317 * 46318 * @param {expression} ngMessageExp|whenExp an expression value corresponding to the message key. 46319 */ 46320 .directive('ngMessageExp', ngMessageDirectiveFactory()); 46321 46322function ngMessageDirectiveFactory() { 46323 return ['$animate', function($animate) { 46324 return { 46325 restrict: 'AE', 46326 transclude: 'element', 46327 priority: 1, // must run before ngBind, otherwise the text is set on the comment 46328 terminal: true, 46329 require: '^^ngMessages', 46330 link: function(scope, element, attrs, ngMessagesCtrl, $transclude) { 46331 var commentNode = element[0]; 46332 46333 var records; 46334 var staticExp = attrs.ngMessage || attrs.when; 46335 var dynamicExp = attrs.ngMessageExp || attrs.whenExp; 46336 var assignRecords = function(items) { 46337 records = items 46338 ? (isArray(items) 46339 ? items 46340 : items.split(/[\s,]+/)) 46341 : null; 46342 ngMessagesCtrl.reRender(); 46343 }; 46344 46345 if (dynamicExp) { 46346 assignRecords(scope.$eval(dynamicExp)); 46347 scope.$watchCollection(dynamicExp, assignRecords); 46348 } else { 46349 assignRecords(staticExp); 46350 } 46351 46352 var currentElement, messageCtrl; 46353 ngMessagesCtrl.register(commentNode, messageCtrl = { 46354 test: function(name) { 46355 return contains(records, name); 46356 }, 46357 attach: function() { 46358 if (!currentElement) { 46359 $transclude(function(elm, newScope) { 46360 $animate.enter(elm, null, element); 46361 currentElement = elm; 46362 46363 // Each time we attach this node to a message we get a new id that we can match 46364 // when we are destroying the node later. 46365 var $$attachId = currentElement.$$attachId = ngMessagesCtrl.getAttachId(); 46366 46367 // in the event that the element or a parent element is destroyed 46368 // by another structural directive then it's time 46369 // to deregister the message from the controller 46370 currentElement.on('$destroy', function() { 46371 if (currentElement && currentElement.$$attachId === $$attachId) { 46372 ngMessagesCtrl.deregister(commentNode); 46373 messageCtrl.detach(); 46374 } 46375 newScope.$destroy(); 46376 }); 46377 }); 46378 } 46379 }, 46380 detach: function() { 46381 if (currentElement) { 46382 var elm = currentElement; 46383 currentElement = null; 46384 $animate.leave(elm); 46385 } 46386 } 46387 }); 46388 } 46389 }; 46390 }]; 46391 46392 function contains(collection, key) { 46393 if (collection) { 46394 return isArray(collection) 46395 ? collection.indexOf(key) >= 0 46396 : collection.hasOwnProperty(key); 46397 } 46398 } 46399} 46400 46401 46402})(window, window.angular); 46403 46404/*! 46405 * Select2 4.0.13
vendor: 17,402 bytes, lines 46405-46904
46405 46406 * https://select2.github.io 46407 * 46408 * Released under the MIT license 46409 * https://github.com/select2/select2/blob/master/LICENSE.md 46410 */ 46411;(function (factory) { 46412 if (typeof define === 'function' && define.amd) { 46413 // AMD. Register as an anonymous module. 46414 define(['jquery'], factory); 46415 } else if (typeof module === 'object' && module.exports) { 46416 // Node/CommonJS 46417 module.exports = function (root, jQuery) { 46418 if (jQuery === undefined) { 46419 // require('jQuery') returns a factory that requires window to 46420 // build a jQuery instance, we normalize how we use modules 46421 // that require this pattern but the window provided is a noop 46422 // if it's defined (how jquery works) 46423 if (typeof window !== 'undefined') { 46424 jQuery = require('jquery'); 46425 } 46426 else { 46427 jQuery = require('jquery')(root); 46428 } 46429 } 46430 factory(jQuery); 46431 return jQuery; 46432 }; 46433 } else { 46434 // Browser globals 46435 factory(jQuery); 46436 } 46437} (function (jQuery) { 46438 // This is needed so we can catch the AMD loader configuration and use it 46439 // The inner file should be wrapped (by `banner.start.js`) in a function that 46440 // returns the AMD loader references. 46441 var S2 =(function () { 46442 // Restore the Select2 AMD loader so it can be used 46443 // Needed mostly in the language files, where the loader is not inserted 46444 if (jQuery && jQuery.fn && jQuery.fn.select2 && jQuery.fn.select2.amd) { 46445 var S2 = jQuery.fn.select2.amd; 46446 } 46447var S2;(function () { if (!S2 || !S2.requirejs) { 46448if (!S2) { S2 = {}; } else { require = S2; } 46449/** 46450 * @license almond 0.3.3 Copyright jQuery Foundation and other contributors. 46451 * Released under MIT license, http://github.com/requirejs/almond/LICENSE 46452 */ 46453//Going sloppy to avoid 'use strict' string cost, but strict practices should 46454//be followed. 46455/*global setTimeout: false */ 46456 46457var requirejs, require, define; 46458(function (undef) { 46459 var main, req, makeMap, handlers, 46460 defined = {}, 46461 waiting = {}, 46462 config = {}, 46463 defining = {}, 46464 hasOwn = Object.prototype.hasOwnProperty, 46465 aps = [].slice, 46466 jsSuffixRegExp = /\.js$/; 46467 46468 function hasProp(obj, prop) { 46469 return hasOwn.call(obj, prop); 46470 } 46471 46472 /** 46473 * Given a relative module name, like ./something, normalize it to 46474 * a real name that can be mapped to a path. 46475 * @param {String} name the relative name 46476 * @param {String} baseName a real name that the name arg is relative 46477 * to. 46478 * @returns {String} normalized name 46479 */ 46480 function normalize(name, baseName) { 46481 var nameParts, nameSegment, mapValue, foundMap, lastIndex, 46482 foundI, foundStarMap, starI, i, j, part, normalizedBaseParts, 46483 baseParts = baseName && baseName.split("/"), 46484 map = config.map, 46485 starMap = (map && map['*']) || {}; 46486 46487 //Adjust any relative paths. 46488 if (name) { 46489 name = name.split('/'); 46490 lastIndex = name.length - 1; 46491 46492 // If wanting node ID compatibility, strip .js from end 46493 // of IDs. Have to do this here, and not in nameToUrl 46494 // because node allows either .js or non .js to map 46495 // to same file. 46496 if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) { 46497 name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, ''); 46498 } 46499 46500 // Starts with a '.' so need the baseName 46501 if (name[0].charAt(0) === '.' && baseParts) { 46502 //Convert baseName to array, and lop off the last part, 46503 //so that . matches that 'directory' and not name of the baseName's 46504 //module. For instance, baseName of 'one/two/three', maps to 46505 //'one/two/three.js', but we want the directory, 'one/two' for 46506 //this normalization. 46507 normalizedBaseParts = baseParts.slice(0, baseParts.length - 1); 46508 name = normalizedBaseParts.concat(name); 46509 } 46510 46511 //start trimDots 46512 for (i = 0; i < name.length; i++) { 46513 part = name[i]; 46514 if (part === '.') { 46515 name.splice(i, 1); 46516 i -= 1; 46517 } else if (part === '..') { 46518 // If at the start, or previous value is still .., 46519 // keep them so that when converted to a path it may 46520 // still work when converted to a path, even though 46521 // as an ID it is less than ideal. In larger point 46522 // releases, may be better to just kick out an error. 46523 if (i === 0 || (i === 1 && name[2] === '..') || name[i - 1] === '..') { 46524 continue; 46525 } else if (i > 0) { 46526 name.splice(i - 1, 2); 46527 i -= 2; 46528 } 46529 } 46530 } 46531 //end trimDots 46532 46533 name = name.join('/'); 46534 } 46535 46536 //Apply map config if available. 46537 if ((baseParts || starMap) && map) { 46538 nameParts = name.split('/'); 46539 46540 for (i = nameParts.length; i > 0; i -= 1) { 46541 nameSegment = nameParts.slice(0, i).join("/"); 46542 46543 if (baseParts) { 46544 //Find the longest baseName segment match in the config. 46545 //So, do joins on the biggest to smallest lengths of baseParts. 46546 for (j = baseParts.length; j > 0; j -= 1) { 46547 mapValue = map[baseParts.slice(0, j).join('/')]; 46548 46549 //baseName segment has config, find if it has one for 46550 //this name. 46551 if (mapValue) { 46552 mapValue = mapValue[nameSegment]; 46553 if (mapValue) { 46554 //Match, update name to the new value. 46555 foundMap = mapValue; 46556 foundI = i; 46557 break; 46558 } 46559 } 46560 } 46561 } 46562 46563 if (foundMap) { 46564 break; 46565 } 46566 46567 //Check for a star map match, but just hold on to it, 46568 //if there is a shorter segment match later in a matching 46569 //config, then favor over this star map. 46570 if (!foundStarMap && starMap && starMap[nameSegment]) { 46571 foundStarMap = starMap[nameSegment]; 46572 starI = i; 46573 } 46574 } 46575 46576 if (!foundMap && foundStarMap) { 46577 foundMap = foundStarMap; 46578 foundI = starI; 46579 } 46580 46581 if (foundMap) { 46582 nameParts.splice(0, foundI, foundMap); 46583 name = nameParts.join('/'); 46584 } 46585 } 46586 46587 return name; 46588 } 46589 46590 function makeRequire(relName, forceSync) { 46591 return function () { 46592 //A version of a require function that passes a moduleName 46593 //value for items that may need to 46594 //look up paths relative to the moduleName 46595 var args = aps.call(arguments, 0); 46596 46597 //If first arg is not require('string'), and there is only 46598 //one arg, it is the array form without a callback. Insert 46599 //a null so that the following concat is correct. 46600 if (typeof args[0] !== 'string' && args.length === 1) { 46601 args.push(null); 46602 } 46603 return req.apply(undef, args.concat([relName, forceSync])); 46604 }; 46605 } 46606 46607 function makeNormalize(relName) { 46608 return function (name) { 46609 return normalize(name, relName); 46610 }; 46611 } 46612 46613 function makeLoad(depName) { 46614 return function (value) { 46615 defined[depName] = value; 46616 }; 46617 } 46618 46619 function callDep(name) { 46620 if (hasProp(waiting, name)) { 46621 var args = waiting[name]; 46622 delete waiting[name]; 46623 defining[name] = true; 46624 main.apply(undef, args); 46625 } 46626 46627 if (!hasProp(defined, name) && !hasProp(defining, name)) { 46628 throw new Error('No ' + name); 46629 } 46630 return defined[name]; 46631 } 46632 46633 //Turns a plugin!resource to [plugin, resource] 46634 //with the plugin being undefined if the name 46635 //did not have a plugin prefix. 46636 function splitPrefix(name) { 46637 var prefix, 46638 index = name ? name.indexOf('!') : -1; 46639 if (index > -1) { 46640 prefix = name.substring(0, index); 46641 name = name.substring(index + 1, name.length); 46642 } 46643 return [prefix, name]; 46644 } 46645 46646 //Creates a parts array for a relName where first part is plugin ID, 46647 //second part is resource ID. Assumes relName has already been normalized. 46648 function makeRelParts(relName) { 46649 return relName ? splitPrefix(relName) : []; 46650 } 46651 46652 /** 46653 * Makes a name map, normalizing the name, and using a plugin 46654 * for normalization if necessary. Grabs a ref to plugin 46655 * too, as an optimization. 46656 */ 46657 makeMap = function (name, relParts) { 46658 var plugin, 46659 parts = splitPrefix(name), 46660 prefix = parts[0], 46661 relResourceName = relParts[1]; 46662 46663 name = parts[1]; 46664 46665 if (prefix) { 46666 prefix = normalize(prefix, relResourceName); 46667 plugin = callDep(prefix); 46668 } 46669 46670 //Normalize according 46671 if (prefix) { 46672 if (plugin && plugin.normalize) { 46673 name = plugin.normalize(name, makeNormalize(relResourceName)); 46674 } else { 46675 name = normalize(name, relResourceName); 46676 } 46677 } else { 46678 name = normalize(name, relResourceName); 46679 parts = splitPrefix(name); 46680 prefix = parts[0]; 46681 name = parts[1]; 46682 if (prefix) { 46683 plugin = callDep(prefix); 46684 } 46685 } 46686 46687 //Using ridiculous property names for space reasons 46688 return { 46689 f: prefix ? prefix + '!' + name : name, //fullName 46690 n: name, 46691 pr: prefix, 46692 p: plugin 46693 }; 46694 }; 46695 46696 function makeConfig(name) { 46697 return function () { 46698 return (config && config.config && config.config[name]) || {}; 46699 }; 46700 } 46701 46702 handlers = { 46703 require: function (name) { 46704 return makeRequire(name); 46705 }, 46706 exports: function (name) { 46707 var e = defined[name]; 46708 if (typeof e !== 'undefined') { 46709 return e; 46710 } else { 46711 return (defined[name] = {}); 46712 } 46713 }, 46714 module: function (name) { 46715 return { 46716 id: name, 46717 uri: '', 46718 exports: defined[name], 46719 config: makeConfig(name) 46720 }; 46721 } 46722 }; 46723 46724 main = function (name, deps, callback, relName) { 46725 var cjsModule, depName, ret, map, i, relParts, 46726 args = [], 46727 callbackType = typeof callback, 46728 usingExports; 46729 46730 //Use name if no relName 46731 relName = relName || name; 46732 relParts = makeRelParts(relName); 46733 46734 //Call the callback to define the module, if necessary. 46735 if (callbackType === 'undefined' || callbackType === 'function') { 46736 //Pull out the defined dependencies and pass the ordered 46737 //values to the callback. 46738 //Default to [require, exports, module] if no deps 46739 deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps; 46740 for (i = 0; i < deps.length; i += 1) { 46741 map = makeMap(deps[i], relParts); 46742 depName = map.f; 46743 46744 //Fast path CommonJS standard dependencies. 46745 if (depName === "require") { 46746 args[i] = handlers.require(name); 46747 } else if (depName === "exports") { 46748 //CommonJS module spec 1.1 46749 args[i] = handlers.exports(name); 46750 usingExports = true; 46751 } else if (depName === "module") { 46752 //CommonJS module spec 1.1 46753 cjsModule = args[i] = handlers.module(name); 46754 } else if (hasProp(defined, depName) || 46755 hasProp(waiting, depName) || 46756 hasProp(defining, depName)) { 46757 args[i] = callDep(depName); 46758 } else if (map.p) { 46759 map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {}); 46760 args[i] = defined[depName]; 46761 } else { 46762 throw new Error(name + ' missing ' + depName); 46763 } 46764 } 46765 46766 ret = callback ? callback.apply(defined[name], args) : undefined; 46767 46768 if (name) { 46769 //If setting exports via "module" is in play, 46770 //favor that over return value and exports. After that, 46771 //favor a non-undefined return value over exports use. 46772 if (cjsModule && cjsModule.exports !== undef && 46773 cjsModule.exports !== defined[name]) { 46774 defined[name] = cjsModule.exports; 46775 } else if (ret !== undef || !usingExports) { 46776 //Use the return value from the function. 46777 defined[name] = ret; 46778 } 46779 } 46780 } else if (name) { 46781 //May just be an object definition for the module. Only 46782 //worry about defining if have a module name. 46783 defined[name] = callback; 46784 } 46785 }; 46786 46787 requirejs = require = req = function (deps, callback, relName, forceSync, alt) { 46788 if (typeof deps === "string") { 46789 if (handlers[deps]) { 46790 //callback in this case is really relName 46791 return handlers[deps](callback); 46792 } 46793 //Just return the module wanted. In this scenario, the 46794 //deps arg is the module name, and second arg (if passed) 46795 //is just the relName. 46796 //Normalize module name, if it contains . or .. 46797 return callDep(makeMap(deps, makeRelParts(callback)).f); 46798 } else if (!deps.splice) { 46799 //deps is a config object, not an array. 46800 config = deps; 46801 if (config.deps) { 46802 req(config.deps, config.callback); 46803 } 46804 if (!callback) { 46805 return; 46806 } 46807 46808 if (callback.splice) { 46809 //callback is an array, which means it is a dependency list. 46810 //Adjust args if there are dependencies 46811 deps = callback; 46812 callback = relName; 46813 relName = null; 46814 } else { 46815 deps = undef; 46816 } 46817 } 46818 46819 //Support require(['a']) 46820 callback = callback || function () {}; 46821 46822 //If relName is a function, it is an errback handler, 46823 //so remove it. 46824 if (typeof relName === 'function') { 46825 relName = forceSync; 46826 forceSync = alt; 46827 } 46828 46829 //Simulate async callback; 46830 if (forceSync) { 46831 main(undef, deps, callback, relName); 46832 } else { 46833 //Using a non-zero value because of concern for what old browsers 46834 //do, and latest browsers "upgrade" to 4 if lower value is used: 46835 //http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout: 46836 //If want a value immediately, use require('id') instead -- something 46837 //that works in almond on the global level, but not guaranteed and 46838 //unlikely to work in other AMD implementations. 46839 setTimeout(function () { 46840 main(undef, deps, callback, relName); 46841 }, 4); 46842 } 46843 46844 return req; 46845 }; 46846 46847 /** 46848 * Just drops the config on the floor, but returns req in case 46849 * the config return value is used. 46850 */ 46851 req.config = function (cfg) { 46852 return req(cfg); 46853 }; 46854 46855 /** 46856 * Expose module registry for debugging and tooling 46857 */ 46858 requirejs._defined = defined; 46859 46860 define = function (name, deps, callback) { 46861 if (typeof name !== 'string') { 46862 throw new Error('See almond README: incorrect module build, no module name'); 46863 } 46864 46865 //This module may not have dependencies 46866 if (!deps.splice) { 46867 //deps is not an array, so probably means 46868 //an object literal or factory function for 46869 //the value. Adjust args. 46870 callback = deps; 46871 deps = []; 46872 } 46873 46874 if (!hasProp(defined, name) && !hasProp(waiting, name)) { 46875 waiting[name] = [name, deps, callback]; 46876 } 46877 }; 46878 46879 define.amd = { 46880 jQuery: true 46881 }; 46882}()); 46883 46884S2.requirejs = requirejs;S2.require = require;S2.define = define; 46885} 46886}()); 46887S2.define("almond", function(){}); 46888 46889/* global jQuery:false, $:false */ 46890S2.define('jquery',[],function () { 46891 var _$ = jQuery || $; 46892 46893 if (_$ == null && console && console.error) { 46894 console.error( 46895 'Select2: An instance of jQuery or a jQuery-compatible library was not ' + 46896 'found. Make sure that you are including jQuery before Select2 on your ' + 46897 'web page.' 46898 ); 46899 } 46900 46901 return _$; 46902}); 46903 46904S2.define('select2/utils',[
vendor: 5,112 bytes, lines 46905-47117
46905 'jquery' 46906], function ($) { 46907 var Utils = {}; 46908 46909 Utils.Extend = function (ChildClass, SuperClass) { 46910 var __hasProp = {}.hasOwnProperty; 46911 46912 function BaseConstructor () { 46913 this.constructor = ChildClass; 46914 } 46915 46916 for (var key in SuperClass) { 46917 if (__hasProp.call(SuperClass, key)) { 46918 ChildClass[key] = SuperClass[key]; 46919 } 46920 } 46921 46922 BaseConstructor.prototype = SuperClass.prototype; 46923 ChildClass.prototype = new BaseConstructor(); 46924 ChildClass.__super__ = SuperClass.prototype; 46925 46926 return ChildClass; 46927 }; 46928 46929 function getMethods (theClass) { 46930 var proto = theClass.prototype; 46931 46932 var methods = []; 46933 46934 for (var methodName in proto) { 46935 var m = proto[methodName]; 46936 46937 if (typeof m !== 'function') { 46938 continue; 46939 } 46940 46941 if (methodName === 'constructor') { 46942 continue; 46943 } 46944 46945 methods.push(methodName); 46946 } 46947 46948 return methods; 46949 } 46950 46951 Utils.Decorate = function (SuperClass, DecoratorClass) { 46952 var decoratedMethods = getMethods(DecoratorClass); 46953 var superMethods = getMethods(SuperClass); 46954 46955 function DecoratedClass () { 46956 var unshift = Array.prototype.unshift; 46957 46958 var argCount = DecoratorClass.prototype.constructor.length; 46959 46960 var calledConstructor = SuperClass.prototype.constructor; 46961 46962 if (argCount > 0) { 46963 unshift.call(arguments, SuperClass.prototype.constructor); 46964 46965 calledConstructor = DecoratorClass.prototype.constructor; 46966 } 46967 46968 calledConstructor.apply(this, arguments); 46969 } 46970 46971 DecoratorClass.displayName = SuperClass.displayName; 46972 46973 function ctr () { 46974 this.constructor = DecoratedClass; 46975 } 46976 46977 DecoratedClass.prototype = new ctr(); 46978 46979 for (var m = 0; m < superMethods.length; m++) { 46980 var superMethod = superMethods[m]; 46981 46982 DecoratedClass.prototype[superMethod] = 46983 SuperClass.prototype[superMethod]; 46984 } 46985 46986 var calledMethod = function (methodName) { 46987 // Stub out the original method if it's not decorating an actual method 46988 var originalMethod = function () {}; 46989 46990 if (methodName in DecoratedClass.prototype) { 46991 originalMethod = DecoratedClass.prototype[methodName]; 46992 } 46993 46994 var decoratedMethod = DecoratorClass.prototype[methodName]; 46995 46996 return function () { 46997 var unshift = Array.prototype.unshift; 46998 46999 unshift.call(arguments, originalMethod); 47000 47001 return decoratedMethod.apply(this, arguments); 47002 }; 47003 }; 47004 47005 for (var d = 0; d < decoratedMethods.length; d++) { 47006 var decoratedMethod = decoratedMethods[d]; 47007 47008 DecoratedClass.prototype[decoratedMethod] = calledMethod(decoratedMethod); 47009 } 47010 47011 return DecoratedClass; 47012 }; 47013 47014 var Observable = function () { 47015 this.listeners = {}; 47016 }; 47017 47018 Observable.prototype.on = function (event, callback) { 47019 this.listeners = this.listeners || {}; 47020 47021 if (event in this.listeners) { 47022 this.listeners[event].push(callback); 47023 } else { 47024 this.listeners[event] = [callback]; 47025 } 47026 }; 47027 47028 Observable.prototype.trigger = function (event) { 47029 var slice = Array.prototype.slice; 47030 var params = slice.call(arguments, 1); 47031 47032 this.listeners = this.listeners || {}; 47033 47034 // Params should always come in as an array 47035 if (params == null) { 47036 params = []; 47037 } 47038 47039 // If there are no arguments to the event, use a temporary object 47040 if (params.length === 0) { 47041 params.push({}); 47042 } 47043 47044 // Set the `_type` of the first object to the event 47045 params[0]._type = event; 47046 47047 if (event in this.listeners) { 47048 this.invoke(this.listeners[event], slice.call(arguments, 1)); 47049 } 47050 47051 if ('*' in this.listeners) { 47052 this.invoke(this.listeners['*'], arguments); 47053 } 47054 }; 47055 47056 Observable.prototype.invoke = function (listeners, params) { 47057 for (var i = 0, len = listeners.length; i < len; i++) { 47058 listeners[i].apply(this, params); 47059 } 47060 }; 47061 47062 Utils.Observable = Observable; 47063 47064 Utils.generateChars = function (length) { 47065 var chars = ''; 47066 47067 for (var i = 0; i < length; i++) { 47068 var randomChar = Math.floor(Math.random() * 36); 47069 chars += randomChar.toString(36); 47070 } 47071 47072 return chars; 47073 }; 47074 47075 Utils.bind = function (func, context) { 47076 return function () { 47077 func.apply(context, arguments); 47078 }; 47079 }; 47080 47081 Utils._convertData = function (data) { 47082 for (var originalKey in data) { 47083 var keys = originalKey.split('-'); 47084 47085 var dataLevel = data; 47086 47087 if (keys.length === 1) { 47088 continue; 47089 } 47090 47091 for (var k = 0; k < keys.length; k++) { 47092 var key = keys[k]; 47093 47094 // Lowercase the first letter 47095 // By default, dash-separated becomes camelCase 47096 key = key.substring(0, 1).toLowerCase() + key.substring(1); 47097 47098 if (!(key in dataLevel)) { 47099 dataLevel[key] = {}; 47100 } 47101 47102 if (k == keys.length - 1) { 47103 dataLevel[key] = data[originalKey]; 47104 } 47105 47106 dataLevel = dataLevel[key]; 47107 } 47108 47109 delete data[originalKey]; 47110 } 47111 47112 return data; 47113 }; 47114 47115 Utils.hasScroll = function (index, el) { 47116 // Adapted from the function created by @ShadowScripter 47117 // and adapted by @BillBarry on the Stack Exchange Code Review website.
47118 // The original code can be found at 47119 // http://codereview.stackexchange.com/q/13338 47120 // and was designed to be used with the Sizzle selector engine. 47121 47122 var $el = $(el); 47123 var overflowX = el.style.overflowX; 47124 var overflowY = el.style.overflowY; 47125 47126 //Check both x and y declarations 47127 if (overflowX === overflowY && 47128 (overflowY === 'hidden' || overflowY === 'visible')) { 47129 return false; 47130 } 47131 47132 if (overflowX === 'scroll' || overflowY === 'scroll') { 47133 return true; 47134 } 47135 47136 return ($el.innerHeight() < el.scrollHeight || 47137 $el.innerWidth() < el.scrollWidth); 47138 }; 47139 47140 Utils.escapeMarkup = function (markup) { 47141 var replaceMap = { 47142 '\\': '\', 47143 '&': '&', 47144 '<': '<', 47145 '>': '>', 47146 '"': '"', 47147 '\'': ''', 47148 '/': '/' 47149 }; 47150 47151 // Do not try to escape the markup if it's not a string 47152 if (typeof markup !== 'string') { 47153 return markup; 47154 } 47155 47156 return String(markup).replace(/[&<>"'\/\\]/g, function (match) { 47157 return replaceMap[match]; 47158 }); 47159 }; 47160 47161 // Append an array of jQuery nodes to a given element. 47162 Utils.appendMany = function ($element, $nodes) { 47163 // jQuery 1.7.x does not support $.fn.append() with an array 47164 // Fall back to a jQuery object collection using $.fn.add() 47165 if ($.fn.jquery.substr(0, 3) === '1.7') { 47166 var $jqNodes = $(); 47167 47168 $.map($nodes, function (node) { 47169 $jqNodes = $jqNodes.add(node); 47170 }); 47171 47172 $nodes = $jqNodes; 47173 } 47174 47175 $element.append($nodes); 47176 }; 47177 47178 // Cache objects in Utils.__cache instead of $.data (see #4346) 47179 Utils.__cache = {}; 47180 47181 var id = 0; 47182 Utils.GetUniqueElementId = function (element) { 47183 // Get a unique element Id. If element has no id, 47184 // creates a new unique number, stores it in the id 47185 // attribute and returns the new id. 47186 // If an id already exists, it simply returns it. 47187 47188 var select2Id = element.getAttribute('data-select2-id'); 47189 if (select2Id == null) { 47190 // If element has id, use it. 47191 if (element.id) { 47192 select2Id = element.id; 47193 element.setAttribute('data-select2-id', select2Id); 47194 } else { 47195 element.setAttribute('data-select2-id', ++id); 47196 select2Id = id.toString(); 47197 } 47198 } 47199 return select2Id; 47200 }; 47201 47202 Utils.StoreData = function (element, name, value) { 47203 // Stores an item in the cache for a specified element. 47204 // name is the cache key. 47205 var id = Utils.GetUniqueElementId(element); 47206 if (!Utils.__cache[id]) { 47207 Utils.__cache[id] = {}; 47208 } 47209 47210 Utils.__cache[id][name] = value; 47211 }; 47212 47213 Utils.GetData = function (element, name) { 47214 // Retrieves a value from the cache by its key (name) 47215 // name is optional. If no name specified, return 47216 // all cache items for the specified element. 47217 // and for a specified element. 47218 var id = Utils.GetUniqueElementId(element); 47219 if (name) { 47220 if (Utils.__cache[id]) { 47221 if (Utils.__cache[id][name] != null) { 47222 return Utils.__cache[id][name]; 47223 } 47224 return $(element).data(name); // Fallback to HTML5 data attribs. 47225 } 47226 return $(element).data(name); // Fallback to HTML5 data attribs. 47227 } else { 47228 return Utils.__cache[id]; 47229 } 47230 }; 47231 47232 Utils.RemoveData = function (element) { 47233 // Removes all cached items for a specified element. 47234 var id = Utils.GetUniqueElementId(element); 47235 if (Utils.__cache[id] != null) { 47236 delete Utils.__cache[id]; 47237 } 47238 47239 element.removeAttribute('data-select2-id'); 47240 }; 47241 47242 return Utils; 47243}); 47244 47245S2.define('select2/results',[ 47246 'jquery', 47247 './utils' 47248], function ($, Utils) { 47249 function Results ($element, options, dataAdapter) { 47250 this.$element = $element; 47251 this.data = dataAdapter; 47252 this.options = options; 47253 47254 Results.__super__.constructor.call(this); 47255 } 47256 47257 Utils.Extend(Results, Utils.Observable); 47258 47259 Results.prototype.render = function () { 47260 var $results = $( 47261 '<ul class="select2-results__options" role="listbox"></ul>' 47262 ); 47263 47264 if (this.options.get('multiple')) { 47265 $results.attr('aria-multiselectable', 'true'); 47266 } 47267 47268 this.$results = $results; 47269 47270 return $results; 47271 }; 47272 47273 Results.prototype.clear = function () { 47274 this.$results.empty(); 47275 }; 47276 47277 Results.prototype.displayMessage = function (params) { 47278 var escapeMarkup = this.options.get('escapeMarkup'); 47279 47280 this.clear(); 47281 this.hideLoading(); 47282 47283 var $message = $( 47284 '<li role="alert" aria-live="assertive"' + 47285 ' class="select2-results__option"></li>' 47286 ); 47287 47288 var message = this.options.get('translations').get(params.message); 47289 47290 $message.append( 47291 escapeMarkup( 47292 message(params.args) 47293 ) 47294 ); 47295 47296 $message[0].className += ' select2-results__message';
vendor: 47,567 bytes, lines 47297-49314
47297 47298 this.$results.append($message); 47299 }; 47300 47301 Results.prototype.hideMessages = function () { 47302 this.$results.find('.select2-results__message').remove(); 47303 }; 47304 47305 Results.prototype.append = function (data) { 47306 this.hideLoading(); 47307 47308 var $options = []; 47309 47310 if (data.results == null || data.results.length === 0) { 47311 if (this.$results.children().length === 0) { 47312 this.trigger('results:message', { 47313 message: 'noResults' 47314 }); 47315 } 47316 47317 return; 47318 } 47319 47320 data.results = this.sort(data.results); 47321 47322 for (var d = 0; d < data.results.length; d++) { 47323 var item = data.results[d]; 47324 47325 var $option = this.option(item); 47326 47327 $options.push($option); 47328 } 47329 47330 this.$results.append($options); 47331 }; 47332 47333 Results.prototype.position = function ($results, $dropdown) { 47334 var $resultsContainer = $dropdown.find('.select2-results'); 47335 $resultsContainer.append($results); 47336 }; 47337 47338 Results.prototype.sort = function (data) { 47339 var sorter = this.options.get('sorter'); 47340 47341 return sorter(data); 47342 }; 47343 47344 Results.prototype.highlightFirstItem = function () { 47345 var $options = this.$results 47346 .find('.select2-results__option[aria-selected]'); 47347 47348 var $selected = $options.filter('[aria-selected=true]'); 47349 47350 // Check if there are any selected options 47351 if ($selected.length > 0) { 47352 // If there are selected options, highlight the first 47353 $selected.first().trigger('mouseenter'); 47354 } else { 47355 // If there are no selected options, highlight the first option 47356 // in the dropdown 47357 $options.first().trigger('mouseenter'); 47358 } 47359 47360 this.ensureHighlightVisible(); 47361 }; 47362 47363 Results.prototype.setClasses = function () { 47364 var self = this; 47365 47366 this.data.current(function (selected) { 47367 var selectedIds = $.map(selected, function (s) { 47368 return s.id.toString(); 47369 }); 47370 47371 var $options = self.$results 47372 .find('.select2-results__option[aria-selected]'); 47373 47374 $options.each(function () { 47375 var $option = $(this); 47376 47377 var item = Utils.GetData(this, 'data'); 47378 47379 // id needs to be converted to a string when comparing 47380 var id = '' + item.id; 47381 47382 if ((item.element != null && item.element.selected) || 47383 (item.element == null && $.inArray(id, selectedIds) > -1)) { 47384 $option.attr('aria-selected', 'true'); 47385 } else { 47386 $option.attr('aria-selected', 'false'); 47387 } 47388 }); 47389 47390 }); 47391 }; 47392 47393 Results.prototype.showLoading = function (params) { 47394 this.hideLoading(); 47395 47396 var loadingMore = this.options.get('translations').get('searching'); 47397 47398 var loading = { 47399 disabled: true, 47400 loading: true, 47401 text: loadingMore(params) 47402 }; 47403 var $loading = this.option(loading); 47404 $loading.className += ' loading-results'; 47405 47406 this.$results.prepend($loading); 47407 }; 47408 47409 Results.prototype.hideLoading = function () { 47410 this.$results.find('.loading-results').remove(); 47411 }; 47412 47413 Results.prototype.option = function (data) { 47414 var option = document.createElement('li'); 47415 option.className = 'select2-results__option'; 47416 47417 var attrs = { 47418 'role': 'option', 47419 'aria-selected': 'false' 47420 }; 47421 47422 var matches = window.Element.prototype.matches || 47423 window.Element.prototype.msMatchesSelector || 47424 window.Element.prototype.webkitMatchesSelector; 47425 47426 if ((data.element != null && matches.call(data.element, ':disabled')) || 47427 (data.element == null && data.disabled)) { 47428 delete attrs['aria-selected']; 47429 attrs['aria-disabled'] = 'true'; 47430 } 47431 47432 if (data.id == null) { 47433 delete attrs['aria-selected']; 47434 } 47435 47436 if (data._resultId != null) { 47437 option.id = data._resultId; 47438 } 47439 47440 if (data.title) { 47441 option.title = data.title; 47442 } 47443 47444 if (data.children) { 47445 attrs.role = 'group'; 47446 attrs['aria-label'] = data.text; 47447 delete attrs['aria-selected']; 47448 } 47449 47450 for (var attr in attrs) { 47451 var val = attrs[attr]; 47452 47453 option.setAttribute(attr, val); 47454 } 47455 47456 if (data.children) { 47457 var $option = $(option); 47458 47459 var label = document.createElement('strong'); 47460 label.className = 'select2-results__group'; 47461 47462 var $label = $(label); 47463 this.template(data, label); 47464 47465 var $children = []; 47466 47467 for (var c = 0; c < data.children.length; c++) { 47468 var child = data.children[c]; 47469 47470 var $child = this.option(child); 47471 47472 $children.push($child); 47473 } 47474 47475 var $childrenContainer = $('<ul></ul>', { 47476 'class': 'select2-results__options select2-results__options--nested' 47477 }); 47478 47479 $childrenContainer.append($children); 47480 47481 $option.append(label); 47482 $option.append($childrenContainer); 47483 } else { 47484 this.template(data, option); 47485 } 47486 47487 Utils.StoreData(option, 'data', data); 47488 47489 return option; 47490 }; 47491 47492 Results.prototype.bind = function (container, $container) { 47493 var self = this; 47494 47495 var id = container.id + '-results'; 47496 47497 this.$results.attr('id', id); 47498 47499 container.on('results:all', function (params) { 47500 self.clear(); 47501 self.append(params.data); 47502 47503 if (container.isOpen()) { 47504 self.setClasses(); 47505 self.highlightFirstItem(); 47506 } 47507 }); 47508 47509 container.on('results:append', function (params) { 47510 self.append(params.data); 47511 47512 if (container.isOpen()) { 47513 self.setClasses(); 47514 } 47515 }); 47516 47517 container.on('query', function (params) { 47518 self.hideMessages(); 47519 self.showLoading(params); 47520 }); 47521 47522 container.on('select', function () { 47523 if (!container.isOpen()) { 47524 return; 47525 } 47526 47527 self.setClasses(); 47528 47529 if (self.options.get('scrollAfterSelect')) { 47530 self.highlightFirstItem(); 47531 } 47532 }); 47533 47534 container.on('unselect', function () { 47535 if (!container.isOpen()) { 47536 return; 47537 } 47538 47539 self.setClasses(); 47540 47541 if (self.options.get('scrollAfterSelect')) { 47542 self.highlightFirstItem(); 47543 } 47544 }); 47545 47546 container.on('open', function () { 47547 // When the dropdown is open, aria-expended="true" 47548 self.$results.attr('aria-expanded', 'true'); 47549 self.$results.attr('aria-hidden', 'false'); 47550 47551 self.setClasses(); 47552 self.ensureHighlightVisible(); 47553 }); 47554 47555 container.on('close', function () { 47556 // When the dropdown is closed, aria-expended="false" 47557 self.$results.attr('aria-expanded', 'false'); 47558 self.$results.attr('aria-hidden', 'true'); 47559 self.$results.removeAttr('aria-activedescendant'); 47560 }); 47561 47562 container.on('results:toggle', function () { 47563 var $highlighted = self.getHighlightedResults(); 47564 47565 if ($highlighted.length === 0) { 47566 return; 47567 } 47568 47569 $highlighted.trigger('mouseup'); 47570 }); 47571 47572 container.on('results:select', function () { 47573 var $highlighted = self.getHighlightedResults(); 47574 47575 if ($highlighted.length === 0) { 47576 return; 47577 } 47578 47579 var data = Utils.GetData($highlighted[0], 'data'); 47580 47581 if ($highlighted.attr('aria-selected') == 'true') { 47582 self.trigger('close', {}); 47583 } else { 47584 self.trigger('select', { 47585 data: data 47586 }); 47587 } 47588 }); 47589 47590 container.on('results:previous', function () { 47591 var $highlighted = self.getHighlightedResults(); 47592 47593 var $options = self.$results.find('[aria-selected]'); 47594 47595 var currentIndex = $options.index($highlighted); 47596 47597 // If we are already at the top, don't move further 47598 // If no options, currentIndex will be -1 47599 if (currentIndex <= 0) { 47600 return; 47601 } 47602 47603 var nextIndex = currentIndex - 1; 47604 47605 // If none are highlighted, highlight the first 47606 if ($highlighted.length === 0) { 47607 nextIndex = 0; 47608 } 47609 47610 var $next = $options.eq(nextIndex); 47611 47612 $next.trigger('mouseenter'); 47613 47614 var currentOffset = self.$results.offset().top; 47615 var nextTop = $next.offset().top; 47616 var nextOffset = self.$results.scrollTop() + (nextTop - currentOffset); 47617 47618 if (nextIndex === 0) { 47619 self.$results.scrollTop(0); 47620 } else if (nextTop - currentOffset < 0) { 47621 self.$results.scrollTop(nextOffset); 47622 } 47623 }); 47624 47625 container.on('results:next', function () { 47626 var $highlighted = self.getHighlightedResults(); 47627 47628 var $options = self.$results.find('[aria-selected]'); 47629 47630 var currentIndex = $options.index($highlighted); 47631 47632 var nextIndex = currentIndex + 1; 47633 47634 // If we are at the last option, stay there 47635 if (nextIndex >= $options.length) { 47636 return; 47637 } 47638 47639 var $next = $options.eq(nextIndex); 47640 47641 $next.trigger('mouseenter'); 47642 47643 var currentOffset = self.$results.offset().top + 47644 self.$results.outerHeight(false); 47645 var nextBottom = $next.offset().top + $next.outerHeight(false); 47646 var nextOffset = self.$results.scrollTop() + nextBottom - currentOffset; 47647 47648 if (nextIndex === 0) { 47649 self.$results.scrollTop(0); 47650 } else if (nextBottom > currentOffset) { 47651 self.$results.scrollTop(nextOffset); 47652 } 47653 }); 47654 47655 container.on('results:focus', function (params) { 47656 params.element.addClass('select2-results__option--highlighted'); 47657 }); 47658 47659 container.on('results:message', function (params) { 47660 self.displayMessage(params); 47661 }); 47662 47663 if ($.fn.mousewheel) { 47664 this.$results.on('mousewheel', function (e) { 47665 var top = self.$results.scrollTop(); 47666 47667 var bottom = self.$results.get(0).scrollHeight - top + e.deltaY; 47668 47669 var isAtTop = e.deltaY > 0 && top - e.deltaY <= 0; 47670 var isAtBottom = e.deltaY < 0 && bottom <= self.$results.height(); 47671 47672 if (isAtTop) { 47673 self.$results.scrollTop(0); 47674 47675 e.preventDefault(); 47676 e.stopPropagation(); 47677 } else if (isAtBottom) { 47678 self.$results.scrollTop( 47679 self.$results.get(0).scrollHeight - self.$results.height() 47680 ); 47681 47682 e.preventDefault(); 47683 e.stopPropagation(); 47684 } 47685 }); 47686 } 47687 47688 this.$results.on('mouseup', '.select2-results__option[aria-selected]', 47689 function (evt) { 47690 var $this = $(this); 47691 47692 var data = Utils.GetData(this, 'data'); 47693 47694 if ($this.attr('aria-selected') === 'true') { 47695 if (self.options.get('multiple')) { 47696 self.trigger('unselect', { 47697 originalEvent: evt, 47698 data: data 47699 }); 47700 } else { 47701 self.trigger('close', {}); 47702 } 47703 47704 return; 47705 } 47706 47707 self.trigger('select', { 47708 originalEvent: evt, 47709 data: data 47710 }); 47711 }); 47712 47713 this.$results.on('mouseenter', '.select2-results__option[aria-selected]', 47714 function (evt) { 47715 var data = Utils.GetData(this, 'data'); 47716 47717 self.getHighlightedResults() 47718 .removeClass('select2-results__option--highlighted'); 47719 47720 self.trigger('results:focus', { 47721 data: data, 47722 element: $(this) 47723 }); 47724 }); 47725 }; 47726 47727 Results.prototype.getHighlightedResults = function () { 47728 var $highlighted = this.$results 47729 .find('.select2-results__option--highlighted'); 47730 47731 return $highlighted; 47732 }; 47733 47734 Results.prototype.destroy = function () { 47735 this.$results.remove(); 47736 }; 47737 47738 Results.prototype.ensureHighlightVisible = function () { 47739 var $highlighted = this.getHighlightedResults(); 47740 47741 if ($highlighted.length === 0) { 47742 return; 47743 } 47744 47745 var $options = this.$results.find('[aria-selected]'); 47746 47747 var currentIndex = $options.index($highlighted); 47748 47749 var currentOffset = this.$results.offset().top; 47750 var nextTop = $highlighted.offset().top; 47751 var nextOffset = this.$results.scrollTop() + (nextTop - currentOffset); 47752 47753 var offsetDelta = nextTop - currentOffset; 47754 nextOffset -= $highlighted.outerHeight(false) * 2; 47755 47756 if (currentIndex <= 2) { 47757 this.$results.scrollTop(0); 47758 } else if (offsetDelta > this.$results.outerHeight() || offsetDelta < 0) { 47759 this.$results.scrollTop(nextOffset); 47760 } 47761 }; 47762 47763 Results.prototype.template = function (result, container) { 47764 var template = this.options.get('templateResult'); 47765 var escapeMarkup = this.options.get('escapeMarkup'); 47766 47767 var content = template(result, container); 47768 47769 if (content == null) { 47770 container.style.display = 'none'; 47771 } else if (typeof content === 'string') { 47772 container.innerHTML = escapeMarkup(content); 47773 } else { 47774 $(container).append(content); 47775 } 47776 }; 47777 47778 return Results; 47779}); 47780 47781S2.define('select2/keys',[ 47782 47783], function () { 47784 var KEYS = { 47785 BACKSPACE: 8, 47786 TAB: 9, 47787 ENTER: 13, 47788 SHIFT: 16, 47789 CTRL: 17, 47790 ALT: 18, 47791 ESC: 27, 47792 SPACE: 32, 47793 PAGE_UP: 33, 47794 PAGE_DOWN: 34, 47795 END: 35, 47796 HOME: 36, 47797 LEFT: 37, 47798 UP: 38, 47799 RIGHT: 39, 47800 DOWN: 40, 47801 DELETE: 46 47802 }; 47803 47804 return KEYS; 47805}); 47806 47807S2.define('select2/selection/base',[ 47808 'jquery', 47809 '../utils', 47810 '../keys' 47811], function ($, Utils, KEYS) { 47812 function BaseSelection ($element, options) { 47813 this.$element = $element; 47814 this.options = options; 47815 47816 BaseSelection.__super__.constructor.call(this); 47817 } 47818 47819 Utils.Extend(BaseSelection, Utils.Observable); 47820 47821 BaseSelection.prototype.render = function () { 47822 var $selection = $( 47823 '<span class="select2-selection" role="combobox" ' + 47824 ' aria-haspopup="true" aria-expanded="false">' + 47825 '</span>' 47826 ); 47827 47828 this._tabindex = 0; 47829 47830 if (Utils.GetData(this.$element[0], 'old-tabindex') != null) { 47831 this._tabindex = Utils.GetData(this.$element[0], 'old-tabindex'); 47832 } else if (this.$element.attr('tabindex') != null) { 47833 this._tabindex = this.$element.attr('tabindex'); 47834 } 47835 47836 $selection.attr('title', this.$element.attr('title')); 47837 $selection.attr('tabindex', this._tabindex); 47838 $selection.attr('aria-disabled', 'false'); 47839 47840 this.$selection = $selection; 47841 47842 return $selection; 47843 }; 47844 47845 BaseSelection.prototype.bind = function (container, $container) { 47846 var self = this; 47847 47848 var resultsId = container.id + '-results'; 47849 47850 this.container = container; 47851 47852 this.$selection.on('focus', function (evt) { 47853 self.trigger('focus', evt); 47854 }); 47855 47856 this.$selection.on('blur', function (evt) { 47857 self._handleBlur(evt); 47858 }); 47859 47860 this.$selection.on('keydown', function (evt) { 47861 self.trigger('keypress', evt); 47862 47863 if (evt.which === KEYS.SPACE) { 47864 evt.preventDefault(); 47865 } 47866 }); 47867 47868 container.on('results:focus', function (params) { 47869 self.$selection.attr('aria-activedescendant', params.data._resultId); 47870 }); 47871 47872 container.on('selection:update', function (params) { 47873 self.update(params.data); 47874 }); 47875 47876 container.on('open', function () { 47877 // When the dropdown is open, aria-expanded="true" 47878 self.$selection.attr('aria-expanded', 'true'); 47879 self.$selection.attr('aria-owns', resultsId); 47880 47881 self._attachCloseHandler(container); 47882 }); 47883 47884 container.on('close', function () { 47885 // When the dropdown is closed, aria-expanded="false" 47886 self.$selection.attr('aria-expanded', 'false'); 47887 self.$selection.removeAttr('aria-activedescendant'); 47888 self.$selection.removeAttr('aria-owns'); 47889 47890 self.$selection.trigger('focus'); 47891 47892 self._detachCloseHandler(container); 47893 }); 47894 47895 container.on('enable', function () { 47896 self.$selection.attr('tabindex', self._tabindex); 47897 self.$selection.attr('aria-disabled', 'false'); 47898 }); 47899 47900 container.on('disable', function () { 47901 self.$selection.attr('tabindex', '-1'); 47902 self.$selection.attr('aria-disabled', 'true'); 47903 }); 47904 }; 47905 47906 BaseSelection.prototype._handleBlur = function (evt) { 47907 var self = this; 47908 47909 // This needs to be delayed as the active element is the body when the tab 47910 // key is pressed, possibly along with others. 47911 window.setTimeout(function () { 47912 // Don't trigger `blur` if the focus is still in the selection 47913 if ( 47914 (document.activeElement == self.$selection[0]) || 47915 ($.contains(self.$selection[0], document.activeElement)) 47916 ) { 47917 return; 47918 } 47919 47920 self.trigger('blur', evt); 47921 }, 1); 47922 }; 47923 47924 BaseSelection.prototype._attachCloseHandler = function (container) { 47925 47926 $(document.body).on('mousedown.select2.' + container.id, function (e) { 47927 var $target = $(e.target); 47928 47929 var $select = $target.closest('.select2'); 47930 47931 var $all = $('.select2.select2-container--open'); 47932 47933 $all.each(function () { 47934 if (this == $select[0]) { 47935 return; 47936 } 47937 47938 var $element = Utils.GetData(this, 'element'); 47939 47940 $element.select2('close'); 47941 }); 47942 }); 47943 }; 47944 47945 BaseSelection.prototype._detachCloseHandler = function (container) { 47946 $(document.body).off('mousedown.select2.' + container.id); 47947 }; 47948 47949 BaseSelection.prototype.position = function ($selection, $container) { 47950 var $selectionContainer = $container.find('.selection'); 47951 $selectionContainer.append($selection); 47952 }; 47953 47954 BaseSelection.prototype.destroy = function () { 47955 this._detachCloseHandler(this.container); 47956 }; 47957 47958 BaseSelection.prototype.update = function (data) { 47959 throw new Error('The `update` method must be defined in child classes.'); 47960 }; 47961 47962 /** 47963 * Helper method to abstract the "enabled" (not "disabled") state of this 47964 * object. 47965 * 47966 * @return {true} if the instance is not disabled. 47967 * @return {false} if the instance is disabled. 47968 */ 47969 BaseSelection.prototype.isEnabled = function () { 47970 return !this.isDisabled(); 47971 }; 47972 47973 /** 47974 * Helper method to abstract the "disabled" state of this object. 47975 * 47976 * @return {true} if the disabled option is true. 47977 * @return {false} if the disabled option is false. 47978 */ 47979 BaseSelection.prototype.isDisabled = function () { 47980 return this.options.get('disabled'); 47981 }; 47982 47983 return BaseSelection; 47984}); 47985 47986S2.define('select2/selection/single',[ 47987 'jquery', 47988 './base', 47989 '../utils', 47990 '../keys' 47991], function ($, BaseSelection, Utils, KEYS) { 47992 function SingleSelection () { 47993 SingleSelection.__super__.constructor.apply(this, arguments); 47994 } 47995 47996 Utils.Extend(SingleSelection, BaseSelection); 47997 47998 SingleSelection.prototype.render = function () { 47999 var $selection = SingleSelection.__super__.render.call(this); 48000 48001 $selection.addClass('select2-selection--single'); 48002 48003 $selection.html( 48004 '<span class="select2-selection__rendered"></span>' + 48005 '<span class="select2-selection__arrow" role="presentation">' + 48006 '<b role="presentation"></b>' + 48007 '</span>' 48008 ); 48009 48010 return $selection; 48011 }; 48012 48013 SingleSelection.prototype.bind = function (container, $container) { 48014 var self = this; 48015 48016 SingleSelection.__super__.bind.apply(this, arguments); 48017 48018 var id = container.id + '-container'; 48019 48020 this.$selection.find('.select2-selection__rendered') 48021 .attr('id', id) 48022 .attr('role', 'textbox') 48023 .attr('aria-readonly', 'true'); 48024 this.$selection.attr('aria-labelledby', id); 48025 48026 this.$selection.on('mousedown', function (evt) { 48027 // Only respond to left clicks 48028 if (evt.which !== 1) { 48029 return; 48030 } 48031 48032 self.trigger('toggle', { 48033 originalEvent: evt 48034 }); 48035 }); 48036 48037 this.$selection.on('focus', function (evt) { 48038 // User focuses on the container 48039 }); 48040 48041 this.$selection.on('blur', function (evt) { 48042 // User exits the container 48043 }); 48044 48045 container.on('focus', function (evt) { 48046 if (!container.isOpen()) { 48047 self.$selection.trigger('focus'); 48048 } 48049 }); 48050 }; 48051 48052 SingleSelection.prototype.clear = function () { 48053 var $rendered = this.$selection.find('.select2-selection__rendered'); 48054 $rendered.empty(); 48055 $rendered.removeAttr('title'); // clear tooltip on empty 48056 }; 48057 48058 SingleSelection.prototype.display = function (data, container) { 48059 var template = this.options.get('templateSelection'); 48060 var escapeMarkup = this.options.get('escapeMarkup'); 48061 48062 return escapeMarkup(template(data, container)); 48063 }; 48064 48065 SingleSelection.prototype.selectionContainer = function () { 48066 return $('<span></span>'); 48067 }; 48068 48069 SingleSelection.prototype.update = function (data) { 48070 if (data.length === 0) { 48071 this.clear(); 48072 return; 48073 } 48074 48075 var selection = data[0]; 48076 48077 var $rendered = this.$selection.find('.select2-selection__rendered'); 48078 var formatted = this.display(selection, $rendered); 48079 48080 $rendered.empty().append(formatted); 48081 48082 var title = selection.title || selection.text; 48083 48084 if (title) { 48085 $rendered.attr('title', title); 48086 } else { 48087 $rendered.removeAttr('title'); 48088 } 48089 }; 48090 48091 return SingleSelection; 48092}); 48093 48094S2.define('select2/selection/multiple',[ 48095 'jquery', 48096 './base', 48097 '../utils' 48098], function ($, BaseSelection, Utils) { 48099 function MultipleSelection ($element, options) { 48100 MultipleSelection.__super__.constructor.apply(this, arguments); 48101 } 48102 48103 Utils.Extend(MultipleSelection, BaseSelection); 48104 48105 MultipleSelection.prototype.render = function () { 48106 var $selection = MultipleSelection.__super__.render.call(this); 48107 48108 $selection.addClass('select2-selection--multiple'); 48109 48110 $selection.html( 48111 '<ul class="select2-selection__rendered"></ul>' 48112 ); 48113 48114 return $selection; 48115 }; 48116 48117 MultipleSelection.prototype.bind = function (container, $container) { 48118 var self = this; 48119 48120 MultipleSelection.__super__.bind.apply(this, arguments); 48121 48122 this.$selection.on('click', function (evt) { 48123 self.trigger('toggle', { 48124 originalEvent: evt 48125 }); 48126 }); 48127 48128 this.$selection.on( 48129 'click', 48130 '.select2-selection__choice__remove', 48131 function (evt) { 48132 // Ignore the event if it is disabled 48133 if (self.isDisabled()) { 48134 return; 48135 } 48136 48137 var $remove = $(this); 48138 var $selection = $remove.parent(); 48139 48140 var data = Utils.GetData($selection[0], 'data'); 48141 48142 self.trigger('unselect', { 48143 originalEvent: evt, 48144 data: data 48145 }); 48146 } 48147 ); 48148 }; 48149 48150 MultipleSelection.prototype.clear = function () { 48151 var $rendered = this.$selection.find('.select2-selection__rendered'); 48152 $rendered.empty(); 48153 $rendered.removeAttr('title'); 48154 }; 48155 48156 MultipleSelection.prototype.display = function (data, container) { 48157 var template = this.options.get('templateSelection'); 48158 var escapeMarkup = this.options.get('escapeMarkup'); 48159 48160 return escapeMarkup(template(data, container)); 48161 }; 48162 48163 MultipleSelection.prototype.selectionContainer = function () { 48164 var $container = $( 48165 '<li class="select2-selection__choice">' + 48166 '<span class="select2-selection__choice__remove" role="presentation">' + 48167 '×' + 48168 '</span>' + 48169 '</li>' 48170 ); 48171 48172 return $container; 48173 }; 48174 48175 MultipleSelection.prototype.update = function (data) { 48176 this.clear(); 48177 48178 if (data.length === 0) { 48179 return; 48180 } 48181 48182 var $selections = []; 48183 48184 for (var d = 0; d < data.length; d++) { 48185 var selection = data[d]; 48186 48187 var $selection = this.selectionContainer(); 48188 var formatted = this.display(selection, $selection); 48189 48190 $selection.append(formatted); 48191 48192 var title = selection.title || selection.text; 48193 48194 if (title) { 48195 $selection.attr('title', title); 48196 } 48197 48198 Utils.StoreData($selection[0], 'data', selection); 48199 48200 $selections.push($selection); 48201 } 48202 48203 var $rendered = this.$selection.find('.select2-selection__rendered'); 48204 48205 Utils.appendMany($rendered, $selections); 48206 }; 48207 48208 return MultipleSelection; 48209}); 48210 48211S2.define('select2/selection/placeholder',[ 48212 '../utils' 48213], function (Utils) { 48214 function Placeholder (decorated, $element, options) { 48215 this.placeholder = this.normalizePlaceholder(options.get('placeholder')); 48216 48217 decorated.call(this, $element, options); 48218 } 48219 48220 Placeholder.prototype.normalizePlaceholder = function (_, placeholder) { 48221 if (typeof placeholder === 'string') { 48222 placeholder = { 48223 id: '', 48224 text: placeholder 48225 }; 48226 } 48227 48228 return placeholder; 48229 }; 48230 48231 Placeholder.prototype.createPlaceholder = function (decorated, placeholder) { 48232 var $placeholder = this.selectionContainer(); 48233 48234 $placeholder.html(this.display(placeholder)); 48235 $placeholder.addClass('select2-selection__placeholder') 48236 .removeClass('select2-selection__choice'); 48237 48238 return $placeholder; 48239 }; 48240 48241 Placeholder.prototype.update = function (decorated, data) { 48242 var singlePlaceholder = ( 48243 data.length == 1 && data[0].id != this.placeholder.id 48244 ); 48245 var multipleSelections = data.length > 1; 48246 48247 if (multipleSelections || singlePlaceholder) { 48248 return decorated.call(this, data); 48249 } 48250 48251 this.clear(); 48252 48253 var $placeholder = this.createPlaceholder(this.placeholder); 48254 48255 this.$selection.find('.select2-selection__rendered').append($placeholder); 48256 }; 48257 48258 return Placeholder; 48259}); 48260 48261S2.define('select2/selection/allowClear',[ 48262 'jquery', 48263 '../keys', 48264 '../utils' 48265], function ($, KEYS, Utils) { 48266 function AllowClear () { } 48267 48268 AllowClear.prototype.bind = function (decorated, container, $container) { 48269 var self = this; 48270 48271 decorated.call(this, container, $container); 48272 48273 if (this.placeholder == null) { 48274 if (this.options.get('debug') && window.console && console.error) { 48275 console.error( 48276 'Select2: The `allowClear` option should be used in combination ' + 48277 'with the `placeholder` option.' 48278 ); 48279 } 48280 } 48281 48282 this.$selection.on('mousedown', '.select2-selection__clear', 48283 function (evt) { 48284 self._handleClear(evt); 48285 }); 48286 48287 container.on('keypress', function (evt) { 48288 self._handleKeyboardClear(evt, container); 48289 }); 48290 }; 48291 48292 AllowClear.prototype._handleClear = function (_, evt) { 48293 // Ignore the event if it is disabled 48294 if (this.isDisabled()) { 48295 return; 48296 } 48297 48298 var $clear = this.$selection.find('.select2-selection__clear'); 48299 48300 // Ignore the event if nothing has been selected 48301 if ($clear.length === 0) { 48302 return; 48303 } 48304 48305 evt.stopPropagation(); 48306 48307 var data = Utils.GetData($clear[0], 'data'); 48308 48309 var previousVal = this.$element.val(); 48310 this.$element.val(this.placeholder.id); 48311 48312 var unselectData = { 48313 data: data 48314 }; 48315 this.trigger('clear', unselectData); 48316 if (unselectData.prevented) { 48317 this.$element.val(previousVal); 48318 return; 48319 } 48320 48321 for (var d = 0; d < data.length; d++) { 48322 unselectData = { 48323 data: data[d] 48324 }; 48325 48326 // Trigger the `unselect` event, so people can prevent it from being 48327 // cleared. 48328 this.trigger('unselect', unselectData); 48329 48330 // If the event was prevented, don't clear it out. 48331 if (unselectData.prevented) { 48332 this.$element.val(previousVal); 48333 return; 48334 } 48335 } 48336 48337 this.$element.trigger('input').trigger('change'); 48338 48339 this.trigger('toggle', {}); 48340 }; 48341 48342 AllowClear.prototype._handleKeyboardClear = function (_, evt, container) { 48343 if (container.isOpen()) { 48344 return; 48345 } 48346 48347 if (evt.which == KEYS.DELETE || evt.which == KEYS.BACKSPACE) { 48348 this._handleClear(evt); 48349 } 48350 }; 48351 48352 AllowClear.prototype.update = function (decorated, data) { 48353 decorated.call(this, data); 48354 48355 if (this.$selection.find('.select2-selection__placeholder').length > 0 || 48356 data.length === 0) { 48357 return; 48358 } 48359 48360 var removeAll = this.options.get('translations').get('removeAllItems'); 48361 48362 var $remove = $( 48363 '<span class="select2-selection__clear" title="' + removeAll() +'">' + 48364 '×' + 48365 '</span>' 48366 ); 48367 Utils.StoreData($remove[0], 'data', data); 48368 48369 this.$selection.find('.select2-selection__rendered').prepend($remove); 48370 }; 48371 48372 return AllowClear; 48373}); 48374 48375S2.define('select2/selection/search',[ 48376 'jquery', 48377 '../utils', 48378 '../keys' 48379], function ($, Utils, KEYS) { 48380 function Search (decorated, $element, options) { 48381 decorated.call(this, $element, options); 48382 } 48383 48384 Search.prototype.render = function (decorated) { 48385 var $search = $( 48386 '<li class="select2-search select2-search--inline">' + 48387 '<input class="select2-search__field" type="search" tabindex="-1"' + 48388 ' autocomplete="off" autocorrect="off" autocapitalize="none"' + 48389 ' spellcheck="false" role="searchbox" aria-autocomplete="list" />' + 48390 '</li>' 48391 ); 48392 48393 this.$searchContainer = $search; 48394 this.$search = $search.find('input'); 48395 48396 var $rendered = decorated.call(this); 48397 48398 this._transferTabIndex(); 48399 48400 return $rendered; 48401 }; 48402 48403 Search.prototype.bind = function (decorated, container, $container) { 48404 var self = this; 48405 48406 var resultsId = container.id + '-results'; 48407 48408 decorated.call(this, container, $container); 48409 48410 container.on('open', function () { 48411 self.$search.attr('aria-controls', resultsId); 48412 self.$search.trigger('focus'); 48413 }); 48414 48415 container.on('close', function () { 48416 self.$search.val(''); 48417 self.$search.removeAttr('aria-controls'); 48418 self.$search.removeAttr('aria-activedescendant'); 48419 self.$search.trigger('focus'); 48420 }); 48421 48422 container.on('enable', function () { 48423 self.$search.prop('disabled', false); 48424 48425 self._transferTabIndex(); 48426 }); 48427 48428 container.on('disable', function () { 48429 self.$search.prop('disabled', true); 48430 }); 48431 48432 container.on('focus', function (evt) { 48433 self.$search.trigger('focus'); 48434 }); 48435 48436 container.on('results:focus', function (params) { 48437 if (params.data._resultId) { 48438 self.$search.attr('aria-activedescendant', params.data._resultId); 48439 } else { 48440 self.$search.removeAttr('aria-activedescendant'); 48441 } 48442 }); 48443 48444 this.$selection.on('focusin', '.select2-search--inline', function (evt) { 48445 self.trigger('focus', evt); 48446 }); 48447 48448 this.$selection.on('focusout', '.select2-search--inline', function (evt) { 48449 self._handleBlur(evt); 48450 }); 48451 48452 this.$selection.on('keydown', '.select2-search--inline', function (evt) { 48453 evt.stopPropagation(); 48454 48455 self.trigger('keypress', evt); 48456 48457 self._keyUpPrevented = evt.isDefaultPrevented(); 48458 48459 var key = evt.which; 48460 48461 if (key === KEYS.BACKSPACE && self.$search.val() === '') { 48462 var $previousChoice = self.$searchContainer 48463 .prev('.select2-selection__choice'); 48464 48465 if ($previousChoice.length > 0) { 48466 var item = Utils.GetData($previousChoice[0], 'data'); 48467 48468 self.searchRemoveChoice(item); 48469 48470 evt.preventDefault(); 48471 } 48472 } 48473 }); 48474 48475 this.$selection.on('click', '.select2-search--inline', function (evt) { 48476 if (self.$search.val()) { 48477 evt.stopPropagation(); 48478 } 48479 }); 48480 48481 // Try to detect the IE version should the `documentMode` property that 48482 // is stored on the document. This is only implemented in IE and is 48483 // slightly cleaner than doing a user agent check. 48484 // This property is not available in Edge, but Edge also doesn't have 48485 // this bug. 48486 var msie = document.documentMode; 48487 var disableInputEvents = msie && msie <= 11; 48488 48489 // Workaround for browsers which do not support the `input` event 48490 // This will prevent double-triggering of events for browsers which support 48491 // both the `keyup` and `input` events. 48492 this.$selection.on( 48493 'input.searchcheck', 48494 '.select2-search--inline', 48495 function (evt) { 48496 // IE will trigger the `input` event when a placeholder is used on a 48497 // search box. To get around this issue, we are forced to ignore all 48498 // `input` events in IE and keep using `keyup`. 48499 if (disableInputEvents) { 48500 self.$selection.off('input.search input.searchcheck'); 48501 return; 48502 } 48503 48504 // Unbind the duplicated `keyup` event 48505 self.$selection.off('keyup.search'); 48506 } 48507 ); 48508 48509 this.$selection.on( 48510 'keyup.search input.search', 48511 '.select2-search--inline', 48512 function (evt) { 48513 // IE will trigger the `input` event when a placeholder is used on a 48514 // search box. To get around this issue, we are forced to ignore all 48515 // `input` events in IE and keep using `keyup`. 48516 if (disableInputEvents && evt.type === 'input') { 48517 self.$selection.off('input.search input.searchcheck'); 48518 return; 48519 } 48520 48521 var key = evt.which; 48522 48523 // We can freely ignore events from modifier keys 48524 if (key == KEYS.SHIFT || key == KEYS.CTRL || key == KEYS.ALT) { 48525 return; 48526 } 48527 48528 // Tabbing will be handled during the `keydown` phase 48529 if (key == KEYS.TAB) { 48530 return; 48531 } 48532 48533 self.handleSearch(evt); 48534 } 48535 ); 48536 }; 48537 48538 /** 48539 * This method will transfer the tabindex attribute from the rendered 48540 * selection to the search box. This allows for the search box to be used as 48541 * the primary focus instead of the selection container. 48542 * 48543 * @private 48544 */ 48545 Search.prototype._transferTabIndex = function (decorated) { 48546 this.$search.attr('tabindex', this.$selection.attr('tabindex')); 48547 this.$selection.attr('tabindex', '-1'); 48548 }; 48549 48550 Search.prototype.createPlaceholder = function (decorated, placeholder) { 48551 this.$search.attr('placeholder', placeholder.text); 48552 }; 48553 48554 Search.prototype.update = function (decorated, data) { 48555 var searchHadFocus = this.$search[0] == document.activeElement; 48556 48557 this.$search.attr('placeholder', ''); 48558 48559 decorated.call(this, data); 48560 48561 this.$selection.find('.select2-selection__rendered') 48562 .append(this.$searchContainer); 48563 48564 this.resizeSearch(); 48565 if (searchHadFocus) { 48566 this.$search.trigger('focus'); 48567 } 48568 }; 48569 48570 Search.prototype.handleSearch = function () { 48571 this.resizeSearch(); 48572 48573 if (!this._keyUpPrevented) { 48574 var input = this.$search.val(); 48575 48576 this.trigger('query', { 48577 term: input 48578 }); 48579 } 48580 48581 this._keyUpPrevented = false; 48582 }; 48583 48584 Search.prototype.searchRemoveChoice = function (decorated, item) { 48585 this.trigger('unselect', { 48586 data: item 48587 }); 48588 48589 this.$search.val(item.text); 48590 this.handleSearch(); 48591 }; 48592 48593 Search.prototype.resizeSearch = function () { 48594 this.$search.css('width', '25px'); 48595 48596 var width = ''; 48597 48598 if (this.$search.attr('placeholder') !== '') { 48599 width = this.$selection.find('.select2-selection__rendered').width(); 48600 } else { 48601 var minimumWidth = this.$search.val().length + 1; 48602 48603 width = (minimumWidth * 0.75) + 'em'; 48604 } 48605 48606 this.$search.css('width', width); 48607 }; 48608 48609 return Search; 48610}); 48611 48612S2.define('select2/selection/eventRelay',[ 48613 'jquery' 48614], function ($) { 48615 function EventRelay () { } 48616 48617 EventRelay.prototype.bind = function (decorated, container, $container) { 48618 var self = this; 48619 var relayEvents = [ 48620 'open', 'opening', 48621 'close', 'closing', 48622 'select', 'selecting', 48623 'unselect', 'unselecting', 48624 'clear', 'clearing' 48625 ]; 48626 48627 var preventableEvents = [ 48628 'opening', 'closing', 'selecting', 'unselecting', 'clearing' 48629 ]; 48630 48631 decorated.call(this, container, $container); 48632 48633 container.on('*', function (name, params) { 48634 // Ignore events that should not be relayed 48635 if ($.inArray(name, relayEvents) === -1) { 48636 return; 48637 } 48638 48639 // The parameters should always be an object 48640 params = params || {}; 48641 48642 // Generate the jQuery event for the Select2 event 48643 var evt = $.Event('select2:' + name, { 48644 params: params 48645 }); 48646 48647 self.$element.trigger(evt); 48648 48649 // Only handle preventable events if it was one 48650 if ($.inArray(name, preventableEvents) === -1) { 48651 return; 48652 } 48653 48654 params.prevented = evt.isDefaultPrevented(); 48655 }); 48656 }; 48657 48658 return EventRelay; 48659}); 48660 48661S2.define('select2/translation',[ 48662 'jquery', 48663 'require' 48664], function ($, require) { 48665 function Translation (dict) { 48666 this.dict = dict || {}; 48667 } 48668 48669 Translation.prototype.all = function () { 48670 return this.dict; 48671 }; 48672 48673 Translation.prototype.get = function (key) { 48674 return this.dict[key]; 48675 }; 48676 48677 Translation.prototype.extend = function (translation) { 48678 this.dict = $.extend({}, translation.all(), this.dict); 48679 }; 48680 48681 // Static functions 48682 48683 Translation._cache = {}; 48684 48685 Translation.loadPath = function (path) { 48686 if (!(path in Translation._cache)) { 48687 var translations = require(path); 48688 48689 Translation._cache[path] = translations; 48690 } 48691 48692 return new Translation(Translation._cache[path]); 48693 }; 48694 48695 return Translation; 48696}); 48697 48698S2.define('select2/diacritics',[ 48699 48700], function () { 48701 var diacritics = { 48702 '\u24B6': 'A', 48703 '\uFF21': 'A', 48704 '\u00C0': 'A', 48705 '\u00C1': 'A', 48706 '\u00C2': 'A', 48707 '\u1EA6': 'A', 48708 '\u1EA4': 'A', 48709 '\u1EAA': 'A', 48710 '\u1EA8': 'A', 48711 '\u00C3': 'A', 48712 '\u0100': 'A', 48713 '\u0102': 'A', 48714 '\u1EB0': 'A', 48715 '\u1EAE': 'A', 48716 '\u1EB4': 'A', 48717 '\u1EB2': 'A', 48718 '\u0226': 'A', 48719 '\u01E0': 'A', 48720 '\u00C4': 'A', 48721 '\u01DE': 'A', 48722 '\u1EA2': 'A', 48723 '\u00C5': 'A', 48724 '\u01FA': 'A', 48725 '\u01CD': 'A', 48726 '\u0200': 'A', 48727 '\u0202': 'A', 48728 '\u1EA0': 'A', 48729 '\u1EAC': 'A', 48730 '\u1EB6': 'A', 48731 '\u1E00': 'A', 48732 '\u0104': 'A', 48733 '\u023A': 'A', 48734 '\u2C6F': 'A', 48735 '\uA732': 'AA', 48736 '\u00C6': 'AE', 48737 '\u01FC': 'AE', 48738 '\u01E2': 'AE', 48739 '\uA734': 'AO', 48740 '\uA736': 'AU', 48741 '\uA738': 'AV', 48742 '\uA73A': 'AV', 48743 '\uA73C': 'AY', 48744 '\u24B7': 'B', 48745 '\uFF22': 'B', 48746 '\u1E02': 'B', 48747 '\u1E04': 'B', 48748 '\u1E06': 'B', 48749 '\u0243': 'B', 48750 '\u0182': 'B', 48751 '\u0181': 'B', 48752 '\u24B8': 'C', 48753 '\uFF23': 'C', 48754 '\u0106': 'C', 48755 '\u0108': 'C', 48756 '\u010A': 'C', 48757 '\u010C': 'C', 48758 '\u00C7': 'C', 48759 '\u1E08': 'C', 48760 '\u0187': 'C', 48761 '\u023B': 'C', 48762 '\uA73E': 'C', 48763 '\u24B9': 'D', 48764 '\uFF24': 'D', 48765 '\u1E0A': 'D', 48766 '\u010E': 'D', 48767 '\u1E0C': 'D', 48768 '\u1E10': 'D', 48769 '\u1E12': 'D', 48770 '\u1E0E': 'D', 48771 '\u0110': 'D', 48772 '\u018B': 'D', 48773 '\u018A': 'D', 48774 '\u0189': 'D', 48775 '\uA779': 'D', 48776 '\u01F1': 'DZ', 48777 '\u01C4': 'DZ', 48778 '\u01F2': 'Dz', 48779 '\u01C5': 'Dz', 48780 '\u24BA': 'E', 48781 '\uFF25': 'E', 48782 '\u00C8': 'E', 48783 '\u00C9': 'E', 48784 '\u00CA': 'E', 48785 '\u1EC0': 'E', 48786 '\u1EBE': 'E', 48787 '\u1EC4': 'E', 48788 '\u1EC2': 'E', 48789 '\u1EBC': 'E', 48790 '\u0112': 'E', 48791 '\u1E14': 'E', 48792 '\u1E16': 'E', 48793 '\u0114': 'E', 48794 '\u0116': 'E', 48795 '\u00CB': 'E', 48796 '\u1EBA': 'E', 48797 '\u011A': 'E', 48798 '\u0204': 'E', 48799 '\u0206': 'E', 48800 '\u1EB8': 'E', 48801 '\u1EC6': 'E', 48802 '\u0228': 'E', 48803 '\u1E1C': 'E', 48804 '\u0118': 'E', 48805 '\u1E18': 'E', 48806 '\u1E1A': 'E', 48807 '\u0190': 'E', 48808 '\u018E': 'E', 48809 '\u24BB': 'F', 48810 '\uFF26': 'F', 48811 '\u1E1E': 'F', 48812 '\u0191': 'F', 48813 '\uA77B': 'F', 48814 '\u24BC': 'G', 48815 '\uFF27': 'G', 48816 '\u01F4': 'G', 48817 '\u011C': 'G', 48818 '\u1E20': 'G', 48819 '\u011E': 'G', 48820 '\u0120': 'G', 48821 '\u01E6': 'G', 48822 '\u0122': 'G', 48823 '\u01E4': 'G', 48824 '\u0193': 'G', 48825 '\uA7A0': 'G', 48826 '\uA77D': 'G', 48827 '\uA77E': 'G', 48828 '\u24BD': 'H', 48829 '\uFF28': 'H', 48830 '\u0124': 'H', 48831 '\u1E22': 'H', 48832 '\u1E26': 'H', 48833 '\u021E': 'H', 48834 '\u1E24': 'H', 48835 '\u1E28': 'H', 48836 '\u1E2A': 'H', 48837 '\u0126': 'H', 48838 '\u2C67': 'H', 48839 '\u2C75': 'H', 48840 '\uA78D': 'H', 48841 '\u24BE': 'I', 48842 '\uFF29': 'I', 48843 '\u00CC': 'I', 48844 '\u00CD': 'I', 48845 '\u00CE': 'I', 48846 '\u0128': 'I', 48847 '\u012A': 'I', 48848 '\u012C': 'I', 48849 '\u0130': 'I', 48850 '\u00CF': 'I', 48851 '\u1E2E': 'I', 48852 '\u1EC8': 'I', 48853 '\u01CF': 'I', 48854 '\u0208': 'I', 48855 '\u020A': 'I', 48856 '\u1ECA': 'I', 48857 '\u012E': 'I', 48858 '\u1E2C': 'I', 48859 '\u0197': 'I', 48860 '\u24BF': 'J', 48861 '\uFF2A': 'J', 48862 '\u0134': 'J', 48863 '\u0248': 'J', 48864 '\u24C0': 'K', 48865 '\uFF2B': 'K', 48866 '\u1E30': 'K', 48867 '\u01E8': 'K', 48868 '\u1E32': 'K', 48869 '\u0136': 'K', 48870 '\u1E34': 'K', 48871 '\u0198': 'K', 48872 '\u2C69': 'K', 48873 '\uA740': 'K', 48874 '\uA742': 'K', 48875 '\uA744': 'K', 48876 '\uA7A2': 'K', 48877 '\u24C1': 'L', 48878 '\uFF2C': 'L', 48879 '\u013F': 'L', 48880 '\u0139': 'L', 48881 '\u013D': 'L', 48882 '\u1E36': 'L', 48883 '\u1E38': 'L', 48884 '\u013B': 'L', 48885 '\u1E3C': 'L', 48886 '\u1E3A': 'L', 48887 '\u0141': 'L', 48888 '\u023D': 'L', 48889 '\u2C62': 'L', 48890 '\u2C60': 'L', 48891 '\uA748': 'L', 48892 '\uA746': 'L', 48893 '\uA780': 'L', 48894 '\u01C7': 'LJ', 48895 '\u01C8': 'Lj', 48896 '\u24C2': 'M', 48897 '\uFF2D': 'M', 48898 '\u1E3E': 'M', 48899 '\u1E40': 'M', 48900 '\u1E42': 'M', 48901 '\u2C6E': 'M', 48902 '\u019C': 'M', 48903 '\u24C3': 'N', 48904 '\uFF2E': 'N', 48905 '\u01F8': 'N', 48906 '\u0143': 'N', 48907 '\u00D1': 'N', 48908 '\u1E44': 'N', 48909 '\u0147': 'N', 48910 '\u1E46': 'N', 48911 '\u0145': 'N', 48912 '\u1E4A': 'N', 48913 '\u1E48': 'N', 48914 '\u0220': 'N', 48915 '\u019D': 'N', 48916 '\uA790': 'N', 48917 '\uA7A4': 'N', 48918 '\u01CA': 'NJ', 48919 '\u01CB': 'Nj', 48920 '\u24C4': 'O', 48921 '\uFF2F': 'O', 48922 '\u00D2': 'O', 48923 '\u00D3': 'O', 48924 '\u00D4': 'O', 48925 '\u1ED2': 'O', 48926 '\u1ED0': 'O', 48927 '\u1ED6': 'O', 48928 '\u1ED4': 'O', 48929 '\u00D5': 'O', 48930 '\u1E4C': 'O', 48931 '\u022C': 'O', 48932 '\u1E4E': 'O', 48933 '\u014C': 'O', 48934 '\u1E50': 'O', 48935 '\u1E52': 'O', 48936 '\u014E': 'O', 48937 '\u022E': 'O', 48938 '\u0230': 'O', 48939 '\u00D6': 'O', 48940 '\u022A': 'O', 48941 '\u1ECE': 'O', 48942 '\u0150': 'O', 48943 '\u01D1': 'O', 48944 '\u020C': 'O', 48945 '\u020E': 'O', 48946 '\u01A0': 'O', 48947 '\u1EDC': 'O', 48948 '\u1EDA': 'O', 48949 '\u1EE0': 'O', 48950 '\u1EDE': 'O', 48951 '\u1EE2': 'O', 48952 '\u1ECC': 'O', 48953 '\u1ED8': 'O', 48954 '\u01EA': 'O', 48955 '\u01EC': 'O', 48956 '\u00D8': 'O', 48957 '\u01FE': 'O', 48958 '\u0186': 'O', 48959 '\u019F': 'O', 48960 '\uA74A': 'O', 48961 '\uA74C': 'O', 48962 '\u0152': 'OE', 48963 '\u01A2': 'OI', 48964 '\uA74E': 'OO', 48965 '\u0222': 'OU', 48966 '\u24C5': 'P', 48967 '\uFF30': 'P', 48968 '\u1E54': 'P', 48969 '\u1E56': 'P', 48970 '\u01A4': 'P', 48971 '\u2C63': 'P', 48972 '\uA750': 'P', 48973 '\uA752': 'P', 48974 '\uA754': 'P', 48975 '\u24C6': 'Q', 48976 '\uFF31': 'Q', 48977 '\uA756': 'Q', 48978 '\uA758': 'Q', 48979 '\u024A': 'Q', 48980 '\u24C7': 'R', 48981 '\uFF32': 'R', 48982 '\u0154': 'R', 48983 '\u1E58': 'R', 48984 '\u0158': 'R', 48985 '\u0210': 'R', 48986 '\u0212': 'R', 48987 '\u1E5A': 'R', 48988 '\u1E5C': 'R', 48989 '\u0156': 'R', 48990 '\u1E5E': 'R', 48991 '\u024C': 'R', 48992 '\u2C64': 'R', 48993 '\uA75A': 'R', 48994 '\uA7A6': 'R', 48995 '\uA782': 'R', 48996 '\u24C8': 'S', 48997 '\uFF33': 'S', 48998 '\u1E9E': 'S', 48999 '\u015A': 'S', 49000 '\u1E64': 'S', 49001 '\u015C': 'S', 49002 '\u1E60': 'S', 49003 '\u0160': 'S', 49004 '\u1E66': 'S', 49005 '\u1E62': 'S', 49006 '\u1E68': 'S', 49007 '\u0218': 'S', 49008 '\u015E': 'S', 49009 '\u2C7E': 'S', 49010 '\uA7A8': 'S', 49011 '\uA784': 'S', 49012 '\u24C9': 'T', 49013 '\uFF34': 'T', 49014 '\u1E6A': 'T', 49015 '\u0164': 'T', 49016 '\u1E6C': 'T', 49017 '\u021A': 'T', 49018 '\u0162': 'T', 49019 '\u1E70': 'T', 49020 '\u1E6E': 'T', 49021 '\u0166': 'T', 49022 '\u01AC': 'T', 49023 '\u01AE': 'T', 49024 '\u023E': 'T', 49025 '\uA786': 'T', 49026 '\uA728': 'TZ', 49027 '\u24CA': 'U', 49028 '\uFF35': 'U', 49029 '\u00D9': 'U', 49030 '\u00DA': 'U', 49031 '\u00DB': 'U', 49032 '\u0168': 'U', 49033 '\u1E78': 'U', 49034 '\u016A': 'U', 49035 '\u1E7A': 'U', 49036 '\u016C': 'U', 49037 '\u00DC': 'U', 49038 '\u01DB': 'U', 49039 '\u01D7': 'U', 49040 '\u01D5': 'U', 49041 '\u01D9': 'U', 49042 '\u1EE6': 'U', 49043 '\u016E': 'U', 49044 '\u0170': 'U', 49045 '\u01D3': 'U', 49046 '\u0214': 'U', 49047 '\u0216': 'U', 49048 '\u01AF': 'U', 49049 '\u1EEA': 'U', 49050 '\u1EE8': 'U', 49051 '\u1EEE': 'U', 49052 '\u1EEC': 'U', 49053 '\u1EF0': 'U', 49054 '\u1EE4': 'U', 49055 '\u1E72': 'U', 49056 '\u0172': 'U', 49057 '\u1E76': 'U', 49058 '\u1E74': 'U', 49059 '\u0244': 'U', 49060 '\u24CB': 'V', 49061 '\uFF36': 'V', 49062 '\u1E7C': 'V', 49063 '\u1E7E': 'V', 49064 '\u01B2': 'V', 49065 '\uA75E': 'V', 49066 '\u0245': 'V', 49067 '\uA760': 'VY', 49068 '\u24CC': 'W', 49069 '\uFF37': 'W', 49070 '\u1E80': 'W', 49071 '\u1E82': 'W', 49072 '\u0174': 'W', 49073 '\u1E86': 'W', 49074 '\u1E84': 'W', 49075 '\u1E88': 'W', 49076 '\u2C72': 'W', 49077 '\u24CD': 'X', 49078 '\uFF38': 'X', 49079 '\u1E8A': 'X', 49080 '\u1E8C': 'X', 49081 '\u24CE': 'Y', 49082 '\uFF39': 'Y', 49083 '\u1EF2': 'Y', 49084 '\u00DD': 'Y', 49085 '\u0176': 'Y', 49086 '\u1EF8': 'Y', 49087 '\u0232': 'Y', 49088 '\u1E8E': 'Y', 49089 '\u0178': 'Y', 49090 '\u1EF6': 'Y', 49091 '\u1EF4': 'Y', 49092 '\u01B3': 'Y', 49093 '\u024E': 'Y', 49094 '\u1EFE': 'Y', 49095 '\u24CF': 'Z', 49096 '\uFF3A': 'Z', 49097 '\u0179': 'Z', 49098 '\u1E90': 'Z', 49099 '\u017B': 'Z', 49100 '\u017D': 'Z', 49101 '\u1E92': 'Z', 49102 '\u1E94': 'Z', 49103 '\u01B5': 'Z', 49104 '\u0224': 'Z', 49105 '\u2C7F': 'Z', 49106 '\u2C6B': 'Z', 49107 '\uA762': 'Z', 49108 '\u24D0': 'a', 49109 '\uFF41': 'a', 49110 '\u1E9A': 'a', 49111 '\u00E0': 'a', 49112 '\u00E1': 'a', 49113 '\u00E2': 'a', 49114 '\u1EA7': 'a', 49115 '\u1EA5': 'a', 49116 '\u1EAB': 'a', 49117 '\u1EA9': 'a', 49118 '\u00E3': 'a', 49119 '\u0101': 'a', 49120 '\u0103': 'a', 49121 '\u1EB1': 'a', 49122 '\u1EAF': 'a', 49123 '\u1EB5': 'a', 49124 '\u1EB3': 'a', 49125 '\u0227': 'a', 49126 '\u01E1': 'a', 49127 '\u00E4': 'a', 49128 '\u01DF': 'a', 49129 '\u1EA3': 'a', 49130 '\u00E5': 'a', 49131 '\u01FB': 'a', 49132 '\u01CE': 'a', 49133 '\u0201': 'a', 49134 '\u0203': 'a', 49135 '\u1EA1': 'a', 49136 '\u1EAD': 'a', 49137 '\u1EB7': 'a', 49138 '\u1E01': 'a', 49139 '\u0105': 'a', 49140 '\u2C65': 'a', 49141 '\u0250': 'a', 49142 '\uA733': 'aa', 49143 '\u00E6': 'ae', 49144 '\u01FD': 'ae', 49145 '\u01E3': 'ae', 49146 '\uA735': 'ao', 49147 '\uA737': 'au', 49148 '\uA739': 'av', 49149 '\uA73B': 'av', 49150 '\uA73D': 'ay', 49151 '\u24D1': 'b', 49152 '\uFF42': 'b', 49153 '\u1E03': 'b', 49154 '\u1E05': 'b', 49155 '\u1E07': 'b', 49156 '\u0180': 'b', 49157 '\u0183': 'b', 49158 '\u0253': 'b', 49159 '\u24D2': 'c', 49160 '\uFF43': 'c', 49161 '\u0107': 'c', 49162 '\u0109': 'c', 49163 '\u010B': 'c', 49164 '\u010D': 'c', 49165 '\u00E7': 'c', 49166 '\u1E09': 'c', 49167 '\u0188': 'c', 49168 '\u023C': 'c', 49169 '\uA73F': 'c', 49170 '\u2184': 'c', 49171 '\u24D3': 'd', 49172 '\uFF44': 'd', 49173 '\u1E0B': 'd', 49174 '\u010F': 'd', 49175 '\u1E0D': 'd', 49176 '\u1E11': 'd', 49177 '\u1E13': 'd', 49178 '\u1E0F': 'd', 49179 '\u0111': 'd', 49180 '\u018C': 'd', 49181 '\u0256': 'd', 49182 '\u0257': 'd', 49183 '\uA77A': 'd', 49184 '\u01F3': 'dz', 49185 '\u01C6': 'dz', 49186 '\u24D4': 'e', 49187 '\uFF45': 'e', 49188 '\u00E8': 'e', 49189 '\u00E9': 'e', 49190 '\u00EA': 'e', 49191 '\u1EC1': 'e', 49192 '\u1EBF': 'e', 49193 '\u1EC5': 'e', 49194 '\u1EC3': 'e', 49195 '\u1EBD': 'e', 49196 '\u0113': 'e', 49197 '\u1E15': 'e', 49198 '\u1E17': 'e', 49199 '\u0115': 'e', 49200 '\u0117': 'e', 49201 '\u00EB': 'e', 49202 '\u1EBB': 'e', 49203 '\u011B': 'e', 49204 '\u0205': 'e', 49205 '\u0207': 'e', 49206 '\u1EB9': 'e', 49207 '\u1EC7': 'e', 49208 '\u0229': 'e', 49209 '\u1E1D': 'e', 49210 '\u0119': 'e', 49211 '\u1E19': 'e', 49212 '\u1E1B': 'e', 49213 '\u0247': 'e', 49214 '\u025B': 'e', 49215 '\u01DD': 'e', 49216 '\u24D5': 'f', 49217 '\uFF46': 'f', 49218 '\u1E1F': 'f', 49219 '\u0192': 'f', 49220 '\uA77C': 'f', 49221 '\u24D6': 'g', 49222 '\uFF47': 'g', 49223 '\u01F5': 'g', 49224 '\u011D': 'g', 49225 '\u1E21': 'g', 49226 '\u011F': 'g', 49227 '\u0121': 'g', 49228 '\u01E7': 'g', 49229 '\u0123': 'g', 49230 '\u01E5': 'g', 49231 '\u0260': 'g', 49232 '\uA7A1': 'g', 49233 '\u1D79': 'g', 49234 '\uA77F': 'g', 49235 '\u24D7': 'h', 49236 '\uFF48': 'h', 49237 '\u0125': 'h', 49238 '\u1E23': 'h', 49239 '\u1E27': 'h', 49240 '\u021F': 'h', 49241 '\u1E25': 'h', 49242 '\u1E29': 'h', 49243 '\u1E2B': 'h', 49244 '\u1E96': 'h', 49245 '\u0127': 'h', 49246 '\u2C68': 'h', 49247 '\u2C76': 'h', 49248 '\u0265': 'h', 49249 '\u0195': 'hv', 49250 '\u24D8': 'i', 49251 '\uFF49': 'i', 49252 '\u00EC': 'i', 49253 '\u00ED': 'i', 49254 '\u00EE': 'i', 49255 '\u0129': 'i', 49256 '\u012B': 'i', 49257 '\u012D': 'i', 49258 '\u00EF': 'i', 49259 '\u1E2F': 'i', 49260 '\u1EC9': 'i', 49261 '\u01D0': 'i', 49262 '\u0209': 'i', 49263 '\u020B': 'i', 49264 '\u1ECB': 'i', 49265 '\u012F': 'i', 49266 '\u1E2D': 'i', 49267 '\u0268': 'i', 49268 '\u0131': 'i', 49269 '\u24D9': 'j', 49270 '\uFF4A': 'j', 49271 '\u0135': 'j', 49272 '\u01F0': 'j', 49273 '\u0249': 'j', 49274 '\u24DA': 'k', 49275 '\uFF4B': 'k', 49276 '\u1E31': 'k', 49277 '\u01E9': 'k', 49278 '\u1E33': 'k', 49279 '\u0137': 'k', 49280 '\u1E35': 'k', 49281 '\u0199': 'k', 49282 '\u2C6A': 'k', 49283 '\uA741': 'k', 49284 '\uA743': 'k', 49285 '\uA745': 'k', 49286 '\uA7A3': 'k', 49287 '\u24DB': 'l', 49288 '\uFF4C': 'l', 49289 '\u0140': 'l', 49290 '\u013A': 'l', 49291 '\u013E': 'l', 49292 '\u1E37': 'l', 49293 '\u1E39': 'l', 49294 '\u013C': 'l', 49295 '\u1E3D': 'l', 49296 '\u1E3B': 'l', 49297 '\u017F': 'l', 49298 '\u0142': 'l', 49299 '\u019A': 'l', 49300 '\u026B': 'l', 49301 '\u2C61': 'l', 49302 '\uA749': 'l', 49303 '\uA781': 'l', 49304 '\uA747': 'l', 49305 '\u01C9': 'lj', 49306 '\u24DC': 'm', 49307 '\uFF4D': 'm', 49308 '\u1E3F': 'm', 49309 '\u1E41': 'm', 49310 '\u1E43': 'm', 49311 '\u0271': 'm', 49312 '\u026F': 'm', 49313 '\u24DD': 'n', 49314 '\uFF4E': 'n',
49315 '\u01F9': 'n', 49316 '\u0144': 'n', 49317 '\u00F1': 'n', 49318 '\u1E45': 'n', 49319 '\u0148': 'n', 49320 '\u1E47': 'n', 49321 '\u0146': 'n', 49322 '\u1E4B': 'n', 49323 '\u1E49': 'n', 49324 '\u019E': 'n', 49325 '\u0272': 'n', 49326 '\u0149': 'n', 49327 '\uA791': 'n', 49328 '\uA7A5': 'n', 49329 '\u01CC': 'nj', 49330 '\u24DE': 'o', 49331 '\uFF4F': 'o', 49332 '\u00F2': 'o', 49333 '\u00F3': 'o', 49334 '\u00F4': 'o', 49335 '\u1ED3': 'o', 49336 '\u1ED1': 'o', 49337 '\u1ED7': 'o', 49338 '\u1ED5': 'o', 49339 '\u00F5': 'o', 49340 '\u1E4D': 'o', 49341 '\u022D': 'o', 49342 '\u1E4F': 'o', 49343 '\u014D': 'o', 49344 '\u1E51': 'o', 49345 '\u1E53': 'o', 49346 '\u014F': 'o', 49347 '\u022F': 'o', 49348 '\u0231': 'o', 49349 '\u00F6': 'o', 49350 '\u022B': 'o', 49351 '\u1ECF': 'o', 49352 '\u0151': 'o', 49353 '\u01D2': 'o', 49354 '\u020D': 'o', 49355 '\u020F': 'o', 49356 '\u01A1': 'o', 49357 '\u1EDD': 'o', 49358 '\u1EDB': 'o', 49359 '\u1EE1': 'o', 49360 '\u1EDF': 'o', 49361 '\u1EE3': 'o', 49362 '\u1ECD': 'o', 49363 '\u1ED9': 'o', 49364 '\u01EB': 'o', 49365 '\u01ED': 'o', 49366 '\u00F8': 'o', 49367 '\u01FF': 'o', 49368 '\u0254': 'o', 49369 '\uA74B': 'o', 49370 '\uA74D': 'o', 49371 '\u0275': 'o', 49372 '\u0153': 'oe', 49373 '\u01A3': 'oi', 49374 '\u0223': 'ou', 49375 '\uA74F': 'oo', 49376 '\u24DF': 'p', 49377 '\uFF50': 'p', 49378 '\u1E55': 'p', 49379 '\u1E57': 'p', 49380 '\u01A5': 'p', 49381 '\u1D7D': 'p', 49382 '\uA751': 'p', 49383 '\uA753': 'p', 49384 '\uA755': 'p', 49385 '\u24E0': 'q', 49386 '\uFF51': 'q', 49387 '\u024B': 'q', 49388 '\uA757': 'q', 49389 '\uA759': 'q', 49390 '\u24E1': 'r', 49391 '\uFF52': 'r', 49392 '\u0155': 'r', 49393 '\u1E59': 'r', 49394 '\u0159': 'r', 49395 '\u0211': 'r', 49396 '\u0213': 'r', 49397 '\u1E5B': 'r', 49398 '\u1E5D': 'r', 49399 '\u0157': 'r', 49400 '\u1E5F': 'r', 49401 '\u024D': 'r', 49402 '\u027D': 'r', 49403 '\uA75B': 'r', 49404 '\uA7A7': 'r', 49405 '\uA783': 'r', 49406 '\u24E2': 's', 49407 '\uFF53': 's', 49408 '\u00DF': 's', 49409 '\u015B': 's', 49410 '\u1E65': 's', 49411 '\u015D': 's', 49412 '\u1E61': 's', 49413 '\u0161': 's', 49414 '\u1E67': 's', 49415 '\u1E63': 's', 49416 '\u1E69': 's', 49417 '\u0219': 's', 49418 '\u015F': 's', 49419 '\u023F': 's', 49420 '\uA7A9': 's', 49421 '\uA785': 's', 49422 '\u1E9B': 's', 49423 '\u24E3': 't', 49424 '\uFF54': 't', 49425 '\u1E6B': 't', 49426 '\u1E97': 't', 49427 '\u0165': 't', 49428 '\u1E6D': 't', 49429 '\u021B': 't', 49430 '\u0163': 't', 49431 '\u1E71': 't', 49432 '\u1E6F': 't', 49433 '\u0167': 't', 49434 '\u01AD': 't', 49435 '\u0288': 't', 49436 '\u2C66': 't', 49437 '\uA787': 't', 49438 '\uA729': 'tz', 49439 '\u24E4': 'u', 49440 '\uFF55': 'u', 49441 '\u00F9': 'u', 49442 '\u00FA': 'u', 49443 '\u00FB': 'u', 49444 '\u0169': 'u', 49445 '\u1E79': 'u', 49446 '\u016B': 'u', 49447 '\u1E7B': 'u', 49448 '\u016D': 'u', 49449 '\u00FC': 'u', 49450 '\u01DC': 'u', 49451 '\u01D8': 'u', 49452 '\u01D6': 'u', 49453 '\u01DA': 'u', 49454 '\u1EE7': 'u', 49455 '\u016F': 'u', 49456 '\u0171': 'u', 49457 '\u01D4': 'u', 49458 '\u0215': 'u', 49459 '\u0217': 'u', 49460 '\u01B0': 'u', 49461 '\u1EEB': 'u', 49462 '\u1EE9': 'u', 49463 '\u1EEF': 'u', 49464 '\u1EED': 'u', 49465 '\u1EF1': 'u', 49466 '\u1EE5': 'u', 49467 '\u1E73': 'u', 49468 '\u0173': 'u', 49469 '\u1E77': 'u', 49470 '\u1E75': 'u', 49471 '\u0289': 'u', 49472 '\u24E5': 'v', 49473 '\uFF56': 'v', 49474 '\u1E7D': 'v', 49475 '\u1E7F': 'v', 49476 '\u028B': 'v', 49477 '\uA75F': 'v', 49478 '\u028C': 'v', 49479 '\uA761': 'vy', 49480 '\u24E6': 'w', 49481 '\uFF57': 'w', 49482 '\u1E81': 'w', 49483 '\u1E83': 'w', 49484 '\u0175': 'w', 49485 '\u1E87': 'w', 49486 '\u1E85': 'w', 49487 '\u1E98': 'w', 49488 '\u1E89': 'w', 49489 '\u2C73': 'w', 49490 '\u24E7': 'x', 49491 '\uFF58': 'x', 49492 '\u1E8B': 'x', 49493 '\u1E8D': 'x', 49494 '\u24E8': 'y', 49495 '\uFF59': 'y', 49496 '\u1EF3': 'y', 49497 '\u00FD': 'y', 49498 '\u0177': 'y', 49499 '\u1EF9': 'y', 49500 '\u0233': 'y', 49501 '\u1E8F': 'y', 49502 '\u00FF': 'y', 49503 '\u1EF7': 'y', 49504 '\u1E99': 'y', 49505 '\u1EF5': 'y', 49506 '\u01B4': 'y', 49507 '\u024F': 'y', 49508 '\u1EFF': 'y', 49509 '\u24E9': 'z', 49510 '\uFF5A': 'z', 49511 '\u017A': 'z', 49512 '\u1E91': 'z', 49513 '\u017C': 'z', 49514 '\u017E': 'z', 49515 '\u1E93': 'z', 49516 '\u1E95': 'z', 49517 '\u01B6': 'z', 49518 '\u0225': 'z', 49519 '\u0240': 'z', 49520 '\u2C6C': 'z', 49521 '\uA763': 'z', 49522 '\u0386': '\u0391', 49523 '\u0388': '\u0395', 49524 '\u0389': '\u0397', 49525 '\u038A': '\u0399', 49526 '\u03AA': '\u0399', 49527 '\u038C': '\u039F', 49528 '\u038E': '\u03A5', 49529 '\u03AB': '\u03A5', 49530 '\u038F': '\u03A9', 49531 '\u03AC': '\u03B1', 49532 '\u03AD': '\u03B5', 49533 '\u03AE': '\u03B7', 49534 '\u03AF': '\u03B9', 49535 '\u03CA': '\u03B9', 49536 '\u0390': '\u03B9', 49537 '\u03CC': '\u03BF', 49538 '\u03CD': '\u03C5', 49539 '\u03CB': '\u03C5', 49540 '\u03B0': '\u03C5', 49541 '\u03CE': '\u03C9', 49542 '\u03C2': '\u03C3', 49543 '\u2019': '\'' 49544 }; 49545 49546 return diacritics; 49547}); 49548 49549S2.define('select2/data/base',[ 49550 '../utils' 49551], function (Utils) { 49552 function BaseAdapter ($element, options) { 49553 BaseAdapter.__super__.constructor.call(this); 49554 } 49555 49556 Utils.Extend(BaseAdapter, Utils.Observable); 49557 49558 BaseAdapter.prototype.current = function (callback) { 49559 throw new Error('The `current` method must be defined in child classes.'); 49560 }; 49561 49562 BaseAdapter.prototype.query = function (params, callback) { 49563 throw new Error('The `query` method must be defined in child classes.'); 49564 }; 49565 49566 BaseAdapter.prototype.bind = function (container, $container) { 49567 // Can be implemented in subclasses 49568 }; 49569 49570 BaseAdapter.prototype.destroy = function () { 49571 // Can be implemented in subclasses 49572 }; 49573 49574 BaseAdapter.prototype.generateResultId = function (container, data) { 49575 var id = container.id + '-result-'; 49576 49577 id += Utils.generateChars(4); 49578 49579 if (data.id != null) { 49580 id += '-' + data.id.toString(); 49581 } else { 49582 id += '-' + Utils.generateChars(4); 49583 } 49584 return id; 49585 }; 49586 49587 return BaseAdapter; 49588}); 49589 49590S2.define('select2/data/select',[ 49591 './base', 49592 '../utils', 49593 'jquery' 49594], function (BaseAdapter, Utils, $) { 49595 function SelectAdapter ($element, options) { 49596 this.$element = $element; 49597 this.options = options; 49598 49599 SelectAdapter.__super__.constructor.call(this); 49600 } 49601 49602 Utils.Extend(SelectAdapter, BaseAdapter); 49603 49604 SelectAdapter.prototype.current = function (callback) { 49605 var data = []; 49606 var self = this; 49607 49608 this.$element.find(':selected').each(function () { 49609 var $option = $(this); 49610 49611 var option = self.item($option); 49612 49613 data.push(option); 49614 }); 49615 49616 callback(data); 49617 }; 49618 49619 SelectAdapter.prototype.select = function (data) { 49620 var self = this; 49621 49622 data.selected = true; 49623 49624 // If data.element is a DOM node, use it instead 49625 if ($(data.element).is('option')) { 49626 data.element.selected = true; 49627 49628 this.$element.trigger('input').trigger('change'); 49629 49630 return; 49631 } 49632 49633 if (this.$element.prop('multiple')) { 49634 this.current(function (currentData) { 49635 var val = []; 49636 49637 data = [data]; 49638 data.push.apply(data, currentData); 49639 49640 for (var d = 0; d < data.length; d++) { 49641 var id = data[d].id; 49642 49643 if ($.inArray(id, val) === -1) { 49644 val.push(id); 49645 } 49646 } 49647 49648 self.$element.val(val); 49649 self.$element.trigger('input').trigger('change'); 49650 }); 49651 } else { 49652 var val = data.id; 49653 49654 this.$element.val(val); 49655 this.$element.trigger('input').trigger('change'); 49656 } 49657 }; 49658 49659 SelectAdapter.prototype.unselect = function (data) { 49660 var self = this; 49661 49662 if (!this.$element.prop('multiple')) { 49663 return; 49664 } 49665 49666 data.selected = false;
vendor: 59,256 bytes, lines 49667-52031
49667 49668 if ($(data.element).is('option')) { 49669 data.element.selected = false; 49670 49671 this.$element.trigger('input').trigger('change'); 49672 49673 return; 49674 } 49675 49676 this.current(function (currentData) { 49677 var val = []; 49678 49679 for (var d = 0; d < currentData.length; d++) { 49680 var id = currentData[d].id; 49681 49682 if (id !== data.id && $.inArray(id, val) === -1) { 49683 val.push(id); 49684 } 49685 } 49686 49687 self.$element.val(val); 49688 49689 self.$element.trigger('input').trigger('change'); 49690 }); 49691 }; 49692 49693 SelectAdapter.prototype.bind = function (container, $container) { 49694 var self = this; 49695 49696 this.container = container; 49697 49698 container.on('select', function (params) { 49699 self.select(params.data); 49700 }); 49701 49702 container.on('unselect', function (params) { 49703 self.unselect(params.data); 49704 }); 49705 }; 49706 49707 SelectAdapter.prototype.destroy = function () { 49708 // Remove anything added to child elements 49709 this.$element.find('*').each(function () { 49710 // Remove any custom data set by Select2 49711 Utils.RemoveData(this); 49712 }); 49713 }; 49714 49715 SelectAdapter.prototype.query = function (params, callback) { 49716 var data = []; 49717 var self = this; 49718 49719 var $options = this.$element.children(); 49720 49721 $options.each(function () { 49722 var $option = $(this); 49723 49724 if (!$option.is('option') && !$option.is('optgroup')) { 49725 return; 49726 } 49727 49728 var option = self.item($option); 49729 49730 var matches = self.matches(params, option); 49731 49732 if (matches !== null) { 49733 data.push(matches); 49734 } 49735 }); 49736 49737 callback({ 49738 results: data 49739 }); 49740 }; 49741 49742 SelectAdapter.prototype.addOptions = function ($options) { 49743 Utils.appendMany(this.$element, $options); 49744 }; 49745 49746 SelectAdapter.prototype.option = function (data) { 49747 var option; 49748 49749 if (data.children) { 49750 option = document.createElement('optgroup'); 49751 option.label = data.text; 49752 } else { 49753 option = document.createElement('option'); 49754 49755 if (option.textContent !== undefined) { 49756 option.textContent = data.text; 49757 } else { 49758 option.innerText = data.text; 49759 } 49760 } 49761 49762 if (data.id !== undefined) { 49763 option.value = data.id; 49764 } 49765 49766 if (data.disabled) { 49767 option.disabled = true; 49768 } 49769 49770 if (data.selected) { 49771 option.selected = true; 49772 } 49773 49774 if (data.title) { 49775 option.title = data.title; 49776 } 49777 49778 var $option = $(option); 49779 49780 var normalizedData = this._normalizeItem(data); 49781 normalizedData.element = option; 49782 49783 // Override the option's data with the combined data 49784 Utils.StoreData(option, 'data', normalizedData); 49785 49786 return $option; 49787 }; 49788 49789 SelectAdapter.prototype.item = function ($option) { 49790 var data = {}; 49791 49792 data = Utils.GetData($option[0], 'data'); 49793 49794 if (data != null) { 49795 return data; 49796 } 49797 49798 if ($option.is('option')) { 49799 data = { 49800 id: $option.val(), 49801 text: $option.text(), 49802 disabled: $option.prop('disabled'), 49803 selected: $option.prop('selected'), 49804 title: $option.prop('title') 49805 }; 49806 } else if ($option.is('optgroup')) { 49807 data = { 49808 text: $option.prop('label'), 49809 children: [], 49810 title: $option.prop('title') 49811 }; 49812 49813 var $children = $option.children('option'); 49814 var children = []; 49815 49816 for (var c = 0; c < $children.length; c++) { 49817 var $child = $($children[c]); 49818 49819 var child = this.item($child); 49820 49821 children.push(child); 49822 } 49823 49824 data.children = children; 49825 } 49826 49827 data = this._normalizeItem(data); 49828 data.element = $option[0]; 49829 49830 Utils.StoreData($option[0], 'data', data); 49831 49832 return data; 49833 }; 49834 49835 SelectAdapter.prototype._normalizeItem = function (item) { 49836 if (item !== Object(item)) { 49837 item = { 49838 id: item, 49839 text: item 49840 }; 49841 } 49842 49843 item = $.extend({}, { 49844 text: '' 49845 }, item); 49846 49847 var defaults = { 49848 selected: false, 49849 disabled: false 49850 }; 49851 49852 if (item.id != null) { 49853 item.id = item.id.toString(); 49854 } 49855 49856 if (item.text != null) { 49857 item.text = item.text.toString(); 49858 } 49859 49860 if (item._resultId == null && item.id && this.container != null) { 49861 item._resultId = this.generateResultId(this.container, item); 49862 } 49863 49864 return $.extend({}, defaults, item); 49865 }; 49866 49867 SelectAdapter.prototype.matches = function (params, data) { 49868 var matcher = this.options.get('matcher'); 49869 49870 return matcher(params, data); 49871 }; 49872 49873 return SelectAdapter; 49874}); 49875 49876S2.define('select2/data/array',[ 49877 './select', 49878 '../utils', 49879 'jquery' 49880], function (SelectAdapter, Utils, $) { 49881 function ArrayAdapter ($element, options) { 49882 this._dataToConvert = options.get('data') || []; 49883 49884 ArrayAdapter.__super__.constructor.call(this, $element, options); 49885 } 49886 49887 Utils.Extend(ArrayAdapter, SelectAdapter); 49888 49889 ArrayAdapter.prototype.bind = function (container, $container) { 49890 ArrayAdapter.__super__.bind.call(this, container, $container); 49891 49892 this.addOptions(this.convertToOptions(this._dataToConvert)); 49893 }; 49894 49895 ArrayAdapter.prototype.select = function (data) { 49896 var $option = this.$element.find('option').filter(function (i, elm) { 49897 return elm.value == data.id.toString(); 49898 }); 49899 49900 if ($option.length === 0) { 49901 $option = this.option(data); 49902 49903 this.addOptions($option); 49904 } 49905 49906 ArrayAdapter.__super__.select.call(this, data); 49907 }; 49908 49909 ArrayAdapter.prototype.convertToOptions = function (data) { 49910 var self = this; 49911 49912 var $existing = this.$element.find('option'); 49913 var existingIds = $existing.map(function () { 49914 return self.item($(this)).id; 49915 }).get(); 49916 49917 var $options = []; 49918 49919 // Filter out all items except for the one passed in the argument 49920 function onlyItem (item) { 49921 return function () { 49922 return $(this).val() == item.id; 49923 }; 49924 } 49925 49926 for (var d = 0; d < data.length; d++) { 49927 var item = this._normalizeItem(data[d]); 49928 49929 // Skip items which were pre-loaded, only merge the data 49930 if ($.inArray(item.id, existingIds) >= 0) { 49931 var $existingOption = $existing.filter(onlyItem(item)); 49932 49933 var existingData = this.item($existingOption); 49934 var newData = $.extend(true, {}, item, existingData); 49935 49936 var $newOption = this.option(newData); 49937 49938 $existingOption.replaceWith($newOption); 49939 49940 continue; 49941 } 49942 49943 var $option = this.option(item); 49944 49945 if (item.children) { 49946 var $children = this.convertToOptions(item.children); 49947 49948 Utils.appendMany($option, $children); 49949 } 49950 49951 $options.push($option); 49952 } 49953 49954 return $options; 49955 }; 49956 49957 return ArrayAdapter; 49958}); 49959 49960S2.define('select2/data/ajax',[ 49961 './array', 49962 '../utils', 49963 'jquery' 49964], function (ArrayAdapter, Utils, $) { 49965 function AjaxAdapter ($element, options) { 49966 this.ajaxOptions = this._applyDefaults(options.get('ajax')); 49967 49968 if (this.ajaxOptions.processResults != null) { 49969 this.processResults = this.ajaxOptions.processResults; 49970 } 49971 49972 AjaxAdapter.__super__.constructor.call(this, $element, options); 49973 } 49974 49975 Utils.Extend(AjaxAdapter, ArrayAdapter); 49976 49977 AjaxAdapter.prototype._applyDefaults = function (options) { 49978 var defaults = { 49979 data: function (params) { 49980 return $.extend({}, params, { 49981 q: params.term 49982 }); 49983 }, 49984 transport: function (params, success, failure) { 49985 var $request = $.ajax(params); 49986 49987 $request.then(success); 49988 $request.fail(failure); 49989 49990 return $request; 49991 } 49992 }; 49993 49994 return $.extend({}, defaults, options, true); 49995 }; 49996 49997 AjaxAdapter.prototype.processResults = function (results) { 49998 return results; 49999 }; 50000 50001 AjaxAdapter.prototype.query = function (params, callback) { 50002 var matches = []; 50003 var self = this; 50004 50005 if (this._request != null) { 50006 // JSONP requests cannot always be aborted 50007 if ($.isFunction(this._request.abort)) { 50008 this._request.abort(); 50009 } 50010 50011 this._request = null; 50012 } 50013 50014 var options = $.extend({ 50015 type: 'GET' 50016 }, this.ajaxOptions); 50017 50018 if (typeof options.url === 'function') { 50019 options.url = options.url.call(this.$element, params); 50020 } 50021 50022 if (typeof options.data === 'function') { 50023 options.data = options.data.call(this.$element, params); 50024 } 50025 50026 function request () { 50027 var $request = options.transport(options, function (data) { 50028 var results = self.processResults(data, params); 50029 50030 if (self.options.get('debug') && window.console && console.error) { 50031 // Check to make sure that the response included a `results` key. 50032 if (!results || !results.results || !$.isArray(results.results)) { 50033 console.error( 50034 'Select2: The AJAX results did not return an array in the ' + 50035 '`results` key of the response.' 50036 ); 50037 } 50038 } 50039 50040 callback(results); 50041 }, function () { 50042 // Attempt to detect if a request was aborted 50043 // Only works if the transport exposes a status property 50044 if ('status' in $request && 50045 ($request.status === 0 || $request.status === '0')) { 50046 return; 50047 } 50048 50049 self.trigger('results:message', { 50050 message: 'errorLoading' 50051 }); 50052 }); 50053 50054 self._request = $request; 50055 } 50056 50057 if (this.ajaxOptions.delay && params.term != null) { 50058 if (this._queryTimeout) { 50059 window.clearTimeout(this._queryTimeout); 50060 } 50061 50062 this._queryTimeout = window.setTimeout(request, this.ajaxOptions.delay); 50063 } else { 50064 request(); 50065 } 50066 }; 50067 50068 return AjaxAdapter; 50069}); 50070 50071S2.define('select2/data/tags',[ 50072 'jquery' 50073], function ($) { 50074 function Tags (decorated, $element, options) { 50075 var tags = options.get('tags'); 50076 50077 var createTag = options.get('createTag'); 50078 50079 if (createTag !== undefined) { 50080 this.createTag = createTag; 50081 } 50082 50083 var insertTag = options.get('insertTag'); 50084 50085 if (insertTag !== undefined) { 50086 this.insertTag = insertTag; 50087 } 50088 50089 decorated.call(this, $element, options); 50090 50091 if ($.isArray(tags)) { 50092 for (var t = 0; t < tags.length; t++) { 50093 var tag = tags[t]; 50094 var item = this._normalizeItem(tag); 50095 50096 var $option = this.option(item); 50097 50098 this.$element.append($option); 50099 } 50100 } 50101 } 50102 50103 Tags.prototype.query = function (decorated, params, callback) { 50104 var self = this; 50105 50106 this._removeOldTags(); 50107 50108 if (params.term == null || params.page != null) { 50109 decorated.call(this, params, callback); 50110 return; 50111 } 50112 50113 function wrapper (obj, child) { 50114 var data = obj.results; 50115 50116 for (var i = 0; i < data.length; i++) { 50117 var option = data[i]; 50118 50119 var checkChildren = ( 50120 option.children != null && 50121 !wrapper({ 50122 results: option.children 50123 }, true) 50124 ); 50125 50126 var optionText = (option.text || '').toUpperCase(); 50127 var paramsTerm = (params.term || '').toUpperCase(); 50128 50129 var checkText = optionText === paramsTerm; 50130 50131 if (checkText || checkChildren) { 50132 if (child) { 50133 return false; 50134 } 50135 50136 obj.data = data; 50137 callback(obj); 50138 50139 return; 50140 } 50141 } 50142 50143 if (child) { 50144 return true; 50145 } 50146 50147 var tag = self.createTag(params); 50148 50149 if (tag != null) { 50150 var $option = self.option(tag); 50151 $option.attr('data-select2-tag', true); 50152 50153 self.addOptions([$option]); 50154 50155 self.insertTag(data, tag); 50156 } 50157 50158 obj.results = data; 50159 50160 callback(obj); 50161 } 50162 50163 decorated.call(this, params, wrapper); 50164 }; 50165 50166 Tags.prototype.createTag = function (decorated, params) { 50167 var term = $.trim(params.term); 50168 50169 if (term === '') { 50170 return null; 50171 } 50172 50173 return { 50174 id: term, 50175 text: term 50176 }; 50177 }; 50178 50179 Tags.prototype.insertTag = function (_, data, tag) { 50180 data.unshift(tag); 50181 }; 50182 50183 Tags.prototype._removeOldTags = function (_) { 50184 var $options = this.$element.find('option[data-select2-tag]'); 50185 50186 $options.each(function () { 50187 if (this.selected) { 50188 return; 50189 } 50190 50191 $(this).remove(); 50192 }); 50193 }; 50194 50195 return Tags; 50196}); 50197 50198S2.define('select2/data/tokenizer',[ 50199 'jquery' 50200], function ($) { 50201 function Tokenizer (decorated, $element, options) { 50202 var tokenizer = options.get('tokenizer'); 50203 50204 if (tokenizer !== undefined) { 50205 this.tokenizer = tokenizer; 50206 } 50207 50208 decorated.call(this, $element, options); 50209 } 50210 50211 Tokenizer.prototype.bind = function (decorated, container, $container) { 50212 decorated.call(this, container, $container); 50213 50214 this.$search = container.dropdown.$search || container.selection.$search || 50215 $container.find('.select2-search__field'); 50216 }; 50217 50218 Tokenizer.prototype.query = function (decorated, params, callback) { 50219 var self = this; 50220 50221 function createAndSelect (data) { 50222 // Normalize the data object so we can use it for checks 50223 var item = self._normalizeItem(data); 50224 50225 // Check if the data object already exists as a tag 50226 // Select it if it doesn't 50227 var $existingOptions = self.$element.find('option').filter(function () { 50228 return $(this).val() === item.id; 50229 }); 50230 50231 // If an existing option wasn't found for it, create the option 50232 if (!$existingOptions.length) { 50233 var $option = self.option(item); 50234 $option.attr('data-select2-tag', true); 50235 50236 self._removeOldTags(); 50237 self.addOptions([$option]); 50238 } 50239 50240 // Select the item, now that we know there is an option for it 50241 select(item); 50242 } 50243 50244 function select (data) { 50245 self.trigger('select', { 50246 data: data 50247 }); 50248 } 50249 50250 params.term = params.term || ''; 50251 50252 var tokenData = this.tokenizer(params, this.options, createAndSelect); 50253 50254 if (tokenData.term !== params.term) { 50255 // Replace the search term if we have the search box 50256 if (this.$search.length) { 50257 this.$search.val(tokenData.term); 50258 this.$search.trigger('focus'); 50259 } 50260 50261 params.term = tokenData.term; 50262 } 50263 50264 decorated.call(this, params, callback); 50265 }; 50266 50267 Tokenizer.prototype.tokenizer = function (_, params, options, callback) { 50268 var separators = options.get('tokenSeparators') || []; 50269 var term = params.term; 50270 var i = 0; 50271 50272 var createTag = this.createTag || function (params) { 50273 return { 50274 id: params.term, 50275 text: params.term 50276 }; 50277 }; 50278 50279 while (i < term.length) { 50280 var termChar = term[i]; 50281 50282 if ($.inArray(termChar, separators) === -1) { 50283 i++; 50284 50285 continue; 50286 } 50287 50288 var part = term.substr(0, i); 50289 var partParams = $.extend({}, params, { 50290 term: part 50291 }); 50292 50293 var data = createTag(partParams); 50294 50295 if (data == null) { 50296 i++; 50297 continue; 50298 } 50299 50300 callback(data); 50301 50302 // Reset the term to not include the tokenized portion 50303 term = term.substr(i + 1) || ''; 50304 i = 0; 50305 } 50306 50307 return { 50308 term: term 50309 }; 50310 }; 50311 50312 return Tokenizer; 50313}); 50314 50315S2.define('select2/data/minimumInputLength',[ 50316 50317], function () { 50318 function MinimumInputLength (decorated, $e, options) { 50319 this.minimumInputLength = options.get('minimumInputLength'); 50320 50321 decorated.call(this, $e, options); 50322 } 50323 50324 MinimumInputLength.prototype.query = function (decorated, params, callback) { 50325 params.term = params.term || ''; 50326 50327 if (params.term.length < this.minimumInputLength) { 50328 this.trigger('results:message', { 50329 message: 'inputTooShort', 50330 args: { 50331 minimum: this.minimumInputLength, 50332 input: params.term, 50333 params: params 50334 } 50335 }); 50336 50337 return; 50338 } 50339 50340 decorated.call(this, params, callback); 50341 }; 50342 50343 return MinimumInputLength; 50344}); 50345 50346S2.define('select2/data/maximumInputLength',[ 50347 50348], function () { 50349 function MaximumInputLength (decorated, $e, options) { 50350 this.maximumInputLength = options.get('maximumInputLength'); 50351 50352 decorated.call(this, $e, options); 50353 } 50354 50355 MaximumInputLength.prototype.query = function (decorated, params, callback) { 50356 params.term = params.term || ''; 50357 50358 if (this.maximumInputLength > 0 && 50359 params.term.length > this.maximumInputLength) { 50360 this.trigger('results:message', { 50361 message: 'inputTooLong', 50362 args: { 50363 maximum: this.maximumInputLength, 50364 input: params.term, 50365 params: params 50366 } 50367 }); 50368 50369 return; 50370 } 50371 50372 decorated.call(this, params, callback); 50373 }; 50374 50375 return MaximumInputLength; 50376}); 50377 50378S2.define('select2/data/maximumSelectionLength',[ 50379 50380], function (){ 50381 function MaximumSelectionLength (decorated, $e, options) { 50382 this.maximumSelectionLength = options.get('maximumSelectionLength'); 50383 50384 decorated.call(this, $e, options); 50385 } 50386 50387 MaximumSelectionLength.prototype.bind = 50388 function (decorated, container, $container) { 50389 var self = this; 50390 50391 decorated.call(this, container, $container); 50392 50393 container.on('select', function () { 50394 self._checkIfMaximumSelected(); 50395 }); 50396 }; 50397 50398 MaximumSelectionLength.prototype.query = 50399 function (decorated, params, callback) { 50400 var self = this; 50401 50402 this._checkIfMaximumSelected(function () { 50403 decorated.call(self, params, callback); 50404 }); 50405 }; 50406 50407 MaximumSelectionLength.prototype._checkIfMaximumSelected = 50408 function (_, successCallback) { 50409 var self = this; 50410 50411 this.current(function (currentData) { 50412 var count = currentData != null ? currentData.length : 0; 50413 if (self.maximumSelectionLength > 0 && 50414 count >= self.maximumSelectionLength) { 50415 self.trigger('results:message', { 50416 message: 'maximumSelected', 50417 args: { 50418 maximum: self.maximumSelectionLength 50419 } 50420 }); 50421 return; 50422 } 50423 50424 if (successCallback) { 50425 successCallback(); 50426 } 50427 }); 50428 }; 50429 50430 return MaximumSelectionLength; 50431}); 50432 50433S2.define('select2/dropdown',[ 50434 'jquery', 50435 './utils' 50436], function ($, Utils) { 50437 function Dropdown ($element, options) { 50438 this.$element = $element; 50439 this.options = options; 50440 50441 Dropdown.__super__.constructor.call(this); 50442 } 50443 50444 Utils.Extend(Dropdown, Utils.Observable); 50445 50446 Dropdown.prototype.render = function () { 50447 var $dropdown = $( 50448 '<span class="select2-dropdown">' + 50449 '<span class="select2-results"></span>' + 50450 '</span>' 50451 ); 50452 50453 $dropdown.attr('dir', this.options.get('dir')); 50454 50455 this.$dropdown = $dropdown; 50456 50457 return $dropdown; 50458 }; 50459 50460 Dropdown.prototype.bind = function () { 50461 // Should be implemented in subclasses 50462 }; 50463 50464 Dropdown.prototype.position = function ($dropdown, $container) { 50465 // Should be implemented in subclasses 50466 }; 50467 50468 Dropdown.prototype.destroy = function () { 50469 // Remove the dropdown from the DOM 50470 this.$dropdown.remove(); 50471 }; 50472 50473 return Dropdown; 50474}); 50475 50476S2.define('select2/dropdown/search',[ 50477 'jquery', 50478 '../utils' 50479], function ($, Utils) { 50480 function Search () { } 50481 50482 Search.prototype.render = function (decorated) { 50483 var $rendered = decorated.call(this); 50484 50485 var $search = $( 50486 '<span class="select2-search select2-search--dropdown">' + 50487 '<input class="select2-search__field" type="search" tabindex="-1"' + 50488 ' autocomplete="off" autocorrect="off" autocapitalize="none"' + 50489 ' spellcheck="false" role="searchbox" aria-autocomplete="list" />' + 50490 '</span>' 50491 ); 50492 50493 this.$searchContainer = $search; 50494 this.$search = $search.find('input'); 50495 50496 $rendered.prepend($search); 50497 50498 return $rendered; 50499 }; 50500 50501 Search.prototype.bind = function (decorated, container, $container) { 50502 var self = this; 50503 50504 var resultsId = container.id + '-results'; 50505 50506 decorated.call(this, container, $container); 50507 50508 this.$search.on('keydown', function (evt) { 50509 self.trigger('keypress', evt); 50510 50511 self._keyUpPrevented = evt.isDefaultPrevented(); 50512 }); 50513 50514 // Workaround for browsers which do not support the `input` event 50515 // This will prevent double-triggering of events for browsers which support 50516 // both the `keyup` and `input` events. 50517 this.$search.on('input', function (evt) { 50518 // Unbind the duplicated `keyup` event 50519 $(this).off('keyup'); 50520 }); 50521 50522 this.$search.on('keyup input', function (evt) { 50523 self.handleSearch(evt); 50524 }); 50525 50526 container.on('open', function () { 50527 self.$search.attr('tabindex', 0); 50528 self.$search.attr('aria-controls', resultsId); 50529 50530 self.$search.trigger('focus'); 50531 50532 window.setTimeout(function () { 50533 self.$search.trigger('focus'); 50534 }, 0); 50535 }); 50536 50537 container.on('close', function () { 50538 self.$search.attr('tabindex', -1); 50539 self.$search.removeAttr('aria-controls'); 50540 self.$search.removeAttr('aria-activedescendant'); 50541 50542 self.$search.val(''); 50543 self.$search.trigger('blur'); 50544 }); 50545 50546 container.on('focus', function () { 50547 if (!container.isOpen()) { 50548 self.$search.trigger('focus'); 50549 } 50550 }); 50551 50552 container.on('results:all', function (params) { 50553 if (params.query.term == null || params.query.term === '') { 50554 var showSearch = self.showSearch(params); 50555 50556 if (showSearch) { 50557 self.$searchContainer.removeClass('select2-search--hide'); 50558 } else { 50559 self.$searchContainer.addClass('select2-search--hide'); 50560 } 50561 } 50562 }); 50563 50564 container.on('results:focus', function (params) { 50565 if (params.data._resultId) { 50566 self.$search.attr('aria-activedescendant', params.data._resultId); 50567 } else { 50568 self.$search.removeAttr('aria-activedescendant'); 50569 } 50570 }); 50571 }; 50572 50573 Search.prototype.handleSearch = function (evt) { 50574 if (!this._keyUpPrevented) { 50575 var input = this.$search.val(); 50576 50577 this.trigger('query', { 50578 term: input 50579 }); 50580 } 50581 50582 this._keyUpPrevented = false; 50583 }; 50584 50585 Search.prototype.showSearch = function (_, params) { 50586 return true; 50587 }; 50588 50589 return Search; 50590}); 50591 50592S2.define('select2/dropdown/hidePlaceholder',[ 50593 50594], function () { 50595 function HidePlaceholder (decorated, $element, options, dataAdapter) { 50596 this.placeholder = this.normalizePlaceholder(options.get('placeholder')); 50597 50598 decorated.call(this, $element, options, dataAdapter); 50599 } 50600 50601 HidePlaceholder.prototype.append = function (decorated, data) { 50602 data.results = this.removePlaceholder(data.results); 50603 50604 decorated.call(this, data); 50605 }; 50606 50607 HidePlaceholder.prototype.normalizePlaceholder = function (_, placeholder) { 50608 if (typeof placeholder === 'string') { 50609 placeholder = { 50610 id: '', 50611 text: placeholder 50612 }; 50613 } 50614 50615 return placeholder; 50616 }; 50617 50618 HidePlaceholder.prototype.removePlaceholder = function (_, data) { 50619 var modifiedData = data.slice(0); 50620 50621 for (var d = data.length - 1; d >= 0; d--) { 50622 var item = data[d]; 50623 50624 if (this.placeholder.id === item.id) { 50625 modifiedData.splice(d, 1); 50626 } 50627 } 50628 50629 return modifiedData; 50630 }; 50631 50632 return HidePlaceholder; 50633}); 50634 50635S2.define('select2/dropdown/infiniteScroll',[ 50636 'jquery' 50637], function ($) { 50638 function InfiniteScroll (decorated, $element, options, dataAdapter) { 50639 this.lastParams = {}; 50640 50641 decorated.call(this, $element, options, dataAdapter); 50642 50643 this.$loadingMore = this.createLoadingMore(); 50644 this.loading = false; 50645 } 50646 50647 InfiniteScroll.prototype.append = function (decorated, data) { 50648 this.$loadingMore.remove(); 50649 this.loading = false; 50650 50651 decorated.call(this, data); 50652 50653 if (this.showLoadingMore(data)) { 50654 this.$results.append(this.$loadingMore); 50655 this.loadMoreIfNeeded(); 50656 } 50657 }; 50658 50659 InfiniteScroll.prototype.bind = function (decorated, container, $container) { 50660 var self = this; 50661 50662 decorated.call(this, container, $container); 50663 50664 container.on('query', function (params) { 50665 self.lastParams = params; 50666 self.loading = true; 50667 }); 50668 50669 container.on('query:append', function (params) { 50670 self.lastParams = params; 50671 self.loading = true; 50672 }); 50673 50674 this.$results.on('scroll', this.loadMoreIfNeeded.bind(this)); 50675 }; 50676 50677 InfiniteScroll.prototype.loadMoreIfNeeded = function () { 50678 var isLoadMoreVisible = $.contains( 50679 document.documentElement, 50680 this.$loadingMore[0] 50681 ); 50682 50683 if (this.loading || !isLoadMoreVisible) { 50684 return; 50685 } 50686 50687 var currentOffset = this.$results.offset().top + 50688 this.$results.outerHeight(false); 50689 var loadingMoreOffset = this.$loadingMore.offset().top + 50690 this.$loadingMore.outerHeight(false); 50691 50692 if (currentOffset + 50 >= loadingMoreOffset) { 50693 this.loadMore(); 50694 } 50695 }; 50696 50697 InfiniteScroll.prototype.loadMore = function () { 50698 this.loading = true; 50699 50700 var params = $.extend({}, {page: 1}, this.lastParams); 50701 50702 params.page++; 50703 50704 this.trigger('query:append', params); 50705 }; 50706 50707 InfiniteScroll.prototype.showLoadingMore = function (_, data) { 50708 return data.pagination && data.pagination.more; 50709 }; 50710 50711 InfiniteScroll.prototype.createLoadingMore = function () { 50712 var $option = $( 50713 '<li ' + 50714 'class="select2-results__option select2-results__option--load-more"' + 50715 'role="option" aria-disabled="true"></li>' 50716 ); 50717 50718 var message = this.options.get('translations').get('loadingMore'); 50719 50720 $option.html(message(this.lastParams)); 50721 50722 return $option; 50723 }; 50724 50725 return InfiniteScroll; 50726}); 50727 50728S2.define('select2/dropdown/attachBody',[ 50729 'jquery', 50730 '../utils' 50731], function ($, Utils) { 50732 function AttachBody (decorated, $element, options) { 50733 this.$dropdownParent = $(options.get('dropdownParent') || document.body); 50734 50735 decorated.call(this, $element, options); 50736 } 50737 50738 AttachBody.prototype.bind = function (decorated, container, $container) { 50739 var self = this; 50740 50741 decorated.call(this, container, $container); 50742 50743 container.on('open', function () { 50744 self._showDropdown(); 50745 self._attachPositioningHandler(container); 50746 50747 // Must bind after the results handlers to ensure correct sizing 50748 self._bindContainerResultHandlers(container); 50749 }); 50750 50751 container.on('close', function () { 50752 self._hideDropdown(); 50753 self._detachPositioningHandler(container); 50754 }); 50755 50756 this.$dropdownContainer.on('mousedown', function (evt) { 50757 evt.stopPropagation(); 50758 }); 50759 }; 50760 50761 AttachBody.prototype.destroy = function (decorated) { 50762 decorated.call(this); 50763 50764 this.$dropdownContainer.remove(); 50765 }; 50766 50767 AttachBody.prototype.position = function (decorated, $dropdown, $container) { 50768 // Clone all of the container classes 50769 $dropdown.attr('class', $container.attr('class')); 50770 50771 $dropdown.removeClass('select2'); 50772 $dropdown.addClass('select2-container--open'); 50773 50774 $dropdown.css({ 50775 position: 'absolute', 50776 top: -999999 50777 }); 50778 50779 this.$container = $container; 50780 }; 50781 50782 AttachBody.prototype.render = function (decorated) { 50783 var $container = $('<span></span>'); 50784 50785 var $dropdown = decorated.call(this); 50786 $container.append($dropdown); 50787 50788 this.$dropdownContainer = $container; 50789 50790 return $container; 50791 }; 50792 50793 AttachBody.prototype._hideDropdown = function (decorated) { 50794 this.$dropdownContainer.detach(); 50795 }; 50796 50797 AttachBody.prototype._bindContainerResultHandlers = 50798 function (decorated, container) { 50799 50800 // These should only be bound once 50801 if (this._containerResultsHandlersBound) { 50802 return; 50803 } 50804 50805 var self = this; 50806 50807 container.on('results:all', function () { 50808 self._positionDropdown(); 50809 self._resizeDropdown(); 50810 }); 50811 50812 container.on('results:append', function () { 50813 self._positionDropdown(); 50814 self._resizeDropdown(); 50815 }); 50816 50817 container.on('results:message', function () { 50818 self._positionDropdown(); 50819 self._resizeDropdown(); 50820 }); 50821 50822 container.on('select', function () { 50823 self._positionDropdown(); 50824 self._resizeDropdown(); 50825 }); 50826 50827 container.on('unselect', function () { 50828 self._positionDropdown(); 50829 self._resizeDropdown(); 50830 }); 50831 50832 this._containerResultsHandlersBound = true; 50833 }; 50834 50835 AttachBody.prototype._attachPositioningHandler = 50836 function (decorated, container) { 50837 var self = this; 50838 50839 var scrollEvent = 'scroll.select2.' + container.id; 50840 var resizeEvent = 'resize.select2.' + container.id; 50841 var orientationEvent = 'orientationchange.select2.' + container.id; 50842 50843 var $watchers = this.$container.parents().filter(Utils.hasScroll); 50844 $watchers.each(function () { 50845 Utils.StoreData(this, 'select2-scroll-position', { 50846 x: $(this).scrollLeft(), 50847 y: $(this).scrollTop() 50848 }); 50849 }); 50850 50851 $watchers.on(scrollEvent, function (ev) { 50852 var position = Utils.GetData(this, 'select2-scroll-position'); 50853 $(this).scrollTop(position.y); 50854 }); 50855 50856 $(window).on(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent, 50857 function (e) { 50858 self._positionDropdown(); 50859 self._resizeDropdown(); 50860 }); 50861 }; 50862 50863 AttachBody.prototype._detachPositioningHandler = 50864 function (decorated, container) { 50865 var scrollEvent = 'scroll.select2.' + container.id; 50866 var resizeEvent = 'resize.select2.' + container.id; 50867 var orientationEvent = 'orientationchange.select2.' + container.id; 50868 50869 var $watchers = this.$container.parents().filter(Utils.hasScroll); 50870 $watchers.off(scrollEvent); 50871 50872 $(window).off(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent); 50873 }; 50874 50875 AttachBody.prototype._positionDropdown = function () { 50876 var $window = $(window); 50877 50878 var isCurrentlyAbove = this.$dropdown.hasClass('select2-dropdown--above'); 50879 var isCurrentlyBelow = this.$dropdown.hasClass('select2-dropdown--below'); 50880 50881 var newDirection = null; 50882 50883 var offset = this.$container.offset(); 50884 50885 offset.bottom = offset.top + this.$container.outerHeight(false); 50886 50887 var container = { 50888 height: this.$container.outerHeight(false) 50889 }; 50890 50891 container.top = offset.top; 50892 container.bottom = offset.top + container.height; 50893 50894 var dropdown = { 50895 height: this.$dropdown.outerHeight(false) 50896 }; 50897 50898 var viewport = { 50899 top: $window.scrollTop(), 50900 bottom: $window.scrollTop() + $window.height() 50901 }; 50902 50903 var enoughRoomAbove = viewport.top < (offset.top - dropdown.height); 50904 var enoughRoomBelow = viewport.bottom > (offset.bottom + dropdown.height); 50905 50906 var css = { 50907 left: offset.left, 50908 top: container.bottom 50909 }; 50910 50911 // Determine what the parent element is to use for calculating the offset 50912 var $offsetParent = this.$dropdownParent; 50913 50914 // For statically positioned elements, we need to get the element 50915 // that is determining the offset 50916 if ($offsetParent.css('position') === 'static') { 50917 $offsetParent = $offsetParent.offsetParent(); 50918 } 50919 50920 var parentOffset = { 50921 top: 0, 50922 left: 0 50923 }; 50924 50925 if ( 50926 $.contains(document.body, $offsetParent[0]) || 50927 $offsetParent[0].isConnected 50928 ) { 50929 parentOffset = $offsetParent.offset(); 50930 } 50931 50932 css.top -= parentOffset.top; 50933 css.left -= parentOffset.left; 50934 50935 if (!isCurrentlyAbove && !isCurrentlyBelow) { 50936 newDirection = 'below'; 50937 } 50938 50939 if (!enoughRoomBelow && enoughRoomAbove && !isCurrentlyAbove) { 50940 newDirection = 'above'; 50941 } else if (!enoughRoomAbove && enoughRoomBelow && isCurrentlyAbove) { 50942 newDirection = 'below'; 50943 } 50944 50945 if (newDirection == 'above' || 50946 (isCurrentlyAbove && newDirection !== 'below')) { 50947 css.top = container.top - parentOffset.top - dropdown.height; 50948 } 50949 50950 if (newDirection != null) { 50951 this.$dropdown 50952 .removeClass('select2-dropdown--below select2-dropdown--above') 50953 .addClass('select2-dropdown--' + newDirection); 50954 this.$container 50955 .removeClass('select2-container--below select2-container--above') 50956 .addClass('select2-container--' + newDirection); 50957 } 50958 50959 this.$dropdownContainer.css(css); 50960 }; 50961 50962 AttachBody.prototype._resizeDropdown = function () { 50963 var css = { 50964 width: this.$container.outerWidth(false) + 'px' 50965 }; 50966 50967 if (this.options.get('dropdownAutoWidth')) { 50968 css.minWidth = css.width; 50969 css.position = 'relative'; 50970 css.width = 'auto'; 50971 } 50972 50973 this.$dropdown.css(css); 50974 }; 50975 50976 AttachBody.prototype._showDropdown = function (decorated) { 50977 this.$dropdownContainer.appendTo(this.$dropdownParent); 50978 50979 this._positionDropdown(); 50980 this._resizeDropdown(); 50981 }; 50982 50983 return AttachBody; 50984}); 50985 50986S2.define('select2/dropdown/minimumResultsForSearch',[ 50987 50988], function () { 50989 function countResults (data) { 50990 var count = 0; 50991 50992 for (var d = 0; d < data.length; d++) { 50993 var item = data[d]; 50994 50995 if (item.children) { 50996 count += countResults(item.children); 50997 } else { 50998 count++; 50999 } 51000 } 51001 51002 return count; 51003 } 51004 51005 function MinimumResultsForSearch (decorated, $element, options, dataAdapter) { 51006 this.minimumResultsForSearch = options.get('minimumResultsForSearch'); 51007 51008 if (this.minimumResultsForSearch < 0) { 51009 this.minimumResultsForSearch = Infinity; 51010 } 51011 51012 decorated.call(this, $element, options, dataAdapter); 51013 } 51014 51015 MinimumResultsForSearch.prototype.showSearch = function (decorated, params) { 51016 if (countResults(params.data.results) < this.minimumResultsForSearch) { 51017 return false; 51018 } 51019 51020 return decorated.call(this, params); 51021 }; 51022 51023 return MinimumResultsForSearch; 51024}); 51025 51026S2.define('select2/dropdown/selectOnClose',[ 51027 '../utils' 51028], function (Utils) { 51029 function SelectOnClose () { } 51030 51031 SelectOnClose.prototype.bind = function (decorated, container, $container) { 51032 var self = this; 51033 51034 decorated.call(this, container, $container); 51035 51036 container.on('close', function (params) { 51037 self._handleSelectOnClose(params); 51038 }); 51039 }; 51040 51041 SelectOnClose.prototype._handleSelectOnClose = function (_, params) { 51042 if (params && params.originalSelect2Event != null) { 51043 var event = params.originalSelect2Event; 51044 51045 // Don't select an item if the close event was triggered from a select or 51046 // unselect event 51047 if (event._type === 'select' || event._type === 'unselect') { 51048 return; 51049 } 51050 } 51051 51052 var $highlightedResults = this.getHighlightedResults(); 51053 51054 // Only select highlighted results 51055 if ($highlightedResults.length < 1) { 51056 return; 51057 } 51058 51059 var data = Utils.GetData($highlightedResults[0], 'data'); 51060 51061 // Don't re-select already selected resulte 51062 if ( 51063 (data.element != null && data.element.selected) || 51064 (data.element == null && data.selected) 51065 ) { 51066 return; 51067 } 51068 51069 this.trigger('select', { 51070 data: data 51071 }); 51072 }; 51073 51074 return SelectOnClose; 51075}); 51076 51077S2.define('select2/dropdown/closeOnSelect',[ 51078 51079], function () { 51080 function CloseOnSelect () { } 51081 51082 CloseOnSelect.prototype.bind = function (decorated, container, $container) { 51083 var self = this; 51084 51085 decorated.call(this, container, $container); 51086 51087 container.on('select', function (evt) { 51088 self._selectTriggered(evt); 51089 }); 51090 51091 container.on('unselect', function (evt) { 51092 self._selectTriggered(evt); 51093 }); 51094 }; 51095 51096 CloseOnSelect.prototype._selectTriggered = function (_, evt) { 51097 var originalEvent = evt.originalEvent; 51098 51099 // Don't close if the control key is being held 51100 if (originalEvent && (originalEvent.ctrlKey || originalEvent.metaKey)) { 51101 return; 51102 } 51103 51104 this.trigger('close', { 51105 originalEvent: originalEvent, 51106 originalSelect2Event: evt 51107 }); 51108 }; 51109 51110 return CloseOnSelect; 51111}); 51112 51113S2.define('select2/i18n/en',[],function () { 51114 // English 51115 return { 51116 errorLoading: function () { 51117 return 'The results could not be loaded.'; 51118 }, 51119 inputTooLong: function (args) { 51120 var overChars = args.input.length - args.maximum; 51121 51122 var message = 'Please delete ' + overChars + ' character'; 51123 51124 if (overChars != 1) { 51125 message += 's'; 51126 } 51127 51128 return message; 51129 }, 51130 inputTooShort: function (args) { 51131 var remainingChars = args.minimum - args.input.length; 51132 51133 var message = 'Please enter ' + remainingChars + ' or more characters'; 51134 51135 return message; 51136 }, 51137 loadingMore: function () { 51138 return 'Loading more resultsâ¦'; 51139 }, 51140 maximumSelected: function (args) { 51141 var message = 'You can only select ' + args.maximum + ' item'; 51142 51143 if (args.maximum != 1) { 51144 message += 's'; 51145 } 51146 51147 return message; 51148 }, 51149 noResults: function () { 51150 return 'No results found'; 51151 }, 51152 searching: function () { 51153 return 'Searchingâ¦'; 51154 }, 51155 removeAllItems: function () { 51156 return 'Remove all items'; 51157 } 51158 }; 51159}); 51160 51161S2.define('select2/defaults',[ 51162 'jquery', 51163 'require', 51164 51165 './results', 51166 51167 './selection/single', 51168 './selection/multiple', 51169 './selection/placeholder', 51170 './selection/allowClear', 51171 './selection/search', 51172 './selection/eventRelay', 51173 51174 './utils', 51175 './translation', 51176 './diacritics', 51177 51178 './data/select', 51179 './data/array', 51180 './data/ajax', 51181 './data/tags', 51182 './data/tokenizer', 51183 './data/minimumInputLength', 51184 './data/maximumInputLength', 51185 './data/maximumSelectionLength', 51186 51187 './dropdown', 51188 './dropdown/search', 51189 './dropdown/hidePlaceholder', 51190 './dropdown/infiniteScroll', 51191 './dropdown/attachBody', 51192 './dropdown/minimumResultsForSearch', 51193 './dropdown/selectOnClose', 51194 './dropdown/closeOnSelect', 51195 51196 './i18n/en' 51197], function ($, require, 51198 51199 ResultsList, 51200 51201 SingleSelection, MultipleSelection, Placeholder, AllowClear, 51202 SelectionSearch, EventRelay, 51203 51204 Utils, Translation, DIACRITICS, 51205 51206 SelectData, ArrayData, AjaxData, Tags, Tokenizer, 51207 MinimumInputLength, MaximumInputLength, MaximumSelectionLength, 51208 51209 Dropdown, DropdownSearch, HidePlaceholder, InfiniteScroll, 51210 AttachBody, MinimumResultsForSearch, SelectOnClose, CloseOnSelect, 51211 51212 EnglishTranslation) { 51213 function Defaults () { 51214 this.reset(); 51215 } 51216 51217 Defaults.prototype.apply = function (options) { 51218 options = $.extend(true, {}, this.defaults, options); 51219 51220 if (options.dataAdapter == null) { 51221 if (options.ajax != null) { 51222 options.dataAdapter = AjaxData; 51223 } else if (options.data != null) { 51224 options.dataAdapter = ArrayData; 51225 } else { 51226 options.dataAdapter = SelectData; 51227 } 51228 51229 if (options.minimumInputLength > 0) { 51230 options.dataAdapter = Utils.Decorate( 51231 options.dataAdapter, 51232 MinimumInputLength 51233 ); 51234 } 51235 51236 if (options.maximumInputLength > 0) { 51237 options.dataAdapter = Utils.Decorate( 51238 options.dataAdapter, 51239 MaximumInputLength 51240 ); 51241 } 51242 51243 if (options.maximumSelectionLength > 0) { 51244 options.dataAdapter = Utils.Decorate( 51245 options.dataAdapter, 51246 MaximumSelectionLength 51247 ); 51248 } 51249 51250 if (options.tags) { 51251 options.dataAdapter = Utils.Decorate(options.dataAdapter, Tags); 51252 } 51253 51254 if (options.tokenSeparators != null || options.tokenizer != null) { 51255 options.dataAdapter = Utils.Decorate( 51256 options.dataAdapter, 51257 Tokenizer 51258 ); 51259 } 51260 51261 if (options.query != null) { 51262 var Query = require(options.amdBase + 'compat/query'); 51263 51264 options.dataAdapter = Utils.Decorate( 51265 options.dataAdapter, 51266 Query 51267 ); 51268 } 51269 51270 if (options.initSelection != null) { 51271 var InitSelection = require(options.amdBase + 'compat/initSelection'); 51272 51273 options.dataAdapter = Utils.Decorate( 51274 options.dataAdapter, 51275 InitSelection 51276 ); 51277 } 51278 } 51279 51280 if (options.resultsAdapter == null) { 51281 options.resultsAdapter = ResultsList; 51282 51283 if (options.ajax != null) { 51284 options.resultsAdapter = Utils.Decorate( 51285 options.resultsAdapter, 51286 InfiniteScroll 51287 ); 51288 } 51289 51290 if (options.placeholder != null) { 51291 options.resultsAdapter = Utils.Decorate( 51292 options.resultsAdapter, 51293 HidePlaceholder 51294 ); 51295 } 51296 51297 if (options.selectOnClose) { 51298 options.resultsAdapter = Utils.Decorate( 51299 options.resultsAdapter, 51300 SelectOnClose 51301 ); 51302 } 51303 } 51304 51305 if (options.dropdownAdapter == null) { 51306 if (options.multiple) { 51307 options.dropdownAdapter = Dropdown; 51308 } else { 51309 var SearchableDropdown = Utils.Decorate(Dropdown, DropdownSearch); 51310 51311 options.dropdownAdapter = SearchableDropdown; 51312 } 51313 51314 if (options.minimumResultsForSearch !== 0) { 51315 options.dropdownAdapter = Utils.Decorate( 51316 options.dropdownAdapter, 51317 MinimumResultsForSearch 51318 ); 51319 } 51320 51321 if (options.closeOnSelect) { 51322 options.dropdownAdapter = Utils.Decorate( 51323 options.dropdownAdapter, 51324 CloseOnSelect 51325 ); 51326 } 51327 51328 if ( 51329 options.dropdownCssClass != null || 51330 options.dropdownCss != null || 51331 options.adaptDropdownCssClass != null 51332 ) { 51333 var DropdownCSS = require(options.amdBase + 'compat/dropdownCss'); 51334 51335 options.dropdownAdapter = Utils.Decorate( 51336 options.dropdownAdapter, 51337 DropdownCSS 51338 ); 51339 } 51340 51341 options.dropdownAdapter = Utils.Decorate( 51342 options.dropdownAdapter, 51343 AttachBody 51344 ); 51345 } 51346 51347 if (options.selectionAdapter == null) { 51348 if (options.multiple) { 51349 options.selectionAdapter = MultipleSelection; 51350 } else { 51351 options.selectionAdapter = SingleSelection; 51352 } 51353 51354 // Add the placeholder mixin if a placeholder was specified 51355 if (options.placeholder != null) { 51356 options.selectionAdapter = Utils.Decorate( 51357 options.selectionAdapter, 51358 Placeholder 51359 ); 51360 } 51361 51362 if (options.allowClear) { 51363 options.selectionAdapter = Utils.Decorate( 51364 options.selectionAdapter, 51365 AllowClear 51366 ); 51367 } 51368 51369 if (options.multiple) { 51370 options.selectionAdapter = Utils.Decorate( 51371 options.selectionAdapter, 51372 SelectionSearch 51373 ); 51374 } 51375 51376 if ( 51377 options.containerCssClass != null || 51378 options.containerCss != null || 51379 options.adaptContainerCssClass != null 51380 ) { 51381 var ContainerCSS = require(options.amdBase + 'compat/containerCss'); 51382 51383 options.selectionAdapter = Utils.Decorate( 51384 options.selectionAdapter, 51385 ContainerCSS 51386 ); 51387 } 51388 51389 options.selectionAdapter = Utils.Decorate( 51390 options.selectionAdapter, 51391 EventRelay 51392 ); 51393 } 51394 51395 // If the defaults were not previously applied from an element, it is 51396 // possible for the language option to have not been resolved 51397 options.language = this._resolveLanguage(options.language); 51398 51399 // Always fall back to English since it will always be complete 51400 options.language.push('en'); 51401 51402 var uniqueLanguages = []; 51403 51404 for (var l = 0; l < options.language.length; l++) { 51405 var language = options.language[l]; 51406 51407 if (uniqueLanguages.indexOf(language) === -1) { 51408 uniqueLanguages.push(language); 51409 } 51410 } 51411 51412 options.language = uniqueLanguages; 51413 51414 options.translations = this._processTranslations( 51415 options.language, 51416 options.debug 51417 ); 51418 51419 return options; 51420 }; 51421 51422 Defaults.prototype.reset = function () { 51423 function stripDiacritics (text) { 51424 // Used 'uni range + named function' from http://jsperf.com/diacritics/18 51425 function match(a) { 51426 return DIACRITICS[a] || a; 51427 } 51428 51429 return text.replace(/[^\u0000-\u007E]/g, match); 51430 } 51431 51432 function matcher (params, data) { 51433 // Always return the object if there is nothing to compare 51434 if ($.trim(params.term) === '') { 51435 return data; 51436 } 51437 51438 // Do a recursive check for options with children 51439 if (data.children && data.children.length > 0) { 51440 // Clone the data object if there are children 51441 // This is required as we modify the object to remove any non-matches 51442 var match = $.extend(true, {}, data); 51443 51444 // Check each child of the option 51445 for (var c = data.children.length - 1; c >= 0; c--) { 51446 var child = data.children[c]; 51447 51448 var matches = matcher(params, child); 51449 51450 // If there wasn't a match, remove the object in the array 51451 if (matches == null) { 51452 match.children.splice(c, 1); 51453 } 51454 } 51455 51456 // If any children matched, return the new object 51457 if (match.children.length > 0) { 51458 return match; 51459 } 51460 51461 // If there were no matching children, check just the plain object 51462 return matcher(params, match); 51463 } 51464 51465 var original = stripDiacritics(data.text).toUpperCase(); 51466 var term = stripDiacritics(params.term).toUpperCase(); 51467 51468 // Check if the text contains the term 51469 if (original.indexOf(term) > -1) { 51470 return data; 51471 } 51472 51473 // If it doesn't contain the term, don't return anything 51474 return null; 51475 } 51476 51477 this.defaults = { 51478 amdBase: './', 51479 amdLanguageBase: './i18n/', 51480 closeOnSelect: true, 51481 debug: false, 51482 dropdownAutoWidth: false, 51483 escapeMarkup: Utils.escapeMarkup, 51484 language: {}, 51485 matcher: matcher, 51486 minimumInputLength: 0, 51487 maximumInputLength: 0, 51488 maximumSelectionLength: 0, 51489 minimumResultsForSearch: 0, 51490 selectOnClose: false, 51491 scrollAfterSelect: false, 51492 sorter: function (data) { 51493 return data; 51494 }, 51495 templateResult: function (result) { 51496 return result.text; 51497 }, 51498 templateSelection: function (selection) { 51499 return selection.text; 51500 }, 51501 theme: 'default', 51502 width: 'resolve' 51503 }; 51504 }; 51505 51506 Defaults.prototype.applyFromElement = function (options, $element) { 51507 var optionLanguage = options.language; 51508 var defaultLanguage = this.defaults.language; 51509 var elementLanguage = $element.prop('lang'); 51510 var parentLanguage = $element.closest('[lang]').prop('lang'); 51511 51512 var languages = Array.prototype.concat.call( 51513 this._resolveLanguage(elementLanguage), 51514 this._resolveLanguage(optionLanguage), 51515 this._resolveLanguage(defaultLanguage), 51516 this._resolveLanguage(parentLanguage) 51517 ); 51518 51519 options.language = languages; 51520 51521 return options; 51522 }; 51523 51524 Defaults.prototype._resolveLanguage = function (language) { 51525 if (!language) { 51526 return []; 51527 } 51528 51529 if ($.isEmptyObject(language)) { 51530 return []; 51531 } 51532 51533 if ($.isPlainObject(language)) { 51534 return [language]; 51535 } 51536 51537 var languages; 51538 51539 if (!$.isArray(language)) { 51540 languages = [language]; 51541 } else { 51542 languages = language; 51543 } 51544 51545 var resolvedLanguages = []; 51546 51547 for (var l = 0; l < languages.length; l++) { 51548 resolvedLanguages.push(languages[l]); 51549 51550 if (typeof languages[l] === 'string' && languages[l].indexOf('-') > 0) { 51551 // Extract the region information if it is included 51552 var languageParts = languages[l].split('-'); 51553 var baseLanguage = languageParts[0]; 51554 51555 resolvedLanguages.push(baseLanguage); 51556 } 51557 } 51558 51559 return resolvedLanguages; 51560 }; 51561 51562 Defaults.prototype._processTranslations = function (languages, debug) { 51563 var translations = new Translation(); 51564 51565 for (var l = 0; l < languages.length; l++) { 51566 var languageData = new Translation(); 51567 51568 var language = languages[l]; 51569 51570 if (typeof language === 'string') { 51571 try { 51572 // Try to load it with the original name 51573 languageData = Translation.loadPath(language); 51574 } catch (e) { 51575 try { 51576 // If we couldn't load it, check if it wasn't the full path 51577 language = this.defaults.amdLanguageBase + language; 51578 languageData = Translation.loadPath(language); 51579 } catch (ex) { 51580 // The translation could not be loaded at all. Sometimes this is 51581 // because of a configuration problem, other times this can be 51582 // because of how Select2 helps load all possible translation files 51583 if (debug && window.console && console.warn) { 51584 console.warn( 51585 'Select2: The language file for "' + language + '" could ' + 51586 'not be automatically loaded. A fallback will be used instead.' 51587 ); 51588 } 51589 } 51590 } 51591 } else if ($.isPlainObject(language)) { 51592 languageData = new Translation(language); 51593 } else { 51594 languageData = language; 51595 } 51596 51597 translations.extend(languageData); 51598 } 51599 51600 return translations; 51601 }; 51602 51603 Defaults.prototype.set = function (key, value) { 51604 var camelKey = $.camelCase(key); 51605 51606 var data = {}; 51607 data[camelKey] = value; 51608 51609 var convertedData = Utils._convertData(data); 51610 51611 $.extend(true, this.defaults, convertedData); 51612 }; 51613 51614 var defaults = new Defaults(); 51615 51616 return defaults; 51617}); 51618 51619S2.define('select2/options',[ 51620 'require', 51621 'jquery', 51622 './defaults', 51623 './utils' 51624], function (require, $, Defaults, Utils) { 51625 function Options (options, $element) { 51626 this.options = options; 51627 51628 if ($element != null) { 51629 this.fromElement($element); 51630 } 51631 51632 if ($element != null) { 51633 this.options = Defaults.applyFromElement(this.options, $element); 51634 } 51635 51636 this.options = Defaults.apply(this.options); 51637 51638 if ($element && $element.is('input')) { 51639 var InputCompat = require(this.get('amdBase') + 'compat/inputData'); 51640 51641 this.options.dataAdapter = Utils.Decorate( 51642 this.options.dataAdapter, 51643 InputCompat 51644 ); 51645 } 51646 } 51647 51648 Options.prototype.fromElement = function ($e) { 51649 var excludedData = ['select2']; 51650 51651 if (this.options.multiple == null) { 51652 this.options.multiple = $e.prop('multiple'); 51653 } 51654 51655 if (this.options.disabled == null) { 51656 this.options.disabled = $e.prop('disabled'); 51657 } 51658 51659 if (this.options.dir == null) { 51660 if ($e.prop('dir')) { 51661 this.options.dir = $e.prop('dir'); 51662 } else if ($e.closest('[dir]').prop('dir')) { 51663 this.options.dir = $e.closest('[dir]').prop('dir'); 51664 } else { 51665 this.options.dir = 'ltr'; 51666 } 51667 } 51668 51669 $e.prop('disabled', this.options.disabled); 51670 $e.prop('multiple', this.options.multiple); 51671 51672 if (Utils.GetData($e[0], 'select2Tags')) { 51673 if (this.options.debug && window.console && console.warn) { 51674 console.warn( 51675 'Select2: The `data-select2-tags` attribute has been changed to ' + 51676 'use the `data-data` and `data-tags="true"` attributes and will be ' + 51677 'removed in future versions of Select2.' 51678 ); 51679 } 51680 51681 Utils.StoreData($e[0], 'data', Utils.GetData($e[0], 'select2Tags')); 51682 Utils.StoreData($e[0], 'tags', true); 51683 } 51684 51685 if (Utils.GetData($e[0], 'ajaxUrl')) { 51686 if (this.options.debug && window.console && console.warn) { 51687 console.warn( 51688 'Select2: The `data-ajax-url` attribute has been changed to ' + 51689 '`data-ajax--url` and support for the old attribute will be removed' + 51690 ' in future versions of Select2.' 51691 ); 51692 } 51693 51694 $e.attr('ajax--url', Utils.GetData($e[0], 'ajaxUrl')); 51695 Utils.StoreData($e[0], 'ajax-Url', Utils.GetData($e[0], 'ajaxUrl')); 51696 } 51697 51698 var dataset = {}; 51699 51700 function upperCaseLetter(_, letter) { 51701 return letter.toUpperCase(); 51702 } 51703 51704 // Pre-load all of the attributes which are prefixed with `data-` 51705 for (var attr = 0; attr < $e[0].attributes.length; attr++) { 51706 var attributeName = $e[0].attributes[attr].name; 51707 var prefix = 'data-'; 51708 51709 if (attributeName.substr(0, prefix.length) == prefix) { 51710 // Get the contents of the attribute after `data-` 51711 var dataName = attributeName.substring(prefix.length); 51712 51713 // Get the data contents from the consistent source 51714 // This is more than likely the jQuery data helper 51715 var dataValue = Utils.GetData($e[0], dataName); 51716 51717 // camelCase the attribute name to match the spec 51718 var camelDataName = dataName.replace(/-([a-z])/g, upperCaseLetter); 51719 51720 // Store the data attribute contents into the dataset since 51721 dataset[camelDataName] = dataValue; 51722 } 51723 } 51724 51725 // Prefer the element's `dataset` attribute if it exists 51726 // jQuery 1.x does not correctly handle data attributes with multiple dashes 51727 if ($.fn.jquery && $.fn.jquery.substr(0, 2) == '1.' && $e[0].dataset) { 51728 dataset = $.extend(true, {}, $e[0].dataset, dataset); 51729 } 51730 51731 // Prefer our internal data cache if it exists 51732 var data = $.extend(true, {}, Utils.GetData($e[0]), dataset); 51733 51734 data = Utils._convertData(data); 51735 51736 for (var key in data) { 51737 if ($.inArray(key, excludedData) > -1) { 51738 continue; 51739 } 51740 51741 if ($.isPlainObject(this.options[key])) { 51742 $.extend(this.options[key], data[key]); 51743 } else { 51744 this.options[key] = data[key]; 51745 } 51746 } 51747 51748 return this; 51749 }; 51750 51751 Options.prototype.get = function (key) { 51752 return this.options[key]; 51753 }; 51754 51755 Options.prototype.set = function (key, val) { 51756 this.options[key] = val; 51757 }; 51758 51759 return Options; 51760}); 51761 51762S2.define('select2/core',[ 51763 'jquery', 51764 './options', 51765 './utils', 51766 './keys' 51767], function ($, Options, Utils, KEYS) { 51768 var Select2 = function ($element, options) { 51769 if (Utils.GetData($element[0], 'select2') != null) { 51770 Utils.GetData($element[0], 'select2').destroy(); 51771 } 51772 51773 this.$element = $element; 51774 51775 this.id = this._generateId($element); 51776 51777 options = options || {}; 51778 51779 this.options = new Options(options, $element); 51780 51781 Select2.__super__.constructor.call(this); 51782 51783 // Set up the tabindex 51784 51785 var tabindex = $element.attr('tabindex') || 0; 51786 Utils.StoreData($element[0], 'old-tabindex', tabindex); 51787 $element.attr('tabindex', '-1'); 51788 51789 // Set up containers and adapters 51790 51791 var DataAdapter = this.options.get('dataAdapter'); 51792 this.dataAdapter = new DataAdapter($element, this.options); 51793 51794 var $container = this.render(); 51795 51796 this._placeContainer($container); 51797 51798 var SelectionAdapter = this.options.get('selectionAdapter'); 51799 this.selection = new SelectionAdapter($element, this.options); 51800 this.$selection = this.selection.render(); 51801 51802 this.selection.position(this.$selection, $container); 51803 51804 var DropdownAdapter = this.options.get('dropdownAdapter'); 51805 this.dropdown = new DropdownAdapter($element, this.options); 51806 this.$dropdown = this.dropdown.render(); 51807 51808 this.dropdown.position(this.$dropdown, $container); 51809 51810 var ResultsAdapter = this.options.get('resultsAdapter'); 51811 this.results = new ResultsAdapter($element, this.options, this.dataAdapter); 51812 this.$results = this.results.render(); 51813 51814 this.results.position(this.$results, this.$dropdown); 51815 51816 // Bind events 51817 51818 var self = this; 51819 51820 // Bind the container to all of the adapters 51821 this._bindAdapters(); 51822 51823 // Register any DOM event handlers 51824 this._registerDomEvents(); 51825 51826 // Register any internal event handlers 51827 this._registerDataEvents(); 51828 this._registerSelectionEvents(); 51829 this._registerDropdownEvents(); 51830 this._registerResultsEvents(); 51831 this._registerEvents(); 51832 51833 // Set the initial state 51834 this.dataAdapter.current(function (initialData) { 51835 self.trigger('selection:update', { 51836 data: initialData 51837 }); 51838 }); 51839 51840 // Hide the original select 51841 $element.addClass('select2-hidden-accessible'); 51842 $element.attr('aria-hidden', 'true'); 51843 51844 // Synchronize any monitored attributes 51845 this._syncAttributes(); 51846 51847 Utils.StoreData($element[0], 'select2', this); 51848 51849 // Ensure backwards compatibility with $element.data('select2'). 51850 $element.data('select2', this); 51851 }; 51852 51853 Utils.Extend(Select2, Utils.Observable); 51854 51855 Select2.prototype._generateId = function ($element) { 51856 var id = ''; 51857 51858 if ($element.attr('id') != null) { 51859 id = $element.attr('id'); 51860 } else if ($element.attr('name') != null) { 51861 id = $element.attr('name') + '-' + Utils.generateChars(2); 51862 } else { 51863 id = Utils.generateChars(4); 51864 } 51865 51866 id = id.replace(/(:|\.|\[|\]|,)/g, ''); 51867 id = 'select2-' + id; 51868 51869 return id; 51870 }; 51871 51872 Select2.prototype._placeContainer = function ($container) { 51873 $container.insertAfter(this.$element); 51874 51875 var width = this._resolveWidth(this.$element, this.options.get('width')); 51876 51877 if (width != null) { 51878 $container.css('width', width); 51879 } 51880 }; 51881 51882 Select2.prototype._resolveWidth = function ($element, method) { 51883 var WIDTH = /^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i; 51884 51885 if (method == 'resolve') { 51886 var styleWidth = this._resolveWidth($element, 'style'); 51887 51888 if (styleWidth != null) { 51889 return styleWidth; 51890 } 51891 51892 return this._resolveWidth($element, 'element'); 51893 } 51894 51895 if (method == 'element') { 51896 var elementWidth = $element.outerWidth(false); 51897 51898 if (elementWidth <= 0) { 51899 return 'auto'; 51900 } 51901 51902 return elementWidth + 'px'; 51903 } 51904 51905 if (method == 'style') { 51906 var style = $element.attr('style'); 51907 51908 if (typeof(style) !== 'string') { 51909 return null; 51910 } 51911 51912 var attrs = style.split(';'); 51913 51914 for (var i = 0, l = attrs.length; i < l; i = i + 1) { 51915 var attr = attrs[i].replace(/\s/g, ''); 51916 var matches = attr.match(WIDTH); 51917 51918 if (matches !== null && matches.length >= 1) { 51919 return matches[1]; 51920 } 51921 } 51922 51923 return null; 51924 } 51925 51926 if (method == 'computedstyle') { 51927 var computedStyle = window.getComputedStyle($element[0]); 51928 51929 return computedStyle.width; 51930 } 51931 51932 return method; 51933 }; 51934 51935 Select2.prototype._bindAdapters = function () { 51936 this.dataAdapter.bind(this, this.$container); 51937 this.selection.bind(this, this.$container); 51938 51939 this.dropdown.bind(this, this.$container); 51940 this.results.bind(this, this.$container); 51941 }; 51942 51943 Select2.prototype._registerDomEvents = function () { 51944 var self = this; 51945 51946 this.$element.on('change.select2', function () { 51947 self.dataAdapter.current(function (data) { 51948 self.trigger('selection:update', { 51949 data: data 51950 }); 51951 }); 51952 }); 51953 51954 this.$element.on('focus.select2', function (evt) { 51955 self.trigger('focus', evt); 51956 }); 51957 51958 this._syncA = Utils.bind(this._syncAttributes, this); 51959 this._syncS = Utils.bind(this._syncSubtree, this); 51960 51961 if (this.$element[0].attachEvent) { 51962 this.$element[0].attachEvent('onpropertychange', this._syncA); 51963 } 51964 51965 var observer = window.MutationObserver || 51966 window.WebKitMutationObserver || 51967 window.MozMutationObserver 51968 ; 51969 51970 if (observer != null) { 51971 this._observer = new observer(function (mutations) { 51972 self._syncA(); 51973 self._syncS(null, mutations); 51974 }); 51975 this._observer.observe(this.$element[0], { 51976 attributes: true, 51977 childList: true, 51978 subtree: false 51979 }); 51980 } else if (this.$element[0].addEventListener) { 51981 this.$element[0].addEventListener( 51982 'DOMAttrModified', 51983 self._syncA, 51984 false 51985 ); 51986 this.$element[0].addEventListener( 51987 'DOMNodeInserted', 51988 self._syncS, 51989 false 51990 ); 51991 this.$element[0].addEventListener( 51992 'DOMNodeRemoved', 51993 self._syncS, 51994 false 51995 ); 51996 } 51997 }; 51998 51999 Select2.prototype._registerDataEvents = function () { 52000 var self = this; 52001 52002 this.dataAdapter.on('*', function (name, params) { 52003 self.trigger(name, params); 52004 }); 52005 }; 52006 52007 Select2.prototype._registerSelectionEvents = function () { 52008 var self = this; 52009 var nonRelayEvents = ['toggle', 'focus']; 52010 52011 this.selection.on('toggle', function () { 52012 self.toggleDropdown(); 52013 }); 52014 52015 this.selection.on('focus', function (params) { 52016 self.focus(params); 52017 }); 52018 52019 this.selection.on('*', function (name, params) { 52020 if ($.inArray(name, nonRelayEvents) !== -1) { 52021 return; 52022 } 52023 52024 self.trigger(name, params); 52025 }); 52026 }; 52027 52028 Select2.prototype._registerDropdownEvents = function () { 52029 var self = this; 52030 52031 this.dropdown.on('*', fu
52031nction (name, params) { 52032 self.trigger(name, params); 52033 }); 52034 }; 52035 52036 Select2.prototype._registerResultsEvents = function () { 52037 var self = this; 52038 52039 this.results.on('*', function (name, params) { 52040 self.trigger(name, params); 52041 }); 52042 }; 52043 52044 Select2.prototype._registerEvents = function () { 52045 var self = this; 52046 52047 this.on('open', function () { 52048 self.$container.addClass('select2-container--open'); 52049 }); 52050 52051 this.on('close', function () { 52052 self.$container.removeClass('select2-container--open'); 52053 }); 52054 52055 this.on('enable', function () { 52056 self.$container.removeClass('select2-container--disabled'); 52057 }); 52058 52059 this.on('disable', function () { 52060 self.$container.addClass('select2-container--disabled'); 52061 }); 52062 52063 this.on('blur', function () { 52064 self.$container.removeClass('select2-container--focus'); 52065 }); 52066 52067 this.on('query', function (params) { 52068 if (!self.isOpen()) { 52069 self.trigger('open', {}); 52070 } 52071 52072 this.dataAdapter.query(params, function (data) { 52073 self.trigger('results:all', { 52074 data: data, 52075 query: params 52076 }); 52077 }); 52078 }); 52079 52080 this.on('query:append', function (params) { 52081 this.dataAdapter.query(params, function (data) { 52082 self.trigger('results:append', { 52083 data: data, 52084 query: params 52085 }); 52086 }); 52087 }); 52088 52089 this.on('keypress', function (evt) { 52090 var key = evt.which; 52091 52092 if (self.isOpen()) { 52093 if (key === KEYS.ESC || key === KEYS.TAB || 52094 (key === KEYS.UP && evt.altKey)) { 52095 self.close(evt); 52096 52097 evt.preventDefault(); 52098 } else if (key === KEYS.ENTER) { 52099 self.trigger('results:select', {}); 52100 52101 evt.preventDefault(); 52102 } else if ((key === KEYS.SPACE && evt.ctrlKey)) { 52103 self.trigger('results:toggle', {}); 52104 52105 evt.preventDefault(); 52106 } else if (key === KEYS.UP) { 52107 self.trigger('results:previous', {}); 52108 52109 evt.preventDefault(); 52110 } else if (key === KEYS.DOWN) { 52111 self.trigger('results:next', {}); 52112 52113 evt.preventDefault(); 52114 } 52115 } else { 52116 if (key === KEYS.ENTER || key === KEYS.SPACE || 52117 (key === KEYS.DOWN && evt.altKey)) { 52118 self.open(); 52119 52120 evt.preventDefault(); 52121 } 52122 } 52123 }); 52124 }; 52125 52126 Select2.prototype._syncAttributes = function () { 52127 this.options.set('disabled', this.$element.prop('disabled')); 52128 52129 if (this.isDisabled()) { 52130 if (this.isOpen()) { 52131 this.close(); 52132 } 52133 52134 this.trigger('disable', {}); 52135 } else { 52136 this.trigger('enable', {}); 52137 } 52138 }; 52139 52140 Select2.prototype._isChangeMutation = function (evt, mutations) { 52141 var changed = false; 52142 var self = this; 52143 52144 // Ignore any mutation events raised for elements that aren't options or 52145 // optgroups. This handles the case when the select element is destroyed 52146 if ( 52147 evt && evt.target && ( 52148 evt.target.nodeName !== 'OPTION' && evt.target.nodeName !== 'OPTGROUP' 52149 ) 52150 ) { 52151 return; 52152 } 52153 52154 if (!mutations) { 52155 // If mutation events aren't supported, then we can only assume that the 52156 // change affected the selections 52157 changed = true; 52158 } else if (mutations.addedNodes && mutations.addedNodes.length > 0) { 52159 for (var n = 0; n < mutations.addedNodes.length; n++) { 52160 var node = mutations.addedNodes[n]; 52161 52162 if (node.selected) { 52163 changed = true; 52164 } 52165 } 52166 } else if (mutations.removedNodes && mutations.removedNodes.length > 0) { 52167 changed = true; 52168 } else if ($.isArray(mutations)) { 52169 $.each(mutations, function(evt, mutation) { 52170 if (self._isChangeMutation(evt, mutation)) { 52171 // We've found a change mutation. 52172 // Let's escape from the loop and continue 52173 changed = true; 52174 return false; 52175 } 52176 }); 52177 } 52178 return changed; 52179 }; 52180 52181 Select2.prototype._syncSubtree = function (evt, mutations) { 52182 var changed = this._isChangeMutation(evt, mutations); 52183 var self = this; 52184 52185 // Only re-pull the data if we think there is a change 52186 if (changed) { 52187 this.dataAdapter.current(function (currentData) { 52188 self.trigger('selection:update', { 52189 data: currentData 52190 }); 52191 }); 52192 } 52193 }; 52194 52195 /** 52196 * Override the trigger method to automatically trigger pre-events when 52197 * there are events that can be prevented. 52198 */ 52199 Select2.prototype.trigger = function (name, args) { 52200 var actualTrigger = Select2.__super__.trigger; 52201 var preTriggerMap = { 52202 'open': 'opening', 52203 'close': 'closing', 52204 'select': 'selecting', 52205 'unselect': 'unselecting', 52206 'clear': 'clearing' 52207 }; 52208 52209 if (args === undefined) { 52210 args = {}; 52211 } 52212 52213 if (name in preTriggerMap) { 52214 var preTriggerName = preTriggerMap[name]; 52215 var preTriggerArgs = { 52216 prevented: false, 52217 name: name, 52218 args: args 52219 }; 52220 52221 actualTrigger.call(this, preTriggerName, preTriggerArgs); 52222 52223 if (preTriggerArgs.prevented) { 52224 args.prevented = true; 52225 52226 return; 52227 } 52228 } 52229 52230 actualTrigger.call(this, name, args); 52231 }; 52232 52233 Select2.prototype.toggleDropdown = function () { 52234 if (this.isDisabled()) { 52235 return; 52236 } 52237 52238 if (this.isOpen()) { 52239 this.close(); 52240 } else { 52241 this.open(); 52242 } 52243 }; 52244 52245 Select2.prototype.open = function () { 52246 if (this.isOpen()) { 52247 return; 52248 } 52249 52250 if (this.isDisabled()) { 52251 return; 52252 } 52253 52254 this.trigger('query', {}); 52255 }; 52256 52257 Select2.prototype.close = function (evt) { 52258 if (!this.isOpen()) { 52259 return; 52260 } 52261 52262 this.trigger('close', { originalEvent : evt }); 52263 }; 52264 52265 /** 52266 * Helper method to abstract the "enabled" (not "disabled") state of this 52267 * object. 52268 * 52269 * @return {true} if the instance is not disabled. 52270 * @return {false} if the instance is disabled. 52271 */ 52272 Select2.prototype.isEnabled = function () { 52273 return !this.isDisabled(); 52274 }; 52275 52276 /** 52277 * Helper method to abstract the "disabled" state of this object. 52278 * 52279 * @return {true} if the disabled option is true. 52280 * @return {false} if the disabled option is false. 52281 */ 52282 Select2.prototype.isDisabled = function () { 52283 return this.options.get('disabled'); 52284 }; 52285 52286 Select2.prototype.isOpen = function () { 52287 return this.$container.hasClass('select2-container--open'); 52288 }; 52289 52290 Select2.prototype.hasFocus = function () { 52291 return this.$container.hasClass('select2-container--focus'); 52292 }; 52293
52294 Select2.prototype.focus = function (data) { 52295 // No need to re-trigger focus events if we are already focused 52296 if (this.hasFocus()) { 52297 return; 52298 } 52299 52300 this.$container.addClass('select2-container--focus'); 52301 this.trigger('focus', {}); 52302 }; 52303 52304 Select2.prototype.enable = function (args) { 52305 if (this.options.get('debug') && window.console && console.warn) { 52306 console.warn( 52307 'Select2: The `select2("enable")` method has been deprecated and will' + 52308 ' be removed in later Select2 versions. Use $element.prop("disabled")' + 52309 ' instead.' 52310 ); 52311 } 52312 52313 if (args == null || args.length === 0) { 52314 args = [true]; 52315 } 52316 52317 var disabled = !args[0]; 52318 52319 this.$element.prop('disabled', disabled); 52320 }; 52321 52322 Select2.prototype.data = function () { 52323 if (this.options.get('debug') && 52324 arguments.length > 0 && window.console && console.warn) { 52325 console.warn( 52326 'Select2: Data can no longer be set using `select2("data")`. You ' + 52327 'should consider setting the value instead using `$element.val()`.' 52328 ); 52329 } 52330 52331 var data = []; 52332 52333 this.dataAdapter.current(function (currentData) { 52334 data = currentData; 52335 }); 52336 52337 return data; 52338 }; 52339 52340 Select2.prototype.val = function (args) { 52341 if (this.options.get('debug') && window.console && console.warn) { 52342 console.warn( 52343 'Select2: The `select2("val")` method has been deprecated and will be' + 52344 ' removed in later Select2 versions. Use $element.val() instead.' 52345 ); 52346 } 52347 52348 if (args == null || args.length === 0) { 52349 return this.$element.val(); 52350 } 52351 52352 var newVal = args[0]; 52353 52354 if ($.isArray(newVal)) { 52355 newVal = $.map(newVal, function (obj) { 52356 return obj.toString(); 52357 }); 52358 } 52359 52360 this.$element.val(newVal).trigger('input').trigger('change'); 52361 }; 52362 52363 Select2.prototype.destroy = function () { 52364 this.$container.remove(); 52365 52366 if (this.$element[0].detachEvent) { 52367 this.$element[0].detachEvent('onpropertychange', this._syncA); 52368 } 52369 52370 if (this._observer != null) { 52371 this._observer.disconnect(); 52372 this._observer = null; 52373 } else if (this.$element[0].removeEventListener) { 52374 this.$element[0] 52375 .removeEventListener('DOMAttrModified', this._syncA, false); 52376 this.$element[0] 52377 .removeEventListener('DOMNodeInserted', this._syncS, false); 52378 this.$element[0] 52379 .removeEventListener('DOMNodeRemoved', this._syncS, false); 52380 } 52381 52382 this._syncA = null; 52383 this._syncS = null; 52384 52385 this.$element.off('.select2'); 52386 this.$element.attr('tabindex', 52387 Utils.GetData(this.$element[0], 'old-tabindex')); 52388 52389 this.$element.removeClass('select2-hidden-accessible'); 52390 this.$element.attr('aria-hidden', 'false'); 52391 Utils.RemoveData(this.$element[0]); 52392 this.$element.removeData('select2'); 52393 52394 this.dataAdapter.destroy(); 52395 this.selection.destroy(); 52396 this.dropdown.destroy(); 52397 this.results.destroy(); 52398 52399 this.dataAdapter = null; 52400 this.selection = null; 52401 this.dropdown = null; 52402 this.results = null; 52403 }; 52404 52405 Select2.prototype.render = function () { 52406 var $container = $( 52407 '<span class="select2 select2-container">' + 52408 '<span class="selection"></span>' + 52409 '<span class="dropdown-wrapper" aria-hidden="true"></span>' + 52410 '</span>' 52411 ); 52412 52413 $container.attr('dir', this.options.get('dir')); 52414 52415 this.$container = $container; 52416 52417 this.$container.addClass('select2-container--' + this.options.get('theme')); 52418 52419 Utils.StoreData($container[0], 'element', this.$element); 52420 52421 return $container; 52422 }; 52423 52424 return Select2; 52425}); 52426 52427S2.define('jquery-mousewheel',[ 52428 'jquery' 52429], function ($) { 52430 // Used to shim jQuery.mousewheel for non-full builds. 52431 return $; 52432}); 52433 52434S2.define('jquery.select2',[ 52435 'jquery', 52436 'jquery-mousewheel', 52437 52438 './select2/core', 52439 './select2/defaults', 52440 './select2/utils' 52441], function ($, _, Select2, Defaults, Utils) { 52442 if ($.fn.select2 == null) { 52443 // All methods that should return the element 52444 var thisMethods = ['open', 'close', 'destroy']; 52445 52446 $.fn.select2 = function (options) { 52447 options = options || {}; 52448 52449 if (typeof options === 'object') { 52450 this.each(function () { 52451 var instanceOptions = $.extend(true, {}, options); 52452 52453 var instance = new Select2($(this), instanceOptions); 52454 }); 52455 52456 return this; 52457 } else if (typeof options === 'string') { 52458 var ret; 52459 var args = Array.prototype.slice.call(arguments, 1); 52460 52461 this.each(function () { 52462 var instance = Utils.GetData(this, 'select2'); 52463 52464 if (instance == null && window.console && console.error) { 52465 console.error( 52466 'The select2(\'' + options + '\') method was called on an ' + 52467 'element that is not using Select2.' 52468 ); 52469 } 52470 52471 ret = instance[options].apply(instance, args); 52472 }); 52473 52474 // Check if we should be returning `this` 52475 if ($.inArray(options, thisMethods) > -1) { 52476 return this; 52477 } 52478 52479 return ret; 52480 } else { 52481 throw new Error('Invalid arguments for Select2: ' + options); 52482 } 52483 }; 52484 } 52485 52486 if ($.fn.select2.defaults == null) { 52487 $.fn.select2.defaults = Defaults; 52488 } 52489 52490 return Select2; 52491}); 52492 52493 // Return the AMD loader configuration so it can be used outside of this file 52494 return { 52495 define: S2.define, 52496 require: S2.require 52497 }; 52498}()); 52499 52500 // Autoload the jQuery bindings 52501 // We know that all of the modules exist above this, so we're safe 52502 var select2 = S2.require('jquery.select2'); 52503 52504 // Hold the AMD module references on the jQuery function that was just loaded 52505 // This allows Select2 to use the internal loader outside of this file, such 52506 // as in the language files. 52507 jQuery.fn.select2.amd = S2; 52508 52509 // Return the Select2 instance for anyone who is importing it. 52510 return select2; 52511})); 52512 52513/*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 52514 52515!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/sv",[],function(){return{errorLoading:function(){return"Resultat kunde inte laddas."},inputTooLong:function(n){return"Vänligen sudda ut "+(n.input.length-n.maximum)+" tecken"},inputTooShort:function(n){return"Vänligen skriv in "+(n.minimum-n.input.length)+" eller fler tecken"}
vendor: 10,360 bytes, lines 52515-52878
52515,loadingMore:function(){return"Laddar fler resultatâ¦"},maximumSelected:function(n){return"Du kan max välja "+n.maximum+" element"},noResults:function(){return"Inga träffar"},searching:function(){return"Sökerâ¦"},removeAllItems:function(){return"Ta bort alla objekt"}}}),n.define,n.require}(); 52516(function (factory) { 52517 if (typeof define === "function" && define.amd) { 52518 // AMD. Register as an anonymous module. 52519 define([], factory); 52520 } else if (typeof exports === "object") { 52521 // Node/CommonJS 52522 module.exports = factory(); 52523 } else { 52524 // Browser globals 52525 window.wNumb = factory(); 52526 } 52527})(function () { 52528 "use strict"; 52529 52530 var FormatOptions = ["decimals", "thousand", "mark", "prefix", "suffix", "encoder", "decoder", "negativeBefore", "negative", "edit", "undo"]; 52531 52532 // General 52533 52534 // Reverse a string 52535 function strReverse(a) { 52536 return a.split("").reverse().join(""); 52537 } 52538 52539 // Check if a string starts with a specified prefix. 52540 function strStartsWith(input, match) { 52541 return input.substring(0, match.length) === match; 52542 } 52543 52544 // Check is a string ends in a specified suffix. 52545 function strEndsWith(input, match) { 52546 return input.slice(-1 * match.length) === match; 52547 } 52548 52549 // Throw an error if formatting options are incompatible. 52550 function throwEqualError(F, a, b) { 52551 if ((F[a] || F[b]) && F[a] === F[b]) { 52552 throw new Error(a); 52553 } 52554 } 52555 52556 // Check if a number is finite and not NaN 52557 function isValidNumber(input) { 52558 return typeof input === "number" && isFinite(input); 52559 } 52560 52561 // Provide rounding-accurate toFixed method. 52562 // Borrowed: http://stackoverflow.com/a/21323330/775265 52563 function toFixed(value, exp) { 52564 value = value.toString().split("e"); 52565 value = Math.round(+(value[0] + "e" + (value[1] ? +value[1] + exp : exp))); 52566 value = value.toString().split("e"); 52567 return (+(value[0] + "e" + (value[1] ? +value[1] - exp : -exp))).toFixed(exp); 52568 } 52569 52570 // Formatting 52571 52572 // Accept a number as input, output formatted string. 52573 function formatTo(decimals, thousand, mark, prefix, suffix, encoder, decoder, negativeBefore, negative, edit, undo, input) { 52574 var originalInput = input, 52575 inputIsNegative, 52576 inputPieces, 52577 inputBase, 52578 inputDecimals = "", 52579 output = ""; 52580 52581 // Apply user encoder to the input. 52582 // Expected outcome: number. 52583 if (encoder) { 52584 input = encoder(input); 52585 } 52586 52587 // Stop if no valid number was provided, the number is infinite or NaN. 52588 if (!isValidNumber(input)) { 52589 return false; 52590 } 52591 52592 // Rounding away decimals might cause a value of -0 52593 // when using very small ranges. Remove those cases. 52594 if (decimals !== false && parseFloat(input.toFixed(decimals)) === 0) { 52595 input = 0; 52596 } 52597 52598 // Formatting is done on absolute numbers, 52599 // decorated by an optional negative symbol. 52600 if (input < 0) { 52601 inputIsNegative = true; 52602 input = Math.abs(input); 52603 } 52604 52605 // Reduce the number of decimals to the specified option. 52606 if (decimals !== false) { 52607 input = toFixed(input, decimals); 52608 } 52609 52610 // Transform the number into a string, so it can be split. 52611 input = input.toString(); 52612 52613 // Break the number on the decimal separator. 52614 if (input.indexOf(".") !== -1) { 52615 inputPieces = input.split("."); 52616 52617 inputBase = inputPieces[0]; 52618 52619 if (mark) { 52620 inputDecimals = mark + inputPieces[1]; 52621 } 52622 } else { 52623 // If it isn't split, the entire number will do. 52624 inputBase = input; 52625 } 52626 52627 // Group numbers in sets of three. 52628 if (thousand) { 52629 inputBase = strReverse(inputBase).match(/.{1,3}/g); 52630 inputBase = strReverse(inputBase.join(strReverse(thousand))); 52631 } 52632 52633 // If the number is negative, prefix with negation symbol. 52634 if (inputIsNegative && negativeBefore) { 52635 output += negativeBefore; 52636 } 52637 52638 // Prefix the number 52639 if (prefix) { 52640 output += prefix; 52641 } 52642 52643 // Normal negative option comes after the prefix. Defaults to '-'. 52644 if (inputIsNegative && negative) { 52645 output += negative; 52646 } 52647 52648 // Append the actual number. 52649 output += inputBase; 52650 output += inputDecimals; 52651 52652 // Apply the suffix. 52653 if (suffix) { 52654 output += suffix; 52655 } 52656 52657 // Run the output through a user-specified post-formatter. 52658 if (edit) { 52659 output = edit(output, originalInput); 52660 } 52661 52662 // All done. 52663 return output; 52664 } 52665 52666 // Accept a sting as input, output decoded number. 52667 function formatFrom(decimals, thousand, mark, prefix, suffix, encoder, decoder, negativeBefore, negative, edit, undo, input) { 52668 var originalInput = input, 52669 inputIsNegative, 52670 output = ""; 52671 52672 // User defined pre-decoder. Result must be a non empty string. 52673 if (undo) { 52674 input = undo(input); 52675 } 52676 52677 // Test the input. Can't be empty. 52678 if (!input || typeof input !== "string") { 52679 return false; 52680 } 52681 52682 // If the string starts with the negativeBefore value: remove it. 52683 // Remember is was there, the number is negative. 52684 if (negativeBefore && strStartsWith(input, negativeBefore)) { 52685 input = input.replace(negativeBefore, ""); 52686 inputIsNegative = true; 52687 } 52688 52689 // Repeat the same procedure for the prefix. 52690 if (prefix && strStartsWith(input, prefix)) { 52691 input = input.replace(prefix, ""); 52692 } 52693 52694 // And again for negative. 52695 if (negative && strStartsWith(input, negative)) { 52696 input = input.replace(negative, ""); 52697 inputIsNegative = true; 52698 } 52699 52700 // Remove the suffix. 52701 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice 52702 if (suffix && strEndsWith(input, suffix)) { 52703 input = input.slice(0, -1 * suffix.length); 52704 } 52705 52706 // Remove the thousand grouping. 52707 if (thousand) { 52708 input = input.split(thousand).join(""); 52709 } 52710 52711 // Set the decimal separator back to period. 52712 if (mark) { 52713 input = input.replace(mark, "."); 52714 } 52715 52716 // Prepend the negative symbol. 52717 if (inputIsNegative) { 52718 output += "-"; 52719 } 52720 52721 // Add the number 52722 output += input; 52723 52724 // Trim all non-numeric characters (allow '.' and '-'); 52725 output = output.replace(/[^0-9\.\-.]/g, ""); 52726 52727 // The value contains no parse-able number. 52728 if (output === "") { 52729 return false; 52730 } 52731 52732 // Covert to number. 52733 output = Number(output); 52734 52735 // Run the user-specified post-decoder. 52736 if (decoder) { 52737 output = decoder(output); 52738 } 52739 52740 // Check is the output is valid, otherwise: return false. 52741 if (!isValidNumber(output)) { 52742 return false; 52743 } 52744 52745 return output; 52746 } 52747 52748 // Framework 52749 52750 // Validate formatting options 52751 function validate(inputOptions) { 52752 var i, 52753 optionName, 52754 optionValue, 52755 filteredOptions = {}; 52756 52757 if (inputOptions["suffix"] === undefined) { 52758 inputOptions["suffix"] = inputOptions["postfix"]; 52759 } 52760 52761 for (i = 0; i < FormatOptions.length; i += 1) { 52762 optionName = FormatOptions[i]; 52763 optionValue = inputOptions[optionName]; 52764 52765 if (optionValue === undefined) { 52766 // Only default if negativeBefore isn't set. 52767 if (optionName === "negative" && !filteredOptions.negativeBefore) { 52768 filteredOptions[optionName] = "-"; 52769 // Don't set a default for mark when 'thousand' is set. 52770 } else if (optionName === "mark" && filteredOptions.thousand !== ".") { 52771 filteredOptions[optionName] = "."; 52772 } else { 52773 filteredOptions[optionName] = false; 52774 } 52775 52776 // Floating points in JS are stable up to 7 decimals. 52777 } else if (optionName === "decimals") { 52778 if (optionValue >= 0 && optionValue < 8) { 52779 filteredOptions[optionName] = optionValue; 52780 } else { 52781 throw new Error(optionName); 52782 } 52783 52784 // These options, when provided, must be functions. 52785 } else if (optionName === "encoder" || optionName === "decoder" || optionName === "edit" || optionName === "undo") { 52786 if (typeof optionValue === "function") { 52787 filteredOptions[optionName] = optionValue; 52788 } else { 52789 throw new Error(optionName); 52790 } 52791 52792 // Other options are strings. 52793 } else { 52794 if (typeof optionValue === "string") { 52795 filteredOptions[optionName] = optionValue; 52796 } else { 52797 throw new Error(optionName); 52798 } 52799 } 52800 } 52801 52802 // Some values can't be extracted from a 52803 // string if certain combinations are present. 52804 throwEqualError(filteredOptions, "mark", "thousand"); 52805 throwEqualError(filteredOptions, "prefix", "negative"); 52806 throwEqualError(filteredOptions, "prefix", "negativeBefore"); 52807 52808 return filteredOptions; 52809 } 52810 52811 // Pass all options as function arguments 52812 function passAll(options, method, input) { 52813 var i, 52814 args = []; 52815 52816 // Add all options in order of FormatOptions 52817 for (i = 0; i < FormatOptions.length; i += 1) { 52818 args.push(options[FormatOptions[i]]); 52819 } 52820 52821 // Append the input, then call the method, presenting all 52822 // options as arguments. 52823 args.push(input); 52824 return method.apply("", args); 52825 } 52826 52827 function wNumb(options) { 52828 if (!(this instanceof wNumb)) { 52829 return new wNumb(options); 52830 } 52831 52832 if (typeof options !== "object") { 52833 return; 52834 } 52835 52836 options = validate(options); 52837 52838 // Call 'formatTo' with proper arguments. 52839 this.to = function (input) { 52840 return passAll(options, formatTo, input); 52841 }; 52842 52843 // Call 'formatFrom' with proper arguments. 52844 this.from = function (input) { 52845 return passAll(options, formatFrom, input); 52846 }; 52847 } 52848 52849 return wNumb; 52850}); 52851 52852/*! nouislider - 14.7.0 - 4/6/2021 */ 52853(function(factory) { 52854 if (typeof define === "function" && define.amd) { 52855 define([], factory); 52856 } else if (typeof exports === "object") { 52857 module.exports = factory(); 52858 } else { 52859 window.noUiSlider = factory(); 52860 } 52861})(function() { 52862 "use strict"; 52863 var VERSION = "14.7.0"; 52864 //region Helper Methods 52865 function isValidFormatter(entry) { 52866 return typeof entry === "object" && typeof entry.to === "function" && typeof entry.from === "function"; 52867 } 52868 function removeElement(el) { 52869 el.parentElement.removeChild(el); 52870 } 52871 function isSet(value) { 52872 return value !== null && value !== undefined; 52873 } 52874 // Bindable version 52875 function preventDefault(e) { 52876 e.preventDefault(); 52877 } 52878 // Removes duplicates from an array.
vendor: 8,282 bytes, lines 52879-53101
52879 function unique(array) { 52880 return array.filter(function (a) { 52881 return !this[a] ? (this[a] = true) : false; 52882 }, {}); 52883 } 52884 // Round a value to the closest 'to'. 52885 function closest(value, to) { 52886 return Math.round(value / to) * to; 52887 } 52888 // Current position of an element relative to the document. 52889 function offset(elem, orientation) { 52890 var rect = elem.getBoundingClientRect(); 52891 var doc = elem.ownerDocument; 52892 var docElem = doc.documentElement; 52893 var pageOffset = getPageOffset(doc); 52894 // getBoundingClientRect contains left scroll in Chrome on Android. 52895 // I haven't found a feature detection that proves this. Worst case 52896 // scenario on mis-match: the 'tap' feature on horizontal sliders breaks. 52897 if (/webkit.*Chrome.*Mobile/i.test(navigator.userAgent)) { 52898 pageOffset.x = 0; 52899 } 52900 return orientation ? rect.top + pageOffset.y - docElem.clientTop : rect.left + pageOffset.x - docElem.clientLeft; 52901 } 52902 // Checks whether a value is numerical. 52903 function isNumeric(a) { 52904 return typeof a === "number" && !isNaN(a) && isFinite(a); 52905 } 52906 // Sets a class and removes it after [duration] ms. 52907 function addClassFor(element, className, duration) { 52908 if (duration > 0) { 52909 addClass(element, className); 52910 setTimeout(function () { 52911 removeClass(element, className); 52912 }, duration); 52913 } 52914 } 52915 // Limits a value to 0 - 100 52916 function limit(a) { 52917 return Math.max(Math.min(a, 100), 0); 52918 } 52919 // Wraps a variable as an array, if it isn't one yet. 52920 // Note that an input array is returned by reference! 52921 function asArray(a) { 52922 return Array.isArray(a) ? a : [a]; 52923 } 52924 // Counts decimals 52925 function countDecimals(numStr) { 52926 numStr = String(numStr); 52927 var pieces = numStr.split("."); 52928 return pieces.length > 1 ? pieces[1].length : 0; 52929 } 52930 // http://youmightnotneedjquery.com/#add_class 52931 function addClass(el, className) { 52932 if (el.classList && !/\s/.test(className)) { 52933 el.classList.add(className); 52934 } 52935 else { 52936 el.className += " " + className; 52937 } 52938 } 52939 // http://youmightnotneedjquery.com/#remove_class 52940 function removeClass(el, className) { 52941 if (el.classList && !/\s/.test(className)) { 52942 el.classList.remove(className); 52943 } 52944 else { 52945 el.className = el.className.replace(new RegExp("(^|\\b)" + className.split(" ").join("|") + "(\\b|$)", "gi"), " "); 52946 } 52947 } 52948 // https://plainjs.com/javascript/attributes/adding-removing-and-testing-for-classes-9/ 52949 function hasClass(el, className) { 52950 return el.classList ? el.classList.contains(className) : new RegExp("\\b" + className + "\\b").test(el.className); 52951 } 52952 // https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollY#Notes 52953 function getPageOffset(doc) { 52954 var supportPageOffset = window.pageXOffset !== undefined; 52955 var isCSS1Compat = (doc.compatMode || "") === "CSS1Compat"; 52956 var x = supportPageOffset 52957 ? window.pageXOffset 52958 : isCSS1Compat 52959 ? doc.documentElement.scrollLeft 52960 : doc.body.scrollLeft; 52961 var y = supportPageOffset ? window.pageYOffset : isCSS1Compat ? doc.documentElement.scrollTop : doc.body.scrollTop; 52962 return { 52963 x: x, 52964 y: y 52965 }; 52966 } 52967 // we provide a function to compute constants instead 52968 // of accessing window.* as soon as the module needs it 52969 // so that we do not compute anything if not needed 52970 function getActions() { 52971 // Determine the events to bind. IE11 implements pointerEvents without 52972 // a prefix, which breaks compatibility with the IE10 implementation. 52973 return window.navigator.pointerEnabled 52974 ? { 52975 start: "pointerdown", 52976 move: "pointermove", 52977 end: "pointerup" 52978 } 52979 : window.navigator.msPointerEnabled 52980 ? { 52981 start: "MSPointerDown", 52982 move: "MSPointerMove", 52983 end: "MSPointerUp" 52984 } 52985 : { 52986 start: "mousedown touchstart", 52987 move: "mousemove touchmove", 52988 end: "mouseup touchend" 52989 }; 52990 } 52991 // https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md 52992 // Issue #785 52993 function getSupportsPassive() { 52994 var supportsPassive = false; 52995 /* eslint-disable */ 52996 try { 52997 var opts = Object.defineProperty({}, "passive", { 52998 get: function () { 52999 supportsPassive = true; 53000 } 53001 }); 53002 window.addEventListener("test", null, opts); 53003 } 53004 catch (e) { } 53005 /* eslint-enable */ 53006 return supportsPassive; 53007 } 53008 function getSupportsTouchActionNone() { 53009 return window.CSS && CSS.supports && CSS.supports("touch-action", "none"); 53010 } 53011 //endregion 53012 //region Range Calculation 53013 // Determine the size of a sub-range in relation to a full range. 53014 function subRangeRatio(pa, pb) { 53015 return 100 / (pb - pa); 53016 } 53017 // (percentage) How many percent is this value of this range? 53018 function fromPercentage(range, value, startRange) { 53019 return (value * 100) / (range[startRange + 1] - range[startRange]); 53020 } 53021 // (percentage) Where is this value on this range? 53022 function toPercentage(range, value) { 53023 return fromPercentage(range, range[0] < 0 ? value + Math.abs(range[0]) : value - range[0], 0); 53024 } 53025 // (value) How much is this percentage on this range? 53026 function isPercentage(range, value) { 53027 return (value * (range[1] - range[0])) / 100 + range[0]; 53028 } 53029 function getJ(value, arr) { 53030 var j = 1; 53031 while (value >= arr[j]) { 53032 j += 1; 53033 } 53034 return j; 53035 } 53036 // (percentage) Input a value, find where, on a scale of 0-100, it applies. 53037 function toStepping(xVal, xPct, value) { 53038 if (value >= xVal.slice(-1)[0]) { 53039 return 100; 53040 } 53041 var j = getJ(value, xVal); 53042 var va = xVal[j - 1]; 53043 var vb = xVal[j]; 53044 var pa = xPct[j - 1]; 53045 var pb = xPct[j]; 53046 return pa + toPercentage([va, vb], value) / subRangeRatio(pa, pb); 53047 } 53048 // (value) Input a percentage, find where it is on the specified range. 53049 function fromStepping(xVal, xPct, value) { 53050 // There is no range group that fits 100 53051 if (value >= 100) { 53052 return xVal.slice(-1)[0]; 53053 } 53054 var j = getJ(value, xPct); 53055 var va = xVal[j - 1]; 53056 var vb = xVal[j]; 53057 var pa = xPct[j - 1]; 53058 var pb = xPct[j]; 53059 return isPercentage([va, vb], (value - pa) * subRangeRatio(pa, pb)); 53060 } 53061 // (percentage) Get the step that applies at a certain value. 53062 function getStep(xPct, xSteps, snap, value) { 53063 if (value === 100) { 53064 return value; 53065 } 53066 var j = getJ(value, xPct); 53067 var a = xPct[j - 1]; 53068 var b = xPct[j]; 53069 // If 'snap' is set, steps are used as fixed points on the slider. 53070 if (snap) { 53071 // Find the closest position, a or b. 53072 if (value - a > (b - a) / 2) { 53073 return b; 53074 } 53075 return a; 53076 } 53077 if (!xSteps[j - 1]) { 53078 return value; 53079 } 53080 return xPct[j - 1] + closest(value - xPct[j - 1], xSteps[j - 1]); 53081 } 53082 function handleEntryPoint(index, value, that) { 53083 var percentage; 53084 // Wrap numerical input in an array. 53085 if (typeof value === "number") { 53086 value = [value]; 53087 } 53088 // Reject any invalid input, by testing whether value is an array. 53089 if (!Array.isArray(value)) { 53090 throw new Error("noUiSlider (" + VERSION + "): 'range' contains invalid value."); 53091 } 53092 // Covert min/max syntax to 0 and 100. 53093 if (index === "min") { 53094 percentage = 0; 53095 } 53096 else if (index === "max") { 53097 percentage = 100; 53098 } 53099 else { 53100 percentage = parseFloat(index); 53101 }
53102 // Check for correct input. 53103 if (!isNumeric(percentage) || !isNumeric(value[0])) { 53104 throw new Error("noUiSlider (" + VERSION + "): 'range' value isn't numeric."); 53105 } 53106 // Store values. 53107 that.xPct.push(percentage); 53108 that.xVal.push(value[0]); 53109 // NaN will evaluate to false too, but to keep 53110 // logging clear, set step explicitly. Make sure 53111 // not to override the 'step' setting with false. 53112 if (!percentage) { 53113 if (!isNaN(value[1])) { 53114 that.xSteps[0] = value[1]; 53115 } 53116 } 53117 else { 53118 that.xSteps.push(isNaN(value[1]) ? false : value[1]); 53119 } 53120 that.xHighestCompleteStep.push(0); 53121 } 53122 function handleStepPoint(i, n, that) { 53123 // Ignore 'false' stepping. 53124 if (!n) { 53125 return; 53126 } 53127 // Step over zero-length ranges (#948); 53128 if (that.xVal[i] === that.xVal[i + 1]) { 53129 that.xSteps[i] = that.xHighestCompleteStep[i] = that.xVal[i]; 53130 return; 53131 } 53132 // Factor to range ratio 53133 that.xSteps[i] = 53134 fromPercentage([that.xVal[i], that.xVal[i + 1]], n, 0) / subRangeRatio(that.xPct[i], that.xPct[i + 1]); 53135 var totalSteps = (that.xVal[i + 1] - that.xVal[i]) / that.xNumSteps[i]; 53136 var highestStep = Math.ceil(Number(totalSteps.toFixed(3)) - 1); 53137 var step = that.xVal[i] + that.xNumSteps[i] * highestStep; 53138 that.xHighestCompleteStep[i] = step; 53139 } 53140 //endregion 53141 //region Spectrum 53142 function Spectrum(entry, snap, singleStep) { 53143 this.xPct = []; 53144 this.xVal = []; 53145 this.xSteps = [singleStep || false]; 53146 this.xNumSteps = [false]; 53147 this.xHighestCompleteStep = []; 53148 this.snap = snap; 53149 var index; 53150 var ordered = []; // [0, 'min'], [1, '50%'], [2, 'max'] 53151 // Map the object keys to an array. 53152 for (index in entry) { 53153 if (entry.hasOwnProperty(index)) { 53154 ordered.push([entry[index], index]); 53155 } 53156 } 53157 // Sort all entries by value (numeric sort). 53158 if (ordered.length && typeof ordered[0][0] === "object") { 53159 ordered.sort(function (a, b) { 53160 return a[0][0] - b[0][0]; 53161 }); 53162 } 53163 else { 53164 ordered.sort(function (a, b) { 53165 return a[0] - b[0]; 53166 }); 53167 } 53168 // Convert all entries to subranges. 53169 for (index = 0; index < ordered.length; index++) { 53170 handleEntryPoint(ordered[index][1], ordered[index][0], this); 53171 } 53172 // Store the actual step values. 53173 // xSteps is sorted in the same order as xPct and xVal. 53174 this.xNumSteps = this.xSteps.slice(0); 53175 // Convert all numeric steps to the percentage of the subrange they represent. 53176 for (index = 0; index < this.xNumSteps.length; index++) { 53177 handleStepPoint(index, this.xNumSteps[index], this); 53178 } 53179 } 53180 Spectrum.prototype.getDistance = function (value) { 53181 var index; 53182 var distances = []; 53183 for (index = 0; index < this.xNumSteps.length - 1; index++) {
vendor: 5,217 bytes, lines 53184-53291
53184 // last "range" can't contain step size as it is purely an endpoint. 53185 var step = this.xNumSteps[index]; 53186 if (step && (value / step) % 1 !== 0) { 53187 throw new Error("noUiSlider (" + 53188 VERSION + 53189 "): 'limit', 'margin' and 'padding' of " + 53190 this.xPct[index] + 53191 "% range must be divisible by step."); 53192 } 53193 // Calculate percentual distance in current range of limit, margin or padding 53194 distances[index] = fromPercentage(this.xVal, value, index); 53195 } 53196 return distances; 53197 }; 53198 // Calculate the percentual distance over the whole scale of ranges. 53199 // direction: 0 = backwards / 1 = forwards 53200 Spectrum.prototype.getAbsoluteDistance = function (value, distances, direction) { 53201 var xPct_index = 0; 53202 // Calculate range where to start calculation 53203 if (value < this.xPct[this.xPct.length - 1]) { 53204 while (value > this.xPct[xPct_index + 1]) { 53205 xPct_index++; 53206 } 53207 } 53208 else if (value === this.xPct[this.xPct.length - 1]) { 53209 xPct_index = this.xPct.length - 2; 53210 } 53211 // If looking backwards and the value is exactly at a range separator then look one range further 53212 if (!direction && value === this.xPct[xPct_index + 1]) { 53213 xPct_index++; 53214 } 53215 var start_factor; 53216 var rest_factor = 1; 53217 var rest_rel_distance = distances[xPct_index]; 53218 var range_pct = 0; 53219 var rel_range_distance = 0; 53220 var abs_distance_counter = 0; 53221 var range_counter = 0; 53222 // Calculate what part of the start range the value is 53223 if (direction) { 53224 start_factor = (value - this.xPct[xPct_index]) / (this.xPct[xPct_index + 1] - this.xPct[xPct_index]); 53225 } 53226 else { 53227 start_factor = (this.xPct[xPct_index + 1] - value) / (this.xPct[xPct_index + 1] - this.xPct[xPct_index]); 53228 } 53229 // Do until the complete distance across ranges is calculated 53230 while (rest_rel_distance > 0) { 53231 // Calculate the percentage of total range 53232 range_pct = this.xPct[xPct_index + 1 + range_counter] - this.xPct[xPct_index + range_counter]; 53233 // Detect if the margin, padding or limit is larger then the current range and calculate 53234 if (distances[xPct_index + range_counter] * rest_factor + 100 - start_factor * 100 > 100) { 53235 // If larger then take the percentual distance of the whole range 53236 rel_range_distance = range_pct * start_factor; 53237 // Rest factor of relative percentual distance still to be calculated 53238 rest_factor = (rest_rel_distance - 100 * start_factor) / distances[xPct_index + range_counter]; 53239 // Set start factor to 1 as for next range it does not apply. 53240 start_factor = 1; 53241 } 53242 else { 53243 // If smaller or equal then take the percentual distance of the calculate percentual part of that range 53244 rel_range_distance = ((distances[xPct_index + range_counter] * range_pct) / 100) * rest_factor; 53245 // No rest left as the rest fits in current range 53246 rest_factor = 0; 53247 } 53248 if (direction) { 53249 abs_distance_counter = abs_distance_counter - rel_range_distance; 53250 // Limit range to first range when distance becomes outside of minimum range 53251 if (this.xPct.length + range_counter >= 1) { 53252 range_counter--; 53253 } 53254 } 53255 else { 53256 abs_distance_counter = abs_distance_counter + rel_range_distance; 53257 // Limit range to last range when distance becomes outside of maximum range 53258 if (this.xPct.length - range_counter >= 1) { 53259 range_counter++; 53260 } 53261 } 53262 // Rest of relative percentual distance still to be calculated 53263 rest_rel_distance = distances[xPct_index + range_counter] * rest_factor; 53264 } 53265 return value + abs_distance_counter; 53266 }; 53267 Spectrum.prototype.toStepping = function (value) { 53268 value = toStepping(this.xVal, this.xPct, value); 53269 return value; 53270 }; 53271 Spectrum.prototype.fromStepping = function (value) { 53272 return fromStepping(this.xVal, this.xPct, value); 53273 }; 53274 Spectrum.prototype.getStep = function (value) { 53275 value = getStep(this.xPct, this.xSteps, this.snap, value); 53276 return value; 53277 }; 53278 Spectrum.prototype.getDefaultStep = function (value, isDown, size) { 53279 var j = getJ(value, this.xPct); 53280 // When at the top or stepping down, look at the previous sub-range 53281 if (value === 100 || (isDown && value === this.xPct[j - 1])) { 53282 j = Math.max(j - 1, 1); 53283 } 53284 return (this.xVal[j] - this.xVal[j - 1]) / size; 53285 }; 53286 Spectrum.prototype.getNearbySteps = function (value) { 53287 var j = getJ(value, this.xPct); 53288 return { 53289 stepBefore: { 53290 startValue: this.xVal[j - 2], 53291 step: this.xNumSteps[j - 2],
53292 highestStep: this.xHighestCompleteStep[j - 2] 53293 }, 53294 thisStep: { 53295 startValue: this.xVal[j - 1], 53296 step: this.xNumSteps[j - 1], 53297 highestStep: this.xHighestCompleteStep[j - 1] 53298 }, 53299 stepAfter: { 53300 startValue: this.xVal[j], 53301 step: this.xNumSteps[j], 53302 highestStep: this.xHighestCompleteStep[j] 53303 } 53304 }; 53305 }; 53306 Spectrum.prototype.countStepDecimals = function () { 53307 var stepDecimals = this.xNumSteps.map(countDecimals); 53308 return Math.max.apply(null, stepDecimals); 53309 }; 53310 // Outside testing 53311 Spectrum.prototype.convert = function (value) { 53312 return this.getStep(this.toStepping(value)); 53313 }; 53314 //endregion 53315 //region Options 53316 /* Every input option is tested and parsed. This'll prevent 53317 endless validation in internal methods. These tests are 53318 structured with an item for every option available. An 53319 option can be marked as required by setting the 'r' flag. 53320 The testing function is provided with three arguments: 53321 - The provided value for the option; 53322 - A reference to the options object; 53323 - The name for the option; 53324 53325 The testing function returns false when an error is detected, 53326 or true when everything is OK. It can also modify the option 53327 object, to make sure all values can be correctly looped elsewhere. */ 53328 //region Defaults 53329 var defaultFormatter = { 53330 to: function (value) { 53331 return value !== undefined && value.toFixed(2); 53332 }, 53333 from: Number 53334 };
53335 var cssClasses = { 53336 target: "target", 53337 base: "base", 53338 origin: "origin", 53339 handle: "handle", 53340 handleLower: "handle-lower", 53341 handleUpper: "handle-upper", 53342 touchArea: "touch-area", 53343 horizontal: "horizontal", 53344 vertical: "vertical", 53345 background: "background", 53346 connect: "connect", 53347 connects: "connects", 53348 ltr: "ltr", 53349 rtl: "rtl", 53350 textDirectionLtr: "txt-dir-ltr", 53351 textDirectionRtl: "txt-dir-rtl", 53352 draggable: "draggable", 53353 drag: "state-drag", 53354 tap: "state-tap", 53355 active: "active", 53356 tooltip: "tooltip", 53357 pips: "pips", 53358 pipsHorizontal: "pips-horizontal", 53359 pipsVertical: "pips-vertical", 53360 marker: "marker", 53361 markerHorizontal: "marker-horizontal", 53362 markerVertical: "marker-vertical", 53363 markerNormal: "marker-normal", 53364 markerLarge: "marker-large", 53365 markerSub: "marker-sub", 53366 value: "value", 53367 valueHorizontal: "value-horizontal", 53368 valueVertical: "value-vertical", 53369 valueNormal: "value-normal", 53370 valueLarge: "value-large", 53371 valueSub: "value-sub" 53372 }; 53373 // Namespaces of internal event listeners 53374 var INTERNAL_EVENT_NS = { 53375 tooltips: ".__tooltips", 53376 aria: ".__aria" 53377 }; 53378 //endregion 53379 function validateFormat(entry) { 53380 // Any object with a to and from method is supported. 53381 if (isValidFormatter(entry)) { 53382 return true; 53383 } 53384 throw new Error("noUiSlider (" + VERSION + "): 'format' requires 'to' and 'from' methods."); 53385 } 53386 function testStep(parsed, entry) { 53387 if (!isNumeric(entry)) { 53388 throw new Error("noUiSlider (" + VERSION + "): 'step' is not numeric."); 53389 } 53390 // The step option can still be used to set stepping 53391 // for linear sliders. Overwritten if set in 'range'. 53392 parsed.singleStep = entry; 53393 } 53394 function testKeyboardPageMultiplier(parsed, entry) { 53395 if (!isNumeric(entry)) { 53396 throw new Error("noUiSlider (" + VERSION + "): 'keyboardPageMultiplier' is not numeric."); 53397 } 53398 parsed.keyboardPageMultiplier = entry; 53399 } 53400 function testKeyboardDefaultStep(parsed, entry) { 53401 if (!isNumeric(entry)) { 53402 throw new Error("noUiSlider (" + VERSION + "): 'keyboardDefaultStep' is not numeric."); 53403 } 53404 parsed.keyboardDefaultStep = entry; 53405 } 53406 function testRange(parsed, entry) { 53407 // Filter incorrect input. 53408 if (typeof entry !== "object" || Array.isArray(entry)) { 53409 throw new Error("noUiSlider (" + VERSION + "): 'range' is not an object."); 53410 } 53411 // Catch missing start or end. 53412 if (entry.min === undefined || entry.max === undefined) { 53413 throw new Error("noUiSlider (" + VERSION + "): Missing 'min' or 'max' in 'range'."); 53414 } 53415 // Catch equal start or end. 53416 if (entry.min === entry.max) { 53417 throw new Error("noUiSlider (" + VERSION + "): 'range' 'min' and 'max' cannot be equal."); 53418 } 53419 parsed.spectrum = new Spectrum(entry, parsed.snap, parsed.singleStep); 53420 } 53421 function testStart(parsed, entry) { 53422 entry = asArray(entry); 53423 // Validate input. Values aren't tested, as the public .val method 53424 // will always provide a valid location. 53425 if (!Array.isArray(entry) || !entry.length) { 53426 throw new Error("noUiSlider (" + VERSION + "): 'start' option is incorrect."); 53427 } 53428 // Store the number of handles. 53429 parsed.handles = entry.length; 53430 // When the slider is initialized, the .val method will 53431 // be called with the start options. 53432 parsed.start = entry; 53433 } 53434 function testSnap(parsed, entry) { 53435 // Enforce 100% stepping within subranges. 53436 parsed.snap = entry; 53437 if (typeof entry !== "boolean") { 53438 throw new Error("noUiSlider (" + VERSION + "): 'snap' option must be a boolean."); 53439 } 53440 } 53441 function testAnimate(parsed, entry) { 53442 // Enforce 100% stepping within subranges. 53443 parsed.animate = entry; 53444 if (typeof entry !== "boolean") { 53445 throw new Error("noUiSlider (" + VERSION + "): 'animate' option must be a boolean."); 53446 } 53447 } 53448 function testAnimationDuration(parsed, entry) { 53449 parsed.animationDuration = entry; 53450 if (typeof entry !== "number") { 53451 throw new Error("noUiSlider (" + VERSION + "): 'animationDuration' option must be a number."); 53452 } 53453 } 53454 function testConnect(parsed, entry) { 53455 var connect = [false]; 53456 var i; 53457 // Map legacy options 53458 if (entry === "lower") { 53459 entry = [true, false]; 53460 } 53461 else if (entry === "upper") {
53462 entry = [false, true]; 53463 } 53464 // Handle boolean options 53465 if (entry === true || entry === false) { 53466 for (i = 1; i < parsed.handles; i++) { 53467 connect.push(entry); 53468 } 53469 connect.push(false); 53470 } 53471 // Reject invalid input 53472 else if (!Array.isArray(entry) || !entry.length || entry.length !== parsed.handles + 1) { 53473 throw new Error("noUiSlider (" + VERSION + "): 'connect' option doesn't match handle count."); 53474 } 53475 else { 53476 connect = entry; 53477 } 53478 parsed.connect = connect; 53479 } 53480 function testOrientation(parsed, entry) { 53481 // Set orientation to an a numerical value for easy 53482 // array selection. 53483 switch (entry) { 53484 case "horizontal": 53485 parsed.ort = 0; 53486 break; 53487 case "vertical": 53488 parsed.ort = 1; 53489 break; 53490 default: 53491 throw new Error("noUiSlider (" + VERSION + "): 'orientation' option is invalid."); 53492 } 53493 } 53494 function testMargin(parsed, entry) { 53495 if (!isNumeric(entry)) { 53496 throw new Error("noUiSlider (" + VERSION + "): 'margin' option must be numeric."); 53497 } 53498 // Issue #582 53499 if (entry === 0) { 53500 return; 53501 } 53502 parsed.margin = parsed.spectrum.getDistance(entry); 53503 } 53504 function testLimit(parsed, entry) { 53505 if (!isNumeric(entry)) { 53506 throw new Error("noUiSlider (" + VERSION + "): 'limit' option must be numeric."); 53507 } 53508 parsed.limit = parsed.spectrum.getDistance(entry); 53509 if (!parsed.limit || parsed.handles < 2) { 53510 throw new Error("noUiSlider (" + VERSION + "): 'limit' option is only supported on linear sliders with 2 or more handles."); 53511 } 53512 } 53513 function testPadding(parsed, entry) { 53514 var index; 53515 if (!isNumeric(entry) && !Array.isArray(entry)) { 53516 throw new Error("noUiSlider (" + VERSION + "): 'padding' option must be numeric or array of exactly 2 numbers."); 53517 } 53518 if (Array.isArray(entry) && !(entry.length === 2 || isNumeric(entry[0]) || isNumeric(entry[1]))) { 53519 throw new Error("noUiSlider (" + VERSION + "): 'padding' option must be numeric or array of exactly 2 numbers."); 53520 } 53521 if (entry === 0) { 53522 return; 53523 } 53524 if (!Array.isArray(entry)) { 53525 entry = [entry, entry]; 53526 } 53527 // 'getDistance' returns false for invalid values. 53528 parsed.padding = [parsed.spectrum.getDistance(entry[0]), parsed.spectrum.getDistance(entry[1])]; 53529 for (index = 0; index < parsed.spectrum.xNumSteps.length - 1; index++) {
53530 // last "range" can't contain step size as it is purely an endpoint. 53531 if (parsed.padding[0][index] < 0 || parsed.padding[1][index] < 0) { 53532 throw new Error("noUiSlider (" + VERSION + "): 'padding' option must be a positive number(s)."); 53533 } 53534 } 53535 var totalPadding = entry[0] + entry[1]; 53536 var firstValue = parsed.spectrum.xVal[0]; 53537 var lastValue = parsed.spectrum.xVal[parsed.spectrum.xVal.length - 1]; 53538 if (totalPadding / (lastValue - firstValue) > 1) { 53539 throw new Error("noUiSlider (" + VERSION + "): 'padding' option must not exceed 100% of the range."); 53540 } 53541 } 53542 function testDirection(parsed, entry) { 53543 // Set direction as a numerical value for easy parsing. 53544 // Invert connection for RTL sliders, so that the proper 53545 // handles get the connect/background classes. 53546 switch (entry) { 53547 case "ltr": 53548 parsed.dir = 0; 53549 break; 53550 case "rtl": 53551 parsed.dir = 1; 53552 break; 53553 default: 53554 throw new Error("noUiSlider (" + VERSION + "): 'direction' option was not recognized."); 53555 } 53556 } 53557 function testBehaviour(parsed, entry) { 53558 // Make sure the input is a string. 53559 if (typeof entry !== "string") { 53560 throw new Error("noUiSlider (" + VERSION + "): 'behaviour' must be a string containing options."); 53561 } 53562 // Check if the string contains any keywords. 53563 // None are required. 53564 var tap = entry.indexOf("tap") >= 0; 53565 var drag = entry.indexOf("drag") >= 0; 53566 var fixed = entry.indexOf("fixed") >= 0; 53567 var snap = entry.indexOf("snap") >= 0; 53568 var hover = entry.indexOf("hover") >= 0; 53569 var unconstrained = entry.indexOf("unconstrained") >= 0; 53570 if (fixed) { 53571 if (parsed.handles !== 2) { 53572 throw new Error("noUiSlider (" + VERSION + "): 'fixed' behaviour must be used with 2 handles"); 53573 } 53574 // Use margin to enforce fixed state 53575 testMargin(parsed, parsed.start[1] - parsed.start[0]); 53576 } 53577 if (unconstrained && (parsed.margin || parsed.limit)) { 53578 throw new Error("noUiSlider (" + VERSION + "): 'unconstrained' behaviour cannot be used with margin or limit"); 53579 } 53580 parsed.events = { 53581 tap: tap || snap, 53582 drag: drag, 53583 fixed: fixed, 53584 snap: snap, 53585 hover: hover, 53586 unconstrained: unconstrained 53587 }; 53588 } 53589 function testTooltips(parsed, entry) { 53590 if (entry === false) { 53591 return; 53592 } 53593 if (entry === true) { 53594 parsed.tooltips = []; 53595 for (var i = 0; i < parsed.handles; i++) { 53596 parsed.tooltips.push(true); 53597 } 53598 } 53599 else { 53600 parsed.tooltips = asArray(entry); 53601 if (parsed.tooltips.length !== parsed.handles) { 53602 throw new Error("noUiSlider (" + VERSION + "): must pass a formatter for all handles."); 53603 } 53604 parsed.tooltips.forEach(function (formatter) { 53605 if (typeof formatter !== "boolean" && 53606 (typeof formatter !== "object" || typeof formatter.to !== "function")) { 53607 throw new Error("noUiSlider (" + VERSION + "): 'tooltips' must be passed a formatter or 'false'."); 53608 } 53609 }); 53610 } 53611 } 53612 function testAriaFormat(parsed, entry) { 53613 parsed.ariaFormat = entry; 53614 validateFormat(entry); 53615 } 53616 function testFormat(parsed, entry) { 53617 parsed.format = entry; 53618 validateFormat(entry); 53619 } 53620 function testKeyboardSupport(parsed, entry) { 53621 parsed.keyboardSupport = entry; 53622 if (typeof entry !== "boolean") { 53623 throw new Error("noUiSlider (" + VERSION + "): 'keyboardSupport' option must be a boolean."); 53624 } 53625 } 53626 function testDocumentElement(parsed, entry) { 53627 // This is an advanced option. Passed values are used without validation. 53628 parsed.documentElement = entry; 53629 } 53630 function testCssPrefix(parsed, entry) { 53631 if (typeof entry !== "string" && entry !== false) { 53632 throw new Error("noUiSlider (" + VERSION + "): 'cssPrefix' must be a string or `false`."); 53633 } 53634 parsed.cssPrefix = entry; 53635 }
53636 function testCssClasses(parsed, entry) { 53637 if (typeof entry !== "object") { 53638 throw new Error("noUiSlider (" + VERSION + "): 'cssClasses' must be an object."); 53639 } 53640 if (typeof parsed.cssPrefix === "string") { 53641 parsed.cssClasses = {}; 53642 for (var key in entry) { 53643 if (!entry.hasOwnProperty(key)) { 53644 continue; 53645 } 53646 parsed.cssClasses[key] = parsed.cssPrefix + entry[key]; 53647 } 53648 } 53649 else { 53650 parsed.cssClasses = entry; 53651 } 53652 } 53653 // Test all developer settings and parse to assumption-safe values. 53654 function testOptions(options) { 53655 // To prove a fix for #537, freeze options here. 53656 // If the object is modified, an error will be thrown. 53657 // Object.freeze(options); 53658 var parsed = { 53659 margin: 0, 53660 limit: 0, 53661 padding: 0, 53662 animate: true, 53663 animationDuration: 300, 53664 ariaFormat: defaultFormatter, 53665 format: defaultFormatter 53666 }; 53667 // Tests are executed in the order they are presented here. 53668 var tests = { 53669 step: { r: false, t: testStep }, 53670 keyboardPageMultiplier: { r: false, t: testKeyboardPageMultiplier }, 53671 keyboardDefaultStep: { r: false, t: testKeyboardDefaultStep }, 53672 start: { r: true, t: testStart }, 53673 connect: { r: true, t: testConnect }, 53674 direction: { r: true, t: testDirection }, 53675 snap: { r: false, t: testSnap }, 53676 animate: { r: false, t: testAnimate }, 53677 animationDuration: { r: false, t: testAnimationDuration }, 53678 range: { r: true, t: testRange }, 53679 orientation: { r: false, t: testOrientation }, 53680 margin: { r: false, t: testMargin }, 53681 limit: { r: false, t: testLimit }, 53682 padding: { r: false, t: testPadding }, 53683 behaviour: { r: true, t: testBehaviour }, 53684 ariaFormat: { r: false, t: testAriaFormat }, 53685 format: { r: false, t: testFormat }, 53686 tooltips: { r: false, t: testTooltips }, 53687 keyboardSupport: { r: true, t: testKeyboardSupport }, 53688 documentElement: { r: false, t: testDocumentElement }, 53689 cssPrefix: { r: true, t: testCssPrefix },
53690 cssClasses: { r: true, t: testCssClasses } 53691 }; 53692 var defaults = { 53693 connect: false, 53694 direction: "ltr", 53695 behaviour: "tap", 53696 orientation: "horizontal", 53697 keyboardSupport: true, 53698 cssPrefix: "noUi-", 53699 cssClasses: cssClasses, 53700 keyboardPageMultiplier: 5, 53701 keyboardDefaultStep: 10 53702 }; 53703 // AriaFormat defaults to regular format, if any. 53704 if (options.format && !options.ariaFormat) { 53705 options.ariaFormat = options.format; 53706 } 53707 // Run all options through a testing mechanism to ensure correct 53708 // input. It should be noted that options might get modified to 53709 // be handled properly. E.g. wrapping integers in arrays. 53710 Object.keys(tests).forEach(function (name) { 53711 // If the option isn't set, but it is required, throw an error. 53712 if (!isSet(options[name]) && defaults[name] === undefined) { 53713 if (tests[name].r) { 53714 throw new Error("noUiSlider (" + VERSION + "): '" + name + "' is required."); 53715 } 53716 return true; 53717 } 53718 tests[name].t(parsed, !isSet(options[name]) ? defaults[name] : options[name]); 53719 }); 53720 // Forward pips options 53721 parsed.pips = options.pips; 53722 // All recent browsers accept unprefixed transform. 53723 // We need -ms- for IE9 and -webkit- for older Android; 53724 // Assume use of -webkit- if unprefixed and -ms- are not supported. 53725 // https://caniuse.com/#feat=transforms2d 53726 var d = document.createElement("div"); 53727 var msPrefix = d.style.msTransform !== undefined; 53728 var noPrefix = d.style.transform !== undefined; 53729 parsed.transformRule = noPrefix ? "transform" : msPrefix ? "msTransform" : "webkitTransform"; 53730 // Pips don't move, so we can place them using left/top. 53731 var styles = [["left", "top"], ["right", "bottom"]]; 53732 parsed.style = styles[parsed.dir][parsed.ort]; 53733 return parsed; 53734 } 53735 //endregion 53736 function scope(target, options, originalOptions) { 53737 var actions = getActions(); 53738 var supportsTouchActionNone = getSupportsTouchActionNone(); 53739 var supportsPassive = supportsTouchActionNone && getSupportsPassive(); 53740 // All variables local to 'scope' are prefixed with 'scope_' 53741 // Slider DOM Nodes 53742 var scope_Target = target; 53743 var scope_Base; 53744 var scope_Handles; 53745 var scope_Connects; 53746 var scope_Pips; 53747 var scope_Tooltips; 53748 // Slider state values 53749 var scope_Spectrum = options.spectrum; 53750 var scope_Values = []; 53751 var scope_Locations = []; 53752 var scope_HandleNumbers = []; 53753 var scope_ActiveHandlesCount = 0; 53754 var scope_Events = {}; 53755 // Exposed API 53756 var scope_Self; 53757 // Document Nodes 53758 var scope_Document = target.ownerDocument; 53759 var scope_DocumentElement = options.documentElement || scope_Document.documentElement; 53760 var scope_Body = scope_Document.body; 53761 // Pips constants 53762 var PIPS_NONE = -1; 53763 var PIPS_NO_VALUE = 0; 53764 var PIPS_LARGE_VALUE = 1; 53765 var PIPS_SMALL_VALUE = 2; 53766 // For horizontal sliders in standard ltr documents, 53767 // make .noUi-origin overflow to the left so the document doesn't scroll. 53768 var scope_DirOffset = scope_Document.dir === "rtl" || options.ort === 1 ? 0 : 100; 53769 // Creates a node, adds it to target, returns the new node. 53770 function addNodeTo(addTarget, className) { 53771 var div = scope_Document.createElement("div"); 53772 if (className) { 53773 addClass(div, className); 53774 } 53775 addTarget.appendChild(div); 53776 return div; 53777 } 53778 // Append a origin to the base 53779 function addOrigin(base, handleNumber) { 53780 var origin = addNodeTo(base, options.cssClasses.origin); 53781 var handle = addNodeTo(origin, options.cssClasses.handle);
vendor: 5,013 bytes, lines 53782-53893
53782 addNodeTo(handle, options.cssClasses.touchArea); 53783 handle.setAttribute("data-handle", handleNumber); 53784 if (options.keyboardSupport) { 53785 // https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex 53786 // 0 = focusable and reachable 53787 handle.setAttribute("tabindex", "0"); 53788 handle.addEventListener("keydown", function (event) { 53789 return eventKeydown(event, handleNumber); 53790 }); 53791 } 53792 handle.setAttribute("role", "slider"); 53793 handle.setAttribute("aria-orientation", options.ort ? "vertical" : "horizontal"); 53794 if (handleNumber === 0) { 53795 addClass(handle, options.cssClasses.handleLower); 53796 } 53797 else if (handleNumber === options.handles - 1) { 53798 addClass(handle, options.cssClasses.handleUpper); 53799 } 53800 return origin; 53801 } 53802 // Insert nodes for connect elements 53803 function addConnect(base, add) { 53804 if (!add) { 53805 return false; 53806 } 53807 return addNodeTo(base, options.cssClasses.connect); 53808 } 53809 // Add handles to the slider base. 53810 function addElements(connectOptions, base) { 53811 var connectBase = addNodeTo(base, options.cssClasses.connects); 53812 scope_Handles = []; 53813 scope_Connects = []; 53814 scope_Connects.push(addConnect(connectBase, connectOptions[0])); 53815 // [::::O====O====O====] 53816 // connectOptions = [0, 1, 1, 1] 53817 for (var i = 0; i < options.handles; i++) { 53818 // Keep a list of all added handles. 53819 scope_Handles.push(addOrigin(base, i)); 53820 scope_HandleNumbers[i] = i; 53821 scope_Connects.push(addConnect(connectBase, connectOptions[i + 1])); 53822 } 53823 } 53824 // Initialize a single slider. 53825 function addSlider(addTarget) { 53826 // Apply classes and data to the target. 53827 addClass(addTarget, options.cssClasses.target); 53828 if (options.dir === 0) { 53829 addClass(addTarget, options.cssClasses.ltr); 53830 } 53831 else { 53832 addClass(addTarget, options.cssClasses.rtl); 53833 } 53834 if (options.ort === 0) { 53835 addClass(addTarget, options.cssClasses.horizontal); 53836 } 53837 else { 53838 addClass(addTarget, options.cssClasses.vertical); 53839 } 53840 var textDirection = getComputedStyle(addTarget).direction; 53841 if (textDirection === "rtl") { 53842 addClass(addTarget, options.cssClasses.textDirectionRtl); 53843 } 53844 else { 53845 addClass(addTarget, options.cssClasses.textDirectionLtr); 53846 } 53847 return addNodeTo(addTarget, options.cssClasses.base); 53848 } 53849 function addTooltip(handle, handleNumber) { 53850 if (!options.tooltips[handleNumber]) { 53851 return false; 53852 } 53853 return addNodeTo(handle.firstChild, options.cssClasses.tooltip); 53854 } 53855 function isSliderDisabled() { 53856 return scope_Target.hasAttribute("disabled"); 53857 } 53858 // Disable the slider dragging if any handle is disabled 53859 function isHandleDisabled(handleNumber) { 53860 var handleOrigin = scope_Handles[handleNumber]; 53861 return handleOrigin.hasAttribute("disabled"); 53862 } 53863 function removeTooltips() { 53864 if (scope_Tooltips) { 53865 removeEvent("update" + INTERNAL_EVENT_NS.tooltips); 53866 scope_Tooltips.forEach(function (tooltip) { 53867 if (tooltip) { 53868 removeElement(tooltip); 53869 } 53870 }); 53871 scope_Tooltips = null; 53872 } 53873 } 53874 // The tooltips option is a shorthand for using the 'update' event. 53875 function tooltips() { 53876 removeTooltips(); 53877 // Tooltips are added with options.tooltips in original order. 53878 scope_Tooltips = scope_Handles.map(addTooltip); 53879 bindEvent("update" + INTERNAL_EVENT_NS.tooltips, function (values, handleNumber, unencoded) { 53880 if (!scope_Tooltips[handleNumber]) { 53881 return; 53882 } 53883 var formattedValue = values[handleNumber]; 53884 if (options.tooltips[handleNumber] !== true) { 53885 formattedValue = options.tooltips[handleNumber].to(unencoded[handleNumber]); 53886 } 53887 scope_Tooltips[handleNumber].innerHTML = formattedValue; 53888 }); 53889 } 53890 function aria() { 53891 removeEvent("update" + INTERNAL_EVENT_NS.aria); 53892 bindEvent("update" + INTERNAL_EVENT_NS.aria, function (values, handleNumber, unencoded, tap, positions) { 53893 // Update Aria Values for all handles, as a change in one changes min and max values for the next.
53894 scope_HandleNumbers.forEach(function (index) { 53895 var handle = scope_Handles[index]; 53896 var min = checkHandlePosition(scope_Locations, index, 0, true, true, true); 53897 var max = checkHandlePosition(scope_Locations, index, 100, true, true, true); 53898 var now = positions[index]; 53899 // Formatted value for display 53900 var text = options.ariaFormat.to(unencoded[index]); 53901 // Map to slider range values 53902 min = scope_Spectrum.fromStepping(min).toFixed(1); 53903 max = scope_Spectrum.fromStepping(max).toFixed(1); 53904 now = scope_Spectrum.fromStepping(now).toFixed(1); 53905 handle.children[0].setAttribute("aria-valuemin", min); 53906 handle.children[0].setAttribute("aria-valuemax", max); 53907 handle.children[0].setAttribute("aria-valuenow", now); 53908 handle.children[0].setAttribute("aria-valuetext", text); 53909 }); 53910 }); 53911 } 53912 function getGroup(mode, values, stepped) { 53913 // Use the range. 53914 if (mode === "range" || mode === "steps") { 53915 return scope_Spectrum.xVal; 53916 } 53917 if (mode === "count") { 53918 if (values < 2) { 53919 throw new Error("noUiSlider (" + VERSION + "): 'values' (>= 2) required for mode 'count'."); 53920 } 53921 // Divide 0 - 100 in 'count' parts. 53922 var interval = values - 1; 53923 var spread = 100 / interval; 53924 values = []; 53925 // List these parts and have them handled as 'positions'. 53926 while (interval--) { 53927 values[interval] = interval * spread; 53928 } 53929 values.push(100); 53930 mode = "positions"; 53931 } 53932 if (mode === "positions") { 53933 // Map all percentages to on-range values. 53934 return values.map(function (value) { 53935 return scope_Spectrum.fromStepping(stepped ? scope_Spectrum.getStep(value) : value); 53936 }); 53937 } 53938 if (mode === "values") { 53939 // If the value must be stepped, it needs to be converted to a percentage first. 53940 if (stepped) { 53941 return values.map(function (value) { 53942 // Convert to percentage, apply step, return to value. 53943 return scope_Spectrum.fromStepping(scope_Spectrum.getStep(scope_Spectrum.toStepping(value))); 53944 }); 53945 } 53946 // Otherwise, we can simply use the values. 53947 return values; 53948 } 53949 } 53950 function generateSpread(density, mode, group) { 53951 function safeIncrement(value, increment) { 53952 // Avoid floating point variance by dropping the smallest decimal places. 53953 return (value + increment).toFixed(7) / 1; 53954 } 53955 var indexes = {}; 53956 var firstInRange = scope_Spectrum.xVal[0]; 53957 var lastInRange = scope_Spectrum.xVal[scope_Spectrum.xVal.length - 1]; 53958 var ignoreFirst = false; 53959 var ignoreLast = false; 53960 var prevPct = 0; 53961 // Create a copy of the group, sort it and filter away all duplicates. 53962 group = unique(group.slice().sort(function (a, b) { 53963 return a - b; 53964 })); 53965 // Make sure the range starts with the first element. 53966 if (group[0] !== firstInRange) { 53967 group.unshift(firstInRange); 53968 ignoreFirst = true; 53969 } 53970 // Likewise for the last one. 53971 if (group[group.length - 1] !== lastInRange) { 53972 group.push(lastInRange); 53973 ignoreLast = true; 53974 } 53975 group.forEach(function (current, index) { 53976 // Get the current step and the lower + upper positions. 53977 var step; 53978 var i; 53979 var q; 53980 var low = current; 53981 var high = group[index + 1]; 53982 var newPct; 53983 var pctDifference; 53984 var pctPos; 53985 var type; 53986 var steps; 53987 var realSteps; 53988 var stepSize; 53989 var isSteps = mode === "steps"; 53990 // When using 'steps' mode, use the provided steps. 53991 // Otherwise, we'll step on to the next subrange. 53992 if (isSteps) { 53993 step = scope_Spectrum.xNumSteps[index]; 53994 } 53995 // Default to a 'full' step. 53996 if (!step) { 53997 step = high - low; 53998 } 53999 // Low can be 0, so test for false. Index 0 is already handled. 54000 if (low === false) { 54001 return; 54002 } 54003 // If high is undefined we are at the last subrange. Make sure it iterates once (#1088) 54004 if (high === undefined) { 54005 high = low; 54006 } 54007 // Make sure step isn't 0, which would cause an infinite loop (#654) 54008 step = Math.max(step, 0.0000001); 54009 // Find all steps in the subrange. 54010 for (i = low; i <= high; i = safeIncrement(i, step)) { 54011 // Get the percentage value for the current step, 54012 // calculate the size for the subrange. 54013 newPct = scope_Spectrum.toStepping(i); 54014 pctDifference = newPct - prevPct; 54015 steps = pctDifference / density; 54016 realSteps = Math.round(steps); 54017 // This ratio represents the amount of percentage-space a point indicates. 54018 // For a density 1 the points/percentage = 1. For density 2, that percentage needs to be re-divided. 54019 // Round the percentage offset to an even number, then divide by two 54020 // to spread the offset on both sides of the range. 54021 stepSize = pctDifference / realSteps; 54022 // Divide all points evenly, adding the correct number to this subrange. 54023 // Run up to <= so that 100% gets a point, event if ignoreLast is set. 54024 for (q = 1; q <= realSteps; q += 1) { 54025 // The ratio between the rounded value and the actual size might be ~1% off. 54026 // Correct the percentage offset by the number of points 54027 // per subrange. density = 1 will result in 100 points on the 54028 // full range, 2 for 50, 4 for 25, etc. 54029 pctPos = prevPct + q * stepSize; 54030 indexes[pctPos.toFixed(5)] = [scope_Spectrum.fromStepping(pctPos), 0]; 54031 } 54032 // Determine the point type. 54033 type = group.indexOf(i) > -1 ? PIPS_LARGE_VALUE : isSteps ? PIPS_SMALL_VALUE : PIPS_NO_VALUE; 54034 // Enforce the 'ignoreFirst' option by overwriting the type for 0. 54035 if (!index && ignoreFirst && i !== high) { 54036 type = 0; 54037 } 54038 if (!(i === high && ignoreLast)) { 54039 // Mark the 'type' of this point. 0 = plain, 1 = real value, 2 = step value. 54040 indexes[newPct.toFixed(5)] = [i, type]; 54041 } 54042 // Update the percentage count. 54043 prevPct = newPct; 54044 } 54045 }); 54046 return indexes; 54047 } 54048 function addMarking(spread, filterFunc, formatter) { 54049 var element = scope_Document.createElement("div"); 54050 var valueSizeClasses = [];
54051 valueSizeClasses[PIPS_NO_VALUE] = options.cssClasses.valueNormal; 54052 valueSizeClasses[PIPS_LARGE_VALUE] = options.cssClasses.valueLarge; 54053 valueSizeClasses[PIPS_SMALL_VALUE] = options.cssClasses.valueSub; 54054 var markerSizeClasses = []; 54055 markerSizeClasses[PIPS_NO_VALUE] = options.cssClasses.markerNormal; 54056 markerSizeClasses[PIPS_LARGE_VALUE] = options.cssClasses.markerLarge; 54057 markerSizeClasses[PIPS_SMALL_VALUE] = options.cssClasses.markerSub; 54058 var valueOrientationClasses = [options.cssClasses.valueHorizontal, options.cssClasses.valueVertical]; 54059 var markerOrientationClasses = [options.cssClasses.markerHorizontal, options.cssClasses.markerVertical]; 54060 addClass(element, options.cssClasses.pips); 54061 addClass(element, options.ort === 0 ? options.cssClasses.pipsHorizontal : options.cssClasses.pipsVertical); 54062 function getClasses(type, source) { 54063 var a = source === options.cssClasses.value; 54064 var orientationClasses = a ? valueOrientationClasses : markerOrientationClasses; 54065 var sizeClasses = a ? valueSizeClasses : markerSizeClasses; 54066 return source + " " + orientationClasses[options.ort] + " " + sizeClasses[type]; 54067 } 54068 function addSpread(offset, value, type) { 54069 // Apply the filter function, if it is set. 54070 type = filterFunc ? filterFunc(value, type) : type; 54071 if (type === PIPS_NONE) { 54072 return; 54073 } 54074 // Add a marker for every point 54075 var node = addNodeTo(element, false); 54076 node.className = getClasses(type, options.cssClasses.marker); 54077 node.style[options.style] = offset + "%"; 54078 // Values are only appended for points marked '1' or '2'. 54079 if (type > PIPS_NO_VALUE) { 54080 node = addNodeTo(element, false); 54081 node.className = getClasses(type, options.cssClasses.value); 54082 node.setAttribute("data-value", value); 54083 node.style[options.style] = offset + "%"; 54084 node.innerHTML = formatter.to(value); 54085 } 54086 } 54087 // Append all points. 54088 Object.keys(spread).forEach(function (offset) { 54089 addSpread(offset, spread[offset][0], spread[offset][1]); 54090 }); 54091 return element; 54092 } 54093 function removePips() { 54094 if (scope_Pips) { 54095 removeElement(scope_Pips); 54096 scope_Pips = null; 54097 } 54098 } 54099 function pips(grid) { 54100 // Fix #669 54101 removePips(); 54102 var mode = grid.mode; 54103 var density = grid.density || 1; 54104 var filter = grid.filter || false; 54105 var values = grid.values || false; 54106 var stepped = grid.stepped || false; 54107 var group = getGroup(mode, values, stepped); 54108 var spread = generateSpread(density, mode, group); 54109 var format = grid.format || { 54110 to: Math.round 54111 }; 54112 scope_Pips = scope_Target.appendChild(addMarking(spread, filter, format)); 54113 return scope_Pips; 54114 } 54115 // Shorthand for base dimensions. 54116 function baseSize() { 54117 var rect = scope_Base.getBoundingClientRect(); 54118 var alt = "offset" + ["Width", "Height"][options.ort]; 54119 return options.ort === 0 ? rect.width || scope_Base[alt] : rect.height || scope_Base[alt]; 54120 } 54121 // Handler for attaching events trough a proxy. 54122 function attachEvent(events, element, callback, data) { 54123 // This function can be used to 'filter' events to the slider. 54124 // element is a node, not a nodeList 54125 var method = function (e) { 54126 e = fixEvent(e, data.pageOffset, data.target || element); 54127 // fixEvent returns false if this event has a different target 54128 // when handling (multi-) touch events; 54129 if (!e) { 54130 return false;
54131 } 54132 // doNotReject is passed by all end events to make sure released touches 54133 // are not rejected, leaving the slider "stuck" to the cursor; 54134 if (isSliderDisabled() && !data.doNotReject) { 54135 return false; 54136 } 54137 // Stop if an active 'tap' transition is taking place. 54138 if (hasClass(scope_Target, options.cssClasses.tap) && !data.doNotReject) { 54139 return false; 54140 } 54141 // Ignore right or middle clicks on start #454 54142 if (events === actions.start && e.buttons !== undefined && e.buttons > 1) { 54143 return false; 54144 } 54145 // Ignore right or middle clicks on start #454 54146 if (data.hover && e.buttons) { 54147 return false; 54148 } 54149 // 'supportsPassive' is only true if a browser also supports touch-action: none in CSS. 54150 // iOS safari does not, so it doesn't get to benefit from passive scrolling. iOS does support 54151 // touch-action: manipulation, but that allows panning, which breaks 54152 // sliders after zooming/on non-responsive pages. 54153 // See: https://bugs.webkit.org/show_bug.cgi?id=133112 54154 if (!supportsPassive) { 54155 e.preventDefault(); 54156 } 54157 e.calcPoint = e.points[options.ort]; 54158 // Call the event handler with the event [ and additional data ]. 54159 callback(e, data); 54160 }; 54161 var methods = []; 54162 // Bind a closure on the target for every event type. 54163 events.split(" ").forEach(function (eventName) { 54164 element.addEventListener(eventName, method, supportsPassive ? { passive: true } : false); 54165 methods.push([eventName, method]); 54166 }); 54167 return methods; 54168 } 54169 // Provide a clean event with standardized offset values. 54170 function fixEvent(e, pageOffset, eventTarget) { 54171 // Filter the event to register the type, which can be 54172 // touch, mouse or pointer. Offset changes need to be 54173 // made on an event specific basis. 54174 var touch = e.type.indexOf("touch") === 0; 54175 var mouse = e.type.indexOf("mouse") === 0; 54176 var pointer = e.type.indexOf("pointer") === 0; 54177 var x; 54178 var y; 54179 // IE10 implemented pointer events with a prefix; 54180 if (e.type.indexOf("MSPointer") === 0) { 54181 pointer = true; 54182 } 54183 // Erroneous events seem to be passed in occasionally on iOS/iPadOS after user finishes interacting with 54184 // the slider. They appear to be of type MouseEvent, yet they don't have usual properties set. Ignore 54185 // events that have no touches or buttons associated with them. (#1057, #1079, #1095) 54186 if (e.type === "mousedown" && !e.buttons && !e.touches) { 54187 return false; 54188 } 54189 // The only thing one handle should be concerned about is the touches that originated on top of it. 54190 if (touch) { 54191 // Returns true if a touch originated on the target. 54192 var isTouchOnTarget = function (checkTouch) { 54193 return (checkTouch.target === eventTarget || 54194 eventTarget.contains(checkTouch.target) || 54195 (checkTouch.target.shadowRoot && checkTouch.target.shadowRoot.contains(eventTarget))); 54196 }; 54197 // In the case of touchstart events, we need to make sure there is still no more than one 54198 // touch on the target so we look amongst all touches. 54199 if (e.type === "touchstart") { 54200 var targetTouches = Array.prototype.filter.call(e.touches, isTouchOnTarget); 54201 // Do not support more than one touch per handle. 54202 if (targetTouches.length > 1) { 54203 return false; 54204 } 54205 x = targetTouches[0].pageX; 54206 y = targetTouches[0].pageY; 54207 } 54208 else { 54209 // In the other cases, find on changedTouches is enough. 54210 var targetTouch = Array.prototype.find.call(e.changedTouches, isTouchOnTarget); 54211 // Cancel if the target touch has not moved. 54212 if (!targetTouch) { 54213 return false;
vendor: 20,990 bytes, lines 54214-54627
54214 } 54215 x = targetTouch.pageX; 54216 y = targetTouch.pageY; 54217 } 54218 } 54219 pageOffset = pageOffset || getPageOffset(scope_Document); 54220 if (mouse || pointer) { 54221 x = e.clientX + pageOffset.x; 54222 y = e.clientY + pageOffset.y; 54223 } 54224 e.pageOffset = pageOffset; 54225 e.points = [x, y]; 54226 e.cursor = mouse || pointer; // Fix #435 54227 return e; 54228 } 54229 // Translate a coordinate in the document to a percentage on the slider 54230 function calcPointToPercentage(calcPoint) { 54231 var location = calcPoint - offset(scope_Base, options.ort); 54232 var proposal = (location * 100) / baseSize(); 54233 // Clamp proposal between 0% and 100% 54234 // Out-of-bound coordinates may occur when .noUi-base pseudo-elements 54235 // are used (e.g. contained handles feature) 54236 proposal = limit(proposal); 54237 return options.dir ? 100 - proposal : proposal; 54238 } 54239 // Find handle closest to a certain percentage on the slider 54240 function getClosestHandle(clickedPosition) { 54241 var smallestDifference = 100; 54242 var handleNumber = false; 54243 scope_Handles.forEach(function (handle, index) { 54244 // Disabled handles are ignored 54245 if (isHandleDisabled(index)) { 54246 return; 54247 } 54248 var handlePosition = scope_Locations[index]; 54249 var differenceWithThisHandle = Math.abs(handlePosition - clickedPosition); 54250 // Initial state 54251 var clickAtEdge = differenceWithThisHandle === 100 && smallestDifference === 100; 54252 // Difference with this handle is smaller than the previously checked handle 54253 var isCloser = differenceWithThisHandle < smallestDifference; 54254 var isCloserAfter = differenceWithThisHandle <= smallestDifference && clickedPosition > handlePosition; 54255 if (isCloser || isCloserAfter || clickAtEdge) { 54256 handleNumber = index; 54257 smallestDifference = differenceWithThisHandle; 54258 } 54259 }); 54260 return handleNumber; 54261 } 54262 // Fire 'end' when a mouse or pen leaves the document. 54263 function documentLeave(event, data) { 54264 if (event.type === "mouseout" && event.target.nodeName === "HTML" && event.relatedTarget === null) { 54265 eventEnd(event, data); 54266 } 54267 } 54268 // Handle movement on document for handle and range drag. 54269 function eventMove(event, data) { 54270 // Fix #498 54271 // Check value of .buttons in 'start' to work around a bug in IE10 mobile (data.buttonsProperty). 54272 // https://connect.microsoft.com/IE/feedback/details/927005/mobile-ie10-windows-phone-buttons-property-of-pointermove-event-always-zero 54273 // IE9 has .buttons and .which zero on mousemove. 54274 // Firefox breaks the spec MDN defines. 54275 if (navigator.appVersion.indexOf("MSIE 9") === -1 && event.buttons === 0 && data.buttonsProperty !== 0) { 54276 return eventEnd(event, data); 54277 } 54278 // Check if we are moving up or down 54279 var movement = (options.dir ? -1 : 1) * (event.calcPoint - data.startCalcPoint); 54280 // Convert the movement into a percentage of the slider width/height 54281 var proposal = (movement * 100) / data.baseSize; 54282 moveHandles(movement > 0, proposal, data.locations, data.handleNumbers); 54283 } 54284 // Unbind move events on document, call callbacks. 54285 function eventEnd(event, data) { 54286 // The handle is no longer active, so remove the class. 54287 if (data.handle) { 54288 removeClass(data.handle, options.cssClasses.active); 54289 scope_ActiveHandlesCount -= 1; 54290 } 54291 // Unbind the move and end events, which are added on 'start'. 54292 data.listeners.forEach(function (c) { 54293 scope_DocumentElement.removeEventListener(c[0], c[1]); 54294 }); 54295 if (scope_ActiveHandlesCount === 0) { 54296 // Remove dragging class. 54297 removeClass(scope_Target, options.cssClasses.drag); 54298 setZindex(); 54299 // Remove cursor styles and text-selection events bound to the body. 54300 if (event.cursor) { 54301 scope_Body.style.cursor = ""; 54302 scope_Body.removeEventListener("selectstart", preventDefault); 54303 } 54304 } 54305 data.handleNumbers.forEach(function (handleNumber) { 54306 fireEvent("change", handleNumber); 54307 fireEvent("set", handleNumber); 54308 fireEvent("end", handleNumber); 54309 }); 54310 } 54311 // Bind move events on document. 54312 function eventStart(event, data) { 54313 // Ignore event if any handle is disabled 54314 if (data.handleNumbers.some(isHandleDisabled)) { 54315 return false; 54316 } 54317 var handle; 54318 if (data.handleNumbers.length === 1) { 54319 var handleOrigin = scope_Handles[data.handleNumbers[0]]; 54320 handle = handleOrigin.children[0]; 54321 scope_ActiveHandlesCount += 1; 54322 // Mark the handle as 'active' so it can be styled. 54323 addClass(handle, options.cssClasses.active); 54324 } 54325 // A drag should never propagate up to the 'tap' event. 54326 event.stopPropagation(); 54327 // Record the event listeners. 54328 var listeners = []; 54329 // Attach the move and end events. 54330 var moveEvent = attachEvent(actions.move, scope_DocumentElement, eventMove, { 54331 // The event target has changed so we need to propagate the original one so that we keep 54332 // relying on it to extract target touches. 54333 target: event.target, 54334 handle: handle, 54335 listeners: listeners, 54336 startCalcPoint: event.calcPoint, 54337 baseSize: baseSize(), 54338 pageOffset: event.pageOffset, 54339 handleNumbers: data.handleNumbers, 54340 buttonsProperty: event.buttons, 54341 locations: scope_Locations.slice() 54342 }); 54343 var endEvent = attachEvent(actions.end, scope_DocumentElement, eventEnd, { 54344 target: event.target, 54345 handle: handle, 54346 listeners: listeners, 54347 doNotReject: true, 54348 handleNumbers: data.handleNumbers 54349 }); 54350 var outEvent = attachEvent("mouseout", scope_DocumentElement, documentLeave, { 54351 target: event.target, 54352 handle: handle, 54353 listeners: listeners, 54354 doNotReject: true, 54355 handleNumbers: data.handleNumbers 54356 }); 54357 // We want to make sure we pushed the listeners in the listener list rather than creating 54358 // a new one as it has already been passed to the event handlers. 54359 listeners.push.apply(listeners, moveEvent.concat(endEvent, outEvent)); 54360 // Text selection isn't an issue on touch devices, 54361 // so adding cursor styles can be skipped. 54362 if (event.cursor) { 54363 // Prevent the 'I' cursor and extend the range-drag cursor. 54364 scope_Body.style.cursor = getComputedStyle(event.target).cursor; 54365 // Mark the target with a dragging state. 54366 if (scope_Handles.length > 1) { 54367 addClass(scope_Target, options.cssClasses.drag); 54368 } 54369 // Prevent text selection when dragging the handles. 54370 // In noUiSlider <= 9.2.0, this was handled by calling preventDefault on mouse/touch start/move, 54371 // which is scroll blocking. The selectstart event is supported by FireFox starting from version 52, 54372 // meaning the only holdout is iOS Safari. This doesn't matter: text selection isn't triggered there. 54373 // The 'cursor' flag is false. 54374 // See: http://caniuse.com/#search=selectstart 54375 scope_Body.addEventListener("selectstart", preventDefault, false); 54376 } 54377 data.handleNumbers.forEach(function (handleNumber) { 54378 fireEvent("start", handleNumber); 54379 }); 54380 } 54381 // Move closest handle to tapped location. 54382 function eventTap(event) { 54383 // The tap event shouldn't propagate up 54384 event.stopPropagation(); 54385 var proposal = calcPointToPercentage(event.calcPoint); 54386 var handleNumber = getClosestHandle(proposal); 54387 // Tackle the case that all handles are 'disabled'. 54388 if (handleNumber === false) { 54389 return false; 54390 } 54391 // Flag the slider as it is now in a transitional state. 54392 // Transition takes a configurable amount of ms (default 300). Re-enable the slider after that. 54393 if (!options.events.snap) { 54394 addClassFor(scope_Target, options.cssClasses.tap, options.animationDuration); 54395 } 54396 setHandle(handleNumber, proposal, true, true); 54397 setZindex(); 54398 fireEvent("slide", handleNumber, true); 54399 fireEvent("update", handleNumber, true); 54400 fireEvent("change", handleNumber, true); 54401 fireEvent("set", handleNumber, true); 54402 if (options.events.snap) { 54403 eventStart(event, { handleNumbers: [handleNumber] }); 54404 } 54405 } 54406 // Fires a 'hover' event for a hovered mouse/pen position. 54407 function eventHover(event) { 54408 var proposal = calcPointToPercentage(event.calcPoint); 54409 var to = scope_Spectrum.getStep(proposal); 54410 var value = scope_Spectrum.fromStepping(to); 54411 Object.keys(scope_Events).forEach(function (targetEvent) { 54412 if ("hover" === targetEvent.split(".")[0]) { 54413 scope_Events[targetEvent].forEach(function (callback) { 54414 callback.call(scope_Self, value); 54415 }); 54416 } 54417 }); 54418 } 54419 // Handles keydown on focused handles 54420 // Don't move the document when pressing arrow keys on focused handles 54421 function eventKeydown(event, handleNumber) { 54422 if (isSliderDisabled() || isHandleDisabled(handleNumber)) { 54423 return false; 54424 } 54425 var horizontalKeys = ["Left", "Right"]; 54426 var verticalKeys = ["Down", "Up"]; 54427 var largeStepKeys = ["PageDown", "PageUp"]; 54428 var edgeKeys = ["Home", "End"]; 54429 if (options.dir && !options.ort) { 54430 // On an right-to-left slider, the left and right keys act inverted 54431 horizontalKeys.reverse(); 54432 } 54433 else if (options.ort && !options.dir) { 54434 // On a top-to-bottom slider, the up and down keys act inverted 54435 verticalKeys.reverse(); 54436 largeStepKeys.reverse(); 54437 } 54438 // Strip "Arrow" for IE compatibility. https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key 54439 var key = event.key.replace("Arrow", ""); 54440 var isLargeDown = key === largeStepKeys[0]; 54441 var isLargeUp = key === largeStepKeys[1]; 54442 var isDown = key === verticalKeys[0] || key === horizontalKeys[0] || isLargeDown; 54443 var isUp = key === verticalKeys[1] || key === horizontalKeys[1] || isLargeUp; 54444 var isMin = key === edgeKeys[0]; 54445 var isMax = key === edgeKeys[1]; 54446 if (!isDown && !isUp && !isMin && !isMax) { 54447 return true; 54448 } 54449 event.preventDefault(); 54450 var to; 54451 if (isUp || isDown) { 54452 var multiplier = options.keyboardPageMultiplier; 54453 var direction = isDown ? 0 : 1; 54454 var steps = getNextStepsForHandle(handleNumber); 54455 var step = steps[direction]; 54456 // At the edge of a slider, do nothing 54457 if (step === null) { 54458 return false; 54459 } 54460 // No step set, use the default of 10% of the sub-range 54461 if (step === false) { 54462 step = scope_Spectrum.getDefaultStep(scope_Locations[handleNumber], isDown, options.keyboardDefaultStep); 54463 } 54464 if (isLargeUp || isLargeDown) { 54465 step *= multiplier; 54466 } 54467 // Step over zero-length ranges (#948); 54468 step = Math.max(step, 0.0000001); 54469 // Decrement for down steps 54470 step = (isDown ? -1 : 1) * step; 54471 to = scope_Values[handleNumber] + step; 54472 } 54473 else if (isMax) { 54474 // End key 54475 to = options.spectrum.xVal[options.spectrum.xVal.length - 1]; 54476 } 54477 else { 54478 // Home key 54479 to = options.spectrum.xVal[0]; 54480 } 54481 setHandle(handleNumber, scope_Spectrum.toStepping(to), true, true); 54482 fireEvent("slide", handleNumber); 54483 fireEvent("update", handleNumber); 54484 fireEvent("change", handleNumber); 54485 fireEvent("set", handleNumber); 54486 return false; 54487 } 54488 // Attach events to several slider parts. 54489 function bindSliderEvents(behaviour) { 54490 // Attach the standard drag event to the handles. 54491 if (!behaviour.fixed) { 54492 scope_Handles.forEach(function (handle, index) { 54493 // These events are only bound to the visual handle 54494 // element, not the 'real' origin element. 54495 attachEvent(actions.start, handle.children[0], eventStart, { 54496 handleNumbers: [index] 54497 }); 54498 }); 54499 } 54500 // Attach the tap event to the slider base. 54501 if (behaviour.tap) { 54502 attachEvent(actions.start, scope_Base, eventTap, {}); 54503 } 54504 // Fire hover events 54505 if (behaviour.hover) { 54506 attachEvent(actions.move, scope_Base, eventHover, { 54507 hover: true 54508 }); 54509 } 54510 // Make the range draggable. 54511 if (behaviour.drag) { 54512 scope_Connects.forEach(function (connect, index) { 54513 if (connect === false || index === 0 || index === scope_Connects.length - 1) { 54514 return; 54515 } 54516 var handleBefore = scope_Handles[index - 1]; 54517 var handleAfter = scope_Handles[index]; 54518 var eventHolders = [connect]; 54519 addClass(connect, options.cssClasses.draggable); 54520 // When the range is fixed, the entire range can 54521 // be dragged by the handles. The handle in the first 54522 // origin will propagate the start event upward, 54523 // but it needs to be bound manually on the other. 54524 if (behaviour.fixed) { 54525 eventHolders.push(handleBefore.children[0]); 54526 eventHolders.push(handleAfter.children[0]); 54527 } 54528 eventHolders.forEach(function (eventHolder) { 54529 attachEvent(actions.start, eventHolder, eventStart, { 54530 handles: [handleBefore, handleAfter], 54531 handleNumbers: [index - 1, index] 54532 }); 54533 }); 54534 }); 54535 } 54536 } 54537 // Attach an event to this slider, possibly including a namespace 54538 function bindEvent(namespacedEvent, callback) { 54539 scope_Events[namespacedEvent] = scope_Events[namespacedEvent] || []; 54540 scope_Events[namespacedEvent].push(callback); 54541 // If the event bound is 'update,' fire it immediately for all handles. 54542 if (namespacedEvent.split(".")[0] === "update") { 54543 scope_Handles.forEach(function (a, index) { 54544 fireEvent("update", index); 54545 }); 54546 } 54547 } 54548 function isInternalNamespace(namespace) { 54549 return namespace === INTERNAL_EVENT_NS.aria || namespace === INTERNAL_EVENT_NS.tooltips; 54550 } 54551 // Undo attachment of event 54552 function removeEvent(namespacedEvent) { 54553 var event = namespacedEvent && namespacedEvent.split(".")[0]; 54554 var namespace = event ? namespacedEvent.substring(event.length) : namespacedEvent; 54555 Object.keys(scope_Events).forEach(function (bind) { 54556 var tEvent = bind.split(".")[0]; 54557 var tNamespace = bind.substring(tEvent.length); 54558 if ((!event || event === tEvent) && (!namespace || namespace === tNamespace)) { 54559 // only delete protected internal event if intentional 54560 if (!isInternalNamespace(tNamespace) || namespace === tNamespace) { 54561 delete scope_Events[bind]; 54562 } 54563 } 54564 }); 54565 } 54566 // External event handling 54567 function fireEvent(eventName, handleNumber, tap) { 54568 Object.keys(scope_Events).forEach(function (targetEvent) { 54569 var eventType = targetEvent.split(".")[0]; 54570 if (eventName === eventType) { 54571 scope_Events[targetEvent].forEach(function (callback) { 54572 callback.call( 54573 // Use the slider public API as the scope ('this') 54574 scope_Self, 54575 // Return values as array, so arg_1[arg_2] is always valid. 54576 scope_Values.map(options.format.to), 54577 // Handle index, 0 or 1 54578 handleNumber, 54579 // Un-formatted slider values 54580 scope_Values.slice(), 54581 // Event is fired by tap, true or false 54582 tap || false, 54583 // Left offset of the handle, in relation to the slider 54584 scope_Locations.slice(), 54585 // add the slider public API to an accessible parameter when this is unavailable 54586 scope_Self); 54587 }); 54588 } 54589 }); 54590 } 54591 // Split out the handle positioning logic so the Move event can use it, too 54592 function checkHandlePosition(reference, handleNumber, to, lookBackward, lookForward, getValue) { 54593 var distance; 54594 // For sliders with multiple handles, limit movement to the other handle. 54595 // Apply the margin option by adding it to the handle positions. 54596 if (scope_Handles.length > 1 && !options.events.unconstrained) { 54597 if (lookBackward && handleNumber > 0) { 54598 distance = scope_Spectrum.getAbsoluteDistance(reference[handleNumber - 1], options.margin, 0); 54599 to = Math.max(to, distance); 54600 } 54601 if (lookForward && handleNumber < scope_Handles.length - 1) { 54602 distance = scope_Spectrum.getAbsoluteDistance(reference[handleNumber + 1], options.margin, 1); 54603 to = Math.min(to, distance); 54604 } 54605 } 54606 // The limit option has the opposite effect, limiting handles to a 54607 // maximum distance from another. Limit must be > 0, as otherwise 54608 // handles would be unmovable. 54609 if (scope_Handles.length > 1 && options.limit) { 54610 if (lookBackward && handleNumber > 0) { 54611 distance = scope_Spectrum.getAbsoluteDistance(reference[handleNumber - 1], options.limit, 0); 54612 to = Math.min(to, distance); 54613 } 54614 if (lookForward && handleNumber < scope_Handles.length - 1) { 54615 distance = scope_Spectrum.getAbsoluteDistance(reference[handleNumber + 1], options.limit, 1); 54616 to = Math.max(to, distance); 54617 } 54618 } 54619 // The padding option keeps the handles a certain distance from the 54620 // edges of the slider. Padding must be > 0. 54621 if (options.padding) { 54622 if (handleNumber === 0) { 54623 distance = scope_Spectrum.getAbsoluteDistance(0, options.padding[0], 0); 54624 to = Math.max(to, distance); 54625 } 54626 if (handleNumber === scope_Handles.length - 1) { 54627 distance = scope_Spectrum.getAbsoluteDistance(100, options.padding[1], 1
54627); 54628 to = Math.min(to, distance); 54629 } 54630 } 54631 to = scope_Spectrum.getStep(to); 54632 // Limit percentage to the 0 - 100 range 54633 to = limit(to); 54634 // Return false if handle can't move 54635 if (to === reference[handleNumber] && !getValue) { 54636 return false; 54637 } 54638 return to; 54639 } 54640 // Uses slider orientation to create CSS rules. a = base value; 54641 function inRuleOrder(v, a) { 54642 var o = options.ort; 54643 return (o ? a : v) + ", " + (o ? v : a); 54644 } 54645 // Moves handle(s) by a percentage 54646 // (bool, % to move, [% where handle started, ...], [index in scope_Handles, ...]) 54647 function moveHandles(upward, proposal, locations, handleNumbers) { 54648 var proposals = locations.slice(); 54649 var b = [!upward, upward]; 54650 var f = [upward, !upward]; 54651 // Copy handleNumbers so we don't change the dataset 54652 handleNumbers = handleNumbers.slice(); 54653 // Check to see which handle is 'leading'. 54654 // If that one can't move the second can't either. 54655 if (upward) { 54656 handleNumbers.reverse(); 54657 } 54658 // Step 1: get the maximum percentage that any of the handles can move 54659 if (handleNumbers.length > 1) { 54660 handleNumbers.forEach(function (handleNumber, o) { 54661 var to = checkHandlePosition(proposals, handleNumber, proposals[handleNumber] + proposal, b[o], f[o], false); 54662 // Stop if one of the handles can't move. 54663 if (to === false) { 54664 proposal = 0; 54665 } 54666 else { 54667 proposal = to - proposals[handleNumber]; 54668 proposals[handleNumber] = to; 54669 } 54670 }); 54671 } 54672 // If using one handle, check backward AND forward 54673 else { 54674 b = f = [true]; 54675 } 54676 var state = false; 54677 // Step 2: Try to set the handles with the found percentage 54678 handleNumbers.forEach(function (handleNumber, o) { 54679 state = setHandle(handleNumber, locations[handleNumber] + proposal, b[o], f[o]) || state; 54680 }); 54681 // Step 3: If a handle moved, fire events 54682 if (state) { 54683 handleNumbers.forEach(function (handleNumber) { 54684 fireEvent("update", handleNumber); 54685 fireEvent("slide", handleNumber); 54686 }); 54687 } 54688 } 54689 // Takes a base value and an offset. This offset is used for the connect bar size. 54690 // In the initial design for this feature, the origin element was 1% wide. 54691 // Unfortunately, a rounding bug in Chrome makes it impossible to implement this feature 54692 // in this manner: https://bugs.chromium.org/p/chromium/issues/detail?id=798223 54693 function transformDirection(a, b) { 54694 return options.dir ? 100 - a - b : a; 54695 } 54696 // Updates scope_Locations and scope_Values, updates visual state 54697 function updateHandlePosition(handleNumber, to) { 54698 // Update locations. 54699 scope_Locations[handleNumber] = to; 54700 // Convert the value to the slider stepping/range. 54701 scope_Values[handleNumber] = scope_Spectrum.fromStepping(to); 54702 var translation = 10 * (transformDirection(to, 0) - scope_DirOffset); 54703 var translateRule = "translate(" + inRuleOrder(translation + "%", "0") + ")"; 54704 scope_Handles[handleNumber].style[options.transformRule] = translateRule; 54705 updateConnect(handleNumber); 54706 updateConnect(handleNumber + 1); 54707 } 54708 // Handles before the slider middle are stacked later = higher, 54709 // Handles after the middle later is lower 54710 // [[7] [8] .......... | .......... [5] [4] 54711 function setZindex() { 54712 scope_HandleNumbers.forEach(function (handleNumber) { 54713 var dir = scope_Locations[handleNumber] > 50 ? -1 : 1; 54714 var zIndex = 3 + (scope_Handles.length + dir * handleNumber); 54715 scope_Handles[handleNumber].style.zIndex = zIndex; 54716 }); 54717 } 54718 // Test suggested values and apply margin, step. 54719 // if exactInput is true, don't run checkHandlePosition, then the handle can be placed in between steps (#436) 54720 function setHandle(handleNumber, to, lookBackward, lookForward, exactInput) { 54721 if (!exactInput) { 54722 to = checkHandlePosition(scope_Locations, handleNumber, to, lookBackward, lookForward, false); 54723 } 54724 if (to === false) { 54725 return false;
vendor: 12,979 bytes, lines 54726-55021
54726 } 54727 updateHandlePosition(handleNumber, to); 54728 return true; 54729 } 54730 // Updates style attribute for connect nodes 54731 function updateConnect(index) { 54732 // Skip connects set to false 54733 if (!scope_Connects[index]) { 54734 return; 54735 } 54736 var l = 0; 54737 var h = 100; 54738 if (index !== 0) { 54739 l = scope_Locations[index - 1]; 54740 } 54741 if (index !== scope_Connects.length - 1) { 54742 h = scope_Locations[index]; 54743 } 54744 // We use two rules: 54745 // 'translate' to change the left/top offset; 54746 // 'scale' to change the width of the element; 54747 // As the element has a width of 100%, a translation of 100% is equal to 100% of the parent (.noUi-base) 54748 var connectWidth = h - l; 54749 var translateRule = "translate(" + inRuleOrder(transformDirection(l, connectWidth) + "%", "0") + ")"; 54750 var scaleRule = "scale(" + inRuleOrder(connectWidth / 100, "1") + ")"; 54751 scope_Connects[index].style[options.transformRule] = translateRule + " " + scaleRule; 54752 } 54753 // Parses value passed to .set method. Returns current value if not parse-able. 54754 function resolveToValue(to, handleNumber) { 54755 // Setting with null indicates an 'ignore'. 54756 // Inputting 'false' is invalid. 54757 if (to === null || to === false || to === undefined) { 54758 return scope_Locations[handleNumber]; 54759 } 54760 // If a formatted number was passed, attempt to decode it. 54761 if (typeof to === "number") { 54762 to = String(to); 54763 } 54764 to = options.format.from(to); 54765 to = scope_Spectrum.toStepping(to); 54766 // If parsing the number failed, use the current value. 54767 if (to === false || isNaN(to)) { 54768 return scope_Locations[handleNumber]; 54769 } 54770 return to; 54771 } 54772 // Set the slider value. 54773 function valueSet(input, fireSetEvent, exactInput) { 54774 var values = asArray(input); 54775 var isInit = scope_Locations[0] === undefined; 54776 // Event fires by default 54777 fireSetEvent = fireSetEvent === undefined ? true : !!fireSetEvent; 54778 // Animation is optional. 54779 // Make sure the initial values were set before using animated placement. 54780 if (options.animate && !isInit) { 54781 addClassFor(scope_Target, options.cssClasses.tap, options.animationDuration); 54782 } 54783 // First pass, without lookAhead but with lookBackward. Values are set from left to right. 54784 scope_HandleNumbers.forEach(function (handleNumber) { 54785 setHandle(handleNumber, resolveToValue(values[handleNumber], handleNumber), true, false, exactInput); 54786 }); 54787 var i = scope_HandleNumbers.length === 1 ? 0 : 1; 54788 // Secondary passes. Now that all base values are set, apply constraints. 54789 // Iterate all handles to ensure constraints are applied for the entire slider (Issue #1009) 54790 for (; i < scope_HandleNumbers.length; ++i) { 54791 scope_HandleNumbers.forEach(function (handleNumber) { 54792 setHandle(handleNumber, scope_Locations[handleNumber], true, true, exactInput); 54793 }); 54794 } 54795 setZindex(); 54796 scope_HandleNumbers.forEach(function (handleNumber) { 54797 fireEvent("update", handleNumber); 54798 // Fire the event only for handles that received a new value, as per #579 54799 if (values[handleNumber] !== null && fireSetEvent) { 54800 fireEvent("set", handleNumber); 54801 } 54802 }); 54803 } 54804 // Reset slider to initial values 54805 function valueReset(fireSetEvent) { 54806 valueSet(options.start, fireSetEvent); 54807 } 54808 // Set value for a single handle 54809 function valueSetHandle(handleNumber, value, fireSetEvent, exactInput) { 54810 // Ensure numeric input 54811 handleNumber = Number(handleNumber); 54812 if (!(handleNumber >= 0 && handleNumber < scope_HandleNumbers.length)) { 54813 throw new Error("noUiSlider (" + VERSION + "): invalid handle number, got: " + handleNumber); 54814 } 54815 // Look both backward and forward, since we don't want this handle to "push" other handles (#960); 54816 // The exactInput argument can be used to ignore slider stepping (#436) 54817 setHandle(handleNumber, resolveToValue(value, handleNumber), true, true, exactInput); 54818 fireEvent("update", handleNumber); 54819 if (fireSetEvent) { 54820 fireEvent("set", handleNumber); 54821 } 54822 } 54823 // Get the slider value. 54824 function valueGet() { 54825 var values = scope_Values.map(options.format.to); 54826 // If only one handle is used, return a single value. 54827 if (values.length === 1) { 54828 return values[0]; 54829 } 54830 return values; 54831 } 54832 // Removes classes from the root and empties it. 54833 function destroy() { 54834 // remove protected internal listeners 54835 removeEvent(INTERNAL_EVENT_NS.aria); 54836 removeEvent(INTERNAL_EVENT_NS.tooltips); 54837 for (var key in options.cssClasses) { 54838 if (!options.cssClasses.hasOwnProperty(key)) { 54839 continue; 54840 } 54841 removeClass(scope_Target, options.cssClasses[key]); 54842 } 54843 while (scope_Target.firstChild) { 54844 scope_Target.removeChild(scope_Target.firstChild); 54845 } 54846 delete scope_Target.noUiSlider; 54847 } 54848 function getNextStepsForHandle(handleNumber) { 54849 var location = scope_Locations[handleNumber]; 54850 var nearbySteps = scope_Spectrum.getNearbySteps(location); 54851 var value = scope_Values[handleNumber]; 54852 var increment = nearbySteps.thisStep.step; 54853 var decrement = null; 54854 // If snapped, directly use defined step value 54855 if (options.snap) { 54856 return [ 54857 value - nearbySteps.stepBefore.startValue || null, 54858 nearbySteps.stepAfter.startValue - value || null 54859 ]; 54860 } 54861 // If the next value in this step moves into the next step, 54862 // the increment is the start of the next step - the current value 54863 if (increment !== false) { 54864 if (value + increment > nearbySteps.stepAfter.startValue) { 54865 increment = nearbySteps.stepAfter.startValue - value; 54866 } 54867 } 54868 // If the value is beyond the starting point 54869 if (value > nearbySteps.thisStep.startValue) { 54870 decrement = nearbySteps.thisStep.step; 54871 } 54872 else if (nearbySteps.stepBefore.step === false) { 54873 decrement = false; 54874 } 54875 // If a handle is at the start of a step, it always steps back into the previous step first 54876 else { 54877 decrement = value - nearbySteps.stepBefore.highestStep; 54878 } 54879 // Now, if at the slider edges, there is no in/decrement 54880 if (location === 100) { 54881 increment = null; 54882 } 54883 else if (location === 0) { 54884 decrement = null; 54885 } 54886 // As per #391, the comparison for the decrement step can have some rounding issues. 54887 var stepDecimals = scope_Spectrum.countStepDecimals(); 54888 // Round per #391 54889 if (increment !== null && increment !== false) { 54890 increment = Number(increment.toFixed(stepDecimals)); 54891 } 54892 if (decrement !== null && decrement !== false) { 54893 decrement = Number(decrement.toFixed(stepDecimals)); 54894 } 54895 return [decrement, increment]; 54896 } 54897 // Get the current step size for the slider. 54898 function getNextSteps() { 54899 return scope_HandleNumbers.map(getNextStepsForHandle); 54900 } 54901 // Updateable: margin, limit, padding, step, range, animate, snap 54902 function updateOptions(optionsToUpdate, fireSetEvent) { 54903 // Spectrum is created using the range, snap, direction and step options. 54904 // 'snap' and 'step' can be updated. 54905 // If 'snap' and 'step' are not passed, they should remain unchanged. 54906 var v = valueGet(); 54907 var updateAble = [ 54908 "margin", 54909 "limit", 54910 "padding", 54911 "range", 54912 "animate", 54913 "snap", 54914 "step", 54915 "format", 54916 "pips", 54917 "tooltips" 54918 ]; 54919 // Only change options that we're actually passed to update. 54920 updateAble.forEach(function (name) { 54921 // Check for undefined. null removes the value. 54922 if (optionsToUpdate[name] !== undefined) { 54923 originalOptions[name] = optionsToUpdate[name]; 54924 } 54925 }); 54926 var newOptions = testOptions(originalOptions); 54927 // Load new options into the slider state 54928 updateAble.forEach(function (name) { 54929 if (optionsToUpdate[name] !== undefined) { 54930 options[name] = newOptions[name]; 54931 } 54932 }); 54933 scope_Spectrum = newOptions.spectrum; 54934 // Limit, margin and padding depend on the spectrum but are stored outside of it. (#677) 54935 options.margin = newOptions.margin; 54936 options.limit = newOptions.limit; 54937 options.padding = newOptions.padding; 54938 // Update pips, removes existing. 54939 if (options.pips) { 54940 pips(options.pips); 54941 } 54942 else { 54943 removePips(); 54944 } 54945 // Update tooltips, removes existing. 54946 if (options.tooltips) { 54947 tooltips(); 54948 } 54949 else { 54950 removeTooltips(); 54951 } 54952 // Invalidate the current positioning so valueSet forces an update. 54953 scope_Locations = []; 54954 valueSet(isSet(optionsToUpdate.start) ? optionsToUpdate.start : v, fireSetEvent); 54955 } 54956 // Initialization steps 54957 function setupSlider() { 54958 // Create the base element, initialize HTML and set classes. 54959 // Add handles and connect elements. 54960 scope_Base = addSlider(scope_Target); 54961 addElements(options.connect, scope_Base); 54962 // Attach user events. 54963 bindSliderEvents(options.events); 54964 // Use the public value method to set the start values. 54965 valueSet(options.start); 54966 if (options.pips) { 54967 pips(options.pips); 54968 } 54969 if (options.tooltips) { 54970 tooltips(); 54971 } 54972 aria(); 54973 } 54974 setupSlider(); 54975 // noinspection JSUnusedGlobalSymbols 54976 scope_Self = { 54977 destroy: destroy, 54978 steps: getNextSteps, 54979 on: bindEvent, 54980 off: removeEvent, 54981 get: valueGet, 54982 set: valueSet, 54983 setHandle: valueSetHandle, 54984 reset: valueReset, 54985 // Exposed for unit testing, don't use this in your application. 54986 __moveHandles: function (a, b, c) { 54987 moveHandles(a, b, scope_Locations, c); 54988 }, 54989 options: originalOptions, 54990 updateOptions: updateOptions, 54991 target: scope_Target, 54992 removePips: removePips, 54993 removeTooltips: removeTooltips, 54994 getTooltips: function () { 54995 return scope_Tooltips; 54996 }, 54997 getOrigins: function () { 54998 return scope_Handles; 54999 }, 55000 pips: pips // Issue #594 55001 }; 55002 return scope_Self; 55003 } 55004 // Run the standard initializer 55005 function initialize(target, originalOptions) { 55006 if (!target || !target.nodeName) { 55007 throw new Error("noUiSlider (" + VERSION + "): create requires a single element, got: " + target); 55008 } 55009 // Throw an error if the slider was already initialized. 55010 if (target.noUiSlider) { 55011 throw new Error("noUiSlider (" + VERSION + "): Slider was already initialized."); 55012 } 55013 // Test the options and create the slider environment; 55014 var options = testOptions(originalOptions); 55015 var api = scope(target, options, originalOptions); 55016 target.noUiSlider = api; 55017 return api; 55018 } 55019 // Use an object instead of a function for future expandability; 55020 return { 55021 // Exposed for unit testing, don't use this in your application.
55022 __spectrum: Spectrum, 55023 version: VERSION, 55024 // A reference to the default classes, allows global changes. 55025 // Use the cssClasses option for changes to one slider. 55026 cssClasses: cssClasses, 55027 create: initialize 55028 }; 55029}); 55030 55031/** 55032 * @namespace Global top namespace 55033 */ 55034var revide = {}; 55035 55036(function () { 55037 $(document).ready(function () { 55038 $("#forgot-password").click(function (e) { 55039 $("#login-box").hide(); 55040 $("#reset-password-error").hide(); 55041 $("#resend-password-box").show(); 55042 }); 55043 55044 $("#close-resend-password-box").click(function (e) { 55045 $("#login-box").show(); 55046 $("#reset-password-error").hide(); 55047 $("#resend-password-box").hide(); 55048 }); 55049 55050 $("#resend-password").click(function (e) { 55051 $("#reset-password-error").hide(); 55052 $.ajax({ 55053 type: "POST", 55054 traditional: true, 55055 url: "/umbraco/surface/member/resetpassword", 55056 data: { 55057 email: $("#resend-email-input").val().trim(), 55058 }, 55059 dataType: "json", 55060 success: function (response) { 55061 $("#reset-password-error").html(response.messages); 55062 $("#reset-password-error").show(); 55063 }, 55064 }); 55065 }); 55066 55067 //Mobile 55068 $("#mob-forgot-password").click(function (e) { 55069 $("#mob-login-box").hide(); 55070 $("#mob-reset-password-error").hide(); 55071 $("#mob-resend-password-box").show(); 55072 }); 55073 55074 $("#mob-close-resend-password-box").click(function (e) { 55075 $("#mob-login-box").show(); 55076 $("#mob-reset-password-error").hide(); 55077 $("#mob-resend-password-box").hide(); 55078 }); 55079 55080 $("#mob-resend-password").click(function (e) { 55081 $("#mob-reset-password-error").hide(); 55082 $.ajax({ 55083 type: "POST", 55084 traditional: true, 55085 url: "/umbraco/surface/member/resetpassword", 55086 data: { 55087 email: $("#mob-resend-email-input").val(), 55088 }, 55089 dataType: "json", 55090 success: function (response) { 55091 $("#mob-reset-password-error").html(response.messages); 55092 $("#mob-reset-password-error").show(); 55093 }, 55094 }); 55095 }); 55096 }); 55097})(); 55098 55099(function () { 55100 "use strict"; 55101 55102 angular.module("webApp", ["ngAnimate", "ngCookies", "ngTouch", "ngSanitize", "ngMessages", "ngAria", "ngResource"]); 55103})();
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.