vendor: 22,881 bytes, lines 1-2
1/*! jQuery v3.7.1 | (c) OpenJS Foundation and other contributors | jquery.org/license */ 2!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(ie,e){"use strict";var oe=[],r=Object.getPrototypeOf,ae=oe.slice,g=oe.flat?function(e){return oe.flat.call(e)}:function(e){return oe.concat.apply([],e)},s=oe.push,se=oe.indexOf,n={},i=n.toString,ue=n.hasOwnProperty,o=ue.toString,a=o.call(Object),le={},v=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},y=function(e){return null!=e&&e===e.window},C=ie.document,u={type:!0,src:!0,nonce:!0,noModule:!0};function m(e,t,n){var r,i,o=(n=n||C).createElement("script");if(o.text=e,t)for(r in u)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[i.call(e)]||"object":typeof e}var t="3.7.1",l=/HTML$/i,ce=function(e,t){return new ce.fn.init(e,t)};function c(e){var t=!!e&&"length"in e&&e.length,n=x(e);return!v(e)&&!y(e)&&("array"===n||0===t||"number"==typeof t&&0<t&&t-1 in e)}function fe(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}ce.fn=ce.prototype={jquery:t,constructor:ce,length:0,toArray:function(){return ae.call(this)},get:function(e){return null==e?ae.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=ce.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return ce.each(this,e)},map:function(n){return this.pushStack(ce.map(this,function(e,t){return n.call(e,t,e)}))},slice:function(){return this.pushStack(ae.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},even:function(){return this.pushStack(ce.grep(this,function(e,t){return(t+1)%2}))},odd:function(){return this.pushStack(ce.grep(this,function(e,t){return t%2}))},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(0<=n&&n<t?[this[n]]:[])},end:function(){return this.prevObject||this.constructor()},push:s,sort:oe.sort,splice:oe.splice},ce.extend=ce.fn.extend=function(){var e,t,n,r,i,o,a=arguments[0]||{},s=1,u=arguments.length,l=!1;for("boolean"==typeof a&&(l=a,a=arguments[s]||{},s++),"object"==typeof a||v(a)||(a={}),s===u&&(a=this,s--);s<u;s++)if(null!=(e=arguments[s]))for(t in e)r=e[t],"__proto__"!==t&&a!==r&&(l&&r&&(ce.isPlainObject(r)||(i=Array.isArray(r)))?(n=a[t],o=i&&!Array.isArray(n)?[]:i||ce.isPlainObject(n)?n:{},i=!1,a[t]=ce.extend(l,o,r)):void 0!==r&&(a[t]=r));return a},ce.extend({expando:"jQuery"+(t+Math.random()).replace(/\D/g,""),isReady:!0,error:function(e){throw new Error(e)},noop:function(){},isPlainObject:function(e){var t,n;return!(!e||"[object Object]"!==i.call(e))&&(!(t=r(e))||"function"==typeof(n=ue.call(t,"constructor")&&t.constructor)&&o.call(n)===a)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},globalEval:function(e,t,n){m(e,{nonce:t&&t.nonce},n)},each:function(e,t){var n,r=0;if(c(e)){for(n=e.length;r<n;r++)if(!1===t.call(e[r],r,e[r]))break}else for(r in e)if(!1===t.call(e[r],r,e[r]))break;return e},text:function(e){var t,n="",r=0,i=e.nodeType;if(!i)while(t=e[r++])n+=ce.text(t);return 1===i||11===i?e.textContent:9===i?e.documentElement.textContent:3===i||4===i?e.nodeValue:n},makeArray:function(e,t){var n=t||[];return null!=e&&(c(Object(e))?ce.merge(n,"string"==typeof e?[e]:e):s.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:se.call(t,e,n)},isXMLDoc:function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!l.test(t||n&&n.nodeName||"HTML")},merge:function(e,t){for(var n=+t.length,r=0,i=e.length;r<n;r++)e[i++]=t[r];return e.length=i,e},grep:function(e,t,n){for(var r=[],i=0,o=e.length,a=!n;i<o;i++)!t(e[i],i)!==a&&r.push(e[i]);return r},map:function(e,t,n){var r,i,o=0,a=[];if(c(e))for(r=e.length;o<r;o++)null!=(i=t(e[o],o,n))&&a.push(i);else for(o in e)null!=(i=t(e[o],o,n))&&a.push(i);return g(a)},guid:1,support:le}),"function"==typeof Symbol&&(ce.fn[Symbol.iterator]=oe[Symbol.iterator]),ce.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(e,t){n["[object "+t+"]"]=t.toLowerCase()});var pe=oe.pop,de=oe.sort,he=oe.splice,ge="[\\x20\\t\\r\\n\\f]",ve=new RegExp("^"+ge+"+|((?:^|[^\\\\])(?:\\\\.)*)"+ge+"+$","g");ce.contains=function(e,t){var n=t&&t.parentNode;return e===n||!(!n||1!==n.nodeType||!(e.contains?e.contains(n):e.compareDocumentPosition&&16&e.compareDocumentPosition(n)))};var f=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g;function p(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e}ce.escapeSelector=function(e){return(e+"").replace(f,p)};var ye=C,me=s;!function(){var e,b,w,o,a,T,r,C,d,i,k=me,S=ce.expando,E=0,n=0,s=W(),c=W(),u=W(),h=W(),l=function(e,t){return e===t&&(a=!0),0},f="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",t="(?:\\\\[\\da-fA-F]{1,6}"+ge+"?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+",p="\\["+ge+"*("+t+")(?:"+ge+"*([*^$|!~]?=)"+ge+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+t+"))|)"+ge+"*\\]",g=":("+t+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+p+")*)|.*)\\)|)",v=new RegExp(ge+"+","g"),y=new RegExp("^"+ge+"*,"+ge+"*"),m=new RegExp("^"+ge+"*([>+~]|"+ge+")"+ge+"*"),x=new RegExp(ge+"|>"),j=new RegExp(g),A=new RegExp("^"+t+"$"),D={ID:new RegExp("^#("+t+")"),CLASS:new RegExp("^\\.("+t+")"),TAG:new RegExp("^("+t+"|[*])"),ATTR:new RegExp("^"+p),PSEUDO:new RegExp("^"+g),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ge+"*(even|odd|(([+-]|)(\\d*)n|)"+ge+"*(?:([+-]|)"+ge+"*(\\d+)|))"+ge+"*\\)|)","i"),bool:new RegExp("^(?:"+f+")$","i"),needsContext:new RegExp("^"+ge+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ge+"*((?:-\\d)?\\d*)"+ge+"*\\)|)(?=[^-]|$)","i")},N=/^(?:input|select|textarea|button)$/i,q=/^h\d$/i,L=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,H=/[+~]/,O=new RegExp("\\\\[\\da-fA-F]{1,6}"+ge+"?|\\\\([^\\r\\n\\f])","g"),P=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},M=function(){V()},R=J(function(e){return!0===e.disabled&&fe(e,"fieldset")},{dir:"parentNode",next:"legend"});try{k.apply(oe=ae.call(ye.childNodes),ye.childNodes),oe[ye.childNodes.length].nodeType}catch(e){k={apply:function(e,t){me.apply(e,ae.call(t))},call:function(e){me.apply(e,ae.call(arguments,1))}}}function I(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(V(e),e=e||T,C)){if(11!==p&&(u=L.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return k.call(n,a),n}else if(f&&(a=f.getElementById(i))&&I.contains(e,a)&&a.id===i)return k.call(n,a),n}else{if(u[2])return k.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&e.getElementsByClassName)return k.apply(n,e.getElementsByClassName(i)),n}if(!(h[t+" "]||d&&d.test(t))){if(c=t,f=e,1===p&&(x.test(t)||m.test(t))){(f=H.test(t)&&U(e.parentNode)||e)==e&&le.scope||((s=e.getAttribute("id"))?s=ce.escapeSelector(s):e.setAttribute("id",s=S)),o=(l=Y(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+Q(l[o]);c=l.join(",")}try{return k.apply(n,f.querySelectorAll(c)),n}catch(e){h(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return re(t.replace(ve,"$1"),e,n,r)}function W(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function F(e){return e[S]=!0,e}function $(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function B(t){return function(e){return fe(e,"input")&&e.type===t}}function _(t){return function(e){return(fe(e,"input")||fe(e,"button"))&&e.type===t}}function z(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&R(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function X(a){return F(function(o){return o=+o,F(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function U(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function V(e){var t,n=e?e.ownerDocument||e:ye;return n!=T&&9===n.nodeType&&n.documentElement&&(r=(T=n).documentElement,C=!ce.isXMLDoc(T),i=r.matches||r.webkitMatchesSelector||r.msMatchesSelector,r.msMatchesSelector&&ye!=T&&(t=T.defaultView)&&t.top!==t&&t.addEventListener("unload",M),le.getById=$(function(e){return r.appendChild(e).id=ce.expando,!T.getElementsByName||!T.getElementsByName(ce.expando).length}),le.disconnectedMatch=$(function(e){return i.call(e,"*")}),le.scope=$(function(){return T.querySelectorAll(":scope")}),le.cssHas=$(function(){try{return T.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}}),le.getById?(b.filter.ID=function(e){var t=e.replace(O,P);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(O,P);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):t.querySelectorAll(e)},b.find.CLASS=function(e,t){if("undefined"!=typeof t.getElementsByClassName&&C)return t.getElementsByClassName(e)},d=[],$(function(e){var t;r.appendChild(e).innerHTML="<a id='"+S+"' href='' disabled='disabled'></a><select id='"+S+"-\r\\' disabled='disabled'><option selected=''></option></select>",e.querySelectorAll("[selected]").length||d.push("\\["+ge+"*(?:value|"+f+")"),e.querySelectorAll("[id~="+S+"-]").length||d.push("~="),e.querySelectorAll("a#"+S+"+*").length||d.push(".#.+[+~]"),e.querySelectorAll(":checked").length||d.push(":checked"),(t=T.createElement("input")).setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),r.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&d.push(":enabled",":disabled"),(t=T.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||d.push("\\["+ge+"*name"+ge+"*="+ge+"*(?:''|\"\")")}),le.cssHas||d.push(":has"),d=d.length&&new RegExp(d.join("|")),l=function(e,t){if(e===t)return a=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!le.sortDetached&&t.compareDocumentPosition(e)===n?e===T||e.ownerDocument==ye&&I.contains(ye,e)?-1:t===T||t.ownerDocument==ye&&I.contains(ye,t)?1:o?se.call(o,e)-se.call(o,t):0:4&n?-1:1)}),T}for(e in I.matches=function(e,t){return I(e,null,null,t)},I.matchesSelector=function(e,t){if(V(e),C&&!h[t+" "]&&(!d||!d.test(t)))try{var n=i.call(e,t);if(n||le.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){h(t,!0)}return 0<I(t,T,null,[e]).length},I.contains=function(e,t){return(e.ownerDocument||e)!=T&&V(e),ce.contains(e,t)},I.attr=function(e,t){(e.ownerDocument||e)!=T&&V(e);var n=b.attrHandle[t.toLowerCase()],r=n&&ue.call(b.attrHandle,t.toLowerCase())?n(e,t,!C):void 0;return void 0!==r?r:e.getAttribute(t)},I.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},ce.uniqueSort=function(e){var t,n=[],r=0,i=0;if(a=!le.sortStable,o=!le.sortStable&&ae.call(e,0),de.call(e,l),a){while(t=e[i++])t===e[i]&&(r=n.push(i));while(r--)he.call(e,n[r],1)}return o=null,e},ce.fn.uniqueSort=function(){return this.pushStack(ce.uniqueSort(ae.apply(this)))},(b=ce.expr={cacheLength:50,createPseudo:F,match:D,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(O,P),e[3]=(e[3]||e[4]||e[5]||"").replace(O,P),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||I.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&I.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return D.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&j.test(n)&&(t=Y(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(O,P).toLowerCase();return"*"===e?function(){return!0}:function(e){return fe(e,t)}},CLASS:function(e){var t=s[e+" "];return t||(t=new RegExp("(^|"+ge+")"+e+"("+ge+"|$)"))&&s(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=I.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1<t.indexOf(i):"$="===r?i&&t.slice(-i.length)===i:"~="===r?-1<(" "+t.replace(v," ")+" ").indexOf(i):"|="===r&&(t===i||t.slice(0,i.length+1)===i+"-"))}},CHILD:function(d,e,t,h,g){var v="nth"!==d.slice(0,3),y="last"!==d.slice(-4),m="of-type"===e;return 1===h&&0===g?function(e){return!!e.parentNode}:function(e,t,n){var r,i,o,a,s,u=v!==y?"nextSibling":"previousSibling",l=e.parentNode,c=m&&e.nodeName.toLowerCase(),f=!n&&!m,p=!1;if(l){if(v){while(u){o=e;while(o=o[u])if(m?fe(o,c):1===o.nodeType)return!1;s=u="only"===d&&!s&&"nextSibling"}return!0}if(s=[y?l.firstChild:l.lastChild],y&&f){p=(a=(r=(i=l[S]||(l[S]={}))[d]||[])[0]===E&&r[1])&&r[2],o=a&&l.childNodes[a];while(o=++a&&o&&o[u]||(p=a=0)||s.pop())if(1===o.nodeType&&++p&&o===e){i[d]=[E,a,p];break}}else if(f&&(p=a=(r=(i=e[S]||(e[S]={}))[d]||[])[0]===E&&r[1]),!1===p)while(o=++a&&o&&o[u]||(p=a=0)||s.pop())if((m?fe(o,c):1===o.nodeType)&&++p&&(f&&((i=o[S]||(o[S]={}))[d]=[E,p]),o===e))break;return(p-=g)===h||p%h==0&&0<=p/h}}},PSEUDO:function(e,o){var t,a=b.pseudos[e]||b.setFilters[e.toLowerCase()]||I.error("unsupported pseudo: "+e);return a[S]?a(o):1<a.length?(t=[e,e,"",o],b.setFilters.hasOwnProperty(e.toLowerCase())?F(function(e,t){var n,r=a(e,o),i=r.length;while(i--)e[n=se.call(e,r[i])]=!(t[n]=r[i])}):function(e){return a(e,0,t)}):a}},pseudos:{not:F(function(e){var r=[],i=[],s=ne(e.replace(ve,"$1"));return s[S]?F(function(e,t,n,r){var i,o=s(e,null,r,[]),a=e.length;while(a--)(i=o[a])&&(e[a]=!(t[a]=i))}):function(e,t,n){return r[0]=e,s(r,null,n,i),r[0]=null,!i.pop()}}),has:F(function(t){return function(e){return 0<I(t,e).length}}),contains:F(function(t){return t=t.replace(O,P),function(e){return-1<(e.textContent||ce.text(e)).indexOf(t)}}),lang:F(function(n){return A.test(n||"")||I.error("unsupported lang: "+n),n=n.replace(O,P).toLowerCase(),function(e){var t;do{if(t=C?e.lang:e.getAttribute("xml:lang")||e.getAttribute("lang"))return(t=t.toLowerCase())===n||0===t.indexOf(n+"-")}while((e=e.parentNode)&&1===e.nodeType);return!1}}),target:function(e){var t=ie.location&&ie.location.hash;return t&&t.slice(1)===e.id},root:function(e){return e===r},focus:function(e){return e===function(){try{return T.activeElement}catch(e){}}()&&T.hasFocus()&&!!(e.type||e.href||~e.tabIndex)},enabled:z(!1),disabled:z(!0),checked:function(e){return fe(e,"input")&&!!e.checked||fe(e,"option")&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!b.pseudos.empty(e)},header:function(e){return q.test(e.nodeName)},input:function(e){return N.test(e.nodeName)},button:function(e){return fe(e,"input")&&"button"===e.type||fe(e,"button")},text:function(e){var t;return fe(e,"input")&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:X(function(){return[0]}),last:X(function(e,t){return[t-1]}),eq:X(function(e,t,n){return[n<0?n+t:n]}),even:X(function(e,t){for(var n=0;n<t;n+=2)e.push(n);return e}),odd:X(function(e,t){for(var n=1;n<t;n+=2)e.push(n);return e}),lt:X(function(e,t,n){var r;for(r=n<0?n+t:t<n?t:n;0<=--r;)e.push(r);return e}),gt:X(function(e,t,n){for(var r=n<0?n+t:n;++r<t;)e.push(r);return e})}}).pseudos.nth=b.pseudos.eq,{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})b.pseudos[e]=B(e);for(e in{submit:!0,reset:!0})b.pseudos[e]=_(e);function G(){}function Y(e,t){var n,r,i,o,a,s,u,l=c[e+" "];if(l)return t?0:l.slice(0);a=e,s=[],u=b.preFilter;while(a){for(o in n&&!(r=y.exec(a))||(r&&(a=a.slice(r[0].length)||a),s.push(i=[])),n=!1,(r=m.exec(a))&&(n=r.shift(),i.push({value:n,type:r[0].replace(ve," ")}),a=a.slice(n.length)),b.filter)!(r=D[o].exec(a))||u[o]&&!(r=u[o](r))||(n=r.shift(),i.push({value:n,type:o,matches:r}),a=a.slice(n.length));if(!n)break}return t?a.length:a?I.error(e):c(e,s).slice(0)}function Q(e){for(var t=0,n=e.length,r="";t<n;t++)r+=e[t].value;return r}function J(a,e,t){var s=e.dir,u=e.next,l=u||s,c=t&&"parentNode"===l,f=n++;return e.first?function(e,t,n){while(e=e[s])if(1===e.nodeType||c)return a(e,t,n);return!1}:function(e,t,n){var r,i,o=[E,f];if(n){while(e=e[s])if((1===e.nodeType||c)&&a(e,t,n))return!0}else while(e=e[s])if(1===e.nodeType||c)if(i=e[S]||(e[S]={}),u&&fe(e,u))e=e[s]||e;else{if((r=i[l])&&r[0]===E&&r[1]===f)return o[2]=r[2];if((i[l]=o)[2]=a(e,t,n))return!0}return!1}}function K(i){return 1<i.length?function(e,t,n){var r=i.length;while(r--)if(!i[r](e,t,n))return!1;return!0}:i[0]}function Z(e,t,n,r,i){for(var o,a=[],s=0,u=e.length,l=null!=t;s<u;s++)(o=e[s])&&(n&&!n(o,r,i)||(a.push(o),l&&t.push(s)));return a}function ee(d,h,g,v,y,e){return v&&!v[S]&&(v=ee(v)),y&&!y[S]&&(y=ee(y,e)),F(function(e,t,n,r){var i,o,a,s,u=[],l=[],c=t.length,f=e||function(e,t,n){for(var r=0,i=t.length;r<i;r++)I(e,t[r],n);return n}(h||"*",n.nodeType?[n]:n,[]),p=!d||!e&&h?f:Z(f,u,d,n,r);if(g?g(p,s=y||(e?d:c||v)?[]:t,n,r):s=p,v){i=Z(s,l),v(i,[],n,r),o=i.length;while(o--)(a=i[o])&&(s[l[o]]=!(p[l[o]]=a))}if(e){if(y||d){if(y){i=[],o=s.length;while(o--)(a=s[o])&&i.push(p[o]=a);y(null,s=[],i,r)}o=s.length;while(o--)(a=s[o])&&-1<(i=y?se.call(e,a):u[o])&&(e[i]=!(t[i]=a))}}else s=Z(s===t?s.splice(c,s.length):s),y?y(null,t,s,r):k.apply(t,s)})}function te(e){for(var i,t,n,r=e.length,o=b.relative[e[0].type],a=o||b.relative[" "],s=o?1:0,u=J(function(e){return e===i},a,!0),l=J(function(e){return-1<se.call(i,e)},a,!0),c=[function(e,t,n){var r=!o&&(n||t!=w)||((i=t).nodeType?u(e,t,n):l(e,t,n));return i=null,r}];s<r;s++)if(t=b.relative[e[s].type])c=[J(K(c),t)];else{if((t=b.filter[e[s].type].apply(null,e[s].matches))[S]){for(n=++s;n<r;n++)if(b.relative[e[n].type])break;return ee(1<s&&K(c),1<s&&Q(e.slice(0,s-1).concat({value:" "===e[s-2].type?"*":""})).replace(ve,"$1"),t,s<n&&te(e.slice(s,n)),n<r&&te(e=e.slice(n)),n<r&&Q(e))}c.push(t)}return K(c)}function ne(e,t){var n,v,y,m,x,r,i=[],o=[],a=u[e+" "];if(!a){t||(t=Y(e)),n=t.length;while(n--)(a=te(t[n]))[S]?i.push(a):o.push(a);(a=u(e,(v=o,m=0<(y=i).length,x=0<v.length,r=function(e,t,n,r,i){var o,a,s,u=0,l="0",c=e&&[],f=[],p=w,d=e||x&&b.find.TAG("*",i),h=E+=null==p?1:Math.random()||.1,g=d.length;for(i&&(w=t==T||t||i);l!==g&&null!=(o=d[l]);l++){if(x&&o){a=0,t||o.ownerDocument==T||(V(o),n=!C);while(s=v[a++])if(s(o,t||T,n)){k.call(r,o);break}i&&(E=h)}m&&((o=!s&&o)&&u--,e&&c.push(o))}if(u+=l,m&&l!==u){a=0;while(s=y[a++])s(c,f,t,n);if(e){if(0<u)while(l--)c[l]||f[l]||(f[l]=pe.call(r));f=Z(f)}k.apply(r,f),i&&!e&&0<f.length&&1<u+y.length&&ce.uniqueSort(r)}return i&&(E=h,w=p),c},m?F(r):r))).selector=e}return a}function re(e,t,n,r){var i,o,a,s,u,l="function"==typeof e&&e,c=!r&&Y(e=l.selector||e);if(n=n||[],1===c.length){if(2<(o=c[0]=c[0].slice(0)).length&&"ID"===(a=o[0]).type&&9===t.nodeType&&C&&b.relative[o[1].type]){if(!(t=(b.find.ID(a.matches[0].replace(O,P),t)||[])[0]))return n;l&&(t=t.parentNode),e=e.slice(o.shift().value.length)}i=D.needsContext.test(e)?0:o.length;while(i--){if(a=o[i],b.relative[s=a.type])break;if((u=b.find[s])&&(r=u(a.matches[0].replace(O,P),H.test(o[0].type)&&U(t.parentNode)||t))){if(o.splice(i,1),!(e=r.length&&Q(o)))return k.apply(n,r),n;break}}}return(l||ne(e,c))(r,t,!C,n,!t||H.test(e)&&U(t.parentNode)||t),n}G.prototype=b.filters=b.pseudos,b.setFilters=new G,le.sortStable=S.split("").sort(l).join("")===S,V(),le.sortDetached=$(function(e){return 1&e.compareDocumentPosition(T.createElement("fieldset"))}),ce.find=I,ce.expr[":"]=ce.expr.pseudos,ce.unique=ce.uniqueSort,I.compile=ne,I.select=re,I.setDocument=V,I.tokenize=Y,I.escape=ce.escapeSelector,I.getText=ce.text,I.isXML=ce.isXMLDoc,I.selectors=ce.expr,I.support=ce.support,I.uniqueSort=ce.uniqueSort}();var d=function(e,t,n){var r=[],i=void 0!==n;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&ce(e).is(n))break;r.push(e)}return r},h=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},b=ce.expr.match.needsContext,w=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function T(e,n,r){return v(n)?ce.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?ce.grep(e,function(e){return e===n!==r}):"string"!=typeof n?ce.grep(e,function(e){return-1<se.call(n,e)!==r}):ce.filter(n,e,r)}ce.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?ce.find.matchesSelector(r,e)?[r]:[]:ce.find.matches(e,ce.grep(t,function(e){return 1===e.nodeType}))},ce.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(ce(e).filter(function(){for(t=0;t<r;t++)if(ce.contains(i[t],this))return!0}));for(n=this.pushStack([]),t=0;t<r;t++)ce.find(e,i[t],n);return 1<r?ce.uniqueSort(n):n},filter:function(e){return this.pushStack(T(this,e||[],!1))},not:function(e){return this.pushStack(T(this,e||[],!0))},is:function(e){return!!T(this,"string"==typeof e&&b.test(e)?ce(e):e||[],!1).length}});var k,S=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(ce.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||k,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:S.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof ce?t[0]:t,ce.merge(this,ce.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:C,!0)),w.test(r[1])&&ce.isPlainObject(t))for(r in t)v(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=C.getElementById(r[2]))&&(this[0]=i,this.length=1),this}
vendor: 1,967 bytes, line 2
2return e.nodeType?(this[0]=e,this.length=1,this):v(e)?void 0!==n.ready?n.ready(e):e(ce):ce.makeArray(e,this)}).prototype=ce.fn,k=ce(C);var E=/^(?:parents|prev(?:Until|All))/,j={children:!0,contents:!0,next:!0,prev:!0};function A(e,t){while((e=e[t])&&1!==e.nodeType);return e}ce.fn.extend({has:function(e){var t=ce(e,this),n=t.length;return this.filter(function(){for(var e=0;e<n;e++)if(ce.contains(this,t[e]))return!0})},closest:function(e,t){var n,r=0,i=this.length,o=[],a="string"!=typeof e&&ce(e);if(!b.test(e))for(;r<i;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(n.nodeType<11&&(a?-1<a.index(n):1===n.nodeType&&ce.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(1<o.length?ce.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?se.call(ce(e),this[0]):se.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(ce.uniqueSort(ce.merge(this.get(),ce(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),ce.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return d(e,"parentNode")},parentsUntil:function(e,t,n){return d(e,"parentNode",n)},next:function(e){return A(e,"nextSibling")},prev:function(e){return A(e,"previousSibling")},nextAll:function(e){return d(e,"nextSibling")},prevAll:function(e){return d(e,"previousSibling")},nextUntil:function(e,t,n){return d(e,"nextSibling",n)},prevUntil:function(e,t,n){return d(e,"previousSibling",n)},siblings:function(e){return h((e.parentNode||{}).firstChild,e)},children:function(e){return h(e.firstChild)},contents:function(e){return null!=e.contentDocument&&r(e.contentDocument)?e.contentDocument:(fe(e,"template")&&(e=e.content||e),ce.merge([],e.childNodes))}},function(r,i){ce.fn[r]=function(e,t){var n=ce.map(this,i,e);return"Until"!==r.slice(-5)&&(t=e),t&&"string"==typeof t&&(n=ce.filter(t,n)),1<this.length&&(j[r]||
vendor: 9,546 bytes, line 2
2ce.uniqueSort(n),E.test(r)&&n.reverse()),this.pushStack(n)}});var D=/[^\x20\t\r\n\f]+/g;function N(e){return e}function q(e){throw e}function L(e,t,n,r){var i;try{e&&v(i=e.promise)?i.call(e).done(t).fail(n):e&&v(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}ce.Callbacks=function(r){var e,n;r="string"==typeof r?(e=r,n={},ce.each(e.match(D)||[],function(e,t){n[t]=!0}),n):ce.extend({},r);var i,t,o,a,s=[],u=[],l=-1,c=function(){for(a=a||r.once,o=i=!0;u.length;l=-1){t=u.shift();while(++l<s.length)!1===s[l].apply(t[0],t[1])&&r.stopOnFalse&&(l=s.length,t=!1)}r.memory||(t=!1),i=!1,a&&(s=t?[]:"")},f={add:function(){return s&&(t&&!i&&(l=s.length-1,u.push(t)),function n(e){ce.each(e,function(e,t){v(t)?r.unique&&f.has(t)||s.push(t):t&&t.length&&"string"!==x(t)&&n(t)})}(arguments),t&&!i&&c()),this},remove:function(){return ce.each(arguments,function(e,t){var n;while(-1<(n=ce.inArray(t,s,n)))s.splice(n,1),n<=l&&l--}),this},has:function(e){return e?-1<ce.inArray(e,s):0<s.length},empty:function(){return s&&(s=[]),this},disable:function(){return a=u=[],s=t="",this},disabled:function(){return!s},lock:function(){return a=u=[],t||i||(s=t=""),this},locked:function(){return!!a},fireWith:function(e,t){return a||(t=[e,(t=t||[]).slice?t.slice():t],u.push(t),i||c()),this},fire:function(){return f.fireWith(this,arguments),this},fired:function(){return!!o}};return f},ce.extend({Deferred:function(e){var o=[["notify","progress",ce.Callbacks("memory"),ce.Callbacks("memory"),2],["resolve","done",ce.Callbacks("once memory"),ce.Callbacks("once memory"),0,"resolved"],["reject","fail",ce.Callbacks("once memory"),ce.Callbacks("once memory"),1,"rejected"]],i="pending",a={state:function(){return i},always:function(){return s.done(arguments).fail(arguments),this},"catch":function(e){return a.then(null,e)},pipe:function(){var i=arguments;return ce.Deferred(function(r){ce.each(o,function(e,t){var n=v(i[t[4]])&&i[t[4]];s[t[1]](function(){var e=n&&n.apply(this,arguments);e&&v(e.promise)?e.promise().progress(r.notify).done(r.resolve).fail(r.reject):r[t[0]+"With"](this,n?[e]:arguments)})}),i=null}).promise()},then:function(t,n,r){var u=0;function l(i,o,a,s){return function(){var n=this,r=arguments,e=function(){var e,t;if(!(i<u)){if((e=a.apply(n,r))===o.promise())throw new TypeError("Thenable self-resolution");t=e&&("object"==typeof e||"function"==typeof e)&&e.then,v(t)?s?t.call(e,l(u,o,N,s),l(u,o,q,s)):(u++,t.call(e,l(u,o,N,s),l(u,o,q,s),l(u,o,N,o.notifyWith))):(a!==N&&(n=void 0,r=[e]),(s||o.resolveWith)(n,r))}},t=s?e:function(){try{e()}catch(e){ce.Deferred.exceptionHook&&ce.Deferred.exceptionHook(e,t.error),u<=i+1&&(a!==q&&(n=void 0,r=[e]),o.rejectWith(n,r))}};i?t():(ce.Deferred.getErrorHook?t.error=ce.Deferred.getErrorHook():ce.Deferred.getStackHook&&(t.error=ce.Deferred.getStackHook()),ie.setTimeout(t))}}return ce.Deferred(function(e){o[0][3].add(l(0,e,v(r)?r:N,e.notifyWith)),o[1][3].add(l(0,e,v(t)?t:N)),o[2][3].add(l(0,e,v(n)?n:q))}).promise()},promise:function(e){return null!=e?ce.extend(e,a):a}},s={};return ce.each(o,function(e,t){var n=t[2],r=t[5];a[t[1]]=n.add,r&&n.add(function(){i=r},o[3-e][2].disable,o[3-e][3].disable,o[0][2].lock,o[0][3].lock),n.add(t[3].fire),s[t[0]]=function(){return s[t[0]+"With"](this===s?void 0:this,arguments),this},s[t[0]+"With"]=n.fireWith}),a.promise(s),e&&e.call(s,s),s},when:function(e){var n=arguments.length,t=n,r=Array(t),i=ae.call(arguments),o=ce.Deferred(),a=function(t){return function(e){r[t]=this,i[t]=1<arguments.length?ae.call(arguments):e,--n||o.resolveWith(r,i)}};if(n<=1&&(L(e,o.done(a(t)).resolve,o.reject,!n),"pending"===o.state()||v(i[t]&&i[t].then)))return o.then();while(t--)L(i[t],a(t),o.reject);return o.promise()}});var H=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;ce.Deferred.exceptionHook=function(e,t){ie.console&&ie.console.warn&&e&&H.test(e.name)&&ie.console.warn("jQuery.Deferred exception: "+e.message,e.stack,t)},ce.readyException=function(e){ie.setTimeout(function(){throw e})};var O=ce.Deferred();function P(){C.removeEventListener("DOMContentLoaded",P),ie.removeEventListener("load",P),ce.ready()}ce.fn.ready=function(e){return O.then(e)["catch"](function(e){ce.readyException(e)}),this},ce.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--ce.readyWait:ce.isReady)||(ce.isReady=!0)!==e&&0<--ce.readyWait||O.resolveWith(C,[ce])}}),ce.ready.then=O.then,"complete"===C.readyState||"loading"!==C.readyState&&!C.documentElement.doScroll?ie.setTimeout(ce.ready):(C.addEventListener("DOMContentLoaded",P),ie.addEventListener("load",P));var M=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if("object"===x(n))for(s in i=!0,n)M(e,t,s,n[s],!0,o,a);else if(void 0!==r&&(i=!0,v(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(ce(e),n)})),t))for(;s<u;s++)t(e[s],n,a?r:r.call(e[s],s,t(e[s],n)));return i?e:l?t.call(e):u?t(e[0],n):o},R=/^-ms-/,I=/-([a-z])/g;function W(e,t){return t.toUpperCase()}function F(e){return e.replace(R,"ms-").replace(I,W)}var $=function(e){return 1===e.nodeType||9===e.nodeType||!+e.nodeType};function B(){this.expando=ce.expando+B.uid++}B.uid=1,B.prototype={cache:function(e){var t=e[this.expando];return t||(t={},$(e)&&(e.nodeType?e[this.expando]=t:Object.defineProperty(e,this.expando,{value:t,configurable:!0}))),t},set:function(e,t,n){var r,i=this.cache(e);if("string"==typeof t)i[F(t)]=n;else for(r in t)i[F(r)]=t[r];return i},get:function(e,t){return void 0===t?this.cache(e):e[this.expando]&&e[this.expando][F(t)]},access:function(e,t,n){return void 0===t||t&&"string"==typeof t&&void 0===n?this.get(e,t):(this.set(e,t,n),void 0!==n?n:t)},remove:function(e,t){var n,r=e[this.expando];if(void 0!==r){if(void 0!==t){n=(t=Array.isArray(t)?t.map(F):(t=F(t))in r?[t]:t.match(D)||[]).length;while(n--)delete r[t[n]]}(void 0===t||ce.isEmptyObject(r))&&(e.nodeType?e[this.expando]=void 0:delete e[this.expando])}},hasData:function(e){var t=e[this.expando];return void 0!==t&&!ce.isEmptyObject(t)}};var _=new B,z=new B,X=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,U=/[A-Z]/g;function V(e,t,n){var r,i;if(void 0===n&&1===e.nodeType)if(r="data-"+t.replace(U,"-$&").toLowerCase(),"string"==typeof(n=e.getAttribute(r))){try{n="true"===(i=n)||"false"!==i&&("null"===i?null:i===+i+""?+i:X.test(i)?JSON.parse(i):i)}catch(e){}z.set(e,t,n)}else n=void 0;return n}ce.extend({hasData:function(e){return z.hasData(e)||_.hasData(e)},data:function(e,t,n){return z.access(e,t,n)},removeData:function(e,t){z.remove(e,t)},_data:function(e,t,n){return _.access(e,t,n)},_removeData:function(e,t){_.remove(e,t)}}),ce.fn.extend({data:function(n,e){var t,r,i,o=this[0],a=o&&o.attributes;if(void 0===n){if(this.length&&(i=z.get(o),1===o.nodeType&&!_.get(o,"hasDataAttrs"))){t=a.length;while(t--)a[t]&&0===(r=a[t].name).indexOf("data-")&&(r=F(r.slice(5)),V(o,r,i[r]));_.set(o,"hasDataAttrs",!0)}return i}return"object"==typeof n?this.each(function(){z.set(this,n)}):M(this,function(e){var t;if(o&&void 0===e)return void 0!==(t=z.get(o,n))?t:void 0!==(t=V(o,n))?t:void 0;this.each(function(){z.set(this,n,e)})},null,e,1<arguments.length,null,!0)},removeData:function(e){return this.each(function(){z.remove(this,e)})}}),ce.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=_.get(e,t),n&&(!r||Array.isArray(n)?r=_.access(e,t,ce.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=ce.queue(e,t),r=n.length,i=n.shift(),o=ce._queueHooks(e,t);"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,function(){ce.dequeue(e,t)},o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return _.get(e,n)||_.access(e,n,{empty:ce.Callbacks("once memory").add(function(){_.remove(e,[t+"queue",n])})})}}),ce.fn.extend({queue:function(t,n){var e=2;return"string"!=typeof t&&(n=t,t="fx",e--),arguments.length<e?ce.queue(this[0],t):void 0===n?this:this.each(function(){var e=ce.queue(this,t,n);ce._queueHooks(this,t),"fx"===t&&"inprogress"!==e[0]&&ce.dequeue(this,t)})},dequeue:function(e){return this.each(function(){ce.dequeue(this,e)})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,t){var n,r=1,i=ce.Deferred(),o=this,a=this.length,s=function(){--r||i.resolveWith(o,[o])};"string"!=typeof e&&(t=e,e=void 0),e=e||"fx";while(a--)(n=_.get(o[a],e+"queueHooks"))&&n.empty&&(r++,n.empty.add(s));return s(),i.promise(t)}});var G=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,Y=new RegExp("^(?:([+-])=|)("+G+")([a-z%]*)$","i"),Q=["Top","Right","Bottom","Left"],J=C.documentElement,K=function(e){return ce.contains(e.ownerDocument,e)},Z={composed:!0};J.getRootNode&&(K=function(e){return ce.contains(e.ownerDocument,e)||e.getRootNode(Z)===e.ownerDocument});var ee=function(e,t){return"none"===(e=t||e).style.display||""===e.style.display&&K(e)&&"none"===ce.css(e,"display")};function te(e,t,n,r){var i,o,a=20,s=r?function(){return r.cur()}:function(){return ce.css(e,t,"")},u=s(),l=n&&n[3]||(ce.cssNumber[t]?"":"px"),c=e.nodeType&&(ce.cssNumber[t]||"px"!==l&&+u)&&Y.exec(ce.css(e,t));if(c&&c[3]!==l){u/=2,l=l||c[3],c=+u||1;while(a--)ce.style(e,t,c+l),(1-o)*(1-(o=s()/u||.5))<=0&&(a=0),c/=o;c*=2,ce.style(e,t,c+l),n=n||[]}return n&&(c=+c||+u||0,i=n[1]?c+(n[1]+1)*n[2]:+n[2],r&&(r.unit=l,r.start=c,r.end=i)),i}var ne={};function re(e,t){for(var n,r,i,o,a,s,u,l=[],c=0,f=e.length;c<f;c++)(r=e[c]).style&&(n=r.style.display,t?("none"===n&&(l[c]=_.get(r,"display")||null,l[c]||(r.style.display="")),""===r.style.display&&ee(r)&&(l[c]=(u=a=o=void 0,a=(i=r).ownerDocument,s=i.nodeName,(u=ne[s])||
vendor: 8,814 bytes, line 2
2(o=a.body.appendChild(a.createElement(s)),u=ce.css(o,"display"),o.parentNode.removeChild(o),"none"===u&&(u="block"),ne[s]=u)))):"none"!==n&&(l[c]="none",_.set(r,"display",n)));for(c=0;c<f;c++)null!=l[c]&&(e[c].style.display=l[c]);return e}ce.fn.extend({show:function(){return re(this,!0)},hide:function(){return re(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){ee(this)?ce(this).show():ce(this).hide()})}});var xe,be,we=/^(?:checkbox|radio)$/i,Te=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i,Ce=/^$|^module$|\/(?:java|ecma)script/i;xe=C.createDocumentFragment().appendChild(C.createElement("div")),(be=C.createElement("input")).setAttribute("type","radio"),be.setAttribute("checked","checked"),be.setAttribute("name","t"),xe.appendChild(be),le.checkClone=xe.cloneNode(!0).cloneNode(!0).lastChild.checked,xe.innerHTML="<textarea>x</textarea>",le.noCloneChecked=!!xe.cloneNode(!0).lastChild.defaultValue,xe.innerHTML="<option></option>",le.option=!!xe.lastChild;var ke={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,"",""]};function Se(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&fe(e,t)?ce.merge([e],n):n}function Ee(e,t){for(var n=0,r=e.length;n<r;n++)_.set(e[n],"globalEval",!t||_.get(t[n],"globalEval"))}ke.tbody=ke.tfoot=ke.colgroup=ke.caption=ke.thead,ke.th=ke.td,le.option||(ke.optgroup=ke.option=[1,"<select multiple='multiple'>","</select>"]);var je=/<|&#?\w+;/;function Ae(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d<h;d++)if((o=e[d])||0===o)if("object"===x(o))ce.merge(p,o.nodeType?[o]:o);else if(je.test(o)){a=a||f.appendChild(t.createElement("div")),s=(Te.exec(o)||["",""])[1].toLowerCase(),u=ke[s]||ke._default,a.innerHTML=u[1]+ce.htmlPrefilter(o)+u[2],c=u[0];while(c--)a=a.lastChild;ce.merge(p,a.childNodes),(a=f.firstChild).textContent=""}else p.push(t.createTextNode(o));f.textContent="",d=0;while(o=p[d++])if(r&&-1<ce.inArray(o,r))i&&i.push(o);else if(l=K(o),a=Se(f.appendChild(o),"script"),l&&Ee(a),n){c=0;while(o=a[c++])Ce.test(o.type||"")&&n.push(o)}return f}var De=/^([^.]*)(?:\.(.+)|)/;function Ne(){return!0}function qe(){return!1}function Le(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)Le(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=qe;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return ce().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=ce.guid++)),e.each(function(){ce.event.add(this,t,i,r,n)})}function He(e,r,t){t?(_.set(e,r,!1),ce.event.add(e,r,{namespace:!1,handler:function(e){var t,n=_.get(this,r);if(1&e.isTrigger&&this[r]){if(n)(ce.event.special[r]||{}).delegateType&&e.stopPropagation();else if(n=ae.call(arguments),_.set(this,r,n),this[r](),t=_.get(this,r),_.set(this,r,!1),n!==t)return e.stopImmediatePropagation(),e.preventDefault(),t}else n&&(_.set(this,r,ce.event.trigger(n[0],n.slice(1),this)),e.stopPropagation(),e.isImmediatePropagationStopped=Ne)}})):void 0===_.get(e,r)&&ce.event.add(e,r,Ne)}ce.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=_.get(t);if($(t)){n.handler&&(n=(o=n).handler,i=o.selector),i&&ce.find.matchesSelector(J,i),n.guid||(n.guid=ce.guid++),(u=v.events)||(u=v.events=Object.create(null)),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof ce&&ce.event.triggered!==e.type?ce.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(D)||[""]).length;while(l--)d=g=(s=De.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=ce.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=ce.event.special[d]||{},c=ce.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&ce.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),ce.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=_.hasData(e)&&_.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(D)||[""]).length;while(l--)if(d=g=(s=De.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d){f=ce.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||ce.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)ce.event.remove(e,d+t[l],n,r,!0);ce.isEmptyObject(u)&&_.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=new Array(arguments.length),u=ce.event.fix(e),l=(_.get(this,"events")||Object.create(null))[u.type]||[],c=ce.event.special[u.type]||{};for(s[0]=u,t=1;t<arguments.length;t++)s[t]=arguments[t];if(u.delegateTarget=this,!c.preDispatch||!1!==c.preDispatch.call(this,u)){a=ce.event.handlers.call(this,u,l),t=0;while((i=a[t++])&&!u.isPropagationStopped()){u.currentTarget=i.elem,n=0;while((o=i.handlers[n++])&&!u.isImmediatePropagationStopped())u.rnamespace&&!1!==o.namespace&&!u.rnamespace.test(o.namespace)||(u.handleObj=o,u.data=o.data,void 0!==(r=((ce.event.special[o.origType]||{}).handle||o.handler).apply(i.elem,s))&&!1===(u.result=r)&&(u.preventDefault(),u.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,u),u.result}},handlers:function(e,t){var n,r,i,o,a,s=[],u=t.delegateCount,l=e.target;if(u&&l.nodeType&&!("click"===e.type&&1<=e.button))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n<u;n++)void 0===a[i=(r=t[n]).selector+" "]&&(a[i]=r.needsContext?-1<ce(i,this).index(l):ce.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u<t.length&&s.push({elem:l,handlers:t.slice(u)}),s},addProp:function(t,e){Object.defineProperty(ce.Event.prototype,t,{enumerable:!0,configurable:!0,get:v(e)?function(){if(this.originalEvent)return e(this.originalEvent)}:function(){if(this.originalEvent)return this.originalEvent[t]},set:function(e){Object.defineProperty(this,t,{enumerable:!0,configurable:!0,writable:!0,value:e})}})},fix:function(e){return e[ce.expando]?e:new ce.Event(e)},special:{load:{noBubble:!0},click:{setup:function(e){var t=this||e;return we.test(t.type)&&t.click&&fe(t,"input")&&He(t,"click",!0),!1},trigger:function(e){var t=this||e;return we.test(t.type)&&t.click&&fe(t,"input")&&He(t,"click"),!0},_default:function(e){var t=e.target;return we.test(t.type)&&t.click&&fe(t,"input")&&_.get(t,"click")||fe(t,"a")}},beforeunload:{postDispatch:function(e){void 0!==e.result&&e.originalEvent&&(e.originalEvent.returnValue=e.result)}}}},ce.removeEvent=function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n)},ce.Event=function(e,t){if(!(this instanceof ce.Event))return new ce.Event(e,t);e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||void 0===e.defaultPrevented&&!1===e.returnValue?Ne:qe,this.target=e.target&&3===e.target.nodeType?e.target.parentNode:e.target,this.currentTarget=e.currentTarget,this.relatedTarget=e.relatedTarget):this.type=e,t&&ce.extend(this,t),this.timeStamp=e&&e.timeStamp||Date.now(),this[ce.expando]=!0},ce.Event.prototype={constructor:ce.Event,isDefaultPrevented:qe,isPropagationStopped:qe,isImmediatePropagationStopped:qe,isSimulated:!1,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=Ne,e&&!this.isSimulated&&e.preventDefault()},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=Ne,e&&!this.isSimulated&&e.stopPropagation()},stopImmediatePropagation:function(){var e=this.originalEvent;this.isImmediatePropagationStopped=Ne,e&&!this.isSimulated&&e.stopImmediatePropagation(),this.stopPropagation()}},ce.each({altKey:!0,bubbles:!0,cancelable:!0,changedTouches:!0,ctrlKey:!0,detail:!0,eventPhase:!0,metaKey:!0,pageX:!0,pageY:!0,shiftKey:!0,view:!0,"char":!0,code:!0,charCode:!0,key:!0,keyCode:!0,button:!0,buttons:!0,clientX:!0,clientY:!0,offsetX:!0,offsetY:!0,pointerId:!0,pointerType:!0,screenX:!0,screenY:!0,targetTouches:!0,toElement:!0,touches:!0,which:!0},ce.event.addProp),ce.each({focus:"focusin",blur:"focusout"},function(r,i){function o(e){if(C.documentMode){var t=_.get(this,"handle"),n=ce.event.fix(e);
vendor: 14,182 bytes, line 2
2n.type="focusin"===e.type?"focus":"blur",n.isSimulated=!0,t(e),n.target===n.currentTarget&&t(n)}else ce.event.simulate(i,e.target,ce.event.fix(e))}ce.event.special[r]={setup:function(){var e;if(He(this,r,!0),!C.documentMode)return!1;(e=_.get(this,i))||this.addEventListener(i,o),_.set(this,i,(e||0)+1)},trigger:function(){return He(this,r),!0},teardown:function(){var e;if(!C.documentMode)return!1;(e=_.get(this,i)-1)?_.set(this,i,e):(this.removeEventListener(i,o),_.remove(this,i))},_default:function(e){return _.get(e.target,r)},delegateType:i},ce.event.special[i]={setup:function(){var e=this.ownerDocument||this.document||this,t=C.documentMode?this:e,n=_.get(t,i);n||(C.documentMode?this.addEventListener(i,o):e.addEventListener(r,o,!0)),_.set(t,i,(n||0)+1)},teardown:function(){var e=this.ownerDocument||this.document||this,t=C.documentMode?this:e,n=_.get(t,i)-1;n?_.set(t,i,n):(C.documentMode?this.removeEventListener(i,o):e.removeEventListener(r,o,!0),_.remove(t,i))}}}),ce.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(e,i){ce.event.special[e]={delegateType:i,bindType:i,handle:function(e){var t,n=e.relatedTarget,r=e.handleObj;return n&&(n===this||ce.contains(this,n))||(e.type=r.origType,t=r.handler.apply(this,arguments),e.type=i),t}}}),ce.fn.extend({on:function(e,t,n,r){return Le(this,e,t,n,r)},one:function(e,t,n,r){return Le(this,e,t,n,r,1)},off:function(e,t,n){var r,i;if(e&&e.preventDefault&&e.handleObj)return r=e.handleObj,ce(e.delegateTarget).off(r.namespace?r.origType+"."+r.namespace:r.origType,r.selector,r.handler),this;if("object"==typeof e){for(i in e)this.off(i,t,e[i]);return this}return!1!==t&&"function"!=typeof t||(n=t,t=void 0),!1===n&&(n=qe),this.each(function(){ce.event.remove(this,e,n,t)})}});var Oe=/<script|<style|<link/i,Pe=/checked\s*(?:[^=]|=\s*.checked.)/i,Me=/^\s*<!\[CDATA\[|\]\]>\s*$/g;function Re(e,t){return fe(e,"table")&&fe(11!==t.nodeType?t:t.firstChild,"tr")&&ce(e).children("tbody")[0]||e}function Ie(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function We(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Fe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(_.hasData(e)&&(s=_.get(e).events))for(i in _.remove(t,"handle events"),s)for(n=0,r=s[i].length;n<r;n++)ce.event.add(t,i,s[i][n]);z.hasData(e)&&(o=z.access(e),a=ce.extend({},o),z.set(t,a))}}function $e(n,r,i,o){r=g(r);var e,t,a,s,u,l,c=0,f=n.length,p=f-1,d=r[0],h=v(d);if(h||1<f&&"string"==typeof d&&!le.checkClone&&Pe.test(d))return n.each(function(e){var t=n.eq(e);h&&(r[0]=d.call(this,e,t.html())),$e(t,r,i,o)});if(f&&(t=(e=Ae(r,n[0].ownerDocument,!1,n,o)).firstChild,1===e.childNodes.length&&(e=t),t||o)){for(s=(a=ce.map(Se(e,"script"),Ie)).length;c<f;c++)u=e,c!==p&&(u=ce.clone(u,!0,!0),s&&ce.merge(a,Se(u,"script"))),i.call(n[c],u,c);if(s)for(l=a[a.length-1].ownerDocument,ce.map(a,We),c=0;c<s;c++)u=a[c],Ce.test(u.type||"")&&!_.access(u,"globalEval")&&ce.contains(l,u)&&(u.src&&"module"!==(u.type||"").toLowerCase()?ce._evalUrl&&!u.noModule&&ce._evalUrl(u.src,{nonce:u.nonce||u.getAttribute("nonce")},l):m(u.textContent.replace(Me,""),u,l))}return n}function Be(e,t,n){for(var r,i=t?ce.filter(t,e):e,o=0;null!=(r=i[o]);o++)n||1!==r.nodeType||ce.cleanData(Se(r)),r.parentNode&&(n&&K(r)&&Ee(Se(r,"script")),r.parentNode.removeChild(r));return e}ce.extend({htmlPrefilter:function(e){return e},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=K(e);if(!(le.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||ce.isXMLDoc(e)))for(a=Se(c),r=0,i=(o=Se(e)).length;r<i;r++)s=o[r],u=a[r],void 0,"input"===(l=u.nodeName.toLowerCase())&&we.test(s.type)?u.checked=s.checked:"input"!==l&&"textarea"!==l||(u.defaultValue=s.defaultValue);if(t)if(n)for(o=o||Se(e),a=a||Se(c),r=0,i=o.length;r<i;r++)Fe(o[r],a[r]);else Fe(e,c);return 0<(a=Se(c,"script")).length&&Ee(a,!f&&Se(e,"script")),c},cleanData:function(e){for(var t,n,r,i=ce.event.special,o=0;void 0!==(n=e[o]);o++)if($(n)){if(t=n[_.expando]){if(t.events)for(r in t.events)i[r]?ce.event.remove(n,r):ce.removeEvent(n,r,t.handle);n[_.expando]=void 0}n[z.expando]&&(n[z.expando]=void 0)}}}),ce.fn.extend({detach:function(e){return Be(this,e,!0)},remove:function(e){return Be(this,e)},text:function(e){return M(this,function(e){return void 0===e?ce.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return $e(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Re(this,e).appendChild(e)})},prepend:function(){return $e(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Re(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return $e(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return $e(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(ce.cleanData(Se(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return ce.clone(this,e,t)})},html:function(e){return M(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Oe.test(e)&&!ke[(Te.exec(e)||["",""])[1].toLowerCase()]){e=ce.htmlPrefilter(e);try{for(;n<r;n++)1===(t=this[n]||{}).nodeType&&(ce.cleanData(Se(t,!1)),t.innerHTML=e);t=0}catch(e){}}t&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var n=[];return $e(this,arguments,function(e){var t=this.parentNode;ce.inArray(this,n)<0&&(ce.cleanData(Se(this)),t&&t.replaceChild(e,this))},n)}}),ce.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,a){ce.fn[e]=function(e){for(var t,n=[],r=ce(e),i=r.length-1,o=0;o<=i;o++)t=o===i?this:this.clone(!0),ce(r[o])[a](t),s.apply(n,t.get());return this.pushStack(n)}});var _e=new RegExp("^("+G+")(?!px)[a-z%]+$","i"),ze=/^--/,Xe=function(e){var t=e.ownerDocument.defaultView;return t&&t.opener||(t=ie),t.getComputedStyle(e)},Ue=function(e,t,n){var r,i,o={};for(i in t)o[i]=e.style[i],e.style[i]=t[i];for(i in r=n.call(e),t)e.style[i]=o[i];return r},Ve=new RegExp(Q.join("|"),"i");function Ge(e,t,n){var r,i,o,a,s=ze.test(t),u=e.style;return(n=n||Xe(e))&&(a=n.getPropertyValue(t)||n[t],s&&a&&(a=a.replace(ve,"$1")||void 0),""!==a||K(e)||(a=ce.style(e,t)),!le.pixelBoxStyles()&&_e.test(a)&&Ve.test(t)&&(r=u.width,i=u.minWidth,o=u.maxWidth,u.minWidth=u.maxWidth=u.width=a,a=n.width,u.width=r,u.minWidth=i,u.maxWidth=o)),void 0!==a?a+"":a}function Ye(e,t){return{get:function(){if(!e())return(this.get=t).apply(this,arguments);delete this.get}}}!function(){function e(){if(l){u.style.cssText="position:absolute;left:-11111px;width:60px;margin-top:1px;padding:0;border:0",l.style.cssText="position:relative;display:block;box-sizing:border-box;overflow:scroll;margin:auto;border:1px;padding:1px;width:60%;top:1%",J.appendChild(u).appendChild(l);var e=ie.getComputedStyle(l);n="1%"!==e.top,s=12===t(e.marginLeft),l.style.right="60%",o=36===t(e.right),r=36===t(e.width),l.style.position="absolute",i=12===t(l.offsetWidth/3),J.removeChild(u),l=null}}function t(e){return Math.round(parseFloat(e))}var n,r,i,o,a,s,u=C.createElement("div"),l=C.createElement("div");l.style&&(l.style.backgroundClip="content-box",l.cloneNode(!0).style.backgroundClip="",le.clearCloneStyle="content-box"===l.style.backgroundClip,ce.extend(le,{boxSizingReliable:function(){return e(),r},pixelBoxStyles:function(){return e(),o},pixelPosition:function(){return e(),n},reliableMarginLeft:function(){return e(),s},scrollboxSize:function(){return e(),i},reliableTrDimensions:function(){var e,t,n,r;return null==a&&(e=C.createElement("table"),t=C.createElement("tr"),n=C.createElement("div"),e.style.cssText="position:absolute;left:-11111px;border-collapse:separate",t.style.cssText="box-sizing:content-box;border:1px solid",t.style.height="1px",n.style.height="9px",n.style.display="block",J.appendChild(e).appendChild(t).appendChild(n),r=ie.getComputedStyle(t),a=parseInt(r.height,10)+parseInt(r.borderTopWidth,10)+parseInt(r.borderBottomWidth,10)===t.offsetHeight,J.removeChild(e)),a}}))}();var Qe=["Webkit","Moz","ms"],Je=C.createElement("div").style,Ke={};function Ze(e){var t=ce.cssProps[e]||Ke[e];return t||(e in Je?e:Ke[e]=function(e){var t=e[0].toUpperCase()+e.slice(1),n=Qe.length;while(n--)if((e=Qe[n]+t)in Je)return e}(e)||e)}var et=/^(none|table(?!-c[ea]).+)/,tt={position:"absolute",visibility:"hidden",display:"block"},nt={letterSpacing:"0",fontWeight:"400"};function rt(e,t,n){var r=Y.exec(t);return r?Math.max(0,r[2]-(n||0))+(r[3]||"px"):t}function it(e,t,n,r,i,o){var a="width"===t?1:0,s=0,u=0,l=0;if(n===(r?"border":"content"))return 0;for(;a<4;a+=2)"margin"===n&&(l+=ce.css(e,n+Q[a],!0,i)),r?("content"===n&&(u-=ce.css(e,"padding"+Q[a],!0,i)),"margin"!==n&&(u-=ce.css(e,"border"+Q[a]+"Width",!0,i))):(u+=ce.css(e,"padding"+Q[a],!0,i),"padding"!==n?u+=ce.css(e,"border"+Q[a]+"Width",!0,i):s+=ce.css(e,"border"+Q[a]+"Width",!0,i));return!r&&0<=o&&(u+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-u-s-.5))||0),u+l}function ot(e,t,n){var r=Xe(e),i=(!le.boxSizingReliable()||n)&&"border-box"===ce.css(e,"boxSizing",!1,r),o=i,a=Ge(e,t,r),s="offset"+t[0].toUpperCase()+t.slice(1);if(_e.test(a)){if(!n)return a;a="auto"}return(!le.boxSizingReliable()&&i||!le.reliableTrDimensions()&&fe(e,"tr")||"auto"===a||!parseFloat(a)&&"inline"===ce.css(e,"display",!1,r))&&e.getClientRects().length&&(i="border-box"===ce.css(e,"boxSizing",!1,r),(o=s in e)&&(a=e[s])),(a=parseFloat(a)||0)+it(e,t,n||(i?"border":"content"),o,r,a)+"px"}function at(e,t,n,r,i){return new at.prototype.init(e,t,n,r,i)}ce.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Ge(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,aspectRatio:!0,borderImageSlice:!0,columnCount:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,gridArea:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnStart:!0,gridRow:!0,gridRowEnd:!0,gridRowStart:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,scale:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeMiterlimit:!0,strokeOpacity:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=F(t),u=ze.test(t),l=e.style;if(u||(t=Ze(s)),a=ce.cssHooks[t]||ce.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];"string"===(o=typeof n)&&(i=Y.exec(n))&&i[1]&&(n=te(e,t,i),o="number"),null!=n&&n==n&&("number"!==o||u||(n+=i&&i[3]||(ce.cssNumber[s]?"":"px")),le.clearCloneStyle||""!==n||0!==t.indexOf("background")||(l[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(u?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,s=F(t);return ze.test(t)||(t=Ze(s)),(a=ce.cssHooks[t]||ce.cssHooks[s])&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=Ge(e,t,r)),"normal"===i&&t in nt&&(i=nt[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),ce.each(["height","width"],function(e,u){ce.cssHooks[u]={get:function(e,t,n){if(t)return!et.test(ce.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?ot(e,u,n):Ue(e,tt,function(){return ot(e,u,n)})},set:function(e,t,n){var r,i=Xe(e),o=!le.scrollboxSize()&&"absolute"===i.position,a=(o||n)&&"border-box"===ce.css(e,"boxSizing",!1,i),s=n?it(e,u,n,a,i):0;return a&&o&&(s-=Math.ceil(e["offset"+u[0].toUpperCase()+u.slice(1)]-parseFloat(i[u])-it(e,u,"border",!1,i)-.5)),s&&(r=Y.exec(t))&&"px"!==(r[3]||"px")&&(e.style[u]=t,t=ce.css(e,u)),rt(0,t,s)}}}),ce.cssHooks.marginLeft=Ye(le.reliableMarginLeft,function(e,t){if(t)return(parseFloat(Ge(e,"marginLeft"))||e.getBoundingClientRect().left-Ue(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),ce.each({margin:"",padding:"",border:"Width"},function(i,o){ce.cssHooks[i+o]={expand:function(e){for(var t=0,n={},r="string"==typeof e?e.split(" "):[e];t<4;t++)n[i+Q[t]+o]=r[t]||r[t-2]||r[0];return n}},"margin"!==i&&(ce.cssHooks[i+o].set=rt)}),ce.fn.extend({css:function(e,t){return M(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=Xe(e),i=t.length;a<i;a++)o[t[a]]=ce.css(e,t[a],!1,r);return o}return void 0!==n?ce.style(e,t,n):ce.css(e,t)},e,t,1<arguments.length)}}),((ce.Tween=at).prototype={constructor:at,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||ce.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(ce.cssNumber[n]?"":"px")},cur:function(){var e=at.propHooks[this.prop];return e&&e.get?e.get(this):at.propHooks._default.get(this)},run:function(e){var t,n=at.propHooks[this.prop];return this.options.duration?this.pos=t=ce.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):at.propHooks._default.set(this),this}}).init.prototype=at.prototype,(at.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=ce.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){ce.fx.step[e.prop]?ce.fx.step[e.prop](e):1!==e.elem.nodeType||!ce.cssHooks[e.prop]&&null==e.elem.style[Ze(e.prop)]?e.elem[e.prop]=e.now:ce.style(e.elem,e.prop,e.now+e.unit)}}}).scrollTop=at.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},ce.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},ce.fx=at.prototype.init,ce.fx.step={};var st,ut,lt,ct,ft=/^(?:toggle|show|hide)$/,pt=/queueHooks$/;function dt(){ut&&(!1===C.hidden&&ie.requestAnimationFrame?ie.requestAnimationFrame(dt):ie.setTimeout(dt,ce.fx.interval),ce.fx.tick())}function ht(){return ie.setTimeout(function(){st=void 0}),st=Date.now()}function gt(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i["margin"+(n=Q[r])]=i["padding"+n]=e;
vendor: 4,590 bytes, line 2
2return t&&(i.opacity=i.width=e),i}function vt(e,t,n){for(var r,i=(yt.tweeners[t]||[]).concat(yt.tweeners["*"]),o=0,a=i.length;o<a;o++)if(r=i[o].call(n,t,e))return r}function yt(o,e,t){var n,a,r=0,i=yt.prefilters.length,s=ce.Deferred().always(function(){delete u.elem}),u=function(){if(a)return!1;for(var e=st||ht(),t=Math.max(0,l.startTime+l.duration-e),n=1-(t/l.duration||0),r=0,i=l.tweens.length;r<i;r++)l.tweens[r].run(n);return s.notifyWith(o,[l,n,t]),n<1&&i?t:(i||s.notifyWith(o,[l,1,0]),s.resolveWith(o,[l]),!1)},l=s.promise({elem:o,props:ce.extend({},e),opts:ce.extend(!0,{specialEasing:{},easing:ce.easing._default},t),originalProperties:e,originalOptions:t,startTime:st||ht(),duration:t.duration,tweens:[],createTween:function(e,t){var n=ce.Tween(o,l.opts,e,t,l.opts.specialEasing[e]||l.opts.easing);return l.tweens.push(n),n},stop:function(e){var t=0,n=e?l.tweens.length:0;if(a)return this;for(a=!0;t<n;t++)l.tweens[t].run(1);return e?(s.notifyWith(o,[l,1,0]),s.resolveWith(o,[l,e])):s.rejectWith(o,[l,e]),this}}),c=l.props;for(!function(e,t){var n,r,i,o,a;for(n in e)if(i=t[r=F(n)],o=e[n],Array.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),(a=ce.cssHooks[r])&&"expand"in a)for(n in o=a.expand(o),delete e[r],o)n in e||(e[n]=o[n],t[n]=i);else t[r]=i}(c,l.opts.specialEasing);r<i;r++)if(n=yt.prefilters[r].call(l,o,c,l.opts))return v(n.stop)&&(ce._queueHooks(l.elem,l.opts.queue).stop=n.stop.bind(n)),n;return ce.map(c,vt,l),v(l.opts.start)&&l.opts.start.call(o,l),l.progress(l.opts.progress).done(l.opts.done,l.opts.complete).fail(l.opts.fail).always(l.opts.always),ce.fx.timer(ce.extend(u,{elem:o,anim:l,queue:l.opts.queue})),l}ce.Animation=ce.extend(yt,{tweeners:{"*":[function(e,t){var n=this.createTween(e,t);return te(n.elem,e,Y.exec(t),n),n}]},tweener:function(e,t){v(e)?(t=e,e=["*"]):e=e.match(D);for(var n,r=0,i=e.length;r<i;r++)n=e[r],yt.tweeners[n]=yt.tweeners[n]||[],yt.tweeners[n].unshift(t)},prefilters:[function(e,t,n){var r,i,o,a,s,u,l,c,f="width"in t||"height"in t,p=this,d={},h=e.style,g=e.nodeType&&ee(e),v=_.get(e,"fxshow");for(r in n.queue||(null==(a=ce._queueHooks(e,"fx")).unqueued&&(a.unqueued=0,s=a.empty.fire,a.empty.fire=function(){a.unqueued||s()}),a.unqueued++,p.always(function(){p.always(function(){a.unqueued--,ce.queue(e,"fx").length||a.empty.fire()})})),t)if(i=t[r],ft.test(i)){if(delete t[r],o=o||"toggle"===i,i===(g?"hide":"show")){if("show"!==i||!v||void 0===v[r])continue;g=!0}d[r]=v&&v[r]||ce.style(e,r)}if((u=!ce.isEmptyObject(t))||!ce.isEmptyObject(d))for(r in f&&1===e.nodeType&&(n.overflow=[h.overflow,h.overflowX,h.overflowY],null==(l=v&&v.display)&&(l=_.get(e,"display")),"none"===(c=ce.css(e,"display"))&&(l?c=l:(re([e],!0),l=e.style.display||l,c=ce.css(e,"display"),re([e]))),("inline"===c||"inline-block"===c&&null!=l)&&"none"===ce.css(e,"float")&&(u||(p.done(function(){h.display=l}),null==l&&(c=h.display,l="none"===c?"":c)),h.display="inline-block")),n.overflow&&(h.overflow="hidden",p.always(function(){h.overflow=n.overflow[0],h.overflowX=n.overflow[1],h.overflowY=n.overflow[2]})),u=!1,d)u||(v?"hidden"in v&&(g=v.hidden):v=_.access(e,"fxshow",{display:l}),o&&(v.hidden=!g),g&&re([e],!0),p.done(function(){for(r in g||re([e]),_.remove(e,"fxshow"),d)ce.style(e,r,d[r])})),u=vt(g?v[r]:0,r,p),r in v||(v[r]=u.start,g&&(u.end=u.start,u.start=0))}],prefilter:function(e,t){t?yt.prefilters.unshift(e):yt.prefilters.push(e)}}),ce.speed=function(e,t,n){var r=e&&"object"==typeof e?ce.extend({},e):{complete:n||!n&&t||v(e)&&e,duration:e,easing:n&&t||t&&!v(t)&&t};return ce.fx.off?r.duration=0:"number"!=typeof r.duration&&(r.duration in ce.fx.speeds?r.duration=ce.fx.speeds[r.duration]:r.duration=ce.fx.speeds._default),null!=r.queue&&!0!==r.queue||(r.queue="fx"),r.old=r.complete,r.complete=function(){v(r.old)&&r.old.call(this),r.queue&&ce.dequeue(this,r.queue)},r},ce.fn.extend({fadeTo:function(e,t,n,r){return this.filter(ee).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(t,e,n,r){var i=ce.isEmptyObject(t),o=ce.speed(e,n,r),a=function(){var e=yt(this,ce.extend({},t),o);(i||_.get(this,"finish"))&&e.stop(!0)};return a.finish=a,i||!1===o.queue?this.each(a):this.queue(o.queue,a)},stop:function(i,e,o){var a=function(e){var t=e.stop;delete e.stop,t(o)};return"string"!=typeof i&&(o=e,e=i,i=void 0),e&&this.queue(i||"fx",[]),this.each(function(){var e=!0,t=null!=i&&i+"queueHooks",n=ce.timers,r=_.get(this);if(t)r[t]&&r[t].stop&&a(r[t]);else for(t in r)r[t]&&r[t].stop&&pt.test(t)&&a(r[t]);for(t=n.length;t--;)n[t].elem!==this||null!=i&&n[t].queue!==i||(n[t].anim.stop(o),e=!1,n.splice(t,1)
vendor: 13,972 bytes, line 2
2);!e&&o||ce.dequeue(this,i)})},finish:function(a){return!1!==a&&(a=a||"fx"),this.each(function(){var e,t=_.get(this),n=t[a+"queue"],r=t[a+"queueHooks"],i=ce.timers,o=n?n.length:0;for(t.finish=!0,ce.queue(this,a,[]),r&&r.stop&&r.stop.call(this,!0),e=i.length;e--;)i[e].elem===this&&i[e].queue===a&&(i[e].anim.stop(!0),i.splice(e,1));for(e=0;e<o;e++)n[e]&&n[e].finish&&n[e].finish.call(this);delete t.finish})}}),ce.each(["toggle","show","hide"],function(e,r){var i=ce.fn[r];ce.fn[r]=function(e,t,n){return null==e||"boolean"==typeof e?i.apply(this,arguments):this.animate(gt(r,!0),e,t,n)}}),ce.each({slideDown:gt("show"),slideUp:gt("hide"),slideToggle:gt("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,r){ce.fn[e]=function(e,t,n){return this.animate(r,e,t,n)}}),ce.timers=[],ce.fx.tick=function(){var e,t=0,n=ce.timers;for(st=Date.now();t<n.length;t++)(e=n[t])()||n[t]!==e||n.splice(t--,1);n.length||ce.fx.stop(),st=void 0},ce.fx.timer=function(e){ce.timers.push(e),ce.fx.start()},ce.fx.interval=13,ce.fx.start=function(){ut||(ut=!0,dt())},ce.fx.stop=function(){ut=null},ce.fx.speeds={slow:600,fast:200,_default:400},ce.fn.delay=function(r,e){return r=ce.fx&&ce.fx.speeds[r]||r,e=e||"fx",this.queue(e,function(e,t){var n=ie.setTimeout(e,r);t.stop=function(){ie.clearTimeout(n)}})},lt=C.createElement("input"),ct=C.createElement("select").appendChild(C.createElement("option")),lt.type="checkbox",le.checkOn=""!==lt.value,le.optSelected=ct.selected,(lt=C.createElement("input")).value="t",lt.type="radio",le.radioValue="t"===lt.value;var mt,xt=ce.expr.attrHandle;ce.fn.extend({attr:function(e,t){return M(this,ce.attr,e,t,1<arguments.length)},removeAttr:function(e){return this.each(function(){ce.removeAttr(this,e)})}}),ce.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return"undefined"==typeof e.getAttribute?ce.prop(e,t,n):(1===o&&ce.isXMLDoc(e)||(i=ce.attrHooks[t.toLowerCase()]||(ce.expr.match.bool.test(t)?mt:void 0)),void 0!==n?null===n?void ce.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=ce.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!le.radioValue&&"radio"===t&&fe(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(D);if(i&&1===e.nodeType)while(n=i[r++])e.removeAttribute(n)}}),mt={set:function(e,t,n){return!1===t?ce.removeAttr(e,n):e.setAttribute(n,n),n}},ce.each(ce.expr.match.bool.source.match(/\w+/g),function(e,t){var a=xt[t]||ce.find.attr;xt[t]=function(e,t,n){var r,i,o=t.toLowerCase();return n||(i=xt[o],xt[o]=r,r=null!=a(e,t,n)?o:null,xt[o]=i),r}});var bt=/^(?:input|select|textarea|button)$/i,wt=/^(?:a|area)$/i;function Tt(e){return(e.match(D)||[]).join(" ")}function Ct(e){return e.getAttribute&&e.getAttribute("class")||""}function kt(e){return Array.isArray(e)?e:"string"==typeof e&&e.match(D)||[]}ce.fn.extend({prop:function(e,t){return M(this,ce.prop,e,t,1<arguments.length)},removeProp:function(e){return this.each(function(){delete this[ce.propFix[e]||e]})}}),ce.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&ce.isXMLDoc(e)||(t=ce.propFix[t]||t,i=ce.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=ce.find.attr(e,"tabindex");return t?parseInt(t,10):bt.test(e.nodeName)||wt.test(e.nodeName)&&e.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),le.optSelected||(ce.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),ce.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){ce.propFix[this.toLowerCase()]=this}),ce.fn.extend({addClass:function(t){var e,n,r,i,o,a;return v(t)?this.each(function(e){ce(this).addClass(t.call(this,e,Ct(this)))}):(e=kt(t)).length?this.each(function(){if(r=Ct(this),n=1===this.nodeType&&" "+Tt(r)+" "){for(o=0;o<e.length;o++)i=e[o],n.indexOf(" "+i+" ")<0&&(n+=i+" ");a=Tt(n),r!==a&&this.setAttribute("class",a)}}):this},removeClass:function(t){var e,n,r,i,o,a;return v(t)?this.each(function(e){ce(this).removeClass(t.call(this,e,Ct(this)))}):arguments.length?(e=kt(t)).length?this.each(function(){if(r=Ct(this),n=1===this.nodeType&&" "+Tt(r)+" "){for(o=0;o<e.length;o++){i=e[o];while(-1<n.indexOf(" "+i+" "))n=n.replace(" "+i+" "," ")}a=Tt(n),r!==a&&this.setAttribute("class",a)}}):this:this.attr("class","")},toggleClass:function(t,n){var e,r,i,o,a=typeof t,s="string"===a||Array.isArray(t);return v(t)?this.each(function(e){ce(this).toggleClass(t.call(this,e,Ct(this),n),n)}):"boolean"==typeof n&&s?n?this.addClass(t):this.removeClass(t):(e=kt(t),this.each(function(){if(s)for(o=ce(this),i=0;i<e.length;i++)r=e[i],o.hasClass(r)?o.removeClass(r):o.addClass(r);else void 0!==t&&"boolean"!==a||((r=Ct(this))&&_.set(this,"__className__",r),this.setAttribute&&this.setAttribute("class",r||!1===t?"":_.get(this,"__className__")||""))}))},hasClass:function(e){var t,n,r=0;t=" "+e+" ";while(n=this[r++])if(1===n.nodeType&&-1<(" "+Tt(Ct(n))+" ").indexOf(t))return!0;return!1}});var St=/\r/g;ce.fn.extend({val:function(n){var r,e,i,t=this[0];return arguments.length?(i=v(n),this.each(function(e){var t;1===this.nodeType&&(null==(t=i?n.call(this,e,ce(this).val()):n)?t="":"number"==typeof t?t+="":Array.isArray(t)&&(t=ce.map(t,function(e){return null==e?"":e+""})),(r=ce.valHooks[this.type]||ce.valHooks[this.nodeName.toLowerCase()])&&"set"in r&&void 0!==r.set(this,t,"value")||(this.value=t))})):t?(r=ce.valHooks[t.type]||ce.valHooks[t.nodeName.toLowerCase()])&&"get"in r&&void 0!==(e=r.get(t,"value"))?e:"string"==typeof(e=t.value)?e.replace(St,""):null==e?"":e:void 0}}),ce.extend({valHooks:{option:{get:function(e){var t=ce.find.attr(e,"value");return null!=t?t:Tt(ce.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a="select-one"===e.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r<u;r++)if(((n=i[r]).selected||r===o)&&!n.disabled&&(!n.parentNode.disabled||!fe(n.parentNode,"optgroup"))){if(t=ce(n).val(),a)return t;s.push(t)}return s},set:function(e,t){var n,r,i=e.options,o=ce.makeArray(t),a=i.length;while(a--)((r=i[a]).selected=-1<ce.inArray(ce.valHooks.option.get(r),o))&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),ce.each(["radio","checkbox"],function(){ce.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=-1<ce.inArray(ce(e).val(),t)}},le.checkOn||(ce.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var Et=ie.location,jt={guid:Date.now()},At=/\?/;ce.parseXML=function(e){var t,n;if(!e||"string"!=typeof e)return null;try{t=(new ie.DOMParser).parseFromString(e,"text/xml")}catch(e){}return n=t&&t.getElementsByTagName("parsererror")[0],t&&!n||ce.error("Invalid XML: "+(n?ce.map(n.childNodes,function(e){return e.textContent}).join("\n"):e)),t};var Dt=/^(?:focusinfocus|focusoutblur)$/,Nt=function(e){e.stopPropagation()};ce.extend(ce.event,{trigger:function(e,t,n,r){var i,o,a,s,u,l,c,f,p=[n||C],d=ue.call(e,"type")?e.type:e,h=ue.call(e,"namespace")?e.namespace.split("."):[];if(o=f=a=n=n||C,3!==n.nodeType&&8!==n.nodeType&&!Dt.test(d+ce.event.triggered)&&(-1<d.indexOf(".")&&(d=(h=d.split(".")).shift(),h.sort()),u=d.indexOf(":")<0&&"on"+d,(e=e[ce.expando]?e:new ce.Event(d,"object"==typeof e&&e)).isTrigger=r?2:3,e.namespace=h.join("."),e.rnamespace=e.namespace?new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,e.result=void 0,e.target||(e.target=n),t=null==t?[e]:ce.makeArray(t,[e]),c=ce.event.special[d]||{},r||!c.trigger||!1!==c.trigger.apply(n,t))){if(!r&&!c.noBubble&&!y(n)){for(s=c.delegateType||d,Dt.test(s+d)||(o=o.parentNode);o;o=o.parentNode)p.push(o),a=o;a===(n.ownerDocument||C)&&p.push(a.defaultView||a.parentWindow||ie)}i=0;while((o=p[i++])&&!e.isPropagationStopped())f=o,e.type=1<i?s:c.bindType||d,(l=(_.get(o,"events")||Object.create(null))[e.type]&&_.get(o,"handle"))&&l.apply(o,t),(l=u&&o[u])&&l.apply&&$(o)&&(e.result=l.apply(o,t),!1===e.result&&e.preventDefault());return e.type=d,r||e.isDefaultPrevented()||c._default&&!1!==c._default.apply(p.pop(),t)||!$(n)||u&&v(n[d])&&!y(n)&&((a=n[u])&&(n[u]=null),ce.event.triggered=d,e.isPropagationStopped()&&f.addEventListener(d,Nt),n[d](),e.isPropagationStopped()&&f.removeEventListener(d,Nt),ce.event.triggered=void 0,a&&(n[u]=a)),e.result}},simulate:function(e,t,n){var r=ce.extend(new ce.Event,n,{type:e,isSimulated:!0});ce.event.trigger(r,null,t)}}),ce.fn.extend({trigger:function(e,t){return this.each(function(){ce.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return ce.event.trigger(e,t,n,!0)}});var qt=/\[\]$/,Lt=/\r?\n/g,Ht=/^(?:submit|button|image|reset|file)$/i,Ot=/^(?:input|select|textarea|keygen)/i;function Pt(n,e,r,i){var t;if(Array.isArray(e))ce.each(e,function(e,t){r||qt.test(n)?i(n,t):Pt(n+"["+("object"==typeof t&&null!=t?e:"")+"]",t,r,i)});else if(r||"object"!==x(e))i(n,e);else for(t in e)Pt(n+"["+t+"]",e[t],r,i)}ce.param=function(e,t){var n,r=[],i=function(e,t){var n=v(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(null==e)return"";if(Array.isArray(e)||e.jquery&&!ce.isPlainObject(e))ce.each(e,function(){i(this.name,this.value)});else for(n in e)Pt(n,e[n],t,i);return r.join("&")},ce.fn.extend({serialize:function(){return ce.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=ce.prop(this,"elements");return e?ce.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!ce(this).is(":disabled")&&Ot.test(this.nodeName)&&!Ht.test(e)&&(this.checked||!we.test(e))}).map(function(e,t){var n=ce(this).val();return null==n?null:Array.isArray(n)?ce.map(n,function(e){return{name:t.name,value:e.replace(Lt,"\r\n")}}):{name:t.name,value:n.replace(Lt,"\r\n")}}).get()}});var Mt=/%20/g,Rt=/#.*$/,It=/([?&])_=[^&]*/,Wt=/^(.*?):[ \t]*([^\r\n]*)$/gm,Ft=/^(?:GET|HEAD)$/,$t=/^\/\//,Bt={},_t={},zt="*/".concat("*"),Xt=C.createElement("a");function Ut(o){return function(e,t){"string"!=typeof e&&(t=e,e="*");var n,r=0,i=e.toLowerCase().match(D)||[];if(v(t))while(n=i[r++])"+"===n[0]?(n=n.slice(1)||"*",(o[n]=o[n]||[]).unshift(t)):(o[n]=o[n]||[]).push(t)}}function Vt(t,i,o,a){var s={},u=t===_t;function l(e){var r;return s[e]=!0,ce.each(t[e]||[],function(e,t){var n=t(i,o,a);return"string"!=typeof n||u||s[n]?u?!(r=n):void 0:(i.dataTypes.unshift(n),l(n),!1)}),r}return l(i.dataTypes[0])||!s["*"]&&l("*")}function Gt(e,t){var n,r,i=ce.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&ce.extend(!0,e,r),e}Xt.href=Et.href,ce.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Et.href,type:"GET",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(Et.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":zt,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":ce.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?Gt(Gt(e,ce.ajaxSettings),t):Gt(ce.ajaxSettings,e)},ajaxPrefilter:Ut(Bt),ajaxTransport:Ut(_t),ajax:function(e,t){"object"==typeof e&&(t=e,e=void 0),t=t||{};var c,f,p,n,d,r,h,g,i,o,v=ce.ajaxSetup({},t),y=v.context||v,m=v.context&&(y.nodeType||y.jquery)?ce(y):ce.event,x=ce.Deferred(),b=ce.Callbacks("once memory"),w=v.statusCode||{},a={},s={},u="canceled",T={readyState:0,getResponseHeader:function(e){var t;if(h){if(!n){n={};while(t=Wt.exec(p))n[t[1].toLowerCase()+" "]=(n[t[1].toLowerCase()+" "]||[]).concat(t[2])}t=n[e.toLowerCase()+" "]}return null==t?null:t.join(", ")},getAllResponseHeaders:function(){return h?p:null},setRequestHeader:function(e,t){return null==h&&(e=s[e.toLowerCase()]=s[e.toLowerCase()]||e,a[e]=t),this},overrideMimeType:function(e){return null==h&&(v.mimeType=e),this},statusCode:function(e){var t;if(e)if(h)T.always(e[T.status]);else for(t in e)w[t]=[w[t],e[t]];return this},abort:function(e){var t=e||u;return c&&c.abort(t),l(0,t),this}};if(x.promise(T),v.url=((e||v.url||Et.href)+"").replace($t,Et.protocol+"//"),v.type=t.method||t.type||v.method||v.type,v.dataTypes=(v.dataType||"*").toLowerCase().match(D)||[""],null==v.crossDomain){r=C.createElement("a");try{r.href=v.url,r.href=r.href,v.crossDomain=Xt.protocol+"//"+Xt.host!=r.protocol+"//"+r.host}catch(e){v.crossDomain=!0}}if(v.data&&v.processData&&"string"!=typeof v.data&&(v.data=ce.param(v.data,v.traditional)),Vt(Bt,v,t,T),h)return T;for(i in(g=ce.event&&v.global)&&0==ce.active++&&ce.event.trigger("ajaxStart"),v.type=v.type.toUpperCase(),v.hasContent=!Ft.test(v.type),f=v.url.replace(Rt,""),v.hasContent?v.data&&v.processData&&0===(v.contentType||"").indexOf("application/x-www-form-urlencoded")&&(v.data=v.data.replace(Mt,"+")):(o=v.url.slice(f.length),v.data&&(v.processData||"string"==typeof v.data)&&(f+=(At.test(f)?"&":"?")+v.data,delete v.data),!1===v.cache&&(f=f.replace(It,"$1"),o=(At.test(f)?"&":"?")+"_="+jt.guid+++o),v.url=f+o),v.ifModified&&(ce.lastModified[f]&&T.setRequestHeader("If-Modified-Since",ce.lastModified[f]),ce.etag[f]&&T.setRequestHeader("If-None-Match",ce.etag[f])),(v.data&&v.hasContent&&!1!==v.contentType||t.contentType)&&T.setRequestHeader("Content-Type",v.contentType),T.setRequestHeader("Accept",v.dataTypes[0]&&v.accepts[v.dataTypes[0]]?v.accepts[v.dataTypes[0]]+("*"!==v.dataTypes[0]?", "+zt+"; q=0.01":""):v.accepts["*"]),v.headers)T.setRequestHeader(i,v.headers[i]);
vendor: 9,033 bytes, line 2
2if(v.beforeSend&&(!1===v.beforeSend.call(y,T,v)||h))return T.abort();if(u="abort",b.add(v.complete),T.done(v.success),T.fail(v.error),c=Vt(_t,v,t,T)){if(T.readyState=1,g&&m.trigger("ajaxSend",[T,v]),h)return T;v.async&&0<v.timeout&&(d=ie.setTimeout(function(){T.abort("timeout")},v.timeout));try{h=!1,c.send(a,l)}catch(e){if(h)throw e;l(-1,e)}}else l(-1,"No Transport");function l(e,t,n,r){var i,o,a,s,u,l=t;h||(h=!0,d&&ie.clearTimeout(d),c=void 0,p=r||"",T.readyState=0<e?4:0,i=200<=e&&e<300||304===e,n&&(s=function(e,t,n){var r,i,o,a,s=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}(v,T,n)),!i&&-1<ce.inArray("script",v.dataTypes)&&ce.inArray("json",v.dataTypes)<0&&(v.converters["text script"]=function(){}),s=function(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(!(a=l[u+" "+o]||l["* "+o]))for(i in l)if((s=i.split(" "))[1]===o&&(a=l[u+" "+s[0]]||l["* "+s[0]])){!0===a?a=l[i]:!0!==l[i]&&(o=s[0],c.unshift(s[1]));break}if(!0!==a)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(e){return{state:"parsererror",error:a?e:"No conversion from "+u+" to "+o}}}return{state:"success",data:t}}(v,s,T,i),i?(v.ifModified&&((u=T.getResponseHeader("Last-Modified"))&&(ce.lastModified[f]=u),(u=T.getResponseHeader("etag"))&&(ce.etag[f]=u)),204===e||"HEAD"===v.type?l="nocontent":304===e?l="notmodified":(l=s.state,o=s.data,i=!(a=s.error))):(a=l,!e&&l||(l="error",e<0&&(e=0))),T.status=e,T.statusText=(t||l)+"",i?x.resolveWith(y,[o,l,T]):x.rejectWith(y,[T,l,a]),T.statusCode(w),w=void 0,g&&m.trigger(i?"ajaxSuccess":"ajaxError",[T,v,i?o:a]),b.fireWith(y,[T,l]),g&&(m.trigger("ajaxComplete",[T,v]),--ce.active||ce.event.trigger("ajaxStop")))}return T},getJSON:function(e,t,n){return ce.get(e,t,n,"json")},getScript:function(e,t){return ce.get(e,void 0,t,"script")}}),ce.each(["get","post"],function(e,i){ce[i]=function(e,t,n,r){return v(t)&&(r=r||n,n=t,t=void 0),ce.ajax(ce.extend({url:e,type:i,dataType:r,data:t,success:n},ce.isPlainObject(e)&&e))}}),ce.ajaxPrefilter(function(e){var t;for(t in e.headers)"content-type"===t.toLowerCase()&&(e.contentType=e.headers[t]||"")}),ce._evalUrl=function(e,t,n){return ce.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,converters:{"text script":function(){}},dataFilter:function(e){ce.globalEval(e,t,n)}})},ce.fn.extend({wrapAll:function(e){var t;return this[0]&&(v(e)&&(e=e.call(this[0])),t=ce(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(n){return v(n)?this.each(function(e){ce(this).wrapInner(n.call(this,e))}):this.each(function(){var e=ce(this),t=e.contents();t.length?t.wrapAll(n):e.append(n)})},wrap:function(t){var n=v(t);return this.each(function(e){ce(this).wrapAll(n?t.call(this,e):t)})},unwrap:function(e){return this.parent(e).not("body").each(function(){ce(this).replaceWith(this.childNodes)}),this}}),ce.expr.pseudos.hidden=function(e){return!ce.expr.pseudos.visible(e)},ce.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},ce.ajaxSettings.xhr=function(){try{return new ie.XMLHttpRequest}catch(e){}};var Yt={0:200,1223:204},Qt=ce.ajaxSettings.xhr();le.cors=!!Qt&&"withCredentials"in Qt,le.ajax=Qt=!!Qt,ce.ajaxTransport(function(i){var o,a;if(le.cors||Qt&&!i.crossDomain)return{send:function(e,t){var n,r=i.xhr();if(r.open(i.type,i.url,i.async,i.username,i.password),i.xhrFields)for(n in i.xhrFields)r[n]=i.xhrFields[n];for(n in i.mimeType&&r.overrideMimeType&&r.overrideMimeType(i.mimeType),i.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest"),e)r.setRequestHeader(n,e[n]);o=function(e){return function(){o&&(o=a=r.onload=r.onerror=r.onabort=r.ontimeout=r.onreadystatechange=null,"abort"===e?r.abort():"error"===e?"number"!=typeof r.status?t(0,"error"):t(r.status,r.statusText):t(Yt[r.status]||r.status,r.statusText,"text"!==(r.responseType||"text")||"string"!=typeof r.responseText?{binary:r.response}:{text:r.responseText},r.getAllResponseHeaders()))}},r.onload=o(),a=r.onerror=r.ontimeout=o("error"),void 0!==r.onabort?r.onabort=a:r.onreadystatechange=function(){4===r.readyState&&ie.setTimeout(function(){o&&a()})},o=o("abort");try{r.send(i.hasContent&&i.data||null)}catch(e){if(o)throw e}},abort:function(){o&&o()}}}),ce.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),ce.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return ce.globalEval(e),e}}}),ce.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),ce.ajaxTransport("script",function(n){var r,i;if(n.crossDomain||n.scriptAttrs)return{send:function(e,t){r=ce("<script>").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),C.head.appendChild(r[0])},abort:function(){i&&i()}}});var Jt,Kt=[],Zt=/(=)\?(?=&|$)|\?\?/;ce.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Kt.pop()||ce.expando+"_"+jt.guid++;return this[e]=!0,e}}),ce.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Zt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Zt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=v(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Zt,"$1"+r):!1!==e.jsonp&&(e.url+=(At.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||ce.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=ie[r],ie[r]=function(){o=arguments},n.always(function(){void 0===i?ce(ie).removeProp(r):ie[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Kt.push(r)),o&&v(i)&&i(o[0]),o=i=void 0}),"script"}),le.createHTMLDocument=((Jt=C.implementation.createHTMLDocument("").body).innerHTML="<form></form><form></form>",2===Jt.childNodes.length),ce.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(le.createHTMLDocument?((r=(t=C.implementation.createHTMLDocument("")).createElement("base")).href=C.location.href,t.head.appendChild(r)):t=C),o=!n&&[],(i=w.exec(e))?[t.createElement(i[1])]:(i=Ae([e],t,o),o&&o.length&&ce(o).remove(),ce.merge([],i.childNodes)));var r,i,o},ce.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1<s&&(r=Tt(e.slice(s)),e=e.slice(0,s)),v(t)?(n=t,t=void 0):t&&"object"==typeof t&&(i="POST"),0<a.length&&ce.ajax({url:e,type:i||"GET",dataType:"html",data:t}).done(function(e){o=arguments,a.html(r?ce("<div>").append(ce.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},ce.expr.pseudos.animated=function(t){return ce.grep(ce.timers,function(e){return t===e.elem}).length},ce.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=ce.css(e,"position"),c=ce(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=ce.css(e,"top"),u=ce.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),v(t)&&(t=t.call(e,n,ce.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},ce.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){ce.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===ce.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===ce.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=ce(e).offset()).top+=ce.css(e,"borderTopWidth",!0),i.left+=ce.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-ce.css(r,"marginTop",!0),left:t.left-i.left-ce.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===ce.css(e,"position"))e=e.offsetParent;return e||J})}}),ce.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;ce.fn[t]=function(e){return
vendor: 2,549 bytes, lines 2-3
2M(this,function(e,t,n){var r;if(y(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),ce.each(["top","left"],function(e,n){ce.cssHooks[n]=Ye(le.pixelPosition,function(e,t){if(t)return t=Ge(e,n),_e.test(t)?ce(e).position()[n]+"px":t})}),ce.each({Height:"height",Width:"width"},function(a,s){ce.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){ce.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return M(this,function(e,t,n){var r;return y(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?ce.css(e,t,i):ce.style(e,t,n,i)},s,n?e:void 0,n)}})}),ce.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){ce.fn[t]=function(e){return this.on(t,e)}}),ce.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.on("mouseenter",e).on("mouseleave",t||e)}}),ce.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){ce.fn[n]=function(e,t){return 0<arguments.length?this.on(n,null,e,t):this.trigger(n)}});var en=/^[\s\uFEFF\xA0]+|([^\s\uFEFF\xA0])[\s\uFEFF\xA0]+$/g;ce.proxy=function(e,t){var n,r,i;if("string"==typeof t&&(n=e[t],t=e,e=n),v(e))return r=ae.call(arguments,2),(i=function(){return e.apply(t||this,r.concat(ae.call(arguments)))}).guid=e.guid=e.guid||ce.guid++,i},ce.holdReady=function(e){e?ce.readyWait++:ce.ready(!0)},ce.isArray=Array.isArray,ce.parseJSON=JSON.parse,ce.nodeName=fe,ce.isFunction=v,ce.isWindow=y,ce.camelCase=F,ce.type=x,ce.now=Date.now,ce.isNumeric=function(e){var t=ce.type(e);return("number"===t||"string"===t)&&!isNaN(e-parseFloat(e))},ce.trim=function(e){return null==e?"":(e+"").replace(en,"$1")},"function"==typeof define&&define.amd&&define("jquery",[],function(){return ce});var tn=ie.jQuery,nn=ie.$;return ce.noConflict=function(e){return ie.$===ce&&(ie.$=nn),e&&ie.jQuery===ce&&(ie.jQuery=tn),ce},"undefined"==typeof e&&(ie.jQuery=ie.$=ce),ce}); 3
vendor: 1,377,909 bytes, lines 4-36603
4/** 5 * @license AngularJS v1.8.3 6 * (c) 2010-2020 Google LLC. http://angularjs.org 7 * License: MIT 8 */ 9(function(window) {'use strict'; 10 11/* exported 12 minErrConfig, 13 errorHandlingConfig, 14 isValidObjectMaxDepth 15*/ 16 17var minErrConfig = { 18 objectMaxDepth: 5, 19 urlErrorParamsEnabled: true 20}; 21 22/** 23 * @ngdoc function 24 * @name angular.errorHandlingConfig 25 * @module ng 26 * @kind function 27 * 28 * @description 29 * Configure several aspects of error handling in AngularJS if used as a setter or return the 30 * current configuration if used as a getter. The following options are supported: 31 * 32 * - **objectMaxDepth**: The maximum depth to which objects are traversed when stringified for error messages. 33 * 34 * Omitted or undefined options will leave the corresponding configuration values unchanged. 35 * 36 * @param {Object=} config - The configuration object. May only contain the options that need to be 37 * updated. Supported keys: 38 * 39 * * `objectMaxDepth` **{Number}** - The max depth for stringifying objects. Setting to a 40 * non-positive or non-numeric value, removes the max depth limit. 41 * Default: 5 42 * 43 * * `urlErrorParamsEnabled` **{Boolean}** - Specifies whether the generated error url will 44 * contain the parameters of the thrown error. Disabling the parameters can be useful if the 45 * generated error url is very long. 46 * 47 * Default: true. When used without argument, it returns the current value. 48 */ 49function errorHandlingConfig(config) { 50 if (isObject(config)) { 51 if (isDefined(config.objectMaxDepth)) { 52 minErrConfig.objectMaxDepth = isValidObjectMaxDepth(config.objectMaxDepth) ? config.objectMaxDepth : NaN; 53 } 54 if (isDefined(config.urlErrorParamsEnabled) && isBoolean(config.urlErrorParamsEnabled)) { 55 minErrConfig.urlErrorParamsEnabled = config.urlErrorParamsEnabled; 56 } 57 } else { 58 return minErrConfig; 59 } 60} 61 62/** 63 * @private 64 * @param {Number} maxDepth 65 * @return {boolean} 66 */ 67function isValidObjectMaxDepth(maxDepth) { 68 return isNumber(maxDepth) && maxDepth > 0; 69} 70 71 72/** 73 * @description 74 * 75 * This object provides a utility for producing rich Error messages within 76 * AngularJS. It can be called as follows: 77 * 78 * var exampleMinErr = minErr('example'); 79 * throw exampleMinErr('one', 'This {0} is {1}', foo, bar); 80 * 81 * The above creates an instance of minErr in the example namespace. The 82 * resulting error will have a namespaced error code of example.one. The 83 * resulting error will replace {0} with the value of foo, and {1} with the 84 * value of bar. The object is not restricted in the number of arguments it can 85 * take. 86 * 87 * If fewer arguments are specified than necessary for interpolation, the extra 88 * interpolation markers will be preserved in the final string. 89 * 90 * Since data will be parsed statically during a build step, some restrictions 91 * are applied with respect to how minErr instances are created and called. 92 * Instances should have names of the form namespaceMinErr for a minErr created 93 * using minErr('namespace'). Error codes, namespaces and template strings 94 * should all be static strings, not variables or general expressions. 95 * 96 * @param {string} module The namespace to use for the new minErr instance. 97 * @param {function} ErrorConstructor Custom error constructor to be instantiated when returning 98 * error from returned function, for cases when a particular type of error is useful. 99 * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance 100 */ 101 102function minErr(module, ErrorConstructor) { 103 ErrorConstructor = ErrorConstructor || Error; 104 105 var url = 'https://errors.angularjs.org/1.8.3/'; 106 var regex = url.replace('.', '\\.') + '[\\s\\S]*'; 107 var errRegExp = new RegExp(regex, 'g'); 108 109 return function() { 110 var code = arguments[0], 111 template = arguments[1], 112 message = '[' + (module ? module + ':' : '') + code + '] ', 113 templateArgs = sliceArgs(arguments, 2).map(function(arg) { 114 return toDebugString(arg, minErrConfig.objectMaxDepth); 115 }), 116 paramPrefix, i; 117 118 // A minErr message has two parts: the message itself and the url that contains the 119 // encoded message. 120 // The message's parameters can contain other error messages which also include error urls. 121 // To prevent the messages from getting too long, we strip the error urls from the parameters. 122 123 message += template.replace(/\{\d+\}/g, function(match) { 124 var index = +match.slice(1, -1); 125 126 if (index < templateArgs.length) { 127 return templateArgs[index].replace(errRegExp, ''); 128 } 129 130 return match; 131 }); 132 133 message += '\n' + url + (module ? module + '/' : '') + code; 134 135 if (minErrConfig.urlErrorParamsEnabled) { 136 for (i = 0, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') { 137 message += paramPrefix + 'p' + i + '=' + encodeURIComponent(templateArgs[i]); 138 } 139 } 140 141 return new ErrorConstructor(message); 142 }; 143} 144 145/* We need to tell ESLint what variables are being exported */ 146/* exported 147 angular, 148 msie, 149 jqLite, 150 jQuery, 151 slice, 152 splice, 153 push, 154 toString, 155 minErrConfig, 156 errorHandlingConfig, 157 isValidObjectMaxDepth, 158 ngMinErr, 159 angularModule, 160 uid, 161 REGEX_STRING_REGEXP, 162 VALIDITY_STATE_PROPERTY, 163 164 lowercase, 165 uppercase, 166 nodeName_, 167 isArrayLike, 168 forEach, 169 forEachSorted, 170 reverseParams, 171 nextUid, 172 setHashKey, 173 extend, 174 toInt, 175 inherit, 176 merge, 177 noop, 178 identity, 179 valueFn, 180 isUndefined, 181 isDefined, 182 isObject, 183 isBlankObject, 184 isString, 185 isNumber, 186 isNumberNaN, 187 isDate, 188 isError, 189 isArray, 190 isFunction, 191 isRegExp, 192 isWindow, 193 isScope, 194 isFile, 195 isFormData, 196 isBlob, 197 isBoolean, 198 isPromiseLike, 199 trim, 200 escapeForRegexp, 201 isElement, 202 makeMap, 203 includes, 204 arrayRemove, 205 copy, 206 simpleCompare, 207 equals, 208 csp, 209 jq, 210 concat, 211 sliceArgs, 212 bind, 213 toJsonReplacer, 214 toJson, 215 fromJson, 216 convertTimezoneToLocal, 217 timezoneToOffset, 218 addDateMinutes, 219 startingTag, 220 tryDecodeURIComponent, 221 parseKeyValue, 222 toKeyValue, 223 encodeUriSegment, 224 encodeUriQuery, 225 angularInit, 226 bootstrap, 227 getTestability, 228 snake_case, 229 bindJQuery, 230 assertArg, 231 assertArgFn, 232 assertNotHasOwnProperty, 233 getter, 234 getBlockNodes, 235 hasOwnProperty, 236 createMap, 237 stringify, 238 UNSAFE_restoreLegacyJqLiteXHTMLReplacement, 239 240 NODE_TYPE_ELEMENT, 241 NODE_TYPE_ATTRIBUTE, 242 NODE_TYPE_TEXT, 243 NODE_TYPE_COMMENT, 244 NODE_TYPE_DOCUMENT, 245 NODE_TYPE_DOCUMENT_FRAGMENT 246*/ 247 248//////////////////////////////////// 249 250/** 251 * @ngdoc module 252 * @name ng 253 * @module ng 254 * @installation 255 * @description 256 * 257 * The ng module is loaded by default when an AngularJS application is started. The module itself 258 * contains the essential components for an AngularJS application to function. The table below 259 * lists a high level breakdown of each of the services/factories, filters, directives and testing 260 * components available within this core module. 261 * 262 */ 263 264var REGEX_STRING_REGEXP = /^\/(.+)\/([a-z]*)$/; 265 266// The name of a form control's ValidityState property. 267// This is used so that it's possible for internal tests to create mock ValidityStates. 268var VALIDITY_STATE_PROPERTY = 'validity'; 269 270 271var hasOwnProperty = Object.prototype.hasOwnProperty; 272 273/** 274 * @private 275 * 276 * @description Converts the specified string to lowercase. 277 * @param {string} string String to be converted to lowercase. 278 * @returns {string} Lowercased string. 279 */ 280var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;}; 281 282/** 283 * @private 284 * 285 * @description Converts the specified string to uppercase. 286 * @param {string} string String to be converted to uppercase. 287 * @returns {string} Uppercased string. 288 */ 289var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;}; 290 291 292var 293 msie, // holds major version number for IE, or NaN if UA is not IE. 294 jqLite, // delay binding since jQuery could be loaded after us. 295 jQuery, // delay binding 296 slice = [].slice, 297 splice = [].splice, 298 push = [].push, 299 toString = Object.prototype.toString, 300 getPrototypeOf = Object.getPrototypeOf, 301 ngMinErr = minErr('ng'), 302 303 /** @name angular */ 304 angular = window.angular || (window.angular = {}), 305 angularModule, 306 uid = 0; 307 308// Support: IE 9-11 only 309/** 310 * documentMode is an IE-only property 311 * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx 312 */ 313msie = window.document.documentMode; 314 315 316/** 317 * @private 318 * @param {*} obj 319 * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, 320 * String ...) 321 */ 322function isArrayLike(obj) { 323 324 // `null`, `undefined` and `window` are not array-like 325 if (obj == null || isWindow(obj)) return false; 326 327 // arrays, strings and jQuery/jqLite objects are array like 328 // * jqLite is either the jQuery or jqLite constructor function 329 // * we have to check the existence of jqLite first as this method is called 330 // via the forEach method when constructing the jqLite object in the first place 331 if (isArray(obj) || isString(obj) || (jqLite && obj instanceof jqLite)) return true; 332 333 // Support: iOS 8.2 (not reproducible in simulator) 334 // "length" in obj used to prevent JIT error (gh-11508) 335 var length = 'length' in Object(obj) && obj.length; 336 337 // NodeList objects (with `item` method) and 338 // other objects with suitable length characteristics are array-like 339 return isNumber(length) && (length >= 0 && (length - 1) in obj || typeof obj.item === 'function'); 340 341} 342 343/** 344 * @ngdoc function 345 * @name angular.forEach 346 * @module ng 347 * @kind function 348 * 349 * @description 350 * Invokes the `iterator` function once for each item in `obj` collection, which can be either an 351 * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value` 352 * is the value of an object property or an array element, `key` is the object property key or 353 * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional. 354 * 355 * It is worth noting that `.forEach` does not iterate over inherited properties because it filters 356 * using the `hasOwnProperty` method. 357 * 358 * Unlike ES262's 359 * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18), 360 * providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just 361 * return the value provided. 362 * 363 ```js 364 var values = {name: 'misko', gender: 'male'}; 365 var log = []; 366 angular.forEach(values, function(value, key) { 367 this.push(key + ': ' + value); 368 }, log); 369 expect(log).toEqual(['name: misko', 'gender: male']); 370 ``` 371 * 372 * @param {Object|Array} obj Object to iterate over. 373 * @param {Function} iterator Iterator function. 374 * @param {Object=} context Object to become context (`this`) for the iterator function. 375 * @returns {Object|Array} Reference to `obj`. 376 */ 377 378function forEach(obj, iterator, context) { 379 var key, length; 380 if (obj) { 381 if (isFunction(obj)) { 382 for (key in obj) { 383 if (key !== 'prototype' && key !== 'length' && key !== 'name' && obj.hasOwnProperty(key)) { 384 iterator.call(context, obj[key], key, obj); 385 } 386 } 387 } else if (isArray(obj) || isArrayLike(obj)) { 388 var isPrimitive = typeof obj !== 'object'; 389 for (key = 0, length = obj.length; key < length; key++) { 390 if (isPrimitive || key in obj) { 391 iterator.call(context, obj[key], key, obj); 392 } 393 } 394 } else if (obj.forEach && obj.forEach !== forEach) { 395 obj.forEach(iterator, context, obj); 396 } else if (isBlankObject(obj)) { 397 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty 398 for (key in obj) { 399 iterator.call(context, obj[key], key, obj); 400 } 401 } else if (typeof obj.hasOwnProperty === 'function') { 402 // Slow path for objects inheriting Object.prototype, hasOwnProperty check needed 403 for (key in obj) { 404 if (obj.hasOwnProperty(key)) { 405 iterator.call(context, obj[key], key, obj); 406 } 407 } 408 } else { 409 // Slow path for objects which do not have a method `hasOwnProperty` 410 for (key in obj) { 411 if (hasOwnProperty.call(obj, key)) { 412 iterator.call(context, obj[key], key, obj); 413 } 414 } 415 } 416 } 417 return obj; 418} 419 420function forEachSorted(obj, iterator, context) { 421 var keys = Object.keys(obj).sort(); 422 for (var i = 0; i < keys.length; i++) { 423 iterator.call(context, obj[keys[i]], keys[i]); 424 } 425 return keys; 426} 427 428 429/** 430 * when using forEach the params are value, key, but it is often useful to have key, value. 431 * @param {function(string, *)} iteratorFn 432 * @returns {function(*, string)} 433 */ 434function reverseParams(iteratorFn) { 435 return function(value, key) {iteratorFn(key, value);}; 436} 437 438/** 439 * A consistent way of creating unique IDs in angular. 440 * 441 * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before 442 * we hit number precision issues in JavaScript. 443 * 444 * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M 445 * 446 * @returns {number} an unique alpha-numeric string 447 */ 448function nextUid() { 449 return ++uid; 450} 451 452 453/** 454 * Set or clear the hashkey for an object. 455 * @param obj object 456 * @param h the hashkey (!truthy to delete the hashkey) 457 */ 458function setHashKey(obj, h) { 459 if (h) { 460 obj.$$hashKey = h; 461 } else { 462 delete obj.$$hashKey; 463 } 464} 465 466 467function baseExtend(dst, objs, deep) { 468 var h = dst.$$hashKey; 469 470 for (var i = 0, ii = objs.length; i < ii; ++i) { 471 var obj = objs[i]; 472 if (!isObject(obj) && !isFunction(obj)) continue; 473 var keys = Object.keys(obj); 474 for (var j = 0, jj = keys.length; j < jj; j++) { 475 var key = keys[j]; 476 var src = obj[key]; 477 478 if (deep && isObject(src)) { 479 if (isDate(src)) { 480 dst[key] = new Date(src.valueOf()); 481 } else if (isRegExp(src)) { 482 dst[key] = new RegExp(src); 483 } else if (src.nodeName) { 484 dst[key] = src.cloneNode(true); 485 } else if (isElement(src)) { 486 dst[key] = src.clone(); 487 } else { 488 if (key !== '__proto__') { 489 if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {}; 490 baseExtend(dst[key], [src], true); 491 } 492 } 493 } else { 494 dst[key] = src; 495 } 496 } 497 } 498 499 setHashKey(dst, h); 500 return dst; 501} 502 503/** 504 * @ngdoc function 505 * @name angular.extend 506 * @module ng 507 * @kind function 508 * 509 * @description 510 * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s) 511 * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so 512 * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`. 513 * 514 * **Note:** Keep in mind that `angular.extend` does not support recursive merge (deep copy). Use 515 * {@link angular.merge} for this. 516 * 517 * @param {Object} dst Destination object. 518 * @param {...Object} src Source object(s). 519 * @returns {Object} Reference to `dst`. 520 */ 521function extend(dst) { 522 return baseExtend(dst, slice.call(arguments, 1), false); 523} 524 525 526/** 527* @ngdoc function 528* @name angular.merge 529* @module ng 530* @kind function 531* 532* @description 533* Deeply extends the destination object `dst` by copying own enumerable properties from the `src` object(s) 534* to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so 535* by passing an empty object as the target: `var object = angular.merge({}, object1, object2)`. 536* 537* Unlike {@link angular.extend extend()}, `merge()` recursively descends into object properties of source 538* objects, performing a deep copy. 539* 540* @deprecated 541* sinceVersion="1.6.5" 542* This function is deprecated, but will not be removed in the 1.x lifecycle. 543* There are edge cases (see {@link angular.merge#known-issues known issues}) that are not 544* supported by this function. We suggest using another, similar library for all-purpose merging, 545* such as [lodash's merge()](https://lodash.com/docs/4.17.4#merge). 546* 547* @knownIssue 548* This is a list of (known) object types that are not handled correctly by this function: 549* - [`Blob`](https://developer.mozilla.org/docs/Web/API/Blob) 550* - [`MediaStream`](https://developer.mozilla.org/docs/Web/API/MediaStream) 551* - [`CanvasGradient`](https://developer.mozilla.org/docs/Web/API/CanvasGradient) 552* - AngularJS {@link $rootScope.Scope scopes}; 553* 554* `angular.merge` also does not support merging objects with circular references. 555* 556* @param {Object} dst Destination object. 557* @param {...Object} src Source object(s). 558* @returns {Object} Reference to `dst`. 559*/ 560function merge(dst) { 561 return baseExtend(dst, slice.call(arguments, 1), true); 562} 563 564 565 566function toInt(str) { 567 return parseInt(str, 10); 568} 569 570var isNumberNaN = Number.isNaN || function isNumberNaN(num) { 571 // eslint-disable-next-line no-self-compare 572 return num !== num; 573}; 574 575 576function inherit(parent, extra) { 577 return extend(Object.create(parent), extra); 578} 579 580/** 581 * @ngdoc function 582 * @name angular.noop 583 * @module ng 584 * @kind function 585 * 586 * @description 587 * A function that performs no operations. This function can be useful when writing code in the 588 * functional style. 589 ```js 590 function foo(callback) { 591 var result = calculateResult(); 592 (callback || angular.noop)(result); 593 } 594 ``` 595 */ 596function noop() {} 597noop.$inject = []; 598 599 600/** 601 * @ngdoc function 602 * @name angular.identity 603 * @module ng 604 * @kind function 605 * 606 * @description 607 * A function that returns its first argument. This function is useful when writing code in the 608 * functional style. 609 * 610 ```js 611 function transformer(transformationFn, value) { 612 return (transformationFn || angular.identity)(value); 613 }; 614 615 // E.g. 616 function getResult(fn, input) { 617 return (fn || angular.identity)(input); 618 }; 619 620 getResult(function(n) { return n * 2; }, 21); // returns 42 621 getResult(null, 21); // returns 21 622 getResult(undefined, 21); // returns 21 623 ``` 624 * 625 * @param {*} value to be returned. 626 * @returns {*} the value passed in. 627 */ 628function identity($) {return $;} 629identity.$inject = []; 630 631 632function valueFn(value) {return function valueRef() {return value;};} 633 634function hasCustomToString(obj) { 635 return isFunction(obj.toString) && obj.toString !== toString; 636} 637 638 639/** 640 * @ngdoc function 641 * @name angular.isUndefined 642 * @module ng 643 * @kind function 644 * 645 * @description 646 * Determines if a reference is undefined. 647 * 648 * @param {*} value Reference to check. 649 * @returns {boolean} True if `value` is undefined. 650 */ 651function isUndefined(value) {return typeof value === 'undefined';} 652 653 654/** 655 * @ngdoc function 656 * @name angular.isDefined 657 * @module ng 658 * @kind function 659 * 660 * @description 661 * Determines if a reference is defined. 662 * 663 * @param {*} value Reference to check. 664 * @returns {boolean} True if `value` is defined. 665 */ 666function isDefined(value) {return typeof value !== 'undefined';} 667 668 669/** 670 * @ngdoc function 671 * @name angular.isObject 672 * @module ng 673 * @kind function 674 * 675 * @description 676 * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not 677 * considered to be objects. Note that JavaScript arrays are objects. 678 * 679 * @param {*} value Reference to check. 680 * @returns {boolean} True if `value` is an `Object` but not `null`. 681 */ 682function isObject(value) { 683 // http://jsperf.com/isobject4 684 return value !== null && typeof value === 'object'; 685} 686 687 688/** 689 * Determine if a value is an object with a null prototype 690 * 691 * @returns {boolean} True if `value` is an `Object` with a null prototype 692 */ 693function isBlankObject(value) { 694 return value !== null && typeof value === 'object' && !getPrototypeOf(value); 695} 696 697 698/** 699 * @ngdoc function 700 * @name angular.isString 701 * @module ng 702 * @kind function 703 * 704 * @description 705 * Determines if a reference is a `String`. 706 * 707 * @param {*} value Reference to check. 708 * @returns {boolean} True if `value` is a `String`. 709 */ 710function isString(value) {return typeof value === 'string';} 711 712 713/** 714 * @ngdoc function 715 * @name angular.isNumber 716 * @module ng 717 * @kind function 718 * 719 * @description 720 * Determines if a reference is a `Number`. 721 * 722 * This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`. 723 * 724 * If you wish to exclude these then you can use the native 725 * [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite) 726 * method. 727 * 728 * @param {*} value Reference to check. 729 * @returns {boolean} True if `value` is a `Number`. 730 */ 731function isNumber(value) {return typeof value === 'number';} 732 733 734/** 735 * @ngdoc function 736 * @name angular.isDate 737 * @module ng 738 * @kind function 739 * 740 * @description 741 * Determines if a value is a date. 742 * 743 * @param {*} value Reference to check. 744 * @returns {boolean} True if `value` is a `Date`. 745 */ 746function isDate(value) { 747 return toString.call(value) === '[object Date]'; 748} 749 750 751/** 752 * @ngdoc function 753 * @name angular.isArray 754 * @module ng 755 * @kind function 756 * 757 * @description 758 * Determines if a reference is an `Array`. 759 * 760 * @param {*} value Reference to check. 761 * @returns {boolean} True if `value` is an `Array`. 762 */ 763function isArray(arr) { 764 return Array.isArray(arr) || arr instanceof Array; 765} 766 767/** 768 * @description 769 * Determines if a reference is an `Error`. 770 * Loosely based on https://www.npmjs.com/package/iserror 771 * 772 * @param {*} value Reference to check. 773 * @returns {boolean} True if `value` is an `Error`. 774 */ 775function isError(value) { 776 var tag = toString.call(value); 777 switch (tag) { 778 case '[object Error]': return true; 779 case '[object Exception]': return true; 780 case '[object DOMException]': return true; 781 default: return value instanceof Error; 782 } 783} 784 785/** 786 * @ngdoc function 787 * @name angular.isFunction 788 * @module ng 789 * @kind function 790 * 791 * @description 792 * Determines if a reference is a `Function`. 793 * 794 * @param {*} value Reference to check. 795 * @returns {boolean} True if `value` is a `Function`. 796 */ 797function isFunction(value) {return typeof value === 'function';} 798 799 800/** 801 * Determines if a value is a regular expression object. 802 * 803 * @private 804 * @param {*} value Reference to check. 805 * @returns {boolean} True if `value` is a `RegExp`. 806 */ 807function isRegExp(value) { 808 return toString.call(value) === '[object RegExp]'; 809} 810 811 812/** 813 * Checks if `obj` is a window object. 814 * 815 * @private 816 * @param {*} obj Object to check 817 * @returns {boolean} True if `obj` is a window obj. 818 */ 819function isWindow(obj) { 820 return obj && obj.window === obj; 821} 822 823 824function isScope(obj) { 825 return obj && obj.$evalAsync && obj.$watch; 826} 827 828 829function isFile(obj) { 830 return toString.call(obj) === '[object File]'; 831} 832 833 834function isFormData(obj) { 835 return toString.call(obj) === '[object FormData]'; 836} 837 838 839function isBlob(obj) { 840 return toString.call(obj) === '[object Blob]'; 841} 842 843 844function isBoolean(value) { 845 return typeof value === 'boolean'; 846} 847 848 849function isPromiseLike(obj) { 850 return obj && isFunction(obj.then); 851} 852 853 854var TYPED_ARRAY_REGEXP = /^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array]$/; 855function isTypedArray(value) { 856 return value && isNumber(value.length) && TYPED_ARRAY_REGEXP.test(toString.call(value)); 857} 858 859function isArrayBuffer(obj) { 860 return toString.call(obj) === '[object ArrayBuffer]'; 861} 862 863 864var trim = function(value) { 865 return isString(value) ? value.trim() : value; 866}; 867 868// Copied from: 869// http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021 870// Prereq: s is a string. 871var escapeForRegexp = function(s) { 872 return s 873 .replace(/([-()[\]{}+?*.$^|,:#<!\\])/g, '\\$1') 874 // eslint-disable-next-line no-control-regex 875 .replace(/\x08/g, '\\x08'); 876}; 877 878 879/** 880 * @ngdoc function 881 * @name angular.isElement 882 * @module ng 883 * @kind function 884 * 885 * @description 886 * Determines if a reference is a DOM element (or wrapped jQuery element). 887 * 888 * @param {*} value Reference to check. 889 * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element). 890 */ 891function isElement(node) { 892 return !!(node && 893 (node.nodeName // We are a direct element. 894 || (node.prop && node.attr && node.find))); // We have an on and find method part of jQuery API. 895} 896 897/** 898 * @param str 'key1,key2,...' 899 * @returns {object} in the form of {key1:true, key2:true, ...} 900 */ 901function makeMap(str) { 902 var obj = {}, items = str.split(','), i; 903 for (i = 0; i < items.length; i++) { 904 obj[items[i]] = true; 905 } 906 return obj; 907} 908 909 910function nodeName_(element) { 911 return lowercase(element.nodeName || (element[0] && element[0].nodeName)); 912} 913 914function includes(array, obj) { 915 return Array.prototype.indexOf.call(array, obj) !== -1; 916} 917 918function arrayRemove(array, value) { 919 var index = array.indexOf(value); 920 if (index >= 0) { 921 array.splice(index, 1); 922 } 923 return index; 924} 925 926/** 927 * @ngdoc function 928 * @name angular.copy 929 * @module ng 930 * @kind function 931 * 932 * @description 933 * Creates a deep copy of `source`, which should be an object or an array. This functions is used 934 * internally, mostly in the change-detection code. It is not intended as an all-purpose copy 935 * function, and has several limitations (see below). 936 * 937 * * If no destination is supplied, a copy of the object or array is created. 938 * * If a destination is provided, all of its elements (for arrays) or properties (for objects) 939 * are deleted and then all elements/properties from the source are copied to it. 940 * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned. 941 * * If `source` is identical to `destination` an exception will be thrown. 942 * 943 * <br /> 944 * 945 * <div class="alert alert-warning"> 946 * Only enumerable properties are taken into account. Non-enumerable properties (both on `source` 947 * and on `destination`) will be ignored. 948 * </div> 949 * 950 * <div class="alert alert-warning"> 951 * `angular.copy` does not check if destination and source are of the same type. It's the 952 * developer's responsibility to make sure they are compatible. 953 * </div> 954 * 955 * @knownIssue 956 * This is a non-exhaustive list of object types / features that are not handled correctly by 957 * `angular.copy`. Note that since this functions is used by the change detection code, this 958 * means binding or watching objects of these types (or that include these types) might not work 959 * correctly. 960 * - [`File`](https://developer.mozilla.org/docs/Web/API/File) 961 * - [`Map`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map) 962 * - [`ImageData`](https://developer.mozilla.org/docs/Web/API/ImageData) 963 * - [`MediaStream`](https://developer.mozilla.org/docs/Web/API/MediaStream) 964 * - [`Set`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Set) 965 * - [`WeakMap`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) 966 * - [`getter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get)/ 967 * [`setter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set) 968 * 969 * @param {*} source The source that will be used to make a copy. Can be any type, including 970 * primitives, `null`, and `undefined`. 971 * @param {(Object|Array)=} destination Destination into which the source is copied. If provided, 972 * must be of the same type as `source`. 973 * @returns {*} The copy or updated `destination`, if `destination` was specified. 974 * 975 * @example 976 <example module="copyExample" name="angular-copy"> 977 <file name="index.html"> 978 <div ng-controller="ExampleController"> 979 <form novalidate class="simple-form"> 980 <label>Name: <input type="text" ng-model="user.name" /></label><br /> 981 <label>Age: <input type="number" ng-model="user.age" /></label><br /> 982 Gender: <label><input type="radio" ng-model="user.gender" value="male" />male</label> 983 <label><input type="radio" ng-model="user.gender" value="female" />female</label><br /> 984 <button ng-click="reset()">RESET</button> 985 <button ng-click="update(user)">SAVE</button> 986 </form> 987 <pre>form = {{user | json}}</pre> 988 <pre>leader = {{leader | json}}</pre> 989 </div> 990 </file> 991 <file name="script.js"> 992 // Module: copyExample 993 angular. 994 module('copyExample', []). 995 controller('ExampleController', ['$scope', function($scope) { 996 $scope.leader = {}; 997 998 $scope.reset = function() { 999 // Example with 1 argument 1000 $scope.user = angular.copy($scope.leader); 1001 }; 1002 1003 $scope.update = function(user) { 1004 // Example with 2 arguments 1005 angular.copy(user, $scope.leader); 1006 }; 1007 1008 $scope.reset(); 1009 }]); 1010 </file> 1011 </example> 1012 */ 1013function copy(source, destination, maxDepth) { 1014 var stackSource = []; 1015 var stackDest = []; 1016 maxDepth = isValidObjectMaxDepth(maxDepth) ? maxDepth : NaN; 1017 1018 if (destination) { 1019 if (isTypedArray(destination) || isArrayBuffer(destination)) { 1020 throw ngMinErr('cpta', 'Can\'t copy! TypedArray destination cannot be mutated.'); 1021 } 1022 if (source === destination) { 1023 throw ngMinErr('cpi', 'Can\'t copy! Source and destination are identical.'); 1024 } 1025 1026 // Empty the destination object 1027 if (isArray(destination)) { 1028 destination.length = 0; 1029 } else { 1030 forEach(destination, function(value, key) { 1031 if (key !== '$$hashKey') { 1032 delete destination[key]; 1033 } 1034 }); 1035 } 1036 1037 stackSource.push(source); 1038 stackDest.push(destination); 1039 return copyRecurse(source, destination, maxDepth); 1040 } 1041 1042 return copyElement(source, maxDepth); 1043 1044 function copyRecurse(source, destination, maxDepth) { 1045 maxDepth--; 1046 if (maxDepth < 0) { 1047 return '...'; 1048 } 1049 var h = destination.$$hashKey; 1050 var key; 1051 if (isArray(source)) { 1052 for (var i = 0, ii = source.length; i < ii; i++) { 1053 destination.push(copyElement(source[i], maxDepth)); 1054 } 1055 } else if (isBlankObject(source)) { 1056 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty 1057 for (key in source) { 1058 destination[key] = copyElement(source[key], maxDepth); 1059 } 1060 } else if (source && typeof source.hasOwnProperty === 'function') { 1061 // Slow path, which must rely on hasOwnProperty 1062 for (key in source) { 1063 if (source.hasOwnProperty(key)) { 1064 destination[key] = copyElement(source[key], maxDepth); 1065 } 1066 } 1067 } else { 1068 // Slowest path --- hasOwnProperty can't be called as a method 1069 for (key in source) { 1070 if (hasOwnProperty.call(source, key)) { 1071 destination[key] = copyElement(source[key], maxDepth); 1072 } 1073 } 1074 } 1075 setHashKey(destination, h); 1076 return destination; 1077 } 1078 1079 function copyElement(source, maxDepth) { 1080 // Simple values 1081 if (!isObject(source)) { 1082 return source; 1083 } 1084 1085 // Already copied values 1086 var index = stackSource.indexOf(source); 1087 if (index !== -1) { 1088 return stackDest[index]; 1089 } 1090 1091 if (isWindow(source) || isScope(source)) { 1092 throw ngMinErr('cpws', 1093 'Can\'t copy! Making copies of Window or Scope instances is not supported.'); 1094 } 1095 1096 var needsRecurse = false; 1097 var destination = copyType(source); 1098 1099 if (destination === undefined) { 1100 destination = isArray(source) ? [] : Object.create(getPrototypeOf(source)); 1101 needsRecurse = true; 1102 } 1103 1104 stackSource.push(source); 1105 stackDest.push(destination); 1106 1107 return needsRecurse 1108 ? copyRecurse(source, destination, maxDepth) 1109 : destination; 1110 } 1111 1112 function copyType(source) { 1113 switch (toString.call(source)) { 1114 case '[object Int8Array]': 1115 case '[object Int16Array]': 1116 case '[object Int32Array]': 1117 case '[object Float32Array]': 1118 case '[object Float64Array]': 1119 case '[object Uint8Array]': 1120 case '[object Uint8ClampedArray]': 1121 case '[object Uint16Array]': 1122 case '[object Uint32Array]': 1123 return new source.constructor(copyElement(source.buffer), source.byteOffset, source.length); 1124 1125 case '[object ArrayBuffer]': 1126 // Support: IE10 1127 if (!source.slice) { 1128 // If we're in this case we know the environment supports ArrayBuffer 1129 /* eslint-disable no-undef */ 1130 var copied = new ArrayBuffer(source.byteLength); 1131 new Uint8Array(copied).set(new Uint8Array(source)); 1132 /* eslint-enable */ 1133 return copied; 1134 } 1135 return source.slice(0); 1136 1137 case '[object Boolean]': 1138 case '[object Number]': 1139 case '[object String]': 1140 case '[object Date]': 1141 return new source.constructor(source.valueOf()); 1142 1143 case '[object RegExp]': 1144 var re = new RegExp(source.source, source.toString().match(/[^/]*$/)[0]); 1145 re.lastIndex = source.lastIndex; 1146 return re; 1147 1148 case '[object Blob]': 1149 return new source.constructor([source], {type: source.type}); 1150 } 1151 1152 if (isFunction(source.cloneNode)) { 1153 return source.cloneNode(true); 1154 } 1155 } 1156} 1157 1158 1159// eslint-disable-next-line no-self-compare 1160function simpleCompare(a, b) { return a === b || (a !== a && b !== b); } 1161 1162 1163/** 1164 * @ngdoc function 1165 * @name angular.equals 1166 * @module ng 1167 * @kind function 1168 * 1169 * @description 1170 * Determines if two objects or two values are equivalent. Supports value types, regular 1171 * expressions, arrays and objects. 1172 * 1173 * Two objects or values are considered equivalent if at least one of the following is true: 1174 * 1175 * * Both objects or values pass `===` comparison. 1176 * * Both objects or values are of the same type and all of their properties are equal by 1177 * comparing them with `angular.equals`. 1178 * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal) 1179 * * Both values represent the same regular expression (In JavaScript, 1180 * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual 1181 * representation matches). 1182 * 1183 * During a property comparison, properties of `function` type and properties with names 1184 * that begin with `$` are ignored. 1185 * 1186 * Scope and DOMWindow objects are being compared only by identify (`===`). 1187 * 1188 * @param {*} o1 Object or value to compare. 1189 * @param {*} o2 Object or value to compare. 1190 * @returns {boolean} True if arguments are equal. 1191 * 1192 * @example 1193 <example module="equalsExample" name="equalsExample"> 1194 <file name="index.html"> 1195 <div ng-controller="ExampleController"> 1196 <form novalidate> 1197 <h3>User 1</h3> 1198 Name: <input type="text" ng-model="user1.name"> 1199 Age: <input type="number" ng-model="user1.age"> 1200 1201 <h3>User 2</h3> 1202 Name: <input type="text" ng-model="user2.name"> 1203 Age: <input type="number" ng-model="user2.age"> 1204 1205 <div> 1206 <br/> 1207 <input type="button" value="Compare" ng-click="compare()"> 1208 </div> 1209 User 1: <pre>{{user1 | json}}</pre> 1210 User 2: <pre>{{user2 | json}}</pre> 1211 Equal: <pre>{{result}}</pre> 1212 </form> 1213 </div> 1214 </file> 1215 <file name="script.js"> 1216 angular.module('equalsExample', []).controller('ExampleController', ['$scope', function($scope) { 1217 $scope.user1 = {}; 1218 $scope.user2 = {}; 1219 $scope.compare = function() { 1220 $scope.result = angular.equals($scope.user1, $scope.user2); 1221 }; 1222 }]); 1223 </file> 1224 </example> 1225 */ 1226function equals(o1, o2) { 1227 if (o1 === o2) return true; 1228 if (o1 === null || o2 === null) return false; 1229 // eslint-disable-next-line no-self-compare 1230 if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN 1231 var t1 = typeof o1, t2 = typeof o2, length, key, keySet; 1232 if (t1 === t2 && t1 === 'object') { 1233 if (isArray(o1)) { 1234 if (!isArray(o2)) return false; 1235 if ((length = o1.length) === o2.length) { 1236 for (key = 0; key < length; key++) { 1237 if (!equals(o1[key], o2[key])) return false; 1238 } 1239 return true; 1240 } 1241 } else if (isDate(o1)) { 1242 if (!isDate(o2)) return false; 1243 return simpleCompare(o1.getTime(), o2.getTime()); 1244 } else if (isRegExp(o1)) { 1245 if (!isRegExp(o2)) return false; 1246 return o1.toString() === o2.toString(); 1247 } else { 1248 if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || 1249 isArray(o2) || isDate(o2) || isRegExp(o2)) return false; 1250 keySet = createMap(); 1251 for (key in o1) { 1252 if (key.charAt(0) === '$' || isFunction(o1[key])) continue; 1253 if (!equals(o1[key], o2[key])) return false; 1254 keySet[key] = true; 1255 } 1256 for (key in o2) { 1257 if (!(key in keySet) && 1258 key.charAt(0) !== '$' && 1259 isDefined(o2[key]) && 1260 !isFunction(o2[key])) return false; 1261 } 1262 return true; 1263 } 1264 } 1265 return false; 1266} 1267 1268var csp = function() { 1269 if (!isDefined(csp.rules)) { 1270 1271 1272 var ngCspElement = (window.document.querySelector('[ng-csp]') || 1273 window.document.querySelector('[data-ng-csp]')); 1274 1275 if (ngCspElement) { 1276 var ngCspAttribute = ngCspElement.getAttribute('ng-csp') || 1277 ngCspElement.getAttribute('data-ng-csp'); 1278 csp.rules = { 1279 noUnsafeEval: !ngCspAttribute || (ngCspAttribute.indexOf('no-unsafe-eval') !== -1), 1280 noInlineStyle: !ngCspAttribute || (ngCspAttribute.indexOf('no-inline-style') !== -1) 1281 }; 1282 } else { 1283 csp.rules = { 1284 noUnsafeEval: noUnsafeEval(), 1285 noInlineStyle: false 1286 }; 1287 } 1288 } 1289 1290 return csp.rules; 1291 1292 function noUnsafeEval() { 1293 try { 1294 // eslint-disable-next-line no-new, no-new-func 1295 new Function(''); 1296 return false; 1297 } catch (e) { 1298 return true; 1299 } 1300 } 1301}; 1302 1303/** 1304 * @ngdoc directive 1305 * @module ng 1306 * @name ngJq 1307 * 1308 * @element ANY 1309 * @param {string=} ngJq the name of the library available under `window` 1310 * to be used for angular.element 1311 * @description 1312 * Use this directive to force the angular.element library. This should be 1313 * used to force either jqLite by leaving ng-jq blank or setting the name of 1314 * the jquery variable under window (eg. jQuery). 1315 * 1316 * Since AngularJS looks for this directive when it is loaded (doesn't wait for the 1317 * DOMContentLoaded event), it must be placed on an element that comes before the script 1318 * which loads angular. Also, only the first instance of `ng-jq` will be used and all 1319 * others ignored. 1320 * 1321 * @example 1322 * This example shows how to force jqLite using the `ngJq` directive to the `html` tag. 1323 ```html 1324 <!doctype html> 1325 <html ng-app ng-jq> 1326 ... 1327 ... 1328 </html> 1329 ``` 1330 * @example 1331 * This example shows how to use a jQuery based library of a different name. 1332 * The library name must be available at the top most 'window'. 1333 ```html 1334 <!doctype html> 1335 <html ng-app ng-jq="jQueryLib"> 1336 ... 1337 ... 1338 </html> 1339 ``` 1340 */ 1341var jq = function() { 1342 if (isDefined(jq.name_)) return jq.name_; 1343 var el; 1344 var i, ii = ngAttrPrefixes.length, prefix, name; 1345 for (i = 0; i < ii; ++i) { 1346 prefix = ngAttrPrefixes[i]; 1347 el = window.document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]'); 1348 if (el) { 1349 name = el.getAttribute(prefix + 'jq'); 1350 break; 1351 } 1352 } 1353 1354 return (jq.name_ = name); 1355}; 1356 1357function concat(array1, array2, index) { 1358 return array1.concat(slice.call(array2, index)); 1359} 1360 1361function sliceArgs(args, startIndex) { 1362 return slice.call(args, startIndex || 0); 1363} 1364 1365 1366/** 1367 * @ngdoc function 1368 * @name angular.bind 1369 * @module ng 1370 * @kind function 1371 * 1372 * @description 1373 * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for 1374 * `fn`). You can supply optional `args` that are prebound to the function. This feature is also 1375 * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as 1376 * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application). 1377 * 1378 * @param {Object} self Context which `fn` should be evaluated in. 1379 * @param {function()} fn Function to be bound. 1380 * @param {...*} args Optional arguments to be prebound to the `fn` function call. 1381 * @returns {function()} Function that wraps the `fn` with all the specified bindings. 1382 */ 1383function bind(self, fn) { 1384 var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : []; 1385 if (isFunction(fn) && !(fn instanceof RegExp)) { 1386 return curryArgs.length 1387 ? function() { 1388 return arguments.length 1389 ? fn.apply(self, concat(curryArgs, arguments, 0)) 1390 : fn.apply(self, curryArgs); 1391 } 1392 : function() { 1393 return arguments.length 1394 ? fn.apply(self, arguments) 1395 : fn.call(self); 1396 }; 1397 } else { 1398 // In IE, native methods are not functions so they cannot be bound (note: they don't need to be). 1399 return fn; 1400 } 1401} 1402 1403 1404function toJsonReplacer(key, value) { 1405 var val = value; 1406 1407 if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') { 1408 val = undefined; 1409 } else if (isWindow(value)) { 1410 val = '$WINDOW'; 1411 } else if (value && window.document === value) { 1412 val = '$DOCUMENT'; 1413 } else if (isScope(value)) { 1414 val = '$SCOPE'; 1415 } 1416 1417 return val; 1418} 1419 1420 1421/** 1422 * @ngdoc function 1423 * @name angular.toJson 1424 * @module ng 1425 * @kind function 1426 * 1427 * @description 1428 * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be 1429 * stripped since AngularJS uses this notation internally. 1430 * 1431 * @param {Object|Array|Date|string|number|boolean} obj Input to be serialized into JSON. 1432 * @param {boolean|number} [pretty=2] If set to true, the JSON output will contain newlines and whitespace. 1433 * If set to an integer, the JSON output will contain that many spaces per indentation. 1434 * @returns {string|undefined} JSON-ified string representing `obj`. 1435 * @knownIssue 1436 * 1437 * The Safari browser throws a `RangeError` instead of returning `null` when it tries to stringify a `Date` 1438 * object with an invalid date value. The only reliable way to prevent this is to monkeypatch the 1439 * `Date.prototype.toJSON` method as follows: 1440 * 1441 * ``` 1442 * var _DatetoJSON = Date.prototype.toJSON; 1443 * Date.prototype.toJSON = function() { 1444 * try { 1445 * return _DatetoJSON.call(this); 1446 * } catch(e) { 1447 * if (e instanceof RangeError) { 1448 * return null; 1449 * } 1450 * throw e; 1451 * } 1452 * }; 1453 * ``` 1454 * 1455 * See https://github.com/angular/angular.js/pull/14221 for more information. 1456 */ 1457function toJson(obj, pretty) { 1458 if (isUndefined(obj)) return undefined; 1459 if (!isNumber(pretty)) { 1460 pretty = pretty ? 2 : null; 1461 } 1462 return JSON.stringify(obj, toJsonReplacer, pretty); 1463} 1464 1465 1466/** 1467 * @ngdoc function 1468 * @name angular.fromJson 1469 * @module ng 1470 * @kind function 1471 * 1472 * @description 1473 * Deserializes a JSON string. 1474 * 1475 * @param {string} json JSON string to deserialize. 1476 * @returns {Object|Array|string|number} Deserialized JSON string. 1477 */ 1478function fromJson(json) { 1479 return isString(json) 1480 ? JSON.parse(json) 1481 : json; 1482} 1483 1484 1485var ALL_COLONS = /:/g; 1486function timezoneToOffset(timezone, fallback) { 1487 // Support: IE 9-11 only, Edge 13-15+ 1488 // IE/Edge do not "understand" colon (`:`) in timezone 1489 timezone = timezone.replace(ALL_COLONS, ''); 1490 var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000; 1491 return isNumberNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset; 1492} 1493 1494 1495function addDateMinutes(date, minutes) { 1496 date = new Date(date.getTime()); 1497 date.setMinutes(date.getMinutes() + minutes); 1498 return date; 1499} 1500 1501 1502function convertTimezoneToLocal(date, timezone, reverse) { 1503 reverse = reverse ? -1 : 1; 1504 var dateTimezoneOffset = date.getTimezoneOffset(); 1505 var timezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset); 1506 return addDateMinutes(date, reverse * (timezoneOffset - dateTimezoneOffset)); 1507} 1508 1509 1510/** 1511 * @returns {string} Returns the string representation of the element. 1512 */ 1513function startingTag(element) { 1514 element = jqLite(element).clone().empty(); 1515 var elemHtml = jqLite('<div></div>').append(element).html(); 1516 try { 1517 return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) : 1518 elemHtml. 1519 match(/^(<[^>]+>)/)[1]. 1520 replace(/^<([\w-]+)/, function(match, nodeName) {return '<' + lowercase(nodeName);}); 1521 } catch (e) { 1522 return lowercase(elemHtml); 1523 } 1524 1525} 1526 1527 1528///////////////////////////////////////////////// 1529 1530/** 1531 * Tries to decode the URI component without throwing an exception. 1532 * 1533 * @private 1534 * @param str value potential URI component to check. 1535 * @returns {boolean} True if `value` can be decoded 1536 * with the decodeURIComponent function. 1537 */ 1538function tryDecodeURIComponent(value) { 1539 try { 1540 return decodeURIComponent(value); 1541 } catch (e) { 1542 // Ignore any invalid uri component. 1543 } 1544} 1545 1546 1547/** 1548 * Parses an escaped url query string into key-value pairs. 1549 * @returns {Object.<string,boolean|Array>} 1550 */ 1551function parseKeyValue(/**string*/keyValue) { 1552 var obj = {}; 1553 forEach((keyValue || '').split('&'), function(keyValue) { 1554 var splitPoint, key, val; 1555 if (keyValue) { 1556 key = keyValue = keyValue.replace(/\+/g,'%20'); 1557 splitPoint = keyValue.indexOf('='); 1558 if (splitPoint !== -1) { 1559 key = keyValue.substring(0, splitPoint); 1560 val = keyValue.substring(splitPoint + 1); 1561 } 1562 key = tryDecodeURIComponent(key); 1563 if (isDefined(key)) { 1564 val = isDefined(val) ? tryDecodeURIComponent(val) : true; 1565 if (!hasOwnProperty.call(obj, key)) { 1566 obj[key] = val; 1567 } else if (isArray(obj[key])) { 1568 obj[key].push(val); 1569 } else { 1570 obj[key] = [obj[key],val]; 1571 } 1572 } 1573 } 1574 }); 1575 return obj; 1576} 1577 1578function toKeyValue(obj) { 1579 var parts = []; 1580 forEach(obj, function(value, key) { 1581 if (isArray(value)) { 1582 forEach(value, function(arrayValue) { 1583 parts.push(encodeUriQuery(key, true) + 1584 (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true))); 1585 }); 1586 } else { 1587 parts.push(encodeUriQuery(key, true) + 1588 (value === true ? '' : '=' + encodeUriQuery(value, true))); 1589 } 1590 }); 1591 return parts.length ? parts.join('&') : ''; 1592} 1593 1594 1595/** 1596 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow 1597 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path 1598 * segments: 1599 * segment = *pchar 1600 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1601 * pct-encoded = "%" HEXDIG HEXDIG 1602 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1603 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1604 * / "*" / "+" / "," / ";" / "=" 1605 */ 1606function encodeUriSegment(val) { 1607 return encodeUriQuery(val, true). 1608 replace(/%26/gi, '&'). 1609 replace(/%3D/gi, '='). 1610 replace(/%2B/gi, '+'); 1611} 1612 1613 1614/** 1615 * This method is intended for encoding *key* or *value* parts of query component. We need a custom 1616 * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be 1617 * encoded per http://tools.ietf.org/html/rfc3986: 1618 * query = *( pchar / "/" / "?" ) 1619 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1620 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1621 * pct-encoded = "%" HEXDIG HEXDIG 1622 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1623 * / "*" / "+" / "," / ";" / "=" 1624 */ 1625function encodeUriQuery(val, pctEncodeSpaces) { 1626 return encodeURIComponent(val). 1627 replace(/%40/gi, '@'). 1628 replace(/%3A/gi, ':'). 1629 replace(/%24/g, '$'). 1630 replace(/%2C/gi, ','). 1631 replace(/%3B/gi, ';'). 1632 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); 1633} 1634 1635var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-']; 1636 1637function getNgAttribute(element, ngAttr) { 1638 var attr, i, ii = ngAttrPrefixes.length; 1639 for (i = 0; i < ii; ++i) { 1640 attr = ngAttrPrefixes[i] + ngAttr; 1641 if (isString(attr = element.getAttribute(attr))) { 1642 return attr; 1643 } 1644 } 1645 return null; 1646} 1647 1648function allowAutoBootstrap(document) { 1649 var script = document.currentScript; 1650 1651 if (!script) { 1652 // Support: IE 9-11 only 1653 // IE does not have `document.currentScript` 1654 return true; 1655 } 1656 1657 // If the `currentScript` property has been clobbered just return false, since this indicates a probable attack 1658 if (!(script instanceof window.HTMLScriptElement || script instanceof window.SVGScriptElement)) { 1659 return false; 1660 } 1661 1662 var attributes = script.attributes; 1663 var srcs = [attributes.getNamedItem('src'), attributes.getNamedItem('href'), attributes.getNamedItem('xlink:href')]; 1664 1665 return srcs.every(function(src) { 1666 if (!src) { 1667 return true; 1668 } 1669 if (!src.value) { 1670 return false; 1671 } 1672 1673 var link = document.createElement('a'); 1674 link.href = src.value; 1675 1676 if (document.location.origin === link.origin) { 1677 // Same-origin resources are always allowed, even for banned URL schemes. 1678 return true; 1679 } 1680 // Disabled bootstrapping unless angular.js was loaded from a known scheme used on the web. 1681 // This is to prevent angular.js bundled with browser extensions from being used to bypass the 1682 // content security policy in web pages and other browser extensions. 1683 switch (link.protocol) { 1684 case 'http:': 1685 case 'https:': 1686 case 'ftp:': 1687 case 'blob:': 1688 case 'file:': 1689 case 'data:': 1690 return true; 1691 default: 1692 return false; 1693 } 1694 }); 1695} 1696 1697// Cached as it has to run during loading so that document.currentScript is available. 1698var isAutoBootstrapAllowed = allowAutoBootstrap(window.document); 1699 1700/** 1701 * @ngdoc directive 1702 * @name ngApp 1703 * @module ng 1704 * 1705 * @element ANY 1706 * @param {angular.Module} ngApp an optional application 1707 * {@link angular.module module} name to load. 1708 * @param {boolean=} ngStrictDi if this attribute is present on the app element, the injector will be 1709 * created in "strict-di" mode. This means that the application will fail to invoke functions which 1710 * do not use explicit function annotation (and are thus unsuitable for minification), as described 1711 * in {@link guide/di the Dependency Injection guide}, and useful debugging info will assist in 1712 * tracking down the root of these bugs. 1713 * 1714 * @description 1715 * 1716 * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive 1717 * designates the **root element** of the application and is typically placed near the root element 1718 * of the page - e.g. on the `<body>` or `<html>` tags. 1719 * 1720 * There are a few things to keep in mind when using `ngApp`: 1721 * - only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp` 1722 * found in the document will be used to define the root element to auto-bootstrap as an 1723 * application. To run multiple applications in an HTML document you must manually bootstrap them using 1724 * {@link angular.bootstrap} instead. 1725 * - AngularJS applications cannot be nested within each other. 1726 * - Do not use a directive that uses {@link ng.$compile#transclusion transclusion} on the same element as `ngApp`. 1727 * This includes directives such as {@link ng.ngIf `ngIf`}, {@link ng.ngInclude `ngInclude`} and 1728 * {@link ngRoute.ngView `ngView`}. 1729 * Doing this misplaces the app {@link ng.$rootElement `$rootElement`} and the app's {@link auto.$injector injector}, 1730 * causing animations to stop working and making the injector inaccessible from outside the app. 1731 * 1732 * You can specify an **AngularJS module** to be used as the root module for the application. This 1733 * module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It 1734 * should contain the application code needed or have dependencies on other modules that will 1735 * contain the code. See {@link angular.module} for more information. 1736 * 1737 * In the example below if the `ngApp` directive were not placed on the `html` element then the 1738 * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}` 1739 * would not be resolved to `3`. 1740 * 1741 * @example 1742 * 1743 * ### Simple Usage 1744 * 1745 * `ngApp` is the easiest, and most common way to bootstrap an application. 1746 * 1747 <example module="ngAppDemo" name="ng-app"> 1748 <file name="index.html"> 1749 <div ng-controller="ngAppDemoController"> 1750 I can add: {{a}} + {{b}} = {{ a+b }} 1751 </div> 1752 </file> 1753 <file name="script.js"> 1754 angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) { 1755 $scope.a = 1; 1756 $scope.b = 2; 1757 }); 1758 </file> 1759 </example> 1760 * 1761 * @example 1762 * 1763 * ### With `ngStrictDi` 1764 * 1765 * Using `ngStrictDi`, you would see something like this: 1766 * 1767 <example ng-app-included="true" name="strict-di"> 1768 <file name="index.html"> 1769 <div ng-app="ngAppStrictDemo" ng-strict-di> 1770 <div ng-controller="GoodController1"> 1771 I can add: {{a}} + {{b}} = {{ a+b }} 1772 1773 <p>This renders because the controller does not fail to 1774 instantiate, by using explicit annotation style (see 1775 script.js for details) 1776 </p> 1777 </div> 1778 1779 <div ng-controller="GoodController2"> 1780 Name: <input ng-model="name"><br /> 1781 Hello, {{name}}! 1782 1783 <p>This renders because the controller does not fail to 1784 instantiate, by using explicit annotation style 1785 (see script.js for details) 1786 </p> 1787 </div> 1788 1789 <div ng-controller="BadController"> 1790 I can add: {{a}} + {{b}} = {{ a+b }} 1791 1792 <p>The controller could not be instantiated, due to relying 1793 on automatic function annotations (which are disabled in 1794 strict mode). As such, the content of this section is not 1795 interpolated, and there should be an error in your web console. 1796 </p> 1797 </div> 1798 </div> 1799 </file> 1800 <file name="script.js"> 1801 angular.module('ngAppStrictDemo', []) 1802 // BadController will fail to instantiate, due to relying on automatic function annotation, 1803 // rather than an explicit annotation 1804 .controller('BadController', function($scope) { 1805 $scope.a = 1; 1806 $scope.b = 2; 1807 }) 1808 // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated, 1809 // due to using explicit annotations using the array style and $inject property, respectively. 1810 .controller('GoodController1', ['$scope', function($scope) { 1811 $scope.a = 1; 1812 $scope.b = 2; 1813 }]) 1814 .controller('GoodController2', GoodController2); 1815 function GoodController2($scope) { 1816 $scope.name = 'World'; 1817 } 1818 GoodController2.$inject = ['$scope']; 1819 </file> 1820 <file name="style.css"> 1821 div[ng-controller] { 1822 margin-bottom: 1em; 1823 -webkit-border-radius: 4px; 1824 border-radius: 4px; 1825 border: 1px solid; 1826 padding: .5em; 1827 } 1828 div[ng-controller^=Good] { 1829 border-color: #d6e9c6; 1830 background-color: #dff0d8; 1831 color: #3c763d; 1832 } 1833 div[ng-controller^=Bad] { 1834 border-color: #ebccd1; 1835 background-color: #f2dede; 1836 color: #a94442; 1837 margin-bottom: 0; 1838 } 1839 </file> 1840 </example> 1841 */ 1842function angularInit(element, bootstrap) { 1843 var appElement, 1844 module, 1845 config = {}; 1846 1847 // The element `element` has priority over any other element. 1848 forEach(ngAttrPrefixes, function(prefix) { 1849 var name = prefix + 'app'; 1850 1851 if (!appElement && element.hasAttribute && element.hasAttribute(name)) { 1852 appElement = element; 1853 module = element.getAttribute(name); 1854 } 1855 }); 1856 forEach(ngAttrPrefixes, function(prefix) { 1857 var name = prefix + 'app'; 1858 var candidate; 1859 1860 if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) { 1861 appElement = candidate; 1862 module = candidate.getAttribute(name); 1863 } 1864 }); 1865 if (appElement) { 1866 if (!isAutoBootstrapAllowed) { 1867 window.console.error('AngularJS: disabling automatic bootstrap. <script> protocol indicates ' + 1868 'an extension, document.location.href does not match.'); 1869 return; 1870 } 1871 config.strictDi = getNgAttribute(appElement, 'strict-di') !== null; 1872 bootstrap(appElement, module ? [module] : [], config); 1873 } 1874} 1875 1876/** 1877 * @ngdoc function 1878 * @name angular.bootstrap 1879 * @module ng 1880 * @description 1881 * Use this function to manually start up AngularJS application. 1882 * 1883 * For more information, see the {@link guide/bootstrap Bootstrap guide}. 1884 * 1885 * AngularJS will detect if it has been loaded into the browser more than once and only allow the 1886 * first loaded script to be bootstrapped and will report a warning to the browser console for 1887 * each of the subsequent scripts. This prevents strange results in applications, where otherwise 1888 * multiple instances of AngularJS try to work on the DOM. 1889 * 1890 * <div class="alert alert-warning"> 1891 * **Note:** Protractor based end-to-end tests cannot use this function to bootstrap manually. 1892 * They must use {@link ng.directive:ngApp ngApp}. 1893 * </div> 1894 * 1895 * <div class="alert alert-warning"> 1896 * **Note:** Do not bootstrap the app on an element with a directive that uses {@link ng.$compile#transclusion transclusion}, 1897 * such as {@link ng.ngIf `ngIf`}, {@link ng.ngInclude `ngInclude`} and {@link ngRoute.ngView `ngView`}. 1898 * Doing this misplaces the app {@link ng.$rootElement `$rootElement`} and the app's {@link auto.$injector injector}, 1899 * causing animations to stop working and making the injector inaccessible from outside the app. 1900 * </div> 1901 * 1902 * ```html 1903 * <!doctype html> 1904 * <html> 1905 * <body> 1906 * <div ng-controller="WelcomeController"> 1907 * {{greeting}} 1908 * </div> 1909 * 1910 * <script src="angular.js"></script> 1911 * <script> 1912 * var app = angular.module('demo', []) 1913 * .controller('WelcomeController', function($scope) { 1914 * $scope.greeting = 'Welcome!'; 1915 * }); 1916 * angular.bootstrap(document, ['demo']); 1917 * </script> 1918 * </body> 1919 * </html> 1920 * ``` 1921 * 1922 * @param {DOMElement} element DOM element which is the root of AngularJS application. 1923 * @param {Array<String|Function|Array>=} modules an array of modules to load into the application. 1924 * Each item in the array should be the name of a predefined module or a (DI annotated) 1925 * function that will be invoked by the injector as a `config` block. 1926 * See: {@link angular.module modules} 1927 * @param {Object=} config an object for defining configuration options for the application. The 1928 * following keys are supported: 1929 * 1930 * * `strictDi` - disable automatic function annotation for the application. This is meant to 1931 * assist in finding bugs which break minified code. Defaults to `false`. 1932 * 1933 * @returns {auto.$injector} Returns the newly created injector for this app. 1934 */ 1935function bootstrap(element, modules, config) { 1936 if (!isObject(config)) config = {}; 1937 var defaultConfig = { 1938 strictDi: false 1939 }; 1940 config = extend(defaultConfig, config); 1941 var doBootstrap = function() { 1942 element = jqLite(element); 1943 1944 if (element.injector()) { 1945 var tag = (element[0] === window.document) ? 'document' : startingTag(element); 1946 // Encode angle brackets to prevent input from being sanitized to empty string #8683. 1947 throw ngMinErr( 1948 'btstrpd', 1949 'App already bootstrapped with this element \'{0}\'', 1950 tag.replace(/</,'<').replace(/>/,'>')); 1951 } 1952 1953 modules = modules || []; 1954 modules.unshift(['$provide', function($provide) { 1955 $provide.value('$rootElement', element); 1956 }]); 1957 1958 if (config.debugInfoEnabled) { 1959 // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`. 1960 modules.push(['$compileProvider', function($compileProvider) { 1961 $compileProvider.debugInfoEnabled(true); 1962 }]); 1963 } 1964 1965 modules.unshift('ng'); 1966 var injector = createInjector(modules, config.strictDi); 1967 injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', 1968 function bootstrapApply(scope, element, compile, injector) { 1969 scope.$apply(function() { 1970 element.data('$injector', injector); 1971 compile(element)(scope); 1972 }); 1973 }] 1974 ); 1975 return injector; 1976 }; 1977 1978 var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/; 1979 var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/; 1980 1981 if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) { 1982 config.debugInfoEnabled = true; 1983 window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, ''); 1984 } 1985 1986 if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) { 1987 return doBootstrap(); 1988 } 1989 1990 window.name = window.name.replace(NG_DEFER_BOOTSTRAP, ''); 1991 angular.resumeBootstrap = function(extraModules) { 1992 forEach(extraModules, function(module) { 1993 modules.push(module); 1994 }); 1995 return doBootstrap(); 1996 }; 1997 1998 if (isFunction(angular.resumeDeferredBootstrap)) { 1999 angular.resumeDeferredBootstrap(); 2000 } 2001} 2002 2003/** 2004 * @ngdoc function 2005 * @name angular.reloadWithDebugInfo 2006 * @module ng 2007 * @description 2008 * Use this function to reload the current application with debug information turned on. 2009 * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`. 2010 * 2011 * See {@link ng.$compileProvider#debugInfoEnabled} for more. 2012 */ 2013function reloadWithDebugInfo() { 2014 window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name; 2015 window.location.reload(); 2016} 2017 2018/** 2019 * @name angular.getTestability 2020 * @module ng 2021 * @description 2022 * Get the testability service for the instance of AngularJS on the given 2023 * element. 2024 * @param {DOMElement} element DOM element which is the root of AngularJS application. 2025 */ 2026function getTestability(rootElement) { 2027 var injector = angular.element(rootElement).injector(); 2028 if (!injector) { 2029 throw ngMinErr('test', 2030 'no injector found for element argument to getTestability'); 2031 } 2032 return injector.get('$$testability'); 2033} 2034 2035var SNAKE_CASE_REGEXP = /[A-Z]/g; 2036function snake_case(name, separator) { 2037 separator = separator || '_'; 2038 return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) { 2039 return (pos ? separator : '') + letter.toLowerCase(); 2040 }); 2041} 2042 2043var bindJQueryFired = false; 2044function bindJQuery() { 2045 var originalCleanData; 2046 2047 if (bindJQueryFired) { 2048 return; 2049 } 2050 2051 // bind to jQuery if present; 2052 var jqName = jq(); 2053 jQuery = isUndefined(jqName) ? window.jQuery : // use jQuery (if present) 2054 !jqName ? undefined : // use jqLite 2055 window[jqName]; // use jQuery specified by `ngJq` 2056 2057 // Use jQuery if it exists with proper functionality, otherwise default to us. 2058 // AngularJS 1.2+ requires jQuery 1.7+ for on()/off() support. 2059 // AngularJS 1.3+ technically requires at least jQuery 2.1+ but it may work with older 2060 // versions. It will not work for sure with jQuery <1.7, though. 2061 if (jQuery && jQuery.fn.on) { 2062 jqLite = jQuery; 2063 extend(jQuery.fn, { 2064 scope: JQLitePrototype.scope, 2065 isolateScope: JQLitePrototype.isolateScope, 2066 controller: /** @type {?} */ (JQLitePrototype).controller, 2067 injector: JQLitePrototype.injector, 2068 inheritedData: JQLitePrototype.inheritedData 2069 }); 2070 } else { 2071 jqLite = JQLite; 2072 } 2073 2074 // All nodes removed from the DOM via various jqLite/jQuery APIs like .remove() 2075 // are passed through jqLite/jQuery.cleanData. Monkey-patch this method to fire 2076 // the $destroy event on all removed nodes. 2077 originalCleanData = jqLite.cleanData; 2078 jqLite.cleanData = function(elems) { 2079 var events; 2080 for (var i = 0, elem; (elem = elems[i]) != null; i++) { 2081 events = (jqLite._data(elem) || {}).events; 2082 if (events && events.$destroy) { 2083 jqLite(elem).triggerHandler('$destroy'); 2084 } 2085 } 2086 originalCleanData(elems); 2087 }; 2088 2089 angular.element = jqLite; 2090 2091 // Prevent double-proxying. 2092 bindJQueryFired = true; 2093} 2094 2095/** 2096 * @ngdoc function 2097 * @name angular.UNSAFE_restoreLegacyJqLiteXHTMLReplacement 2098 * @module ng 2099 * @kind function 2100 * 2101 * @description 2102 * Restores the pre-1.8 behavior of jqLite that turns XHTML-like strings like 2103 * `<div /><span />` to `<div></div><span></span>` instead of `<div><span></span></div>`. 2104 * The new behavior is a security fix. Thus, if you need to call this function, please try to adjust 2105 * your code for this change and remove your use of this function as soon as possible. 2106 2107 * Note that this only patches jqLite. If you use jQuery 3.5.0 or newer, please read the 2108 * [jQuery 3.5 upgrade guide](https://jquery.com/upgrade-guide/3.5/) for more details 2109 * about the workarounds. 2110 */ 2111function UNSAFE_restoreLegacyJqLiteXHTMLReplacement() { 2112 JQLite.legacyXHTMLReplacement = true; 2113} 2114 2115/** 2116 * throw error if the argument is falsy. 2117 */ 2118function assertArg(arg, name, reason) { 2119 if (!arg) { 2120 throw ngMinErr('areq', 'Argument \'{0}\' is {1}', (name || '?'), (reason || 'required')); 2121 } 2122 return arg; 2123} 2124 2125function assertArgFn(arg, name, acceptArrayAnnotation) { 2126 if (acceptArrayAnnotation && isArray(arg)) { 2127 arg = arg[arg.length - 1]; 2128 } 2129 2130 assertArg(isFunction(arg), name, 'not a function, got ' + 2131 (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg)); 2132 return arg; 2133} 2134 2135/** 2136 * throw error if the name given is hasOwnProperty 2137 * @param {String} name the name to test 2138 * @param {String} context the context in which the name is used, such as module or directive 2139 */ 2140function assertNotHasOwnProperty(name, context) { 2141 if (name === 'hasOwnProperty') { 2142 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); 2143 } 2144} 2145 2146/** 2147 * Return the value accessible from the object by path. Any undefined traversals are ignored 2148 * @param {Object} obj starting object 2149 * @param {String} path path to traverse 2150 * @param {boolean} [bindFnToScope=true] 2151 * @returns {Object} value as accessible by path 2152 */ 2153//TODO(misko): this function needs to be removed 2154function getter(obj, path, bindFnToScope) { 2155 if (!path) return obj; 2156 var keys = path.split('.'); 2157 var key; 2158 var lastInstance = obj; 2159 var len = keys.length; 2160 2161 for (var i = 0; i < len; i++) { 2162 key = keys[i]; 2163 if (obj) { 2164 obj = (lastInstance = obj)[key]; 2165 } 2166 } 2167 if (!bindFnToScope && isFunction(obj)) { 2168 return bind(lastInstance, obj); 2169 } 2170 return obj; 2171} 2172 2173/** 2174 * Return the DOM siblings between the first and last node in the given array. 2175 * @param {Array} array like object 2176 * @returns {Array} the inputted object or a jqLite collection containing the nodes 2177 */ 2178function getBlockNodes(nodes) { 2179 // TODO(perf): update `nodes` instead of creating a new object? 2180 var node = nodes[0]; 2181 var endNode = nodes[nodes.length - 1]; 2182 var blockNodes; 2183 2184 for (var i = 1; node !== endNode && (node = node.nextSibling); i++) { 2185 if (blockNodes || nodes[i] !== node) { 2186 if (!blockNodes) { 2187 blockNodes = jqLite(slice.call(nodes, 0, i)); 2188 } 2189 blockNodes.push(node); 2190 } 2191 } 2192 2193 return blockNodes || nodes; 2194} 2195 2196 2197/** 2198 * Creates a new object without a prototype. This object is useful for lookup without having to 2199 * guard against prototypically inherited properties via hasOwnProperty. 2200 * 2201 * Related micro-benchmarks: 2202 * - http://jsperf.com/object-create2 2203 * - http://jsperf.com/proto-map-lookup/2 2204 * - http://jsperf.com/for-in-vs-object-keys2 2205 * 2206 * @returns {Object} 2207 */ 2208function createMap() { 2209 return Object.create(null); 2210} 2211 2212function stringify(value) { 2213 if (value == null) { // null || undefined 2214 return ''; 2215 } 2216 switch (typeof value) { 2217 case 'string': 2218 break; 2219 case 'number': 2220 value = '' + value; 2221 break; 2222 default: 2223 if (hasCustomToString(value) && !isArray(value) && !isDate(value)) { 2224 value = value.toString(); 2225 } else { 2226 value = toJson(value); 2227 } 2228 } 2229 2230 return value; 2231} 2232 2233var NODE_TYPE_ELEMENT = 1; 2234var NODE_TYPE_ATTRIBUTE = 2; 2235var NODE_TYPE_TEXT = 3; 2236var NODE_TYPE_COMMENT = 8; 2237var NODE_TYPE_DOCUMENT = 9; 2238var NODE_TYPE_DOCUMENT_FRAGMENT = 11; 2239 2240/** 2241 * @ngdoc type 2242 * @name angular.Module 2243 * @module ng 2244 * @description 2245 * 2246 * Interface for configuring AngularJS {@link angular.module modules}. 2247 */ 2248 2249function setupModuleLoader(window) { 2250 2251 var $injectorMinErr = minErr('$injector'); 2252 var ngMinErr = minErr('ng'); 2253 2254 function ensure(obj, name, factory) { 2255 return obj[name] || (obj[name] = factory()); 2256 } 2257 2258 var angular = ensure(window, 'angular', Object); 2259 2260 // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap 2261 angular.$$minErr = angular.$$minErr || minErr; 2262 2263 return ensure(angular, 'module', function() { 2264 /** @type {Object.<string, angular.Module>} */ 2265 var modules = {}; 2266 2267 /** 2268 * @ngdoc function 2269 * @name angular.module 2270 * @module ng 2271 * @description 2272 * 2273 * The `angular.module` is a global place for creating, registering and retrieving AngularJS 2274 * modules. 2275 * All modules (AngularJS core or 3rd party) that should be available to an application must be 2276 * registered using this mechanism. 2277 * 2278 * Passing one argument retrieves an existing {@link angular.Module}, 2279 * whereas passing more than one argument creates a new {@link angular.Module} 2280 * 2281 * 2282 * # Module 2283 * 2284 * A module is a collection of services, directives, controllers, filters, and configuration information. 2285 * `angular.module` is used to configure the {@link auto.$injector $injector}. 2286 * 2287 * ```js 2288 * // Create a new module 2289 * var myModule = angular.module('myModule', []); 2290 * 2291 * // register a new service 2292 * myModule.value('appName', 'MyCoolApp'); 2293 * 2294 * // configure existing services inside initialization blocks. 2295 * myModule.config(['$locationProvider', function($locationProvider) { 2296 * // Configure existing providers 2297 * $locationProvider.hashPrefix('!'); 2298 * }]); 2299 * ``` 2300 * 2301 * Then you can create an injector and load your modules like this: 2302 * 2303 * ```js 2304 * var injector = angular.injector(['ng', 'myModule']) 2305 * ``` 2306 * 2307 * However it's more likely that you'll just use 2308 * {@link ng.directive:ngApp ngApp} or 2309 * {@link angular.bootstrap} to simplify this process for you. 2310 * 2311 * @param {!string} name The name of the module to create or retrieve. 2312 * @param {!Array.<string>=} requires If specified then new module is being created. If 2313 * unspecified then the module is being retrieved for further configuration. 2314 * @param {Function=} configFn Optional configuration function for the module. Same as 2315 * {@link angular.Module#config Module#config()}. 2316 * @returns {angular.Module} new module with the {@link angular.Module} api. 2317 */ 2318 return function module(name, requires, configFn) { 2319 2320 var info = {}; 2321 2322 var assertNotHasOwnProperty = function(name, context) { 2323 if (name === 'hasOwnProperty') { 2324 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); 2325 } 2326 }; 2327 2328 assertNotHasOwnProperty(name, 'module'); 2329 if (requires && modules.hasOwnProperty(name)) { 2330 modules[name] = null; 2331 } 2332 return ensure(modules, name, function() { 2333 if (!requires) { 2334 throw $injectorMinErr('nomod', 'Module \'{0}\' is not available! You either misspelled ' + 2335 'the module name or forgot to load it. If registering a module ensure that you ' + 2336 'specify the dependencies as the second argument.', name); 2337 } 2338 2339 /** @type {!Array.<Array.<*>>} */ 2340 var invokeQueue = []; 2341 2342 /** @type {!Array.<Function>} */ 2343 var configBlocks = []; 2344 2345 /** @type {!Array.<Function>} */ 2346 var runBlocks = []; 2347 2348 var config = invokeLater('$injector', 'invoke', 'push', configBlocks); 2349 2350 /** @type {angular.Module} */ 2351 var moduleInstance = { 2352 // Private state 2353 _invokeQueue: invokeQueue, 2354 _configBlocks: configBlocks, 2355 _runBlocks: runBlocks, 2356 2357 /** 2358 * @ngdoc method 2359 * @name angular.Module#info 2360 * @module ng 2361 * 2362 * @param {Object=} info Information about the module 2363 * @returns {Object|Module} The current info object for this module if called as a getter, 2364 * or `this` if called as a setter. 2365 * 2366 * @description 2367 * Read and write custom information about this module. 2368 * For example you could put the version of the module in here. 2369 * 2370 * ```js 2371 * angular.module('myModule', []).info({ version: '1.0.0' }); 2372 * ``` 2373 * 2374 * The version could then be read back out by accessing the module elsewhere: 2375 * 2376 * ``` 2377 * var version = angular.module('myModule').info().version; 2378 * ``` 2379 * 2380 * You can also retrieve this information during runtime via the 2381 * {@link $injector#modules `$injector.modules`} property: 2382 * 2383 * ```js 2384 * var version = $injector.modules['myModule'].info().version; 2385 * ``` 2386 */ 2387 info: function(value) { 2388 if (isDefined(value)) { 2389 if (!isObject(value)) throw ngMinErr('aobj', 'Argument \'{0}\' must be an object', 'value'); 2390 info = value; 2391 return this; 2392 } 2393 return info; 2394 }, 2395 2396 /** 2397 * @ngdoc property 2398 * @name angular.Module#requires 2399 * @module ng 2400 * 2401 * @description 2402 * Holds the list of modules which the injector will load before the current module is 2403 * loaded. 2404 */ 2405 requires: requires, 2406 2407 /** 2408 * @ngdoc property 2409 * @name angular.Module#name 2410 * @module ng 2411 * 2412 * @description 2413 * Name of the module. 2414 */ 2415 name: name, 2416 2417 2418 /** 2419 * @ngdoc method 2420 * @name angular.Module#provider 2421 * @module ng 2422 * @param {string} name service name 2423 * @param {Function} providerType Construction function for creating new instance of the 2424 * service. 2425 * @description 2426 * See {@link auto.$provide#provider $provide.provider()}. 2427 */ 2428 provider: invokeLaterAndSetModuleName('$provide', 'provider'), 2429 2430 /** 2431 * @ngdoc method 2432 * @name angular.Module#factory 2433 * @module ng 2434 * @param {string} name service name 2435 * @param {Function} providerFunction Function for creating new instance of the service. 2436 * @description 2437 * See {@link auto.$provide#factory $provide.factory()}. 2438 */ 2439 factory: invokeLaterAndSetModuleName('$provide', 'factory'), 2440 2441 /** 2442 * @ngdoc method 2443 * @name angular.Module#service 2444 * @module ng 2445 * @param {string} name service name 2446 * @param {Function} constructor A constructor function that will be instantiated. 2447 * @description 2448 * See {@link auto.$provide#service $provide.service()}. 2449 */ 2450 service: invokeLaterAndSetModuleName('$provide', 'service'), 2451 2452 /** 2453 * @ngdoc method 2454 * @name angular.Module#value 2455 * @module ng 2456 * @param {string} name service name 2457 * @param {*} object Service instance object. 2458 * @description 2459 * See {@link auto.$provide#value $provide.value()}. 2460 */ 2461 value: invokeLater('$provide', 'value'), 2462 2463 /** 2464 * @ngdoc method 2465 * @name angular.Module#constant 2466 * @module ng 2467 * @param {string} name constant name 2468 * @param {*} object Constant value. 2469 * @description 2470 * Because the constants are fixed, they get applied before other provide methods. 2471 * See {@link auto.$provide#constant $provide.constant()}. 2472 */ 2473 constant: invokeLater('$provide', 'constant', 'unshift'), 2474 2475 /** 2476 * @ngdoc method 2477 * @name angular.Module#decorator 2478 * @module ng 2479 * @param {string} name The name of the service to decorate. 2480 * @param {Function} decorFn This function will be invoked when the service needs to be 2481 * instantiated and should return the decorated service instance. 2482 * @description 2483 * See {@link auto.$provide#decorator $provide.decorator()}. 2484 */ 2485 decorator: invokeLaterAndSetModuleName('$provide', 'decorator', configBlocks), 2486 2487 /** 2488 * @ngdoc method 2489 * @name angular.Module#animation 2490 * @module ng 2491 * @param {string} name animation name 2492 * @param {Function} animationFactory Factory function for creating new instance of an 2493 * animation. 2494 * @description 2495 * 2496 * **NOTE**: animations take effect only if the **ngAnimate** module is loaded. 2497 * 2498 * 2499 * Defines an animation hook that can be later used with 2500 * {@link $animate $animate} service and directives that use this service. 2501 * 2502 * ```js 2503 * module.animation('.animation-name', function($inject1, $inject2) { 2504 * return { 2505 * eventName : function(element, done) { 2506 * //code to run the animation 2507 * //once complete, then run done() 2508 * return function cancellationFunction(element) { 2509 * //code to cancel the animation 2510 * } 2511 * } 2512 * } 2513 * }) 2514 * ``` 2515 * 2516 * See {@link ng.$animateProvider#register $animateProvider.register()} and 2517 * {@link ngAnimate ngAnimate module} for more information. 2518 */ 2519 animation: invokeLaterAndSetModuleName('$animateProvider', 'register'), 2520 2521 /** 2522 * @ngdoc method 2523 * @name angular.Module#filter 2524 * @module ng 2525 * @param {string} name Filter name - this must be a valid AngularJS expression identifier 2526 * @param {Function} filterFactory Factory function for creating new instance of filter. 2527 * @description 2528 * See {@link ng.$filterProvider#register $filterProvider.register()}. 2529 * 2530 * <div class="alert alert-warning"> 2531 * **Note:** Filter names must be valid AngularJS {@link expression} identifiers, such as `uppercase` or `orderBy`. 2532 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 2533 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 2534 * (`myapp_subsection_filterx`). 2535 * </div> 2536 */ 2537 filter: invokeLaterAndSetModuleName('$filterProvider', 'register'), 2538 2539 /** 2540 * @ngdoc method 2541 * @name angular.Module#controller 2542 * @module ng 2543 * @param {string|Object} name Controller name, or an object map of controllers where the 2544 * keys are the names and the values are the constructors. 2545 * @param {Function} constructor Controller constructor function. 2546 * @description 2547 * See {@link ng.$controllerProvider#register $controllerProvider.register()}. 2548 */ 2549 controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'), 2550 2551 /** 2552 * @ngdoc method 2553 * @name angular.Module#directive 2554 * @module ng 2555 * @param {string|Object} name Directive name, or an object map of directives where the 2556 * keys are the names and the values are the factories. 2557 * @param {Function} directiveFactory Factory function for creating new instance of 2558 * directives. 2559 * @description 2560 * See {@link ng.$compileProvider#directive $compileProvider.directive()}. 2561 */ 2562 directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'), 2563 2564 /** 2565 * @ngdoc method 2566 * @name angular.Module#component 2567 * @module ng 2568 * @param {string|Object} name Name of the component in camelCase (i.e. `myComp` which will match `<my-comp>`), 2569 * or an object map of components where the keys are the names and the values are the component definition objects. 2570 * @param {Object} options Component definition object (a simplified 2571 * {@link ng.$compile#directive-definition-object directive definition object}) 2572 * 2573 * @description 2574 * See {@link ng.$compileProvider#component $compileProvider.component()}. 2575 */ 2576 component: invokeLaterAndSetModuleName('$compileProvider', 'component'), 2577 2578 /** 2579 * @ngdoc method 2580 * @name angular.Module#config 2581 * @module ng 2582 * @param {Function} configFn Execute this function on module load. Useful for service 2583 * configuration. 2584 * @description 2585 * Use this method to configure services by injecting their 2586 * {@link angular.Module#provider `providers`}, e.g. for adding routes to the 2587 * {@link ngRoute.$routeProvider $routeProvider}. 2588 * 2589 * Note that you can only inject {@link angular.Module#provider `providers`} and 2590 * {@link angular.Module#constant `constants`} into this function. 2591 * 2592 * For more about how to configure services, see 2593 * {@link providers#provider-recipe Provider Recipe}. 2594 */ 2595 config: config, 2596 2597 /** 2598 * @ngdoc method 2599 * @name angular.Module#run 2600 * @module ng 2601 * @param {Function} initializationFn Execute this function after injector creation. 2602 * Useful for application initialization. 2603 * @description 2604 * Use this method to register work which should be performed when the injector is done 2605 * loading all modules. 2606 */ 2607 run: function(block) { 2608 runBlocks.push(block); 2609 return this; 2610 } 2611 }; 2612 2613 if (configFn) { 2614 config(configFn); 2615 } 2616 2617 return moduleInstance; 2618 2619 /** 2620 * @param {string} provider 2621 * @param {string} method 2622 * @param {String=} insertMethod 2623 * @returns {angular.Module} 2624 */ 2625 function invokeLater(provider, method, insertMethod, queue) { 2626 if (!queue) queue = invokeQueue; 2627 return function() { 2628 queue[insertMethod || 'push']([provider, method, arguments]); 2629 return moduleInstance; 2630 }; 2631 } 2632 2633 /** 2634 * @param {string} provider 2635 * @param {string} method 2636 * @returns {angular.Module} 2637 */ 2638 function invokeLaterAndSetModuleName(provider, method, queue) { 2639 if (!queue) queue = invokeQueue; 2640 return function(recipeName, factoryFunction) { 2641 if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name; 2642 queue.push([provider, method, arguments]); 2643 return moduleInstance; 2644 }; 2645 } 2646 }); 2647 }; 2648 }); 2649 2650} 2651 2652/* global shallowCopy: true */ 2653 2654/** 2655 * Creates a shallow copy of an object, an array or a primitive. 2656 * 2657 * Assumes that there are no proto properties for objects. 2658 */ 2659function shallowCopy(src, dst) { 2660 if (isArray(src)) { 2661 dst = dst || []; 2662 2663 for (var i = 0, ii = src.length; i < ii; i++) { 2664 dst[i] = src[i]; 2665 } 2666 } else if (isObject(src)) { 2667 dst = dst || {}; 2668 2669 for (var key in src) { 2670 if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) { 2671 dst[key] = src[key]; 2672 } 2673 } 2674 } 2675 2676 return dst || src; 2677} 2678 2679/* exported toDebugString */ 2680 2681function serializeObject(obj, maxDepth) { 2682 var seen = []; 2683 2684 // There is no direct way to stringify object until reaching a specific depth 2685 // and a very deep object can cause a performance issue, so we copy the object 2686 // based on this specific depth and then stringify it. 2687 if (isValidObjectMaxDepth(maxDepth)) { 2688 // This file is also included in `angular-loader`, so `copy()` might not always be available in 2689 // the closure. Therefore, it is lazily retrieved as `angular.copy()` when needed. 2690 obj = angular.copy(obj, null, maxDepth); 2691 } 2692 return JSON.stringify(obj, function(key, val) { 2693 val = toJsonReplacer(key, val); 2694 if (isObject(val)) { 2695 2696 if (seen.indexOf(val) >= 0) return '...'; 2697 2698 seen.push(val); 2699 } 2700 return val; 2701 }); 2702} 2703 2704function toDebugString(obj, maxDepth) { 2705 if (typeof obj === 'function') { 2706 return obj.toString().replace(/ \{[\s\S]*$/, ''); 2707 } else if (isUndefined(obj)) { 2708 return 'undefined'; 2709 } else if (typeof obj !== 'string') { 2710 return serializeObject(obj, maxDepth); 2711 } 2712 return obj; 2713} 2714 2715/* global angularModule: true, 2716 version: true, 2717 2718 $CompileProvider, 2719 2720 htmlAnchorDirective, 2721 inputDirective, 2722 hiddenInputBrowserCacheDirective, 2723 formDirective, 2724 scriptDirective, 2725 selectDirective, 2726 optionDirective, 2727 ngBindDirective, 2728 ngBindHtmlDirective, 2729 ngBindTemplateDirective, 2730 ngClassDirective, 2731 ngClassEvenDirective, 2732 ngClassOddDirective, 2733 ngCloakDirective, 2734 ngControllerDirective, 2735 ngFormDirective, 2736 ngHideDirective, 2737 ngIfDirective, 2738 ngIncludeDirective, 2739 ngIncludeFillContentDirective, 2740 ngInitDirective, 2741 ngNonBindableDirective, 2742 ngPluralizeDirective, 2743 ngRefDirective, 2744 ngRepeatDirective, 2745 ngShowDirective, 2746 ngStyleDirective, 2747 ngSwitchDirective, 2748 ngSwitchWhenDirective, 2749 ngSwitchDefaultDirective, 2750 ngOptionsDirective, 2751 ngTranscludeDirective, 2752 ngModelDirective, 2753 ngListDirective, 2754 ngChangeDirective, 2755 patternDirective, 2756 patternDirective, 2757 requiredDirective, 2758 requiredDirective, 2759 minlengthDirective, 2760 minlengthDirective, 2761 maxlengthDirective, 2762 maxlengthDirective, 2763 ngValueDirective, 2764 ngModelOptionsDirective, 2765 ngAttributeAliasDirectives, 2766 ngEventDirectives, 2767 2768 $AnchorScrollProvider, 2769 $AnimateProvider, 2770 $CoreAnimateCssProvider, 2771 $$CoreAnimateJsProvider, 2772 $$CoreAnimateQueueProvider, 2773 $$AnimateRunnerFactoryProvider, 2774 $$AnimateAsyncRunFactoryProvider, 2775 $BrowserProvider, 2776 $CacheFactoryProvider, 2777 $ControllerProvider, 2778 $DateProvider, 2779 $DocumentProvider, 2780 $$IsDocumentHiddenProvider, 2781 $ExceptionHandlerProvider, 2782 $FilterProvider, 2783 $$ForceReflowProvider, 2784 $InterpolateProvider, 2785 $$IntervalFactoryProvider, 2786 $IntervalProvider, 2787 $HttpProvider, 2788 $HttpParamSerializerProvider, 2789 $HttpParamSerializerJQLikeProvider, 2790 $HttpBackendProvider, 2791 $xhrFactoryProvider, 2792 $jsonpCallbacksProvider, 2793 $LocationProvider, 2794 $LogProvider, 2795 $$MapProvider, 2796 $ParseProvider, 2797 $RootScopeProvider, 2798 $QProvider, 2799 $$QProvider, 2800 $$SanitizeUriProvider, 2801 $SceProvider, 2802 $SceDelegateProvider, 2803 $SnifferProvider, 2804 $$TaskTrackerFactoryProvider, 2805 $TemplateCacheProvider, 2806 $TemplateRequestProvider, 2807 $$TestabilityProvider, 2808 $TimeoutProvider, 2809 $$RAFProvider, 2810 $WindowProvider, 2811 $$jqLiteProvider, 2812 $$CookieReaderProvider 2813*/ 2814 2815 2816/** 2817 * @ngdoc object 2818 * @name angular.version 2819 * @module ng 2820 * @description 2821 * An object that contains information about the current AngularJS version. 2822 * 2823 * This object has the following properties: 2824 * 2825 * - `full` â `{string}` â Full version string, such as "0.9.18". 2826 * - `major` â `{number}` â Major version number, such as "0". 2827 * - `minor` â `{number}` â Minor version number, such as "9". 2828 * - `dot` â `{number}` â Dot version number, such as "18". 2829 * - `codeName` â `{string}` â Code name of the release, such as "jiggling-armfat". 2830 */ 2831var version = { 2832 // These placeholder strings will be replaced by grunt's `build` task. 2833 // They need to be double- or single-quoted. 2834 full: '1.8.3', 2835 major: 1, 2836 minor: 8, 2837 dot: 3, 2838 codeName: 'ultimate-farewell' 2839}; 2840 2841 2842function publishExternalAPI(angular) { 2843 extend(angular, { 2844 'errorHandlingConfig': errorHandlingConfig, 2845 'bootstrap': bootstrap, 2846 'copy': copy, 2847 'extend': extend, 2848 'merge': merge, 2849 'equals': equals, 2850 'element': jqLite, 2851 'forEach': forEach, 2852 'injector': createInjector, 2853 'noop': noop, 2854 'bind': bind, 2855 'toJson': toJson, 2856 'fromJson': fromJson, 2857 'identity': identity, 2858 'isUndefined': isUndefined, 2859 'isDefined': isDefined, 2860 'isString': isString, 2861 'isFunction': isFunction, 2862 'isObject': isObject, 2863 'isNumber': isNumber, 2864 'isElement': isElement, 2865 'isArray': isArray, 2866 'version': version, 2867 'isDate': isDate, 2868 'callbacks': {$$counter: 0}, 2869 'getTestability': getTestability, 2870 'reloadWithDebugInfo': reloadWithDebugInfo, 2871 'UNSAFE_restoreLegacyJqLiteXHTMLReplacement': UNSAFE_restoreLegacyJqLiteXHTMLReplacement, 2872 '$$minErr': minErr, 2873 '$$csp': csp, 2874 '$$encodeUriSegment': encodeUriSegment, 2875 '$$encodeUriQuery': encodeUriQuery, 2876 '$$lowercase': lowercase, 2877 '$$stringify': stringify, 2878 '$$uppercase': uppercase 2879 }); 2880 2881 angularModule = setupModuleLoader(window); 2882 2883 angularModule('ng', ['ngLocale'], ['$provide', 2884 function ngModule($provide) { 2885 // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it. 2886 $provide.provider({ 2887 $$sanitizeUri: $$SanitizeUriProvider 2888 }); 2889 $provide.provider('$compile', $CompileProvider). 2890 directive({ 2891 a: htmlAnchorDirective, 2892 input: inputDirective, 2893 textarea: inputDirective, 2894 form: formDirective, 2895 script: scriptDirective, 2896 select: selectDirective, 2897 option: optionDirective, 2898 ngBind: ngBindDirective, 2899 ngBindHtml: ngBindHtmlDirective, 2900 ngBindTemplate: ngBindTemplateDirective, 2901 ngClass: ngClassDirective, 2902 ngClassEven: ngClassEvenDirective, 2903 ngClassOdd: ngClassOddDirective, 2904 ngCloak: ngCloakDirective, 2905 ngController: ngControllerDirective, 2906 ngForm: ngFormDirective, 2907 ngHide: ngHideDirective, 2908 ngIf: ngIfDirective, 2909 ngInclude: ngIncludeDirective, 2910 ngInit: ngInitDirective, 2911 ngNonBindable: ngNonBindableDirective, 2912 ngPluralize: ngPluralizeDirective, 2913 ngRef: ngRefDirective, 2914 ngRepeat: ngRepeatDirective, 2915 ngShow: ngShowDirective, 2916 ngStyle: ngStyleDirective, 2917 ngSwitch: ngSwitchDirective, 2918 ngSwitchWhen: ngSwitchWhenDirective, 2919 ngSwitchDefault: ngSwitchDefaultDirective, 2920 ngOptions: ngOptionsDirective, 2921 ngTransclude: ngTranscludeDirective, 2922 ngModel: ngModelDirective, 2923 ngList: ngListDirective, 2924 ngChange: ngChangeDirective, 2925 pattern: patternDirective, 2926 ngPattern: patternDirective, 2927 required: requiredDirective, 2928 ngRequired: requiredDirective, 2929 minlength: minlengthDirective, 2930 ngMinlength: minlengthDirective, 2931 maxlength: maxlengthDirective, 2932 ngMaxlength: maxlengthDirective, 2933 ngValue: ngValueDirective, 2934 ngModelOptions: ngModelOptionsDirective 2935 }). 2936 directive({ 2937 ngInclude: ngIncludeFillContentDirective, 2938 input: hiddenInputBrowserCacheDirective 2939 }). 2940 directive(ngAttributeAliasDirectives). 2941 directive(ngEventDirectives); 2942 $provide.provider({ 2943 $anchorScroll: $AnchorScrollProvider, 2944 $animate: $AnimateProvider, 2945 $animateCss: $CoreAnimateCssProvider, 2946 $$animateJs: $$CoreAnimateJsProvider, 2947 $$animateQueue: $$CoreAnimateQueueProvider, 2948 $$AnimateRunner: $$AnimateRunnerFactoryProvider, 2949 $$animateAsyncRun: $$AnimateAsyncRunFactoryProvider, 2950 $browser: $BrowserProvider, 2951 $cacheFactory: $CacheFactoryProvider, 2952 $controller: $ControllerProvider, 2953 $document: $DocumentProvider, 2954 $$isDocumentHidden: $$IsDocumentHiddenProvider, 2955 $exceptionHandler: $ExceptionHandlerProvider, 2956 $filter: $FilterProvider, 2957 $$forceReflow: $$ForceReflowProvider, 2958 $interpolate: $InterpolateProvider, 2959 $interval: $IntervalProvider, 2960 $$intervalFactory: $$IntervalFactoryProvider, 2961 $http: $HttpProvider, 2962 $httpParamSerializer: $HttpParamSerializerProvider, 2963 $httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider, 2964 $httpBackend: $HttpBackendProvider, 2965 $xhrFactory: $xhrFactoryProvider, 2966 $jsonpCallbacks: $jsonpCallbacksProvider, 2967 $location: $LocationProvider, 2968 $log: $LogProvider, 2969 $parse: $ParseProvider, 2970 $rootScope: $RootScopeProvider, 2971 $q: $QProvider, 2972 $$q: $$QProvider, 2973 $sce: $SceProvider, 2974 $sceDelegate: $SceDelegateProvider, 2975 $sniffer: $SnifferProvider, 2976 $$taskTrackerFactory: $$TaskTrackerFactoryProvider, 2977 $templateCache: $TemplateCacheProvider, 2978 $templateRequest: $TemplateRequestProvider, 2979 $$testability: $$TestabilityProvider, 2980 $timeout: $TimeoutProvider, 2981 $window: $WindowProvider, 2982 $$rAF: $$RAFProvider, 2983 $$jqLite: $$jqLiteProvider, 2984 $$Map: $$MapProvider, 2985 $$cookieReader: $$CookieReaderProvider 2986 }); 2987 } 2988 ]) 2989 .info({ angularVersion: '1.8.3' }); 2990} 2991 2992/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 2993 * Any commits to this file should be reviewed with security in mind. * 2994 * Changes to this file can potentially create security vulnerabilities. * 2995 * An approval from 2 Core members with history of modifying * 2996 * this file is required. * 2997 * * 2998 * Does the change somehow allow for arbitrary javascript to be executed? * 2999 * Or allows for someone to change the prototype of built-in objects? * 3000 * Or gives undesired access to variables likes document or window? * 3001 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 3002 3003/* global 3004 JQLitePrototype: true, 3005 BOOLEAN_ATTR: true, 3006 ALIASED_ATTR: true 3007*/ 3008 3009////////////////////////////////// 3010//JQLite 3011////////////////////////////////// 3012 3013/** 3014 * @ngdoc function 3015 * @name angular.element 3016 * @module ng 3017 * @kind function 3018 * 3019 * @description 3020 * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element. 3021 * 3022 * If jQuery is available, `angular.element` is an alias for the 3023 * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element` 3024 * delegates to AngularJS's built-in subset of jQuery, called "jQuery lite" or **jqLite**. 3025 * 3026 * jqLite is a tiny, API-compatible subset of jQuery that allows 3027 * AngularJS to manipulate the DOM in a cross-browser compatible way. jqLite implements only the most 3028 * commonly needed functionality with the goal of having a very small footprint. 3029 * 3030 * To use `jQuery`, simply ensure it is loaded before the `angular.js` file. You can also use the 3031 * {@link ngJq `ngJq`} directive to specify that jqlite should be used over jQuery, or to use a 3032 * specific version of jQuery if multiple versions exist on the page. 3033 * 3034 * <div class="alert alert-info">**Note:** All element references in AngularJS are always wrapped with jQuery or 3035 * jqLite (such as the element argument in a directive's compile / link function). They are never raw DOM references.</div> 3036 * 3037 * <div class="alert alert-warning">**Note:** Keep in mind that this function will not find elements 3038 * by tag name / CSS selector. For lookups by tag name, try instead `angular.element(document).find(...)` 3039 * or `$document.find()`, or use the standard DOM APIs, e.g. `document.querySelectorAll()`.</div> 3040 * 3041 * ## AngularJS's jqLite 3042 * jqLite provides only the following jQuery methods: 3043 * 3044 * - [`addClass()`](http://api.jquery.com/addClass/) - Does not support a function as first argument 3045 * - [`after()`](http://api.jquery.com/after/) 3046 * - [`append()`](http://api.jquery.com/append/) - Contrary to jQuery, this doesn't clone elements 3047 * so will not work correctly when invoked on a jqLite object containing more than one DOM node 3048 * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters 3049 * - [`bind()`](http://api.jquery.com/bind/) (_deprecated_, use [`on()`](http://api.jquery.com/on/)) - Does not support namespaces, selectors or eventData 3050 * - [`children()`](http://api.jquery.com/children/) - Does not support selectors 3051 * - [`clone()`](http://api.jquery.com/clone/) 3052 * - [`contents()`](http://api.jquery.com/contents/) 3053 * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`. 3054 * As a setter, does not convert numbers to strings or append 'px', and also does not have automatic property prefixing. 3055 * - [`data()`](http://api.jquery.com/data/) 3056 * - [`detach()`](http://api.jquery.com/detach/) 3057 * - [`empty()`](http://api.jquery.com/empty/) 3058 * - [`eq()`](http://api.jquery.com/eq/) 3059 * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name 3060 * - [`hasClass()`](http://api.jquery.com/hasClass/) 3061 * - [`html()`](http://api.jquery.com/html/) 3062 * - [`next()`](http://api.jquery.com/next/) - Does not support selectors 3063 * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData 3064 * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces, selectors or event object as parameter 3065 * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors 3066 * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors 3067 * - [`prepend()`](http://api.jquery.com/prepend/) 3068 * - [`prop()`](http://api.jquery.com/prop/) 3069 * - [`ready()`](http://api.jquery.com/ready/) (_deprecated_, use `angular.element(callback)` instead of `angular.element(document).ready(callback)`) 3070 * - [`remove()`](http://api.jquery.com/remove/) 3071 * - [`removeAttr()`](http://api.jquery.com/removeAttr/) - Does not support multiple attributes 3072 * - [`removeClass()`](http://api.jquery.com/removeClass/) - Does not support a function as first argument 3073 * - [`removeData()`](http://api.jquery.com/removeData/) 3074 * - [`replaceWith()`](http://api.jquery.com/replaceWith/) 3075 * - [`text()`](http://api.jquery.com/text/) 3076 * - [`toggleClass()`](http://api.jquery.com/toggleClass/) - Does not support a function as first argument 3077 * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers 3078 * - [`unbind()`](http://api.jquery.com/unbind/) (_deprecated_, use [`off()`](http://api.jquery.com/off/)) - Does not support namespaces or event object as parameter 3079 * - [`val()`](http://api.jquery.com/val/) 3080 * - [`wrap()`](http://api.jquery.com/wrap/) 3081 * 3082 * jqLite also provides a method restoring pre-1.8 insecure treatment of XHTML-like tags. 3083 * This legacy behavior turns input like `<div /><span />` to `<div></div><span></span>` 3084 * instead of `<div><span></span></div>` like version 1.8 & newer do. To restore it, invoke: 3085 * ```js 3086 * angular.UNSAFE_restoreLegacyJqLiteXHTMLReplacement(); 3087 * ``` 3088 * Note that this only patches jqLite. If you use jQuery 3.5.0 or newer, please read the 3089 * [jQuery 3.5 upgrade guide](https://jquery.com/upgrade-guide/3.5/) for more details 3090 * about the workarounds. 3091 * 3092 * ## jQuery/jqLite Extras 3093 * AngularJS also provides the following additional methods and events to both jQuery and jqLite: 3094 * 3095 * ### Events 3096 * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event 3097 * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM 3098 * element before it is removed. 3099 * 3100 * ### Methods 3101 * - `controller(name)` - retrieves the controller of the current element or its parent. By default 3102 * retrieves controller associated with the `ngController` directive. If `name` is provided as 3103 * camelCase directive name, then the controller for this directive will be retrieved (e.g. 3104 * `'ngModel'`). 3105 * - `injector()` - retrieves the injector of the current element or its parent. 3106 * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current 3107 * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to 3108 * be enabled. 3109 * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the 3110 * current element. This getter should be used only on elements that contain a directive which starts a new isolate 3111 * scope. Calling `scope()` on this element always returns the original non-isolate scope. 3112 * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled. 3113 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top 3114 * parent element is reached. 3115 * 3116 * @knownIssue You cannot spy on `angular.element` if you are using Jasmine version 1.x. See 3117 * https://github.com/angular/angular.js/issues/14251 for more information. 3118 * 3119 * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery. 3120 * @returns {Object} jQuery object. 3121 */ 3122 3123JQLite.expando = 'ng339'; 3124 3125var jqCache = JQLite.cache = {}, 3126 jqId = 1; 3127 3128/* 3129 * !!! This is an undocumented "private" function !!! 3130 */ 3131JQLite._data = function(node) { 3132 //jQuery always returns an object on cache miss 3133 return this.cache[node[this.expando]] || {}; 3134}; 3135 3136function jqNextId() { return ++jqId; } 3137 3138 3139var DASH_LOWERCASE_REGEXP = /-([a-z])/g; 3140var MS_HACK_REGEXP = /^-ms-/; 3141var MOUSE_EVENT_MAP = { mouseleave: 'mouseout', mouseenter: 'mouseover' }; 3142var jqLiteMinErr = minErr('jqLite'); 3143 3144/** 3145 * Converts kebab-case to camelCase. 3146 * There is also a special case for the ms prefix starting with a lowercase letter. 3147 * @param name Name to normalize 3148 */ 3149function cssKebabToCamel(name) { 3150 return kebabToCamel(name.replace(MS_HACK_REGEXP, 'ms-')); 3151} 3152 3153function fnCamelCaseReplace(all, letter) { 3154 return letter.toUpperCase(); 3155} 3156 3157/** 3158 * Converts kebab-case to camelCase. 3159 * @param name Name to normalize 3160 */ 3161function kebabToCamel(name) { 3162 return name 3163 .replace(DASH_LOWERCASE_REGEXP, fnCamelCaseReplace); 3164} 3165 3166var SINGLE_TAG_REGEXP = /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/; 3167var HTML_REGEXP = /<|&#?\w+;/; 3168var TAG_NAME_REGEXP = /<([\w:-]+)/; 3169var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi; 3170 3171// Table parts need to be wrapped with `<table>` or they're 3172// stripped to their contents when put in a div. 3173// XHTML parsers do not magically insert elements in the 3174// same way that tag soup parsers do, so we cannot shorten 3175// this by omitting <tbody> or other required elements. 3176var wrapMap = { 3177 thead: ['table'], 3178 col: ['colgroup', 'table'], 3179 tr: ['tbody', 'table'], 3180 td: ['tr', 'tbody', 'table'] 3181}; 3182 3183wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; 3184wrapMap.th = wrapMap.td; 3185 3186// Support: IE <10 only 3187// IE 9 requires an option wrapper & it needs to have the whole table structure 3188// set up in advance; assigning `"<td></td>"` to `tr.innerHTML` doesn't work, etc. 3189var wrapMapIE9 = { 3190 option: [1, '<select multiple="multiple">', '</select>'], 3191 _default: [0, '', ''] 3192}; 3193 3194for (var key in wrapMap) { 3195 var wrapMapValueClosing = wrapMap[key]; 3196 var wrapMapValue = wrapMapValueClosing.slice().reverse(); 3197 wrapMapIE9[key] = [wrapMapValue.length, '<' + wrapMapValue.join('><') + '>', '</' + wrapMapValueClosing.join('></') + '>']; 3198} 3199 3200wrapMapIE9.optgroup = wrapMapIE9.option; 3201 3202function jqLiteIsTextNode(html) { 3203 return !HTML_REGEXP.test(html); 3204} 3205 3206function jqLiteAcceptsData(node) { 3207 // The window object can accept data but has no nodeType 3208 // Otherwise we are only interested in elements (1) and documents (9) 3209 var nodeType = node.nodeType; 3210 return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT; 3211} 3212 3213function jqLiteHasData(node) { 3214 for (var key in jqCache[node.ng339]) { 3215 return true; 3216 } 3217 return false; 3218} 3219 3220function jqLiteBuildFragment(html, context) { 3221 var tmp, tag, wrap, finalHtml, 3222 fragment = context.createDocumentFragment(), 3223 nodes = [], i; 3224 3225 if (jqLiteIsTextNode(html)) { 3226 // Convert non-html into a text node 3227 nodes.push(context.createTextNode(html)); 3228 } else { 3229 // Convert html into DOM nodes 3230 tmp = fragment.appendChild(context.createElement('div')); 3231 tag = (TAG_NAME_REGEXP.exec(html) || ['', ''])[1].toLowerCase(); 3232 finalHtml = JQLite.legacyXHTMLReplacement ? 3233 html.replace(XHTML_TAG_REGEXP, '<$1></$2>') : 3234 html; 3235 3236 if (msie < 10) { 3237 wrap = wrapMapIE9[tag] || wrapMapIE9._default; 3238 tmp.innerHTML = wrap[1] + finalHtml + wrap[2]; 3239 3240 // Descend through wrappers to the right content 3241 i = wrap[0]; 3242 while (i--) { 3243 tmp = tmp.firstChild; 3244 } 3245 } else { 3246 wrap = wrapMap[tag] || []; 3247 3248 // Create wrappers & descend into them 3249 i = wrap.length; 3250 while (--i > -1) { 3251 tmp.appendChild(window.document.createElement(wrap[i])); 3252 tmp = tmp.firstChild; 3253 } 3254 3255 tmp.innerHTML = finalHtml; 3256 } 3257 3258 nodes = concat(nodes, tmp.childNodes); 3259 3260 tmp = fragment.firstChild; 3261 tmp.textContent = ''; 3262 } 3263 3264 // Remove wrapper from fragment 3265 fragment.textContent = ''; 3266 fragment.innerHTML = ''; // Clear inner HTML 3267 forEach(nodes, function(node) { 3268 fragment.appendChild(node); 3269 }); 3270 3271 return fragment; 3272} 3273 3274function jqLiteParseHTML(html, context) { 3275 context = context || window.document; 3276 var parsed; 3277 3278 if ((parsed = SINGLE_TAG_REGEXP.exec(html))) { 3279 return [context.createElement(parsed[1])]; 3280 } 3281 3282 if ((parsed = jqLiteBuildFragment(html, context))) { 3283 return parsed.childNodes; 3284 } 3285 3286 return []; 3287} 3288 3289function jqLiteWrapNode(node, wrapper) { 3290 var parent = node.parentNode; 3291 3292 if (parent) { 3293 parent.replaceChild(wrapper, node); 3294 } 3295 3296 wrapper.appendChild(node); 3297} 3298 3299 3300// IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259. 3301var jqLiteContains = window.Node.prototype.contains || /** @this */ function(arg) { 3302 // eslint-disable-next-line no-bitwise 3303 return !!(this.compareDocumentPosition(arg) & 16); 3304}; 3305 3306///////////////////////////////////////////// 3307function JQLite(element) { 3308 if (element instanceof JQLite) { 3309 return element; 3310 } 3311 3312 var argIsString; 3313 3314 if (isString(element)) { 3315 element = trim(element); 3316 argIsString = true; 3317 } 3318 if (!(this instanceof JQLite)) { 3319 if (argIsString && element.charAt(0) !== '<') { 3320 throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element'); 3321 } 3322 return new JQLite(element); 3323 } 3324 3325 if (argIsString) { 3326 jqLiteAddNodes(this, jqLiteParseHTML(element)); 3327 } else if (isFunction(element)) { 3328 jqLiteReady(element); 3329 } else { 3330 jqLiteAddNodes(this, element); 3331 } 3332} 3333 3334function jqLiteClone(element) { 3335 return element.cloneNode(true); 3336} 3337 3338function jqLiteDealoc(element, onlyDescendants) { 3339 if (!onlyDescendants && jqLiteAcceptsData(element)) jqLite.cleanData([element]); 3340 3341 if (element.querySelectorAll) { 3342 jqLite.cleanData(element.querySelectorAll('*')); 3343 } 3344} 3345 3346function isEmptyObject(obj) { 3347 var name; 3348 3349 for (name in obj) { 3350 return false; 3351 } 3352 return true; 3353} 3354 3355function removeIfEmptyData(element) { 3356 var expandoId = element.ng339; 3357 var expandoStore = expandoId && jqCache[expandoId]; 3358 3359 var events = expandoStore && expandoStore.events; 3360 var data = expandoStore && expandoStore.data; 3361 3362 if ((!data || isEmptyObject(data)) && (!events || isEmptyObject(events))) { 3363 delete jqCache[expandoId]; 3364 element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it 3365 } 3366} 3367 3368function jqLiteOff(element, type, fn, unsupported) { 3369 if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument'); 3370 3371 var expandoStore = jqLiteExpandoStore(element); 3372 var events = expandoStore && expandoStore.events; 3373 var handle = expandoStore && expandoStore.handle; 3374 3375 if (!handle) return; //no listeners registered 3376 3377 if (!type) { 3378 for (type in events) { 3379 if (type !== '$destroy') { 3380 element.removeEventListener(type, handle); 3381 } 3382 delete events[type]; 3383 } 3384 } else { 3385 3386 var removeHandler = function(type) { 3387 var listenerFns = events[type]; 3388 if (isDefined(fn)) { 3389 arrayRemove(listenerFns || [], fn); 3390 } 3391 if (!(isDefined(fn) && listenerFns && listenerFns.length > 0)) { 3392 element.removeEventListener(type, handle); 3393 delete events[type]; 3394 } 3395 }; 3396 3397 forEach(type.split(' '), function(type) { 3398 removeHandler(type); 3399 if (MOUSE_EVENT_MAP[type]) { 3400 removeHandler(MOUSE_EVENT_MAP[type]); 3401 } 3402 }); 3403 } 3404 3405 removeIfEmptyData(element); 3406} 3407 3408function jqLiteRemoveData(element, name) { 3409 var expandoId = element.ng339; 3410 var expandoStore = expandoId && jqCache[expandoId]; 3411 3412 if (expandoStore) { 3413 if (name) { 3414 delete expandoStore.data[name]; 3415 } else { 3416 expandoStore.data = {}; 3417 } 3418 3419 removeIfEmptyData(element); 3420 } 3421} 3422 3423 3424function jqLiteExpandoStore(element, createIfNecessary) { 3425 var expandoId = element.ng339, 3426 expandoStore = expandoId && jqCache[expandoId]; 3427 3428 if (createIfNecessary && !expandoStore) { 3429 element.ng339 = expandoId = jqNextId(); 3430 expandoStore = jqCache[expandoId] = {events: {}, data: {}, handle: undefined}; 3431 } 3432 3433 return expandoStore; 3434} 3435 3436 3437function jqLiteData(element, key, value) { 3438 if (jqLiteAcceptsData(element)) { 3439 var prop; 3440 3441 var isSimpleSetter = isDefined(value); 3442 var isSimpleGetter = !isSimpleSetter && key && !isObject(key); 3443 var massGetter = !key; 3444 var expandoStore = jqLiteExpandoStore(element, !isSimpleGetter); 3445 var data = expandoStore && expandoStore.data; 3446 3447 if (isSimpleSetter) { // data('key', value) 3448 data[kebabToCamel(key)] = value; 3449 } else { 3450 if (massGetter) { // data() 3451 return data; 3452 } else { 3453 if (isSimpleGetter) { // data('key') 3454 // don't force creation of expandoStore if it doesn't exist yet 3455 return data && data[kebabToCamel(key)]; 3456 } else { // mass-setter: data({key1: val1, key2: val2}) 3457 for (prop in key) { 3458 data[kebabToCamel(prop)] = key[prop]; 3459 } 3460 } 3461 } 3462 } 3463 } 3464} 3465 3466function jqLiteHasClass(element, selector) { 3467 if (!element.getAttribute) return false; 3468 return ((' ' + (element.getAttribute('class') || '') + ' ').replace(/[\n\t]/g, ' '). 3469 indexOf(' ' + selector + ' ') > -1); 3470} 3471 3472function jqLiteRemoveClass(element, cssClasses) { 3473 if (cssClasses && element.setAttribute) { 3474 var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') 3475 .replace(/[\n\t]/g, ' '); 3476 var newClasses = existingClasses; 3477 3478 forEach(cssClasses.split(' '), function(cssClass) { 3479 cssClass = trim(cssClass); 3480 newClasses = newClasses.replace(' ' + cssClass + ' ', ' '); 3481 }); 3482 3483 if (newClasses !== existingClasses) { 3484 element.setAttribute('class', trim(newClasses)); 3485 } 3486 } 3487} 3488 3489function jqLiteAddClass(element, cssClasses) { 3490 if (cssClasses && element.setAttribute) { 3491 var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') 3492 .replace(/[\n\t]/g, ' '); 3493 var newClasses = existingClasses; 3494 3495 forEach(cssClasses.split(' '), function(cssClass) { 3496 cssClass = trim(cssClass); 3497 if (newClasses.indexOf(' ' + cssClass + ' ') === -1) { 3498 newClasses += cssClass + ' '; 3499 } 3500 }); 3501 3502 if (newClasses !== existingClasses) { 3503 element.setAttribute('class', trim(newClasses)); 3504 } 3505 } 3506} 3507 3508 3509function jqLiteAddNodes(root, elements) { 3510 // THIS CODE IS VERY HOT. Don't make changes without benchmarking. 3511 3512 if (elements) { 3513 3514 // if a Node (the most common case) 3515 if (elements.nodeType) { 3516 root[root.length++] = elements; 3517 } else { 3518 var length = elements.length; 3519 3520 // if an Array or NodeList and not a Window 3521 if (typeof length === 'number' && elements.window !== elements) { 3522 if (length) { 3523 for (var i = 0; i < length; i++) { 3524 root[root.length++] = elements[i]; 3525 } 3526 } 3527 } else { 3528 root[root.length++] = elements; 3529 } 3530 } 3531 } 3532} 3533 3534 3535function jqLiteController(element, name) { 3536 return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller'); 3537} 3538 3539function jqLiteInheritedData(element, name, value) { 3540 // if element is the document object work with the html element instead 3541 // this makes $(document).scope() possible 3542 if (element.nodeType === NODE_TYPE_DOCUMENT) { 3543 element = element.documentElement; 3544 } 3545 var names = isArray(name) ? name : [name]; 3546 3547 while (element) { 3548 for (var i = 0, ii = names.length; i < ii; i++) { 3549 if (isDefined(value = jqLite.data(element, names[i]))) return value; 3550 } 3551 3552 // If dealing with a document fragment node with a host element, and no parent, use the host 3553 // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM 3554 // to lookup parent controllers. 3555 element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host); 3556 } 3557} 3558 3559function jqLiteEmpty(element) { 3560 jqLiteDealoc(element, true); 3561 while (element.firstChild) { 3562 element.removeChild(element.firstChild); 3563 } 3564} 3565 3566function jqLiteRemove(element, keepData) { 3567 if (!keepData) jqLiteDealoc(element); 3568 var parent = element.parentNode; 3569 if (parent) parent.removeChild(element); 3570} 3571 3572 3573function jqLiteDocumentLoaded(action, win) { 3574 win = win || window; 3575 if (win.document.readyState === 'complete') { 3576 // Force the action to be run async for consistent behavior 3577 // from the action's point of view 3578 // i.e. it will definitely not be in a $apply 3579 win.setTimeout(action); 3580 } else { 3581 // No need to unbind this handler as load is only ever called once 3582 jqLite(win).on('load', action); 3583 } 3584} 3585 3586function jqLiteReady(fn) { 3587 function trigger() { 3588 window.document.removeEventListener('DOMContentLoaded', trigger); 3589 window.removeEventListener('load', trigger); 3590 fn(); 3591 } 3592 3593 // check if document is already loaded 3594 if (window.document.readyState === 'complete') { 3595 window.setTimeout(fn); 3596 } else { 3597 // We can not use jqLite since we are not done loading and jQuery could be loaded later. 3598 3599 // Works for modern browsers and IE9 3600 window.document.addEventListener('DOMContentLoaded', trigger); 3601 3602 // Fallback to window.onload for others 3603 window.addEventListener('load', trigger); 3604 } 3605} 3606 3607////////////////////////////////////////// 3608// Functions which are declared directly. 3609////////////////////////////////////////// 3610var JQLitePrototype = JQLite.prototype = { 3611 ready: jqLiteReady, 3612 toString: function() { 3613 var value = []; 3614 forEach(this, function(e) { value.push('' + e);}); 3615 return '[' + value.join(', ') + ']'; 3616 }, 3617 3618 eq: function(index) { 3619 return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]); 3620 }, 3621 3622 length: 0, 3623 push: push, 3624 sort: [].sort, 3625 splice: [].splice 3626}; 3627 3628////////////////////////////////////////// 3629// Functions iterating getter/setters. 3630// these functions return self on setter and 3631// value on get. 3632////////////////////////////////////////// 3633var BOOLEAN_ATTR = {}; 3634forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) { 3635 BOOLEAN_ATTR[lowercase(value)] = value; 3636}); 3637var BOOLEAN_ELEMENTS = {}; 3638forEach('input,select,option,textarea,button,form,details'.split(','), function(value) { 3639 BOOLEAN_ELEMENTS[value] = true; 3640}); 3641var ALIASED_ATTR = { 3642 'ngMinlength': 'minlength', 3643 'ngMaxlength': 'maxlength', 3644 'ngMin': 'min', 3645 'ngMax': 'max', 3646 'ngPattern': 'pattern', 3647 'ngStep': 'step' 3648}; 3649 3650function getBooleanAttrName(element, name) { 3651 // check dom last since we will most likely fail on name 3652 var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()]; 3653 3654 // booleanAttr is here twice to minimize DOM access 3655 return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr; 3656} 3657 3658function getAliasedAttrName(name) { 3659 return ALIASED_ATTR[name]; 3660} 3661 3662forEach({ 3663 data: jqLiteData, 3664 removeData: jqLiteRemoveData, 3665 hasData: jqLiteHasData, 3666 cleanData: function jqLiteCleanData(nodes) { 3667 for (var i = 0, ii = nodes.length; i < ii; i++) { 3668 jqLiteRemoveData(nodes[i]); 3669 jqLiteOff(nodes[i]); 3670 } 3671 } 3672}, function(fn, name) { 3673 JQLite[name] = fn; 3674}); 3675 3676forEach({ 3677 data: jqLiteData, 3678 inheritedData: jqLiteInheritedData, 3679 3680 scope: function(element) { 3681 // Can't use jqLiteData here directly so we stay compatible with jQuery! 3682 return jqLite.data(element, '$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']); 3683 }, 3684 3685 isolateScope: function(element) { 3686 // Can't use jqLiteData here directly so we stay compatible with jQuery! 3687 return jqLite.data(element, '$isolateScope') || jqLite.data(element, '$isolateScopeNoTemplate'); 3688 }, 3689 3690 controller: jqLiteController, 3691 3692 injector: function(element) { 3693 return jqLiteInheritedData(element, '$injector'); 3694 }, 3695 3696 removeAttr: function(element, name) { 3697 element.removeAttribute(name); 3698 }, 3699 3700 hasClass: jqLiteHasClass, 3701 3702 css: function(element, name, value) { 3703 name = cssKebabToCamel(name); 3704 3705 if (isDefined(value)) { 3706 element.style[name] = value; 3707 } else { 3708 return element.style[name]; 3709 } 3710 }, 3711 3712 attr: function(element, name, value) { 3713 var ret; 3714 var nodeType = element.nodeType; 3715 if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT || 3716 !element.getAttribute) { 3717 return; 3718 } 3719 3720 var lowercasedName = lowercase(name); 3721 var isBooleanAttr = BOOLEAN_ATTR[lowercasedName]; 3722 3723 if (isDefined(value)) { 3724 // setter 3725 3726 if (value === null || (value === false && isBooleanAttr)) { 3727 element.removeAttribute(name); 3728 } else { 3729 element.setAttribute(name, isBooleanAttr ? lowercasedName : value); 3730 } 3731 } else { 3732 // getter 3733 3734 ret = element.getAttribute(name); 3735 3736 if (isBooleanAttr && ret !== null) { 3737 ret = lowercasedName; 3738 } 3739 // Normalize non-existing attributes to undefined (as jQuery). 3740 return ret === null ? undefined : ret; 3741 } 3742 }, 3743 3744 prop: function(element, name, value) { 3745 if (isDefined(value)) { 3746 element[name] = value; 3747 } else { 3748 return element[name]; 3749 } 3750 }, 3751 3752 text: (function() { 3753 getText.$dv = ''; 3754 return getText; 3755 3756 function getText(element, value) { 3757 if (isUndefined(value)) { 3758 var nodeType = element.nodeType; 3759 return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : ''; 3760 } 3761 element.textContent = value; 3762 } 3763 })(), 3764 3765 val: function(element, value) { 3766 if (isUndefined(value)) { 3767 if (element.multiple && nodeName_(element) === 'select') { 3768 var result = []; 3769 forEach(element.options, function(option) { 3770 if (option.selected) { 3771 result.push(option.value || option.text); 3772 } 3773 }); 3774 return result; 3775 } 3776 return element.value; 3777 } 3778 element.value = value; 3779 }, 3780 3781 html: function(element, value) { 3782 if (isUndefined(value)) { 3783 return element.innerHTML; 3784 } 3785 jqLiteDealoc(element, true); 3786 element.innerHTML = value; 3787 }, 3788 3789 empty: jqLiteEmpty 3790}, function(fn, name) { 3791 /** 3792 * Properties: writes return selection, reads return first value 3793 */ 3794 JQLite.prototype[name] = function(arg1, arg2) { 3795 var i, key; 3796 var nodeCount = this.length; 3797 3798 // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it 3799 // in a way that survives minification. 3800 // jqLiteEmpty takes no arguments but is a setter. 3801 if (fn !== jqLiteEmpty && 3802 (isUndefined((fn.length === 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) { 3803 if (isObject(arg1)) { 3804 3805 // we are a write, but the object properties are the key/values 3806 for (i = 0; i < nodeCount; i++) { 3807 if (fn === jqLiteData) { 3808 // data() takes the whole object in jQuery 3809 fn(this[i], arg1); 3810 } else { 3811 for (key in arg1) { 3812 fn(this[i], key, arg1[key]); 3813 } 3814 } 3815 } 3816 // return self for chaining 3817 return this; 3818 } else { 3819 // we are a read, so read the first child. 3820 // TODO: do we still need this? 3821 var value = fn.$dv; 3822 // Only if we have $dv do we iterate over all, otherwise it is just the first element. 3823 var jj = (isUndefined(value)) ? Math.min(nodeCount, 1) : nodeCount; 3824 for (var j = 0; j < jj; j++) { 3825 var nodeValue = fn(this[j], arg1, arg2); 3826 value = value ? value + nodeValue : nodeValue; 3827 } 3828 return value; 3829 } 3830 } else { 3831 // we are a write, so apply to all children 3832 for (i = 0; i < nodeCount; i++) { 3833 fn(this[i], arg1, arg2); 3834 } 3835 // return self for chaining 3836 return this; 3837 } 3838 }; 3839}); 3840 3841function createEventHandler(element, events) { 3842 var eventHandler = function(event, type) { 3843 // jQuery specific api 3844 event.isDefaultPrevented = function() { 3845 return event.defaultPrevented; 3846 }; 3847 3848 var eventFns = events[type || event.type]; 3849 var eventFnsLength = eventFns ? eventFns.length : 0; 3850 3851 if (!eventFnsLength) return; 3852 3853 if (isUndefined(event.immediatePropagationStopped)) { 3854 var originalStopImmediatePropagation = event.stopImmediatePropagation; 3855 event.stopImmediatePropagation = function() { 3856 event.immediatePropagationStopped = true; 3857 3858 if (event.stopPropagation) { 3859 event.stopPropagation(); 3860 } 3861 3862 if (originalStopImmediatePropagation) { 3863 originalStopImmediatePropagation.call(event); 3864 } 3865 }; 3866 } 3867 3868 event.isImmediatePropagationStopped = function() { 3869 return event.immediatePropagationStopped === true; 3870 }; 3871 3872 // Some events have special handlers that wrap the real handler 3873 var handlerWrapper = eventFns.specialHandlerWrapper || defaultHandlerWrapper; 3874 3875 // Copy event handlers in case event handlers array is modified during execution. 3876 if ((eventFnsLength > 1)) { 3877 eventFns = shallowCopy(eventFns); 3878 } 3879 3880 for (var i = 0; i < eventFnsLength; i++) { 3881 if (!event.isImmediatePropagationStopped()) { 3882 handlerWrapper(element, event, eventFns[i]); 3883 } 3884 } 3885 }; 3886 3887 // TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all 3888 // events on `element` 3889 eventHandler.elem = element; 3890 return eventHandler; 3891} 3892 3893function defaultHandlerWrapper(element, event, handler) { 3894 handler.call(element, event); 3895} 3896 3897function specialMouseHandlerWrapper(target, event, handler) { 3898 // Refer to jQuery's implementation of mouseenter & mouseleave 3899 // Read about mouseenter and mouseleave: 3900 // http://www.quirksmode.org/js/events_mouse.html#link8 3901 var related = event.relatedTarget; 3902 // For mousenter/leave call the handler if related is outside the target. 3903 // NB: No relatedTarget if the mouse left/entered the browser window 3904 if (!related || (related !== target && !jqLiteContains.call(target, related))) { 3905 handler.call(target, event); 3906 } 3907} 3908 3909////////////////////////////////////////// 3910// Functions iterating traversal. 3911// These functions chain results into a single 3912// selector. 3913////////////////////////////////////////// 3914forEach({ 3915 removeData: jqLiteRemoveData, 3916 3917 on: function jqLiteOn(element, type, fn, unsupported) { 3918 if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); 3919 3920 // Do not add event handlers to non-elements because they will not be cleaned up. 3921 if (!jqLiteAcceptsData(element)) { 3922 return; 3923 } 3924 3925 var expandoStore = jqLiteExpandoStore(element, true); 3926 var events = expandoStore.events; 3927 var handle = expandoStore.handle; 3928 3929 if (!handle) { 3930 handle = expandoStore.handle = createEventHandler(element, events); 3931 } 3932 3933 // http://jsperf.com/string-indexof-vs-split 3934 var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type]; 3935 var i = types.length; 3936 3937 var addHandler = function(type, specialHandlerWrapper, noEventListener) { 3938 var eventFns = events[type]; 3939 3940 if (!eventFns) { 3941 eventFns = events[type] = []; 3942 eventFns.specialHandlerWrapper = specialHandlerWrapper; 3943 if (type !== '$destroy' && !noEventListener) { 3944 element.addEventListener(type, handle); 3945 } 3946 } 3947 3948 eventFns.push(fn); 3949 }; 3950 3951 while (i--) { 3952 type = types[i]; 3953 if (MOUSE_EVENT_MAP[type]) { 3954 addHandler(MOUSE_EVENT_MAP[type], specialMouseHandlerWrapper); 3955 addHandler(type, undefined, true); 3956 } else { 3957 addHandler(type); 3958 } 3959 } 3960 }, 3961 3962 off: jqLiteOff, 3963 3964 one: function(element, type, fn) { 3965 element = jqLite(element); 3966 3967 //add the listener twice so that when it is called 3968 //you can remove the original function and still be 3969 //able to call element.off(ev, fn) normally 3970 element.on(type, function onFn() { 3971 element.off(type, fn); 3972 element.off(type, onFn); 3973 }); 3974 element.on(type, fn); 3975 }, 3976 3977 replaceWith: function(element, replaceNode) { 3978 var index, parent = element.parentNode; 3979 jqLiteDealoc(element); 3980 forEach(new JQLite(replaceNode), function(node) { 3981 if (index) { 3982 parent.insertBefore(node, index.nextSibling); 3983 } else { 3984 parent.replaceChild(node, element); 3985 } 3986 index = node; 3987 }); 3988 }, 3989 3990 children: function(element) { 3991 var children = []; 3992 forEach(element.childNodes, function(element) { 3993 if (element.nodeType === NODE_TYPE_ELEMENT) { 3994 children.push(element); 3995 } 3996 }); 3997 return children; 3998 }, 3999 4000 contents: function(element) { 4001 return element.contentDocument || element.childNodes || []; 4002 }, 4003 4004 append: function(element, node) { 4005 var nodeType = element.nodeType; 4006 if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return; 4007 4008 node = new JQLite(node); 4009 4010 for (var i = 0, ii = node.length; i < ii; i++) { 4011 var child = node[i]; 4012 element.appendChild(child); 4013 } 4014 }, 4015 4016 prepend: function(element, node) { 4017 if (element.nodeType === NODE_TYPE_ELEMENT) { 4018 var index = element.firstChild; 4019 forEach(new JQLite(node), function(child) { 4020 element.insertBefore(child, index); 4021 }); 4022 } 4023 }, 4024 4025 wrap: function(element, wrapNode) { 4026 jqLiteWrapNode(element, jqLite(wrapNode).eq(0).clone()[0]); 4027 }, 4028 4029 remove: jqLiteRemove, 4030 4031 detach: function(element) { 4032 jqLiteRemove(element, true); 4033 }, 4034 4035 after: function(element, newElement) { 4036 var index = element, parent = element.parentNode; 4037 4038 if (parent) { 4039 newElement = new JQLite(newElement); 4040 4041 for (var i = 0, ii = newElement.length; i < ii; i++) { 4042 var node = newElement[i]; 4043 parent.insertBefore(node, index.nextSibling); 4044 index = node; 4045 } 4046 } 4047 }, 4048 4049 addClass: jqLiteAddClass, 4050 removeClass: jqLiteRemoveClass, 4051 4052 toggleClass: function(element, selector, condition) { 4053 if (selector) { 4054 forEach(selector.split(' '), function(className) { 4055 var classCondition = condition; 4056 if (isUndefined(classCondition)) { 4057 classCondition = !jqLiteHasClass(element, className); 4058 } 4059 (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className); 4060 }); 4061 } 4062 }, 4063 4064 parent: function(element) { 4065 var parent = element.parentNode; 4066 return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null; 4067 }, 4068 4069 next: function(element) { 4070 return element.nextElementSibling; 4071 }, 4072 4073 find: function(element, selector) { 4074 if (element.getElementsByTagName) { 4075 return element.getElementsByTagName(selector); 4076 } else { 4077 return []; 4078 } 4079 }, 4080 4081 clone: jqLiteClone, 4082 4083 triggerHandler: function(element, event, extraParameters) { 4084 4085 var dummyEvent, eventFnsCopy, handlerArgs; 4086 var eventName = event.type || event; 4087 var expandoStore = jqLiteExpandoStore(element); 4088 var events = expandoStore && expandoStore.events; 4089 var eventFns = events && events[eventName]; 4090 4091 if (eventFns) { 4092 // Create a dummy event to pass to the handlers 4093 dummyEvent = { 4094 preventDefault: function() { this.defaultPrevented = true; }, 4095 isDefaultPrevented: function() { return this.defaultPrevented === true; }, 4096 stopImmediatePropagation: function() { this.immediatePropagationStopped = true; }, 4097 isImmediatePropagationStopped: function() { return this.immediatePropagationStopped === true; }, 4098 stopPropagation: noop, 4099 type: eventName, 4100 target: element 4101 }; 4102 4103 // If a custom event was provided then extend our dummy event with it 4104 if (event.type) { 4105 dummyEvent = extend(dummyEvent, event); 4106 } 4107 4108 // Copy event handlers in case event handlers array is modified during execution. 4109 eventFnsCopy = shallowCopy(eventFns); 4110 handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent]; 4111 4112 forEach(eventFnsCopy, function(fn) { 4113 if (!dummyEvent.isImmediatePropagationStopped()) { 4114 fn.apply(element, handlerArgs); 4115 } 4116 }); 4117 } 4118 } 4119}, function(fn, name) { 4120 /** 4121 * chaining functions 4122 */ 4123 JQLite.prototype[name] = function(arg1, arg2, arg3) { 4124 var value; 4125 4126 for (var i = 0, ii = this.length; i < ii; i++) { 4127 if (isUndefined(value)) { 4128 value = fn(this[i], arg1, arg2, arg3); 4129 if (isDefined(value)) { 4130 // any function which returns a value needs to be wrapped 4131 value = jqLite(value); 4132 } 4133 } else { 4134 jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3)); 4135 } 4136 } 4137 return isDefined(value) ? value : this; 4138 }; 4139}); 4140 4141// bind legacy bind/unbind to on/off 4142JQLite.prototype.bind = JQLite.prototype.on; 4143JQLite.prototype.unbind = JQLite.prototype.off; 4144 4145 4146// Provider for private $$jqLite service 4147/** @this */ 4148function $$jqLiteProvider() { 4149 this.$get = function $$jqLite() { 4150 return extend(JQLite, { 4151 hasClass: function(node, classes) { 4152 if (node.attr) node = node[0]; 4153 return jqLiteHasClass(node, classes); 4154 }, 4155 addClass: function(node, classes) { 4156 if (node.attr) node = node[0]; 4157 return jqLiteAddClass(node, classes); 4158 }, 4159 removeClass: function(node, classes) { 4160 if (node.attr) node = node[0]; 4161 return jqLiteRemoveClass(node, classes); 4162 } 4163 }); 4164 }; 4165} 4166 4167/** 4168 * Computes a hash of an 'obj'. 4169 * Hash of a: 4170 * string is string 4171 * number is number as string 4172 * object is either result of calling $$hashKey function on the object or uniquely generated id, 4173 * that is also assigned to the $$hashKey property of the object. 4174 * 4175 * @param obj 4176 * @returns {string} hash string such that the same input will have the same hash string. 4177 * The resulting string key is in 'type:hashKey' format. 4178 */ 4179function hashKey(obj, nextUidFn) { 4180 var key = obj && obj.$$hashKey; 4181 4182 if (key) { 4183 if (typeof key === 'function') { 4184 key = obj.$$hashKey(); 4185 } 4186 return key; 4187 } 4188 4189 var objType = typeof obj; 4190 if (objType === 'function' || (objType === 'object' && obj !== null)) { 4191 key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)(); 4192 } else { 4193 key = objType + ':' + obj; 4194 } 4195 4196 return key; 4197} 4198 4199// A minimal ES2015 Map implementation. 4200// Should be bug/feature equivalent to the native implementations of supported browsers 4201// (for the features required in Angular). 4202// See https://kangax.github.io/compat-table/es6/#test-Map 4203var nanKey = Object.create(null); 4204function NgMapShim() { 4205 this._keys = []; 4206 this._values = []; 4207 this._lastKey = NaN; 4208 this._lastIndex = -1; 4209} 4210NgMapShim.prototype = { 4211 _idx: function(key) { 4212 if (key !== this._lastKey) { 4213 this._lastKey = key; 4214 this._lastIndex = this._keys.indexOf(key); 4215 } 4216 return this._lastIndex; 4217 }, 4218 _transformKey: function(key) { 4219 return isNumberNaN(key) ? nanKey : key; 4220 }, 4221 get: function(key) { 4222 key = this._transformKey(key); 4223 var idx = this._idx(key); 4224 if (idx !== -1) { 4225 return this._values[idx]; 4226 } 4227 }, 4228 has: function(key) { 4229 key = this._transformKey(key); 4230 var idx = this._idx(key); 4231 return idx !== -1; 4232 }, 4233 set: function(key, value) { 4234 key = this._transformKey(key); 4235 var idx = this._idx(key); 4236 if (idx === -1) { 4237 idx = this._lastIndex = this._keys.length; 4238 } 4239 this._keys[idx] = key; 4240 this._values[idx] = value; 4241 4242 // Support: IE11 4243 // Do not `return this` to simulate the partial IE11 implementation 4244 }, 4245 delete: function(key) { 4246 key = this._transformKey(key); 4247 var idx = this._idx(key); 4248 if (idx === -1) { 4249 return false; 4250 } 4251 this._keys.splice(idx, 1); 4252 this._values.splice(idx, 1); 4253 this._lastKey = NaN; 4254 this._lastIndex = -1; 4255 return true; 4256 } 4257}; 4258 4259// For now, always use `NgMapShim`, even if `window.Map` is available. Some native implementations 4260// are still buggy (often in subtle ways) and can cause hard-to-debug failures. When native `Map` 4261// implementations get more stable, we can reconsider switching to `window.Map` (when available). 4262var NgMap = NgMapShim; 4263 4264var $$MapProvider = [/** @this */function() { 4265 this.$get = [function() { 4266 return NgMap; 4267 }]; 4268}]; 4269 4270/** 4271 * @ngdoc function 4272 * @module ng 4273 * @name angular.injector 4274 * @kind function 4275 * 4276 * @description 4277 * Creates an injector object that can be used for retrieving services as well as for 4278 * dependency injection (see {@link guide/di dependency injection}). 4279 * 4280 * @param {Array.<string|Function>} modules A list of module functions or their aliases. See 4281 * {@link angular.module}. The `ng` module must be explicitly added. 4282 * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which 4283 * disallows argument name annotation inference. 4284 * @returns {injector} Injector object. See {@link auto.$injector $injector}. 4285 * 4286 * @example 4287 * Typical usage 4288 * ```js 4289 * // create an injector 4290 * var $injector = angular.injector(['ng']); 4291 * 4292 * // use the injector to kick off your application 4293 * // use the type inference to auto inject arguments, or use implicit injection 4294 * $injector.invoke(function($rootScope, $compile, $document) { 4295 * $compile($document)($rootScope); 4296 * $rootScope.$digest(); 4297 * }); 4298 * ``` 4299 * 4300 * Sometimes you want to get access to the injector of a currently running AngularJS app 4301 * from outside AngularJS. Perhaps, you want to inject and compile some markup after the 4302 * application has been bootstrapped. You can do this using the extra `injector()` added 4303 * to JQuery/jqLite elements. See {@link angular.element}. 4304 * 4305 * *This is fairly rare but could be the case if a third party library is injecting the 4306 * markup.* 4307 * 4308 * In the following example a new block of HTML containing a `ng-controller` 4309 * directive is added to the end of the document body by JQuery. We then compile and link 4310 * it into the current AngularJS scope. 4311 * 4312 * ```js 4313 * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>'); 4314 * $(document.body).append($div); 4315 * 4316 * angular.element(document).injector().invoke(function($compile) { 4317 * var scope = angular.element($div).scope(); 4318 * $compile($div)(scope); 4319 * }); 4320 * ``` 4321 */ 4322 4323 4324/** 4325 * @ngdoc module 4326 * @name auto 4327 * @installation 4328 * @description 4329 * 4330 * Implicit module which gets automatically added to each {@link auto.$injector $injector}. 4331 */ 4332 4333var ARROW_ARG = /^([^(]+?)=>/; 4334var FN_ARGS = /^[^(]*\(\s*([^)]*)\)/m; 4335var FN_ARG_SPLIT = /,/; 4336var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; 4337var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; 4338var $injectorMinErr = minErr('$injector'); 4339 4340function stringifyFn(fn) { 4341 return Function.prototype.toString.call(fn); 4342} 4343 4344function extractArgs(fn) { 4345 var fnText = stringifyFn(fn).replace(STRIP_COMMENTS, ''), 4346 args = fnText.match(ARROW_ARG) || fnText.match(FN_ARGS); 4347 return args; 4348} 4349 4350function anonFn(fn) { 4351 // For anonymous functions, showing at the very least the function signature can help in 4352 // debugging. 4353 var args = extractArgs(fn); 4354 if (args) { 4355 return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')'; 4356 } 4357 return 'fn'; 4358} 4359 4360function annotate(fn, strictDi, name) { 4361 var $inject, 4362 argDecl, 4363 last; 4364 4365 if (typeof fn === 'function') { 4366 if (!($inject = fn.$inject)) { 4367 $inject = []; 4368 if (fn.length) { 4369 if (strictDi) { 4370 if (!isString(name) || !name) { 4371 name = fn.name || anonFn(fn); 4372 } 4373 throw $injectorMinErr('strictdi', 4374 '{0} is not using explicit annotation and cannot be invoked in strict mode', name); 4375 } 4376 argDecl = extractArgs(fn); 4377 forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) { 4378 arg.replace(FN_ARG, function(all, underscore, name) { 4379 $inject.push(name); 4380 }); 4381 }); 4382 } 4383 fn.$inject = $inject; 4384 } 4385 } else if (isArray(fn)) { 4386 last = fn.length - 1; 4387 assertArgFn(fn[last], 'fn'); 4388 $inject = fn.slice(0, last); 4389 } else { 4390 assertArgFn(fn, 'fn', true); 4391 } 4392 return $inject; 4393} 4394 4395/////////////////////////////////////// 4396 4397/** 4398 * @ngdoc service 4399 * @name $injector 4400 * 4401 * @description 4402 * 4403 * `$injector` is used to retrieve object instances as defined by 4404 * {@link auto.$provide provider}, instantiate types, invoke methods, 4405 * and load modules. 4406 * 4407 * The following always holds true: 4408 * 4409 * ```js 4410 * var $injector = angular.injector(); 4411 * expect($injector.get('$injector')).toBe($injector); 4412 * expect($injector.invoke(function($injector) { 4413 * return $injector; 4414 * })).toBe($injector); 4415 * ``` 4416 * 4417 * ## Injection Function Annotation 4418 * 4419 * JavaScript does not have annotations, and annotations are needed for dependency injection. The 4420 * following are all valid ways of annotating function with injection arguments and are equivalent. 4421 * 4422 * ```js 4423 * // inferred (only works if code not minified/obfuscated) 4424 * $injector.invoke(function(serviceA){}); 4425 * 4426 * // annotated 4427 * function explicit(serviceA) {}; 4428 * explicit.$inject = ['serviceA']; 4429 * $injector.invoke(explicit); 4430 * 4431 * // inline 4432 * $injector.invoke(['serviceA', function(serviceA){}]); 4433 * ``` 4434 * 4435 * ### Inference 4436 * 4437 * In JavaScript calling `toString()` on a function returns the function definition. The definition 4438 * can then be parsed and the function arguments can be extracted. This method of discovering 4439 * annotations is disallowed when the injector is in strict mode. 4440 * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the 4441 * argument names. 4442 * 4443 * ### `$inject` Annotation 4444 * By adding an `$inject` property onto a function the injection parameters can be specified. 4445 * 4446 * ### Inline 4447 * As an array of injection names, where the last item in the array is the function to call. 4448 */ 4449 4450/** 4451 * @ngdoc property 4452 * @name $injector#modules 4453 * @type {Object} 4454 * @description 4455 * A hash containing all the modules that have been loaded into the 4456 * $injector. 4457 * 4458 * You can use this property to find out information about a module via the 4459 * {@link angular.Module#info `myModule.info(...)`} method. 4460 * 4461 * For example: 4462 * 4463 * ``` 4464 * var info = $injector.modules['ngAnimate'].info(); 4465 * ``` 4466 * 4467 * **Do not use this property to attempt to modify the modules after the application 4468 * has been bootstrapped.** 4469 */ 4470 4471 4472/** 4473 * @ngdoc method 4474 * @name $injector#get 4475 * 4476 * @description 4477 * Return an instance of the service. 4478 * 4479 * @param {string} name The name of the instance to retrieve. 4480 * @param {string=} caller An optional string to provide the origin of the function call for error messages. 4481 * @return {*} The instance. 4482 */ 4483 4484/** 4485 * @ngdoc method 4486 * @name $injector#invoke 4487 * 4488 * @description 4489 * Invoke the method and supply the method arguments from the `$injector`. 4490 * 4491 * @param {Function|Array.<string|Function>} fn The injectable function to invoke. Function parameters are 4492 * injected according to the {@link guide/di $inject Annotation} rules. 4493 * @param {Object=} self The `this` for the invoked method. 4494 * @param {Object=} locals Optional object. If preset then any argument names are read from this 4495 * object first, before the `$injector` is consulted. 4496 * @returns {*} the value returned by the invoked `fn` function. 4497 */ 4498 4499/** 4500 * @ngdoc method 4501 * @name $injector#has 4502 * 4503 * @description 4504 * Allows the user to query if the particular service exists. 4505 * 4506 * @param {string} name Name of the service to query. 4507 * @returns {boolean} `true` if injector has given service. 4508 */ 4509 4510/** 4511 * @ngdoc method 4512 * @name $injector#instantiate 4513 * @description 4514 * Create a new instance of JS type. The method takes a constructor function, invokes the new 4515 * operator, and supplies all of the arguments to the constructor function as specified by the 4516 * constructor annotation. 4517 * 4518 * @param {Function} Type Annotated constructor function. 4519 * @param {Object=} locals Optional object. If preset then any argument names are read from this 4520 * object first, before the `$injector` is consulted. 4521 * @returns {Object} new instance of `Type`. 4522 */ 4523 4524/** 4525 * @ngdoc method 4526 * @name $injector#annotate 4527 * 4528 * @description 4529 * Returns an array of service names which the function is requesting for injection. This API is 4530 * used by the injector to determine which services need to be injected into the function when the 4531 * function is invoked. There are three ways in which the function can be annotated with the needed 4532 * dependencies. 4533 * 4534 * #### Argument names 4535 * 4536 * The simplest form is to extract the dependencies from the arguments of the function. This is done 4537 * by converting the function into a string using `toString()` method and extracting the argument 4538 * names. 4539 * ```js 4540 * // Given 4541 * function MyController($scope, $route) { 4542 * // ... 4543 * } 4544 * 4545 * // Then 4546 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 4547 * ``` 4548 * 4549 * You can disallow this method by using strict injection mode. 4550 * 4551 * This method does not work with code minification / obfuscation. For this reason the following 4552 * annotation strategies are supported. 4553 * 4554 * #### The `$inject` property 4555 * 4556 * If a function has an `$inject` property and its value is an array of strings, then the strings 4557 * represent names of services to be injected into the function. 4558 * ```js 4559 * // Given 4560 * var MyController = function(obfuscatedScope, obfuscatedRoute) { 4561 * // ... 4562 * } 4563 * // Define function dependencies 4564 * MyController['$inject'] = ['$scope', '$route']; 4565 * 4566 * // Then 4567 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 4568 * ``` 4569 * 4570 * #### The array notation 4571 * 4572 * It is often desirable to inline Injected functions and that's when setting the `$inject` property 4573 * is very inconvenient. In these situations using the array notation to specify the dependencies in 4574 * a way that survives minification is a better choice: 4575 * 4576 * ```js 4577 * // We wish to write this (not minification / obfuscation safe) 4578 * injector.invoke(function($compile, $rootScope) { 4579 * // ... 4580 * }); 4581 * 4582 * // We are forced to write break inlining 4583 * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) { 4584 * // ... 4585 * }; 4586 * tmpFn.$inject = ['$compile', '$rootScope']; 4587 * injector.invoke(tmpFn); 4588 * 4589 * // To better support inline function the inline annotation is supported 4590 * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) { 4591 * // ... 4592 * }]); 4593 * 4594 * // Therefore 4595 * expect(injector.annotate( 4596 * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}]) 4597 * ).toEqual(['$compile', '$rootScope']); 4598 * ``` 4599 * 4600 * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to 4601 * be retrieved as described above. 4602 * 4603 * @param {boolean=} [strictDi=false] Disallow argument name annotation inference. 4604 * 4605 * @returns {Array.<string>} The names of the services which the function requires. 4606 */ 4607/** 4608 * @ngdoc method 4609 * @name $injector#loadNewModules 4610 * 4611 * @description 4612 * 4613 * **This is a dangerous API, which you use at your own risk!** 4614 * 4615 * Add the specified modules to the current injector. 4616 * 4617 * This method will add each of the injectables to the injector and execute all of the config and run 4618 * blocks for each module passed to the method. 4619 * 4620 * If a module has already been loaded into the injector then it will not be loaded again. 4621 * 4622 * * The application developer is responsible for loading the code containing the modules; and for 4623 * ensuring that lazy scripts are not downloaded and executed more often that desired. 4624 * * Previously compiled HTML will not be affected by newly loaded directives, filters and components. 4625 * * Modules cannot be unloaded. 4626 * 4627 * You can use {@link $injector#modules `$injector.modules`} to check whether a module has been loaded 4628 * into the injector, which may indicate whether the script has been executed already. 4629 * 4630 * @example 4631 * Here is an example of loading a bundle of modules, with a utility method called `getScript`: 4632 * 4633 * ```javascript 4634 * app.factory('loadModule', function($injector) { 4635 * return function loadModule(moduleName, bundleUrl) { 4636 * return getScript(bundleUrl).then(function() { $injector.loadNewModules([moduleName]); }); 4637 * }; 4638 * }) 4639 * ``` 4640 * 4641 * @param {Array<String|Function|Array>=} mods an array of modules to load into the application. 4642 * Each item in the array should be the name of a predefined module or a (DI annotated) 4643 * function that will be invoked by the injector as a `config` block. 4644 * See: {@link angular.module modules} 4645 */ 4646 4647 4648/** 4649 * @ngdoc service 4650 * @name $provide 4651 * 4652 * @description 4653 * 4654 * The {@link auto.$provide $provide} service has a number of methods for registering components 4655 * with the {@link auto.$injector $injector}. Many of these functions are also exposed on 4656 * {@link angular.Module}. 4657 * 4658 * An AngularJS **service** is a singleton object created by a **service factory**. These **service 4659 * factories** are functions which, in turn, are created by a **service provider**. 4660 * The **service providers** are constructor functions. When instantiated they must contain a 4661 * property called `$get`, which holds the **service factory** function. 4662 * 4663 * When you request a service, the {@link auto.$injector $injector} is responsible for finding the 4664 * correct **service provider**, instantiating it and then calling its `$get` **service factory** 4665 * function to get the instance of the **service**. 4666 * 4667 * Often services have no configuration options and there is no need to add methods to the service 4668 * provider. The provider will be no more than a constructor function with a `$get` property. For 4669 * these cases the {@link auto.$provide $provide} service has additional helper methods to register 4670 * services without specifying a provider. 4671 * 4672 * * {@link auto.$provide#provider provider(name, provider)} - registers a **service provider** with the 4673 * {@link auto.$injector $injector} 4674 * * {@link auto.$provide#constant constant(name, obj)} - registers a value/object that can be accessed by 4675 * providers and services. 4676 * * {@link auto.$provide#value value(name, obj)} - registers a value/object that can only be accessed by 4677 * services, not providers. 4678 * * {@link auto.$provide#factory factory(name, fn)} - registers a service **factory function** 4679 * that will be wrapped in a **service provider** object, whose `$get` property will contain the 4680 * given factory function. 4681 * * {@link auto.$provide#service service(name, Fn)} - registers a **constructor function** 4682 * that will be wrapped in a **service provider** object, whose `$get` property will instantiate 4683 * a new object using the given constructor function. 4684 * * {@link auto.$provide#decorator decorator(name, decorFn)} - registers a **decorator function** that 4685 * will be able to modify or replace the implementation of another service. 4686 * 4687 * See the individual methods for more information and examples. 4688 */ 4689 4690/** 4691 * @ngdoc method 4692 * @name $provide#provider 4693 * @description 4694 * 4695 * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions 4696 * are constructor functions, whose instances are responsible for "providing" a factory for a 4697 * service. 4698 * 4699 * Service provider names start with the name of the service they provide followed by `Provider`. 4700 * For example, the {@link ng.$log $log} service has a provider called 4701 * {@link ng.$logProvider $logProvider}. 4702 * 4703 * Service provider objects can have additional methods which allow configuration of the provider 4704 * and its service. Importantly, you can configure what kind of service is created by the `$get` 4705 * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a 4706 * method {@link ng.$logProvider#debugEnabled debugEnabled} 4707 * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the 4708 * console or not. 4709 * 4710 * It is possible to inject other providers into the provider function, 4711 * but the injected provider must have been defined before the one that requires it. 4712 * 4713 * @param {string} name The name of the instance. NOTE: the provider will be available under `name + 4714 'Provider'` key. 4715 * @param {(Object|function())} provider If the provider is: 4716 * 4717 * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using 4718 * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created. 4719 * - `Constructor`: a new instance of the provider will be created using 4720 * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`. 4721 * 4722 * @returns {Object} registered provider instance 4723 4724 * @example 4725 * 4726 * The following example shows how to create a simple event tracking service and register it using 4727 * {@link auto.$provide#provider $provide.provider()}. 4728 * 4729 * ```js 4730 * // Define the eventTracker provider 4731 * function EventTrackerProvider() { 4732 * var trackingUrl = '/track'; 4733 * 4734 * // A provider method for configuring where the tracked events should been saved 4735 * this.setTrackingUrl = function(url) { 4736 * trackingUrl = url; 4737 * }; 4738 * 4739 * // The service factory function 4740 * this.$get = ['$http', function($http) { 4741 * var trackedEvents = {}; 4742 * return { 4743 * // Call this to track an event 4744 * event: function(event) { 4745 * var count = trackedEvents[event] || 0; 4746 * count += 1; 4747 * trackedEvents[event] = count; 4748 * return count; 4749 * }, 4750 * // Call this to save the tracked events to the trackingUrl 4751 * save: function() { 4752 * $http.post(trackingUrl, trackedEvents); 4753 * } 4754 * }; 4755 * }]; 4756 * } 4757 * 4758 * describe('eventTracker', function() { 4759 * var postSpy; 4760 * 4761 * beforeEach(module(function($provide) { 4762 * // Register the eventTracker provider 4763 * $provide.provider('eventTracker', EventTrackerProvider); 4764 * })); 4765 * 4766 * beforeEach(module(function(eventTrackerProvider) { 4767 * // Configure eventTracker provider 4768 * eventTrackerProvider.setTrackingUrl('/custom-track'); 4769 * })); 4770 * 4771 * it('tracks events', inject(function(eventTracker) { 4772 * expect(eventTracker.event('login')).toEqual(1); 4773 * expect(eventTracker.event('login')).toEqual(2); 4774 * })); 4775 * 4776 * it('saves to the tracking url', inject(function(eventTracker, $http) { 4777 * postSpy = spyOn($http, 'post'); 4778 * eventTracker.event('login'); 4779 * eventTracker.save(); 4780 * expect(postSpy).toHaveBeenCalled(); 4781 * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track'); 4782 * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track'); 4783 * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 }); 4784 * })); 4785 * }); 4786 * ``` 4787 */ 4788 4789/** 4790 * @ngdoc method 4791 * @name $provide#factory 4792 * @description 4793 * 4794 * Register a **service factory**, which will be called to return the service instance. 4795 * This is short for registering a service where its provider consists of only a `$get` property, 4796 * which is the given service factory function. 4797 * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to 4798 * configure your service in a provider. 4799 * 4800 * @param {string} name The name of the instance. 4801 * @param {Function|Array.<string|Function>} $getFn The injectable $getFn for the instance creation. 4802 * Internally this is a short hand for `$provide.provider(name, {$get: $getFn})`. 4803 * @returns {Object} registered provider instance 4804 * 4805 * @example 4806 * Here is an example of registering a service 4807 * ```js 4808 * $provide.factory('ping', ['$http', function($http) { 4809 * return function ping() { 4810 * return $http.send('/ping'); 4811 * }; 4812 * }]); 4813 * ``` 4814 * You would then inject and use this service like this: 4815 * ```js 4816 * someModule.controller('Ctrl', ['ping', function(ping) { 4817 * ping(); 4818 * }]); 4819 * ``` 4820 */ 4821 4822 4823/** 4824 * @ngdoc method 4825 * @name $provide#service 4826 * @description 4827 * 4828 * Register a **service constructor**, which will be invoked with `new` to create the service 4829 * instance. 4830 * This is short for registering a service where its provider's `$get` property is a factory 4831 * function that returns an instance instantiated by the injector from the service constructor 4832 * function. 4833 * 4834 * Internally it looks a bit like this: 4835 * 4836 * ``` 4837 * { 4838 * $get: function() { 4839 * return $injector.instantiate(constructor); 4840 * } 4841 * } 4842 * ``` 4843 * 4844 * 4845 * You should use {@link auto.$provide#service $provide.service(class)} if you define your service 4846 * as a type/class. 4847 * 4848 * @param {string} name The name of the instance. 4849 * @param {Function|Array.<string|Function>} constructor An injectable class (constructor function) 4850 * that will be instantiated. 4851 * @returns {Object} registered provider instance 4852 * 4853 * @example 4854 * Here is an example of registering a service using 4855 * {@link auto.$provide#service $provide.service(class)}. 4856 * ```js 4857 * var Ping = function($http) { 4858 * this.$http = $http; 4859 * }; 4860 * 4861 * Ping.$inject = ['$http']; 4862 * 4863 * Ping.prototype.send = function() { 4864 * return this.$http.get('/ping'); 4865 * }; 4866 * $provide.service('ping', Ping); 4867 * ``` 4868 * You would then inject and use this service like this: 4869 * ```js 4870 * someModule.controller('Ctrl', ['ping', function(ping) { 4871 * ping.send(); 4872 * }]); 4873 * ``` 4874 */ 4875 4876 4877/** 4878 * @ngdoc method 4879 * @name $provide#value 4880 * @description 4881 * 4882 * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a 4883 * number, an array, an object or a function. This is short for registering a service where its 4884 * provider's `$get` property is a factory function that takes no arguments and returns the **value 4885 * service**. That also means it is not possible to inject other services into a value service. 4886 * 4887 * Value services are similar to constant services, except that they cannot be injected into a 4888 * module configuration function (see {@link angular.Module#config}) but they can be overridden by 4889 * an AngularJS {@link auto.$provide#decorator decorator}. 4890 * 4891 * @param {string} name The name of the instance. 4892 * @param {*} value The value. 4893 * @returns {Object} registered provider instance 4894 * 4895 * @example 4896 * Here are some examples of creating value services. 4897 * ```js 4898 * $provide.value('ADMIN_USER', 'admin'); 4899 * 4900 * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 }); 4901 * 4902 * $provide.value('halfOf', function(value) { 4903 * return value / 2; 4904 * }); 4905 * ``` 4906 */ 4907 4908 4909/** 4910 * @ngdoc method 4911 * @name $provide#constant 4912 * @description 4913 * 4914 * Register a **constant service** with the {@link auto.$injector $injector}, such as a string, 4915 * a number, an array, an object or a function. Like the {@link auto.$provide#value value}, it is not 4916 * possible to inject other services into a constant. 4917 * 4918 * But unlike {@link auto.$provide#value value}, a constant can be 4919 * injected into a module configuration function (see {@link angular.Module#config}) and it cannot 4920 * be overridden by an AngularJS {@link auto.$provide#decorator decorator}. 4921 * 4922 * @param {string} name The name of the constant. 4923 * @param {*} value The constant value. 4924 * @returns {Object} registered instance 4925 * 4926 * @example 4927 * Here a some examples of creating constants: 4928 * ```js 4929 * $provide.constant('SHARD_HEIGHT', 306); 4930 * 4931 * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']); 4932 * 4933 * $provide.constant('double', function(value) { 4934 * return value * 2; 4935 * }); 4936 * ``` 4937 */ 4938 4939 4940/** 4941 * @ngdoc method 4942 * @name $provide#decorator 4943 * @description 4944 * 4945 * Register a **decorator function** with the {@link auto.$injector $injector}. A decorator function 4946 * intercepts the creation of a service, allowing it to override or modify the behavior of the 4947 * service. The return value of the decorator function may be the original service, or a new service 4948 * that replaces (or wraps and delegates to) the original service. 4949 * 4950 * You can find out more about using decorators in the {@link guide/decorators} guide. 4951 * 4952 * @param {string} name The name of the service to decorate. 4953 * @param {Function|Array.<string|Function>} decorator This function will be invoked when the service needs to be 4954 * provided and should return the decorated service instance. The function is called using 4955 * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable. 4956 * Local injection arguments: 4957 * 4958 * * `$delegate` - The original service instance, which can be replaced, monkey patched, configured, 4959 * decorated or delegated to. 4960 * 4961 * @example 4962 * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting 4963 * calls to {@link ng.$log#error $log.warn()}. 4964 * ```js 4965 * $provide.decorator('$log', ['$delegate', function($delegate) { 4966 * $delegate.warn = $delegate.error; 4967 * return $delegate; 4968 * }]); 4969 * ``` 4970 */ 4971 4972 4973function createInjector(modulesToLoad, strictDi) { 4974 strictDi = (strictDi === true); 4975 var INSTANTIATING = {}, 4976 providerSuffix = 'Provider', 4977 path = [], 4978 loadedModules = new NgMap(), 4979 providerCache = { 4980 $provide: { 4981 provider: supportObject(provider), 4982 factory: supportObject(factory), 4983 service: supportObject(service), 4984 value: supportObject(value), 4985 constant: supportObject(constant), 4986 decorator: decorator 4987 } 4988 }, 4989 providerInjector = (providerCache.$injector = 4990 createInternalInjector(providerCache, function(serviceName, caller) { 4991 if (angular.isString(caller)) { 4992 path.push(caller); 4993 } 4994 throw $injectorMinErr('unpr', 'Unknown provider: {0}', path.join(' <- ')); 4995 })), 4996 instanceCache = {}, 4997 protoInstanceInjector = 4998 createInternalInjector(instanceCache, function(serviceName, caller) { 4999 var provider = providerInjector.get(serviceName + providerSuffix, caller); 5000 return instanceInjector.invoke( 5001 provider.$get, provider, undefined, serviceName); 5002 }), 5003 instanceInjector = protoInstanceInjector; 5004 5005 providerCache['$injector' + providerSuffix] = { $get: valueFn(protoInstanceInjector) }; 5006 instanceInjector.modules = providerInjector.modules = createMap(); 5007 var runBlocks = loadModules(modulesToLoad); 5008 instanceInjector = protoInstanceInjector.get('$injector'); 5009 instanceInjector.strictDi = strictDi; 5010 forEach(runBlocks, function(fn) { if (fn) instanceInjector.invoke(fn); }); 5011 5012 instanceInjector.loadNewModules = function(mods) { 5013 forEach(loadModules(mods), function(fn) { if (fn) instanceInjector.invoke(fn); }); 5014 }; 5015 5016 5017 return instanceInjector; 5018 5019 //////////////////////////////////// 5020 // $provider 5021 //////////////////////////////////// 5022 5023 function supportObject(delegate) { 5024 return function(key, value) { 5025 if (isObject(key)) { 5026 forEach(key, reverseParams(delegate)); 5027 } else { 5028 return delegate(key, value); 5029 } 5030 }; 5031 } 5032 5033 function provider(name, provider_) { 5034 assertNotHasOwnProperty(name, 'service'); 5035 if (isFunction(provider_) || isArray(provider_)) { 5036 provider_ = providerInjector.instantiate(provider_); 5037 } 5038 if (!provider_.$get) { 5039 throw $injectorMinErr('pget', 'Provider \'{0}\' must define $get factory method.', name); 5040 } 5041 return (providerCache[name + providerSuffix] = provider_); 5042 } 5043 5044 function enforceReturnValue(name, factory) { 5045 return /** @this */ function enforcedReturnValue() { 5046 var result = instanceInjector.invoke(factory, this); 5047 if (isUndefined(result)) { 5048 throw $injectorMinErr('undef', 'Provider \'{0}\' must return a value from $get factory method.', name); 5049 } 5050 return result; 5051 }; 5052 } 5053 5054 function factory(name, factoryFn, enforce) { 5055 return provider(name, { 5056 $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn 5057 }); 5058 } 5059 5060 function service(name, constructor) { 5061 return factory(name, ['$injector', function($injector) { 5062 return $injector.instantiate(constructor); 5063 }]); 5064 } 5065 5066 function value(name, val) { return factory(name, valueFn(val), false); } 5067 5068 function constant(name, value) { 5069 assertNotHasOwnProperty(name, 'constant'); 5070 providerCache[name] = value; 5071 instanceCache[name] = value; 5072 } 5073 5074 function decorator(serviceName, decorFn) { 5075 var origProvider = providerInjector.get(serviceName + providerSuffix), 5076 orig$get = origProvider.$get; 5077 5078 origProvider.$get = function() { 5079 var origInstance = instanceInjector.invoke(orig$get, origProvider); 5080 return instanceInjector.invoke(decorFn, null, {$delegate: origInstance}); 5081 }; 5082 } 5083 5084 //////////////////////////////////// 5085 // Module Loading 5086 //////////////////////////////////// 5087 function loadModules(modulesToLoad) { 5088 assertArg(isUndefined(modulesToLoad) || isArray(modulesToLoad), 'modulesToLoad', 'not an array'); 5089 var runBlocks = [], moduleFn; 5090 forEach(modulesToLoad, function(module) { 5091 if (loadedModules.get(module)) return; 5092 loadedModules.set(module, true); 5093 5094 function runInvokeQueue(queue) { 5095 var i, ii; 5096 for (i = 0, ii = queue.length; i < ii; i++) { 5097 var invokeArgs = queue[i], 5098 provider = providerInjector.get(invokeArgs[0]); 5099 5100 provider[invokeArgs[1]].apply(provider, invokeArgs[2]); 5101 } 5102 } 5103 5104 try { 5105 if (isString(module)) { 5106 moduleFn = angularModule(module); 5107 instanceInjector.modules[module] = moduleFn; 5108 runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); 5109 runInvokeQueue(moduleFn._invokeQueue); 5110 runInvokeQueue(moduleFn._configBlocks); 5111 } else if (isFunction(module)) { 5112 runBlocks.push(providerInjector.invoke(module)); 5113 } else if (isArray(module)) { 5114 runBlocks.push(providerInjector.invoke(module)); 5115 } else { 5116 assertArgFn(module, 'module'); 5117 } 5118 } catch (e) { 5119 if (isArray(module)) { 5120 module = module[module.length - 1]; 5121 } 5122 if (e.message && e.stack && e.stack.indexOf(e.message) === -1) { 5123 // Safari & FF's stack traces don't contain error.message content 5124 // unlike those of Chrome and IE 5125 // So if stack doesn't contain message, we create a new string that contains both. 5126 // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here. 5127 // eslint-disable-next-line no-ex-assign 5128 e = e.message + '\n' + e.stack; 5129 } 5130 throw $injectorMinErr('modulerr', 'Failed to instantiate module {0} due to:\n{1}', 5131 module, e.stack || e.message || e); 5132 } 5133 }); 5134 return runBlocks; 5135 } 5136 5137 //////////////////////////////////// 5138 // internal Injector 5139 //////////////////////////////////// 5140 5141 function createInternalInjector(cache, factory) { 5142 5143 function getService(serviceName, caller) { 5144 if (cache.hasOwnProperty(serviceName)) { 5145 if (cache[serviceName] === INSTANTIATING) { 5146 throw $injectorMinErr('cdep', 'Circular dependency found: {0}', 5147 serviceName + ' <- ' + path.join(' <- ')); 5148 } 5149 return cache[serviceName]; 5150 } else { 5151 try { 5152 path.unshift(serviceName); 5153 cache[serviceName] = INSTANTIATING; 5154 cache[serviceName] = factory(serviceName, caller); 5155 return cache[serviceName]; 5156 } catch (err) { 5157 if (cache[serviceName] === INSTANTIATING) { 5158 delete cache[serviceName]; 5159 } 5160 throw err; 5161 } finally { 5162 path.shift(); 5163 } 5164 } 5165 } 5166 5167 5168 function injectionArgs(fn, locals, serviceName) { 5169 var args = [], 5170 $inject = createInjector.$$annotate(fn, strictDi, serviceName); 5171 5172 for (var i = 0, length = $inject.length; i < length; i++) { 5173 var key = $inject[i]; 5174 if (typeof key !== 'string') { 5175 throw $injectorMinErr('itkn', 5176 'Incorrect injection token! Expected service name as string, got {0}', key); 5177 } 5178 args.push(locals && locals.hasOwnProperty(key) ? locals[key] : 5179 getService(key, serviceName)); 5180 } 5181 return args; 5182 } 5183 5184 function isClass(func) { 5185 // Support: IE 9-11 only 5186 // IE 9-11 do not support classes and IE9 leaks with the code below. 5187 if (msie || typeof func !== 'function') { 5188 return false; 5189 } 5190 var result = func.$$ngIsClass; 5191 if (!isBoolean(result)) { 5192 result = func.$$ngIsClass = /^class\b/.test(stringifyFn(func)); 5193 } 5194 return result; 5195 } 5196 5197 function invoke(fn, self, locals, serviceName) { 5198 if (typeof locals === 'string') { 5199 serviceName = locals; 5200 locals = null; 5201 } 5202 5203 var args = injectionArgs(fn, locals, serviceName); 5204 if (isArray(fn)) { 5205 fn = fn[fn.length - 1]; 5206 } 5207 5208 if (!isClass(fn)) { 5209 // http://jsperf.com/angularjs-invoke-apply-vs-switch 5210 // #5388 5211 return fn.apply(self, args); 5212 } else { 5213 args.unshift(null); 5214 return new (Function.prototype.bind.apply(fn, args))(); 5215 } 5216 } 5217 5218 5219 function instantiate(Type, locals, serviceName) { 5220 // Check if Type is annotated and use just the given function at n-1 as parameter 5221 // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]); 5222 var ctor = (isArray(Type) ? Type[Type.length - 1] : Type); 5223 var args = injectionArgs(Type, locals, serviceName); 5224 // Empty object at position 0 is ignored for invocation with `new`, but required. 5225 args.unshift(null); 5226 return new (Function.prototype.bind.apply(ctor, args))(); 5227 } 5228 5229 5230 return { 5231 invoke: invoke, 5232 instantiate: instantiate, 5233 get: getService, 5234 annotate: createInjector.$$annotate, 5235 has: function(name) { 5236 return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); 5237 } 5238 }; 5239 } 5240} 5241 5242createInjector.$$annotate = annotate; 5243 5244/** 5245 * @ngdoc provider 5246 * @name $anchorScrollProvider 5247 * @this 5248 * 5249 * @description 5250 * Use `$anchorScrollProvider` to disable automatic scrolling whenever 5251 * {@link ng.$location#hash $location.hash()} changes. 5252 */ 5253function $AnchorScrollProvider() { 5254 5255 var autoScrollingEnabled = true; 5256 5257 /** 5258 * @ngdoc method 5259 * @name $anchorScrollProvider#disableAutoScrolling 5260 * 5261 * @description 5262 * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to 5263 * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br /> 5264 * Use this method to disable automatic scrolling. 5265 * 5266 * If automatic scrolling is disabled, one must explicitly call 5267 * {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the 5268 * current hash. 5269 */ 5270 this.disableAutoScrolling = function() { 5271 autoScrollingEnabled = false; 5272 }; 5273 5274 /** 5275 * @ngdoc service 5276 * @name $anchorScroll 5277 * @kind function 5278 * @requires $window 5279 * @requires $location 5280 * @requires $rootScope 5281 * 5282 * @description 5283 * When called, it scrolls to the element related to the specified `hash` or (if omitted) to the 5284 * current value of {@link ng.$location#hash $location.hash()}, according to the rules specified 5285 * in the 5286 * [HTML5 spec](http://www.w3.org/html/wg/drafts/html/master/browsers.html#an-indicated-part-of-the-document). 5287 * 5288 * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to 5289 * match any anchor whenever it changes. This can be disabled by calling 5290 * {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}. 5291 * 5292 * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a 5293 * vertical scroll-offset (either fixed or dynamic). 5294 * 5295 * @param {string=} hash The hash specifying the element to scroll to. If omitted, the value of 5296 * {@link ng.$location#hash $location.hash()} will be used. 5297 * 5298 * @property {(number|function|jqLite)} yOffset 5299 * If set, specifies a vertical scroll-offset. This is often useful when there are fixed 5300 * positioned elements at the top of the page, such as navbars, headers etc. 5301 * 5302 * `yOffset` can be specified in various ways: 5303 * - **number**: A fixed number of pixels to be used as offset.<br /><br /> 5304 * - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return 5305 * a number representing the offset (in pixels).<br /><br /> 5306 * - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The distance from 5307 * the top of the page to the element's bottom will be used as offset.<br /> 5308 * **Note**: The element will be taken into account only as long as its `position` is set to 5309 * `fixed`. This option is useful, when dealing with responsive navbars/headers that adjust 5310 * their height and/or positioning according to the viewport's size. 5311 * 5312 * <br /> 5313 * <div class="alert alert-warning"> 5314 * In order for `yOffset` to work properly, scrolling should take place on the document's root and 5315 * not some child element. 5316 * </div> 5317 * 5318 * @example 5319 <example module="anchorScrollExample" name="anchor-scroll"> 5320 <file name="index.html"> 5321 <div id="scrollArea" ng-controller="ScrollController"> 5322 <a ng-click="gotoBottom()">Go to bottom</a> 5323 <a id="bottom"></a> You're at the bottom! 5324 </div> 5325 </file> 5326 <file name="script.js"> 5327 angular.module('anchorScrollExample', []) 5328 .controller('ScrollController', ['$scope', '$location', '$anchorScroll', 5329 function($scope, $location, $anchorScroll) { 5330 $scope.gotoBottom = function() { 5331 // set the location.hash to the id of 5332 // the element you wish to scroll to. 5333 $location.hash('bottom'); 5334 5335 // call $anchorScroll() 5336 $anchorScroll(); 5337 }; 5338 }]); 5339 </file> 5340 <file name="style.css"> 5341 #scrollArea { 5342 height: 280px; 5343 overflow: auto; 5344 } 5345 5346 #bottom { 5347 display: block; 5348 margin-top: 2000px; 5349 } 5350 </file> 5351 </example> 5352 * 5353 * <hr /> 5354 * The example below illustrates the use of a vertical scroll-offset (specified as a fixed value). 5355 * See {@link ng.$anchorScroll#yOffset $anchorScroll.yOffset} for more details. 5356 * 5357 * @example 5358 <example module="anchorScrollOffsetExample" name="anchor-scroll-offset"> 5359 <file name="index.html"> 5360 <div class="fixed-header" ng-controller="headerCtrl"> 5361 <a href="" ng-click="gotoAnchor(x)" ng-repeat="x in [1,2,3,4,5]"> 5362 Go to anchor {{x}} 5363 </a> 5364 </div> 5365 <div id="anchor{{x}}" class="anchor" ng-repeat="x in [1,2,3,4,5]"> 5366 Anchor {{x}} of 5 5367 </div> 5368 </file> 5369 <file name="script.js"> 5370 angular.module('anchorScrollOffsetExample', []) 5371 .run(['$anchorScroll', function($anchorScroll) { 5372 $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels 5373 }]) 5374 .controller('headerCtrl', ['$anchorScroll', '$location', '$scope', 5375 function($anchorScroll, $location, $scope) { 5376 $scope.gotoAnchor = function(x) { 5377 var newHash = 'anchor' + x; 5378 if ($location.hash() !== newHash) { 5379 // set the $location.hash to `newHash` and 5380 // $anchorScroll will automatically scroll to it 5381 $location.hash('anchor' + x); 5382 } else { 5383 // call $anchorScroll() explicitly, 5384 // since $location.hash hasn't changed 5385 $anchorScroll(); 5386 } 5387 }; 5388 } 5389 ]); 5390 </file> 5391 <file name="style.css"> 5392 body { 5393 padding-top: 50px; 5394 } 5395 5396 .anchor { 5397 border: 2px dashed DarkOrchid; 5398 padding: 10px 10px 200px 10px; 5399 } 5400 5401 .fixed-header { 5402 background-color: rgba(0, 0, 0, 0.2); 5403 height: 50px; 5404 position: fixed; 5405 top: 0; left: 0; right: 0; 5406 } 5407 5408 .fixed-header > a { 5409 display: inline-block; 5410 margin: 5px 15px; 5411 } 5412 </file> 5413 </example> 5414 */ 5415 this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) { 5416 var document = $window.document; 5417 5418 // Helper function to get first anchor from a NodeList 5419 // (using `Array#some()` instead of `angular#forEach()` since it's more performant 5420 // and working in all supported browsers.) 5421 function getFirstAnchor(list) { 5422 var result = null; 5423 Array.prototype.some.call(list, function(element) { 5424 if (nodeName_(element) === 'a') { 5425 result = element; 5426 return true; 5427 } 5428 }); 5429 return result; 5430 } 5431 5432 function getYOffset() { 5433 5434 var offset = scroll.yOffset; 5435 5436 if (isFunction(offset)) { 5437 offset = offset(); 5438 } else if (isElement(offset)) { 5439 var elem = offset[0]; 5440 var style = $window.getComputedStyle(elem); 5441 if (style.position !== 'fixed') { 5442 offset = 0; 5443 } else { 5444 offset = elem.getBoundingClientRect().bottom; 5445 } 5446 } else if (!isNumber(offset)) { 5447 offset = 0; 5448 } 5449 5450 return offset; 5451 } 5452 5453 function scrollTo(elem) { 5454 if (elem) { 5455 elem.scrollIntoView(); 5456 5457 var offset = getYOffset(); 5458 5459 if (offset) { 5460 // `offset` is the number of pixels we should scroll UP in order to align `elem` properly. 5461 // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the 5462 // top of the viewport. 5463 // 5464 // IF the number of pixels from the top of `elem` to the end of the page's content is less 5465 // than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some 5466 // way down the page. 5467 // 5468 // This is often the case for elements near the bottom of the page. 5469 // 5470 // In such cases we do not need to scroll the whole `offset` up, just the difference between 5471 // the top of the element and the offset, which is enough to align the top of `elem` at the 5472 // desired position. 5473 var elemTop = elem.getBoundingClientRect().top; 5474 $window.scrollBy(0, elemTop - offset); 5475 } 5476 } else { 5477 $window.scrollTo(0, 0); 5478 } 5479 } 5480 5481 function scroll(hash) { 5482 // Allow numeric hashes 5483 hash = isString(hash) ? hash : isNumber(hash) ? hash.toString() : $location.hash(); 5484 var elm; 5485 5486 // empty hash, scroll to the top of the page 5487 if (!hash) scrollTo(null); 5488 5489 // element with given id 5490 else if ((elm = document.getElementById(hash))) scrollTo(elm); 5491 5492 // first anchor with given name :-D 5493 else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm); 5494 5495 // no element and hash === 'top', scroll to the top of the page 5496 else if (hash === 'top') scrollTo(null); 5497 } 5498 5499 // does not scroll when user clicks on anchor link that is currently on 5500 // (no url change, no $location.hash() change), browser native does scroll 5501 if (autoScrollingEnabled) { 5502 $rootScope.$watch(function autoScrollWatch() {return $location.hash();}, 5503 function autoScrollWatchAction(newVal, oldVal) { 5504 // skip the initial scroll if $location.hash is empty 5505 if (newVal === oldVal && newVal === '') return; 5506 5507 jqLiteDocumentLoaded(function() { 5508 $rootScope.$evalAsync(scroll); 5509 }); 5510 }); 5511 } 5512 5513 return scroll; 5514 }]; 5515} 5516 5517var $animateMinErr = minErr('$animate'); 5518var ELEMENT_NODE = 1; 5519var NG_ANIMATE_CLASSNAME = 'ng-animate'; 5520 5521function mergeClasses(a,b) { 5522 if (!a && !b) return ''; 5523 if (!a) return b; 5524 if (!b) return a; 5525 if (isArray(a)) a = a.join(' '); 5526 if (isArray(b)) b = b.join(' '); 5527 return a + ' ' + b; 5528} 5529 5530function extractElementNode(element) { 5531 for (var i = 0; i < element.length; i++) { 5532 var elm = element[i]; 5533 if (elm.nodeType === ELEMENT_NODE) { 5534 return elm; 5535 } 5536 } 5537} 5538 5539function splitClasses(classes) { 5540 if (isString(classes)) { 5541 classes = classes.split(' '); 5542 } 5543 5544 // Use createMap() to prevent class assumptions involving property names in 5545 // Object.prototype 5546 var obj = createMap(); 5547 forEach(classes, function(klass) { 5548 // sometimes the split leaves empty string values 5549 // incase extra spaces were applied to the options 5550 if (klass.length) { 5551 obj[klass] = true; 5552 } 5553 }); 5554 return obj; 5555} 5556 5557// if any other type of options value besides an Object value is 5558// passed into the $animate.method() animation then this helper code 5559// will be run which will ignore it. While this patch is not the 5560// greatest solution to this, a lot of existing plugins depend on 5561// $animate to either call the callback (< 1.2) or return a promise 5562// that can be changed. This helper function ensures that the options 5563// are wiped clean incase a callback function is provided. 5564function prepareAnimateOptions(options) { 5565 return isObject(options) 5566 ? options 5567 : {}; 5568} 5569 5570var $$CoreAnimateJsProvider = /** @this */ function() { 5571 this.$get = noop; 5572}; 5573 5574// this is prefixed with Core since it conflicts with 5575// the animateQueueProvider defined in ngAnimate/animateQueue.js 5576var $$CoreAnimateQueueProvider = /** @this */ function() { 5577 var postDigestQueue = new NgMap(); 5578 var postDigestElements = []; 5579 5580 this.$get = ['$$AnimateRunner', '$rootScope', 5581 function($$AnimateRunner, $rootScope) { 5582 return { 5583 enabled: noop, 5584 on: noop, 5585 off: noop, 5586 pin: noop, 5587 5588 push: function(element, event, options, domOperation) { 5589 if (domOperation) { 5590 domOperation(); 5591 } 5592 5593 options = options || {}; 5594 if (options.from) { 5595 element.css(options.from); 5596 } 5597 if (options.to) { 5598 element.css(options.to); 5599 } 5600 5601 if (options.addClass || options.removeClass) { 5602 addRemoveClassesPostDigest(element, options.addClass, options.removeClass); 5603 } 5604 5605 var runner = new $$AnimateRunner(); 5606 5607 // since there are no animations to run the runner needs to be 5608 // notified that the animation call is complete. 5609 runner.complete(); 5610 return runner; 5611 } 5612 }; 5613 5614 5615 function updateData(data, classes, value) { 5616 var changed = false; 5617 if (classes) { 5618 classes = isString(classes) ? classes.split(' ') : 5619 isArray(classes) ? classes : []; 5620 forEach(classes, function(className) { 5621 if (className) { 5622 changed = true; 5623 data[className] = value; 5624 } 5625 }); 5626 } 5627 return changed; 5628 } 5629 5630 function handleCSSClassChanges() { 5631 forEach(postDigestElements, function(element) { 5632 var data = postDigestQueue.get(element); 5633 if (data) { 5634 var existing = splitClasses(element.attr('class')); 5635 var toAdd = ''; 5636 var toRemove = ''; 5637 forEach(data, function(status, className) { 5638 var hasClass = !!existing[className]; 5639 if (status !== hasClass) { 5640 if (status) { 5641 toAdd += (toAdd.length ? ' ' : '') + className; 5642 } else { 5643 toRemove += (toRemove.length ? ' ' : '') + className; 5644 } 5645 } 5646 }); 5647 5648 forEach(element, function(elm) { 5649 if (toAdd) { 5650 jqLiteAddClass(elm, toAdd); 5651 } 5652 if (toRemove) { 5653 jqLiteRemoveClass(elm, toRemove); 5654 } 5655 }); 5656 postDigestQueue.delete(element); 5657 } 5658 }); 5659 postDigestElements.length = 0; 5660 } 5661 5662 5663 function addRemoveClassesPostDigest(element, add, remove) { 5664 var data = postDigestQueue.get(element) || {}; 5665 5666 var classesAdded = updateData(data, add, true); 5667 var classesRemoved = updateData(data, remove, false); 5668 5669 if (classesAdded || classesRemoved) { 5670 5671 postDigestQueue.set(element, data); 5672 postDigestElements.push(element); 5673 5674 if (postDigestElements.length === 1) { 5675 $rootScope.$$postDigest(handleCSSClassChanges); 5676 } 5677 } 5678 } 5679 }]; 5680}; 5681 5682/** 5683 * @ngdoc provider 5684 * @name $animateProvider 5685 * 5686 * @description 5687 * Default implementation of $animate that doesn't perform any animations, instead just 5688 * synchronously performs DOM updates and resolves the returned runner promise. 5689 * 5690 * In order to enable animations the `ngAnimate` module has to be loaded. 5691 * 5692 * To see the functional implementation check out `src/ngAnimate/animate.js`. 5693 */ 5694var $AnimateProvider = ['$provide', /** @this */ function($provide) { 5695 var provider = this; 5696 var classNameFilter = null; 5697 var customFilter = null; 5698 5699 this.$$registeredAnimations = Object.create(null); 5700 5701 /** 5702 * @ngdoc method 5703 * @name $animateProvider#register 5704 * 5705 * @description 5706 * Registers a new injectable animation factory function. The factory function produces the 5707 * animation object which contains callback functions for each event that is expected to be 5708 * animated. 5709 * 5710 * * `eventFn`: `function(element, ... , doneFunction, options)` 5711 * The element to animate, the `doneFunction` and the options fed into the animation. Depending 5712 * on the type of animation additional arguments will be injected into the animation function. The 5713 * list below explains the function signatures for the different animation methods: 5714 * 5715 * - setClass: function(element, addedClasses, removedClasses, doneFunction, options) 5716 * - addClass: function(element, addedClasses, doneFunction, options) 5717 * - removeClass: function(element, removedClasses, doneFunction, options) 5718 * - enter, leave, move: function(element, doneFunction, options) 5719 * - animate: function(element, fromStyles, toStyles, doneFunction, options) 5720 * 5721 * Make sure to trigger the `doneFunction` once the animation is fully complete. 5722 * 5723 * ```js 5724 * return { 5725 * //enter, leave, move signature 5726 * eventFn : function(element, done, options) { 5727 * //code to run the animation 5728 * //once complete, then run done() 5729 * return function endFunction(wasCancelled) { 5730 * //code to cancel the animation 5731 * } 5732 * } 5733 * } 5734 * ``` 5735 * 5736 * @param {string} name The name of the animation (this is what the class-based CSS value will be compared to). 5737 * @param {Function} factory The factory function that will be executed to return the animation 5738 * object. 5739 */ 5740 this.register = function(name, factory) { 5741 if (name && name.charAt(0) !== '.') { 5742 throw $animateMinErr('notcsel', 'Expecting class selector starting with \'.\' got \'{0}\'.', name); 5743 } 5744 5745 var key = name + '-animation'; 5746 provider.$$registeredAnimations[name.substr(1)] = key; 5747 $provide.factory(key, factory); 5748 }; 5749 5750 /** 5751 * @ngdoc method 5752 * @name $animateProvider#customFilter 5753 * 5754 * @description 5755 * Sets and/or returns the custom filter function that is used to "filter" animations, i.e. 5756 * determine if an animation is allowed or not. When no filter is specified (the default), no 5757 * animation will be blocked. Setting the `customFilter` value will only allow animations for 5758 * which the filter function's return value is truthy. 5759 * 5760 * This allows to easily create arbitrarily complex rules for filtering animations, such as 5761 * allowing specific events only, or enabling animations on specific subtrees of the DOM, etc. 5762 * Filtering animations can also boost performance for low-powered devices, as well as 5763 * applications containing a lot of structural operations. 5764 * 5765 * <div class="alert alert-success"> 5766 * **Best Practice:** 5767 * Keep the filtering function as lean as possible, because it will be called for each DOM 5768 * action (e.g. insertion, removal, class change) performed by "animation-aware" directives. 5769 * See {@link guide/animations#which-directives-support-animations- here} for a list of built-in 5770 * directives that support animations. 5771 * Performing computationally expensive or time-consuming operations on each call of the 5772 * filtering function can make your animations sluggish. 5773 * </div> 5774 * 5775 * **Note:** If present, `customFilter` will be checked before 5776 * {@link $animateProvider#classNameFilter classNameFilter}. 5777 * 5778 * @param {Function=} filterFn - The filter function which will be used to filter all animations. 5779 * If a falsy value is returned, no animation will be performed. The function will be called 5780 * with the following arguments: 5781 * - **node** `{DOMElement}` - The DOM element to be animated. 5782 * - **event** `{String}` - The name of the animation event (e.g. `enter`, `leave`, `addClass` 5783 * etc). 5784 * - **options** `{Object}` - A collection of options/styles used for the animation. 5785 * @return {Function} The current filter function or `null` if there is none set. 5786 */ 5787 this.customFilter = function(filterFn) { 5788 if (arguments.length === 1) { 5789 customFilter = isFunction(filterFn) ? filterFn : null; 5790 } 5791 5792 return customFilter; 5793 }; 5794 5795 /** 5796 * @ngdoc method 5797 * @name $animateProvider#classNameFilter 5798 * 5799 * @description 5800 * Sets and/or returns the CSS class regular expression that is checked when performing 5801 * an animation. Upon bootstrap the classNameFilter value is not set at all and will 5802 * therefore enable $animate to attempt to perform an animation on any element that is triggered. 5803 * When setting the `classNameFilter` value, animations will only be performed on elements 5804 * that successfully match the filter expression. This in turn can boost performance 5805 * for low-powered devices as well as applications containing a lot of structural operations. 5806 * 5807 * **Note:** If present, `classNameFilter` will be checked after 5808 * {@link $animateProvider#customFilter customFilter}. If `customFilter` is present and returns 5809 * false, `classNameFilter` will not be checked. 5810 * 5811 * @param {RegExp=} expression The className expression which will be checked against all animations 5812 * @return {RegExp} The current CSS className expression value. If null then there is no expression value 5813 */ 5814 this.classNameFilter = function(expression) { 5815 if (arguments.length === 1) { 5816 classNameFilter = (expression instanceof RegExp) ? expression : null; 5817 if (classNameFilter) { 5818 var reservedRegex = new RegExp('[(\\s|\\/)]' + NG_ANIMATE_CLASSNAME + '[(\\s|\\/)]'); 5819 if (reservedRegex.test(classNameFilter.toString())) { 5820 classNameFilter = null; 5821 throw $animateMinErr('nongcls', '$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME); 5822 } 5823 } 5824 } 5825 return classNameFilter; 5826 }; 5827 5828 this.$get = ['$$animateQueue', function($$animateQueue) { 5829 function domInsert(element, parentElement, afterElement) { 5830 // if for some reason the previous element was removed 5831 // from the dom sometime before this code runs then let's 5832 // just stick to using the parent element as the anchor 5833 if (afterElement) { 5834 var afterNode = extractElementNode(afterElement); 5835 if (afterNode && !afterNode.parentNode && !afterNode.previousElementSibling) { 5836 afterElement = null; 5837 } 5838 } 5839 if (afterElement) { 5840 afterElement.after(element); 5841 } else { 5842 parentElement.prepend(element); 5843 } 5844 } 5845 5846 /** 5847 * @ngdoc service 5848 * @name $animate 5849 * @description The $animate service exposes a series of DOM utility methods that provide support 5850 * for animation hooks. The default behavior is the application of DOM operations, however, 5851 * when an animation is detected (and animations are enabled), $animate will do the heavy lifting 5852 * to ensure that animation runs with the triggered DOM operation. 5853 * 5854 * By default $animate doesn't trigger any animations. This is because the `ngAnimate` module isn't 5855 * included and only when it is active then the animation hooks that `$animate` triggers will be 5856 * functional. Once active then all structural `ng-` directives will trigger animations as they perform 5857 * their DOM-related operations (enter, leave and move). Other directives such as `ngClass`, 5858 * `ngShow`, `ngHide` and `ngMessages` also provide support for animations. 5859 * 5860 * It is recommended that the`$animate` service is always used when executing DOM-related procedures within directives. 5861 * 5862 * To learn more about enabling animation support, click here to visit the 5863 * {@link ngAnimate ngAnimate module page}. 5864 */ 5865 return { 5866 // we don't call it directly since non-existant arguments may 5867 // be interpreted as null within the sub enabled function 5868 5869 /** 5870 * 5871 * @ngdoc method 5872 * @name $animate#on 5873 * @kind function 5874 * @description Sets up an event listener to fire whenever the animation event (enter, leave, move, etc...) 5875 * has fired on the given element or among any of its children. Once the listener is fired, the provided callback 5876 * is fired with the following params: 5877 * 5878 * ```js 5879 * $animate.on('enter', container, 5880 * function callback(element, phase) { 5881 * // cool we detected an enter animation within the container 5882 * } 5883 * ); 5884 * ``` 5885 * 5886 * <div class="alert alert-warning"> 5887 * **Note**: Generally, the events that are fired correspond 1:1 to `$animate` method names, 5888 * e.g. {@link ng.$animate#addClass addClass()} will fire `addClass`, and {@link ng.ngClass} 5889 * will fire `addClass` if classes are added, and `removeClass` if classes are removed. 5890 * However, there are two exceptions: 5891 * 5892 * <ul> 5893 * <li>if both an {@link ng.$animate#addClass addClass()} and a 5894 * {@link ng.$animate#removeClass removeClass()} action are performed during the same 5895 * animation, the event fired will be `setClass`. This is true even for `ngClass`.</li> 5896 * <li>an {@link ng.$animate#animate animate()} call that adds and removes classes will fire 5897 * the `setClass` event, but if it either removes or adds classes, 5898 * it will fire `animate` instead.</li> 5899 * </ul> 5900 * 5901 * </div> 5902 * 5903 * @param {string} event the animation event that will be captured (e.g. enter, leave, move, addClass, removeClass, etc...) 5904 * @param {DOMElement} container the container element that will capture each of the animation events that are fired on itself 5905 * as well as among its children 5906 * @param {Function} callback the callback function that will be fired when the listener is triggered. 5907 * 5908 * The arguments present in the callback function are: 5909 * * `element` - The captured DOM element that the animation was fired on. 5910 * * `phase` - The phase of the animation. The two possible phases are **start** (when the animation starts) and **close** (when it ends). 5911 * * `data` - an object with these properties: 5912 * * addClass - `{string|null}` - space-separated CSS classes to add to the element 5913 * * removeClass - `{string|null}` - space-separated CSS classes to remove from the element 5914 * * from - `{Object|null}` - CSS properties & values at the beginning of the animation 5915 * * to - `{Object|null}` - CSS properties & values at the end of the animation 5916 * 5917 * Note that the callback does not trigger a scope digest. Wrap your call into a 5918 * {@link $rootScope.Scope#$apply scope.$apply} to propagate changes to the scope. 5919 */ 5920 on: $$animateQueue.on, 5921 5922 /** 5923 * 5924 * @ngdoc method 5925 * @name $animate#off 5926 * @kind function 5927 * @description Deregisters an event listener based on the event which has been associated with the provided element. This method 5928 * can be used in three different ways depending on the arguments: 5929 * 5930 * ```js 5931 * // remove all the animation event listeners listening for `enter` 5932 * $animate.off('enter'); 5933 * 5934 * // remove listeners for all animation events from the container element 5935 * $animate.off(container); 5936 * 5937 * // remove all the animation event listeners listening for `enter` on the given element and its children 5938 * $animate.off('enter', container); 5939 * 5940 * // remove the event listener function provided by `callback` that is set 5941 * // to listen for `enter` on the given `container` as well as its children 5942 * $animate.off('enter', container, callback); 5943 * ``` 5944 * 5945 * @param {string|DOMElement} event|container the animation event (e.g. enter, leave, move, 5946 * addClass, removeClass, etc...), or the container element. If it is the element, all other 5947 * arguments are ignored. 5948 * @param {DOMElement=} container the container element the event listener was placed on 5949 * @param {Function=} callback the callback function that was registered as the listener 5950 */ 5951 off: $$animateQueue.off, 5952 5953 /** 5954 * @ngdoc method 5955 * @name $animate#pin 5956 * @kind function 5957 * @description Associates the provided element with a host parent element to allow the element to be animated even if it exists 5958 * outside of the DOM structure of the AngularJS application. By doing so, any animation triggered via `$animate` can be issued on the 5959 * element despite being outside the realm of the application or within another application. Say for example if the application 5960 * was bootstrapped on an element that is somewhere inside of the `<body>` tag, but we wanted to allow for an element to be situated 5961 * as a direct child of `document.body`, then this can be achieved by pinning the element via `$animate.pin(element)`. Keep in mind 5962 * that calling `$animate.pin(element, parentElement)` will not actually insert into the DOM anywhere; it will just create the association. 5963 * 5964 * Note that this feature is only active when the `ngAnimate` module is used. 5965 * 5966 * @param {DOMElement} element the external element that will be pinned 5967 * @param {DOMElement} parentElement the host parent element that will be associated with the external element 5968 */ 5969 pin: $$animateQueue.pin, 5970 5971 /** 5972 * 5973 * @ngdoc method 5974 * @name $animate#enabled 5975 * @kind function 5976 * @description Used to get and set whether animations are enabled or not on the entire application or on an element and its children. This 5977 * function can be called in four ways: 5978 * 5979 * ```js 5980 * // returns true or false 5981 * $animate.enabled(); 5982 * 5983 * // changes the enabled state for all animations 5984 * $animate.enabled(false); 5985 * $animate.enabled(true); 5986 * 5987 * // returns true or false if animations are enabled for an element 5988 * $animate.enabled(element); 5989 * 5990 * // changes the enabled state for an element and its children 5991 * $animate.enabled(element, true); 5992 * $animate.enabled(element, false); 5993 * ``` 5994 * 5995 * @param {DOMElement=} element the element that will be considered for checking/setting the enabled state 5996 * @param {boolean=} enabled whether or not the animations will be enabled for the element 5997 * 5998 * @return {boolean} whether or not animations are enabled 5999 */ 6000 enabled: $$animateQueue.enabled, 6001 6002 /** 6003 * @ngdoc method 6004 * @name $animate#cancel 6005 * @kind function 6006 * @description Cancels the provided animation and applies the end state of the animation. 6007 * Note that this does not cancel the underlying operation, e.g. the setting of classes or 6008 * adding the element to the DOM. 6009 * 6010 * @param {animationRunner} animationRunner An animation runner returned by an $animate function. 6011 * 6012 * @example 6013 <example module="animationExample" deps="angular-animate.js" animations="true" name="animate-cancel"> 6014 <file name="app.js"> 6015 angular.module('animationExample', ['ngAnimate']).component('cancelExample', { 6016 templateUrl: 'template.html', 6017 controller: function($element, $animate) { 6018 this.runner = null; 6019 6020 this.addClass = function() { 6021 this.runner = $animate.addClass($element.find('div'), 'red'); 6022 var ctrl = this; 6023 this.runner.finally(function() { 6024 ctrl.runner = null; 6025 }); 6026 }; 6027 6028 this.removeClass = function() { 6029 this.runner = $animate.removeClass($element.find('div'), 'red'); 6030 var ctrl = this; 6031 this.runner.finally(function() { 6032 ctrl.runner = null; 6033 }); 6034 }; 6035 6036 this.cancel = function() { 6037 $animate.cancel(this.runner); 6038 }; 6039 } 6040 }); 6041 </file> 6042 <file name="template.html"> 6043 <p> 6044 <button id="add" ng-click="$ctrl.addClass()">Add</button> 6045 <button ng-click="$ctrl.removeClass()">Remove</button> 6046 <br> 6047 <button id="cancel" ng-click="$ctrl.cancel()" ng-disabled="!$ctrl.runner">Cancel</button> 6048 <br> 6049 <div id="target">CSS-Animated Text</div> 6050 </p> 6051 </file> 6052 <file name="index.html"> 6053 <cancel-example></cancel-example> 6054 </file> 6055 <file name="style.css"> 6056 .red-add, .red-remove { 6057 transition: all 4s cubic-bezier(0.250, 0.460, 0.450, 0.940); 6058 } 6059 6060 .red, 6061 .red-add.red-add-active { 6062 color: #FF0000; 6063 font-size: 40px; 6064 } 6065 6066 .red-remove.red-remove-active { 6067 font-size: 10px; 6068 color: black; 6069 } 6070 6071 </file> 6072 </example> 6073 */ 6074 cancel: function(runner) { 6075 if (runner.cancel) { 6076 runner.cancel(); 6077 } 6078 }, 6079 6080 /** 6081 * 6082 * @ngdoc method 6083 * @name $animate#enter 6084 * @kind function 6085 * @description Inserts the element into the DOM either after the `after` element (if provided) or 6086 * as the first child within the `parent` element and then triggers an animation. 6087 * A promise is returned that will be resolved during the next digest once the animation 6088 * has completed. 6089 * 6090 * @param {DOMElement} element the element which will be inserted into the DOM 6091 * @param {DOMElement} parent the parent element which will append the element as 6092 * a child (so long as the after element is not present) 6093 * @param {DOMElement=} after the sibling element after which the element will be appended 6094 * @param {object=} options an optional collection of options/styles that will be applied to the element. 6095 * The object can have the following properties: 6096 * 6097 * - **addClass** - `{string}` - space-separated CSS classes to add to element 6098 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` 6099 * - **removeClass** - `{string}` - space-separated CSS classes to remove from element 6100 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` 6101 * 6102 * @return {Runner} the animation runner 6103 */ 6104 enter: function(element, parent, after, options) { 6105 parent = parent && jqLite(parent); 6106 after = after && jqLite(after); 6107 parent = parent || after.parent(); 6108 domInsert(element, parent, after); 6109 return $$animateQueue.push(element, 'enter', prepareAnimateOptions(options)); 6110 }, 6111 6112 /** 6113 * 6114 * @ngdoc method 6115 * @name $animate#move 6116 * @kind function 6117 * @description Inserts (moves) the element into its new position in the DOM either after 6118 * the `after` element (if provided) or as the first child within the `parent` element 6119 * and then triggers an animation. A promise is returned that will be resolved 6120 * during the next digest once the animation has completed. 6121 * 6122 * @param {DOMElement} element the element which will be moved into the new DOM position 6123 * @param {DOMElement} parent the parent element which will append the element as 6124 * a child (so long as the after element is not present) 6125 * @param {DOMElement=} after the sibling element after which the element will be appended 6126 * @param {object=} options an optional collection of options/styles that will be applied to the element. 6127 * The object can have the following properties: 6128 * 6129 * - **addClass** - `{string}` - space-separated CSS classes to add to element 6130 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` 6131 * - **removeClass** - `{string}` - space-separated CSS classes to remove from element 6132 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` 6133 * 6134 * @return {Runner} the animation runner 6135 */ 6136 move: function(element, parent, after, options) { 6137 parent = parent && jqLite(parent); 6138 after = after && jqLite(after); 6139 parent = parent || after.parent(); 6140 domInsert(element, parent, after); 6141 return $$animateQueue.push(element, 'move', prepareAnimateOptions(options)); 6142 }, 6143 6144 /** 6145 * @ngdoc method 6146 * @name $animate#leave 6147 * @kind function 6148 * @description Triggers an animation and then removes the element from the DOM. 6149 * When the function is called a promise is returned that will be resolved during the next 6150 * digest once the animation has completed. 6151 * 6152 * @param {DOMElement} element the element which will be removed from the DOM 6153 * @param {object=} options an optional collection of options/styles that will be applied to the element. 6154 * The object can have the following properties: 6155 * 6156 * - **addClass** - `{string}` - space-separated CSS classes to add to element 6157 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` 6158 * - **removeClass** - `{string}` - space-separated CSS classes to remove from element 6159 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` 6160 * 6161 * @return {Runner} the animation runner 6162 */ 6163 leave: function(element, options) { 6164 return $$animateQueue.push(element, 'leave', prepareAnimateOptions(options), function() { 6165 element.remove(); 6166 }); 6167 }, 6168 6169 /** 6170 * @ngdoc method 6171 * @name $animate#addClass 6172 * @kind function 6173 * 6174 * @description Triggers an addClass animation surrounding the addition of the provided CSS class(es). Upon 6175 * execution, the addClass operation will only be handled after the next digest and it will not trigger an 6176 * animation if element already contains the CSS class or if the class is removed at a later step. 6177 * Note that class-based animations are treated differently compared to structural animations 6178 * (like enter, move and leave) since the CSS classes may be added/removed at different points 6179 * depending if CSS or JavaScript animations are used. 6180 * 6181 * @param {DOMElement} element the element which the CSS classes will be applied to 6182 * @param {string} className the CSS class(es) that will be added (multiple classes are separated via spaces) 6183 * @param {object=} options an optional collection of options/styles that will be applied to the element. 6184 * The object can have the following properties: 6185 * 6186 * - **removeClass** - `{string}` - space-separated CSS classes to remove from element 6187 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` 6188 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` 6189 * 6190 * @return {Runner} animationRunner the animation runner 6191 */ 6192 addClass: function(element, className, options) { 6193 options = prepareAnimateOptions(options); 6194 options.addClass = mergeClasses(options.addclass, className); 6195 return $$animateQueue.push(element, 'addClass', options); 6196 }, 6197 6198 /** 6199 * @ngdoc method 6200 * @name $animate#removeClass 6201 * @kind function 6202 * 6203 * @description Triggers a removeClass animation surrounding the removal of the provided CSS class(es). Upon 6204 * execution, the removeClass operation will only be handled after the next digest and it will not trigger an 6205 * animation if element does not contain the CSS class or if the class is added at a later step. 6206 * Note that class-based animations are treated differently compared to structural animations 6207 * (like enter, move and leave) since the CSS classes may be added/removed at different points 6208 * depending if CSS or JavaScript animations are used. 6209 * 6210 * @param {DOMElement} element the element which the CSS classes will be applied to 6211 * @param {string} className the CSS class(es) that will be removed (multiple classes are separated via spaces) 6212 * @param {object=} options an optional collection of options/styles that will be applied to the element. 6213 * The object can have the following properties: 6214 * 6215 * - **addClass** - `{string}` - space-separated CSS classes to add to element 6216 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` 6217 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` 6218 * 6219 * @return {Runner} the animation runner 6220 */ 6221 removeClass: function(element, className, options) { 6222 options = prepareAnimateOptions(options); 6223 options.removeClass = mergeClasses(options.removeClass, className); 6224 return $$animateQueue.push(element, 'removeClass', options); 6225 }, 6226 6227 /** 6228 * @ngdoc method 6229 * @name $animate#setClass 6230 * @kind function 6231 * 6232 * @description Performs both the addition and removal of a CSS classes on an element and (during the process) 6233 * triggers an animation surrounding the class addition/removal. Much like `$animate.addClass` and 6234 * `$animate.removeClass`, `setClass` will only evaluate the classes being added/removed once a digest has 6235 * passed. Note that class-based animations are treated differently compared to structural animations 6236 * (like enter, move and leave) since the CSS classes may be added/removed at different points 6237 * depending if CSS or JavaScript animations are used. 6238 * 6239 * @param {DOMElement} element the element which the CSS classes will be applied to 6240 * @param {string} add the CSS class(es) that will be added (multiple classes are separated via spaces) 6241 * @param {string} remove the CSS class(es) that will be removed (multiple classes are separated via spaces) 6242 * @param {object=} options an optional collection of options/styles that will be applied to the element. 6243 * The object can have the following properties: 6244 * 6245 * - **addClass** - `{string}` - space-separated CSS classes to add to element 6246 * - **removeClass** - `{string}` - space-separated CSS classes to remove from element 6247 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` 6248 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` 6249 * 6250 * @return {Runner} the animation runner 6251 */ 6252 setClass: function(element, add, remove, options) { 6253 options = prepareAnimateOptions(options); 6254 options.addClass = mergeClasses(options.addClass, add); 6255 options.removeClass = mergeClasses(options.removeClass, remove); 6256 return $$animateQueue.push(element, 'setClass', options); 6257 }, 6258 6259 /** 6260 * @ngdoc method 6261 * @name $animate#animate 6262 * @kind function 6263 * 6264 * @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element. 6265 * If any detected CSS transition, keyframe or JavaScript matches the provided className value, then the animation will take 6266 * on the provided styles. For example, if a transition animation is set for the given className, then the provided `from` and 6267 * `to` styles will be applied alongside the given transition. If the CSS style provided in `from` does not have a corresponding 6268 * style in `to`, the style in `from` is applied immediately, and no animation is run. 6269 * If a JavaScript animation is detected then the provided styles will be given in as function parameters into the `animate` 6270 * method (or as part of the `options` parameter): 6271 * 6272 * ```js 6273 * ngModule.animation('.my-inline-animation', function() { 6274 * return { 6275 * animate : function(element, from, to, done, options) { 6276 * //animation 6277 * done(); 6278 * } 6279 * } 6280 * }); 6281 * ``` 6282 * 6283 * @param {DOMElement} element the element which the CSS styles will be applied to 6284 * @param {object} from the from (starting) CSS styles that will be applied to the element and across the animation. 6285 * @param {object} to the to (destination) CSS styles that will be applied to the element and across the animation. 6286 * @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If 6287 * this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element. 6288 * (Note that if no animation is detected then this value will not be applied to the element.) 6289 * @param {object=} options an optional collection of options/styles that will be applied to the element. 6290 * The object can have the following properties: 6291 * 6292 * - **addClass** - `{string}` - space-separated CSS classes to add to element 6293 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` 6294 * - **removeClass** - `{string}` - space-separated CSS classes to remove from element 6295 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` 6296 * 6297 * @return {Runner} the animation runner 6298 */ 6299 animate: function(element, from, to, className, options) { 6300 options = prepareAnimateOptions(options); 6301 options.from = options.from ? extend(options.from, from) : from; 6302 options.to = options.to ? extend(options.to, to) : to; 6303 6304 className = className || 'ng-inline-animate'; 6305 options.tempClasses = mergeClasses(options.tempClasses, className); 6306 return $$animateQueue.push(element, 'animate', options); 6307 } 6308 }; 6309 }]; 6310}]; 6311 6312var $$AnimateAsyncRunFactoryProvider = /** @this */ function() { 6313 this.$get = ['$$rAF', function($$rAF) { 6314 var waitQueue = []; 6315 6316 function waitForTick(fn) { 6317 waitQueue.push(fn); 6318 if (waitQueue.length > 1) return; 6319 $$rAF(function() { 6320 for (var i = 0; i < waitQueue.length; i++) { 6321 waitQueue[i](); 6322 } 6323 waitQueue = []; 6324 }); 6325 } 6326 6327 return function() { 6328 var passed = false; 6329 waitForTick(function() { 6330 passed = true; 6331 }); 6332 return function(callback) { 6333 if (passed) { 6334 callback(); 6335 } else { 6336 waitForTick(callback); 6337 } 6338 }; 6339 }; 6340 }]; 6341}; 6342 6343var $$AnimateRunnerFactoryProvider = /** @this */ function() { 6344 this.$get = ['$q', '$sniffer', '$$animateAsyncRun', '$$isDocumentHidden', '$timeout', 6345 function($q, $sniffer, $$animateAsyncRun, $$isDocumentHidden, $timeout) { 6346 6347 var INITIAL_STATE = 0; 6348 var DONE_PENDING_STATE = 1; 6349 var DONE_COMPLETE_STATE = 2; 6350 6351 AnimateRunner.chain = function(chain, callback) { 6352 var index = 0; 6353 6354 next(); 6355 function next() { 6356 if (index === chain.length) { 6357 callback(true); 6358 return; 6359 } 6360 6361 chain[index](function(response) { 6362 if (response === false) { 6363 callback(false); 6364 return; 6365 } 6366 index++; 6367 next(); 6368 }); 6369 } 6370 }; 6371 6372 AnimateRunner.all = function(runners, callback) { 6373 var count = 0; 6374 var status = true; 6375 forEach(runners, function(runner) { 6376 runner.done(onProgress); 6377 }); 6378 6379 function onProgress(response) { 6380 status = status && response; 6381 if (++count === runners.length) { 6382 callback(status); 6383 } 6384 } 6385 }; 6386 6387 function AnimateRunner(host) { 6388 this.setHost(host); 6389 6390 var rafTick = $$animateAsyncRun(); 6391 var timeoutTick = function(fn) { 6392 $timeout(fn, 0, false); 6393 }; 6394 6395 this._doneCallbacks = []; 6396 this._tick = function(fn) { 6397 if ($$isDocumentHidden()) { 6398 timeoutTick(fn); 6399 } else { 6400 rafTick(fn); 6401 } 6402 }; 6403 this._state = 0; 6404 } 6405 6406 AnimateRunner.prototype = { 6407 setHost: function(host) { 6408 this.host = host || {}; 6409 }, 6410 6411 done: function(fn) { 6412 if (this._state === DONE_COMPLETE_STATE) { 6413 fn(); 6414 } else { 6415 this._doneCallbacks.push(fn); 6416 } 6417 }, 6418 6419 progress: noop, 6420 6421 getPromise: function() { 6422 if (!this.promise) { 6423 var self = this; 6424 this.promise = $q(function(resolve, reject) { 6425 self.done(function(status) { 6426 if (status === false) { 6427 reject(); 6428 } else { 6429 resolve(); 6430 } 6431 }); 6432 }); 6433 } 6434 return this.promise; 6435 }, 6436 6437 then: function(resolveHandler, rejectHandler) { 6438 return this.getPromise().then(resolveHandler, rejectHandler); 6439 }, 6440 6441 'catch': function(handler) { 6442 return this.getPromise()['catch'](handler); 6443 }, 6444 6445 'finally': function(handler) { 6446 return this.getPromise()['finally'](handler); 6447 }, 6448 6449 pause: function() { 6450 if (this.host.pause) { 6451 this.host.pause(); 6452 } 6453 }, 6454 6455 resume: function() { 6456 if (this.host.resume) { 6457 this.host.resume(); 6458 } 6459 }, 6460 6461 end: function() { 6462 if (this.host.end) { 6463 this.host.end(); 6464 } 6465 this._resolve(true); 6466 }, 6467 6468 cancel: function() { 6469 if (this.host.cancel) { 6470 this.host.cancel(); 6471 } 6472 this._resolve(false); 6473 }, 6474 6475 complete: function(response) { 6476 var self = this; 6477 if (self._state === INITIAL_STATE) { 6478 self._state = DONE_PENDING_STATE; 6479 self._tick(function() { 6480 self._resolve(response); 6481 }); 6482 } 6483 }, 6484 6485 _resolve: function(response) { 6486 if (this._state !== DONE_COMPLETE_STATE) { 6487 forEach(this._doneCallbacks, function(fn) { 6488 fn(response); 6489 }); 6490 this._doneCallbacks.length = 0; 6491 this._state = DONE_COMPLETE_STATE; 6492 } 6493 } 6494 }; 6495 6496 return AnimateRunner; 6497 }]; 6498}; 6499 6500/* exported $CoreAnimateCssProvider */ 6501 6502/** 6503 * @ngdoc service 6504 * @name $animateCss 6505 * @kind object 6506 * @this 6507 * 6508 * @description 6509 * This is the core version of `$animateCss`. By default, only when the `ngAnimate` is included, 6510 * then the `$animateCss` service will actually perform animations. 6511 * 6512 * Click here {@link ngAnimate.$animateCss to read the documentation for $animateCss}. 6513 */ 6514var $CoreAnimateCssProvider = function() { 6515 this.$get = ['$$rAF', '$q', '$$AnimateRunner', function($$rAF, $q, $$AnimateRunner) { 6516 6517 return function(element, initialOptions) { 6518 // all of the animation functions should create 6519 // a copy of the options data, however, if a 6520 // parent service has already created a copy then 6521 // we should stick to using that 6522 var options = initialOptions || {}; 6523 if (!options.$$prepared) { 6524 options = copy(options); 6525 } 6526 6527 // there is no point in applying the styles since 6528 // there is no animation that goes on at all in 6529 // this version of $animateCss. 6530 if (options.cleanupStyles) { 6531 options.from = options.to = null; 6532 } 6533 6534 if (options.from) { 6535 element.css(options.from); 6536 options.from = null; 6537 } 6538 6539 var closed, runner = new $$AnimateRunner(); 6540 return { 6541 start: run, 6542 end: run 6543 }; 6544 6545 function run() { 6546 $$rAF(function() { 6547 applyAnimationContents(); 6548 if (!closed) { 6549 runner.complete(); 6550 } 6551 closed = true; 6552 }); 6553 return runner; 6554 } 6555 6556 function applyAnimationContents() { 6557 if (options.addClass) { 6558 element.addClass(options.addClass); 6559 options.addClass = null; 6560 } 6561 if (options.removeClass) { 6562 element.removeClass(options.removeClass); 6563 options.removeClass = null; 6564 } 6565 if (options.to) { 6566 element.css(options.to); 6567 options.to = null; 6568 } 6569 } 6570 }; 6571 }]; 6572}; 6573 6574/* global getHash: true, stripHash: false */ 6575 6576function getHash(url) { 6577 var index = url.indexOf('#'); 6578 return index === -1 ? '' : url.substr(index); 6579} 6580 6581function trimEmptyHash(url) { 6582 return url.replace(/#$/, ''); 6583} 6584 6585/** 6586 * ! This is a private undocumented service ! 6587 * 6588 * @name $browser 6589 * @requires $log 6590 * @description 6591 * This object has two goals: 6592 * 6593 * - hide all the global state in the browser caused by the window object 6594 * - abstract away all the browser specific features and inconsistencies 6595 * 6596 * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser` 6597 * service, which can be used for convenient testing of the application without the interaction with 6598 * the real browser apis. 6599 */ 6600/** 6601 * @param {object} window The global window object. 6602 * @param {object} document jQuery wrapped document. 6603 * @param {object} $log window.console or an object with the same interface. 6604 * @param {object} $sniffer $sniffer service 6605 */ 6606function Browser(window, document, $log, $sniffer, $$taskTrackerFactory) { 6607 var self = this, 6608 location = window.location, 6609 history = window.history, 6610 setTimeout = window.setTimeout, 6611 clearTimeout = window.clearTimeout, 6612 pendingDeferIds = {}, 6613 taskTracker = $$taskTrackerFactory($log); 6614 6615 self.isMock = false; 6616 6617 ////////////////////////////////////////////////////////////// 6618 // Task-tracking API 6619 ////////////////////////////////////////////////////////////// 6620 6621 // TODO(vojta): remove this temporary api 6622 self.$$completeOutstandingRequest = taskTracker.completeTask; 6623 self.$$incOutstandingRequestCount = taskTracker.incTaskCount; 6624 6625 // TODO(vojta): prefix this method with $$ ? 6626 self.notifyWhenNoOutstandingRequests = taskTracker.notifyWhenNoPendingTasks; 6627 6628 ////////////////////////////////////////////////////////////// 6629 // URL API 6630 ////////////////////////////////////////////////////////////// 6631 6632 var cachedState, lastHistoryState, 6633 lastBrowserUrl = location.href, 6634 baseElement = document.find('base'), 6635 pendingLocation = null, 6636 getCurrentState = !$sniffer.history ? noop : function getCurrentState() { 6637 try { 6638 return history.state; 6639 } catch (e) { 6640 // MSIE can reportedly throw when there is no state (UNCONFIRMED). 6641 } 6642 }; 6643 6644 cacheState(); 6645 6646 /** 6647 * @name $browser#url 6648 * 6649 * @description 6650 * GETTER: 6651 * Without any argument, this method just returns current value of `location.href` (with a 6652 * trailing `#` stripped of if the hash is empty). 6653 * 6654 * SETTER: 6655 * With at least one argument, this method sets url to new value. 6656 * If html5 history api supported, `pushState`/`replaceState` is used, otherwise 6657 * `location.href`/`location.replace` is used. 6658 * Returns its own instance to allow chaining. 6659 * 6660 * NOTE: this api is intended for use only by the `$location` service. Please use the 6661 * {@link ng.$location $location service} to change url. 6662 * 6663 * @param {string} url New url (when used as setter) 6664 * @param {boolean=} replace Should new url replace current history record? 6665 * @param {object=} state State object to use with `pushState`/`replaceState` 6666 */ 6667 self.url = function(url, replace, state) { 6668 // In modern browsers `history.state` is `null` by default; treating it separately 6669 // from `undefined` would cause `$browser.url('/foo')` to change `history.state` 6670 // to undefined via `pushState`. Instead, let's change `undefined` to `null` here. 6671 if (isUndefined(state)) { 6672 state = null; 6673 } 6674 6675 // Android Browser BFCache causes location, history reference to become stale. 6676 if (location !== window.location) location = window.location; 6677 if (history !== window.history) history = window.history; 6678 6679 // setter 6680 if (url) { 6681 var sameState = lastHistoryState === state; 6682 6683 // Normalize the inputted URL 6684 url = urlResolve(url).href; 6685 6686 // Don't change anything if previous and current URLs and states match. This also prevents 6687 // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode. 6688 // See https://github.com/angular/angular.js/commit/ffb2701 6689 if (lastBrowserUrl === url && (!$sniffer.history || sameState)) { 6690 return self; 6691 } 6692 var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url); 6693 lastBrowserUrl = url; 6694 lastHistoryState = state; 6695 // Don't use history API if only the hash changed 6696 // due to a bug in IE10/IE11 which leads 6697 // to not firing a `hashchange` nor `popstate` event 6698 // in some cases (see #9143). 6699 if ($sniffer.history && (!sameBase || !sameState)) { 6700 history[replace ? 'replaceState' : 'pushState'](state, '', url); 6701 cacheState(); 6702 } else { 6703 if (!sameBase) { 6704 pendingLocation = url; 6705 } 6706 if (replace) { 6707 location.replace(url); 6708 } else if (!sameBase) { 6709 location.href = url; 6710 } else { 6711 location.hash = getHash(url); 6712 } 6713 if (location.href !== url) { 6714 pendingLocation = url; 6715 } 6716 } 6717 if (pendingLocation) { 6718 pendingLocation = url; 6719 } 6720 return self; 6721 // getter 6722 } else { 6723 // - pendingLocation is needed as browsers don't allow to read out 6724 // the new location.href if a reload happened or if there is a bug like in iOS 9 (see 6725 // https://openradar.appspot.com/22186109). 6726 return trimEmptyHash(pendingLocation || location.href); 6727 } 6728 }; 6729 6730 /** 6731 * @name $browser#state 6732 * 6733 * @description 6734 * This method is a getter. 6735 * 6736 * Return history.state or null if history.state is undefined. 6737 * 6738 * @returns {object} state 6739 */ 6740 self.state = function() { 6741 return cachedState; 6742 }; 6743 6744 var urlChangeListeners = [], 6745 urlChangeInit = false; 6746 6747 function cacheStateAndFireUrlChange() { 6748 pendingLocation = null; 6749 fireStateOrUrlChange(); 6750 } 6751 6752 // This variable should be used *only* inside the cacheState function. 6753 var lastCachedState = null; 6754 function cacheState() { 6755 // This should be the only place in $browser where `history.state` is read. 6756 cachedState = getCurrentState(); 6757 cachedState = isUndefined(cachedState) ? null : cachedState; 6758 6759 // Prevent callbacks fo fire twice if both hashchange & popstate were fired. 6760 if (equals(cachedState, lastCachedState)) { 6761 cachedState = lastCachedState; 6762 } 6763 6764 lastCachedState = cachedState; 6765 lastHistoryState = cachedState; 6766 } 6767 6768 function fireStateOrUrlChange() { 6769 var prevLastHistoryState = lastHistoryState; 6770 cacheState(); 6771 6772 if (lastBrowserUrl === self.url() && prevLastHistoryState === cachedState) { 6773 return; 6774 } 6775 6776 lastBrowserUrl = self.url(); 6777 lastHistoryState = cachedState; 6778 forEach(urlChangeListeners, function(listener) { 6779 listener(self.url(), cachedState); 6780 }); 6781 } 6782 6783 /** 6784 * @name $browser#onUrlChange 6785 * 6786 * @description 6787 * Register callback function that will be called, when url changes. 6788 * 6789 * It's only called when the url is changed from outside of AngularJS: 6790 * - user types different url into address bar 6791 * - user clicks on history (forward/back) button 6792 * - user clicks on a link 6793 * 6794 * It's not called when url is changed by $browser.url() method 6795 * 6796 * The listener gets called with new url as parameter. 6797 * 6798 * NOTE: this api is intended for use only by the $location service. Please use the 6799 * {@link ng.$location $location service} to monitor url changes in AngularJS apps. 6800 * 6801 * @param {function(string)} listener Listener function to be called when url changes. 6802 * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous. 6803 */ 6804 self.onUrlChange = function(callback) { 6805 // TODO(vojta): refactor to use node's syntax for events 6806 if (!urlChangeInit) { 6807 // We listen on both (hashchange/popstate) when available, as some browsers don't 6808 // fire popstate when user changes the address bar and don't fire hashchange when url 6809 // changed by push/replaceState 6810 6811 // html5 history api - popstate event 6812 if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange); 6813 // hashchange event 6814 jqLite(window).on('hashchange', cacheStateAndFireUrlChange); 6815 6816 urlChangeInit = true; 6817 } 6818 6819 urlChangeListeners.push(callback); 6820 return callback; 6821 }; 6822 6823 /** 6824 * @private 6825 * Remove popstate and hashchange handler from window. 6826 * 6827 * NOTE: this api is intended for use only by $rootScope. 6828 */ 6829 self.$$applicationDestroyed = function() { 6830 jqLite(window).off('hashchange popstate', cacheStateAndFireUrlChange); 6831 }; 6832 6833 /** 6834 * Checks whether the url has changed outside of AngularJS. 6835 * Needs to be exported to be able to check for changes that have been done in sync, 6836 * as hashchange/popstate events fire in async. 6837 */ 6838 self.$$checkUrlChange = fireStateOrUrlChange; 6839 6840 ////////////////////////////////////////////////////////////// 6841 // Misc API 6842 ////////////////////////////////////////////////////////////// 6843 6844 /** 6845 * @name $browser#baseHref 6846 * 6847 * @description 6848 * Returns current <base href> 6849 * (always relative - without domain) 6850 * 6851 * @returns {string} The current base href 6852 */ 6853 self.baseHref = function() { 6854 var href = baseElement.attr('href'); 6855 return href ? href.replace(/^(https?:)?\/\/[^/]*/, '') : ''; 6856 }; 6857 6858 /** 6859 * @name $browser#defer 6860 * @param {function()} fn A function, who's execution should be deferred. 6861 * @param {number=} [delay=0] Number of milliseconds to defer the function execution. 6862 * @param {string=} [taskType=DEFAULT_TASK_TYPE] The type of task that is deferred. 6863 * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`. 6864 * 6865 * @description 6866 * Executes a fn asynchronously via `setTimeout(fn, delay)`. 6867 * 6868 * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using 6869 * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed 6870 * via `$browser.defer.flush()`. 6871 * 6872 */ 6873 self.defer = function(fn, delay, taskType) { 6874 var timeoutId; 6875 6876 delay = delay || 0; 6877 taskType = taskType || taskTracker.DEFAULT_TASK_TYPE; 6878 6879 taskTracker.incTaskCount(taskType); 6880 timeoutId = setTimeout(function() { 6881 delete pendingDeferIds[timeoutId]; 6882 taskTracker.completeTask(fn, taskType); 6883 }, delay); 6884 pendingDeferIds[timeoutId] = taskType; 6885 6886 return timeoutId; 6887 }; 6888 6889 6890 /** 6891 * @name $browser#defer.cancel 6892 * 6893 * @description 6894 * Cancels a deferred task identified with `deferId`. 6895 * 6896 * @param {*} deferId Token returned by the `$browser.defer` function. 6897 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 6898 * canceled. 6899 */ 6900 self.defer.cancel = function(deferId) { 6901 if (pendingDeferIds.hasOwnProperty(deferId)) { 6902 var taskType = pendingDeferIds[deferId]; 6903 delete pendingDeferIds[deferId]; 6904 clearTimeout(deferId); 6905 taskTracker.completeTask(noop, taskType); 6906 return true; 6907 } 6908 return false; 6909 }; 6910 6911} 6912 6913/** @this */ 6914function $BrowserProvider() { 6915 this.$get = ['$window', '$log', '$sniffer', '$document', '$$taskTrackerFactory', 6916 function($window, $log, $sniffer, $document, $$taskTrackerFactory) { 6917 return new Browser($window, $document, $log, $sniffer, $$taskTrackerFactory); 6918 }]; 6919} 6920 6921/** 6922 * @ngdoc service 6923 * @name $cacheFactory 6924 * @this 6925 * 6926 * @description 6927 * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to 6928 * them. 6929 * 6930 * ```js 6931 * 6932 * var cache = $cacheFactory('cacheId'); 6933 * expect($cacheFactory.get('cacheId')).toBe(cache); 6934 * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined(); 6935 * 6936 * cache.put("key", "value"); 6937 * cache.put("another key", "another value"); 6938 * 6939 * // We've specified no options on creation 6940 * expect(cache.info()).toEqual({id: 'cacheId', size: 2}); 6941 * 6942 * ``` 6943 * 6944 * 6945 * @param {string} cacheId Name or id of the newly created cache. 6946 * @param {object=} options Options object that specifies the cache behavior. Properties: 6947 * 6948 * - `{number=}` `capacity` â turns the cache into LRU cache. 6949 * 6950 * @returns {object} Newly created cache object with the following set of methods: 6951 * 6952 * - `{object}` `info()` â Returns id, size, and options of cache. 6953 * - `{{*}}` `put({string} key, {*} value)` â Puts a new key-value pair into the cache and returns 6954 * it. 6955 * - `{{*}}` `get({string} key)` â Returns cached value for `key` or undefined for cache miss. 6956 * - `{void}` `remove({string} key)` â Removes a key-value pair from the cache. 6957 * - `{void}` `removeAll()` â Removes all cached values. 6958 * - `{void}` `destroy()` â Removes references to this cache from $cacheFactory. 6959 * 6960 * @example 6961 <example module="cacheExampleApp" name="cache-factory"> 6962 <file name="index.html"> 6963 <div ng-controller="CacheController"> 6964 <input ng-model="newCacheKey" placeholder="Key"> 6965 <input ng-model="newCacheValue" placeholder="Value"> 6966 <button ng-click="put(newCacheKey, newCacheValue)">Cache</button> 6967 6968 <p ng-if="keys.length">Cached Values</p> 6969 <div ng-repeat="key in keys"> 6970 <span ng-bind="key"></span> 6971 <span>: </span> 6972 <b ng-bind="cache.get(key)"></b> 6973 </div> 6974 6975 <p>Cache Info</p> 6976 <div ng-repeat="(key, value) in cache.info()"> 6977 <span ng-bind="key"></span> 6978 <span>: </span> 6979 <b ng-bind="value"></b> 6980 </div> 6981 </div> 6982 </file> 6983 <file name="script.js"> 6984 angular.module('cacheExampleApp', []). 6985 controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) { 6986 $scope.keys = []; 6987 $scope.cache = $cacheFactory('cacheId'); 6988 $scope.put = function(key, value) { 6989 if (angular.isUndefined($scope.cache.get(key))) { 6990 $scope.keys.push(key); 6991 } 6992 $scope.cache.put(key, angular.isUndefined(value) ? null : value); 6993 }; 6994 }]); 6995 </file> 6996 <file name="style.css"> 6997 p { 6998 margin: 10px 0 3px; 6999 } 7000 </file> 7001 </example> 7002 */ 7003function $CacheFactoryProvider() { 7004 7005 this.$get = function() { 7006 var caches = {}; 7007 7008 function cacheFactory(cacheId, options) { 7009 if (cacheId in caches) { 7010 throw minErr('$cacheFactory')('iid', 'CacheId \'{0}\' is already taken!', cacheId); 7011 } 7012 7013 var size = 0, 7014 stats = extend({}, options, {id: cacheId}), 7015 data = createMap(), 7016 capacity = (options && options.capacity) || Number.MAX_VALUE, 7017 lruHash = createMap(), 7018 freshEnd = null, 7019 staleEnd = null; 7020 7021 /** 7022 * @ngdoc type 7023 * @name $cacheFactory.Cache 7024 * 7025 * @description 7026 * A cache object used to store and retrieve data, primarily used by 7027 * {@link $templateRequest $templateRequest} and the {@link ng.directive:script script} 7028 * directive to cache templates and other data. 7029 * 7030 * ```js 7031 * angular.module('superCache') 7032 * .factory('superCache', ['$cacheFactory', function($cacheFactory) { 7033 * return $cacheFactory('super-cache'); 7034 * }]); 7035 * ``` 7036 * 7037 * Example test: 7038 * 7039 * ```js 7040 * it('should behave like a cache', inject(function(superCache) { 7041 * superCache.put('key', 'value'); 7042 * superCache.put('another key', 'another value'); 7043 * 7044 * expect(superCache.info()).toEqual({ 7045 * id: 'super-cache', 7046 * size: 2 7047 * }); 7048 * 7049 * superCache.remove('another key'); 7050 * expect(superCache.get('another key')).toBeUndefined(); 7051 * 7052 * superCache.removeAll(); 7053 * expect(superCache.info()).toEqual({ 7054 * id: 'super-cache', 7055 * size: 0 7056 * }); 7057 * })); 7058 * ``` 7059 */ 7060 return (caches[cacheId] = { 7061 7062 /** 7063 * @ngdoc method 7064 * @name $cacheFactory.Cache#put 7065 * @kind function 7066 * 7067 * @description 7068 * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be 7069 * retrieved later, and incrementing the size of the cache if the key was not already 7070 * present in the cache. If behaving like an LRU cache, it will also remove stale 7071 * entries from the set. 7072 * 7073 * It will not insert undefined values into the cache. 7074 * 7075 * @param {string} key the key under which the cached data is stored. 7076 * @param {*} value the value to store alongside the key. If it is undefined, the key 7077 * will not be stored. 7078 * @returns {*} the value stored. 7079 */ 7080 put: function(key, value) { 7081 if (isUndefined(value)) return; 7082 if (capacity < Number.MAX_VALUE) { 7083 var lruEntry = lruHash[key] || (lruHash[key] = {key: key}); 7084 7085 refresh(lruEntry); 7086 } 7087 7088 if (!(key in data)) size++; 7089 data[key] = value; 7090 7091 if (size > capacity) { 7092 this.remove(staleEnd.key); 7093 } 7094 7095 return value; 7096 }, 7097 7098 /** 7099 * @ngdoc method 7100 * @name $cacheFactory.Cache#get 7101 * @kind function 7102 * 7103 * @description 7104 * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object. 7105 * 7106 * @param {string} key the key of the data to be retrieved 7107 * @returns {*} the value stored. 7108 */ 7109 get: function(key) { 7110 if (capacity < Number.MAX_VALUE) { 7111 var lruEntry = lruHash[key]; 7112 7113 if (!lruEntry) return; 7114 7115 refresh(lruEntry); 7116 } 7117 7118 return data[key]; 7119 }, 7120 7121 7122 /** 7123 * @ngdoc method 7124 * @name $cacheFactory.Cache#remove 7125 * @kind function 7126 * 7127 * @description 7128 * Removes an entry from the {@link $cacheFactory.Cache Cache} object. 7129 * 7130 * @param {string} key the key of the entry to be removed 7131 */ 7132 remove: function(key) { 7133 if (capacity < Number.MAX_VALUE) { 7134 var lruEntry = lruHash[key]; 7135 7136 if (!lruEntry) return; 7137 7138 if (lruEntry === freshEnd) freshEnd = lruEntry.p; 7139 if (lruEntry === staleEnd) staleEnd = lruEntry.n; 7140 link(lruEntry.n,lruEntry.p); 7141 7142 delete lruHash[key]; 7143 } 7144 7145 if (!(key in data)) return; 7146 7147 delete data[key]; 7148 size--; 7149 }, 7150 7151 7152 /** 7153 * @ngdoc method 7154 * @name $cacheFactory.Cache#removeAll 7155 * @kind function 7156 * 7157 * @description 7158 * Clears the cache object of any entries. 7159 */ 7160 removeAll: function() { 7161 data = createMap(); 7162 size = 0; 7163 lruHash = createMap(); 7164 freshEnd = staleEnd = null; 7165 }, 7166 7167 7168 /** 7169 * @ngdoc method 7170 * @name $cacheFactory.Cache#destroy 7171 * @kind function 7172 * 7173 * @description 7174 * Destroys the {@link $cacheFactory.Cache Cache} object entirely, 7175 * removing it from the {@link $cacheFactory $cacheFactory} set. 7176 */ 7177 destroy: function() { 7178 data = null; 7179 stats = null; 7180 lruHash = null; 7181 delete caches[cacheId]; 7182 }, 7183 7184 7185 /** 7186 * @ngdoc method 7187 * @name $cacheFactory.Cache#info 7188 * @kind function 7189 * 7190 * @description 7191 * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}. 7192 * 7193 * @returns {object} an object with the following properties: 7194 * <ul> 7195 * <li>**id**: the id of the cache instance</li> 7196 * <li>**size**: the number of entries kept in the cache instance</li> 7197 * <li>**...**: any additional properties from the options object when creating the 7198 * cache.</li> 7199 * </ul> 7200 */ 7201 info: function() { 7202 return extend({}, stats, {size: size}); 7203 } 7204 }); 7205 7206 7207 /** 7208 * makes the `entry` the freshEnd of the LRU linked list 7209 */ 7210 function refresh(entry) { 7211 if (entry !== freshEnd) { 7212 if (!staleEnd) { 7213 staleEnd = entry; 7214 } else if (staleEnd === entry) { 7215 staleEnd = entry.n; 7216 } 7217 7218 link(entry.n, entry.p); 7219 link(entry, freshEnd); 7220 freshEnd = entry; 7221 freshEnd.n = null; 7222 } 7223 } 7224 7225 7226 /** 7227 * bidirectionally links two entries of the LRU linked list 7228 */ 7229 function link(nextEntry, prevEntry) { 7230 if (nextEntry !== prevEntry) { 7231 if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify 7232 if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify 7233 } 7234 } 7235 } 7236 7237 7238 /** 7239 * @ngdoc method 7240 * @name $cacheFactory#info 7241 * 7242 * @description 7243 * Get information about all the caches that have been created 7244 * 7245 * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info` 7246 */ 7247 cacheFactory.info = function() { 7248 var info = {}; 7249 forEach(caches, function(cache, cacheId) { 7250 info[cacheId] = cache.info(); 7251 }); 7252 return info; 7253 }; 7254 7255 7256 /** 7257 * @ngdoc method 7258 * @name $cacheFactory#get 7259 * 7260 * @description 7261 * Get access to a cache object by the `cacheId` used when it was created. 7262 * 7263 * @param {string} cacheId Name or id of a cache to access. 7264 * @returns {object} Cache object identified by the cacheId or undefined if no such cache. 7265 */ 7266 cacheFactory.get = function(cacheId) { 7267 return caches[cacheId]; 7268 }; 7269 7270 7271 return cacheFactory; 7272 }; 7273} 7274 7275/** 7276 * @ngdoc service 7277 * @name $templateCache 7278 * @this 7279 * 7280 * @description 7281 * `$templateCache` is a {@link $cacheFactory.Cache Cache object} created by the 7282 * {@link ng.$cacheFactory $cacheFactory}. 7283 * 7284 * The first time a template is used, it is loaded in the template cache for quick retrieval. You 7285 * can load templates directly into the cache in a `script` tag, by using {@link $templateRequest}, 7286 * or by consuming the `$templateCache` service directly. 7287 * 7288 * Adding via the `script` tag: 7289 * 7290 * ```html 7291 * <script type="text/ng-template" id="templateId.html"> 7292 * <p>This is the content of the template</p> 7293 * </script> 7294 * ``` 7295 * 7296 * **Note:** the `script` tag containing the template does not need to be included in the `head` of 7297 * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (e.g. 7298 * element with {@link ngApp} attribute), otherwise the template will be ignored. 7299 * 7300 * Adding via the `$templateCache` service: 7301 * 7302 * ```js 7303 * var myApp = angular.module('myApp', []); 7304 * myApp.run(function($templateCache) { 7305 * $templateCache.put('templateId.html', 'This is the content of the template'); 7306 * }); 7307 * ``` 7308 * 7309 * To retrieve the template later, simply use it in your component: 7310 * ```js 7311 * myApp.component('myComponent', { 7312 * templateUrl: 'templateId.html' 7313 * }); 7314 * ``` 7315 * 7316 * or get it via the `$templateCache` service: 7317 * ```js 7318 * $templateCache.get('templateId.html') 7319 * ``` 7320 * 7321 */ 7322function $TemplateCacheProvider() { 7323 this.$get = ['$cacheFactory', function($cacheFactory) { 7324 return $cacheFactory('templates'); 7325 }]; 7326} 7327 7328/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 7329 * Any commits to this file should be reviewed with security in mind. * 7330 * Changes to this file can potentially create security vulnerabilities. * 7331 * An approval from 2 Core members with history of modifying * 7332 * this file is required. * 7333 * * 7334 * Does the change somehow allow for arbitrary javascript to be executed? * 7335 * Or allows for someone to change the prototype of built-in objects? * 7336 * Or gives undesired access to variables like document or window? * 7337 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 7338 7339/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! 7340 * 7341 * DOM-related variables: 7342 * 7343 * - "node" - DOM Node 7344 * - "element" - DOM Element or Node 7345 * - "$node" or "$element" - jqLite-wrapped node or element 7346 * 7347 * 7348 * Compiler related stuff: 7349 * 7350 * - "linkFn" - linking fn of a single directive 7351 * - "nodeLinkFn" - function that aggregates all linking fns for a particular node 7352 * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node 7353 * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList) 7354 */ 7355 7356 7357/** 7358 * @ngdoc service 7359 * @name $compile 7360 * @kind function 7361 * 7362 * @description 7363 * Compiles an HTML string or DOM into a template and produces a template function, which 7364 * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together. 7365 * 7366 * The compilation is a process of walking the DOM tree and matching DOM elements to 7367 * {@link ng.$compileProvider#directive directives}. 7368 * 7369 * <div class="alert alert-warning"> 7370 * **Note:** This document is an in-depth reference of all directive options. 7371 * For a gentle introduction to directives with examples of common use cases, 7372 * see the {@link guide/directive directive guide}. 7373 * </div> 7374 * 7375 * ## Comprehensive Directive API 7376 * 7377 * There are many different options for a directive. 7378 * 7379 * The difference resides in the return value of the factory function. 7380 * You can either return a {@link $compile#directive-definition-object Directive Definition Object (see below)} 7381 * that defines the directive properties, or just the `postLink` function (all other properties will have 7382 * the default values). 7383 * 7384 * <div class="alert alert-success"> 7385 * **Best Practice:** It's recommended to use the "directive definition object" form. 7386 * </div> 7387 * 7388 * Here's an example directive declared with a Directive Definition Object: 7389 * 7390 * ```js 7391 * var myModule = angular.module(...); 7392 * 7393 * myModule.directive('directiveName', function factory(injectables) { 7394 * var directiveDefinitionObject = { 7395 * {@link $compile#-priority- priority}: 0, 7396 * {@link $compile#-template- template}: '<div></div>', // or // function(tElement, tAttrs) { ... }, 7397 * // or 7398 * // {@link $compile#-templateurl- templateUrl}: 'directive.html', // or // function(tElement, tAttrs) { ... }, 7399 * {@link $compile#-transclude- transclude}: false, 7400 * {@link $compile#-restrict- restrict}: 'A', 7401 * {@link $compile#-templatenamespace- templateNamespace}: 'html', 7402 * {@link $compile#-scope- scope}: false, 7403 * {@link $compile#-controller- controller}: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, 7404 * {@link $compile#-controlleras- controllerAs}: 'stringIdentifier', 7405 * {@link $compile#-bindtocontroller- bindToController}: false, 7406 * {@link $compile#-require- require}: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], 7407 * {@link $compile#-multielement- multiElement}: false, 7408 * {@link $compile#-compile- compile}: function compile(tElement, tAttrs, transclude) { 7409 * return { 7410 * {@link $compile#pre-linking-function pre}: function preLink(scope, iElement, iAttrs, controller) { ... }, 7411 * {@link $compile#post-linking-function post}: function postLink(scope, iElement, iAttrs, controller) { ... } 7412 * } 7413 * // or 7414 * // return function postLink( ... ) { ... } 7415 * }, 7416 * // or 7417 * // {@link $compile#-link- link}: { 7418 * // {@link $compile#pre-linking-function pre}: function preLink(scope, iElement, iAttrs, controller) { ... }, 7419 * // {@link $compile#post-linking-function post}: function postLink(scope, iElement, iAttrs, controller) { ... } 7420 * // } 7421 * // or 7422 * // {@link $compile#-link- link}: function postLink( ... ) { ... } 7423 * }; 7424 * return directiveDefinitionObject; 7425 * }); 7426 * ``` 7427 * 7428 * <div class="alert alert-warning"> 7429 * **Note:** Any unspecified options will use the default value. You can see the default values below. 7430 * </div> 7431 * 7432 * Therefore the above can be simplified as: 7433 * 7434 * ```js 7435 * var myModule = angular.module(...); 7436 * 7437 * myModule.directive('directiveName', function factory(injectables) { 7438 * var directiveDefinitionObject = { 7439 * link: function postLink(scope, iElement, iAttrs) { ... } 7440 * }; 7441 * return directiveDefinitionObject; 7442 * // or 7443 * // return function postLink(scope, iElement, iAttrs) { ... } 7444 * }); 7445 * ``` 7446 * 7447 * ### Life-cycle hooks 7448 * Directive controllers can provide the following methods that are called by AngularJS at points in the life-cycle of the 7449 * directive: 7450 * * `$onInit()` - Called on each controller after all the controllers on an element have been constructed and 7451 * had their bindings initialized (and before the pre & post linking functions for the directives on 7452 * this element). This is a good place to put initialization code for your controller. 7453 * * `$onChanges(changesObj)` - Called whenever one-way (`<`) or interpolation (`@`) bindings are updated. The 7454 * `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an 7455 * object of the form `{ currentValue, previousValue, isFirstChange() }`. Use this hook to trigger updates within a 7456 * component such as cloning the bound value to prevent accidental mutation of the outer value. Note that this will 7457 * also be called when your bindings are initialized. 7458 * * `$doCheck()` - Called on each turn of the digest cycle. Provides an opportunity to detect and act on 7459 * changes. Any actions that you wish to take in response to the changes that you detect must be 7460 * invoked from this hook; implementing this has no effect on when `$onChanges` is called. For example, this hook 7461 * could be useful if you wish to perform a deep equality check, or to check a Date object, changes to which would not 7462 * be detected by AngularJS's change detector and thus not trigger `$onChanges`. This hook is invoked with no arguments; 7463 * if detecting changes, you must store the previous value(s) for comparison to the current values. 7464 * * `$onDestroy()` - Called on a controller when its containing scope is destroyed. Use this hook for releasing 7465 * external resources, watches and event handlers. Note that components have their `$onDestroy()` hooks called in 7466 * the same order as the `$scope.$broadcast` events are triggered, which is top down. This means that parent 7467 * components will have their `$onDestroy()` hook called before child components. 7468 * * `$postLink()` - Called after this controller's element and its children have been linked. Similar to the post-link 7469 * function this hook can be used to set up DOM event handlers and do direct DOM manipulation. 7470 * Note that child elements that contain `templateUrl` directives will not have been compiled and linked since 7471 * they are waiting for their template to load asynchronously and their own compilation and linking has been 7472 * suspended until that occurs. 7473 * 7474 * #### Comparison with life-cycle hooks in the new Angular 7475 * The new Angular also uses life-cycle hooks for its components. While the AngularJS life-cycle hooks are similar there are 7476 * some differences that you should be aware of, especially when it comes to moving your code from AngularJS to Angular: 7477 * 7478 * * AngularJS hooks are prefixed with `$`, such as `$onInit`. Angular hooks are prefixed with `ng`, such as `ngOnInit`. 7479 * * AngularJS hooks can be defined on the controller prototype or added to the controller inside its constructor. 7480 * In Angular you can only define hooks on the prototype of the Component class. 7481 * * Due to the differences in change-detection, you may get many more calls to `$doCheck` in AngularJS than you would to 7482 * `ngDoCheck` in Angular. 7483 * * Changes to the model inside `$doCheck` will trigger new turns of the digest loop, which will cause the changes to be 7484 * propagated throughout the application. 7485 * Angular does not allow the `ngDoCheck` hook to trigger a change outside of the component. It will either throw an 7486 * error or do nothing depending upon the state of `enableProdMode()`. 7487 * 7488 * #### Life-cycle hook examples 7489 * 7490 * This example shows how you can check for mutations to a Date object even though the identity of the object 7491 * has not changed. 7492 * 7493 * <example name="doCheckDateExample" module="do-check-module"> 7494 * <file name="app.js"> 7495 * angular.module('do-check-module', []) 7496 * .component('app', { 7497 * template: 7498 * 'Month: <input ng-model="$ctrl.month" ng-change="$ctrl.updateDate()">' + 7499 * 'Date: {{ $ctrl.date }}' + 7500 * '<test date="$ctrl.date"></test>', 7501 * controller: function() { 7502 * this.date = new Date(); 7503 * this.month = this.date.getMonth(); 7504 * this.updateDate = function() { 7505 * this.date.setMonth(this.month); 7506 * }; 7507 * } 7508 * }) 7509 * .component('test', { 7510 * bindings: { date: '<' }, 7511 * template: 7512 * '<pre>{{ $ctrl.log | json }}</pre>', 7513 * controller: function() { 7514 * var previousValue; 7515 * this.log = []; 7516 * this.$doCheck = function() { 7517 * var currentValue = this.date && this.date.valueOf(); 7518 * if (previousValue !== currentValue) { 7519 * this.log.push('doCheck: date mutated: ' + this.date); 7520 * previousValue = currentValue; 7521 * } 7522 * }; 7523 * } 7524 * }); 7525 * </file> 7526 * <file name="index.html"> 7527 * <app></app> 7528 * </file> 7529 * </example> 7530 * 7531 * This example show how you might use `$doCheck` to trigger changes in your component's inputs even if the 7532 * actual identity of the component doesn't change. (Be aware that cloning and deep equality checks on large 7533 * arrays or objects can have a negative impact on your application performance.) 7534 * 7535 * <example name="doCheckArrayExample" module="do-check-module"> 7536 * <file name="index.html"> 7537 * <div ng-init="items = []"> 7538 * <button ng-click="items.push(items.length)">Add Item</button> 7539 * <button ng-click="items = []">Reset Items</button> 7540 * <pre>{{ items }}</pre> 7541 * <test items="items"></test> 7542 * </div> 7543 * </file> 7544 * <file name="app.js"> 7545 * angular.module('do-check-module', []) 7546 * .component('test', { 7547 * bindings: { items: '<' }, 7548 * template: 7549 * '<pre>{{ $ctrl.log | json }}</pre>', 7550 * controller: function() { 7551 * this.log = []; 7552 * 7553 * this.$doCheck = function() { 7554 * if (this.items_ref !== this.items) { 7555 * this.log.push('doCheck: items changed'); 7556 * this.items_ref = this.items; 7557 * } 7558 * if (!angular.equals(this.items_clone, this.items)) { 7559 * this.log.push('doCheck: items mutated'); 7560 * this.items_clone = angular.copy(this.items); 7561 * } 7562 * }; 7563 * } 7564 * }); 7565 * </file> 7566 * </example> 7567 * 7568 * 7569 * ### Directive Definition Object 7570 * 7571 * The directive definition object provides instructions to the {@link ng.$compile 7572 * compiler}. The attributes are: 7573 * 7574 * #### `multiElement` 7575 * When this property is set to true (default is `false`), the HTML compiler will collect DOM nodes between 7576 * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them 7577 * together as the directive elements. It is recommended that this feature be used on directives 7578 * which are not strictly behavioral (such as {@link ngClick}), and which 7579 * do not manipulate or replace child nodes (such as {@link ngInclude}). 7580 * 7581 * #### `priority` 7582 * When there are multiple directives defined on a single DOM element, sometimes it 7583 * is necessary to specify the order in which the directives are applied. The `priority` is used 7584 * to sort the directives before their `compile` functions get called. Priority is defined as a 7585 * number. Directives with greater numerical `priority` are compiled first. Pre-link functions 7586 * are also run in priority order, but post-link functions are run in reverse order. The order 7587 * of directives with the same priority is undefined. The default priority is `0`. 7588 * 7589 * #### `terminal` 7590 * If set to true then the current `priority` will be the last set of directives 7591 * which will execute (any directives at the current priority will still execute 7592 * as the order of execution on same `priority` is undefined). Note that expressions 7593 * and other directives used in the directive's template will also be excluded from execution. 7594 * 7595 * #### `scope` 7596 * The scope property can be `false`, `true`, or an object: 7597 * 7598 * * **`false` (default):** No scope will be created for the directive. The directive will use its 7599 * parent's scope. 7600 * 7601 * * **`true`:** A new child scope that prototypically inherits from its parent will be created for 7602 * the directive's element. If multiple directives on the same element request a new scope, 7603 * only one new scope is created. 7604 * 7605 * * **`{...}` (an object hash):** A new "isolate" scope is created for the directive's template. 7606 * The 'isolate' scope differs from normal scope in that it does not prototypically 7607 * inherit from its parent scope. This is useful when creating reusable components, which should not 7608 * accidentally read or modify data in the parent scope. Note that an isolate scope 7609 * directive without a `template` or `templateUrl` will not apply the isolate scope 7610 * to its children elements. 7611 * 7612 * The 'isolate' scope object hash defines a set of local scope properties derived from attributes on the 7613 * directive's element. These local properties are useful for aliasing values for templates. The keys in 7614 * the object hash map to the name of the property on the isolate scope; the values define how the property 7615 * is bound to the parent scope, via matching attributes on the directive's element: 7616 * 7617 * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is 7618 * always a string since DOM attributes are strings. If no `attr` name is specified then the 7619 * attribute name is assumed to be the same as the local name. Given `<my-component 7620 * my-attr="hello {{name}}">` and the isolate scope definition `scope: { localName:'@myAttr' }`, 7621 * the directive's scope property `localName` will reflect the interpolated value of `hello 7622 * {{name}}`. As the `name` attribute changes so will the `localName` property on the directive's 7623 * scope. The `name` is read from the parent scope (not the directive's scope). 7624 * 7625 * * `=` or `=attr` - set up a bidirectional binding between a local scope property and an expression 7626 * passed via the attribute `attr`. The expression is evaluated in the context of the parent scope. 7627 * If no `attr` name is specified then the attribute name is assumed to be the same as the local 7628 * name. Given `<my-component my-attr="parentModel">` and the isolate scope definition `scope: { 7629 * localModel: '=myAttr' }`, the property `localModel` on the directive's scope will reflect the 7630 * value of `parentModel` on the parent scope. Changes to `parentModel` will be reflected in 7631 * `localModel` and vice versa. If the binding expression is non-assignable, or if the attribute 7632 * isn't optional and doesn't exist, an exception 7633 * ({@link error/$compile/nonassign `$compile:nonassign`}) will be thrown upon discovering changes 7634 * to the local value, since it will be impossible to sync them back to the parent scope. 7635 * 7636 * By default, the {@link ng.$rootScope.Scope#$watch `$watch`} 7637 * method is used for tracking changes, and the equality check is based on object identity. 7638 * However, if an object literal or an array literal is passed as the binding expression, the 7639 * equality check is done by value (using the {@link angular.equals} function). It's also possible 7640 * to watch the evaluated value shallowly with {@link ng.$rootScope.Scope#$watchCollection 7641 * `$watchCollection`}: use `=*` or `=*attr` 7642 * 7643 * * `<` or `<attr` - set up a one-way (one-directional) binding between a local scope property and an 7644 * expression passed via the attribute `attr`. The expression is evaluated in the context of the 7645 * parent scope. If no `attr` name is specified then the attribute name is assumed to be the same as the 7646 * local name. 7647 * 7648 * For example, given `<my-component my-attr="parentModel">` and directive definition of 7649 * `scope: { localModel:'<myAttr' }`, then the isolated scope property `localModel` will reflect the 7650 * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected 7651 * in `localModel`, but changes in `localModel` will not reflect in `parentModel`. There are however 7652 * two caveats: 7653 * 1. one-way binding does not copy the value from the parent to the isolate scope, it simply 7654 * sets the same value. That means if your bound value is an object, changes to its properties 7655 * in the isolated scope will be reflected in the parent scope (because both reference the same object). 7656 * 2. one-way binding watches changes to the **identity** of the parent value. That means the 7657 * {@link ng.$rootScope.Scope#$watch `$watch`} on the parent value only fires if the reference 7658 * to the value has changed. In most cases, this should not be of concern, but can be important 7659 * to know if you one-way bind to an object, and then replace that object in the isolated scope. 7660 * If you now change a property of the object in your parent scope, the change will not be 7661 * propagated to the isolated scope, because the identity of the object on the parent scope 7662 * has not changed. Instead you must assign a new object. 7663 * 7664 * One-way binding is useful if you do not plan to propagate changes to your isolated scope bindings 7665 * back to the parent. However, it does not make this completely impossible. 7666 * 7667 * By default, the {@link ng.$rootScope.Scope#$watch `$watch`} 7668 * method is used for tracking changes, and the equality check is based on object identity. 7669 * It's also possible to watch the evaluated value shallowly with 7670 * {@link ng.$rootScope.Scope#$watchCollection `$watchCollection`}: use `<*` or `<*attr` 7671 * 7672 * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. If 7673 * no `attr` name is specified then the attribute name is assumed to be the same as the local name. 7674 * Given `<my-component my-attr="count = count + value">` and the isolate scope definition `scope: { 7675 * localFn:'&myAttr' }`, the isolate scope property `localFn` will point to a function wrapper for 7676 * the `count = count + value` expression. Often it's desirable to pass data from the isolated scope 7677 * via an expression to the parent scope. This can be done by passing a map of local variable names 7678 * and values into the expression wrapper fn. For example, if the expression is `increment(amount)` 7679 * then we can specify the amount value by calling the `localFn` as `localFn({amount: 22})`. 7680 * 7681 * All 4 kinds of bindings (`@`, `=`, `<`, and `&`) can be made optional by adding `?` to the expression. 7682 * The marker must come after the mode and before the attribute name. 7683 * See the {@link error/$compile/iscp Invalid Isolate Scope Definition error} for definition examples. 7684 * This is useful to refine the interface directives provide. 7685 * One subtle difference between optional and non-optional happens **when the binding attribute is not 7686 * set**: 7687 * - the binding is optional: the property will not be defined 7688 * - the binding is not optional: the property is defined 7689 * 7690 * ```js 7691 *app.directive('testDir', function() { 7692 return { 7693 scope: { 7694 notoptional: '=', 7695 optional: '=?', 7696 }, 7697 bindToController: true, 7698 controller: function() { 7699 this.$onInit = function() { 7700 console.log(this.hasOwnProperty('notoptional')) // true 7701 console.log(this.hasOwnProperty('optional')) // false 7702 } 7703 } 7704 } 7705 }) 7706 *``` 7707 * 7708 * 7709 * ##### Combining directives with different scope defintions 7710 * 7711 * In general it's possible to apply more than one directive to one element, but there might be limitations 7712 * depending on the type of scope required by the directives. The following points will help explain these limitations. 7713 * For simplicity only two directives are taken into account, but it is also applicable for several directives: 7714 * 7715 * * **no scope** + **no scope** => Two directives which don't require their own scope will use their parent's scope 7716 * * **child scope** + **no scope** => Both directives will share one single child scope 7717 * * **child scope** + **child scope** => Both directives will share one single child scope 7718 * * **isolated scope** + **no scope** => The isolated directive will use it's own created isolated scope. The other directive will use 7719 * its parent's scope 7720 * * **isolated scope** + **child scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives cannot 7721 * be applied to the same element. 7722 * * **isolated scope** + **isolated scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives 7723 * cannot be applied to the same element. 7724 * 7725 * 7726 * #### `bindToController` 7727 * This property is used to bind scope properties directly to the controller. It can be either 7728 * `true` or an object hash with the same format as the `scope` property. 7729 * 7730 * When an isolate scope is used for a directive (see above), `bindToController: true` will 7731 * allow a component to have its properties bound to the controller, rather than to scope. 7732 * 7733 * After the controller is instantiated, the initial values of the isolate scope bindings will be bound to the controller 7734 * properties. You can access these bindings once they have been initialized by providing a controller method called 7735 * `$onInit`, which is called after all the controllers on an element have been constructed and had their bindings 7736 * initialized. 7737 * 7738 * It is also possible to set `bindToController` to an object hash with the same format as the `scope` property. 7739 * This will set up the scope bindings to the controller directly. Note that `scope` can still be used 7740 * to define which kind of scope is created. By default, no scope is created. Use `scope: {}` to create an isolate 7741 * scope (useful for component directives). 7742 * 7743 * If both `bindToController` and `scope` are defined and have object hashes, `bindToController` overrides `scope`. 7744 * 7745 * 7746 * #### `controller` 7747 * Controller constructor function. The controller is instantiated before the 7748 * pre-linking phase and can be accessed by other directives (see 7749 * `require` attribute). This allows the directives to communicate with each other and augment 7750 * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals: 7751 * 7752 * * `$scope` - Current scope associated with the element 7753 * * `$element` - Current element 7754 * * `$attrs` - Current attributes object for the element 7755 * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope: 7756 * `function([scope], cloneLinkingFn, futureParentElement, slotName)`: 7757 * * `scope`: (optional) override the scope. 7758 * * `cloneLinkingFn`: (optional) argument to create clones of the original transcluded content. 7759 * * `futureParentElement` (optional): 7760 * * defines the parent to which the `cloneLinkingFn` will add the cloned elements. 7761 * * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`. 7762 * * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements) 7763 * and when the `cloneLinkingFn` is passed, 7764 * as those elements need to created and cloned in a special way when they are defined outside their 7765 * usual containers (e.g. like `<svg>`). 7766 * * See also the `directive.templateNamespace` property. 7767 * * `slotName`: (optional) the name of the slot to transclude. If falsy (e.g. `null`, `undefined` or `''`) 7768 * then the default transclusion is provided. 7769 * The `$transclude` function also has a method on it, `$transclude.isSlotFilled(slotName)`, which returns 7770 * `true` if the specified slot contains content (i.e. one or more DOM nodes). 7771 * 7772 * #### `require` 7773 * Require another directive and inject its controller as the fourth argument to the linking function. The 7774 * `require` property can be a string, an array or an object: 7775 * * a **string** containing the name of the directive to pass to the linking function 7776 * * an **array** containing the names of directives to pass to the linking function. The argument passed to the 7777 * linking function will be an array of controllers in the same order as the names in the `require` property 7778 * * an **object** whose property values are the names of the directives to pass to the linking function. The argument 7779 * passed to the linking function will also be an object with matching keys, whose values will hold the corresponding 7780 * controllers. 7781 * 7782 * If the `require` property is an object and `bindToController` is truthy, then the required controllers are 7783 * bound to the controller using the keys of the `require` property. This binding occurs after all the controllers 7784 * have been constructed but before `$onInit` is called. 7785 * If the name of the required controller is the same as the local name (the key), the name can be 7786 * omitted. For example, `{parentDir: '^^'}` is equivalent to `{parentDir: '^^parentDir'}`. 7787 * See the {@link $compileProvider#component} helper for an example of how this can be used. 7788 * If no such required directive(s) can be found, or if the directive does not have a controller, then an error is 7789 * raised (unless no link function is specified and the required controllers are not being bound to the directive 7790 * controller, in which case error checking is skipped). The name can be prefixed with: 7791 * 7792 * * (no prefix) - Locate the required controller on the current element. Throw an error if not found. 7793 * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found. 7794 * * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found. 7795 * * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found. 7796 * * `?^` - Attempt to locate the required controller by searching the element and its parents or pass 7797 * `null` to the `link` fn if not found. 7798 * * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass 7799 * `null` to the `link` fn if not found. 7800 * 7801 * 7802 * #### `controllerAs` 7803 * Identifier name for a reference to the controller in the directive's scope. 7804 * This allows the controller to be referenced from the directive template. This is especially 7805 * useful when a directive is used as component, i.e. with an `isolate` scope. It's also possible 7806 * to use it in a directive without an `isolate` / `new` scope, but you need to be aware that the 7807 * `controllerAs` reference might overwrite a property that already exists on the parent scope. 7808 * 7809 * 7810 * #### `restrict` 7811 * String of subset of `EACM` which restricts the directive to a specific directive 7812 * declaration style. If omitted, the defaults (elements and attributes) are used. 7813 * 7814 * * `E` - Element name (default): `<my-directive></my-directive>` 7815 * * `A` - Attribute (default): `<div my-directive="exp"></div>` 7816 * * `C` - Class: `<div class="my-directive: exp;"></div>` 7817 * * `M` - Comment: `<!-- directive: my-directive exp -->` 7818 * 7819 * 7820 * #### `templateNamespace` 7821 * String representing the document type used by the markup in the template. 7822 * AngularJS needs this information as those elements need to be created and cloned 7823 * in a special way when they are defined outside their usual containers like `<svg>` and `<math>`. 7824 * 7825 * * `html` - All root nodes in the template are HTML. Root nodes may also be 7826 * top-level elements such as `<svg>` or `<math>`. 7827 * * `svg` - The root nodes in the template are SVG elements (excluding `<math>`). 7828 * * `math` - The root nodes in the template are MathML elements (excluding `<svg>`). 7829 * 7830 * If no `templateNamespace` is specified, then the namespace is considered to be `html`. 7831 * 7832 * #### `template` 7833 * HTML markup that may: 7834 * * Replace the contents of the directive's element (default). 7835 * * Replace the directive's element itself (if `replace` is true - DEPRECATED). 7836 * * Wrap the contents of the directive's element (if `transclude` is true). 7837 * 7838 * Value may be: 7839 * 7840 * * A string. For example `<div red-on-hover>{{delete_str}}</div>`. 7841 * * A function which takes two arguments `tElement` and `tAttrs` (described in the `compile` 7842 * function api below) and returns a string value. 7843 * 7844 * 7845 * #### `templateUrl` 7846 * This is similar to `template` but the template is loaded from the specified URL, asynchronously. 7847 * 7848 * Because template loading is asynchronous the compiler will suspend compilation of directives on that element 7849 * for later when the template has been resolved. In the meantime it will continue to compile and link 7850 * sibling and parent elements as though this element had not contained any directives. 7851 * 7852 * The compiler does not suspend the entire compilation to wait for templates to be loaded because this 7853 * would result in the whole app "stalling" until all templates are loaded asynchronously - even in the 7854 * case when only one deeply nested directive has `templateUrl`. 7855 * 7856 * Template loading is asynchronous even if the template has been preloaded into the {@link $templateCache}. 7857 * 7858 * You can specify `templateUrl` as a string representing the URL or as a function which takes two 7859 * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns 7860 * a string value representing the url. In either case, the template URL is passed through {@link 7861 * $sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}. 7862 * 7863 * 7864 * #### `replace` 7865 * <div class="alert alert-danger"> 7866 * **Note:** `replace` is deprecated in AngularJS and has been removed in the new Angular (v2+). 7867 * </div> 7868 * 7869 * Specifies what the template should replace. Defaults to `false`. 7870 * 7871 * * `true` - the template will replace the directive's element. 7872 * * `false` - the template will replace the contents of the directive's element. 7873 * 7874 * The replacement process migrates all of the attributes / classes from the old element to the new 7875 * one. See the {@link guide/directive#template-expanding-directive 7876 * Directives Guide} for an example. 7877 * 7878 * There are very few scenarios where element replacement is required for the application function, 7879 * the main one being reusable custom components that are used within SVG contexts 7880 * (because SVG doesn't work with custom elements in the DOM tree). 7881 * 7882 * #### `transclude` 7883 * Extract the contents of the element where the directive appears and make it available to the directive. 7884 * The contents are compiled and provided to the directive as a **transclusion function**. See the 7885 * {@link $compile#transclusion Transclusion} section below. 7886 * 7887 * 7888 * #### `compile` 7889 * 7890 * ```js 7891 * function compile(tElement, tAttrs, transclude) { ... } 7892 * ``` 7893 * 7894 * The compile function deals with transforming the template DOM. Since most directives do not do 7895 * template transformation, it is not used often. The compile function takes the following arguments: 7896 * 7897 * * `tElement` - template element - The element where the directive has been declared. It is 7898 * safe to do template transformation on the element and child elements only. 7899 * 7900 * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared 7901 * between all directive compile functions. 7902 * 7903 * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)` 7904 * 7905 * <div class="alert alert-warning"> 7906 * **Note:** The template instance and the link instance may be different objects if the template has 7907 * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that 7908 * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration 7909 * should be done in a linking function rather than in a compile function. 7910 * </div> 7911 7912 * <div class="alert alert-warning"> 7913 * **Note:** The compile function cannot handle directives that recursively use themselves in their 7914 * own templates or compile functions. Compiling these directives results in an infinite loop and 7915 * stack overflow errors. 7916 * 7917 * This can be avoided by manually using `$compile` in the postLink function to imperatively compile 7918 * a directive's template instead of relying on automatic template compilation via `template` or 7919 * `templateUrl` declaration or manual compilation inside the compile function. 7920 * </div> 7921 * 7922 * <div class="alert alert-danger"> 7923 * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it 7924 * e.g. does not know about the right outer scope. Please use the transclude function that is passed 7925 * to the link function instead. 7926 * </div> 7927 7928 * A compile function can have a return value which can be either a function or an object. 7929 * 7930 * * returning a (post-link) function - is equivalent to registering the linking function via the 7931 * `link` property of the config object when the compile function is empty. 7932 * 7933 * * returning an object with function(s) registered via `pre` and `post` properties - allows you to 7934 * control when a linking function should be called during the linking phase. See info about 7935 * pre-linking and post-linking functions below. 7936 * 7937 * 7938 * #### `link` 7939 * This property is used only if the `compile` property is not defined. 7940 * 7941 * ```js 7942 * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... } 7943 * ``` 7944 * 7945 * The link function is responsible for registering DOM listeners as well as updating the DOM. It is 7946 * executed after the template has been cloned. This is where most of the directive logic will be 7947 * put. 7948 * 7949 * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the 7950 * directive for registering {@link ng.$rootScope.Scope#$watch watches}. 7951 * 7952 * * `iElement` - instance element - The element where the directive is to be used. It is safe to 7953 * manipulate the children of the element only in `postLink` function since the children have 7954 * already been linked. 7955 * 7956 * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared 7957 * between all directive linking functions. 7958 * 7959 * * `controller` - the directive's required controller instance(s) - Instances are shared 7960 * among all directives, which allows the directives to use the controllers as a communication 7961 * channel. The exact value depends on the directive's `require` property: 7962 * * no controller(s) required: the directive's own controller, or `undefined` if it doesn't have one 7963 * * `string`: the controller instance 7964 * * `array`: array of controller instances 7965 * 7966 * If a required controller cannot be found, and it is optional, the instance is `null`, 7967 * otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown. 7968 * 7969 * Note that you can also require the directive's own controller - it will be made available like 7970 * any other controller. 7971 * 7972 * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope. 7973 * This is the same as the `$transclude` parameter of directive controllers, 7974 * see {@link ng.$compile#-controller- the controller section for details}. 7975 * `function([scope], cloneLinkingFn, futureParentElement)`. 7976 * 7977 * #### Pre-linking function 7978 * 7979 * Executed before the child elements are linked. Not safe to do DOM transformation since the 7980 * compiler linking function will fail to locate the correct elements for linking. 7981 * 7982 * #### Post-linking function 7983 * 7984 * Executed after the child elements are linked. 7985 * 7986 * Note that child elements that contain `templateUrl` directives will not have been compiled 7987 * and linked since they are waiting for their template to load asynchronously and their own 7988 * compilation and linking has been suspended until that occurs. 7989 * 7990 * It is safe to do DOM transformation in the post-linking function on elements that are not waiting 7991 * for their async templates to be resolved. 7992 * 7993 * 7994 * ### Transclusion 7995 * 7996 * Transclusion is the process of extracting a collection of DOM elements from one part of the DOM and 7997 * copying them to another part of the DOM, while maintaining their connection to the original AngularJS 7998 * scope from where they were taken. 7999 * 8000 * Transclusion is used (often with {@link ngTransclude}) to insert the 8001 * original contents of a directive's element into a specified place in the template of the directive. 8002 * The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded 8003 * content has access to the properties on the scope from which it was taken, even if the directive 8004 * has isolated scope. 8005 * See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}. 8006 * 8007 * This makes it possible for the widget to have private state for its template, while the transcluded 8008 * content has access to its originating scope. 8009 * 8010 * <div class="alert alert-warning"> 8011 * **Note:** When testing an element transclude directive you must not place the directive at the root of the 8012 * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives 8013 * Testing Transclusion Directives}. 8014 * </div> 8015 * 8016 * There are three kinds of transclusion depending upon whether you want to transclude just the contents of the 8017 * directive's element, the entire element or multiple parts of the element contents: 8018 * 8019 * * `true` - transclude the content (i.e. the child nodes) of the directive's element. 8020 * * `'element'` - transclude the whole of the directive's element including any directives on this 8021 * element that are defined at a lower priority than this directive. When used, the `template` 8022 * property is ignored. 8023 * * **`{...}` (an object hash):** - map elements of the content onto transclusion "slots" in the template. 8024 * 8025 * **Multi-slot transclusion** is declared by providing an object for the `transclude` property. 8026 * 8027 * This object is a map where the keys are the name of the slot to fill and the value is an element selector 8028 * used to match the HTML to the slot. The element selector should be in normalized form (e.g. `myElement`) 8029 * and will match the standard element variants (e.g. `my-element`, `my:element`, `data-my-element`, etc). 8030 * 8031 * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives}. 8032 * 8033 * If the element selector is prefixed with a `?` then that slot is optional. 8034 * 8035 * For example, the transclude object `{ slotA: '?myCustomElement' }` maps `<my-custom-element>` elements to 8036 * the `slotA` slot, which can be accessed via the `$transclude` function or via the {@link ngTransclude} directive. 8037 * 8038 * Slots that are not marked as optional (`?`) will trigger a compile time error if there are no matching elements 8039 * in the transclude content. If you wish to know if an optional slot was filled with content, then you can call 8040 * `$transclude.isSlotFilled(slotName)` on the transclude function passed to the directive's link function and 8041 * injectable into the directive's controller. 8042 * 8043 * 8044 * #### Transclusion Functions 8045 * 8046 * When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion 8047 * function** to the directive's `link` function and `controller`. This transclusion function is a special 8048 * **linking function** that will return the compiled contents linked to a new transclusion scope. 8049 * 8050 * <div class="alert alert-info"> 8051 * If you are just using {@link ngTransclude} then you don't need to worry about this function, since 8052 * ngTransclude will deal with it for us. 8053 * </div> 8054 * 8055 * If you want to manually control the insertion and removal of the transcluded content in your directive 8056 * then you must use this transclude function. When you call a transclude function it returns a jqLite/JQuery 8057 * object that contains the compiled DOM, which is linked to the correct transclusion scope. 8058 * 8059 * When you call a transclusion function you can pass in a **clone attach function**. This function accepts 8060 * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded 8061 * content and the `scope` is the newly created transclusion scope, which the clone will be linked to. 8062 * 8063 * <div class="alert alert-info"> 8064 * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a transclude function 8065 * since you then get a fresh clone of the original DOM and also have access to the new transclusion scope. 8066 * </div> 8067 * 8068 * It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone 8069 * attach function**: 8070 * 8071 * ```js 8072 * var transcludedContent, transclusionScope; 8073 * 8074 * $transclude(function(clone, scope) { 8075 * element.append(clone); 8076 * transcludedContent = clone; 8077 * transclusionScope = scope; 8078 * }); 8079 * ``` 8080 * 8081 * Later, if you want to remove the transcluded content from your DOM then you should also destroy the 8082 * associated transclusion scope: 8083 * 8084 * ```js 8085 * transcludedContent.remove(); 8086 * transclusionScope.$destroy(); 8087 * ``` 8088 * 8089 * <div class="alert alert-info"> 8090 * **Best Practice**: if you intend to add and remove transcluded content manually in your directive 8091 * (by calling the transclude function to get the DOM and calling `element.remove()` to remove it), 8092 * then you are also responsible for calling `$destroy` on the transclusion scope. 8093 * </div> 8094 * 8095 * The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat} 8096 * automatically destroy their transcluded clones as necessary so you do not need to worry about this if 8097 * you are simply using {@link ngTransclude} to inject the transclusion into your directive. 8098 * 8099 * 8100 * #### Transclusion Scopes 8101 * 8102 * When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion 8103 * scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed 8104 * when the directive's scope gets destroyed) but it inherits the properties of the scope from which it 8105 * was taken. 8106 * 8107 * For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look 8108 * like this: 8109 * 8110 * ```html 8111 * <div ng-app> 8112 * <div isolate> 8113 * <div transclusion> 8114 * </div> 8115 * </div> 8116 * </div> 8117 * ``` 8118 * 8119 * The `$parent` scope hierarchy will look like this: 8120 * 8121 ``` 8122 - $rootScope 8123 - isolate 8124 - transclusion 8125 ``` 8126 * 8127 * but the scopes will inherit prototypically from different scopes to their `$parent`. 8128 * 8129 ``` 8130 - $rootScope 8131 - transclusion 8132 - isolate 8133 ``` 8134 * 8135 * 8136 * ### Attributes 8137 * 8138 * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the 8139 * `link()` or `compile()` functions. It has a variety of uses. 8140 * 8141 * * *Accessing normalized attribute names:* Directives like `ngBind` can be expressed in many ways: 8142 * `ng:bind`, `data-ng-bind`, or `x-ng-bind`. The attributes object allows for normalized access 8143 * to the attributes. 8144 * 8145 * * *Directive inter-communication:* All directives share the same instance of the attributes 8146 * object which allows the directives to use the attributes object as inter directive 8147 * communication. 8148 * 8149 * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object 8150 * allowing other directives to read the interpolated value. 8151 * 8152 * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes 8153 * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also 8154 * the only way to easily get the actual value because during the linking phase the interpolation 8155 * hasn't been evaluated yet and so the value is at this time set to `undefined`. 8156 * 8157 * ```js 8158 * function linkingFn(scope, elm, attrs, ctrl) { 8159 * // get the attribute value 8160 * console.log(attrs.ngModel); 8161 * 8162 * // change the attribute 8163 * attrs.$set('ngModel', 'new value'); 8164 * 8165 * // observe changes to interpolated attribute 8166 * attrs.$observe('ngModel', function(value) { 8167 * console.log('ngModel has changed value to ' + value); 8168 * }); 8169 * } 8170 * ``` 8171 * 8172 * ## Example 8173 * 8174 * <div class="alert alert-warning"> 8175 * **Note**: Typically directives are registered with `module.directive`. The example below is 8176 * to illustrate how `$compile` works. 8177 * </div> 8178 * 8179 <example module="compileExample" name="compile"> 8180 <file name="index.html"> 8181 <script> 8182 angular.module('compileExample', [], function($compileProvider) { 8183 // Configure new 'compile' directive by passing a directive 8184 // factory function. The factory function injects '$compile'. 8185 $compileProvider.directive('compile', function($compile) { 8186 // The directive factory creates a link function. 8187 return function(scope, element, attrs) { 8188 scope.$watch( 8189 function(scope) { 8190 // Watch the 'compile' expression for changes. 8191 return scope.$eval(attrs.compile); 8192 }, 8193 function(value) { 8194 // When the 'compile' expression changes 8195 // assign it into the current DOM. 8196 element.html(value); 8197 8198 // Compile the new DOM and link it to the current scope. 8199 // NOTE: we only compile '.childNodes' so that we 8200 // don't get into an infinite loop compiling ourselves. 8201 $compile(element.contents())(scope); 8202 } 8203 ); 8204 }; 8205 }); 8206 }) 8207 .controller('GreeterController', ['$scope', function($scope) { 8208 $scope.name = 'AngularJS'; 8209 $scope.html = 'Hello {{name}}'; 8210 }]); 8211 </script> 8212 <div ng-controller="GreeterController"> 8213 <input ng-model="name"> <br/> 8214 <textarea ng-model="html"></textarea> <br/> 8215 <div compile="html"></div> 8216 </div> 8217 </file> 8218 <file name="protractor.js" type="protractor"> 8219 it('should auto compile', function() { 8220 var textarea = $('textarea'); 8221 var output = $('div[compile]'); 8222 // The initial state reads 'Hello AngularJS'. 8223 expect(output.getText()).toBe('Hello AngularJS'); 8224 textarea.clear(); 8225 textarea.sendKeys('{{name}}!'); 8226 expect(output.getText()).toBe('AngularJS!'); 8227 }); 8228 </file> 8229 </example> 8230 8231 * 8232 * 8233 * @param {string|DOMElement} element Element or HTML string to compile into a template function. 8234 * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED. 8235 * 8236 * <div class="alert alert-danger"> 8237 * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it 8238 * e.g. will not use the right outer scope. Please pass the transclude function as a 8239 * `parentBoundTranscludeFn` to the link function instead. 8240 * </div> 8241 * 8242 * @param {number} maxPriority only apply directives lower than given priority (Only effects the 8243 * root element(s), not their children) 8244 * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template 8245 * (a DOM element/tree) to a scope. Where: 8246 * 8247 * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. 8248 * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the 8249 * `template` and call the `cloneAttachFn` function allowing the caller to attach the 8250 * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is 8251 * called as: <br/> `cloneAttachFn(clonedElement, scope)` where: 8252 * 8253 * * `clonedElement` - is a clone of the original `element` passed into the compiler. 8254 * * `scope` - is the current scope with which the linking function is working with. 8255 * 8256 * * `options` - An optional object hash with linking options. If `options` is provided, then the following 8257 * keys may be used to control linking behavior: 8258 * 8259 * * `parentBoundTranscludeFn` - the transclude function made available to 8260 * directives; if given, it will be passed through to the link functions of 8261 * directives found in `element` during compilation. 8262 * * `transcludeControllers` - an object hash with keys that map controller names 8263 * to a hash with the key `instance`, which maps to the controller instance; 8264 * if given, it will make the controllers available to directives on the compileNode: 8265 * ``` 8266 * { 8267 * parent: { 8268 * instance: parentControllerInstance 8269 * } 8270 * } 8271 * ``` 8272 * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add 8273 * the cloned elements; only needed for transcludes that are allowed to contain non HTML 8274 * elements (e.g. SVG elements). See also the `directive.controller` property. 8275 * 8276 * Calling the linking function returns the element of the template. It is either the original 8277 * element passed in, or the clone of the element if the `cloneAttachFn` is provided. 8278 * 8279 * After linking the view is not updated until after a call to `$digest`, which typically is done by 8280 * AngularJS automatically. 8281 * 8282 * If you need access to the bound view, there are two ways to do it: 8283 * 8284 * - If you are not asking the linking function to clone the template, create the DOM element(s) 8285 * before you send them to the compiler and keep this reference around. 8286 * ```js 8287 * var element = angular.element('<p>{{total}}</p>'); 8288 * $compile(element)(scope); 8289 * ``` 8290 * 8291 * - if on the other hand, you need the element to be cloned, the view reference from the original 8292 * example would not point to the clone, but rather to the original template that was cloned. In 8293 * this case, you can access the clone either via the `cloneAttachFn` or the value returned by the 8294 * linking function: 8295 * ```js 8296 * var templateElement = angular.element('<p>{{total}}</p>'); 8297 * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) { 8298 * // Attach the clone to DOM document at the right place. 8299 * }); 8300 * 8301 * // Now we have reference to the cloned DOM via `clonedElement`. 8302 * // NOTE: The `clonedElement` returned by the linking function is the same as the 8303 * // `clonedElement` passed to `cloneAttachFn`. 8304 * ``` 8305 * 8306 * 8307 * For information on how the compiler works, see the 8308 * {@link guide/compiler AngularJS HTML Compiler} section of the Developer Guide. 8309 * 8310 * @knownIssue 8311 * 8312 * ### Double Compilation 8313 * 8314 Double compilation occurs when an already compiled part of the DOM gets 8315 compiled again. This is an undesired effect and can lead to misbehaving directives, performance issues, 8316 and memory leaks. Refer to the Compiler Guide {@link guide/compiler#double-compilation-and-how-to-avoid-it 8317 section on double compilation} for an in-depth explanation and ways to avoid it. 8318 8319 * @knownIssue 8320 8321 ### Issues with `replace: true` 8322 * 8323 * <div class="alert alert-danger"> 8324 * **Note**: {@link $compile#-replace- `replace: true`} is deprecated and not recommended to use, 8325 * mainly due to the issues listed here. It has been completely removed in the new Angular. 8326 * </div> 8327 * 8328 * #### Attribute values are not merged 8329 * 8330 * When a `replace` directive encounters the same attribute on the original and the replace node, 8331 * it will simply deduplicate the attribute and join the values with a space or with a `;` in case of 8332 * the `style` attribute. 8333 * ```html 8334 * Original Node: <span class="original" style="color: red;"></span> 8335 * Replace Template: <span class="replaced" style="background: blue;"></span> 8336 * Result: <span class="original replaced" style="color: red; background: blue;"></span> 8337 * ``` 8338 * 8339 * That means attributes that contain AngularJS expressions will not be merged correctly, e.g. 8340 * {@link ngShow} or {@link ngClass} will cause a {@link $parse} error: 8341 * 8342 * ```html 8343 * Original Node: <span ng-class="{'something': something}" ng-show="!condition"></span> 8344 * Replace Template: <span ng-class="{'else': else}" ng-show="otherCondition"></span> 8345 * Result: <span ng-class="{'something': something} {'else': else}" ng-show="!condition otherCondition"></span> 8346 * ``` 8347 * 8348 * See issue [#5695](https://github.com/angular/angular.js/issues/5695). 8349 * 8350 * #### Directives are not deduplicated before compilation 8351 * 8352 * When the original node and the replace template declare the same directive(s), they will be 8353 * {@link guide/compiler#double-compilation-and-how-to-avoid-it compiled twice} because the compiler 8354 * does not deduplicate them. In many cases, this is not noticeable, but e.g. {@link ngModel} will 8355 * attach `$formatters` and `$parsers` twice. 8356 * 8357 * See issue [#2573](https://github.com/angular/angular.js/issues/2573). 8358 * 8359 * #### `transclude: element` in the replace template root can have unexpected effects 8360 * 8361 * When the replace template has a directive at the root node that uses 8362 * {@link $compile#-transclude- `transclude: element`}, e.g. 8363 * {@link ngIf} or {@link ngRepeat}, the DOM structure or scope inheritance can be incorrect. 8364 * See the following issues: 8365 * 8366 * - Incorrect scope on replaced element: 8367 * [#9837](https://github.com/angular/angular.js/issues/9837) 8368 * - Different DOM between `template` and `templateUrl`: 8369 * [#10612](https://github.com/angular/angular.js/issues/14326) 8370 * 8371 */ 8372 8373/** 8374 * @ngdoc directive 8375 * @name ngProp 8376 * @restrict A 8377 * @element ANY 8378 * 8379 * @usage 8380 * 8381 * ```html 8382 * <ANY ng-prop-propname="expression"> 8383 * </ANY> 8384 * ``` 8385 * 8386 * or with uppercase letters in property (e.g. "propName"): 8387 * 8388 * 8389 * ```html 8390 * <ANY ng-prop-prop_name="expression"> 8391 * </ANY> 8392 * ``` 8393 * 8394 * 8395 * @description 8396 * The `ngProp` directive binds an expression to a DOM element property. 8397 * `ngProp` allows writing to arbitrary properties by including 8398 * the property name in the attribute, e.g. `ng-prop-value="'my value'"` binds 'my value' to 8399 * the `value` property. 8400 * 8401 * Usually, it's not necessary to write to properties in AngularJS, as the built-in directives 8402 * handle the most common use cases (instead of the above example, you would use {@link ngValue}). 8403 * 8404 * However, [custom elements](https://developer.mozilla.org/docs/Web/Web_Components/Using_custom_elements) 8405 * often use custom properties to hold data, and `ngProp` can be used to provide input to these 8406 * custom elements. 8407 * 8408 * ## Binding to camelCase properties 8409 * 8410 * Since HTML attributes are case-insensitive, camelCase properties like `innerHTML` must be escaped. 8411 * AngularJS uses the underscore (_) in front of a character to indicate that it is uppercase, so 8412 * `innerHTML` must be written as `ng-prop-inner_h_t_m_l="expression"` (Note that this is just an 8413 * example, and for binding HTML {@link ngBindHtml} should be used. 8414 * 8415 * ## Security 8416 * 8417 * Binding expressions to arbitrary properties poses a security risk, as properties like `innerHTML` 8418 * can insert potentially dangerous HTML into the application, e.g. script tags that execute 8419 * malicious code. 8420 * For this reason, `ngProp` applies Strict Contextual Escaping with the {@link ng.$sce $sce service}. 8421 * This means vulnerable properties require their content to be "trusted", based on the 8422 * context of the property. For example, the `innerHTML` is in the `HTML` context, and the 8423 * `iframe.src` property is in the `RESOURCE_URL` context, which requires that values written to 8424 * this property are trusted as a `RESOURCE_URL`. 8425 * 8426 * This can be set explicitly by calling $sce.trustAs(type, value) on the value that is 8427 * trusted before passing it to the `ng-prop-*` directive. There are exist shorthand methods for 8428 * each context type in the form of {@link ng.$sce#trustAsResourceUrl $sce.trustAsResourceUrl()} et al. 8429 * 8430 * In some cases you can also rely upon automatic sanitization of untrusted values - see below. 8431 * 8432 * Based on the context, other options may exist to mark a value as trusted / configure the behavior 8433 * of {@link ng.$sce}. For example, to restrict the `RESOURCE_URL` context to specific origins, use 8434 * the {@link $sceDelegateProvider#trustedResourceUrlList trustedResourceUrlList()} 8435 * and {@link $sceDelegateProvider#bannedResourceUrlList bannedResourceUrlList()}. 8436 * 8437 * {@link ng.$sce#what-trusted-context-types-are-supported- Find out more about the different context types}. 8438 * 8439 * ### HTML Sanitization 8440 * 8441 * By default, `$sce` will throw an error if it detects untrusted HTML content, and will not bind the 8442 * content. 8443 * However, if you include the {@link ngSanitize ngSanitize module}, it will try to sanitize the 8444 * potentially dangerous HTML, e.g. strip non-trusted tags and attributes when binding to 8445 * `innerHTML`. 8446 * 8447 * @example 8448 * ### Binding to different contexts 8449 * 8450 * <example name="ngProp" module="exampleNgProp"> 8451 * <file name="app.js"> 8452 * angular.module('exampleNgProp', []) 8453 * .component('main', { 8454 * templateUrl: 'main.html', 8455 * controller: function($sce) { 8456 * this.safeContent = '<strong>Safe content</strong>'; 8457 * this.unsafeContent = '<button onclick="alert(\'Hello XSS!\')">Click for XSS</button>'; 8458 * this.trustedUnsafeContent = $sce.trustAsHtml(this.unsafeContent); 8459 * } 8460 * }); 8461 * </file> 8462 * <file name="main.html"> 8463 * <div> 8464 * <div class="prop-unit"> 8465 * Binding to a property without security context: 8466 * <div class="prop-binding" ng-prop-inner_text="$ctrl.safeContent"></div> 8467 * <span class="prop-note">innerText</span> (safeContent) 8468 * </div> 8469 * 8470 * <div class="prop-unit"> 8471 * "Safe" content that requires a security context will throw because the contents could potentially be dangerous ... 8472 * <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.safeContent"></div> 8473 * <span class="prop-note">innerHTML</span> (safeContent) 8474 * </div> 8475 * 8476 * <div class="prop-unit"> 8477 * ... so that actually dangerous content cannot be executed: 8478 * <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.unsafeContent"></div> 8479 * <span class="prop-note">innerHTML</span> (unsafeContent) 8480 * </div> 8481 * 8482 * <div class="prop-unit"> 8483 * ... but unsafe Content that has been trusted explicitly works - only do this if you are 100% sure! 8484 * <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.trustedUnsafeContent"></div> 8485 * <span class="prop-note">innerHTML</span> (trustedUnsafeContent) 8486 * </div> 8487 * </div> 8488 * </file> 8489 * <file name="index.html"> 8490 * <main></main> 8491 * </file> 8492 * <file name="styles.css"> 8493 * .prop-unit { 8494 * margin-bottom: 10px; 8495 * } 8496 * 8497 * .prop-binding { 8498 * min-height: 30px; 8499 * border: 1px solid blue; 8500 * } 8501 * 8502 * .prop-note { 8503 * font-family: Monospace; 8504 * } 8505 * </file> 8506 * </example> 8507 * 8508 * 8509 * @example 8510 * ### Binding to innerHTML with ngSanitize 8511 * 8512 * <example name="ngProp" module="exampleNgProp" deps="angular-sanitize.js"> 8513 * <file name="app.js"> 8514 * angular.module('exampleNgProp', ['ngSanitize']) 8515 * .component('main', { 8516 * templateUrl: 'main.html', 8517 * controller: function($sce) { 8518 * this.safeContent = '<strong>Safe content</strong>'; 8519 * this.unsafeContent = '<button onclick="alert(\'Hello XSS!\')">Click for XSS</button>'; 8520 * this.trustedUnsafeContent = $sce.trustAsHtml(this.unsafeContent); 8521 * } 8522 * }); 8523 * </file> 8524 * <file name="main.html"> 8525 * <div> 8526 * <div class="prop-unit"> 8527 * "Safe" content will be sanitized ... 8528 * <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.safeContent"></div> 8529 * <span class="prop-note">innerHTML</span> (safeContent) 8530 * </div> 8531 * 8532 * <div class="prop-unit"> 8533 * ... as will dangerous content: 8534 * <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.unsafeContent"></div> 8535 * <span class="prop-note">innerHTML</span> (unsafeContent) 8536 * </div> 8537 * 8538 * <div class="prop-unit"> 8539 * ... and content that has been trusted explicitly works the same as without ngSanitize: 8540 * <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.trustedUnsafeContent"></div> 8541 * <span class="prop-note">innerHTML</span> (trustedUnsafeContent) 8542 * </div> 8543 * </div> 8544 * </file> 8545 * <file name="index.html"> 8546 * <main></main> 8547 * </file> 8548 * <file name="styles.css"> 8549 * .prop-unit { 8550 * margin-bottom: 10px; 8551 * } 8552 * 8553 * .prop-binding { 8554 * min-height: 30px; 8555 * border: 1px solid blue; 8556 * } 8557 * 8558 * .prop-note { 8559 * font-family: Monospace; 8560 * } 8561 * </file> 8562 * </example> 8563 * 8564 */ 8565 8566/** @ngdoc directive 8567 * @name ngOn 8568 * @restrict A 8569 * @element ANY 8570 * 8571 * @usage 8572 * 8573 * ```html 8574 * <ANY ng-on-eventname="expression"> 8575 * </ANY> 8576 * ``` 8577 * 8578 * or with uppercase letters in property (e.g. "eventName"): 8579 * 8580 * 8581 * ```html 8582 * <ANY ng-on-event_name="expression"> 8583 * </ANY> 8584 * ``` 8585 * 8586 * @description 8587 * The `ngOn` directive adds an event listener to a DOM element via 8588 * {@link angular.element angular.element().on()}, and evaluates an expression when the event is 8589 * fired. 8590 * `ngOn` allows adding listeners for arbitrary events by including 8591 * the event name in the attribute, e.g. `ng-on-drop="onDrop()"` executes the 'onDrop()' expression 8592 * when the `drop` event is fired. 8593 * 8594 * AngularJS provides specific directives for many events, such as {@link ngClick}, so in most 8595 * cases it is not necessary to use `ngOn`. However, AngularJS does not support all events 8596 * (e.g. the `drop` event in the example above), and new events might be introduced in later DOM 8597 * standards. 8598 * 8599 * Another use-case for `ngOn` is listening to 8600 * [custom events](https://developer.mozilla.org/docs/Web/Guide/Events/Creating_and_triggering_events) 8601 * fired by 8602 * [custom elements](https://developer.mozilla.org/docs/Web/Web_Components/Using_custom_elements). 8603 * 8604 * ## Binding to camelCase properties 8605 * 8606 * Since HTML attributes are case-insensitive, camelCase properties like `myEvent` must be escaped. 8607 * AngularJS uses the underscore (_) in front of a character to indicate that it is uppercase, so 8608 * `myEvent` must be written as `ng-on-my_event="expression"`. 8609 * 8610 * @example 8611 * ### Bind to built-in DOM events 8612 * 8613 * <example name="ngOn" module="exampleNgOn"> 8614 * <file name="app.js"> 8615 * angular.module('exampleNgOn', []) 8616 * .component('main', { 8617 * templateUrl: 'main.html', 8618 * controller: function() { 8619 * this.clickCount = 0; 8620 * this.mouseoverCount = 0; 8621 * 8622 * this.loadingState = 0; 8623 * } 8624 * }); 8625 * </file> 8626 * <file name="main.html"> 8627 * <div> 8628 * This is equivalent to `ngClick` and `ngMouseover`:<br> 8629 * <button 8630 * ng-on-click="$ctrl.clickCount = $ctrl.clickCount + 1" 8631 * ng-on-mouseover="$ctrl.mouseoverCount = $ctrl.mouseoverCount + 1">Click or mouseover</button><br> 8632 * clickCount: {{$ctrl.clickCount}}<br> 8633 * mouseover: {{$ctrl.mouseoverCount}} 8634 * 8635 * <hr> 8636 * 8637 * For the `error` and `load` event on images no built-in AngularJS directives exist:<br> 8638 * <img src="thisimagedoesnotexist.png" ng-on-error="$ctrl.loadingState = -1" ng-on-load="$ctrl.loadingState = 1"><br> 8639 * <div ng-switch="$ctrl.loadingState"> 8640 * <span ng-switch-when="0">Image is loading</span> 8641 * <span ng-switch-when="-1">Image load error</span> 8642 * <span ng-switch-when="1">Image loaded successfully</span> 8643 * </div> 8644 * </div> 8645 * </file> 8646 * <file name="index.html"> 8647 * <main></main> 8648 * </file> 8649 * </example> 8650 * 8651 * 8652 * @example 8653 * ### Bind to custom DOM events 8654 * 8655 * <example name="ngOnCustom" module="exampleNgOn"> 8656 * <file name="app.js"> 8657 * angular.module('exampleNgOn', []) 8658 * .component('main', { 8659 * templateUrl: 'main.html', 8660 * controller: function() { 8661 * this.eventLog = ''; 8662 * 8663 * this.listener = function($event) { 8664 * this.eventLog = 'Event with type "' + $event.type + '" fired at ' + $event.detail; 8665 * }; 8666 * } 8667 * }) 8668 * .component('childComponent', { 8669 * templateUrl: 'child.html', 8670 * controller: function($element) { 8671 * this.fireEvent = function() { 8672 * var event = new CustomEvent('customtype', { detail: new Date()}); 8673 * 8674 * $element[0].dispatchEvent(event); 8675 * }; 8676 * } 8677 * }); 8678 * </file> 8679 * <file name="main.html"> 8680 * <child-component ng-on-customtype="$ctrl.listener($event)"></child-component><br> 8681 * <span>Event log: {{$ctrl.eventLog}}</span> 8682 * </file> 8683 * <file name="child.html"> 8684 <button ng-click="$ctrl.fireEvent()">Fire custom event</button> 8685 * </file> 8686 * <file name="index.html"> 8687 * <main></main> 8688 * </file> 8689 * </example> 8690 */ 8691 8692var $compileMinErr = minErr('$compile'); 8693 8694function UNINITIALIZED_VALUE() {} 8695var _UNINITIALIZED_VALUE = new UNINITIALIZED_VALUE(); 8696 8697/** 8698 * @ngdoc provider 8699 * @name $compileProvider 8700 * 8701 * @description 8702 */ 8703$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider']; 8704/** @this */ 8705function $CompileProvider($provide, $$sanitizeUriProvider) { 8706 var hasDirectives = {}, 8707 Suffix = 'Directive', 8708 COMMENT_DIRECTIVE_REGEXP = /^\s*directive:\s*([\w-]+)\s+(.*)$/, 8709 CLASS_DIRECTIVE_REGEXP = /(([\w-]+)(?::([^;]+))?;?)/, 8710 ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'), 8711 REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/; 8712 8713 // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes 8714 // The assumption is that future DOM event attribute names will begin with 8715 // 'on' and be composed of only English letters. 8716 var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; 8717 var bindingCache = createMap(); 8718 8719 function parseIsolateBindings(scope, directiveName, isController) { 8720 var LOCAL_REGEXP = /^([@&]|[=<](\*?))(\??)\s*([\w$]*)$/; 8721 8722 var bindings = createMap(); 8723 8724 forEach(scope, function(definition, scopeName) { 8725 definition = definition.trim(); 8726 8727 if (definition in bindingCache) { 8728 bindings[scopeName] = bindingCache[definition]; 8729 return; 8730 } 8731 var match = definition.match(LOCAL_REGEXP); 8732 8733 if (!match) { 8734 throw $compileMinErr('iscp', 8735 'Invalid {3} for directive \'{0}\'.' + 8736 ' Definition: {... {1}: \'{2}\' ...}', 8737 directiveName, scopeName, definition, 8738 (isController ? 'controller bindings definition' : 8739 'isolate scope definition')); 8740 } 8741 8742 bindings[scopeName] = { 8743 mode: match[1][0], 8744 collection: match[2] === '*', 8745 optional: match[3] === '?', 8746 attrName: match[4] || scopeName 8747 }; 8748 if (match[4]) { 8749 bindingCache[definition] = bindings[scopeName]; 8750 } 8751 }); 8752 8753 return bindings; 8754 } 8755 8756 function parseDirectiveBindings(directive, directiveName) { 8757 var bindings = { 8758 isolateScope: null, 8759 bindToController: null 8760 }; 8761 if (isObject(directive.scope)) { 8762 if (directive.bindToController === true) { 8763 bindings.bindToController = parseIsolateBindings(directive.scope, 8764 directiveName, true); 8765 bindings.isolateScope = {}; 8766 } else { 8767 bindings.isolateScope = parseIsolateBindings(directive.scope, 8768 directiveName, false); 8769 } 8770 } 8771 if (isObject(directive.bindToController)) { 8772 bindings.bindToController = 8773 parseIsolateBindings(directive.bindToController, directiveName, true); 8774 } 8775 if (bindings.bindToController && !directive.controller) { 8776 // There is no controller 8777 throw $compileMinErr('noctrl', 8778 'Cannot bind to controller without directive \'{0}\'s controller.', 8779 directiveName); 8780 } 8781 return bindings; 8782 } 8783 8784 function assertValidDirectiveName(name) { 8785 var letter = name.charAt(0); 8786 if (!letter || letter !== lowercase(letter)) { 8787 throw $compileMinErr('baddir', 'Directive/Component name \'{0}\' is invalid. The first character must be a lowercase letter', name); 8788 } 8789 if (name !== name.trim()) { 8790 throw $compileMinErr('baddir', 8791 'Directive/Component name \'{0}\' is invalid. The name should not contain leading or trailing whitespaces', 8792 name); 8793 } 8794 } 8795 8796 function getDirectiveRequire(directive) { 8797 var require = directive.require || (directive.controller && directive.name); 8798 8799 if (!isArray(require) && isObject(require)) { 8800 forEach(require, function(value, key) { 8801 var match = value.match(REQUIRE_PREFIX_REGEXP); 8802 var name = value.substring(match[0].length); 8803 if (!name) require[key] = match[0] + key; 8804 }); 8805 } 8806 8807 return require; 8808 } 8809 8810 function getDirectiveRestrict(restrict, name) { 8811 if (restrict && !(isString(restrict) && /[EACM]/.test(restrict))) { 8812 throw $compileMinErr('badrestrict', 8813 'Restrict property \'{0}\' of directive \'{1}\' is invalid', 8814 restrict, 8815 name); 8816 } 8817 8818 return restrict || 'EA'; 8819 } 8820 8821 /** 8822 * @ngdoc method 8823 * @name $compileProvider#directive 8824 * @kind function 8825 * 8826 * @description 8827 * Register a new directive with the compiler. 8828 * 8829 * @param {string|Object} name Name of the directive in camel-case (i.e. `ngBind` which will match 8830 * as `ng-bind`), or an object map of directives where the keys are the names and the values 8831 * are the factories. 8832 * @param {Function|Array} directiveFactory An injectable directive factory function. See the 8833 * {@link guide/directive directive guide} and the {@link $compile compile API} for more info. 8834 * @returns {ng.$compileProvider} Self for chaining. 8835 */ 8836 this.directive = function registerDirective(name, directiveFactory) { 8837 assertArg(name, 'name'); 8838 assertNotHasOwnProperty(name, 'directive'); 8839 if (isString(name)) { 8840 assertValidDirectiveName(name); 8841 assertArg(directiveFactory, 'directiveFactory'); 8842 if (!hasDirectives.hasOwnProperty(name)) { 8843 hasDirectives[name] = []; 8844 $provide.factory(name + Suffix, ['$injector', '$exceptionHandler', 8845 function($injector, $exceptionHandler) { 8846 var directives = []; 8847 forEach(hasDirectives[name], function(directiveFactory, index) { 8848 try { 8849 var directive = $injector.invoke(directiveFactory); 8850 if (isFunction(directive)) { 8851 directive = { compile: valueFn(directive) }; 8852 } else if (!directive.compile && directive.link) { 8853 directive.compile = valueFn(directive.link); 8854 } 8855 directive.priority = directive.priority || 0; 8856 directive.index = index; 8857 directive.name = directive.name || name; 8858 directive.require = getDirectiveRequire(directive); 8859 directive.restrict = getDirectiveRestrict(directive.restrict, name); 8860 directive.$$moduleName = directiveFactory.$$moduleName; 8861 directives.push(directive); 8862 } catch (e) { 8863 $exceptionHandler(e); 8864 } 8865 }); 8866 return directives; 8867 }]); 8868 } 8869 hasDirectives[name].push(directiveFactory); 8870 } else { 8871 forEach(name, reverseParams(registerDirective)); 8872 } 8873 return this; 8874 }; 8875 8876 /** 8877 * @ngdoc method 8878 * @name $compileProvider#component 8879 * @module ng 8880 * @param {string|Object} name Name of the component in camelCase (i.e. `myComp` which will match `<my-comp>`), 8881 * or an object map of components where the keys are the names and the values are the component definition objects. 8882 * @param {Object} options Component definition object (a simplified 8883 * {@link ng.$compile#directive-definition-object directive definition object}), 8884 * with the following properties (all optional): 8885 * 8886 * - `controller` â `{(string|function()=}` â controller constructor function that should be 8887 * associated with newly created scope or the name of a {@link ng.$compile#-controller- 8888 * registered controller} if passed as a string. An empty `noop` function by default. 8889 * - `controllerAs` â `{string=}` â identifier name for to reference the controller in the component's scope. 8890 * If present, the controller will be published to scope under the `controllerAs` name. 8891 * If not present, this will default to be `$ctrl`. 8892 * - `template` â `{string=|function()=}` â html template as a string or a function that 8893 * returns an html template as a string which should be used as the contents of this component. 8894 * Empty string by default. 8895 * 8896 * If `template` is a function, then it is {@link auto.$injector#invoke injected} with 8897 * the following locals: 8898 * 8899 * - `$element` - Current element 8900 * - `$attrs` - Current attributes object for the element 8901 * 8902 * - `templateUrl` â `{string=|function()=}` â path or function that returns a path to an html 8903 * template that should be used as the contents of this component. 8904 * 8905 * If `templateUrl` is a function, then it is {@link auto.$injector#invoke injected} with 8906 * the following locals: 8907 * 8908 * - `$element` - Current element 8909 * - `$attrs` - Current attributes object for the element 8910 * 8911 * - `bindings` â `{object=}` â defines bindings between DOM attributes and component properties. 8912 * Component properties are always bound to the component controller and not to the scope. 8913 * See {@link ng.$compile#-bindtocontroller- `bindToController`}. 8914 * - `transclude` â `{boolean=}` â whether {@link $compile#transclusion content transclusion} is enabled. 8915 * Disabled by default. 8916 * - `require` - `{Object<string, string>=}` - requires the controllers of other directives and binds them to 8917 * this component's controller. The object keys specify the property names under which the required 8918 * controllers (object values) will be bound. See {@link ng.$compile#-require- `require`}. 8919 * - `$...` â additional properties to attach to the directive factory function and the controller 8920 * constructor function. (This is used by the component router to annotate) 8921 * 8922 * @returns {ng.$compileProvider} the compile provider itself, for chaining of function calls. 8923 * @description 8924 * Register a **component definition** with the compiler. This is a shorthand for registering a special 8925 * type of directive, which represents a self-contained UI component in your application. Such components 8926 * are always isolated (i.e. `scope: {}`) and are always restricted to elements (i.e. `restrict: 'E'`). 8927 * 8928 * Component definitions are very simple and do not require as much configuration as defining general 8929 * directives. Component definitions usually consist only of a template and a controller backing it. 8930 * 8931 * In order to make the definition easier, components enforce best practices like use of `controllerAs`, 8932 * `bindToController`. They always have **isolate scope** and are restricted to elements. 8933 * 8934 * Here are a few examples of how you would usually define components: 8935 * 8936 * ```js 8937 * var myMod = angular.module(...); 8938 * myMod.component('myComp', { 8939 * template: '<div>My name is {{$ctrl.name}}</div>', 8940 * controller: function() { 8941 * this.name = 'shahar'; 8942 * } 8943 * }); 8944 * 8945 * myMod.component('myComp', { 8946 * template: '<div>My name is {{$ctrl.name}}</div>', 8947 * bindings: {name: '@'} 8948 * }); 8949 * 8950 * myMod.component('myComp', { 8951 * templateUrl: 'views/my-comp.html', 8952 * controller: 'MyCtrl', 8953 * controllerAs: 'ctrl', 8954 * bindings: {name: '@'} 8955 * }); 8956 * 8957 * ``` 8958 * For more examples, and an in-depth guide, see the {@link guide/component component guide}. 8959 * 8960 * <br /> 8961 * See also {@link ng.$compileProvider#directive $compileProvider.directive()}. 8962 */ 8963 this.component = function registerComponent(name, options) { 8964 if (!isString(name)) { 8965 forEach(name, reverseParams(bind(this, registerComponent))); 8966 return this; 8967 } 8968 8969 var controller = options.controller || function() {}; 8970 8971 function factory($injector) { 8972 function makeInjectable(fn) { 8973 if (isFunction(fn) || isArray(fn)) { 8974 return /** @this */ function(tElement, tAttrs) { 8975 return $injector.invoke(fn, this, {$element: tElement, $attrs: tAttrs}); 8976 }; 8977 } else { 8978 return fn; 8979 } 8980 } 8981 8982 var template = (!options.template && !options.templateUrl ? '' : options.template); 8983 var ddo = { 8984 controller: controller, 8985 controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl', 8986 template: makeInjectable(template), 8987 templateUrl: makeInjectable(options.templateUrl), 8988 transclude: options.transclude, 8989 scope: {}, 8990 bindToController: options.bindings || {}, 8991 restrict: 'E', 8992 require: options.require 8993 }; 8994 8995 // Copy annotations (starting with $) over to the DDO 8996 forEach(options, function(val, key) { 8997 if (key.charAt(0) === '$') ddo[key] = val; 8998 }); 8999 9000 return ddo; 9001 } 9002 9003 // TODO(pete) remove the following `forEach` before we release 1.6.0 9004 // The [email protected] looks for the annotations on the controller constructor 9005 // Nothing in AngularJS looks for annotations on the factory function but we can't remove 9006 // it from 1.5.x yet. 9007 9008 // Copy any annotation properties (starting with $) over to the factory and controller constructor functions 9009 // These could be used by libraries such as the new component router 9010 forEach(options, function(val, key) { 9011 if (key.charAt(0) === '$') { 9012 factory[key] = val; 9013 // Don't try to copy over annotations to named controller 9014 if (isFunction(controller)) controller[key] = val; 9015 } 9016 }); 9017 9018 factory.$inject = ['$injector']; 9019 9020 return this.directive(name, factory); 9021 }; 9022 9023 9024 /** 9025 * @ngdoc method 9026 * @name $compileProvider#aHrefSanitizationTrustedUrlList 9027 * @kind function 9028 * 9029 * @description 9030 * Retrieves or overrides the default regular expression that is used for determining trusted safe 9031 * urls during a[href] sanitization. 9032 * 9033 * The sanitization is a security measure aimed at preventing XSS attacks via html links. 9034 * 9035 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 9036 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationTrustedUrlList` 9037 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 9038 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 9039 * 9040 * @param {RegExp=} regexp New regexp to trust urls with. 9041 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 9042 * chaining otherwise. 9043 */ 9044 this.aHrefSanitizationTrustedUrlList = function(regexp) { 9045 if (isDefined(regexp)) { 9046 $$sanitizeUriProvider.aHrefSanitizationTrustedUrlList(regexp); 9047 return this; 9048 } else { 9049 return $$sanitizeUriProvider.aHrefSanitizationTrustedUrlList(); 9050 } 9051 }; 9052 9053 9054 /** 9055 * @ngdoc method 9056 * @name $compileProvider#aHrefSanitizationWhitelist 9057 * @kind function 9058 * 9059 * @deprecated 9060 * sinceVersion="1.8.1" 9061 * 9062 * This method is deprecated. Use {@link $compileProvider#aHrefSanitizationTrustedUrlList 9063 * aHrefSanitizationTrustedUrlList} instead. 9064 */ 9065 Object.defineProperty(this, 'aHrefSanitizationWhitelist', { 9066 get: function() { 9067 return this.aHrefSanitizationTrustedUrlList; 9068 }, 9069 set: function(value) { 9070 this.aHrefSanitizationTrustedUrlList = value; 9071 } 9072 }); 9073 9074 9075 /** 9076 * @ngdoc method 9077 * @name $compileProvider#imgSrcSanitizationTrustedUrlList 9078 * @kind function 9079 * 9080 * @description 9081 * Retrieves or overrides the default regular expression that is used for determining trusted safe 9082 * urls during img[src] sanitization. 9083 * 9084 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 9085 * 9086 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 9087 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationTrustedUrlList` 9088 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 9089 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 9090 * 9091 * @param {RegExp=} regexp New regexp to trust urls with. 9092 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 9093 * chaining otherwise. 9094 */ 9095 this.imgSrcSanitizationTrustedUrlList = function(regexp) { 9096 if (isDefined(regexp)) { 9097 $$sanitizeUriProvider.imgSrcSanitizationTrustedUrlList(regexp); 9098 return this; 9099 } else { 9100 return $$sanitizeUriProvider.imgSrcSanitizationTrustedUrlList(); 9101 } 9102 }; 9103 9104 9105 /** 9106 * @ngdoc method 9107 * @name $compileProvider#imgSrcSanitizationWhitelist 9108 * @kind function 9109 * 9110 * @deprecated 9111 * sinceVersion="1.8.1" 9112 * 9113 * This method is deprecated. Use {@link $compileProvider#imgSrcSanitizationTrustedUrlList 9114 * imgSrcSanitizationTrustedUrlList} instead. 9115 */ 9116 Object.defineProperty(this, 'imgSrcSanitizationWhitelist', { 9117 get: function() { 9118 return this.imgSrcSanitizationTrustedUrlList; 9119 }, 9120 set: function(value) { 9121 this.imgSrcSanitizationTrustedUrlList = value; 9122 } 9123 }); 9124 9125 /** 9126 * @ngdoc method 9127 * @name $compileProvider#debugInfoEnabled 9128 * 9129 * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the 9130 * current debugInfoEnabled state 9131 * @returns {*} current value if used as getter or itself (chaining) if used as setter 9132 * 9133 * @kind function 9134 * 9135 * @description 9136 * Call this method to enable/disable various debug runtime information in the compiler such as adding 9137 * binding information and a reference to the current scope on to DOM elements. 9138 * If enabled, the compiler will add the following to DOM elements that have been bound to the scope 9139 * * `ng-binding` CSS class 9140 * * `ng-scope` and `ng-isolated-scope` CSS classes 9141 * * `$binding` data property containing an array of the binding expressions 9142 * * Data properties used by the {@link angular.element#methods `scope()`/`isolateScope()` methods} to return 9143 * the element's scope. 9144 * * Placeholder comments will contain information about what directive and binding caused the placeholder. 9145 * E.g. `<!-- ngIf: shouldShow() -->`. 9146 * 9147 * You may want to disable this in production for a significant performance boost. See 9148 * {@link guide/production#disabling-debug-data Disabling Debug Data} for more. 9149 * 9150 * The default value is true. 9151 */ 9152 var debugInfoEnabled = true; 9153 this.debugInfoEnabled = function(enabled) { 9154 if (isDefined(enabled)) { 9155 debugInfoEnabled = enabled; 9156 return this; 9157 } 9158 return debugInfoEnabled; 9159 }; 9160 9161 /** 9162 * @ngdoc method 9163 * @name $compileProvider#strictComponentBindingsEnabled 9164 * 9165 * @param {boolean=} enabled update the strictComponentBindingsEnabled state if provided, 9166 * otherwise return the current strictComponentBindingsEnabled state. 9167 * @returns {*} current value if used as getter or itself (chaining) if used as setter 9168 * 9169 * @kind function 9170 * 9171 * @description 9172 * Call this method to enable / disable the strict component bindings check. If enabled, the 9173 * compiler will enforce that all scope / controller bindings of a 9174 * {@link $compileProvider#directive directive} / {@link $compileProvider#component component} 9175 * that are not set as optional with `?`, must be provided when the directive is instantiated. 9176 * If not provided, the compiler will throw the 9177 * {@link error/$compile/missingattr $compile:missingattr error}. 9178 * 9179 * The default value is false. 9180 */ 9181 var strictComponentBindingsEnabled = false; 9182 this.strictComponentBindingsEnabled = function(enabled) { 9183 if (isDefined(enabled)) { 9184 strictComponentBindingsEnabled = enabled; 9185 return this; 9186 } 9187 return strictComponentBindingsEnabled; 9188 }; 9189 9190 var TTL = 10; 9191 /** 9192 * @ngdoc method 9193 * @name $compileProvider#onChangesTtl 9194 * @description 9195 * 9196 * Sets the number of times `$onChanges` hooks can trigger new changes before giving up and 9197 * assuming that the model is unstable. 9198 * 9199 * The current default is 10 iterations. 9200 * 9201 * In complex applications it's possible that dependencies between `$onChanges` hooks and bindings will result 9202 * in several iterations of calls to these hooks. However if an application needs more than the default 10 9203 * iterations to stabilize then you should investigate what is causing the model to continuously change during 9204 * the `$onChanges` hook execution. 9205 * 9206 * Increasing the TTL could have performance implications, so you should not change it without proper justification. 9207 * 9208 * @param {number} limit The number of `$onChanges` hook iterations. 9209 * @returns {number|object} the current limit (or `this` if called as a setter for chaining) 9210 */ 9211 this.onChangesTtl = function(value) { 9212 if (arguments.length) { 9213 TTL = value; 9214 return this; 9215 } 9216 return TTL; 9217 }; 9218 9219 var commentDirectivesEnabledConfig = true; 9220 /** 9221 * @ngdoc method 9222 * @name $compileProvider#commentDirectivesEnabled 9223 * @description 9224 * 9225 * It indicates to the compiler 9226 * whether or not directives on comments should be compiled. 9227 * Defaults to `true`. 9228 * 9229 * Calling this function with false disables the compilation of directives 9230 * on comments for the whole application. 9231 * This results in a compilation performance gain, 9232 * as the compiler doesn't have to check comments when looking for directives. 9233 * This should however only be used if you are sure that no comment directives are used in 9234 * the application (including any 3rd party directives). 9235 * 9236 * @param {boolean} enabled `false` if the compiler may ignore directives on comments 9237 * @returns {boolean|object} the current value (or `this` if called as a setter for chaining) 9238 */ 9239 this.commentDirectivesEnabled = function(value) { 9240 if (arguments.length) { 9241 commentDirectivesEnabledConfig = value; 9242 return this; 9243 } 9244 return commentDirectivesEnabledConfig; 9245 }; 9246 9247 9248 var cssClassDirectivesEnabledConfig = true; 9249 /** 9250 * @ngdoc method 9251 * @name $compileProvider#cssClassDirectivesEnabled 9252 * @description 9253 * 9254 * It indicates to the compiler 9255 * whether or not directives on element classes should be compiled. 9256 * Defaults to `true`. 9257 * 9258 * Calling this function with false disables the compilation of directives 9259 * on element classes for the whole application. 9260 * This results in a compilation performance gain, 9261 * as the compiler doesn't have to check element classes when looking for directives. 9262 * This should however only be used if you are sure that no class directives are used in 9263 * the application (including any 3rd party directives). 9264 * 9265 * @param {boolean} enabled `false` if the compiler may ignore directives on element classes 9266 * @returns {boolean|object} the current value (or `this` if called as a setter for chaining) 9267 */ 9268 this.cssClassDirectivesEnabled = function(value) { 9269 if (arguments.length) { 9270 cssClassDirectivesEnabledConfig = value; 9271 return this; 9272 } 9273 return cssClassDirectivesEnabledConfig; 9274 }; 9275 9276 9277 /** 9278 * The security context of DOM Properties. 9279 * @private 9280 */ 9281 var PROP_CONTEXTS = createMap(); 9282 9283 /** 9284 * @ngdoc method 9285 * @name $compileProvider#addPropertySecurityContext 9286 * @description 9287 * 9288 * Defines the security context for DOM properties bound by ng-prop-*. 9289 * 9290 * @param {string} elementName The element name or '*' to match any element. 9291 * @param {string} propertyName The DOM property name. 9292 * @param {string} ctx The {@link $sce} security context in which this value is safe for use, e.g. `$sce.URL` 9293 * @returns {object} `this` for chaining 9294 */ 9295 this.addPropertySecurityContext = function(elementName, propertyName, ctx) { 9296 var key = (elementName.toLowerCase() + '|' + propertyName.toLowerCase()); 9297 9298 if (key in PROP_CONTEXTS && PROP_CONTEXTS[key] !== ctx) { 9299 throw $compileMinErr('ctxoverride', 'Property context \'{0}.{1}\' already set to \'{2}\', cannot override to \'{3}\'.', elementName, propertyName, PROP_CONTEXTS[key], ctx); 9300 } 9301 9302 PROP_CONTEXTS[key] = ctx; 9303 return this; 9304 }; 9305 9306 /* Default property contexts. 9307 * 9308 * Copy of https://github.com/angular/angular/blob/6.0.6/packages/compiler/src/schema/dom_security_schema.ts#L31-L58 9309 * Changing: 9310 * - SecurityContext.* => SCE_CONTEXTS/$sce.* 9311 * - STYLE => CSS 9312 * - various URL => MEDIA_URL 9313 * - *|formAction, form|action URL => RESOURCE_URL (like the attribute) 9314 */ 9315 (function registerNativePropertyContexts() { 9316 function registerContext(ctx, values) { 9317 forEach(values, function(v) { PROP_CONTEXTS[v.toLowerCase()] = ctx; }); 9318 } 9319 9320 registerContext(SCE_CONTEXTS.HTML, [ 9321 'iframe|srcdoc', 9322 '*|innerHTML', 9323 '*|outerHTML' 9324 ]); 9325 registerContext(SCE_CONTEXTS.CSS, ['*|style']); 9326 registerContext(SCE_CONTEXTS.URL, [ 9327 'area|href', 'area|ping', 9328 'a|href', 'a|ping', 9329 'blockquote|cite', 9330 'body|background', 9331 'del|cite', 9332 'input|src', 9333 'ins|cite', 9334 'q|cite' 9335 ]); 9336 registerContext(SCE_CONTEXTS.MEDIA_URL, [ 9337 'audio|src', 9338 'img|src', 'img|srcset', 9339 'source|src', 'source|srcset', 9340 'track|src', 9341 'video|src', 'video|poster' 9342 ]); 9343 registerContext(SCE_CONTEXTS.RESOURCE_URL, [ 9344 '*|formAction', 9345 'applet|code', 'applet|codebase', 9346 'base|href', 9347 'embed|src', 9348 'frame|src', 9349 'form|action', 9350 'head|profile', 9351 'html|manifest', 9352 'iframe|src', 9353 'link|href', 9354 'media|src', 9355 'object|codebase', 'object|data', 9356 'script|src' 9357 ]); 9358 })(); 9359 9360 9361 this.$get = [ 9362 '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse', 9363 '$controller', '$rootScope', '$sce', '$animate', 9364 function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse, 9365 $controller, $rootScope, $sce, $animate) { 9366 9367 var SIMPLE_ATTR_NAME = /^\w/; 9368 var specialAttrHolder = window.document.createElement('div'); 9369 9370 9371 var commentDirectivesEnabled = commentDirectivesEnabledConfig; 9372 var cssClassDirectivesEnabled = cssClassDirectivesEnabledConfig; 9373 9374 9375 var onChangesTtl = TTL; 9376 // The onChanges hooks should all be run together in a single digest 9377 // When changes occur, the call to trigger their hooks will be added to this queue 9378 var onChangesQueue; 9379 9380 // This function is called in a $$postDigest to trigger all the onChanges hooks in a single digest 9381 function flushOnChangesQueue() { 9382 try { 9383 if (!(--onChangesTtl)) { 9384 // We have hit the TTL limit so reset everything 9385 onChangesQueue = undefined; 9386 throw $compileMinErr('infchng', '{0} $onChanges() iterations reached. Aborting!\n', TTL); 9387 } 9388 // We must run this hook in an apply since the $$postDigest runs outside apply 9389 $rootScope.$apply(function() { 9390 for (var i = 0, ii = onChangesQueue.length; i < ii; ++i) { 9391 try { 9392 onChangesQueue[i](); 9393 } catch (e) { 9394 $exceptionHandler(e); 9395 } 9396 } 9397 // Reset the queue to trigger a new schedule next time there is a change 9398 onChangesQueue = undefined; 9399 }); 9400 } finally { 9401 onChangesTtl++; 9402 } 9403 } 9404 9405 9406 function sanitizeSrcset(value, invokeType) { 9407 if (!value) { 9408 return value; 9409 } 9410 if (!isString(value)) { 9411 throw $compileMinErr('srcset', 'Can\'t pass trusted values to `{0}`: "{1}"', invokeType, value.toString()); 9412 } 9413 9414 // Such values are a bit too complex to handle automatically inside $sce. 9415 // Instead, we sanitize each of the URIs individually, which works, even dynamically. 9416 9417 // It's not possible to work around this using `$sce.trustAsMediaUrl`. 9418 // If you want to programmatically set explicitly trusted unsafe URLs, you should use 9419 // `$sce.trustAsHtml` on the whole `img` tag and inject it into the DOM using the 9420 // `ng-bind-html` directive. 9421 9422 var result = ''; 9423 9424 // first check if there are spaces because it's not the same pattern 9425 var trimmedSrcset = trim(value); 9426 // ( 999x ,| 999w ,| ,|, ) 9427 var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/; 9428 var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/; 9429 9430 // split srcset into tuple of uri and descriptor except for the last item 9431 var rawUris = trimmedSrcset.split(pattern); 9432 9433 // for each tuples 9434 var nbrUrisWith2parts = Math.floor(rawUris.length / 2); 9435 for (var i = 0; i < nbrUrisWith2parts; i++) { 9436 var innerIdx = i * 2; 9437 // sanitize the uri 9438 result += $sce.getTrustedMediaUrl(trim(rawUris[innerIdx])); 9439 // add the descriptor 9440 result += ' ' + trim(rawUris[innerIdx + 1]); 9441 } 9442 9443 // split the last item into uri and descriptor 9444 var lastTuple = trim(rawUris[i * 2]).split(/\s/); 9445 9446 // sanitize the last uri 9447 result += $sce.getTrustedMediaUrl(trim(lastTuple[0])); 9448 9449 // and add the last descriptor if any 9450 if (lastTuple.length === 2) { 9451 result += (' ' + trim(lastTuple[1])); 9452 } 9453 return result; 9454 } 9455 9456 9457 function Attributes(element, attributesToCopy) { 9458 if (attributesToCopy) { 9459 var keys = Object.keys(attributesToCopy); 9460 var i, l, key; 9461 9462 for (i = 0, l = keys.length; i < l; i++) { 9463 key = keys[i]; 9464 this[key] = attributesToCopy[key]; 9465 } 9466 } else { 9467 this.$attr = {}; 9468 } 9469 9470 this.$$element = element; 9471 } 9472 9473 Attributes.prototype = { 9474 /** 9475 * @ngdoc method 9476 * @name $compile.directive.Attributes#$normalize 9477 * @kind function 9478 * 9479 * @description 9480 * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or 9481 * `data-`) to its normalized, camelCase form. 9482 * 9483 * Also there is special case for Moz prefix starting with upper case letter. 9484 * 9485 * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives} 9486 * 9487 * @param {string} name Name to normalize 9488 */ 9489 $normalize: directiveNormalize, 9490 9491 9492 /** 9493 * @ngdoc method 9494 * @name $compile.directive.Attributes#$addClass 9495 * @kind function 9496 * 9497 * @description 9498 * Adds the CSS class value specified by the classVal parameter to the element. If animations 9499 * are enabled then an animation will be triggered for the class addition. 9500 * 9501 * @param {string} classVal The className value that will be added to the element 9502 */ 9503 $addClass: function(classVal) { 9504 if (classVal && classVal.length > 0) { 9505 $animate.addClass(this.$$element, classVal); 9506 } 9507 }, 9508 9509 /** 9510 * @ngdoc method 9511 * @name $compile.directive.Attributes#$removeClass 9512 * @kind function 9513 * 9514 * @description 9515 * Removes the CSS class value specified by the classVal parameter from the element. If 9516 * animations are enabled then an animation will be triggered for the class removal. 9517 * 9518 * @param {string} classVal The className value that will be removed from the element 9519 */ 9520 $removeClass: function(classVal) { 9521 if (classVal && classVal.length > 0) { 9522 $animate.removeClass(this.$$element, classVal); 9523 } 9524 }, 9525 9526 /** 9527 * @ngdoc method 9528 * @name $compile.directive.Attributes#$updateClass 9529 * @kind function 9530 * 9531 * @description 9532 * Adds and removes the appropriate CSS class values to the element based on the difference 9533 * between the new and old CSS class values (specified as newClasses and oldClasses). 9534 * 9535 * @param {string} newClasses The current CSS className value 9536 * @param {string} oldClasses The former CSS className value 9537 */ 9538 $updateClass: function(newClasses, oldClasses) { 9539 var toAdd = tokenDifference(newClasses, oldClasses); 9540 if (toAdd && toAdd.length) { 9541 $animate.addClass(this.$$element, toAdd); 9542 } 9543 9544 var toRemove = tokenDifference(oldClasses, newClasses); 9545 if (toRemove && toRemove.length) { 9546 $animate.removeClass(this.$$element, toRemove); 9547 } 9548 }, 9549 9550 /** 9551 * Set a normalized attribute on the element in a way such that all directives 9552 * can share the attribute. This function properly handles boolean attributes. 9553 * @param {string} key Normalized key. (ie ngAttribute) 9554 * @param {string|boolean} value The value to set. If `null` attribute will be deleted. 9555 * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. 9556 * Defaults to true. 9557 * @param {string=} attrName Optional none normalized name. Defaults to key. 9558 */ 9559 $set: function(key, value, writeAttr, attrName) { 9560 // TODO: decide whether or not to throw an error if "class" 9561 // is set through this function since it may cause $updateClass to 9562 // become unstable. 9563 9564 var node = this.$$element[0], 9565 booleanKey = getBooleanAttrName(node, key), 9566 aliasedKey = getAliasedAttrName(key), 9567 observer = key, 9568 nodeName; 9569 9570 if (booleanKey) { 9571 this.$$element.prop(key, value); 9572 attrName = booleanKey; 9573 } else if (aliasedKey) { 9574 this[aliasedKey] = value; 9575 observer = aliasedKey; 9576 } 9577 9578 this[key] = value; 9579 9580 // translate normalized key to actual key 9581 if (attrName) { 9582 this.$attr[key] = attrName; 9583 } else { 9584 attrName = this.$attr[key]; 9585 if (!attrName) { 9586 this.$attr[key] = attrName = snake_case(key, '-'); 9587 } 9588 } 9589 9590 nodeName = nodeName_(this.$$element); 9591 9592 // Sanitize img[srcset] values. 9593 if (nodeName === 'img' && key === 'srcset') { 9594 this[key] = value = sanitizeSrcset(value, '$set(\'srcset\', value)'); 9595 } 9596 9597 if (writeAttr !== false) { 9598 if (value === null || isUndefined(value)) { 9599 this.$$element.removeAttr(attrName); 9600 } else { 9601 if (SIMPLE_ATTR_NAME.test(attrName)) { 9602 // jQuery skips special boolean attrs treatment in XML nodes for 9603 // historical reasons and hence AngularJS cannot freely call 9604 // `.attr(attrName, false) with such attributes. To avoid issues 9605 // in XHTML, call `removeAttr` in such cases instead. 9606 // See https://github.com/jquery/jquery/issues/4249 9607 if (booleanKey && value === false) { 9608 this.$$element.removeAttr(attrName); 9609 } else { 9610 this.$$element.attr(attrName, value); 9611 } 9612 } else { 9613 setSpecialAttr(this.$$element[0], attrName, value); 9614 } 9615 } 9616 } 9617 9618 // fire observers 9619 var $$observers = this.$$observers; 9620 if ($$observers) { 9621 forEach($$observers[observer], function(fn) { 9622 try { 9623 fn(value); 9624 } catch (e) { 9625 $exceptionHandler(e); 9626 } 9627 }); 9628 } 9629 }, 9630 9631 9632 /** 9633 * @ngdoc method 9634 * @name $compile.directive.Attributes#$observe 9635 * @kind function 9636 * 9637 * @description 9638 * Observes an interpolated attribute. 9639 * 9640 * The observer function will be invoked once during the next `$digest` following 9641 * compilation. The observer is then invoked whenever the interpolated value 9642 * changes. 9643 * 9644 * @param {string} key Normalized key. (ie ngAttribute) . 9645 * @param {function(interpolatedValue)} fn Function that will be called whenever 9646 the interpolated value of the attribute changes. 9647 * See the {@link guide/interpolation#how-text-and-attribute-bindings-work Interpolation 9648 * guide} for more info. 9649 * @returns {function()} Returns a deregistration function for this observer. 9650 */ 9651 $observe: function(key, fn) { 9652 var attrs = this, 9653 $$observers = (attrs.$$observers || (attrs.$$observers = createMap())), 9654 listeners = ($$observers[key] || ($$observers[key] = [])); 9655 9656 listeners.push(fn); 9657 $rootScope.$evalAsync(function() { 9658 if (!listeners.$$inter && attrs.hasOwnProperty(key) && !isUndefined(attrs[key])) { 9659 // no one registered attribute interpolation function, so lets call it manually 9660 fn(attrs[key]); 9661 } 9662 }); 9663 9664 return function() { 9665 arrayRemove(listeners, fn); 9666 }; 9667 } 9668 }; 9669 9670 function setSpecialAttr(element, attrName, value) { 9671 // Attributes names that do not start with letters (such as `(click)`) cannot be set using `setAttribute` 9672 // so we have to jump through some hoops to get such an attribute 9673 // https://github.com/angular/angular.js/pull/13318 9674 specialAttrHolder.innerHTML = '<span ' + attrName + '>'; 9675 var attributes = specialAttrHolder.firstChild.attributes; 9676 var attribute = attributes[0]; 9677 // We have to remove the attribute from its container element before we can add it to the destination element 9678 attributes.removeNamedItem(attribute.name); 9679 attribute.value = value; 9680 element.attributes.setNamedItem(attribute); 9681 } 9682 9683 function safeAddClass($element, className) { 9684 try { 9685 $element.addClass(className); 9686 } catch (e) { 9687 // ignore, since it means that we are trying to set class on 9688 // SVG element, where class name is read-only. 9689 } 9690 } 9691 9692 9693 var startSymbol = $interpolate.startSymbol(), 9694 endSymbol = $interpolate.endSymbol(), 9695 denormalizeTemplate = (startSymbol === '{{' && endSymbol === '}}') 9696 ? identity 9697 : function denormalizeTemplate(template) { 9698 return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); 9699 }, 9700 NG_PREFIX_BINDING = /^ng(Attr|Prop|On)([A-Z].*)$/; 9701 var MULTI_ELEMENT_DIR_RE = /^(.+)Start$/; 9702 9703 compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) { 9704 var bindings = $element.data('$binding') || []; 9705 9706 if (isArray(binding)) { 9707 bindings = bindings.concat(binding); 9708 } else { 9709 bindings.push(binding); 9710 } 9711 9712 $element.data('$binding', bindings); 9713 } : noop; 9714 9715 compile.$$addBindingClass = debugInfoEnabled ? function $$addBindingClass($element) { 9716 safeAddClass($element, 'ng-binding'); 9717 } : noop; 9718 9719 compile.$$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo($element, scope, isolated, noTemplate) { 9720 var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope'; 9721 $element.data(dataName, scope); 9722 } : noop; 9723 9724 compile.$$addScopeClass = debugInfoEnabled ? function $$addScopeClass($element, isolated) { 9725 safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope'); 9726 } : noop; 9727 9728 compile.$$createComment = function(directiveName, comment) { 9729 var content = ''; 9730 if (debugInfoEnabled) { 9731 content = ' ' + (directiveName || '') + ': '; 9732 if (comment) content += comment + ' '; 9733 } 9734 return window.document.createComment(content); 9735 }; 9736 9737 return compile; 9738 9739 //================================ 9740 9741 function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, 9742 previousCompileContext) { 9743 if (!($compileNodes instanceof jqLite)) { 9744 // jquery always rewraps, whereas we need to preserve the original selector so that we can 9745 // modify it. 9746 $compileNodes = jqLite($compileNodes); 9747 } 9748 var compositeLinkFn = 9749 compileNodes($compileNodes, transcludeFn, $compileNodes, 9750 maxPriority, ignoreDirective, previousCompileContext); 9751 compile.$$addScopeClass($compileNodes); 9752 var namespace = null; 9753 return function publicLinkFn(scope, cloneConnectFn, options) { 9754 if (!$compileNodes) { 9755 throw $compileMinErr('multilink', 'This element has already been linked.'); 9756 } 9757 assertArg(scope, 'scope'); 9758 9759 if (previousCompileContext && previousCompileContext.needsNewScope) { 9760 // A parent directive did a replace and a directive on this element asked 9761 // for transclusion, which caused us to lose a layer of element on which 9762 // we could hold the new transclusion scope, so we will create it manually 9763 // here. 9764 scope = scope.$parent.$new(); 9765 } 9766 9767 options = options || {}; 9768 var parentBoundTranscludeFn = options.parentBoundTranscludeFn, 9769 transcludeControllers = options.transcludeControllers, 9770 futureParentElement = options.futureParentElement; 9771 9772 // When `parentBoundTranscludeFn` is passed, it is a 9773 // `controllersBoundTransclude` function (it was previously passed 9774 // as `transclude` to directive.link) so we must unwrap it to get 9775 // its `boundTranscludeFn` 9776 if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) { 9777 parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude; 9778 } 9779 9780 if (!namespace) { 9781 namespace = detectNamespaceForChildElements(futureParentElement); 9782 } 9783 var $linkNode; 9784 if (namespace !== 'html') { 9785 // When using a directive with replace:true and templateUrl the $compileNodes 9786 // (or a child element inside of them) 9787 // might change, so we need to recreate the namespace adapted compileNodes 9788 // for call to the link function. 9789 // Note: This will already clone the nodes... 9790 $linkNode = jqLite( 9791 wrapTemplate(namespace, jqLite('<div></div>').append($compileNodes).html()) 9792 ); 9793 } else if (cloneConnectFn) { 9794 // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart 9795 // and sometimes changes the structure of the DOM. 9796 $linkNode = JQLitePrototype.clone.call($compileNodes); 9797 } else { 9798 $linkNode = $compileNodes; 9799 } 9800 9801 if (transcludeControllers) { 9802 for (var controllerName in transcludeControllers) { 9803 $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance); 9804 } 9805 } 9806 9807 compile.$$addScopeInfo($linkNode, scope); 9808 9809 if (cloneConnectFn) cloneConnectFn($linkNode, scope); 9810 if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn); 9811 9812 if (!cloneConnectFn) { 9813 $compileNodes = compositeLinkFn = null; 9814 } 9815 return $linkNode; 9816 }; 9817 } 9818 9819 function detectNamespaceForChildElements(parentElement) { 9820 // TODO: Make this detect MathML as well... 9821 var node = parentElement && parentElement[0]; 9822 if (!node) { 9823 return 'html'; 9824 } else { 9825 return nodeName_(node) !== 'foreignobject' && toString.call(node).match(/SVG/) ? 'svg' : 'html'; 9826 } 9827 } 9828 9829 /** 9830 * Compile function matches each node in nodeList against the directives. Once all directives 9831 * for a particular node are collected their compile functions are executed. The compile 9832 * functions return values - the linking functions - are combined into a composite linking 9833 * function, which is the a linking function for the node. 9834 * 9835 * @param {NodeList} nodeList an array of nodes or NodeList to compile 9836 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 9837 * scope argument is auto-generated to the new child of the transcluded parent scope. 9838 * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then 9839 * the rootElement must be set the jqLite collection of the compile root. This is 9840 * needed so that the jqLite collection items can be replaced with widgets. 9841 * @param {number=} maxPriority Max directive priority. 9842 * @returns {Function} A composite linking function of all of the matched directives or null. 9843 */ 9844 function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, 9845 previousCompileContext) { 9846 var linkFns = [], 9847 // `nodeList` can be either an element's `.childNodes` (live NodeList) 9848 // or a jqLite/jQuery collection or an array 9849 notLiveList = isArray(nodeList) || (nodeList instanceof jqLite), 9850 attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound; 9851 9852 9853 for (var i = 0; i < nodeList.length; i++) { 9854 attrs = new Attributes(); 9855 9856 // Support: IE 11 only 9857 // Workaround for #11781 and #14924 9858 if (msie === 11) { 9859 mergeConsecutiveTextNodes(nodeList, i, notLiveList); 9860 } 9861 9862 // We must always refer to `nodeList[i]` hereafter, 9863 // since the nodes can be replaced underneath us. 9864 directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined, 9865 ignoreDirective); 9866 9867 nodeLinkFn = (directives.length) 9868 ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, 9869 null, [], [], previousCompileContext) 9870 : null; 9871 9872 if (nodeLinkFn && nodeLinkFn.scope) { 9873 compile.$$addScopeClass(attrs.$$element); 9874 } 9875 9876 childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || 9877 !(childNodes = nodeList[i].childNodes) || 9878 !childNodes.length) 9879 ? null 9880 : compileNodes(childNodes, 9881 nodeLinkFn ? ( 9882 (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement) 9883 && nodeLinkFn.transclude) : transcludeFn); 9884 9885 if (nodeLinkFn || childLinkFn) { 9886 linkFns.push(i, nodeLinkFn, childLinkFn); 9887 linkFnFound = true; 9888 nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn; 9889 } 9890 9891 //use the previous context only for the first element in the virtual group 9892 previousCompileContext = null; 9893 } 9894 9895 // return a linking function if we have found anything, null otherwise 9896 return linkFnFound ? compositeLinkFn : null; 9897 9898 function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) { 9899 var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn; 9900 var stableNodeList; 9901 9902 9903 if (nodeLinkFnFound) { 9904 // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our 9905 // offsets don't get screwed up 9906 var nodeListLength = nodeList.length; 9907 stableNodeList = new Array(nodeListLength); 9908 9909 // create a sparse array by only copying the elements which have a linkFn 9910 for (i = 0; i < linkFns.length; i += 3) { 9911 idx = linkFns[i]; 9912 stableNodeList[idx] = nodeList[idx]; 9913 } 9914 } else { 9915 stableNodeList = nodeList; 9916 } 9917 9918 for (i = 0, ii = linkFns.length; i < ii;) { 9919 node = stableNodeList[linkFns[i++]]; 9920 nodeLinkFn = linkFns[i++]; 9921 childLinkFn = linkFns[i++]; 9922 9923 if (nodeLinkFn) { 9924 if (nodeLinkFn.scope) { 9925 childScope = scope.$new(); 9926 compile.$$addScopeInfo(jqLite(node), childScope); 9927 } else { 9928 childScope = scope; 9929 } 9930 9931 if (nodeLinkFn.transcludeOnThisElement) { 9932 childBoundTranscludeFn = createBoundTranscludeFn( 9933 scope, nodeLinkFn.transclude, parentBoundTranscludeFn); 9934 9935 } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) { 9936 childBoundTranscludeFn = parentBoundTranscludeFn; 9937 9938 } else if (!parentBoundTranscludeFn && transcludeFn) { 9939 childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn); 9940 9941 } else { 9942 childBoundTranscludeFn = null; 9943 } 9944 9945 nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn); 9946 9947 } else if (childLinkFn) { 9948 childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn); 9949 } 9950 } 9951 } 9952 } 9953 9954 function mergeConsecutiveTextNodes(nodeList, idx, notLiveList) { 9955 var node = nodeList[idx]; 9956 var parent = node.parentNode; 9957 var sibling; 9958 9959 if (node.nodeType !== NODE_TYPE_TEXT) { 9960 return; 9961 } 9962 9963 while (true) { 9964 sibling = parent ? node.nextSibling : nodeList[idx + 1]; 9965 if (!sibling || sibling.nodeType !== NODE_TYPE_TEXT) { 9966 break; 9967 } 9968 9969 node.nodeValue = node.nodeValue + sibling.nodeValue; 9970 9971 if (sibling.parentNode) { 9972 sibling.parentNode.removeChild(sibling); 9973 } 9974 if (notLiveList && sibling === nodeList[idx + 1]) { 9975 nodeList.splice(idx + 1, 1); 9976 } 9977 } 9978 } 9979 9980 function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) { 9981 function boundTranscludeFn(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) { 9982 9983 if (!transcludedScope) { 9984 transcludedScope = scope.$new(false, containingScope); 9985 transcludedScope.$$transcluded = true; 9986 } 9987 9988 return transcludeFn(transcludedScope, cloneFn, { 9989 parentBoundTranscludeFn: previousBoundTranscludeFn, 9990 transcludeControllers: controllers, 9991 futureParentElement: futureParentElement 9992 }); 9993 } 9994 9995 // We need to attach the transclusion slots onto the `boundTranscludeFn` 9996 // so that they are available inside the `controllersBoundTransclude` function 9997 var boundSlots = boundTranscludeFn.$$slots = createMap(); 9998 for (var slotName in transcludeFn.$$slots) { 9999 if (transcludeFn.$$slots[slotName]) { 10000 boundSlots[slotName] = createBoundTranscludeFn(scope, transcludeFn.$$slots[slotName], previousBoundTranscludeFn); 10001 } else { 10002 boundSlots[slotName] = null; 10003 } 10004 } 10005 10006 return boundTranscludeFn; 10007 } 10008 10009 /** 10010 * Looks for directives on the given node and adds them to the directive collection which is 10011 * sorted. 10012 * 10013 * @param node Node to search. 10014 * @param directives An array to which the directives are added to. This array is sorted before 10015 * the function returns. 10016 * @param attrs The shared attrs object which is used to populate the normalized attributes. 10017 * @param {number=} maxPriority Max directive priority. 10018 */ 10019 function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) { 10020 var nodeType = node.nodeType, 10021 attrsMap = attrs.$attr, 10022 match, 10023 nodeName, 10024 className; 10025 10026 switch (nodeType) { 10027 case NODE_TYPE_ELEMENT: /* Element */ 10028 10029 nodeName = nodeName_(node); 10030 10031 // use the node name: <directive> 10032 addDirective(directives, 10033 directiveNormalize(nodeName), 'E', maxPriority, ignoreDirective); 10034 10035 // iterate over the attributes 10036 for (var attr, name, nName, value, ngPrefixMatch, nAttrs = node.attributes, 10037 j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { 10038 var attrStartName = false; 10039 var attrEndName = false; 10040 10041 var isNgAttr = false, isNgProp = false, isNgEvent = false; 10042 var multiElementMatch; 10043 10044 attr = nAttrs[j]; 10045 name = attr.name; 10046 value = attr.value; 10047 10048 nName = directiveNormalize(name.toLowerCase()); 10049 10050 // Support ng-attr-*, ng-prop-* and ng-on-* 10051 if ((ngPrefixMatch = nName.match(NG_PREFIX_BINDING))) { 10052 isNgAttr = ngPrefixMatch[1] === 'Attr'; 10053 isNgProp = ngPrefixMatch[1] === 'Prop'; 10054 isNgEvent = ngPrefixMatch[1] === 'On'; 10055 10056 // Normalize the non-prefixed name 10057 name = name.replace(PREFIX_REGEXP, '') 10058 .toLowerCase() 10059 .substr(4 + ngPrefixMatch[1].length).replace(/_(.)/g, function(match, letter) { 10060 return letter.toUpperCase(); 10061 }); 10062 10063 // Support *-start / *-end multi element directives 10064 } else if ((multiElementMatch = nName.match(MULTI_ELEMENT_DIR_RE)) && directiveIsMultiElement(multiElementMatch[1])) { 10065 attrStartName = name; 10066 attrEndName = name.substr(0, name.length - 5) + 'end'; 10067 name = name.substr(0, name.length - 6); 10068 } 10069 10070 if (isNgProp || isNgEvent) { 10071 attrs[nName] = value; 10072 attrsMap[nName] = attr.name; 10073 10074 if (isNgProp) { 10075 addPropertyDirective(node, directives, nName, name); 10076 } else { 10077 addEventDirective(directives, nName, name); 10078 } 10079 } else { 10080 // Update nName for cases where a prefix was removed 10081 // NOTE: the .toLowerCase() is unnecessary and causes https://github.com/angular/angular.js/issues/16624 for ng-attr-* 10082 nName = directiveNormalize(name.toLowerCase()); 10083 attrsMap[nName] = name; 10084 10085 if (isNgAttr || !attrs.hasOwnProperty(nName)) { 10086 attrs[nName] = value; 10087 if (getBooleanAttrName(node, nName)) { 10088 attrs[nName] = true; // presence means true 10089 } 10090 } 10091 10092 addAttrInterpolateDirective(node, directives, value, nName, isNgAttr); 10093 addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, 10094 attrEndName); 10095 } 10096 } 10097 10098 if (nodeName === 'input' && node.getAttribute('type') === 'hidden') { 10099 // Hidden input elements can have strange behaviour when navigating back to the page 10100 // This tells the browser not to try to cache and reinstate previous values 10101 node.setAttribute('autocomplete', 'off'); 10102 } 10103 10104 // use class as directive 10105 if (!cssClassDirectivesEnabled) break; 10106 className = node.className; 10107 if (isObject(className)) { 10108 // Maybe SVGAnimatedString 10109 className = className.animVal; 10110 } 10111 if (isString(className) && className !== '') { 10112 while ((match = CLASS_DIRECTIVE_REGEXP.exec(className))) { 10113 nName = directiveNormalize(match[2]); 10114 if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) { 10115 attrs[nName] = trim(match[3]); 10116 } 10117 className = className.substr(match.index + match[0].length); 10118 } 10119 } 10120 break; 10121 case NODE_TYPE_TEXT: /* Text Node */ 10122 addTextInterpolateDirective(directives, node.nodeValue); 10123 break; 10124 case NODE_TYPE_COMMENT: /* Comment */ 10125 if (!commentDirectivesEnabled) break; 10126 collectCommentDirectives(node, directives, attrs, maxPriority, ignoreDirective); 10127 break; 10128 } 10129 10130 directives.sort(byPriority); 10131 return directives; 10132 } 10133 10134 function collectCommentDirectives(node, directives, attrs, maxPriority, ignoreDirective) { 10135 // function created because of performance, try/catch disables 10136 // the optimization of the whole function #14848 10137 try { 10138 var match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); 10139 if (match) { 10140 var nName = directiveNormalize(match[1]); 10141 if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) { 10142 attrs[nName] = trim(match[2]); 10143 } 10144 } 10145 } catch (e) { 10146 // turns out that under some circumstances IE9 throws errors when one attempts to read 10147 // comment's node value. 10148 // Just ignore it and continue. (Can't seem to reproduce in test case.) 10149 } 10150 } 10151 10152 /** 10153 * Given a node with a directive-start it collects all of the siblings until it finds 10154 * directive-end. 10155 * @param node 10156 * @param attrStart 10157 * @param attrEnd 10158 * @returns {*} 10159 */ 10160 function groupScan(node, attrStart, attrEnd) { 10161 var nodes = []; 10162 var depth = 0; 10163 if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { 10164 do { 10165 if (!node) { 10166 throw $compileMinErr('uterdir', 10167 'Unterminated attribute, found \'{0}\' but no matching \'{1}\' found.', 10168 attrStart, attrEnd); 10169 } 10170 if (node.nodeType === NODE_TYPE_ELEMENT) { 10171 if (node.hasAttribute(attrStart)) depth++; 10172 if (node.hasAttribute(attrEnd)) depth--; 10173 } 10174 nodes.push(node); 10175 node = node.nextSibling; 10176 } while (depth > 0); 10177 } else { 10178 nodes.push(node); 10179 } 10180 10181 return jqLite(nodes); 10182 } 10183 10184 /** 10185 * Wrapper for linking function which converts normal linking function into a grouped 10186 * linking function. 10187 * @param linkFn 10188 * @param attrStart 10189 * @param attrEnd 10190 * @returns {Function} 10191 */ 10192 function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) { 10193 return function groupedElementsLink(scope, element, attrs, controllers, transcludeFn) { 10194 element = groupScan(element[0], attrStart, attrEnd); 10195 return linkFn(scope, element, attrs, controllers, transcludeFn); 10196 }; 10197 } 10198 10199 /** 10200 * A function generator that is used to support both eager and lazy compilation 10201 * linking function. 10202 * @param eager 10203 * @param $compileNodes 10204 * @param transcludeFn 10205 * @param maxPriority 10206 * @param ignoreDirective 10207 * @param previousCompileContext 10208 * @returns {Function} 10209 */ 10210 function compilationGenerator(eager, $compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext) { 10211 var compiled; 10212 10213 if (eager) { 10214 return compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext); 10215 } 10216 return /** @this */ function lazyCompilation() { 10217 if (!compiled) { 10218 compiled = compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext); 10219 10220 // Null out all of these references in order to make them eligible for garbage collection 10221 // since this is a potentially long lived closure 10222 $compileNodes = transcludeFn = previousCompileContext = null; 10223 } 10224 return compiled.apply(this, arguments); 10225 }; 10226 } 10227 10228 /** 10229 * Once the directives have been collected, their compile functions are executed. This method 10230 * is responsible for inlining directive templates as well as terminating the application 10231 * of the directives if the terminal directive has been reached. 10232 * 10233 * @param {Array} directives Array of collected directives to execute their compile function. 10234 * this needs to be pre-sorted by priority order. 10235 * @param {Node} compileNode The raw DOM node to apply the compile functions to 10236 * @param {Object} templateAttrs The shared attribute function 10237 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 10238 * scope argument is auto-generated to the new 10239 * child of the transcluded parent scope. 10240 * @param {JQLite} jqCollection If we are working on the root of the compile tree then this 10241 * argument has the root jqLite array so that we can replace nodes 10242 * on it. 10243 * @param {Object=} originalReplaceDirective An optional directive that will be ignored when 10244 * compiling the transclusion. 10245 * @param {Array.<Function>} preLinkFns 10246 * @param {Array.<Function>} postLinkFns 10247 * @param {Object} previousCompileContext Context used for previous compilation of the current 10248 * node 10249 * @returns {Function} linkFn 10250 */ 10251 function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, 10252 jqCollection, originalReplaceDirective, preLinkFns, postLinkFns, 10253 previousCompileContext) { 10254 previousCompileContext = previousCompileContext || {}; 10255 10256 var terminalPriority = -Number.MAX_VALUE, 10257 newScopeDirective = previousCompileContext.newScopeDirective, 10258 controllerDirectives = previousCompileContext.controllerDirectives, 10259 newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective, 10260 templateDirective = previousCompileContext.templateDirective, 10261 nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective, 10262 hasTranscludeDirective = false, 10263 hasTemplate = false, 10264 hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective, 10265 $compileNode = templateAttrs.$$element = jqLite(compileNode), 10266 directive, 10267 directiveName, 10268 $template, 10269 replaceDirective = originalReplaceDirective, 10270 childTranscludeFn = transcludeFn, 10271 linkFn, 10272 didScanForMultipleTransclusion = false, 10273 mightHaveMultipleTransclusionError = false, 10274 directiveValue; 10275 10276 // executes all directives on the current element 10277 for (var i = 0, ii = directives.length; i < ii; i++) { 10278 directive = directives[i]; 10279 var attrStart = directive.$$start; 10280 var attrEnd = directive.$$end; 10281 10282 // collect multiblock sections 10283 if (attrStart) { 10284 $compileNode = groupScan(compileNode, attrStart, attrEnd); 10285 } 10286 $template = undefined; 10287 10288 if (terminalPriority > directive.priority) { 10289 break; // prevent further processing of directives 10290 } 10291 10292 directiveValue = directive.scope; 10293 10294 if (directiveValue) { 10295 10296 // skip the check for directives with async templates, we'll check the derived sync 10297 // directive when the template arrives 10298 if (!directive.templateUrl) { 10299 if (isObject(directiveValue)) { 10300 // This directive is trying to add an isolated scope. 10301 // Check that there is no scope of any kind already 10302 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective || newScopeDirective, 10303 directive, $compileNode); 10304 newIsolateScopeDirective = directive; 10305 } else { 10306 // This directive is trying to add a child scope. 10307 // Check that there is no isolated scope already 10308 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, 10309 $compileNode); 10310 } 10311 } 10312 10313 newScopeDirective = newScopeDirective || directive; 10314 } 10315 10316 directiveName = directive.name; 10317 10318 // If we encounter a condition that can result in transclusion on the directive, 10319 // then scan ahead in the remaining directives for others that may cause a multiple 10320 // transclusion error to be thrown during the compilation process. If a matching directive 10321 // is found, then we know that when we encounter a transcluded directive, we need to eagerly 10322 // compile the `transclude` function rather than doing it lazily in order to throw 10323 // exceptions at the correct time 10324 if (!didScanForMultipleTransclusion && ((directive.replace && (directive.templateUrl || directive.template)) 10325 || (directive.transclude && !directive.$$tlb))) { 10326 var candidateDirective; 10327 10328 for (var scanningIndex = i + 1; (candidateDirective = directives[scanningIndex++]);) { 10329 if ((candidateDirective.transclude && !candidateDirective.$$tlb) 10330 || (candidateDirective.replace && (candidateDirective.templateUrl || candidateDirective.template))) { 10331 mightHaveMultipleTransclusionError = true; 10332 break; 10333 } 10334 } 10335 10336 didScanForMultipleTransclusion = true; 10337 } 10338 10339 if (!directive.templateUrl && directive.controller) { 10340 controllerDirectives = controllerDirectives || createMap(); 10341 assertNoDuplicate('\'' + directiveName + '\' controller', 10342 controllerDirectives[directiveName], directive, $compileNode); 10343 controllerDirectives[directiveName] = directive; 10344 } 10345 10346 directiveValue = directive.transclude; 10347 10348 if (directiveValue) { 10349 hasTranscludeDirective = true; 10350 10351 // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion. 10352 // This option should only be used by directives that know how to safely handle element transclusion, 10353 // where the transcluded nodes are added or replaced after linking. 10354 if (!directive.$$tlb) { 10355 assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode); 10356 nonTlbTranscludeDirective = directive; 10357 } 10358 10359 if (directiveValue === 'element') { 10360 hasElementTranscludeDirective = true; 10361 terminalPriority = directive.priority; 10362 $template = $compileNode; 10363 $compileNode = templateAttrs.$$element = 10364 jqLite(compile.$$createComment(directiveName, templateAttrs[directiveName])); 10365 compileNode = $compileNode[0]; 10366 replaceWith(jqCollection, sliceArgs($template), compileNode); 10367 10368 childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, terminalPriority, 10369 replaceDirective && replaceDirective.name, { 10370 // Don't pass in: 10371 // - controllerDirectives - otherwise we'll create duplicates controllers 10372 // - newIsolateScopeDirective or templateDirective - combining templates with 10373 // element transclusion doesn't make sense. 10374 // 10375 // We need only nonTlbTranscludeDirective so that we prevent putting transclusion 10376 // on the same element more than once. 10377 nonTlbTranscludeDirective: nonTlbTranscludeDirective 10378 }); 10379 } else { 10380 10381 var slots = createMap(); 10382 10383 if (!isObject(directiveValue)) { 10384 $template = jqLite(jqLiteClone(compileNode)).contents(); 10385 } else { 10386 10387 // We have transclusion slots, 10388 // collect them up, compile them and store their transclusion functions 10389 $template = window.document.createDocumentFragment(); 10390 10391 var slotMap = createMap(); 10392 var filledSlots = createMap(); 10393 10394 // Parse the element selectors 10395 forEach(directiveValue, function(elementSelector, slotName) { 10396 // If an element selector starts with a ? then it is optional 10397 var optional = (elementSelector.charAt(0) === '?'); 10398 elementSelector = optional ? elementSelector.substring(1) : elementSelector; 10399 10400 slotMap[elementSelector] = slotName; 10401 10402 // We explicitly assign `null` since this implies that a slot was defined but not filled. 10403 // Later when calling boundTransclusion functions with a slot name we only error if the 10404 // slot is `undefined` 10405 slots[slotName] = null; 10406 10407 // filledSlots contains `true` for all slots that are either optional or have been 10408 // filled. This is used to check that we have not missed any required slots 10409 filledSlots[slotName] = optional; 10410 }); 10411 10412 // Add the matching elements into their slot 10413 forEach($compileNode.contents(), function(node) { 10414 var slotName = slotMap[directiveNormalize(nodeName_(node))]; 10415 if (slotName) { 10416 filledSlots[slotName] = true; 10417 slots[slotName] = slots[slotName] || window.document.createDocumentFragment(); 10418 slots[slotName].appendChild(node); 10419 } else { 10420 $template.appendChild(node); 10421 } 10422 }); 10423 10424 // Check for required slots that were not filled 10425 forEach(filledSlots, function(filled, slotName) { 10426 if (!filled) { 10427 throw $compileMinErr('reqslot', 'Required transclusion slot `{0}` was not filled.', slotName); 10428 } 10429 }); 10430 10431 for (var slotName in slots) { 10432 if (slots[slotName]) { 10433 // Only define a transclusion function if the slot was filled 10434 var slotCompileNodes = jqLite(slots[slotName].childNodes); 10435 slots[slotName] = compilationGenerator(mightHaveMultipleTransclusionError, slotCompileNodes, transcludeFn); 10436 } 10437 } 10438 10439 $template = jqLite($template.childNodes); 10440 } 10441 10442 $compileNode.empty(); // clear contents 10443 childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, undefined, 10444 undefined, { needsNewScope: directive.$$isolateScope || directive.$$newScope}); 10445 childTranscludeFn.$$slots = slots; 10446 } 10447 } 10448 10449 if (directive.template) { 10450 hasTemplate = true; 10451 assertNoDuplicate('template', templateDirective, directive, $compileNode); 10452 templateDirective = directive; 10453 10454 directiveValue = (isFunction(directive.template)) 10455 ? directive.template($compileNode, templateAttrs) 10456 : directive.template; 10457 10458 directiveValue = denormalizeTemplate(directiveValue); 10459 10460 if (directive.replace) { 10461 replaceDirective = directive; 10462 if (jqLiteIsTextNode(directiveValue)) { 10463 $template = []; 10464 } else { 10465 $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue))); 10466 } 10467 compileNode = $template[0]; 10468 10469 if ($template.length !== 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { 10470 throw $compileMinErr('tplrt', 10471 'Template for directive \'{0}\' must have exactly one root element. {1}', 10472 directiveName, ''); 10473 } 10474 10475 replaceWith(jqCollection, $compileNode, compileNode); 10476 10477 var newTemplateAttrs = {$attr: {}}; 10478 10479 // combine directives from the original node and from the template: 10480 // - take the array of directives for this element 10481 // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed) 10482 // - collect directives from the template and sort them by priority 10483 // - combine directives as: processed + template + unprocessed 10484 var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs); 10485 var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1)); 10486 10487 if (newIsolateScopeDirective || newScopeDirective) { 10488 // The original directive caused the current element to be replaced but this element 10489 // also needs to have a new scope, so we need to tell the template directives 10490 // that they would need to get their scope from further up, if they require transclusion 10491 markDirectiveScope(templateDirectives, newIsolateScopeDirective, newScopeDirective); 10492 } 10493 directives = directives.concat(templateDirectives).concat(unprocessedDirectives); 10494 mergeTemplateAttributes(templateAttrs, newTemplateAttrs); 10495 10496 ii = directives.length; 10497 } else { 10498 $compileNode.html(directiveValue); 10499 } 10500 } 10501 10502 if (directive.templateUrl) { 10503 hasTemplate = true; 10504 assertNoDuplicate('template', templateDirective, directive, $compileNode); 10505 templateDirective = directive; 10506 10507 if (directive.replace) { 10508 replaceDirective = directive; 10509 } 10510 10511 // eslint-disable-next-line no-func-assign 10512 nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode, 10513 templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, { 10514 controllerDirectives: controllerDirectives, 10515 newScopeDirective: (newScopeDirective !== directive) && newScopeDirective, 10516 newIsolateScopeDirective: newIsolateScopeDirective, 10517 templateDirective: templateDirective, 10518 nonTlbTranscludeDirective: nonTlbTranscludeDirective 10519 }); 10520 ii = directives.length; 10521 } else if (directive.compile) { 10522 try { 10523 linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); 10524 var context = directive.$$originalDirective || directive; 10525 if (isFunction(linkFn)) { 10526 addLinkFns(null, bind(context, linkFn), attrStart, attrEnd); 10527 } else if (linkFn) { 10528 addLinkFns(bind(context, linkFn.pre), bind(context, linkFn.post), attrStart, attrEnd); 10529 } 10530 } catch (e) { 10531 $exceptionHandler(e, startingTag($compileNode)); 10532 } 10533 } 10534 10535 if (directive.terminal) { 10536 nodeLinkFn.terminal = true; 10537 terminalPriority = Math.max(terminalPriority, directive.priority); 10538 } 10539 10540 } 10541 10542 nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; 10543 nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective; 10544 nodeLinkFn.templateOnThisElement = hasTemplate; 10545 nodeLinkFn.transclude = childTranscludeFn; 10546 10547 previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective; 10548 10549 // might be normal or delayed nodeLinkFn depending on if templateUrl is present 10550 return nodeLinkFn; 10551 10552 //////////////////// 10553 10554 function addLinkFns(pre, post, attrStart, attrEnd) { 10555 if (pre) { 10556 if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd); 10557 pre.require = directive.require; 10558 pre.directiveName = directiveName; 10559 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 10560 pre = cloneAndAnnotateFn(pre, {isolateScope: true}); 10561 } 10562 preLinkFns.push(pre); 10563 } 10564 if (post) { 10565 if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd); 10566 post.require = directive.require; 10567 post.directiveName = directiveName; 10568 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 10569 post = cloneAndAnnotateFn(post, {isolateScope: true}); 10570 } 10571 postLinkFns.push(post); 10572 } 10573 } 10574 10575 function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) { 10576 var i, ii, linkFn, isolateScope, controllerScope, elementControllers, transcludeFn, $element, 10577 attrs, scopeBindingInfo; 10578 10579 if (compileNode === linkNode) { 10580 attrs = templateAttrs; 10581 $element = templateAttrs.$$element; 10582 } else { 10583 $element = jqLite(linkNode); 10584 attrs = new Attributes($element, templateAttrs); 10585 } 10586 10587 controllerScope = scope; 10588 if (newIsolateScopeDirective) { 10589 isolateScope = scope.$new(true); 10590 } else if (newScopeDirective) { 10591 controllerScope = scope.$parent; 10592 } 10593 10594 if (boundTranscludeFn) { 10595 // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn` 10596 // is later passed as `parentBoundTranscludeFn` to `publicLinkFn` 10597 transcludeFn = controllersBoundTransclude; 10598 transcludeFn.$$boundTransclude = boundTranscludeFn; 10599 // expose the slots on the `$transclude` function 10600 transcludeFn.isSlotFilled = function(slotName) { 10601 return !!boundTranscludeFn.$$slots[slotName]; 10602 }; 10603 } 10604 10605 if (controllerDirectives) { 10606 elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope, newIsolateScopeDirective); 10607 } 10608 10609 if (newIsolateScopeDirective) { 10610 // Initialize isolate scope bindings for new isolate scope directive. 10611 compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective || 10612 templateDirective === newIsolateScopeDirective.$$originalDirective))); 10613 compile.$$addScopeClass($element, true); 10614 isolateScope.$$isolateBindings = 10615 newIsolateScopeDirective.$$isolateBindings; 10616 scopeBindingInfo = initializeDirectiveBindings(scope, attrs, isolateScope, 10617 isolateScope.$$isolateBindings, 10618 newIsolateScopeDirective); 10619 if (scopeBindingInfo.removeWatches) { 10620 isolateScope.$on('$destroy', scopeBindingInfo.removeWatches); 10621 } 10622 } 10623 10624 // Initialize bindToController bindings 10625 for (var name in elementControllers) { 10626 var controllerDirective = controllerDirectives[name]; 10627 var controller = elementControllers[name]; 10628 var bindings = controllerDirective.$$bindings.bindToController; 10629 10630 controller.instance = controller(); 10631 $element.data('$' + controllerDirective.name + 'Controller', controller.instance); 10632 controller.bindingInfo = 10633 initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective); 10634 } 10635 10636 // Bind the required controllers to the controller, if `require` is an object and `bindToController` is truthy 10637 forEach(controllerDirectives, function(controllerDirective, name) { 10638 var require = controllerDirective.require; 10639 if (controllerDirective.bindToController && !isArray(require) && isObject(require)) { 10640 extend(elementControllers[name].instance, getControllers(name, require, $element, elementControllers)); 10641 } 10642 }); 10643 10644 // Handle the init and destroy lifecycle hooks on all controllers that have them 10645 forEach(elementControllers, function(controller) { 10646 var controllerInstance = controller.instance; 10647 if (isFunction(controllerInstance.$onChanges)) { 10648 try { 10649 controllerInstance.$onChanges(controller.bindingInfo.initialChanges); 10650 } catch (e) { 10651 $exceptionHandler(e); 10652 } 10653 } 10654 if (isFunction(controllerInstance.$onInit)) { 10655 try { 10656 controllerInstance.$onInit(); 10657 } catch (e) { 10658 $exceptionHandler(e); 10659 } 10660 } 10661 if (isFunction(controllerInstance.$doCheck)) { 10662 controllerScope.$watch(function() { controllerInstance.$doCheck(); }); 10663 controllerInstance.$doCheck(); 10664 } 10665 if (isFunction(controllerInstance.$onDestroy)) { 10666 controllerScope.$on('$destroy', function callOnDestroyHook() { 10667 controllerInstance.$onDestroy(); 10668 }); 10669 } 10670 }); 10671 10672 // PRELINKING 10673 for (i = 0, ii = preLinkFns.length; i < ii; i++) { 10674 linkFn = preLinkFns[i]; 10675 invokeLinkFn(linkFn, 10676 linkFn.isolateScope ? isolateScope : scope, 10677 $element, 10678 attrs, 10679 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), 10680 transcludeFn 10681 ); 10682 } 10683 10684 // RECURSION 10685 // We only pass the isolate scope, if the isolate directive has a template, 10686 // otherwise the child elements do not belong to the isolate directive. 10687 var scopeToChild = scope; 10688 if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) { 10689 scopeToChild = isolateScope; 10690 } 10691 if (childLinkFn) { 10692 childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn); 10693 } 10694 10695 // POSTLINKING 10696 for (i = postLinkFns.length - 1; i >= 0; i--) { 10697 linkFn = postLinkFns[i]; 10698 invokeLinkFn(linkFn, 10699 linkFn.isolateScope ? isolateScope : scope, 10700 $element, 10701 attrs, 10702 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), 10703 transcludeFn 10704 ); 10705 } 10706 10707 // Trigger $postLink lifecycle hooks 10708 forEach(elementControllers, function(controller) { 10709 var controllerInstance = controller.instance; 10710 if (isFunction(controllerInstance.$postLink)) { 10711 controllerInstance.$postLink(); 10712 } 10713 }); 10714 10715 // This is the function that is injected as `$transclude`. 10716 // Note: all arguments are optional! 10717 function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement, slotName) { 10718 var transcludeControllers; 10719 // No scope passed in: 10720 if (!isScope(scope)) { 10721 slotName = futureParentElement; 10722 futureParentElement = cloneAttachFn; 10723 cloneAttachFn = scope; 10724 scope = undefined; 10725 } 10726 10727 if (hasElementTranscludeDirective) { 10728 transcludeControllers = elementControllers; 10729 } 10730 if (!futureParentElement) { 10731 futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element; 10732 } 10733 if (slotName) { 10734 // slotTranscludeFn can be one of three things: 10735 // * a transclude function - a filled slot 10736 // * `null` - an optional slot that was not filled 10737 // * `undefined` - a slot that was not declared (i.e. invalid) 10738 var slotTranscludeFn = boundTranscludeFn.$$slots[slotName]; 10739 if (slotTranscludeFn) { 10740 return slotTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild); 10741 } else if (isUndefined(slotTranscludeFn)) { 10742 throw $compileMinErr('noslot', 10743 'No parent directive that requires a transclusion with slot name "{0}". ' + 10744 'Element: {1}', 10745 slotName, startingTag($element)); 10746 } 10747 } else { 10748 return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild); 10749 } 10750 } 10751 } 10752 } 10753 10754 function getControllers(directiveName, require, $element, elementControllers) { 10755 var value; 10756 10757 if (isString(require)) { 10758 var match = require.match(REQUIRE_PREFIX_REGEXP); 10759 var name = require.substring(match[0].length); 10760 var inheritType = match[1] || match[3]; 10761 var optional = match[2] === '?'; 10762 10763 //If only parents then start at the parent element 10764 if (inheritType === '^^') { 10765 $element = $element.parent(); 10766 //Otherwise attempt getting the controller from elementControllers in case 10767 //the element is transcluded (and has no data) and to avoid .data if possible 10768 } else { 10769 value = elementControllers && elementControllers[name]; 10770 value = value && value.instance; 10771 } 10772 10773 if (!value) { 10774 var dataName = '$' + name + 'Controller'; 10775 10776 if (inheritType === '^^' && $element[0] && $element[0].nodeType === NODE_TYPE_DOCUMENT) { 10777 // inheritedData() uses the documentElement when it finds the document, so we would 10778 // require from the element itself. 10779 value = null; 10780 } else { 10781 value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName); 10782 } 10783 } 10784 10785 if (!value && !optional) { 10786 throw $compileMinErr('ctreq', 10787 'Controller \'{0}\', required by directive \'{1}\', can\'t be found!', 10788 name, directiveName); 10789 } 10790 } else if (isArray(require)) { 10791 value = []; 10792 for (var i = 0, ii = require.length; i < ii; i++) { 10793 value[i] = getControllers(directiveName, require[i], $element, elementControllers); 10794 } 10795 } else if (isObject(require)) { 10796 value = {}; 10797 forEach(require, function(controller, property) { 10798 value[property] = getControllers(directiveName, controller, $element, elementControllers); 10799 }); 10800 } 10801 10802 return value || null; 10803 } 10804 10805 function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope, newIsolateScopeDirective) { 10806 var elementControllers = createMap(); 10807 for (var controllerKey in controllerDirectives) { 10808 var directive = controllerDirectives[controllerKey]; 10809 var locals = { 10810 $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope, 10811 $element: $element, 10812 $attrs: attrs, 10813 $transclude: transcludeFn 10814 }; 10815 10816 var controller = directive.controller; 10817 if (controller === '@') { 10818 controller = attrs[directive.name]; 10819 } 10820 10821 var controllerInstance = $controller(controller, locals, true, directive.controllerAs); 10822 10823 // For directives with element transclusion the element is a comment. 10824 // In this case .data will not attach any data. 10825 // Instead, we save the controllers for the element in a local hash and attach to .data 10826 // later, once we have the actual element. 10827 elementControllers[directive.name] = controllerInstance; 10828 $element.data('$' + directive.name + 'Controller', controllerInstance.instance); 10829 } 10830 return elementControllers; 10831 } 10832 10833 // Depending upon the context in which a directive finds itself it might need to have a new isolated 10834 // or child scope created. For instance: 10835 // * if the directive has been pulled into a template because another directive with a higher priority 10836 // asked for element transclusion 10837 // * if the directive itself asks for transclusion but it is at the root of a template and the original 10838 // element was replaced. See https://github.com/angular/angular.js/issues/12936 10839 function markDirectiveScope(directives, isolateScope, newScope) { 10840 for (var j = 0, jj = directives.length; j < jj; j++) { 10841 directives[j] = inherit(directives[j], {$$isolateScope: isolateScope, $$newScope: newScope}); 10842 } 10843 } 10844 10845 /** 10846 * looks up the directive and decorates it with exception handling and proper parameters. We 10847 * call this the boundDirective. 10848 * 10849 * @param {string} name name of the directive to look up. 10850 * @param {string} location The directive must be found in specific format. 10851 * String containing any of theses characters: 10852 * 10853 * * `E`: element name 10854 * * `A': attribute 10855 * * `C`: class 10856 * * `M`: comment 10857 * @returns {boolean} true if directive was added. 10858 */ 10859 function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, 10860 endAttrName) { 10861 if (name === ignoreDirective) return null; 10862 var match = null; 10863 if (hasDirectives.hasOwnProperty(name)) { 10864 for (var directive, directives = $injector.get(name + Suffix), 10865 i = 0, ii = directives.length; i < ii; i++) { 10866 directive = directives[i]; 10867 if ((isUndefined(maxPriority) || maxPriority > directive.priority) && 10868 directive.restrict.indexOf(location) !== -1) { 10869 if (startAttrName) { 10870 directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); 10871 } 10872 if (!directive.$$bindings) { 10873 var bindings = directive.$$bindings = 10874 parseDirectiveBindings(directive, directive.name); 10875 if (isObject(bindings.isolateScope)) { 10876 directive.$$isolateBindings = bindings.isolateScope; 10877 } 10878 } 10879 tDirectives.push(directive); 10880 match = directive; 10881 } 10882 } 10883 } 10884 return match; 10885 } 10886 10887 10888 /** 10889 * looks up the directive and returns true if it is a multi-element directive, 10890 * and therefore requires DOM nodes between -start and -end markers to be grouped 10891 * together. 10892 * 10893 * @param {string} name name of the directive to look up. 10894 * @returns true if directive was registered as multi-element. 10895 */ 10896 function directiveIsMultiElement(name) { 10897 if (hasDirectives.hasOwnProperty(name)) { 10898 for (var directive, directives = $injector.get(name + Suffix), 10899 i = 0, ii = directives.length; i < ii; i++) { 10900 directive = directives[i]; 10901 if (directive.multiElement) { 10902 return true; 10903 } 10904 } 10905 } 10906 return false; 10907 } 10908 10909 /** 10910 * When the element is replaced with HTML template then the new attributes 10911 * on the template need to be merged with the existing attributes in the DOM. 10912 * The desired effect is to have both of the attributes present. 10913 * 10914 * @param {object} dst destination attributes (original DOM) 10915 * @param {object} src source attributes (from the directive template) 10916 */ 10917 function mergeTemplateAttributes(dst, src) { 10918 var srcAttr = src.$attr, 10919 dstAttr = dst.$attr; 10920 10921 // reapply the old attributes to the new element 10922 forEach(dst, function(value, key) { 10923 if (key.charAt(0) !== '$') { 10924 if (src[key] && src[key] !== value) { 10925 if (value.length) { 10926 value += (key === 'style' ? ';' : ' ') + src[key]; 10927 } else { 10928 value = src[key]; 10929 } 10930 } 10931 dst.$set(key, value, true, srcAttr[key]); 10932 } 10933 }); 10934 10935 // copy the new attributes on the old attrs object 10936 forEach(src, function(value, key) { 10937 // Check if we already set this attribute in the loop above. 10938 // `dst` will never contain hasOwnProperty as DOM parser won't let it. 10939 // You will get an "InvalidCharacterError: DOM Exception 5" error if you 10940 // have an attribute like "has-own-property" or "data-has-own-property", etc. 10941 if (!dst.hasOwnProperty(key) && key.charAt(0) !== '$') { 10942 dst[key] = value; 10943 10944 if (key !== 'class' && key !== 'style') { 10945 dstAttr[key] = srcAttr[key]; 10946 } 10947 } 10948 }); 10949 } 10950 10951 10952 function compileTemplateUrl(directives, $compileNode, tAttrs, 10953 $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) { 10954 var linkQueue = [], 10955 afterTemplateNodeLinkFn, 10956 afterTemplateChildLinkFn, 10957 beforeTemplateCompileNode = $compileNode[0], 10958 origAsyncDirective = directives.shift(), 10959 derivedSyncDirective = inherit(origAsyncDirective, { 10960 templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective 10961 }), 10962 templateUrl = (isFunction(origAsyncDirective.templateUrl)) 10963 ? origAsyncDirective.templateUrl($compileNode, tAttrs) 10964 : origAsyncDirective.templateUrl, 10965 templateNamespace = origAsyncDirective.templateNamespace; 10966 10967 $compileNode.empty(); 10968 10969 $templateRequest(templateUrl) 10970 .then(function(content) { 10971 var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn; 10972 10973 content = denormalizeTemplate(content); 10974 10975 if (origAsyncDirective.replace) { 10976 if (jqLiteIsTextNode(content)) { 10977 $template = []; 10978 } else { 10979 $template = removeComments(wrapTemplate(templateNamespace, trim(content))); 10980 } 10981 compileNode = $template[0]; 10982 10983 if ($template.length !== 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { 10984 throw $compileMinErr('tplrt', 10985 'Template for directive \'{0}\' must have exactly one root element. {1}', 10986 origAsyncDirective.name, templateUrl); 10987 } 10988 10989 tempTemplateAttrs = {$attr: {}}; 10990 replaceWith($rootElement, $compileNode, compileNode); 10991 var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs); 10992 10993 if (isObject(origAsyncDirective.scope)) { 10994 // the original directive that caused the template to be loaded async required 10995 // an isolate scope 10996 markDirectiveScope(templateDirectives, true); 10997 } 10998 directives = templateDirectives.concat(directives); 10999 mergeTemplateAttributes(tAttrs, tempTemplateAttrs); 11000 } else { 11001 compileNode = beforeTemplateCompileNode; 11002 $compileNode.html(content); 11003 } 11004 11005 directives.unshift(derivedSyncDirective); 11006 11007 afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, 11008 childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns, 11009 previousCompileContext); 11010 forEach($rootElement, function(node, i) { 11011 if (node === compileNode) { 11012 $rootElement[i] = $compileNode[0]; 11013 } 11014 }); 11015 afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); 11016 11017 while (linkQueue.length) { 11018 var scope = linkQueue.shift(), 11019 beforeTemplateLinkNode = linkQueue.shift(), 11020 linkRootElement = linkQueue.shift(), 11021 boundTranscludeFn = linkQueue.shift(), 11022 linkNode = $compileNode[0]; 11023 11024 if (scope.$$destroyed) continue; 11025 11026 if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { 11027 var oldClasses = beforeTemplateLinkNode.className; 11028 11029 if (!(previousCompileContext.hasElementTranscludeDirective && 11030 origAsyncDirective.replace)) { 11031 // it was cloned therefore we have to clone as well. 11032 linkNode = jqLiteClone(compileNode); 11033 } 11034 replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); 11035 11036 // Copy in CSS classes from original node 11037 safeAddClass(jqLite(linkNode), oldClasses); 11038 } 11039 if (afterTemplateNodeLinkFn.transcludeOnThisElement) { 11040 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); 11041 } else { 11042 childBoundTranscludeFn = boundTranscludeFn; 11043 } 11044 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, 11045 childBoundTranscludeFn); 11046 } 11047 linkQueue = null; 11048 }).catch(function(error) { 11049 if (isError(error)) { 11050 $exceptionHandler(error); 11051 } 11052 }); 11053 11054 return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) { 11055 var childBoundTranscludeFn = boundTranscludeFn; 11056 if (scope.$$destroyed) return; 11057 if (linkQueue) { 11058 linkQueue.push(scope, 11059 node, 11060 rootElement, 11061 childBoundTranscludeFn); 11062 } else { 11063 if (afterTemplateNodeLinkFn.transcludeOnThisElement) { 11064 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); 11065 } 11066 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn); 11067 } 11068 }; 11069 } 11070 11071 11072 /** 11073 * Sorting function for bound directives. 11074 */ 11075 function byPriority(a, b) { 11076 var diff = b.priority - a.priority; 11077 if (diff !== 0) return diff; 11078 if (a.name !== b.name) return (a.name < b.name) ? -1 : 1; 11079 return a.index - b.index; 11080 } 11081 11082 function assertNoDuplicate(what, previousDirective, directive, element) { 11083 11084 function wrapModuleNameIfDefined(moduleName) { 11085 return moduleName ? 11086 (' (module: ' + moduleName + ')') : 11087 ''; 11088 } 11089 11090 if (previousDirective) { 11091 throw $compileMinErr('multidir', 'Multiple directives [{0}{1}, {2}{3}] asking for {4} on: {5}', 11092 previousDirective.name, wrapModuleNameIfDefined(previousDirective.$$moduleName), 11093 directive.name, wrapModuleNameIfDefined(directive.$$moduleName), what, startingTag(element)); 11094 } 11095 } 11096 11097 11098 function addTextInterpolateDirective(directives, text) { 11099 var interpolateFn = $interpolate(text, true); 11100 if (interpolateFn) { 11101 directives.push({ 11102 priority: 0, 11103 compile: function textInterpolateCompileFn(templateNode) { 11104 var templateNodeParent = templateNode.parent(), 11105 hasCompileParent = !!templateNodeParent.length; 11106 11107 // When transcluding a template that has bindings in the root 11108 // we don't have a parent and thus need to add the class during linking fn. 11109 if (hasCompileParent) compile.$$addBindingClass(templateNodeParent); 11110 11111 return function textInterpolateLinkFn(scope, node) { 11112 var parent = node.parent(); 11113 if (!hasCompileParent) compile.$$addBindingClass(parent); 11114 compile.$$addBindingInfo(parent, interpolateFn.expressions); 11115 scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { 11116 node[0].nodeValue = value; 11117 }); 11118 }; 11119 } 11120 }); 11121 } 11122 } 11123 11124 11125 function wrapTemplate(type, template) { 11126 type = lowercase(type || 'html'); 11127 switch (type) { 11128 case 'svg': 11129 case 'math': 11130 var wrapper = window.document.createElement('div'); 11131 wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>'; 11132 return wrapper.childNodes[0].childNodes; 11133 default: 11134 return template; 11135 } 11136 } 11137 11138 11139 function getTrustedAttrContext(nodeName, attrNormalizedName) { 11140 if (attrNormalizedName === 'srcdoc') { 11141 return $sce.HTML; 11142 } 11143 // All nodes with src attributes require a RESOURCE_URL value, except for 11144 // img and various html5 media nodes, which require the MEDIA_URL context. 11145 if (attrNormalizedName === 'src' || attrNormalizedName === 'ngSrc') { 11146 if (['img', 'video', 'audio', 'source', 'track'].indexOf(nodeName) === -1) { 11147 return $sce.RESOURCE_URL; 11148 } 11149 return $sce.MEDIA_URL; 11150 } else if (attrNormalizedName === 'xlinkHref') { 11151 // Some xlink:href are okay, most aren't 11152 if (nodeName === 'image') return $sce.MEDIA_URL; 11153 if (nodeName === 'a') return $sce.URL; 11154 return $sce.RESOURCE_URL; 11155 } else if ( 11156 // Formaction 11157 (nodeName === 'form' && attrNormalizedName === 'action') || 11158 // If relative URLs can go where they are not expected to, then 11159 // all sorts of trust issues can arise. 11160 (nodeName === 'base' && attrNormalizedName === 'href') || 11161 // links can be stylesheets or imports, which can run script in the current origin 11162 (nodeName === 'link' && attrNormalizedName === 'href') 11163 ) { 11164 return $sce.RESOURCE_URL; 11165 } else if (nodeName === 'a' && (attrNormalizedName === 'href' || 11166 attrNormalizedName === 'ngHref')) { 11167 return $sce.URL; 11168 } 11169 } 11170 11171 function getTrustedPropContext(nodeName, propNormalizedName) { 11172 var prop = propNormalizedName.toLowerCase(); 11173 return PROP_CONTEXTS[nodeName + '|' + prop] || PROP_CONTEXTS['*|' + prop]; 11174 } 11175 11176 function sanitizeSrcsetPropertyValue(value) { 11177 return sanitizeSrcset($sce.valueOf(value), 'ng-prop-srcset'); 11178 } 11179 function addPropertyDirective(node, directives, attrName, propName) { 11180 if (EVENT_HANDLER_ATTR_REGEXP.test(propName)) { 11181 throw $compileMinErr('nodomevents', 'Property bindings for HTML DOM event properties are disallowed'); 11182 } 11183 11184 var nodeName = nodeName_(node); 11185 var trustedContext = getTrustedPropContext(nodeName, propName); 11186 11187 var sanitizer = identity; 11188 // Sanitize img[srcset] + source[srcset] values. 11189 if (propName === 'srcset' && (nodeName === 'img' || nodeName === 'source')) { 11190 sanitizer = sanitizeSrcsetPropertyValue; 11191 } else if (trustedContext) { 11192 sanitizer = $sce.getTrusted.bind($sce, trustedContext); 11193 } 11194 11195 directives.push({ 11196 priority: 100, 11197 compile: function ngPropCompileFn(_, attr) { 11198 var ngPropGetter = $parse(attr[attrName]); 11199 var ngPropWatch = $parse(attr[attrName], function sceValueOf(val) { 11200 // Unwrap the value to compare the actual inner safe value, not the wrapper object. 11201 return $sce.valueOf(val); 11202 }); 11203 11204 return { 11205 pre: function ngPropPreLinkFn(scope, $element) { 11206 function applyPropValue() { 11207 var propValue = ngPropGetter(scope); 11208 $element[0][propName] = sanitizer(propValue); 11209 } 11210 11211 applyPropValue(); 11212 scope.$watch(ngPropWatch, applyPropValue); 11213 } 11214 }; 11215 } 11216 }); 11217 } 11218 11219 function addEventDirective(directives, attrName, eventName) { 11220 directives.push( 11221 createEventDirective($parse, $rootScope, $exceptionHandler, attrName, eventName, /*forceAsync=*/false) 11222 ); 11223 } 11224 11225 function addAttrInterpolateDirective(node, directives, value, name, isNgAttr) { 11226 var nodeName = nodeName_(node); 11227 var trustedContext = getTrustedAttrContext(nodeName, name); 11228 var mustHaveExpression = !isNgAttr; 11229 var allOrNothing = ALL_OR_NOTHING_ATTRS[name] || isNgAttr; 11230 11231 var interpolateFn = $interpolate(value, mustHaveExpression, trustedContext, allOrNothing); 11232 11233 // no interpolation found -> ignore 11234 if (!interpolateFn) return; 11235 11236 if (name === 'multiple' && nodeName === 'select') { 11237 throw $compileMinErr('selmulti', 11238 'Binding to the \'multiple\' attribute is not supported. Element: {0}', 11239 startingTag(node)); 11240 } 11241 11242 if (EVENT_HANDLER_ATTR_REGEXP.test(name)) { 11243 throw $compileMinErr('nodomevents', 'Interpolations for HTML DOM event attributes are disallowed'); 11244 } 11245 11246 directives.push({ 11247 priority: 100, 11248 compile: function() { 11249 return { 11250 pre: function attrInterpolatePreLinkFn(scope, element, attr) { 11251 var $$observers = (attr.$$observers || (attr.$$observers = createMap())); 11252 11253 // If the attribute has changed since last $interpolate()ed 11254 var newValue = attr[name]; 11255 if (newValue !== value) { 11256 // we need to interpolate again since the attribute value has been updated 11257 // (e.g. by another directive's compile function) 11258 // ensure unset/empty values make interpolateFn falsy 11259 interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing); 11260 value = newValue; 11261 } 11262 11263 // if attribute was updated so that there is no interpolation going on we don't want to 11264 // register any observers 11265 if (!interpolateFn) return; 11266 11267 // initialize attr object so that it's ready in case we need the value for isolate 11268 // scope initialization, otherwise the value would not be available from isolate 11269 // directive's linking fn during linking phase 11270 attr[name] = interpolateFn(scope); 11271 11272 ($$observers[name] || ($$observers[name] = [])).$$inter = true; 11273 (attr.$$observers && attr.$$observers[name].$$scope || scope). 11274 $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) { 11275 //special case for class attribute addition + removal 11276 //so that class changes can tap into the animation 11277 //hooks provided by the $animate service. Be sure to 11278 //skip animations when the first digest occurs (when 11279 //both the new and the old values are the same) since 11280 //the CSS classes are the non-interpolated values 11281 if (name === 'class' && newValue !== oldValue) { 11282 attr.$updateClass(newValue, oldValue); 11283 } else { 11284 attr.$set(name, newValue); 11285 } 11286 }); 11287 } 11288 }; 11289 } 11290 }); 11291 } 11292 11293 11294 /** 11295 * This is a special jqLite.replaceWith, which can replace items which 11296 * have no parents, provided that the containing jqLite collection is provided. 11297 * 11298 * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes 11299 * in the root of the tree. 11300 * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep 11301 * the shell, but replace its DOM node reference. 11302 * @param {Node} newNode The new DOM node. 11303 */ 11304 function replaceWith($rootElement, elementsToRemove, newNode) { 11305 var firstElementToRemove = elementsToRemove[0], 11306 removeCount = elementsToRemove.length, 11307 parent = firstElementToRemove.parentNode, 11308 i, ii; 11309 11310 if ($rootElement) { 11311 for (i = 0, ii = $rootElement.length; i < ii; i++) { 11312 if ($rootElement[i] === firstElementToRemove) { 11313 $rootElement[i++] = newNode; 11314 for (var j = i, j2 = j + removeCount - 1, 11315 jj = $rootElement.length; 11316 j < jj; j++, j2++) { 11317 if (j2 < jj) { 11318 $rootElement[j] = $rootElement[j2]; 11319 } else { 11320 delete $rootElement[j]; 11321 } 11322 } 11323 $rootElement.length -= removeCount - 1; 11324 11325 // If the replaced element is also the jQuery .context then replace it 11326 // .context is a deprecated jQuery api, so we should set it only when jQuery set it 11327 // http://api.jquery.com/context/ 11328 if ($rootElement.context === firstElementToRemove) { 11329 $rootElement.context = newNode; 11330 } 11331 break; 11332 } 11333 } 11334 } 11335 11336 if (parent) { 11337 parent.replaceChild(newNode, firstElementToRemove); 11338 } 11339 11340 // Append all the `elementsToRemove` to a fragment. This will... 11341 // - remove them from the DOM 11342 // - allow them to still be traversed with .nextSibling 11343 // - allow a single fragment.qSA to fetch all elements being removed 11344 var fragment = window.document.createDocumentFragment(); 11345 for (i = 0; i < removeCount; i++) { 11346 fragment.appendChild(elementsToRemove[i]); 11347 } 11348 11349 if (jqLite.hasData(firstElementToRemove)) { 11350 // Copy over user data (that includes AngularJS's $scope etc.). Don't copy private 11351 // data here because there's no public interface in jQuery to do that and copying over 11352 // event listeners (which is the main use of private data) wouldn't work anyway. 11353 jqLite.data(newNode, jqLite.data(firstElementToRemove)); 11354 11355 // Remove $destroy event listeners from `firstElementToRemove` 11356 jqLite(firstElementToRemove).off('$destroy'); 11357 } 11358 11359 // Cleanup any data/listeners on the elements and children. 11360 // This includes invoking the $destroy event on any elements with listeners. 11361 jqLite.cleanData(fragment.querySelectorAll('*')); 11362 11363 // Update the jqLite collection to only contain the `newNode` 11364 for (i = 1; i < removeCount; i++) { 11365 delete elementsToRemove[i]; 11366 } 11367 elementsToRemove[0] = newNode; 11368 elementsToRemove.length = 1; 11369 } 11370 11371 11372 function cloneAndAnnotateFn(fn, annotation) { 11373 return extend(function() { return fn.apply(null, arguments); }, fn, annotation); 11374 } 11375 11376 11377 function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) { 11378 try { 11379 linkFn(scope, $element, attrs, controllers, transcludeFn); 11380 } catch (e) { 11381 $exceptionHandler(e, startingTag($element)); 11382 } 11383 } 11384 11385 function strictBindingsCheck(attrName, directiveName) { 11386 if (strictComponentBindingsEnabled) { 11387 throw $compileMinErr('missingattr', 11388 'Attribute \'{0}\' of \'{1}\' is non-optional and must be set!', 11389 attrName, directiveName); 11390 } 11391 } 11392 11393 // Set up $watches for isolate scope and controller bindings. 11394 function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) { 11395 var removeWatchCollection = []; 11396 var initialChanges = {}; 11397 var changes; 11398 11399 forEach(bindings, function initializeBinding(definition, scopeName) { 11400 var attrName = definition.attrName, 11401 optional = definition.optional, 11402 mode = definition.mode, // @, =, <, or & 11403 lastValue, 11404 parentGet, parentSet, compare, removeWatch; 11405 11406 switch (mode) { 11407 11408 case '@': 11409 if (!optional && !hasOwnProperty.call(attrs, attrName)) { 11410 strictBindingsCheck(attrName, directive.name); 11411 destination[scopeName] = attrs[attrName] = undefined; 11412 11413 } 11414 removeWatch = attrs.$observe(attrName, function(value) { 11415 if (isString(value) || isBoolean(value)) { 11416 var oldValue = destination[scopeName]; 11417 recordChanges(scopeName, value, oldValue); 11418 destination[scopeName] = value; 11419 } 11420 }); 11421 attrs.$$observers[attrName].$$scope = scope; 11422 lastValue = attrs[attrName]; 11423 if (isString(lastValue)) { 11424 // If the attribute has been provided then we trigger an interpolation to ensure 11425 // the value is there for use in the link fn 11426 destination[scopeName] = $interpolate(lastValue)(scope); 11427 } else if (isBoolean(lastValue)) { 11428 // If the attributes is one of the BOOLEAN_ATTR then AngularJS will have converted 11429 // the value to boolean rather than a string, so we special case this situation 11430 destination[scopeName] = lastValue; 11431 } 11432 initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]); 11433 removeWatchCollection.push(removeWatch); 11434 break; 11435 11436 case '=': 11437 if (!hasOwnProperty.call(attrs, attrName)) { 11438 if (optional) break; 11439 strictBindingsCheck(attrName, directive.name); 11440 attrs[attrName] = undefined; 11441 } 11442 if (optional && !attrs[attrName]) break; 11443 11444 parentGet = $parse(attrs[attrName]); 11445 if (parentGet.literal) { 11446 compare = equals; 11447 } else { 11448 compare = simpleCompare; 11449 } 11450 parentSet = parentGet.assign || function() { 11451 // reset the change, or we will throw this exception on every $digest 11452 lastValue = destination[scopeName] = parentGet(scope); 11453 throw $compileMinErr('nonassign', 11454 'Expression \'{0}\' in attribute \'{1}\' used with directive \'{2}\' is non-assignable!', 11455 attrs[attrName], attrName, directive.name); 11456 }; 11457 lastValue = destination[scopeName] = parentGet(scope); 11458 var parentValueWatch = function parentValueWatch(parentValue) { 11459 if (!compare(parentValue, destination[scopeName])) { 11460 // we are out of sync and need to copy 11461 if (!compare(parentValue, lastValue)) { 11462 // parent changed and it has precedence 11463 destination[scopeName] = parentValue; 11464 } else { 11465 // if the parent can be assigned then do so 11466 parentSet(scope, parentValue = destination[scopeName]); 11467 } 11468 } 11469 lastValue = parentValue; 11470 return lastValue; 11471 }; 11472 parentValueWatch.$stateful = true; 11473 if (definition.collection) { 11474 removeWatch = scope.$watchCollection(attrs[attrName], parentValueWatch); 11475 } else { 11476 removeWatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal); 11477 } 11478 removeWatchCollection.push(removeWatch); 11479 break; 11480 11481 case '<': 11482 if (!hasOwnProperty.call(attrs, attrName)) { 11483 if (optional) break; 11484 strictBindingsCheck(attrName, directive.name); 11485 attrs[attrName] = undefined; 11486 } 11487 if (optional && !attrs[attrName]) break; 11488 11489 parentGet = $parse(attrs[attrName]); 11490 var isLiteral = parentGet.literal; 11491 11492 var initialValue = destination[scopeName] = parentGet(scope); 11493 initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]); 11494 11495 removeWatch = scope[definition.collection ? '$watchCollection' : '$watch'](parentGet, function parentValueWatchAction(newValue, oldValue) { 11496 if (oldValue === newValue) { 11497 if (oldValue === initialValue || (isLiteral && equals(oldValue, initialValue))) { 11498 return; 11499 } 11500 oldValue = initialValue; 11501 } 11502 recordChanges(scopeName, newValue, oldValue); 11503 destination[scopeName] = newValue; 11504 }); 11505 11506 removeWatchCollection.push(removeWatch); 11507 break; 11508 11509 case '&': 11510 if (!optional && !hasOwnProperty.call(attrs, attrName)) { 11511 strictBindingsCheck(attrName, directive.name); 11512 } 11513 // Don't assign Object.prototype method to scope 11514 parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop; 11515 11516 // Don't assign noop to destination if expression is not valid 11517 if (parentGet === noop && optional) break; 11518 11519 destination[scopeName] = function(locals) { 11520 return parentGet(scope, locals); 11521 }; 11522 break; 11523 } 11524 }); 11525 11526 function recordChanges(key, currentValue, previousValue) { 11527 if (isFunction(destination.$onChanges) && !simpleCompare(currentValue, previousValue)) { 11528 // If we have not already scheduled the top level onChangesQueue handler then do so now 11529 if (!onChangesQueue) { 11530 scope.$$postDigest(flushOnChangesQueue); 11531 onChangesQueue = []; 11532 } 11533 // If we have not already queued a trigger of onChanges for this controller then do so now 11534 if (!changes) { 11535 changes = {}; 11536 onChangesQueue.push(triggerOnChangesHook); 11537 } 11538 // If the has been a change on this property already then we need to reuse the previous value 11539 if (changes[key]) { 11540 previousValue = changes[key].previousValue; 11541 } 11542 // Store this change 11543 changes[key] = new SimpleChange(previousValue, currentValue); 11544 } 11545 } 11546 11547 function triggerOnChangesHook() { 11548 destination.$onChanges(changes); 11549 // Now clear the changes so that we schedule onChanges when more changes arrive 11550 changes = undefined; 11551 } 11552 11553 return { 11554 initialChanges: initialChanges, 11555 removeWatches: removeWatchCollection.length && function removeWatches() { 11556 for (var i = 0, ii = removeWatchCollection.length; i < ii; ++i) { 11557 removeWatchCollection[i](); 11558 } 11559 } 11560 }; 11561 } 11562 }]; 11563} 11564 11565function SimpleChange(previous, current) { 11566 this.previousValue = previous; 11567 this.currentValue = current; 11568} 11569SimpleChange.prototype.isFirstChange = function() { return this.previousValue === _UNINITIALIZED_VALUE; }; 11570 11571 11572var PREFIX_REGEXP = /^((?:x|data)[:\-_])/i; 11573var SPECIAL_CHARS_REGEXP = /[:\-_]+(.)/g; 11574 11575/** 11576 * Converts all accepted directives format into proper directive name. 11577 * @param name Name to normalize 11578 */ 11579function directiveNormalize(name) { 11580 return name 11581 .replace(PREFIX_REGEXP, '') 11582 .replace(SPECIAL_CHARS_REGEXP, function(_, letter, offset) { 11583 return offset ? letter.toUpperCase() : letter; 11584 }); 11585} 11586 11587/** 11588 * @ngdoc type 11589 * @name $compile.directive.Attributes 11590 * 11591 * @description 11592 * A shared object between directive compile / linking functions which contains normalized DOM 11593 * element attributes. The values reflect current binding state `{{ }}`. The normalization is 11594 * needed since all of these are treated as equivalent in AngularJS: 11595 * 11596 * ``` 11597 * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a"> 11598 * ``` 11599 */ 11600 11601/** 11602 * @ngdoc property 11603 * @name $compile.directive.Attributes#$attr 11604 * 11605 * @description 11606 * A map of DOM element attribute names to the normalized name. This is 11607 * needed to do reverse lookup from normalized name back to actual name. 11608 */ 11609 11610 11611/** 11612 * @ngdoc method 11613 * @name $compile.directive.Attributes#$set 11614 * @kind function 11615 * 11616 * @description 11617 * Set DOM element attribute value. 11618 * 11619 * 11620 * @param {string} name Normalized element attribute name of the property to modify. The name is 11621 * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr} 11622 * property to the original name. 11623 * @param {string} value Value to set the attribute to. The value can be an interpolated string. 11624 */ 11625 11626 11627 11628/** 11629 * Closure compiler type information 11630 */ 11631 11632function nodesetLinkingFn( 11633 /* angular.Scope */ scope, 11634 /* NodeList */ nodeList, 11635 /* Element */ rootElement, 11636 /* function(Function) */ boundTranscludeFn 11637) {} 11638 11639function directiveLinkingFn( 11640 /* nodesetLinkingFn */ nodesetLinkingFn, 11641 /* angular.Scope */ scope, 11642 /* Node */ node, 11643 /* Element */ rootElement, 11644 /* function(Function) */ boundTranscludeFn 11645) {} 11646 11647function tokenDifference(str1, str2) { 11648 var values = '', 11649 tokens1 = str1.split(/\s+/), 11650 tokens2 = str2.split(/\s+/); 11651 11652 outer: 11653 for (var i = 0; i < tokens1.length; i++) { 11654 var token = tokens1[i]; 11655 for (var j = 0; j < tokens2.length; j++) { 11656 if (token === tokens2[j]) continue outer; 11657 } 11658 values += (values.length > 0 ? ' ' : '') + token; 11659 } 11660 return values; 11661} 11662 11663function removeComments(jqNodes) { 11664 jqNodes = jqLite(jqNodes); 11665 var i = jqNodes.length; 11666 11667 if (i <= 1) { 11668 return jqNodes; 11669 } 11670 11671 while (i--) { 11672 var node = jqNodes[i]; 11673 if (node.nodeType === NODE_TYPE_COMMENT || 11674 (node.nodeType === NODE_TYPE_TEXT && node.nodeValue.trim() === '')) { 11675 splice.call(jqNodes, i, 1); 11676 } 11677 } 11678 return jqNodes; 11679} 11680 11681var $controllerMinErr = minErr('$controller'); 11682 11683 11684var CNTRL_REG = /^(\S+)(\s+as\s+([\w$]+))?$/; 11685function identifierForController(controller, ident) { 11686 if (ident && isString(ident)) return ident; 11687 if (isString(controller)) { 11688 var match = CNTRL_REG.exec(controller); 11689 if (match) return match[3]; 11690 } 11691} 11692 11693 11694/** 11695 * @ngdoc provider 11696 * @name $controllerProvider 11697 * @this 11698 * 11699 * @description 11700 * The {@link ng.$controller $controller service} is used by AngularJS to create new 11701 * controllers. 11702 * 11703 * This provider allows controller registration via the 11704 * {@link ng.$controllerProvider#register register} method. 11705 */ 11706function $ControllerProvider() { 11707 var controllers = {}; 11708 11709 /** 11710 * @ngdoc method 11711 * @name $controllerProvider#has 11712 * @param {string} name Controller name to check. 11713 */ 11714 this.has = function(name) { 11715 return controllers.hasOwnProperty(name); 11716 }; 11717 11718 /** 11719 * @ngdoc method 11720 * @name $controllerProvider#register 11721 * @param {string|Object} name Controller name, or an object map of controllers where the keys are 11722 * the names and the values are the constructors. 11723 * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI 11724 * annotations in the array notation). 11725 */ 11726 this.register = function(name, constructor) { 11727 assertNotHasOwnProperty(name, 'controller'); 11728 if (isObject(name)) { 11729 extend(controllers, name); 11730 } else { 11731 controllers[name] = constructor; 11732 } 11733 }; 11734 11735 this.$get = ['$injector', function($injector) { 11736 11737 /** 11738 * @ngdoc service 11739 * @name $controller 11740 * @requires $injector 11741 * 11742 * @param {Function|string} constructor If called with a function then it's considered to be the 11743 * controller constructor function. Otherwise it's considered to be a string which is used 11744 * to retrieve the controller constructor using the following steps: 11745 * 11746 * * check if a controller with given name is registered via `$controllerProvider` 11747 * * check if evaluating the string on the current scope returns a constructor 11748 * 11749 * The string can use the `controller as property` syntax, where the controller instance is published 11750 * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this 11751 * to work correctly. 11752 * 11753 * @param {Object} locals Injection locals for Controller. 11754 * @return {Object} Instance of given controller. 11755 * 11756 * @description 11757 * `$controller` service is responsible for instantiating controllers. 11758 * 11759 * It's just a simple call to {@link auto.$injector $injector}, but extracted into 11760 * a service, so that one can override this service with [BC version](https://gist.github.com/1649788). 11761 */ 11762 return function $controller(expression, locals, later, ident) { 11763 // PRIVATE API: 11764 // param `later` --- indicates that the controller's constructor is invoked at a later time. 11765 // If true, $controller will allocate the object with the correct 11766 // prototype chain, but will not invoke the controller until a returned 11767 // callback is invoked. 11768 // param `ident` --- An optional label which overrides the label parsed from the controller 11769 // expression, if any. 11770 var instance, match, constructor, identifier; 11771 later = later === true; 11772 if (ident && isString(ident)) { 11773 identifier = ident; 11774 } 11775 11776 if (isString(expression)) { 11777 match = expression.match(CNTRL_REG); 11778 if (!match) { 11779 throw $controllerMinErr('ctrlfmt', 11780 'Badly formed controller string \'{0}\'. ' + 11781 'Must match `__name__ as __id__` or `__name__`.', expression); 11782 } 11783 constructor = match[1]; 11784 identifier = identifier || match[3]; 11785 expression = controllers.hasOwnProperty(constructor) 11786 ? controllers[constructor] 11787 : getter(locals.$scope, constructor, true); 11788 11789 if (!expression) { 11790 throw $controllerMinErr('ctrlreg', 11791 'The controller with the name \'{0}\' is not registered.', constructor); 11792 } 11793 11794 assertArgFn(expression, constructor, true); 11795 } 11796 11797 if (later) { 11798 // Instantiate controller later: 11799 // This machinery is used to create an instance of the object before calling the 11800 // controller's constructor itself. 11801 // 11802 // This allows properties to be added to the controller before the constructor is 11803 // invoked. Primarily, this is used for isolate scope bindings in $compile. 11804 // 11805 // This feature is not intended for use by applications, and is thus not documented 11806 // publicly. 11807 // Object creation: http://jsperf.com/create-constructor/2 11808 var controllerPrototype = (isArray(expression) ? 11809 expression[expression.length - 1] : expression).prototype; 11810 instance = Object.create(controllerPrototype || null); 11811 11812 if (identifier) { 11813 addIdentifier(locals, identifier, instance, constructor || expression.name); 11814 } 11815 11816 return extend(function $controllerInit() { 11817 var result = $injector.invoke(expression, instance, locals, constructor); 11818 if (result !== instance && (isObject(result) || isFunction(result))) { 11819 instance = result; 11820 if (identifier) { 11821 // If result changed, re-assign controllerAs value to scope. 11822 addIdentifier(locals, identifier, instance, constructor || expression.name); 11823 } 11824 } 11825 return instance; 11826 }, { 11827 instance: instance, 11828 identifier: identifier 11829 }); 11830 } 11831 11832 instance = $injector.instantiate(expression, locals, constructor); 11833 11834 if (identifier) { 11835 addIdentifier(locals, identifier, instance, constructor || expression.name); 11836 } 11837 11838 return instance; 11839 }; 11840 11841 function addIdentifier(locals, identifier, instance, name) { 11842 if (!(locals && isObject(locals.$scope))) { 11843 throw minErr('$controller')('noscp', 11844 'Cannot export controller \'{0}\' as \'{1}\'! No $scope object provided via `locals`.', 11845 name, identifier); 11846 } 11847 11848 locals.$scope[identifier] = instance; 11849 } 11850 }]; 11851} 11852 11853/** 11854 * @ngdoc service 11855 * @name $document 11856 * @requires $window 11857 * @this 11858 * 11859 * @description 11860 * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object. 11861 * 11862 * @example 11863 <example module="documentExample" name="document"> 11864 <file name="index.html"> 11865 <div ng-controller="ExampleController"> 11866 <p>$document title: <b ng-bind="title"></b></p> 11867 <p>window.document title: <b ng-bind="windowTitle"></b></p> 11868 </div> 11869 </file> 11870 <file name="script.js"> 11871 angular.module('documentExample', []) 11872 .controller('ExampleController', ['$scope', '$document', function($scope, $document) { 11873 $scope.title = $document[0].title; 11874 $scope.windowTitle = angular.element(window.document)[0].title; 11875 }]); 11876 </file> 11877 </example> 11878 */ 11879function $DocumentProvider() { 11880 this.$get = ['$window', function(window) { 11881 return jqLite(window.document); 11882 }]; 11883} 11884 11885 11886/** 11887 * @private 11888 * @this 11889 * Listens for document visibility change and makes the current status accessible. 11890 */ 11891function $$IsDocumentHiddenProvider() { 11892 this.$get = ['$document', '$rootScope', function($document, $rootScope) { 11893 var doc = $document[0]; 11894 var hidden = doc && doc.hidden; 11895 11896 $document.on('visibilitychange', changeListener); 11897 11898 $rootScope.$on('$destroy', function() { 11899 $document.off('visibilitychange', changeListener); 11900 }); 11901 11902 function changeListener() { 11903 hidden = doc.hidden; 11904 } 11905 11906 return function() { 11907 return hidden; 11908 }; 11909 }]; 11910} 11911 11912/** 11913 * @ngdoc service 11914 * @name $exceptionHandler 11915 * @requires ng.$log 11916 * @this 11917 * 11918 * @description 11919 * Any uncaught exception in AngularJS expressions is delegated to this service. 11920 * The default implementation simply delegates to `$log.error` which logs it into 11921 * the browser console. 11922 * 11923 * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by 11924 * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing. 11925 * 11926 * ## Example: 11927 * 11928 * The example below will overwrite the default `$exceptionHandler` in order to (a) log uncaught 11929 * errors to the backend for later inspection by the developers and (b) to use `$log.warn()` instead 11930 * of `$log.error()`. 11931 * 11932 * ```js 11933 * angular. 11934 * module('exceptionOverwrite', []). 11935 * factory('$exceptionHandler', ['$log', 'logErrorsToBackend', function($log, logErrorsToBackend) { 11936 * return function myExceptionHandler(exception, cause) { 11937 * logErrorsToBackend(exception, cause); 11938 * $log.warn(exception, cause); 11939 * }; 11940 * }]); 11941 * ``` 11942 * 11943 * <hr /> 11944 * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind` 11945 * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler} 11946 * (unless executed during a digest). 11947 * 11948 * If you wish, you can manually delegate exceptions, e.g. 11949 * `try { ... } catch(e) { $exceptionHandler(e); }` 11950 * 11951 * @param {Error} exception Exception associated with the error. 11952 * @param {string=} cause Optional information about the context in which 11953 * the error was thrown. 11954 * 11955 */ 11956function $ExceptionHandlerProvider() { 11957 this.$get = ['$log', function($log) { 11958 return function(exception, cause) { 11959 $log.error.apply($log, arguments); 11960 }; 11961 }]; 11962} 11963 11964var $$ForceReflowProvider = /** @this */ function() { 11965 this.$get = ['$document', function($document) { 11966 return function(domNode) { 11967 //the line below will force the browser to perform a repaint so 11968 //that all the animated elements within the animation frame will 11969 //be properly updated and drawn on screen. This is required to 11970 //ensure that the preparation animation is properly flushed so that 11971 //the active state picks up from there. DO NOT REMOVE THIS LINE. 11972 //DO NOT OPTIMIZE THIS LINE. THE MINIFIER WILL REMOVE IT OTHERWISE WHICH 11973 //WILL RESULT IN AN UNPREDICTABLE BUG THAT IS VERY HARD TO TRACK DOWN AND 11974 //WILL TAKE YEARS AWAY FROM YOUR LIFE. 11975 if (domNode) { 11976 if (!domNode.nodeType && domNode instanceof jqLite) { 11977 domNode = domNode[0]; 11978 } 11979 } else { 11980 domNode = $document[0].body; 11981 } 11982 return domNode.offsetWidth + 1; 11983 }; 11984 }]; 11985}; 11986 11987var APPLICATION_JSON = 'application/json'; 11988var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'}; 11989var JSON_START = /^\[|^\{(?!\{)/; 11990var JSON_ENDS = { 11991 '[': /]$/, 11992 '{': /}$/ 11993}; 11994var JSON_PROTECTION_PREFIX = /^\)]\}',?\n/; 11995var $httpMinErr = minErr('$http'); 11996 11997function serializeValue(v) { 11998 if (isObject(v)) { 11999 return isDate(v) ? v.toISOString() : toJson(v); 12000 } 12001 return v; 12002} 12003 12004 12005/** @this */ 12006function $HttpParamSerializerProvider() { 12007 /** 12008 * @ngdoc service 12009 * @name $httpParamSerializer 12010 * @description 12011 * 12012 * Default {@link $http `$http`} params serializer that converts objects to strings 12013 * according to the following rules: 12014 * 12015 * * `{'foo': 'bar'}` results in `foo=bar` 12016 * * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object) 12017 * * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element) 12018 * * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D` (stringified and encoded representation of an object) 12019 * 12020 * Note that serializer will sort the request parameters alphabetically. 12021 */ 12022 12023 this.$get = function() { 12024 return function ngParamSerializer(params) { 12025 if (!params) return ''; 12026 var parts = []; 12027 forEachSorted(params, function(value, key) { 12028 if (value === null || isUndefined(value) || isFunction(value)) return; 12029 if (isArray(value)) { 12030 forEach(value, function(v) { 12031 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v))); 12032 }); 12033 } else { 12034 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value))); 12035 } 12036 }); 12037 12038 return parts.join('&'); 12039 }; 12040 }; 12041} 12042 12043/** @this */ 12044function $HttpParamSerializerJQLikeProvider() { 12045 /** 12046 * @ngdoc service 12047 * @name $httpParamSerializerJQLike 12048 * 12049 * @description 12050 * 12051 * Alternative {@link $http `$http`} params serializer that follows 12052 * jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic. 12053 * The serializer will also sort the params alphabetically. 12054 * 12055 * To use it for serializing `$http` request parameters, set it as the `paramSerializer` property: 12056 * 12057 * ```js 12058 * $http({ 12059 * url: myUrl, 12060 * method: 'GET', 12061 * params: myParams, 12062 * paramSerializer: '$httpParamSerializerJQLike' 12063 * }); 12064 * ``` 12065 * 12066 * It is also possible to set it as the default `paramSerializer` in the 12067 * {@link $httpProvider#defaults `$httpProvider`}. 12068 * 12069 * Additionally, you can inject the serializer and use it explicitly, for example to serialize 12070 * form data for submission: 12071 * 12072 * ```js 12073 * .controller(function($http, $httpParamSerializerJQLike) { 12074 * //... 12075 * 12076 * $http({ 12077 * url: myUrl, 12078 * method: 'POST', 12079 * data: $httpParamSerializerJQLike(myData), 12080 * headers: { 12081 * 'Content-Type': 'application/x-www-form-urlencoded' 12082 * } 12083 * }); 12084 * 12085 * }); 12086 * ``` 12087 * 12088 */ 12089 this.$get = function() { 12090 return function jQueryLikeParamSerializer(params) { 12091 if (!params) return ''; 12092 var parts = []; 12093 serialize(params, '', true); 12094 return parts.join('&'); 12095 12096 function serialize(toSerialize, prefix, topLevel) { 12097 if (isArray(toSerialize)) { 12098 forEach(toSerialize, function(value, index) { 12099 serialize(value, prefix + '[' + (isObject(value) ? index : '') + ']'); 12100 }); 12101 } else if (isObject(toSerialize) && !isDate(toSerialize)) { 12102 forEachSorted(toSerialize, function(value, key) { 12103 serialize(value, prefix + 12104 (topLevel ? '' : '[') + 12105 key + 12106 (topLevel ? '' : ']')); 12107 }); 12108 } else { 12109 if (isFunction(toSerialize)) { 12110 toSerialize = toSerialize(); 12111 } 12112 parts.push(encodeUriQuery(prefix) + '=' + 12113 (toSerialize == null ? '' : encodeUriQuery(serializeValue(toSerialize)))); 12114 } 12115 } 12116 }; 12117 }; 12118} 12119 12120function defaultHttpResponseTransform(data, headers) { 12121 if (isString(data)) { 12122 // Strip json vulnerability protection prefix and trim whitespace 12123 var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim(); 12124 12125 if (tempData) { 12126 var contentType = headers('Content-Type'); 12127 var hasJsonContentType = contentType && (contentType.indexOf(APPLICATION_JSON) === 0); 12128 12129 if (hasJsonContentType || isJsonLike(tempData)) { 12130 try { 12131 data = fromJson(tempData); 12132 } catch (e) { 12133 if (!hasJsonContentType) { 12134 return data; 12135 } 12136 throw $httpMinErr('baddata', 'Data must be a valid JSON object. Received: "{0}". ' + 12137 'Parse error: "{1}"', data, e); 12138 } 12139 } 12140 } 12141 } 12142 12143 return data; 12144} 12145 12146function isJsonLike(str) { 12147 var jsonStart = str.match(JSON_START); 12148 return jsonStart && JSON_ENDS[jsonStart[0]].test(str); 12149} 12150 12151/** 12152 * Parse headers into key value object 12153 * 12154 * @param {string} headers Raw headers as a string 12155 * @returns {Object} Parsed headers as key value object 12156 */ 12157function parseHeaders(headers) { 12158 var parsed = createMap(), i; 12159 12160 function fillInParsed(key, val) { 12161 if (key) { 12162 parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; 12163 } 12164 } 12165 12166 if (isString(headers)) { 12167 forEach(headers.split('\n'), function(line) { 12168 i = line.indexOf(':'); 12169 fillInParsed(lowercase(trim(line.substr(0, i))), trim(line.substr(i + 1))); 12170 }); 12171 } else if (isObject(headers)) { 12172 forEach(headers, function(headerVal, headerKey) { 12173 fillInParsed(lowercase(headerKey), trim(headerVal)); 12174 }); 12175 } 12176 12177 return parsed; 12178} 12179 12180 12181/** 12182 * Returns a function that provides access to parsed headers. 12183 * 12184 * Headers are lazy parsed when first requested. 12185 * @see parseHeaders 12186 * 12187 * @param {(string|Object)} headers Headers to provide access to. 12188 * @returns {function(string=)} Returns a getter function which if called with: 12189 * 12190 * - if called with an argument returns a single header value or null 12191 * - if called with no arguments returns an object containing all headers. 12192 */ 12193function headersGetter(headers) { 12194 var headersObj; 12195 12196 return function(name) { 12197 if (!headersObj) headersObj = parseHeaders(headers); 12198 12199 if (name) { 12200 var value = headersObj[lowercase(name)]; 12201 if (value === undefined) { 12202 value = null; 12203 } 12204 return value; 12205 } 12206 12207 return headersObj; 12208 }; 12209} 12210 12211 12212/** 12213 * Chain all given functions 12214 * 12215 * This function is used for both request and response transforming 12216 * 12217 * @param {*} data Data to transform. 12218 * @param {function(string=)} headers HTTP headers getter fn. 12219 * @param {number} status HTTP status code of the response. 12220 * @param {(Function|Array.<Function>)} fns Function or an array of functions. 12221 * @returns {*} Transformed data. 12222 */ 12223function transformData(data, headers, status, fns) { 12224 if (isFunction(fns)) { 12225 return fns(data, headers, status); 12226 } 12227 12228 forEach(fns, function(fn) { 12229 data = fn(data, headers, status); 12230 }); 12231 12232 return data; 12233} 12234 12235 12236function isSuccess(status) { 12237 return 200 <= status && status < 300; 12238} 12239 12240 12241/** 12242 * @ngdoc provider 12243 * @name $httpProvider 12244 * @this 12245 * 12246 * @description 12247 * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service. 12248 */ 12249function $HttpProvider() { 12250 /** 12251 * @ngdoc property 12252 * @name $httpProvider#defaults 12253 * @description 12254 * 12255 * Object containing default values for all {@link ng.$http $http} requests. 12256 * 12257 * - **`defaults.cache`** - {boolean|Object} - A boolean value or object created with 12258 * {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of HTTP responses 12259 * by default. See {@link $http#caching $http Caching} for more information. 12260 * 12261 * - **`defaults.headers`** - {Object} - Default headers for all $http requests. 12262 * Refer to {@link ng.$http#setting-http-headers $http} for documentation on 12263 * setting default headers. 12264 * - **`defaults.headers.common`** 12265 * - **`defaults.headers.post`** 12266 * - **`defaults.headers.put`** 12267 * - **`defaults.headers.patch`** 12268 * 12269 * - **`defaults.jsonpCallbackParam`** - `{string}` - the name of the query parameter that passes the name of the 12270 * callback in a JSONP request. The value of this parameter will be replaced with the expression generated by the 12271 * {@link $jsonpCallbacks} service. Defaults to `'callback'`. 12272 * 12273 * - **`defaults.paramSerializer`** - `{string|function(Object<string,string>):string}` - A function 12274 * used to the prepare string representation of request parameters (specified as an object). 12275 * If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}. 12276 * Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}. 12277 * 12278 * - **`defaults.transformRequest`** - 12279 * `{Array<function(data, headersGetter)>|function(data, headersGetter)}` - 12280 * An array of functions (or a single function) which are applied to the request data. 12281 * By default, this is an array with one request transformation function: 12282 * 12283 * - If the `data` property of the request configuration object contains an object, serialize it 12284 * into JSON format. 12285 * 12286 * - **`defaults.transformResponse`** - 12287 * `{Array<function(data, headersGetter, status)>|function(data, headersGetter, status)}` - 12288 * An array of functions (or a single function) which are applied to the response data. By default, 12289 * this is an array which applies one response transformation function that does two things: 12290 * 12291 * - If XSRF prefix is detected, strip it 12292 * (see {@link ng.$http#security-considerations Security Considerations in the $http docs}). 12293 * - If the `Content-Type` is `application/json` or the response looks like JSON, 12294 * deserialize it using a JSON parser. 12295 * 12296 * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token. 12297 * Defaults value is `'XSRF-TOKEN'`. 12298 * 12299 * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the 12300 * XSRF token. Defaults value is `'X-XSRF-TOKEN'`. 12301 * 12302 */ 12303 var defaults = this.defaults = { 12304 // transform incoming response data 12305 transformResponse: [defaultHttpResponseTransform], 12306 12307 // transform outgoing request data 12308 transformRequest: [function(d) { 12309 return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d; 12310 }], 12311 12312 // default headers 12313 headers: { 12314 common: { 12315 'Accept': 'application/json, text/plain, */*' 12316 }, 12317 post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), 12318 put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), 12319 patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON) 12320 }, 12321 12322 xsrfCookieName: 'XSRF-TOKEN', 12323 xsrfHeaderName: 'X-XSRF-TOKEN', 12324 12325 paramSerializer: '$httpParamSerializer', 12326 12327 jsonpCallbackParam: 'callback' 12328 }; 12329 12330 var useApplyAsync = false; 12331 /** 12332 * @ngdoc method 12333 * @name $httpProvider#useApplyAsync 12334 * @description 12335 * 12336 * Configure $http service to combine processing of multiple http responses received at around 12337 * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in 12338 * significant performance improvement for bigger applications that make many HTTP requests 12339 * concurrently (common during application bootstrap). 12340 * 12341 * Defaults to false. If no value is specified, returns the current configured value. 12342 * 12343 * @param {boolean=} value If true, when requests are loaded, they will schedule a deferred 12344 * "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window 12345 * to load and share the same digest cycle. 12346 * 12347 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining. 12348 * otherwise, returns the current configured value. 12349 */ 12350 this.useApplyAsync = function(value) { 12351 if (isDefined(value)) { 12352 useApplyAsync = !!value; 12353 return this; 12354 } 12355 return useApplyAsync; 12356 }; 12357 12358 /** 12359 * @ngdoc property 12360 * @name $httpProvider#interceptors 12361 * @description 12362 * 12363 * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http} 12364 * pre-processing of request or postprocessing of responses. 12365 * 12366 * These service factories are ordered by request, i.e. they are applied in the same order as the 12367 * array, on request, but reverse order, on response. 12368 * 12369 * {@link ng.$http#interceptors Interceptors detailed info} 12370 */ 12371 var interceptorFactories = this.interceptors = []; 12372 12373 /** 12374 * @ngdoc property 12375 * @name $httpProvider#xsrfTrustedOrigins 12376 * @description 12377 * 12378 * Array containing URLs whose origins are trusted to receive the XSRF token. See the 12379 * {@link ng.$http#security-considerations Security Considerations} sections for more details on 12380 * XSRF. 12381 * 12382 * **Note:** An "origin" consists of the [URI scheme](https://en.wikipedia.org/wiki/URI_scheme), 12383 * the [hostname](https://en.wikipedia.org/wiki/Hostname) and the 12384 * [port number](https://en.wikipedia.org/wiki/Port_(computer_networking). For `http:` and 12385 * `https:`, the port number can be omitted if using th default ports (80 and 443 respectively). 12386 * Examples: `http://example.com`, `https://api.example.com:9876` 12387 * 12388 * <div class="alert alert-warning"> 12389 * It is not possible to trust specific URLs/paths. The `path`, `query` and `fragment` parts 12390 * of a URL will be ignored. For example, `https://foo.com/path/bar?query=baz#fragment` will be 12391 * treated as `https://foo.com`, meaning that **all** requests to URLs starting with 12392 * `https://foo.com/` will include the XSRF token. 12393 * </div> 12394 * 12395 * @example 12396 * 12397 * ```js 12398 * // App served from `https://example.com/`. 12399 * angular. 12400 * module('xsrfTrustedOriginsExample', []). 12401 * config(['$httpProvider', function($httpProvider) { 12402 * $httpProvider.xsrfTrustedOrigins.push('https://api.example.com'); 12403 * }]). 12404 * run(['$http', function($http) { 12405 * // The XSRF token will be sent. 12406 * $http.get('https://api.example.com/preferences').then(...); 12407 * 12408 * // The XSRF token will NOT be sent. 12409 * $http.get('https://stats.example.com/activity').then(...); 12410 * }]); 12411 * ``` 12412 */ 12413 var xsrfTrustedOrigins = this.xsrfTrustedOrigins = []; 12414 12415 /** 12416 * @ngdoc property 12417 * @name $httpProvider#xsrfWhitelistedOrigins 12418 * @description 12419 * 12420 * @deprecated 12421 * sinceVersion="1.8.1" 12422 * 12423 * This property is deprecated. Use {@link $httpProvider#xsrfTrustedOrigins xsrfTrustedOrigins} 12424 * instead. 12425 */ 12426 Object.defineProperty(this, 'xsrfWhitelistedOrigins', { 12427 get: function() { 12428 return this.xsrfTrustedOrigins; 12429 }, 12430 set: function(origins) { 12431 this.xsrfTrustedOrigins = origins; 12432 } 12433 }); 12434 12435 this.$get = ['$browser', '$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector', '$sce', 12436 function($browser, $httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector, $sce) { 12437 12438 var defaultCache = $cacheFactory('$http'); 12439 12440 /** 12441 * Make sure that default param serializer is exposed as a function 12442 */ 12443 defaults.paramSerializer = isString(defaults.paramSerializer) ? 12444 $injector.get(defaults.paramSerializer) : defaults.paramSerializer; 12445 12446 /** 12447 * Interceptors stored in reverse order. Inner interceptors before outer interceptors. 12448 * The reversal is needed so that we can build up the interception chain around the 12449 * server request. 12450 */ 12451 var reversedInterceptors = []; 12452 12453 forEach(interceptorFactories, function(interceptorFactory) { 12454 reversedInterceptors.unshift(isString(interceptorFactory) 12455 ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); 12456 }); 12457 12458 /** 12459 * A function to check request URLs against a list of allowed origins. 12460 */ 12461 var urlIsAllowedOrigin = urlIsAllowedOriginFactory(xsrfTrustedOrigins); 12462 12463 /** 12464 * @ngdoc service 12465 * @kind function 12466 * @name $http 12467 * @requires ng.$httpBackend 12468 * @requires $cacheFactory 12469 * @requires $rootScope 12470 * @requires $q 12471 * @requires $injector 12472 * 12473 * @description 12474 * The `$http` service is a core AngularJS service that facilitates communication with the remote 12475 * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest) 12476 * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP). 12477 * 12478 * For unit testing applications that use `$http` service, see 12479 * {@link ngMock.$httpBackend $httpBackend mock}. 12480 * 12481 * For a higher level of abstraction, please check out the {@link ngResource.$resource 12482 * $resource} service. 12483 * 12484 * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by 12485 * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage 12486 * it is important to familiarize yourself with these APIs and the guarantees they provide. 12487 * 12488 * 12489 * ## General usage 12490 * The `$http` service is a function which takes a single argument â a {@link $http#usage configuration object} â 12491 * that is used to generate an HTTP request and returns a {@link ng.$q promise} that is 12492 * resolved (request success) or rejected (request failure) with a 12493 * {@link ng.$http#$http-returns response} object. 12494 * 12495 * ```js 12496 * // Simple GET request example: 12497 * $http({ 12498 * method: 'GET', 12499 * url: '/someUrl' 12500 * }).then(function successCallback(response) { 12501 * // this callback will be called asynchronously 12502 * // when the response is available 12503 * }, function errorCallback(response) { 12504 * // called asynchronously if an error occurs 12505 * // or server returns response with an error status. 12506 * }); 12507 * ``` 12508 * 12509 * 12510 * ## Shortcut methods 12511 * 12512 * Shortcut methods are also available. All shortcut methods require passing in the URL, and 12513 * request data must be passed in for POST/PUT requests. An optional config can be passed as the 12514 * last argument. 12515 * 12516 * ```js 12517 * $http.get('/someUrl', config).then(successCallback, errorCallback); 12518 * $http.post('/someUrl', data, config).then(successCallback, errorCallback); 12519 * ``` 12520 * 12521 * Complete list of shortcut methods: 12522 * 12523 * - {@link ng.$http#get $http.get} 12524 * - {@link ng.$http#head $http.head} 12525 * - {@link ng.$http#post $http.post} 12526 * - {@link ng.$http#put $http.put} 12527 * - {@link ng.$http#delete $http.delete} 12528 * - {@link ng.$http#jsonp $http.jsonp} 12529 * - {@link ng.$http#patch $http.patch} 12530 * 12531 * 12532 * ## Writing Unit Tests that use $http 12533 * When unit testing (using {@link ngMock ngMock}), it is necessary to call 12534 * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending 12535 * request using trained responses. 12536 * 12537 * ``` 12538 * $httpBackend.expectGET(...); 12539 * $http.get(...); 12540 * $httpBackend.flush(); 12541 * ``` 12542 * 12543 * ## Setting HTTP Headers 12544 * 12545 * The $http service will automatically add certain HTTP headers to all requests. These defaults 12546 * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration 12547 * object, which currently contains this default configuration: 12548 * 12549 * - `$httpProvider.defaults.headers.common` (headers that are common for all requests): 12550 * - <code>Accept: application/json, text/plain, \*/\*</code> 12551 * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests) 12552 * - `Content-Type: application/json` 12553 * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests) 12554 * - `Content-Type: application/json` 12555 * 12556 * To add or overwrite these defaults, simply add or remove a property from these configuration 12557 * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object 12558 * with the lowercased HTTP method name as the key, e.g. 12559 * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }`. 12560 * 12561 * The defaults can also be set at runtime via the `$http.defaults` object in the same 12562 * fashion. For example: 12563 * 12564 * ``` 12565 * module.run(function($http) { 12566 * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w'; 12567 * }); 12568 * ``` 12569 * 12570 * In addition, you can supply a `headers` property in the config object passed when 12571 * calling `$http(config)`, which overrides the defaults without changing them globally. 12572 * 12573 * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis, 12574 * Use the `headers` property, setting the desired header to `undefined`. For example: 12575 * 12576 * ```js 12577 * var req = { 12578 * method: 'POST', 12579 * url: 'http://example.com', 12580 * headers: { 12581 * 'Content-Type': undefined 12582 * }, 12583 * data: { test: 'test' } 12584 * } 12585 * 12586 * $http(req).then(function(){...}, function(){...}); 12587 * ``` 12588 * 12589 * ## Transforming Requests and Responses 12590 * 12591 * Both requests and responses can be transformed using transformation functions: `transformRequest` 12592 * and `transformResponse`. These properties can be a single function that returns 12593 * the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions, 12594 * which allows you to `push` or `unshift` a new transformation function into the transformation chain. 12595 * 12596 * <div class="alert alert-warning"> 12597 * **Note:** AngularJS does not make a copy of the `data` parameter before it is passed into the `transformRequest` pipeline. 12598 * That means changes to the properties of `data` are not local to the transform function (since Javascript passes objects by reference). 12599 * For example, when calling `$http.get(url, $scope.myObject)`, modifications to the object's properties in a transformRequest 12600 * function will be reflected on the scope and in any templates where the object is data-bound. 12601 * To prevent this, transform functions should have no side-effects. 12602 * If you need to modify properties, it is recommended to make a copy of the data, or create new object to return. 12603 * </div> 12604 * 12605 * ### Default Transformations 12606 * 12607 * The `$httpProvider` provider and `$http` service expose `defaults.transformRequest` and 12608 * `defaults.transformResponse` properties. If a request does not provide its own transformations 12609 * then these will be applied. 12610 * 12611 * You can augment or replace the default transformations by modifying these properties by adding to or 12612 * replacing the array. 12613 * 12614 * AngularJS provides the following default transformations: 12615 * 12616 * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`) is 12617 * an array with one function that does the following: 12618 * 12619 * - If the `data` property of the request configuration object contains an object, serialize it 12620 * into JSON format. 12621 * 12622 * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`) is 12623 * an array with one function that does the following: 12624 * 12625 * - If XSRF prefix is detected, strip it (see Security Considerations section below). 12626 * - If the `Content-Type` is `application/json` or the response looks like JSON, 12627 * deserialize it using a JSON parser. 12628 * 12629 * 12630 * ### Overriding the Default Transformations Per Request 12631 * 12632 * If you wish to override the request/response transformations only for a single request then provide 12633 * `transformRequest` and/or `transformResponse` properties on the configuration object passed 12634 * into `$http`. 12635 * 12636 * Note that if you provide these properties on the config object the default transformations will be 12637 * overwritten. If you wish to augment the default transformations then you must include them in your 12638 * local transformation array. 12639 * 12640 * The following code demonstrates adding a new response transformation to be run after the default response 12641 * transformations have been run. 12642 * 12643 * ```js 12644 * function appendTransform(defaults, transform) { 12645 * 12646 * // We can't guarantee that the default transformation is an array 12647 * defaults = angular.isArray(defaults) ? defaults : [defaults]; 12648 * 12649 * // Append the new transformation to the defaults 12650 * return defaults.concat(transform); 12651 * } 12652 * 12653 * $http({ 12654 * url: '...', 12655 * method: 'GET', 12656 * transformResponse: appendTransform($http.defaults.transformResponse, function(value) { 12657 * return doTransform(value); 12658 * }) 12659 * }); 12660 * ``` 12661 * 12662 * 12663 * ## Caching 12664 * 12665 * {@link ng.$http `$http`} responses are not cached by default. To enable caching, you must 12666 * set the config.cache value or the default cache value to TRUE or to a cache object (created 12667 * with {@link ng.$cacheFactory `$cacheFactory`}). If defined, the value of config.cache takes 12668 * precedence over the default cache value. 12669 * 12670 * In order to: 12671 * * cache all responses - set the default cache value to TRUE or to a cache object 12672 * * cache a specific response - set config.cache value to TRUE or to a cache object 12673 * 12674 * If caching is enabled, but neither the default cache nor config.cache are set to a cache object, 12675 * then the default `$cacheFactory("$http")` object is used. 12676 * 12677 * The default cache value can be set by updating the 12678 * {@link ng.$http#defaults `$http.defaults.cache`} property or the 12679 * {@link $httpProvider#defaults `$httpProvider.defaults.cache`} property. 12680 * 12681 * When caching is enabled, {@link ng.$http `$http`} stores the response from the server using 12682 * the relevant cache object. The next time the same request is made, the response is returned 12683 * from the cache without sending a request to the server. 12684 * 12685 * Take note that: 12686 * 12687 * * Only GET and JSONP requests are cached. 12688 * * The cache key is the request URL including search parameters; headers are not considered. 12689 * * Cached responses are returned asynchronously, in the same way as responses from the server. 12690 * * If multiple identical requests are made using the same cache, which is not yet populated, 12691 * one request will be made to the server and remaining requests will return the same response. 12692 * * A cache-control header on the response does not affect if or how responses are cached. 12693 * 12694 * 12695 * ## Interceptors 12696 * 12697 * Before you start creating interceptors, be sure to understand the 12698 * {@link ng.$q $q and deferred/promise APIs}. 12699 * 12700 * For purposes of global error handling, authentication, or any kind of synchronous or 12701 * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be 12702 * able to intercept requests before they are handed to the server and 12703 * responses before they are handed over to the application code that 12704 * initiated these requests. The interceptors leverage the {@link ng.$q 12705 * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing. 12706 * 12707 * The interceptors are service factories that are registered with the `$httpProvider` by 12708 * adding them to the `$httpProvider.interceptors` array. The factory is called and 12709 * injected with dependencies (if specified) and returns the interceptor. 12710 * 12711 * There are two kinds of interceptors (and two kinds of rejection interceptors): 12712 * 12713 * * `request`: interceptors get called with a http {@link $http#usage config} object. The function is free to 12714 * modify the `config` object or create a new one. The function needs to return the `config` 12715 * object directly, or a promise containing the `config` or a new `config` object. 12716 * * `requestError`: interceptor gets called when a previous interceptor threw an error or 12717 * resolved with a rejection. 12718 * * `response`: interceptors get called with http `response` object. The function is free to 12719 * modify the `response` object or create a new one. The function needs to return the `response` 12720 * object directly, or as a promise containing the `response` or a new `response` object. 12721 * * `responseError`: interceptor gets called when a previous interceptor threw an error or 12722 * resolved with a rejection. 12723 * 12724 * 12725 * ```js 12726 * // register the interceptor as a service 12727 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { 12728 * return { 12729 * // optional method 12730 * 'request': function(config) { 12731 * // do something on success 12732 * return config; 12733 * }, 12734 * 12735 * // optional method 12736 * 'requestError': function(rejection) { 12737 * // do something on error 12738 * if (canRecover(rejection)) { 12739 * return responseOrNewPromise 12740 * } 12741 * return $q.reject(rejection); 12742 * }, 12743 * 12744 * 12745 * 12746 * // optional method 12747 * 'response': function(response) { 12748 * // do something on success 12749 * return response; 12750 * }, 12751 * 12752 * // optional method 12753 * 'responseError': function(rejection) { 12754 * // do something on error 12755 * if (canRecover(rejection)) { 12756 * return responseOrNewPromise 12757 * } 12758 * return $q.reject(rejection); 12759 * } 12760 * }; 12761 * }); 12762 * 12763 * $httpProvider.interceptors.push('myHttpInterceptor'); 12764 * 12765 * 12766 * // alternatively, register the interceptor via an anonymous factory 12767 * $httpProvider.interceptors.push(function($q, dependency1, dependency2) { 12768 * return { 12769 * 'request': function(config) { 12770 * // same as above 12771 * }, 12772 * 12773 * 'response': function(response) { 12774 * // same as above 12775 * } 12776 * }; 12777 * }); 12778 * ``` 12779 * 12780 * ## Security Considerations 12781 * 12782 * When designing web applications, consider security threats from: 12783 * 12784 * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 12785 * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) 12786 * 12787 * Both server and the client must cooperate in order to eliminate these threats. AngularJS comes 12788 * pre-configured with strategies that address these issues, but for this to work backend server 12789 * cooperation is required. 12790 * 12791 * ### JSON Vulnerability Protection 12792 * 12793 * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 12794 * allows third party website to turn your JSON resource URL into 12795 * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To 12796 * counter this your server can prefix all JSON requests with following string `")]}',\n"`. 12797 * AngularJS will automatically strip the prefix before processing it as JSON. 12798 * 12799 * For example if your server needs to return: 12800 * ```js 12801 * ['one','two'] 12802 * ``` 12803 * 12804 * which is vulnerable to attack, your server can return: 12805 * ```js 12806 * )]}', 12807 * ['one','two'] 12808 * ``` 12809 * 12810 * AngularJS will strip the prefix, before processing the JSON. 12811 * 12812 * 12813 * ### Cross Site Request Forgery (XSRF) Protection 12814 * 12815 * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is an attack technique by 12816 * which the attacker can trick an authenticated user into unknowingly executing actions on your 12817 * website. AngularJS provides a mechanism to counter XSRF. When performing XHR requests, the 12818 * $http service reads a token from a cookie (by default, `XSRF-TOKEN`) and sets it as an HTTP 12819 * header (by default `X-XSRF-TOKEN`). Since only JavaScript that runs on your domain could read 12820 * the cookie, your server can be assured that the XHR came from JavaScript running on your 12821 * domain. 12822 * 12823 * To take advantage of this, your server needs to set a token in a JavaScript readable session 12824 * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the 12825 * server can verify that the cookie matches the `X-XSRF-TOKEN` HTTP header, and therefore be 12826 * sure that only JavaScript running on your domain could have sent the request. The token must 12827 * be unique for each user and must be verifiable by the server (to prevent the JavaScript from 12828 * making up its own tokens). We recommend that the token is a digest of your site's 12829 * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography)) 12830 * for added security. 12831 * 12832 * The header will — by default — **not** be set for cross-domain requests. This 12833 * prevents unauthorized servers (e.g. malicious or compromised 3rd-party APIs) from gaining 12834 * access to your users' XSRF tokens and exposing them to Cross Site Request Forgery. If you 12835 * want to, you can trust additional origins to also receive the XSRF token, by adding them 12836 * to {@link ng.$httpProvider#xsrfTrustedOrigins xsrfTrustedOrigins}. This might be 12837 * useful, for example, if your application, served from `example.com`, needs to access your API 12838 * at `api.example.com`. 12839 * See {@link ng.$httpProvider#xsrfTrustedOrigins $httpProvider.xsrfTrustedOrigins} for 12840 * more details. 12841 * 12842 * <div class="alert alert-danger"> 12843 * **Warning**<br /> 12844 * Only trusted origins that you have control over and make sure you understand the 12845 * implications of doing so. 12846 * </div> 12847 * 12848 * The name of the cookie and the header can be specified using the `xsrfCookieName` and 12849 * `xsrfHeaderName` properties of either `$httpProvider.defaults` at config-time, 12850 * `$http.defaults` at run-time, or the per-request config object. 12851 * 12852 * In order to prevent collisions in environments where multiple AngularJS apps share the 12853 * same domain or subdomain, we recommend that each application uses a unique cookie name. 12854 * 12855 * 12856 * @param {object} config Object describing the request to be made and how it should be 12857 * processed. The object has following properties: 12858 * 12859 * - **method** â `{string}` â HTTP method (e.g. 'GET', 'POST', etc) 12860 * - **url** â `{string|TrustedObject}` â Absolute or relative URL of the resource that is being requested; 12861 * or an object created by a call to `$sce.trustAsResourceUrl(url)`. 12862 * - **params** â `{Object.<string|Object>}` â Map of strings or objects which will be serialized 12863 * with the `paramSerializer` and appended as GET parameters. 12864 * - **data** â `{string|Object}` â Data to be sent as the request message data. 12865 * - **headers** â `{Object}` â Map of strings or functions which return strings representing 12866 * HTTP headers to send to the server. If the return value of a function is null, the 12867 * header will not be sent. Functions accept a config object as an argument. 12868 * - **eventHandlers** - `{Object}` - Event listeners to be bound to the XMLHttpRequest object. 12869 * To bind events to the XMLHttpRequest upload object, use `uploadEventHandlers`. 12870 * The handler will be called in the context of a `$apply` block. 12871 * - **uploadEventHandlers** - `{Object}` - Event listeners to be bound to the XMLHttpRequest upload 12872 * object. To bind events to the XMLHttpRequest object, use `eventHandlers`. 12873 * The handler will be called in the context of a `$apply` block. 12874 * - **xsrfHeaderName** â `{string}` â Name of HTTP header to populate with the XSRF token. 12875 * - **xsrfCookieName** â `{string}` â Name of cookie containing the XSRF token. 12876 * - **transformRequest** â 12877 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` â 12878 * transform function or an array of such functions. The transform function takes the http 12879 * request body and headers and returns its transformed (typically serialized) version. 12880 * See {@link ng.$http#overriding-the-default-transformations-per-request 12881 * Overriding the Default Transformations} 12882 * - **transformResponse** â 12883 * `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}` â 12884 * transform function or an array of such functions. The transform function takes the http 12885 * response body, headers and status and returns its transformed (typically deserialized) version. 12886 * See {@link ng.$http#overriding-the-default-transformations-per-request 12887 * Overriding the Default Transformations} 12888 * - **paramSerializer** - `{string|function(Object<string,string>):string}` - A function used to 12889 * prepare the string representation of request parameters (specified as an object). 12890 * If specified as string, it is interpreted as function registered with the 12891 * {@link $injector $injector}, which means you can create your own serializer 12892 * by registering it as a {@link auto.$provide#service service}. 12893 * The default serializer is the {@link $httpParamSerializer $httpParamSerializer}; 12894 * alternatively, you can use the {@link $httpParamSerializerJQLike $httpParamSerializerJQLike} 12895 * - **cache** â `{boolean|Object}` â A boolean value or object created with 12896 * {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of the HTTP response. 12897 * See {@link $http#caching $http Caching} for more information. 12898 * - **timeout** â `{number|Promise}` â timeout in milliseconds, or {@link ng.$q promise} 12899 * that should abort the request when resolved. 12900 * 12901 * A numerical timeout or a promise returned from {@link ng.$timeout $timeout}, will set 12902 * the `xhrStatus` in the {@link $http#$http-returns response} to "timeout", and any other 12903 * resolved promise will set it to "abort", following standard XMLHttpRequest behavior. 12904 * 12905 * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the 12906 * XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials) 12907 * for more information. 12908 * - **responseType** - `{string}` - see 12909 * [XMLHttpRequest.responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#xmlhttprequest-responsetype). 12910 * 12911 * @returns {HttpPromise} A {@link ng.$q `Promise}` that will be resolved (request success) 12912 * or rejected (request failure) with a response object. 12913 * 12914 * The response object has these properties: 12915 * 12916 * - **data** â `{string|Object}` â The response body transformed with 12917 * the transform functions. 12918 * - **status** â `{number}` â HTTP status code of the response. 12919 * - **headers** â `{function([headerName])}` â Header getter function. 12920 * - **config** â `{Object}` â The configuration object that was used 12921 * to generate the request. 12922 * - **statusText** â `{string}` â HTTP status text of the response. 12923 * - **xhrStatus** â `{string}` â Status of the XMLHttpRequest 12924 * (`complete`, `error`, `timeout` or `abort`). 12925 * 12926 * 12927 * A response status code between 200 and 299 is considered a success status 12928 * and will result in the success callback being called. Any response status 12929 * code outside of that range is considered an error status and will result 12930 * in the error callback being called. 12931 * Also, status codes less than -1 are normalized to zero. -1 usually means 12932 * the request was aborted, e.g. using a `config.timeout`. More information 12933 * about the status might be available in the `xhrStatus` property. 12934 * 12935 * Note that if the response is a redirect, XMLHttpRequest will transparently 12936 * follow it, meaning that the outcome (success or error) will be determined 12937 * by the final response status code. 12938 * 12939 * 12940 * @property {Array.<Object>} pendingRequests Array of config objects for currently pending 12941 * requests. This is primarily meant to be used for debugging purposes. 12942 * 12943 * 12944 * @example 12945<example module="httpExample" name="http-service"> 12946<file name="index.html"> 12947 <div ng-controller="FetchController"> 12948 <select ng-model="method" aria-label="Request method"> 12949 <option>GET</option> 12950 <option>JSONP</option> 12951 </select> 12952 <input type="text" ng-model="url" size="80" aria-label="URL" /> 12953 <button id="fetchbtn" ng-click="fetch()">fetch</button><br> 12954 <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button> 12955 <button id="samplejsonpbtn" 12956 ng-click="updateModel('JSONP', 12957 'https://angularjs.org/greet.php?name=Super%20Hero')"> 12958 Sample JSONP 12959 </button> 12960 <button id="invalidjsonpbtn" 12961 ng-click="updateModel('JSONP', 'https://angularjs.org/doesntexist')"> 12962 Invalid JSONP 12963 </button> 12964 <pre>http status code: {{status}}</pre> 12965 <pre>http response data: {{data}}</pre> 12966 </div> 12967</file> 12968<file name="script.js"> 12969 angular.module('httpExample', []) 12970 .config(['$sceDelegateProvider', function($sceDelegateProvider) { 12971 // We must add the JSONP endpoint that we are using to the trusted list to show that we trust it 12972 $sceDelegateProvider.trustedResourceUrlList([ 12973 'self', 12974 'https://angularjs.org/**' 12975 ]); 12976 }]) 12977 .controller('FetchController', ['$scope', '$http', '$templateCache', 12978 function($scope, $http, $templateCache) { 12979 $scope.method = 'GET'; 12980 $scope.url = 'http-hello.html'; 12981 12982 $scope.fetch = function() { 12983 $scope.code = null; 12984 $scope.response = null; 12985 12986 $http({method: $scope.method, url: $scope.url, cache: $templateCache}). 12987 then(function(response) { 12988 $scope.status = response.status; 12989 $scope.data = response.data; 12990 }, function(response) { 12991 $scope.data = response.data || 'Request failed'; 12992 $scope.status = response.status; 12993 }); 12994 }; 12995 12996 $scope.updateModel = function(method, url) { 12997 $scope.method = method; 12998 $scope.url = url; 12999 }; 13000 }]); 13001</file> 13002<file name="http-hello.html"> 13003 Hello, $http! 13004</file> 13005<file name="protractor.js" type="protractor"> 13006 var status = element(by.binding('status')); 13007 var data = element(by.binding('data')); 13008 var fetchBtn = element(by.id('fetchbtn')); 13009 var sampleGetBtn = element(by.id('samplegetbtn')); 13010 var invalidJsonpBtn = element(by.id('invalidjsonpbtn')); 13011 13012 it('should make an xhr GET request', function() { 13013 sampleGetBtn.click(); 13014 fetchBtn.click(); 13015 expect(status.getText()).toMatch('200'); 13016 expect(data.getText()).toMatch(/Hello, \$http!/); 13017 }); 13018 13019// Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185 13020// it('should make a JSONP request to angularjs.org', function() { 13021// var sampleJsonpBtn = element(by.id('samplejsonpbtn')); 13022// sampleJsonpBtn.click(); 13023// fetchBtn.click(); 13024// expect(status.getText()).toMatch('200'); 13025// expect(data.getText()).toMatch(/Super Hero!/); 13026// }); 13027 13028 it('should make JSONP request to invalid URL and invoke the error handler', 13029 function() { 13030 invalidJsonpBtn.click(); 13031 fetchBtn.click(); 13032 expect(status.getText()).toMatch('0'); 13033 expect(data.getText()).toMatch('Request failed'); 13034 }); 13035</file> 13036</example> 13037 */ 13038 function $http(requestConfig) { 13039 13040 if (!isObject(requestConfig)) { 13041 throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig); 13042 } 13043 13044 if (!isString($sce.valueOf(requestConfig.url))) { 13045 throw minErr('$http')('badreq', 'Http request configuration url must be a string or a $sce trusted object. Received: {0}', requestConfig.url); 13046 } 13047 13048 var config = extend({ 13049 method: 'get', 13050 transformRequest: defaults.transformRequest, 13051 transformResponse: defaults.transformResponse, 13052 paramSerializer: defaults.paramSerializer, 13053 jsonpCallbackParam: defaults.jsonpCallbackParam 13054 }, requestConfig); 13055 13056 config.headers = mergeHeaders(requestConfig); 13057 config.method = uppercase(config.method); 13058 config.paramSerializer = isString(config.paramSerializer) ? 13059 $injector.get(config.paramSerializer) : config.paramSerializer; 13060 13061 $browser.$$incOutstandingRequestCount('$http'); 13062 13063 var requestInterceptors = []; 13064 var responseInterceptors = []; 13065 var promise = $q.resolve(config); 13066 13067 // apply interceptors 13068 forEach(reversedInterceptors, function(interceptor) { 13069 if (interceptor.request || interceptor.requestError) { 13070 requestInterceptors.unshift(interceptor.request, interceptor.requestError); 13071 } 13072 if (interceptor.response || interceptor.responseError) { 13073 responseInterceptors.push(interceptor.response, interceptor.responseError); 13074 } 13075 }); 13076 13077 promise = chainInterceptors(promise, requestInterceptors); 13078 promise = promise.then(serverRequest); 13079 promise = chainInterceptors(promise, responseInterceptors); 13080 promise = promise.finally(completeOutstandingRequest); 13081 13082 return promise; 13083 13084 13085 function chainInterceptors(promise, interceptors) { 13086 for (var i = 0, ii = interceptors.length; i < ii;) { 13087 var thenFn = interceptors[i++]; 13088 var rejectFn = interceptors[i++]; 13089 13090 promise = promise.then(thenFn, rejectFn); 13091 } 13092 13093 interceptors.length = 0; 13094 13095 return promise; 13096 } 13097 13098 function completeOutstandingRequest() { 13099 $browser.$$completeOutstandingRequest(noop, '$http'); 13100 } 13101 13102 function executeHeaderFns(headers, config) { 13103 var headerContent, processedHeaders = {}; 13104 13105 forEach(headers, function(headerFn, header) { 13106 if (isFunction(headerFn)) { 13107 headerContent = headerFn(config); 13108 if (headerContent != null) { 13109 processedHeaders[header] = headerContent; 13110 } 13111 } else { 13112 processedHeaders[header] = headerFn; 13113 } 13114 }); 13115 13116 return processedHeaders; 13117 } 13118 13119 function mergeHeaders(config) { 13120 var defHeaders = defaults.headers, 13121 reqHeaders = extend({}, config.headers), 13122 defHeaderName, lowercaseDefHeaderName, reqHeaderName; 13123 13124 defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]); 13125 13126 // using for-in instead of forEach to avoid unnecessary iteration after header has been found 13127 defaultHeadersIteration: 13128 for (defHeaderName in defHeaders) { 13129 lowercaseDefHeaderName = lowercase(defHeaderName); 13130 13131 for (reqHeaderName in reqHeaders) { 13132 if (lowercase(reqHeaderName) === lowercaseDefHeaderName) { 13133 continue defaultHeadersIteration; 13134 } 13135 } 13136 13137 reqHeaders[defHeaderName] = defHeaders[defHeaderName]; 13138 } 13139 13140 // execute if header value is a function for merged headers 13141 return executeHeaderFns(reqHeaders, shallowCopy(config)); 13142 } 13143 13144 function serverRequest(config) { 13145 var headers = config.headers; 13146 var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest); 13147 13148 // strip content-type if data is undefined 13149 if (isUndefined(reqData)) { 13150 forEach(headers, function(value, header) { 13151 if (lowercase(header) === 'content-type') { 13152 delete headers[header]; 13153 } 13154 }); 13155 } 13156 13157 if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) { 13158 config.withCredentials = defaults.withCredentials; 13159 } 13160 13161 // send request 13162 return sendReq(config, reqData).then(transformResponse, transformResponse); 13163 } 13164 13165 function transformResponse(response) { 13166 // make a copy since the response must be cacheable 13167 var resp = extend({}, response); 13168 resp.data = transformData(response.data, response.headers, response.status, 13169 config.transformResponse); 13170 return (isSuccess(response.status)) 13171 ? resp 13172 : $q.reject(resp); 13173 } 13174 } 13175 13176 $http.pendingRequests = []; 13177 13178 /** 13179 * @ngdoc method 13180 * @name $http#get 13181 * 13182 * @description 13183 * Shortcut method to perform `GET` request. 13184 * 13185 * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested; 13186 * or an object created by a call to `$sce.trustAsResourceUrl(url)`. 13187 * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}. 13188 * @returns {HttpPromise} A Promise that will be resolved or rejected with a response object. 13189 * See {@link ng.$http#$http-returns `$http()` return value}. 13190 */ 13191 13192 /** 13193 * @ngdoc method 13194 * @name $http#delete 13195 * 13196 * @description 13197 * Shortcut method to perform `DELETE` request. 13198 * 13199 * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested; 13200 * or an object created by a call to `$sce.trustAsResourceUrl(url)`. 13201 * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}. 13202 * @returns {HttpPromise} A Promise that will be resolved or rejected with a response object. 13203 * See {@link ng.$http#$http-returns `$http()` return value}. 13204 */ 13205 13206 /** 13207 * @ngdoc method 13208 * @name $http#head 13209 * 13210 * @description 13211 * Shortcut method to perform `HEAD` request. 13212 * 13213 * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested; 13214 * or an object created by a call to `$sce.trustAsResourceUrl(url)`. 13215 * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}. 13216 * @returns {HttpPromise} A Promise that will be resolved or rejected with a response object. 13217 * See {@link ng.$http#$http-returns `$http()` return value}. 13218 */ 13219 13220 /** 13221 * @ngdoc method 13222 * @name $http#jsonp 13223 * 13224 * @description 13225 * Shortcut method to perform `JSONP` request. 13226 * 13227 * Note that, since JSONP requests are sensitive because the response is given full access to the browser, 13228 * the url must be declared, via {@link $sce} as a trusted resource URL. 13229 * You can trust a URL by adding it to the trusted resource URL list via 13230 * {@link $sceDelegateProvider#trustedResourceUrlList `$sceDelegateProvider.trustedResourceUrlList`} or 13231 * by explicitly trusting the URL via {@link $sce#trustAsResourceUrl `$sce.trustAsResourceUrl(url)`}. 13232 * 13233 * You should avoid generating the URL for the JSONP request from user provided data. 13234 * Provide additional query parameters via `params` property of the `config` parameter, rather than 13235 * modifying the URL itself. 13236 * 13237 * JSONP requests must specify a callback to be used in the response from the server. This callback 13238 * is passed as a query parameter in the request. You must specify the name of this parameter by 13239 * setting the `jsonpCallbackParam` property on the request config object. 13240 * 13241 * ``` 13242 * $http.jsonp('some/trusted/url', {jsonpCallbackParam: 'callback'}) 13243 * ``` 13244 * 13245 * You can also specify a default callback parameter name in `$http.defaults.jsonpCallbackParam`. 13246 * Initially this is set to `'callback'`. 13247 * 13248 * <div class="alert alert-danger"> 13249 * You can no longer use the `JSON_CALLBACK` string as a placeholder for specifying where the callback 13250 * parameter value should go. 13251 * </div> 13252 * 13253 * If you would like to customise where and how the callbacks are stored then try overriding 13254 * or decorating the {@link $jsonpCallbacks} service. 13255 * 13256 * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested; 13257 * or an object created by a call to `$sce.trustAsResourceUrl(url)`. 13258 * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}. 13259 * @returns {HttpPromise} A Promise that will be resolved or rejected with a response object. 13260 * See {@link ng.$http#$http-returns `$http()` return value}. 13261 */ 13262 createShortMethods('get', 'delete', 'head', 'jsonp'); 13263 13264 /** 13265 * @ngdoc method 13266 * @name $http#post 13267 * 13268 * @description 13269 * Shortcut method to perform `POST` request. 13270 * 13271 * @param {string} url Relative or absolute URL specifying the destination of the request 13272 * @param {*} data Request content 13273 * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}. 13274 * @returns {HttpPromise} A Promise that will be resolved or rejected with a response object. 13275 * See {@link ng.$http#$http-returns `$http()` return value}. 13276 */ 13277 13278 /** 13279 * @ngdoc method 13280 * @name $http#put 13281 * 13282 * @description 13283 * Shortcut method to perform `PUT` request. 13284 * 13285 * @param {string} url Relative or absolute URL specifying the destination of the request 13286 * @param {*} data Request content 13287 * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}. 13288 * @returns {HttpPromise} A Promise that will be resolved or rejected with a response object. 13289 * See {@link ng.$http#$http-returns `$http()` return value}. 13290 */ 13291 13292 /** 13293 * @ngdoc method 13294 * @name $http#patch 13295 * 13296 * @description 13297 * Shortcut method to perform `PATCH` request. 13298 * 13299 * @param {string} url Relative or absolute URL specifying the destination of the request 13300 * @param {*} data Request content 13301 * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}. 13302 * @returns {HttpPromise} A Promise that will be resolved or rejected with a response object. 13303 * See {@link ng.$http#$http-returns `$http()` return value}. 13304 */ 13305 createShortMethodsWithData('post', 'put', 'patch'); 13306 13307 /** 13308 * @ngdoc property 13309 * @name $http#defaults 13310 * 13311 * @description 13312 * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of 13313 * default headers, withCredentials as well as request and response transformations. 13314 * 13315 * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. 13316 */ 13317 $http.defaults = defaults; 13318 13319 13320 return $http; 13321 13322 13323 function createShortMethods(names) { 13324 forEach(arguments, function(name) { 13325 $http[name] = function(url, config) { 13326 return $http(extend({}, config || {}, { 13327 method: name, 13328 url: url 13329 })); 13330 }; 13331 }); 13332 } 13333 13334 13335 function createShortMethodsWithData(name) { 13336 forEach(arguments, function(name) { 13337 $http[name] = function(url, data, config) { 13338 return $http(extend({}, config || {}, { 13339 method: name, 13340 url: url, 13341 data: data 13342 })); 13343 }; 13344 }); 13345 } 13346 13347 13348 /** 13349 * Makes the request. 13350 * 13351 * !!! ACCESSES CLOSURE VARS: 13352 * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests 13353 */ 13354 function sendReq(config, reqData) { 13355 var deferred = $q.defer(), 13356 promise = deferred.promise, 13357 cache, 13358 cachedResp, 13359 reqHeaders = config.headers, 13360 isJsonp = lowercase(config.method) === 'jsonp', 13361 url = config.url; 13362 13363 if (isJsonp) { 13364 // JSONP is a pretty sensitive operation where we're allowing a script to have full access to 13365 // our DOM and JS space. So we require that the URL satisfies SCE.RESOURCE_URL. 13366 url = $sce.getTrustedResourceUrl(url); 13367 } else if (!isString(url)) { 13368 // If it is not a string then the URL must be a $sce trusted object 13369 url = $sce.valueOf(url); 13370 } 13371 13372 url = buildUrl(url, config.paramSerializer(config.params)); 13373 13374 if (isJsonp) { 13375 // Check the url and add the JSONP callback placeholder 13376 url = sanitizeJsonpCallbackParam(url, config.jsonpCallbackParam); 13377 } 13378 13379 $http.pendingRequests.push(config); 13380 promise.then(removePendingReq, removePendingReq); 13381 13382 if ((config.cache || defaults.cache) && config.cache !== false && 13383 (config.method === 'GET' || config.method === 'JSONP')) { 13384 cache = isObject(config.cache) ? config.cache 13385 : isObject(/** @type {?} */ (defaults).cache) 13386 ? /** @type {?} */ (defaults).cache 13387 : defaultCache; 13388 } 13389 13390 if (cache) { 13391 cachedResp = cache.get(url); 13392 if (isDefined(cachedResp)) { 13393 if (isPromiseLike(cachedResp)) { 13394 // cached request has already been sent, but there is no response yet 13395 cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult); 13396 } else { 13397 // serving from cache 13398 if (isArray(cachedResp)) { 13399 resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3], cachedResp[4]); 13400 } else { 13401 resolvePromise(cachedResp, 200, {}, 'OK', 'complete'); 13402 } 13403 } 13404 } else { 13405 // put the promise for the non-transformed response into cache as a placeholder 13406 cache.put(url, promise); 13407 } 13408 } 13409 13410 13411 // if we won't have the response in cache, set the xsrf headers and 13412 // send the request to the backend 13413 if (isUndefined(cachedResp)) { 13414 var xsrfValue = urlIsAllowedOrigin(config.url) 13415 ? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName] 13416 : undefined; 13417 if (xsrfValue) { 13418 reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue; 13419 } 13420 13421 $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, 13422 config.withCredentials, config.responseType, 13423 createApplyHandlers(config.eventHandlers), 13424 createApplyHandlers(config.uploadEventHandlers)); 13425 } 13426 13427 return promise; 13428 13429 function createApplyHandlers(eventHandlers) { 13430 if (eventHandlers) { 13431 var applyHandlers = {}; 13432 forEach(eventHandlers, function(eventHandler, key) { 13433 applyHandlers[key] = function(event) { 13434 if (useApplyAsync) { 13435 $rootScope.$applyAsync(callEventHandler); 13436 } else if ($rootScope.$$phase) { 13437 callEventHandler(); 13438 } else { 13439 $rootScope.$apply(callEventHandler); 13440 } 13441 13442 function callEventHandler() { 13443 eventHandler(event); 13444 } 13445 }; 13446 }); 13447 return applyHandlers; 13448 } 13449 } 13450 13451 13452 /** 13453 * Callback registered to $httpBackend(): 13454 * - caches the response if desired 13455 * - resolves the raw $http promise 13456 * - calls $apply 13457 */ 13458 function done(status, response, headersString, statusText, xhrStatus) { 13459 if (cache) { 13460 if (isSuccess(status)) { 13461 cache.put(url, [status, response, parseHeaders(headersString), statusText, xhrStatus]); 13462 } else { 13463 // remove promise from the cache 13464 cache.remove(url); 13465 } 13466 } 13467 13468 function resolveHttpPromise() { 13469 resolvePromise(response, status, headersString, statusText, xhrStatus); 13470 } 13471 13472 if (useApplyAsync) { 13473 $rootScope.$applyAsync(resolveHttpPromise); 13474 } else { 13475 resolveHttpPromise(); 13476 if (!$rootScope.$$phase) $rootScope.$apply(); 13477 } 13478 } 13479 13480 13481 /** 13482 * Resolves the raw $http promise. 13483 */ 13484 function resolvePromise(response, status, headers, statusText, xhrStatus) { 13485 //status: HTTP response status code, 0, -1 (aborted by timeout / promise) 13486 status = status >= -1 ? status : 0; 13487 13488 (isSuccess(status) ? deferred.resolve : deferred.reject)({ 13489 data: response, 13490 status: status, 13491 headers: headersGetter(headers), 13492 config: config, 13493 statusText: statusText, 13494 xhrStatus: xhrStatus 13495 }); 13496 } 13497 13498 function resolvePromiseWithResult(result) { 13499 resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText, result.xhrStatus); 13500 } 13501 13502 function removePendingReq() { 13503 var idx = $http.pendingRequests.indexOf(config); 13504 if (idx !== -1) $http.pendingRequests.splice(idx, 1); 13505 } 13506 } 13507 13508 13509 function buildUrl(url, serializedParams) { 13510 if (serializedParams.length > 0) { 13511 url += ((url.indexOf('?') === -1) ? '?' : '&') + serializedParams; 13512 } 13513 return url; 13514 } 13515 13516 function sanitizeJsonpCallbackParam(url, cbKey) { 13517 var parts = url.split('?'); 13518 if (parts.length > 2) { 13519 // Throw if the url contains more than one `?` query indicator 13520 throw $httpMinErr('badjsonp', 'Illegal use more than one "?", in url, "{1}"', url); 13521 } 13522 var params = parseKeyValue(parts[1]); 13523 forEach(params, function(value, key) { 13524 if (value === 'JSON_CALLBACK') { 13525 // Throw if the url already contains a reference to JSON_CALLBACK 13526 throw $httpMinErr('badjsonp', 'Illegal use of JSON_CALLBACK in url, "{0}"', url); 13527 } 13528 if (key === cbKey) { 13529 // Throw if the callback param was already provided 13530 throw $httpMinErr('badjsonp', 'Illegal use of callback param, "{0}", in url, "{1}"', cbKey, url); 13531 } 13532 }); 13533 13534 // Add in the JSON_CALLBACK callback param value 13535 url += ((url.indexOf('?') === -1) ? '?' : '&') + cbKey + '=JSON_CALLBACK'; 13536 13537 return url; 13538 } 13539 }]; 13540} 13541 13542/** 13543 * @ngdoc service 13544 * @name $xhrFactory 13545 * @this 13546 * 13547 * @description 13548 * Factory function used to create XMLHttpRequest objects. 13549 * 13550 * Replace or decorate this service to create your own custom XMLHttpRequest objects. 13551 * 13552 * ``` 13553 * angular.module('myApp', []) 13554 * .factory('$xhrFactory', function() { 13555 * return function createXhr(method, url) { 13556 * return new window.XMLHttpRequest({mozSystem: true}); 13557 * }; 13558 * }); 13559 * ``` 13560 * 13561 * @param {string} method HTTP method of the request (GET, POST, PUT, ..) 13562 * @param {string} url URL of the request. 13563 */ 13564function $xhrFactoryProvider() { 13565 this.$get = function() { 13566 return function createXhr() { 13567 return new window.XMLHttpRequest(); 13568 }; 13569 }; 13570} 13571 13572/** 13573 * @ngdoc service 13574 * @name $httpBackend 13575 * @requires $jsonpCallbacks 13576 * @requires $document 13577 * @requires $xhrFactory 13578 * @this 13579 * 13580 * @description 13581 * HTTP backend used by the {@link ng.$http service} that delegates to 13582 * XMLHttpRequest object or JSONP and deals with browser incompatibilities. 13583 * 13584 * You should never need to use this service directly, instead use the higher-level abstractions: 13585 * {@link ng.$http $http} or {@link ngResource.$resource $resource}. 13586 * 13587 * During testing this implementation is swapped with {@link ngMock.$httpBackend mock 13588 * $httpBackend} which can be trained with responses. 13589 */ 13590function $HttpBackendProvider() { 13591 this.$get = ['$browser', '$jsonpCallbacks', '$document', '$xhrFactory', function($browser, $jsonpCallbacks, $document, $xhrFactory) { 13592 return createHttpBackend($browser, $xhrFactory, $browser.defer, $jsonpCallbacks, $document[0]); 13593 }]; 13594} 13595 13596function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) { 13597 // TODO(vojta): fix the signature 13598 return function(method, url, post, callback, headers, timeout, withCredentials, responseType, eventHandlers, uploadEventHandlers) { 13599 url = url || $browser.url(); 13600 13601 if (lowercase(method) === 'jsonp') { 13602 var callbackPath = callbacks.createCallback(url); 13603 var jsonpDone = jsonpReq(url, callbackPath, function(status, text) { 13604 // jsonpReq only ever sets status to 200 (OK), 404 (ERROR) or -1 (WAITING) 13605 var response = (status === 200) && callbacks.getResponse(callbackPath); 13606 completeRequest(callback, status, response, '', text, 'complete'); 13607 callbacks.removeCallback(callbackPath); 13608 }); 13609 } else { 13610 13611 var xhr = createXhr(method, url); 13612 var abortedByTimeout = false; 13613 13614 xhr.open(method, url, true); 13615 forEach(headers, function(value, key) { 13616 if (isDefined(value)) { 13617 xhr.setRequestHeader(key, value); 13618 } 13619 }); 13620 13621 xhr.onload = function requestLoaded() { 13622 var statusText = xhr.statusText || ''; 13623 13624 // responseText is the old-school way of retrieving response (supported by IE9) 13625 // response/responseType properties were introduced in XHR Level2 spec (supported by IE10) 13626 var response = ('response' in xhr) ? xhr.response : xhr.responseText; 13627 13628 // normalize IE9 bug (http://bugs.jquery.com/ticket/1450) 13629 var status = xhr.status === 1223 ? 204 : xhr.status; 13630 13631 // fix status code when it is 0 (0 status is undocumented). 13632 // Occurs when accessing file resources or on Android 4.1 stock browser 13633 // while retrieving files from application cache. 13634 if (status === 0) { 13635 status = response ? 200 : urlResolve(url).protocol === 'file' ? 404 : 0; 13636 } 13637 13638 completeRequest(callback, 13639 status, 13640 response, 13641 xhr.getAllResponseHeaders(), 13642 statusText, 13643 'complete'); 13644 }; 13645 13646 var requestError = function() { 13647 // The response is always empty 13648 // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error 13649 completeRequest(callback, -1, null, null, '', 'error'); 13650 }; 13651 13652 var requestAborted = function() { 13653 completeRequest(callback, -1, null, null, '', abortedByTimeout ? 'timeout' : 'abort'); 13654 }; 13655 13656 var requestTimeout = function() { 13657 // The response is always empty 13658 // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error 13659 completeRequest(callback, -1, null, null, '', 'timeout'); 13660 }; 13661 13662 xhr.onerror = requestError; 13663 xhr.ontimeout = requestTimeout; 13664 xhr.onabort = requestAborted; 13665 13666 forEach(eventHandlers, function(value, key) { 13667 xhr.addEventListener(key, value); 13668 }); 13669 13670 forEach(uploadEventHandlers, function(value, key) { 13671 xhr.upload.addEventListener(key, value); 13672 }); 13673 13674 if (withCredentials) { 13675 xhr.withCredentials = true; 13676 } 13677 13678 if (responseType) { 13679 try { 13680 xhr.responseType = responseType; 13681 } catch (e) { 13682 // WebKit added support for the json responseType value on 09/03/2013 13683 // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are 13684 // known to throw when setting the value "json" as the response type. Other older 13685 // browsers implementing the responseType 13686 // 13687 // The json response type can be ignored if not supported, because JSON payloads are 13688 // parsed on the client-side regardless. 13689 if (responseType !== 'json') { 13690 throw e; 13691 } 13692 } 13693 } 13694 13695 xhr.send(isUndefined(post) ? null : post); 13696 } 13697 13698 // Since we are using xhr.abort() when a request times out, we have to set a flag that 13699 // indicates to requestAborted if the request timed out or was aborted. 13700 // 13701 // http.timeout = numerical timeout timeout 13702 // http.timeout = $timeout timeout 13703 // http.timeout = promise abort 13704 // xhr.abort() abort (The xhr object is normally inaccessible, but 13705 // can be exposed with the xhrFactory) 13706 if (timeout > 0) { 13707 var timeoutId = $browserDefer(function() { 13708 timeoutRequest('timeout'); 13709 }, timeout); 13710 } else if (isPromiseLike(timeout)) { 13711 timeout.then(function() { 13712 timeoutRequest(isDefined(timeout.$$timeoutId) ? 'timeout' : 'abort'); 13713 }); 13714 } 13715 13716 function timeoutRequest(reason) { 13717 abortedByTimeout = reason === 'timeout'; 13718 if (jsonpDone) { 13719 jsonpDone(); 13720 } 13721 if (xhr) { 13722 xhr.abort(); 13723 } 13724 } 13725 13726 function completeRequest(callback, status, response, headersString, statusText, xhrStatus) { 13727 // cancel timeout and subsequent timeout promise resolution 13728 if (isDefined(timeoutId)) { 13729 $browserDefer.cancel(timeoutId); 13730 } 13731 jsonpDone = xhr = null; 13732 13733 callback(status, response, headersString, statusText, xhrStatus); 13734 } 13735 }; 13736 13737 function jsonpReq(url, callbackPath, done) { 13738 url = url.replace('JSON_CALLBACK', callbackPath); 13739 // we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.: 13740 // - fetches local scripts via XHR and evals them 13741 // - adds and immediately removes script elements from the document 13742 var script = rawDocument.createElement('script'), callback = null; 13743 script.type = 'text/javascript'; 13744 script.src = url; 13745 script.async = true; 13746 13747 callback = function(event) { 13748 script.removeEventListener('load', callback); 13749 script.removeEventListener('error', callback); 13750 rawDocument.body.removeChild(script); 13751 script = null; 13752 var status = -1; 13753 var text = 'unknown'; 13754 13755 if (event) { 13756 if (event.type === 'load' && !callbacks.wasCalled(callbackPath)) { 13757 event = { type: 'error' }; 13758 } 13759 text = event.type; 13760 status = event.type === 'error' ? 404 : 200; 13761 } 13762 13763 if (done) { 13764 done(status, text); 13765 } 13766 }; 13767 13768 script.addEventListener('load', callback); 13769 script.addEventListener('error', callback); 13770 rawDocument.body.appendChild(script); 13771 return callback; 13772 } 13773} 13774 13775var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate'); 13776$interpolateMinErr.throwNoconcat = function(text) { 13777 throw $interpolateMinErr('noconcat', 13778 'Error while interpolating: {0}\nStrict Contextual Escaping disallows ' + 13779 'interpolations that concatenate multiple expressions when a trusted value is ' + 13780 'required. See http://docs.angularjs.org/api/ng.$sce', text); 13781}; 13782 13783$interpolateMinErr.interr = function(text, err) { 13784 return $interpolateMinErr('interr', 'Can\'t interpolate: {0}\n{1}', text, err.toString()); 13785}; 13786 13787/** 13788 * @ngdoc provider 13789 * @name $interpolateProvider 13790 * @this 13791 * 13792 * @description 13793 * 13794 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. 13795 * 13796 * <div class="alert alert-danger"> 13797 * This feature is sometimes used to mix different markup languages, e.g. to wrap an AngularJS 13798 * template within a Python Jinja template (or any other template language). Mixing templating 13799 * languages is **very dangerous**. The embedding template language will not safely escape AngularJS 13800 * expressions, so any user-controlled values in the template will cause Cross Site Scripting (XSS) 13801 * security bugs! 13802 * </div> 13803 * 13804 * @example 13805<example name="custom-interpolation-markup" module="customInterpolationApp"> 13806<file name="index.html"> 13807<script> 13808 var customInterpolationApp = angular.module('customInterpolationApp', []); 13809 13810 customInterpolationApp.config(function($interpolateProvider) { 13811 $interpolateProvider.startSymbol('//'); 13812 $interpolateProvider.endSymbol('//'); 13813 }); 13814 13815 13816 customInterpolationApp.controller('DemoController', function() { 13817 this.label = "This binding is brought you by // interpolation symbols."; 13818 }); 13819</script> 13820<div ng-controller="DemoController as demo"> 13821 //demo.label// 13822</div> 13823</file> 13824<file name="protractor.js" type="protractor"> 13825 it('should interpolate binding with custom symbols', function() { 13826 expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.'); 13827 }); 13828</file> 13829</example> 13830 */ 13831function $InterpolateProvider() { 13832 var startSymbol = '{{'; 13833 var endSymbol = '}}'; 13834 13835 /** 13836 * @ngdoc method 13837 * @name $interpolateProvider#startSymbol 13838 * @description 13839 * Symbol to denote start of expression in the interpolated string. Defaults to `{{`. 13840 * 13841 * @param {string=} value new value to set the starting symbol to. 13842 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 13843 */ 13844 this.startSymbol = function(value) { 13845 if (value) { 13846 startSymbol = value; 13847 return this; 13848 } 13849 return startSymbol; 13850 }; 13851 13852 /** 13853 * @ngdoc method 13854 * @name $interpolateProvider#endSymbol 13855 * @description 13856 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 13857 * 13858 * @param {string=} value new value to set the ending symbol to. 13859 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 13860 */ 13861 this.endSymbol = function(value) { 13862 if (value) { 13863 endSymbol = value; 13864 return this; 13865 } 13866 return endSymbol; 13867 }; 13868 13869 13870 this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) { 13871 var startSymbolLength = startSymbol.length, 13872 endSymbolLength = endSymbol.length, 13873 escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'), 13874 escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g'); 13875 13876 function escape(ch) { 13877 return '\\\\\\' + ch; 13878 } 13879 13880 function unescapeText(text) { 13881 return text.replace(escapedStartRegexp, startSymbol). 13882 replace(escapedEndRegexp, endSymbol); 13883 } 13884 13885 // TODO: this is the same as the constantWatchDelegate in parse.js 13886 function constantWatchDelegate(scope, listener, objectEquality, constantInterp) { 13887 var unwatch = scope.$watch(function constantInterpolateWatch(scope) { 13888 unwatch(); 13889 return constantInterp(scope); 13890 }, listener, objectEquality); 13891 return unwatch; 13892 } 13893 13894 /** 13895 * @ngdoc service 13896 * @name $interpolate 13897 * @kind function 13898 * 13899 * @requires $parse 13900 * @requires $sce 13901 * 13902 * @description 13903 * 13904 * Compiles a string with markup into an interpolation function. This service is used by the 13905 * HTML {@link ng.$compile $compile} service for data binding. See 13906 * {@link ng.$interpolateProvider $interpolateProvider} for configuring the 13907 * interpolation markup. 13908 * 13909 * 13910 * ```js 13911 * var $interpolate = ...; // injected 13912 * var exp = $interpolate('Hello {{name | uppercase}}!'); 13913 * expect(exp({name:'AngularJS'})).toEqual('Hello ANGULARJS!'); 13914 * ``` 13915 * 13916 * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is 13917 * `true`, the interpolation function will return `undefined` unless all embedded expressions 13918 * evaluate to a value other than `undefined`. 13919 * 13920 * ```js 13921 * var $interpolate = ...; // injected 13922 * var context = {greeting: 'Hello', name: undefined }; 13923 * 13924 * // default "forgiving" mode 13925 * var exp = $interpolate('{{greeting}} {{name}}!'); 13926 * expect(exp(context)).toEqual('Hello !'); 13927 * 13928 * // "allOrNothing" mode 13929 * exp = $interpolate('{{greeting}} {{name}}!', false, null, true); 13930 * expect(exp(context)).toBeUndefined(); 13931 * context.name = 'AngularJS'; 13932 * expect(exp(context)).toEqual('Hello AngularJS!'); 13933 * ``` 13934 * 13935 * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior. 13936 * 13937 * #### Escaped Interpolation 13938 * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers 13939 * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash). 13940 * It will be rendered as a regular start/end marker, and will not be interpreted as an expression 13941 * or binding. 13942 * 13943 * This enables web-servers to prevent script injection attacks and defacing attacks, to some 13944 * degree, while also enabling code examples to work without relying on the 13945 * {@link ng.directive:ngNonBindable ngNonBindable} directive. 13946 * 13947 * **For security purposes, it is strongly encouraged that web servers escape user-supplied data, 13948 * replacing angle brackets (<, >) with &lt; and &gt; respectively, and replacing all 13949 * interpolation start/end markers with their escaped counterparts.** 13950 * 13951 * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered 13952 * output when the $interpolate service processes the text. So, for HTML elements interpolated 13953 * by {@link ng.$compile $compile}, or otherwise interpolated with the `mustHaveExpression` parameter 13954 * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such, 13955 * this is typically useful only when user-data is used in rendering a template from the server, or 13956 * when otherwise untrusted data is used by a directive. 13957 * 13958 * <example name="interpolation"> 13959 * <file name="index.html"> 13960 * <div ng-init="username='A user'"> 13961 * <p ng-init="apptitle='Escaping demo'">{{apptitle}}: \{\{ username = "defaced value"; \}\} 13962 * </p> 13963 * <p><strong>{{username}}</strong> attempts to inject code which will deface the 13964 * application, but fails to accomplish their task, because the server has correctly 13965 * escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash) 13966 * characters.</p> 13967 * <p>Instead, the result of the attempted script injection is visible, and can be removed 13968 * from the database by an administrator.</p> 13969 * </div> 13970 * </file> 13971 * </example> 13972 * 13973 * @knownIssue 13974 * It is currently not possible for an interpolated expression to contain the interpolation end 13975 * symbol. For example, `{{ '}}' }}` will be incorrectly interpreted as `{{ ' }}` + `' }}`, i.e. 13976 * an interpolated expression consisting of a single-quote (`'`) and the `' }}` string. 13977 * 13978 * @knownIssue 13979 * All directives and components must use the standard `{{` `}}` interpolation symbols 13980 * in their templates. If you change the application interpolation symbols the {@link $compile} 13981 * service will attempt to denormalize the standard symbols to the custom symbols. 13982 * The denormalization process is not clever enough to know not to replace instances of the standard 13983 * symbols where they would not normally be treated as interpolation symbols. For example in the following 13984 * code snippet the closing braces of the literal object will get incorrectly denormalized: 13985 * 13986 * ``` 13987 * <div data-context='{"context":{"id":3,"type":"page"}}"> 13988 * ``` 13989 * 13990 * The workaround is to ensure that such instances are separated by whitespace: 13991 * ``` 13992 * <div data-context='{"context":{"id":3,"type":"page"} }"> 13993 * ``` 13994 * 13995 * See https://github.com/angular/angular.js/pull/14610#issuecomment-219401099 for more information. 13996 * 13997 * @param {string} text The text with markup to interpolate. 13998 * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have 13999 * embedded expression in order to return an interpolation function. Strings with no 14000 * embedded expression will return null for the interpolation function. 14001 * @param {string=} trustedContext when provided, the returned function passes the interpolated 14002 * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult, 14003 * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that 14004 * provides Strict Contextual Escaping for details. 14005 * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined 14006 * unless all embedded expressions evaluate to a value other than `undefined`. 14007 * @returns {function(context)} an interpolation function which is used to compute the 14008 * interpolated string. The function has these parameters: 14009 * 14010 * - `context`: evaluation context for all expressions embedded in the interpolated text 14011 */ 14012 function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) { 14013 var contextAllowsConcatenation = trustedContext === $sce.URL || trustedContext === $sce.MEDIA_URL; 14014 14015 // Provide a quick exit and simplified result function for text with no interpolation 14016 if (!text.length || text.indexOf(startSymbol) === -1) { 14017 if (mustHaveExpression) return; 14018 14019 var unescapedText = unescapeText(text); 14020 if (contextAllowsConcatenation) { 14021 unescapedText = $sce.getTrusted(trustedContext, unescapedText); 14022 } 14023 var constantInterp = valueFn(unescapedText); 14024 constantInterp.exp = text; 14025 constantInterp.expressions = []; 14026 constantInterp.$$watchDelegate = constantWatchDelegate; 14027 14028 return constantInterp; 14029 } 14030 14031 allOrNothing = !!allOrNothing; 14032 var startIndex, 14033 endIndex, 14034 index = 0, 14035 expressions = [], 14036 parseFns, 14037 textLength = text.length, 14038 exp, 14039 concat = [], 14040 expressionPositions = [], 14041 singleExpression; 14042 14043 14044 while (index < textLength) { 14045 if (((startIndex = text.indexOf(startSymbol, index)) !== -1) && 14046 ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) !== -1)) { 14047 if (index !== startIndex) { 14048 concat.push(unescapeText(text.substring(index, startIndex))); 14049 } 14050 exp = text.substring(startIndex + startSymbolLength, endIndex); 14051 expressions.push(exp); 14052 index = endIndex + endSymbolLength; 14053 expressionPositions.push(concat.length); 14054 concat.push(''); // Placeholder that will get replaced with the evaluated expression. 14055 } else { 14056 // we did not find an interpolation, so we have to add the remainder to the separators array 14057 if (index !== textLength) { 14058 concat.push(unescapeText(text.substring(index))); 14059 } 14060 break; 14061 } 14062 } 14063 14064 singleExpression = concat.length === 1 && expressionPositions.length === 1; 14065 // Intercept expression if we need to stringify concatenated inputs, which may be SCE trusted 14066 // objects rather than simple strings 14067 // (we don't modify the expression if the input consists of only a single trusted input) 14068 var interceptor = contextAllowsConcatenation && singleExpression ? undefined : parseStringifyInterceptor; 14069 parseFns = expressions.map(function(exp) { return $parse(exp, interceptor); }); 14070 14071 // Concatenating expressions makes it hard to reason about whether some combination of 14072 // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a 14073 // single expression be used for some $sce-managed secure contexts (RESOURCE_URLs mostly), 14074 // we ensure that the value that's used is assigned or constructed by some JS code somewhere 14075 // that is more testable or make it obvious that you bound the value to some user controlled 14076 // value. This helps reduce the load when auditing for XSS issues. 14077 14078 // Note that URL and MEDIA_URL $sce contexts do not need this, since `$sce` can sanitize the values 14079 // passed to it. In that case, `$sce.getTrusted` will be called on either the single expression 14080 // or on the overall concatenated string (losing trusted types used in the mix, by design). 14081 // Both these methods will sanitize plain strings. Also, HTML could be included, but since it's 14082 // only used in srcdoc attributes, this would not be very useful. 14083 14084 if (!mustHaveExpression || expressions.length) { 14085 var compute = function(values) { 14086 for (var i = 0, ii = expressions.length; i < ii; i++) { 14087 if (allOrNothing && isUndefined(values[i])) return; 14088 concat[expressionPositions[i]] = values[i]; 14089 } 14090 14091 if (contextAllowsConcatenation) { 14092 // If `singleExpression` then `concat[0]` might be a "trusted" value or `null`, rather than a string 14093 return $sce.getTrusted(trustedContext, singleExpression ? concat[0] : concat.join('')); 14094 } else if (trustedContext && concat.length > 1) { 14095 // This context does not allow more than one part, e.g. expr + string or exp + exp. 14096 $interpolateMinErr.throwNoconcat(text); 14097 } 14098 // In an unprivileged context or only one part: just concatenate and return. 14099 return concat.join(''); 14100 }; 14101 14102 return extend(function interpolationFn(context) { 14103 var i = 0; 14104 var ii = expressions.length; 14105 var values = new Array(ii); 14106 14107 try { 14108 for (; i < ii; i++) { 14109 values[i] = parseFns[i](context); 14110 } 14111 14112 return compute(values); 14113 } catch (err) { 14114 $exceptionHandler($interpolateMinErr.interr(text, err)); 14115 } 14116 14117 }, { 14118 // all of these properties are undocumented for now 14119 exp: text, //just for compatibility with regular watchers created via $watch 14120 expressions: expressions, 14121 $$watchDelegate: function(scope, listener) { 14122 var lastValue; 14123 return scope.$watchGroup(parseFns, /** @this */ function interpolateFnWatcher(values, oldValues) { 14124 var currValue = compute(values); 14125 listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope); 14126 lastValue = currValue; 14127 }); 14128 } 14129 }); 14130 } 14131 14132 function parseStringifyInterceptor(value) { 14133 try { 14134 // In concatenable contexts, getTrusted comes at the end, to avoid sanitizing individual 14135 // parts of a full URL. We don't care about losing the trustedness here. 14136 // In non-concatenable contexts, where there is only one expression, this interceptor is 14137 // not applied to the expression. 14138 value = (trustedContext && !contextAllowsConcatenation) ? 14139 $sce.getTrusted(trustedContext, value) : 14140 $sce.valueOf(value); 14141 return allOrNothing && !isDefined(value) ? value : stringify(value); 14142 } catch (err) { 14143 $exceptionHandler($interpolateMinErr.interr(text, err)); 14144 } 14145 } 14146 } 14147 14148 14149 /** 14150 * @ngdoc method 14151 * @name $interpolate#startSymbol 14152 * @description 14153 * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`. 14154 * 14155 * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change 14156 * the symbol. 14157 * 14158 * @returns {string} start symbol. 14159 */ 14160 $interpolate.startSymbol = function() { 14161 return startSymbol; 14162 }; 14163 14164 14165 /** 14166 * @ngdoc method 14167 * @name $interpolate#endSymbol 14168 * @description 14169 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 14170 * 14171 * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change 14172 * the symbol. 14173 * 14174 * @returns {string} end symbol. 14175 */ 14176 $interpolate.endSymbol = function() { 14177 return endSymbol; 14178 }; 14179 14180 return $interpolate; 14181 }]; 14182} 14183 14184var $intervalMinErr = minErr('$interval'); 14185 14186/** @this */ 14187function $IntervalProvider() { 14188 this.$get = ['$$intervalFactory', '$window', 14189 function($$intervalFactory, $window) { 14190 var intervals = {}; 14191 var setIntervalFn = function(tick, delay, deferred) { 14192 var id = $window.setInterval(tick, delay); 14193 intervals[id] = deferred; 14194 return id; 14195 }; 14196 var clearIntervalFn = function(id) { 14197 $window.clearInterval(id); 14198 delete intervals[id]; 14199 }; 14200 14201 /** 14202 * @ngdoc service 14203 * @name $interval 14204 * 14205 * @description 14206 * AngularJS's wrapper for `window.setInterval`. The `fn` function is executed every `delay` 14207 * milliseconds. 14208 * 14209 * The return value of registering an interval function is a promise. This promise will be 14210 * notified upon each tick of the interval, and will be resolved after `count` iterations, or 14211 * run indefinitely if `count` is not defined. The value of the notification will be the 14212 * number of iterations that have run. 14213 * To cancel an interval, call `$interval.cancel(promise)`. 14214 * 14215 * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to 14216 * move forward by `millis` milliseconds and trigger any functions scheduled to run in that 14217 * time. 14218 * 14219 * <div class="alert alert-warning"> 14220 * **Note**: Intervals created by this service must be explicitly destroyed when you are finished 14221 * with them. In particular they are not automatically destroyed when a controller's scope or a 14222 * directive's element are destroyed. 14223 * You should take this into consideration and make sure to always cancel the interval at the 14224 * appropriate moment. See the example below for more details on how and when to do this. 14225 * </div> 14226 * 14227 * @param {function()} fn A function that should be called repeatedly. If no additional arguments 14228 * are passed (see below), the function is called with the current iteration count. 14229 * @param {number} delay Number of milliseconds between each function call. 14230 * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat 14231 * indefinitely. 14232 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 14233 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 14234 * @param {...*=} Pass additional parameters to the executed function. 14235 * @returns {promise} A promise which will be notified on each iteration. It will resolve once all iterations of the interval complete. 14236 * 14237 * @example 14238 * <example module="intervalExample" name="interval-service"> 14239 * <file name="index.html"> 14240 * <script> 14241 * angular.module('intervalExample', []) 14242 * .controller('ExampleController', ['$scope', '$interval', 14243 * function($scope, $interval) { 14244 * $scope.format = 'M/d/yy h:mm:ss a'; 14245 * $scope.blood_1 = 100; 14246 * $scope.blood_2 = 120; 14247 * 14248 * var stop; 14249 * $scope.fight = function() { 14250 * // Don't start a new fight if we are already fighting 14251 * if ( angular.isDefined(stop) ) return; 14252 * 14253 * stop = $interval(function() { 14254 * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) { 14255 * $scope.blood_1 = $scope.blood_1 - 3; 14256 * $scope.blood_2 = $scope.blood_2 - 4; 14257 * } else { 14258 * $scope.stopFight(); 14259 * } 14260 * }, 100); 14261 * }; 14262 * 14263 * $scope.stopFight = function() { 14264 * if (angular.isDefined(stop)) { 14265 * $interval.cancel(stop); 14266 * stop = undefined; 14267 * } 14268 * }; 14269 * 14270 * $scope.resetFight = function() { 14271 * $scope.blood_1 = 100; 14272 * $scope.blood_2 = 120; 14273 * }; 14274 * 14275 * $scope.$on('$destroy', function() { 14276 * // Make sure that the interval is destroyed too 14277 * $scope.stopFight(); 14278 * }); 14279 * }]) 14280 * // Register the 'myCurrentTime' directive factory method. 14281 * // We inject $interval and dateFilter service since the factory method is DI. 14282 * .directive('myCurrentTime', ['$interval', 'dateFilter', 14283 * function($interval, dateFilter) { 14284 * // return the directive link function. (compile function not needed) 14285 * return function(scope, element, attrs) { 14286 * var format, // date format 14287 * stopTime; // so that we can cancel the time updates 14288 * 14289 * // used to update the UI 14290 * function updateTime() { 14291 * element.text(dateFilter(new Date(), format)); 14292 * } 14293 * 14294 * // watch the expression, and update the UI on change. 14295 * scope.$watch(attrs.myCurrentTime, function(value) { 14296 * format = value; 14297 * updateTime(); 14298 * }); 14299 * 14300 * stopTime = $interval(updateTime, 1000); 14301 * 14302 * // listen on DOM destroy (removal) event, and cancel the next UI update 14303 * // to prevent updating time after the DOM element was removed. 14304 * element.on('$destroy', function() { 14305 * $interval.cancel(stopTime); 14306 * }); 14307 * } 14308 * }]); 14309 * </script> 14310 * 14311 * <div> 14312 * <div ng-controller="ExampleController"> 14313 * <label>Date format: <input ng-model="format"></label> <hr/> 14314 * Current time is: <span my-current-time="format"></span> 14315 * <hr/> 14316 * Blood 1 : <font color='red'>{{blood_1}}</font> 14317 * Blood 2 : <font color='red'>{{blood_2}}</font> 14318 * <button type="button" data-ng-click="fight()">Fight</button> 14319 * <button type="button" data-ng-click="stopFight()">StopFight</button> 14320 * <button type="button" data-ng-click="resetFight()">resetFight</button> 14321 * </div> 14322 * </div> 14323 * 14324 * </file> 14325 * </example> 14326 */ 14327 var interval = $$intervalFactory(setIntervalFn, clearIntervalFn); 14328 14329 /** 14330 * @ngdoc method 14331 * @name $interval#cancel 14332 * 14333 * @description 14334 * Cancels a task associated with the `promise`. 14335 * 14336 * @param {Promise=} promise returned by the `$interval` function. 14337 * @returns {boolean} Returns `true` if the task was successfully canceled. 14338 */ 14339 interval.cancel = function(promise) { 14340 if (!promise) return false; 14341 14342 if (!promise.hasOwnProperty('$$intervalId')) { 14343 throw $intervalMinErr('badprom', 14344 '`$interval.cancel()` called with a promise that was not generated by `$interval()`.'); 14345 } 14346 14347 if (!intervals.hasOwnProperty(promise.$$intervalId)) return false; 14348 14349 var id = promise.$$intervalId; 14350 var deferred = intervals[id]; 14351 14352 // Interval cancels should not report an unhandled promise. 14353 markQExceptionHandled(deferred.promise); 14354 deferred.reject('canceled'); 14355 clearIntervalFn(id); 14356 14357 return true; 14358 }; 14359 14360 return interval; 14361 }]; 14362} 14363 14364/** @this */ 14365function $$IntervalFactoryProvider() { 14366 this.$get = ['$browser', '$q', '$$q', '$rootScope', 14367 function($browser, $q, $$q, $rootScope) { 14368 return function intervalFactory(setIntervalFn, clearIntervalFn) { 14369 return function intervalFn(fn, delay, count, invokeApply) { 14370 var hasParams = arguments.length > 4, 14371 args = hasParams ? sliceArgs(arguments, 4) : [], 14372 iteration = 0, 14373 skipApply = isDefined(invokeApply) && !invokeApply, 14374 deferred = (skipApply ? $$q : $q).defer(), 14375 promise = deferred.promise; 14376 14377 count = isDefined(count) ? count : 0; 14378 14379 function callback() { 14380 if (!hasParams) { 14381 fn(iteration); 14382 } else { 14383 fn.apply(null, args); 14384 } 14385 } 14386 14387 function tick() { 14388 if (skipApply) { 14389 $browser.defer(callback); 14390 } else { 14391 $rootScope.$evalAsync(callback); 14392 } 14393 deferred.notify(iteration++); 14394 14395 if (count > 0 && iteration >= count) { 14396 deferred.resolve(iteration); 14397 clearIntervalFn(promise.$$intervalId); 14398 } 14399 14400 if (!skipApply) $rootScope.$apply(); 14401 } 14402 14403 promise.$$intervalId = setIntervalFn(tick, delay, deferred, skipApply); 14404 14405 return promise; 14406 }; 14407 }; 14408 }]; 14409} 14410 14411/** 14412 * @ngdoc service 14413 * @name $jsonpCallbacks 14414 * @requires $window 14415 * @description 14416 * This service handles the lifecycle of callbacks to handle JSONP requests. 14417 * Override this service if you wish to customise where the callbacks are stored and 14418 * how they vary compared to the requested url. 14419 */ 14420var $jsonpCallbacksProvider = /** @this */ function() { 14421 this.$get = function() { 14422 var callbacks = angular.callbacks; 14423 var callbackMap = {}; 14424 14425 function createCallback(callbackId) { 14426 var callback = function(data) { 14427 callback.data = data; 14428 callback.called = true; 14429 }; 14430 callback.id = callbackId; 14431 return callback; 14432 } 14433 14434 return { 14435 /** 14436 * @ngdoc method 14437 * @name $jsonpCallbacks#createCallback 14438 * @param {string} url the url of the JSONP request 14439 * @returns {string} the callback path to send to the server as part of the JSONP request 14440 * @description 14441 * {@link $httpBackend} calls this method to create a callback and get hold of the path to the callback 14442 * to pass to the server, which will be used to call the callback with its payload in the JSONP response. 14443 */ 14444 createCallback: function(url) { 14445 var callbackId = '_' + (callbacks.$$counter++).toString(36); 14446 var callbackPath = 'angular.callbacks.' + callbackId; 14447 var callback = createCallback(callbackId); 14448 callbackMap[callbackPath] = callbacks[callbackId] = callback; 14449 return callbackPath; 14450 }, 14451 /** 14452 * @ngdoc method 14453 * @name $jsonpCallbacks#wasCalled 14454 * @param {string} callbackPath the path to the callback that was sent in the JSONP request 14455 * @returns {boolean} whether the callback has been called, as a result of the JSONP response 14456 * @description 14457 * {@link $httpBackend} calls this method to find out whether the JSONP response actually called the 14458 * callback that was passed in the request. 14459 */ 14460 wasCalled: function(callbackPath) { 14461 return callbackMap[callbackPath].called; 14462 }, 14463 /** 14464 * @ngdoc method 14465 * @name $jsonpCallbacks#getResponse 14466 * @param {string} callbackPath the path to the callback that was sent in the JSONP request 14467 * @returns {*} the data received from the response via the registered callback 14468 * @description 14469 * {@link $httpBackend} calls this method to get hold of the data that was provided to the callback 14470 * in the JSONP response. 14471 */ 14472 getResponse: function(callbackPath) { 14473 return callbackMap[callbackPath].data; 14474 }, 14475 /** 14476 * @ngdoc method 14477 * @name $jsonpCallbacks#removeCallback 14478 * @param {string} callbackPath the path to the callback that was sent in the JSONP request 14479 * @description 14480 * {@link $httpBackend} calls this method to remove the callback after the JSONP request has 14481 * completed or timed-out. 14482 */ 14483 removeCallback: function(callbackPath) { 14484 var callback = callbackMap[callbackPath]; 14485 delete callbacks[callback.id]; 14486 delete callbackMap[callbackPath]; 14487 } 14488 }; 14489 }; 14490}; 14491 14492/** 14493 * @ngdoc service 14494 * @name $locale 14495 * 14496 * @description 14497 * $locale service provides localization rules for various AngularJS components. As of right now the 14498 * only public api is: 14499 * 14500 * * `id` â `{string}` â locale id formatted as `languageId-countryId` (e.g. `en-us`) 14501 */ 14502 14503/* global stripHash: true */ 14504 14505var PATH_MATCH = /^([^?#]*)(\?([^#]*))?(#(.*))?$/, 14506 DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21}; 14507var $locationMinErr = minErr('$location'); 14508 14509 14510/** 14511 * Encode path using encodeUriSegment, ignoring forward slashes 14512 * 14513 * @param {string} path Path to encode 14514 * @returns {string} 14515 */ 14516function encodePath(path) { 14517 var segments = path.split('/'), 14518 i = segments.length; 14519 14520 while (i--) { 14521 // decode forward slashes to prevent them from being double encoded 14522 segments[i] = encodeUriSegment(segments[i].replace(/%2F/g, '/')); 14523 } 14524 14525 return segments.join('/'); 14526} 14527 14528function decodePath(path, html5Mode) { 14529 var segments = path.split('/'), 14530 i = segments.length; 14531 14532 while (i--) { 14533 segments[i] = decodeURIComponent(segments[i]); 14534 if (html5Mode) { 14535 // encode forward slashes to prevent them from being mistaken for path separators 14536 segments[i] = segments[i].replace(/\//g, '%2F'); 14537 } 14538 } 14539 14540 return segments.join('/'); 14541} 14542 14543function normalizePath(pathValue, searchValue, hashValue) { 14544 var search = toKeyValue(searchValue), 14545 hash = hashValue ? '#' + encodeUriSegment(hashValue) : '', 14546 path = encodePath(pathValue); 14547 14548 return path + (search ? '?' + search : '') + hash; 14549} 14550 14551function parseAbsoluteUrl(absoluteUrl, locationObj) { 14552 var parsedUrl = urlResolve(absoluteUrl); 14553 14554 locationObj.$$protocol = parsedUrl.protocol; 14555 locationObj.$$host = parsedUrl.hostname; 14556 locationObj.$$port = toInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null; 14557} 14558 14559var DOUBLE_SLASH_REGEX = /^\s*[\\/]{2,}/; 14560function parseAppUrl(url, locationObj, html5Mode) { 14561 14562 if (DOUBLE_SLASH_REGEX.test(url)) { 14563 throw $locationMinErr('badpath', 'Invalid url "{0}".', url); 14564 } 14565 14566 var prefixed = (url.charAt(0) !== '/'); 14567 if (prefixed) { 14568 url = '/' + url; 14569 } 14570 var match = urlResolve(url); 14571 var path = prefixed && match.pathname.charAt(0) === '/' ? match.pathname.substring(1) : match.pathname; 14572 locationObj.$$path = decodePath(path, html5Mode); 14573 locationObj.$$search = parseKeyValue(match.search); 14574 locationObj.$$hash = decodeURIComponent(match.hash); 14575 14576 // make sure path starts with '/'; 14577 if (locationObj.$$path && locationObj.$$path.charAt(0) !== '/') { 14578 locationObj.$$path = '/' + locationObj.$$path; 14579 } 14580} 14581 14582function startsWith(str, search) { 14583 return str.slice(0, search.length) === search; 14584} 14585 14586/** 14587 * 14588 * @param {string} base 14589 * @param {string} url 14590 * @returns {string} returns text from `url` after `base` or `undefined` if it does not begin with 14591 * the expected string. 14592 */ 14593function stripBaseUrl(base, url) { 14594 if (startsWith(url, base)) { 14595 return url.substr(base.length); 14596 } 14597} 14598 14599function stripHash(url) { 14600 var index = url.indexOf('#'); 14601 return index === -1 ? url : url.substr(0, index); 14602} 14603 14604function stripFile(url) { 14605 return url.substr(0, stripHash(url).lastIndexOf('/') + 1); 14606} 14607 14608/* return the server only (scheme://host:port) */ 14609function serverBase(url) { 14610 return url.substring(0, url.indexOf('/', url.indexOf('//') + 2)); 14611} 14612 14613 14614/** 14615 * LocationHtml5Url represents a URL 14616 * This object is exposed as $location service when HTML5 mode is enabled and supported 14617 * 14618 * @constructor 14619 * @param {string} appBase application base URL 14620 * @param {string} appBaseNoFile application base URL stripped of any filename 14621 * @param {string} basePrefix URL path prefix 14622 */ 14623function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) { 14624 this.$$html5 = true; 14625 basePrefix = basePrefix || ''; 14626 parseAbsoluteUrl(appBase, this); 14627 14628 14629 /** 14630 * Parse given HTML5 (regular) URL string into properties 14631 * @param {string} url HTML5 URL 14632 * @private 14633 */ 14634 this.$$parse = function(url) { 14635 var pathUrl = stripBaseUrl(appBaseNoFile, url); 14636 if (!isString(pathUrl)) { 14637 throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url, 14638 appBaseNoFile); 14639 } 14640 14641 parseAppUrl(pathUrl, this, true); 14642 14643 if (!this.$$path) { 14644 this.$$path = '/'; 14645 } 14646 14647 this.$$compose(); 14648 }; 14649 14650 this.$$normalizeUrl = function(url) { 14651 return appBaseNoFile + url.substr(1); // first char is always '/' 14652 }; 14653 14654 this.$$parseLinkUrl = function(url, relHref) { 14655 if (relHref && relHref[0] === '#') { 14656 // special case for links to hash fragments: 14657 // keep the old url and only replace the hash fragment 14658 this.hash(relHref.slice(1)); 14659 return true; 14660 } 14661 var appUrl, prevAppUrl; 14662 var rewrittenUrl; 14663 14664 14665 if (isDefined(appUrl = stripBaseUrl(appBase, url))) { 14666 prevAppUrl = appUrl; 14667 if (basePrefix && isDefined(appUrl = stripBaseUrl(basePrefix, appUrl))) { 14668 rewrittenUrl = appBaseNoFile + (stripBaseUrl('/', appUrl) || appUrl); 14669 } else { 14670 rewrittenUrl = appBase + prevAppUrl; 14671 } 14672 } else if (isDefined(appUrl = stripBaseUrl(appBaseNoFile, url))) { 14673 rewrittenUrl = appBaseNoFile + appUrl; 14674 } else if (appBaseNoFile === url + '/') { 14675 rewrittenUrl = appBaseNoFile; 14676 } 14677 if (rewrittenUrl) { 14678 this.$$parse(rewrittenUrl); 14679 } 14680 return !!rewrittenUrl; 14681 }; 14682} 14683 14684 14685/** 14686 * LocationHashbangUrl represents URL 14687 * This object is exposed as $location service when developer doesn't opt into html5 mode. 14688 * It also serves as the base class for html5 mode fallback on legacy browsers. 14689 * 14690 * @constructor 14691 * @param {string} appBase application base URL 14692 * @param {string} appBaseNoFile application base URL stripped of any filename 14693 * @param {string} hashPrefix hashbang prefix 14694 */ 14695function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) { 14696 14697 parseAbsoluteUrl(appBase, this); 14698 14699 14700 /** 14701 * Parse given hashbang URL into properties 14702 * @param {string} url Hashbang URL 14703 * @private 14704 */ 14705 this.$$parse = function(url) { 14706 var withoutBaseUrl = stripBaseUrl(appBase, url) || stripBaseUrl(appBaseNoFile, url); 14707 var withoutHashUrl; 14708 14709 if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === '#') { 14710 14711 // The rest of the URL starts with a hash so we have 14712 // got either a hashbang path or a plain hash fragment 14713 withoutHashUrl = stripBaseUrl(hashPrefix, withoutBaseUrl); 14714 if (isUndefined(withoutHashUrl)) { 14715 // There was no hashbang prefix so we just have a hash fragment 14716 withoutHashUrl = withoutBaseUrl; 14717 } 14718 14719 } else { 14720 // There was no hashbang path nor hash fragment: 14721 // If we are in HTML5 mode we use what is left as the path; 14722 // Otherwise we ignore what is left 14723 if (this.$$html5) { 14724 withoutHashUrl = withoutBaseUrl; 14725 } else { 14726 withoutHashUrl = ''; 14727 if (isUndefined(withoutBaseUrl)) { 14728 appBase = url; 14729 /** @type {?} */ (this).replace(); 14730 } 14731 } 14732 } 14733 14734 parseAppUrl(withoutHashUrl, this, false); 14735 14736 this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase); 14737 14738 this.$$compose(); 14739 14740 /* 14741 * In Windows, on an anchor node on documents loaded from 14742 * the filesystem, the browser will return a pathname 14743 * prefixed with the drive name ('/C:/path') when a 14744 * pathname without a drive is set: 14745 * * a.setAttribute('href', '/foo') 14746 * * a.pathname === '/C:/foo' //true 14747 * 14748 * Inside of AngularJS, we're always using pathnames that 14749 * do not include drive names for routing. 14750 */ 14751 function removeWindowsDriveName(path, url, base) { 14752 /* 14753 Matches paths for file protocol on windows, 14754 such as /C:/foo/bar, and captures only /foo/bar. 14755 */ 14756 var windowsFilePathExp = /^\/[A-Z]:(\/.*)/; 14757 14758 var firstPathSegmentMatch; 14759 14760 //Get the relative path from the input URL. 14761 if (startsWith(url, base)) { 14762 url = url.replace(base, ''); 14763 } 14764 14765 // The input URL intentionally contains a first path segment that ends with a colon. 14766 if (windowsFilePathExp.exec(url)) { 14767 return path; 14768 } 14769 14770 firstPathSegmentMatch = windowsFilePathExp.exec(path); 14771 return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path; 14772 } 14773 }; 14774 14775 this.$$normalizeUrl = function(url) { 14776 return appBase + (url ? hashPrefix + url : ''); 14777 }; 14778 14779 this.$$parseLinkUrl = function(url, relHref) { 14780 if (stripHash(appBase) === stripHash(url)) { 14781 this.$$parse(url); 14782 return true; 14783 } 14784 return false; 14785 }; 14786} 14787 14788 14789/** 14790 * LocationHashbangUrl represents URL 14791 * This object is exposed as $location service when html5 history api is enabled but the browser 14792 * does not support it. 14793 * 14794 * @constructor 14795 * @param {string} appBase application base URL 14796 * @param {string} appBaseNoFile application base URL stripped of any filename 14797 * @param {string} hashPrefix hashbang prefix 14798 */ 14799function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) { 14800 this.$$html5 = true; 14801 LocationHashbangUrl.apply(this, arguments); 14802 14803 this.$$parseLinkUrl = function(url, relHref) { 14804 if (relHref && relHref[0] === '#') { 14805 // special case for links to hash fragments: 14806 // keep the old url and only replace the hash fragment 14807 this.hash(relHref.slice(1)); 14808 return true; 14809 } 14810 14811 var rewrittenUrl; 14812 var appUrl; 14813 14814 if (appBase === stripHash(url)) { 14815 rewrittenUrl = url; 14816 } else if ((appUrl = stripBaseUrl(appBaseNoFile, url))) { 14817 rewrittenUrl = appBase + hashPrefix + appUrl; 14818 } else if (appBaseNoFile === url + '/') { 14819 rewrittenUrl = appBaseNoFile; 14820 } 14821 if (rewrittenUrl) { 14822 this.$$parse(rewrittenUrl); 14823 } 14824 return !!rewrittenUrl; 14825 }; 14826 14827 this.$$normalizeUrl = function(url) { 14828 // include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#' 14829 return appBase + hashPrefix + url; 14830 }; 14831} 14832 14833 14834var locationPrototype = { 14835 14836 /** 14837 * Ensure absolute URL is initialized. 14838 * @private 14839 */ 14840 $$absUrl:'', 14841 14842 /** 14843 * Are we in html5 mode? 14844 * @private 14845 */ 14846 $$html5: false, 14847 14848 /** 14849 * Has any change been replacing? 14850 * @private 14851 */ 14852 $$replace: false, 14853 14854 /** 14855 * Compose url and update `url` and `absUrl` property 14856 * @private 14857 */ 14858 $$compose: function() { 14859 this.$$url = normalizePath(this.$$path, this.$$search, this.$$hash); 14860 this.$$absUrl = this.$$normalizeUrl(this.$$url); 14861 this.$$urlUpdatedByLocation = true; 14862 }, 14863 14864 /** 14865 * @ngdoc method 14866 * @name $location#absUrl 14867 * 14868 * @description 14869 * This method is getter only. 14870 * 14871 * Return full URL representation with all segments encoded according to rules specified in 14872 * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt). 14873 * 14874 * 14875 * ```js 14876 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo 14877 * var absUrl = $location.absUrl(); 14878 * // => "http://example.com/#/some/path?foo=bar&baz=xoxo" 14879 * ``` 14880 * 14881 * @return {string} full URL 14882 */ 14883 absUrl: locationGetter('$$absUrl'), 14884 14885 /** 14886 * @ngdoc method 14887 * @name $location#url 14888 * 14889 * @description 14890 * This method is getter / setter. 14891 * 14892 * Return URL (e.g. `/path?a=b#hash`) when called without any parameter. 14893 * 14894 * Change path, search and hash, when called with parameter and return `$location`. 14895 * 14896 * 14897 * ```js 14898 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo 14899 * var url = $location.url(); 14900 * // => "/some/path?foo=bar&baz=xoxo" 14901 * ``` 14902 * 14903 * @param {string=} url New URL without base prefix (e.g. `/path?a=b#hash`) 14904 * @return {string} url 14905 */ 14906 url: function(url) { 14907 if (isUndefined(url)) { 14908 return this.$$url; 14909 } 14910 14911 var match = PATH_MATCH.exec(url); 14912 if (match[1] || url === '') this.path(decodeURIComponent(match[1])); 14913 if (match[2] || match[1] || url === '') this.search(match[3] || ''); 14914 this.hash(match[5] || ''); 14915 14916 return this; 14917 }, 14918 14919 /** 14920 * @ngdoc method 14921 * @name $location#protocol 14922 * 14923 * @description 14924 * This method is getter only. 14925 * 14926 * Return protocol of current URL. 14927 * 14928 * 14929 * ```js 14930 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo 14931 * var protocol = $location.protocol(); 14932 * // => "http" 14933 * ``` 14934 * 14935 * @return {string} protocol of current URL 14936 */ 14937 protocol: locationGetter('$$protocol'), 14938 14939 /** 14940 * @ngdoc method 14941 * @name $location#host 14942 * 14943 * @description 14944 * This method is getter only. 14945 * 14946 * Return host of current URL. 14947 * 14948 * Note: compared to the non-AngularJS version `location.host` which returns `hostname:port`, this returns the `hostname` portion only. 14949 * 14950 * 14951 * ```js 14952 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo 14953 * var host = $location.host(); 14954 * // => "example.com" 14955 * 14956 * // given URL http://user:[email protected]:8080/#/some/path?foo=bar&baz=xoxo 14957 * host = $location.host(); 14958 * // => "example.com" 14959 * host = location.host; 14960 * // => "example.com:8080" 14961 * ``` 14962 * 14963 * @return {string} host of current URL. 14964 */ 14965 host: locationGetter('$$host'), 14966 14967 /** 14968 * @ngdoc method 14969 * @name $location#port 14970 * 14971 * @description 14972 * This method is getter only. 14973 * 14974 * Return port of current URL. 14975 * 14976 * 14977 * ```js 14978 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo 14979 * var port = $location.port(); 14980 * // => 80 14981 * ``` 14982 * 14983 * @return {Number} port 14984 */ 14985 port: locationGetter('$$port'), 14986 14987 /** 14988 * @ngdoc method 14989 * @name $location#path 14990 * 14991 * @description 14992 * This method is getter / setter. 14993 * 14994 * Return path of current URL when called without any parameter. 14995 * 14996 * Change path when called with parameter and return `$location`. 14997 * 14998 * Note: Path should always begin with forward slash (/), this method will add the forward slash 14999 * if it is missing. 15000 * 15001 * 15002 * ```js 15003 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo 15004 * var path = $location.path(); 15005 * // => "/some/path" 15006 * ``` 15007 * 15008 * @param {(string|number)=} path New path 15009 * @return {(string|object)} path if called with no parameters, or `$location` if called with a parameter 15010 */ 15011 path: locationGetterSetter('$$path', function(path) { 15012 path = path !== null ? path.toString() : ''; 15013 return path.charAt(0) === '/' ? path : '/' + path; 15014 }), 15015 15016 /** 15017 * @ngdoc method 15018 * @name $location#search 15019 * 15020 * @description 15021 * This method is getter / setter. 15022 * 15023 * Return search part (as object) of current URL when called without any parameter. 15024 * 15025 * Change search part when called with parameter and return `$location`. 15026 * 15027 * 15028 * ```js 15029 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo 15030 * var searchObject = $location.search(); 15031 * // => {foo: 'bar', baz: 'xoxo'} 15032 * 15033 * // set foo to 'yipee' 15034 * $location.search('foo', 'yipee'); 15035 * // $location.search() => {foo: 'yipee', baz: 'xoxo'} 15036 * ``` 15037 * 15038 * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or 15039 * hash object. 15040 * 15041 * When called with a single argument the method acts as a setter, setting the `search` component 15042 * of `$location` to the specified value. 15043 * 15044 * If the argument is a hash object containing an array of values, these values will be encoded 15045 * as duplicate search parameters in the URL. 15046 * 15047 * @param {(string|Number|Array<string>|boolean)=} paramValue If `search` is a string or number, then `paramValue` 15048 * will override only a single search property. 15049 * 15050 * If `paramValue` is an array, it will override the property of the `search` component of 15051 * `$location` specified via the first argument. 15052 * 15053 * If `paramValue` is `null`, the property specified via the first argument will be deleted. 15054 * 15055 * If `paramValue` is `true`, the property specified via the first argument will be added with no 15056 * value nor trailing equal sign. 15057 * 15058 * @return {Object} If called with no arguments returns the parsed `search` object. If called with 15059 * one or more arguments returns `$location` object itself. 15060 */ 15061 search: function(search, paramValue) { 15062 switch (arguments.length) { 15063 case 0: 15064 return this.$$search; 15065 case 1: 15066 if (isString(search) || isNumber(search)) { 15067 search = search.toString(); 15068 this.$$search = parseKeyValue(search); 15069 } else if (isObject(search)) { 15070 search = copy(search, {}); 15071 // remove object undefined or null properties 15072 forEach(search, function(value, key) { 15073 if (value == null) delete search[key]; 15074 }); 15075 15076 this.$$search = search; 15077 } else { 15078 throw $locationMinErr('isrcharg', 15079 'The first argument of the `$location#search()` call must be a string or an object.'); 15080 } 15081 break; 15082 default: 15083 if (isUndefined(paramValue) || paramValue === null) { 15084 delete this.$$search[search]; 15085 } else { 15086 this.$$search[search] = paramValue; 15087 } 15088 } 15089 15090 this.$$compose(); 15091 return this; 15092 }, 15093 15094 /** 15095 * @ngdoc method 15096 * @name $location#hash 15097 * 15098 * @description 15099 * This method is getter / setter. 15100 * 15101 * Returns the hash fragment when called without any parameters. 15102 * 15103 * Changes the hash fragment when called with a parameter and returns `$location`. 15104 * 15105 * 15106 * ```js 15107 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue 15108 * var hash = $location.hash(); 15109 * // => "hashValue" 15110 * ``` 15111 * 15112 * @param {(string|number)=} hash New hash fragment 15113 * @return {string} hash 15114 */ 15115 hash: locationGetterSetter('$$hash', function(hash) { 15116 return hash !== null ? hash.toString() : ''; 15117 }), 15118 15119 /** 15120 * @ngdoc method 15121 * @name $location#replace 15122 * 15123 * @description 15124 * If called, all changes to $location during the current `$digest` will replace the current history 15125 * record, instead of adding a new one. 15126 */ 15127 replace: function() { 15128 this.$$replace = true; 15129 return this; 15130 } 15131}; 15132 15133forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) { 15134 Location.prototype = Object.create(locationPrototype); 15135 15136 /** 15137 * @ngdoc method 15138 * @name $location#state 15139 * 15140 * @description 15141 * This method is getter / setter. 15142 * 15143 * Return the history state object when called without any parameter. 15144 * 15145 * Change the history state object when called with one parameter and return `$location`. 15146 * The state object is later passed to `pushState` or `replaceState`. 15147 * 15148 * NOTE: This method is supported only in HTML5 mode and only in browsers supporting 15149 * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support 15150 * older browsers (like IE9 or Android < 4.0), don't use this method. 15151 * 15152 * @param {object=} state State object for pushState or replaceState 15153 * @return {object} state 15154 */ 15155 Location.prototype.state = function(state) { 15156 if (!arguments.length) { 15157 return this.$$state; 15158 } 15159 15160 if (Location !== LocationHtml5Url || !this.$$html5) { 15161 throw $locationMinErr('nostate', 'History API state support is available only ' + 15162 'in HTML5 mode and only in browsers supporting HTML5 History API'); 15163 } 15164 // The user might modify `stateObject` after invoking `$location.state(stateObject)` 15165 // but we're changing the $$state reference to $browser.state() during the $digest 15166 // so the modification window is narrow. 15167 this.$$state = isUndefined(state) ? null : state; 15168 this.$$urlUpdatedByLocation = true; 15169 15170 return this; 15171 }; 15172}); 15173 15174 15175function locationGetter(property) { 15176 return /** @this */ function() { 15177 return this[property]; 15178 }; 15179} 15180 15181 15182function locationGetterSetter(property, preprocess) { 15183 return /** @this */ function(value) { 15184 if (isUndefined(value)) { 15185 return this[property]; 15186 } 15187 15188 this[property] = preprocess(value); 15189 this.$$compose(); 15190 15191 return this; 15192 }; 15193} 15194 15195 15196/** 15197 * @ngdoc service 15198 * @name $location 15199 * 15200 * @requires $rootElement 15201 * 15202 * @description 15203 * The $location service parses the URL in the browser address bar (based on the 15204 * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL 15205 * available to your application. Changes to the URL in the address bar are reflected into 15206 * $location service and changes to $location are reflected into the browser address bar. 15207 * 15208 * **The $location service:** 15209 * 15210 * - Exposes the current URL in the browser address bar, so you can 15211 * - Watch and observe the URL. 15212 * - Change the URL. 15213 * - Synchronizes the URL with the browser when the user 15214 * - Changes the address bar. 15215 * - Clicks the back or forward button (or clicks a History link). 15216 * - Clicks on a link. 15217 * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). 15218 * 15219 * For more information see {@link guide/$location Developer Guide: Using $location} 15220 */ 15221 15222/** 15223 * @ngdoc provider 15224 * @name $locationProvider 15225 * @this 15226 * 15227 * @description 15228 * Use the `$locationProvider` to configure how the application deep linking paths are stored. 15229 */ 15230function $LocationProvider() { 15231 var hashPrefix = '!', 15232 html5Mode = { 15233 enabled: false, 15234 requireBase: true, 15235 rewriteLinks: true 15236 }; 15237 15238 /** 15239 * @ngdoc method 15240 * @name $locationProvider#hashPrefix 15241 * @description 15242 * The default value for the prefix is `'!'`. 15243 * @param {string=} prefix Prefix for hash part (containing path and search) 15244 * @returns {*} current value if used as getter or itself (chaining) if used as setter 15245 */ 15246 this.hashPrefix = function(prefix) { 15247 if (isDefined(prefix)) { 15248 hashPrefix = prefix; 15249 return this; 15250 } else { 15251 return hashPrefix; 15252 } 15253 }; 15254 15255 /** 15256 * @ngdoc method 15257 * @name $locationProvider#html5Mode 15258 * @description 15259 * @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value. 15260 * If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported 15261 * properties: 15262 * - **enabled** â `{boolean}` â (default: false) If true, will rely on `history.pushState` to 15263 * change urls where supported. Will fall back to hash-prefixed paths in browsers that do not 15264 * support `pushState`. 15265 * - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies 15266 * whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are 15267 * true, and a base tag is not present, an error will be thrown when `$location` is injected. 15268 * See the {@link guide/$location $location guide for more information} 15269 * - **rewriteLinks** - `{boolean|string}` - (default: `true`) When html5Mode is enabled, 15270 * enables/disables URL rewriting for relative links. If set to a string, URL rewriting will 15271 * only happen on links with an attribute that matches the given string. For example, if set 15272 * to `'internal-link'`, then the URL will only be rewritten for `<a internal-link>` links. 15273 * Note that [attribute name normalization](guide/directive#normalization) does not apply 15274 * here, so `'internalLink'` will **not** match `'internal-link'`. 15275 * 15276 * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter 15277 */ 15278 this.html5Mode = function(mode) { 15279 if (isBoolean(mode)) { 15280 html5Mode.enabled = mode; 15281 return this; 15282 } else if (isObject(mode)) { 15283 15284 if (isBoolean(mode.enabled)) { 15285 html5Mode.enabled = mode.enabled; 15286 } 15287 15288 if (isBoolean(mode.requireBase)) { 15289 html5Mode.requireBase = mode.requireBase; 15290 } 15291 15292 if (isBoolean(mode.rewriteLinks) || isString(mode.rewriteLinks)) { 15293 html5Mode.rewriteLinks = mode.rewriteLinks; 15294 } 15295 15296 return this; 15297 } else { 15298 return html5Mode; 15299 } 15300 }; 15301 15302 /** 15303 * @ngdoc event 15304 * @name $location#$locationChangeStart 15305 * @eventType broadcast on root scope 15306 * @description 15307 * Broadcasted before a URL will change. 15308 * 15309 * This change can be prevented by calling 15310 * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more 15311 * details about event object. Upon successful change 15312 * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired. 15313 * 15314 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when 15315 * the browser supports the HTML5 History API. 15316 * 15317 * @param {Object} angularEvent Synthetic event object. 15318 * @param {string} newUrl New URL 15319 * @param {string=} oldUrl URL that was before it was changed. 15320 * @param {string=} newState New history state object 15321 * @param {string=} oldState History state object that was before it was changed. 15322 */ 15323 15324 /** 15325 * @ngdoc event 15326 * @name $location#$locationChangeSuccess 15327 * @eventType broadcast on root scope 15328 * @description 15329 * Broadcasted after a URL was changed. 15330 * 15331 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when 15332 * the browser supports the HTML5 History API. 15333 * 15334 * @param {Object} angularEvent Synthetic event object. 15335 * @param {string} newUrl New URL 15336 * @param {string=} oldUrl URL that was before it was changed. 15337 * @param {string=} newState New history state object 15338 * @param {string=} oldState History state object that was before it was changed. 15339 */ 15340 15341 this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window', 15342 function($rootScope, $browser, $sniffer, $rootElement, $window) { 15343 var $location, 15344 LocationMode, 15345 baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' 15346 initialUrl = $browser.url(), 15347 appBase; 15348 15349 if (html5Mode.enabled) { 15350 if (!baseHref && html5Mode.requireBase) { 15351 throw $locationMinErr('nobase', 15352 '$location in HTML5 mode requires a <base> tag to be present!'); 15353 } 15354 appBase = serverBase(initialUrl) + (baseHref || '/'); 15355 LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url; 15356 } else { 15357 appBase = stripHash(initialUrl); 15358 LocationMode = LocationHashbangUrl; 15359 } 15360 var appBaseNoFile = stripFile(appBase); 15361 15362 $location = new LocationMode(appBase, appBaseNoFile, '#' + hashPrefix); 15363 $location.$$parseLinkUrl(initialUrl, initialUrl); 15364 15365 $location.$$state = $browser.state(); 15366 15367 var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i; 15368 15369 // Determine if two URLs are equal despite potentially having different encoding/normalizing 15370 // such as $location.absUrl() vs $browser.url() 15371 // See https://github.com/angular/angular.js/issues/16592 15372 function urlsEqual(a, b) { 15373 return a === b || urlResolve(a).href === urlResolve(b).href; 15374 } 15375 15376 function setBrowserUrlWithFallback(url, replace, state) { 15377 var oldUrl = $location.url(); 15378 var oldState = $location.$$state; 15379 try { 15380 $browser.url(url, replace, state); 15381 15382 // Make sure $location.state() returns referentially identical (not just deeply equal) 15383 // state object; this makes possible quick checking if the state changed in the digest 15384 // loop. Checking deep equality would be too expensive. 15385 $location.$$state = $browser.state(); 15386 } catch (e) { 15387 // Restore old values if pushState fails 15388 $location.url(oldUrl); 15389 $location.$$state = oldState; 15390 15391 throw e; 15392 } 15393 } 15394 15395 $rootElement.on('click', function(event) { 15396 var rewriteLinks = html5Mode.rewriteLinks; 15397 // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) 15398 // currently we open nice url link and redirect then 15399 15400 if (!rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which === 2 || event.button === 2) return; 15401 15402 var elm = jqLite(event.target); 15403 15404 // traverse the DOM up to find first A tag 15405 while (nodeName_(elm[0]) !== 'a') { 15406 // ignore rewriting if no A tag (reached root element, or no parent - removed from document) 15407 if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return; 15408 } 15409 15410 if (isString(rewriteLinks) && isUndefined(elm.attr(rewriteLinks))) return; 15411 15412 var absHref = elm.prop('href'); 15413 // get the actual href attribute - see 15414 // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx 15415 var relHref = elm.attr('href') || elm.attr('xlink:href'); 15416 15417 if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') { 15418 // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during 15419 // an animation. 15420 absHref = urlResolve(absHref.animVal).href; 15421 } 15422 15423 // Ignore when url is started with javascript: or mailto: 15424 if (IGNORE_URI_REGEXP.test(absHref)) return; 15425 15426 if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) { 15427 if ($location.$$parseLinkUrl(absHref, relHref)) { 15428 // We do a preventDefault for all urls that are part of the AngularJS application, 15429 // in html5mode and also without, so that we are able to abort navigation without 15430 // getting double entries in the location history. 15431 event.preventDefault(); 15432 // update location manually 15433 if ($location.absUrl() !== $browser.url()) { 15434 $rootScope.$apply(); 15435 } 15436 } 15437 } 15438 }); 15439 15440 15441 // rewrite hashbang url <> html5 url 15442 if ($location.absUrl() !== initialUrl) { 15443 $browser.url($location.absUrl(), true); 15444 } 15445 15446 var initializing = true; 15447 15448 // update $location when $browser url changes 15449 $browser.onUrlChange(function(newUrl, newState) { 15450 15451 if (!startsWith(newUrl, appBaseNoFile)) { 15452 // If we are navigating outside of the app then force a reload 15453 $window.location.href = newUrl; 15454 return; 15455 } 15456 15457 $rootScope.$evalAsync(function() { 15458 var oldUrl = $location.absUrl(); 15459 var oldState = $location.$$state; 15460 var defaultPrevented; 15461 $location.$$parse(newUrl); 15462 $location.$$state = newState; 15463 15464 defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, 15465 newState, oldState).defaultPrevented; 15466 15467 // if the location was changed by a `$locationChangeStart` handler then stop 15468 // processing this location change 15469 if ($location.absUrl() !== newUrl) return; 15470 15471 if (defaultPrevented) { 15472 $location.$$parse(oldUrl); 15473 $location.$$state = oldState; 15474 setBrowserUrlWithFallback(oldUrl, false, oldState); 15475 } else { 15476 initializing = false; 15477 afterLocationChange(oldUrl, oldState); 15478 } 15479 }); 15480 if (!$rootScope.$$phase) $rootScope.$digest(); 15481 }); 15482 15483 // update browser 15484 $rootScope.$watch(function $locationWatch() { 15485 if (initializing || $location.$$urlUpdatedByLocation) { 15486 $location.$$urlUpdatedByLocation = false; 15487 15488 var oldUrl = $browser.url(); 15489 var newUrl = $location.absUrl(); 15490 var oldState = $browser.state(); 15491 var currentReplace = $location.$$replace; 15492 var urlOrStateChanged = !urlsEqual(oldUrl, newUrl) || 15493 ($location.$$html5 && $sniffer.history && oldState !== $location.$$state); 15494 15495 if (initializing || urlOrStateChanged) { 15496 initializing = false; 15497 15498 $rootScope.$evalAsync(function() { 15499 var newUrl = $location.absUrl(); 15500 var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, 15501 $location.$$state, oldState).defaultPrevented; 15502 15503 // if the location was changed by a `$locationChangeStart` handler then stop 15504 // processing this location change 15505 if ($location.absUrl() !== newUrl) return; 15506 15507 if (defaultPrevented) { 15508 $location.$$parse(oldUrl); 15509 $location.$$state = oldState; 15510 } else { 15511 if (urlOrStateChanged) { 15512 setBrowserUrlWithFallback(newUrl, currentReplace, 15513 oldState === $location.$$state ? null : $location.$$state); 15514 } 15515 afterLocationChange(oldUrl, oldState); 15516 } 15517 }); 15518 } 15519 } 15520 15521 $location.$$replace = false; 15522 15523 // we don't need to return anything because $evalAsync will make the digest loop dirty when 15524 // there is a change 15525 }); 15526 15527 return $location; 15528 15529 function afterLocationChange(oldUrl, oldState) { 15530 $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl, 15531 $location.$$state, oldState); 15532 } 15533}]; 15534} 15535 15536/** 15537 * @ngdoc service 15538 * @name $log 15539 * @requires $window 15540 * 15541 * @description 15542 * Simple service for logging. Default implementation safely writes the message 15543 * into the browser's console (if present). 15544 * 15545 * The main purpose of this service is to simplify debugging and troubleshooting. 15546 * 15547 * To reveal the location of the calls to `$log` in the JavaScript console, 15548 * you can "blackbox" the AngularJS source in your browser: 15549 * 15550 * [Mozilla description of blackboxing](https://developer.mozilla.org/en-US/docs/Tools/Debugger/How_to/Black_box_a_source). 15551 * [Chrome description of blackboxing](https://developer.chrome.com/devtools/docs/blackboxing). 15552 * 15553 * Note: Not all browsers support blackboxing. 15554 * 15555 * The default is to log `debug` messages. You can use 15556 * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this. 15557 * 15558 * @example 15559 <example module="logExample" name="log-service"> 15560 <file name="script.js"> 15561 angular.module('logExample', []) 15562 .controller('LogController', ['$scope', '$log', function($scope, $log) { 15563 $scope.$log = $log; 15564 $scope.message = 'Hello World!'; 15565 }]); 15566 </file> 15567 <file name="index.html"> 15568 <div ng-controller="LogController"> 15569 <p>Reload this page with open console, enter text and hit the log button...</p> 15570 <label>Message: 15571 <input type="text" ng-model="message" /></label> 15572 <button ng-click="$log.log(message)">log</button> 15573 <button ng-click="$log.warn(message)">warn</button> 15574 <button ng-click="$log.info(message)">info</button> 15575 <button ng-click="$log.error(message)">error</button> 15576 <button ng-click="$log.debug(message)">debug</button> 15577 </div> 15578 </file> 15579 </example> 15580 */ 15581 15582/** 15583 * @ngdoc provider 15584 * @name $logProvider 15585 * @this 15586 * 15587 * @description 15588 * Use the `$logProvider` to configure how the application logs messages 15589 */ 15590function $LogProvider() { 15591 var debug = true, 15592 self = this; 15593 15594 /** 15595 * @ngdoc method 15596 * @name $logProvider#debugEnabled 15597 * @description 15598 * @param {boolean=} flag enable or disable debug level messages 15599 * @returns {*} current value if used as getter or itself (chaining) if used as setter 15600 */ 15601 this.debugEnabled = function(flag) { 15602 if (isDefined(flag)) { 15603 debug = flag; 15604 return this; 15605 } else { 15606 return debug; 15607 } 15608 }; 15609 15610 this.$get = ['$window', function($window) { 15611 // Support: IE 9-11, Edge 12-14+ 15612 // IE/Edge display errors in such a way that it requires the user to click in 4 places 15613 // to see the stack trace. There is no way to feature-detect it so there's a chance 15614 // of the user agent sniffing to go wrong but since it's only about logging, this shouldn't 15615 // break apps. Other browsers display errors in a sensible way and some of them map stack 15616 // traces along source maps if available so it makes sense to let browsers display it 15617 // as they want. 15618 var formatStackTrace = msie || /\bEdge\//.test($window.navigator && $window.navigator.userAgent); 15619 15620 return { 15621 /** 15622 * @ngdoc method 15623 * @name $log#log 15624 * 15625 * @description 15626 * Write a log message 15627 */ 15628 log: consoleLog('log'), 15629 15630 /** 15631 * @ngdoc method 15632 * @name $log#info 15633 * 15634 * @description 15635 * Write an information message 15636 */ 15637 info: consoleLog('info'), 15638 15639 /** 15640 * @ngdoc method 15641 * @name $log#warn 15642 * 15643 * @description 15644 * Write a warning message 15645 */ 15646 warn: consoleLog('warn'), 15647 15648 /** 15649 * @ngdoc method 15650 * @name $log#error 15651 * 15652 * @description 15653 * Write an error message 15654 */ 15655 error: consoleLog('error'), 15656 15657 /** 15658 * @ngdoc method 15659 * @name $log#debug 15660 * 15661 * @description 15662 * Write a debug message 15663 */ 15664 debug: (function() { 15665 var fn = consoleLog('debug'); 15666 15667 return function() { 15668 if (debug) { 15669 fn.apply(self, arguments); 15670 } 15671 }; 15672 })() 15673 }; 15674 15675 function formatError(arg) { 15676 if (isError(arg)) { 15677 if (arg.stack && formatStackTrace) { 15678 arg = (arg.message && arg.stack.indexOf(arg.message) === -1) 15679 ? 'Error: ' + arg.message + '\n' + arg.stack 15680 : arg.stack; 15681 } else if (arg.sourceURL) { 15682 arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line; 15683 } 15684 } 15685 return arg; 15686 } 15687 15688 function consoleLog(type) { 15689 var console = $window.console || {}, 15690 logFn = console[type] || console.log || noop; 15691 15692 return function() { 15693 var args = []; 15694 forEach(arguments, function(arg) { 15695 args.push(formatError(arg)); 15696 }); 15697 // Support: IE 9 only 15698 // console methods don't inherit from Function.prototype in IE 9 so we can't 15699 // call `logFn.apply(console, args)` directly. 15700 return Function.prototype.apply.call(logFn, console, args); 15701 }; 15702 } 15703 }]; 15704} 15705 15706/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 15707 * Any commits to this file should be reviewed with security in mind. * 15708 * Changes to this file can potentially create security vulnerabilities. * 15709 * An approval from 2 Core members with history of modifying * 15710 * this file is required. * 15711 * * 15712 * Does the change somehow allow for arbitrary javascript to be executed? * 15713 * Or allows for someone to change the prototype of built-in objects? * 15714 * Or gives undesired access to variables likes document or window? * 15715 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 15716 15717var $parseMinErr = minErr('$parse'); 15718 15719var objectValueOf = {}.constructor.prototype.valueOf; 15720 15721// Sandboxing AngularJS Expressions 15722// ------------------------------ 15723// AngularJS expressions are no longer sandboxed. So it is now even easier to access arbitrary JS code by 15724// various means such as obtaining a reference to native JS functions like the Function constructor. 15725// 15726// As an example, consider the following AngularJS expression: 15727// 15728// {}.toString.constructor('alert("evil JS code")') 15729// 15730// It is important to realize that if you create an expression from a string that contains user provided 15731// content then it is possible that your application contains a security vulnerability to an XSS style attack. 15732// 15733// See https://docs.angularjs.org/guide/security 15734 15735 15736function getStringValue(name) { 15737 // Property names must be strings. This means that non-string objects cannot be used 15738 // as keys in an object. Any non-string object, including a number, is typecasted 15739 // into a string via the toString method. 15740 // -- MDN, https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Property_accessors#Property_names 15741 // 15742 // So, to ensure that we are checking the same `name` that JavaScript would use, we cast it 15743 // to a string. It's not always possible. If `name` is an object and its `toString` method is 15744 // 'broken' (doesn't return a string, isn't a function, etc.), an error will be thrown: 15745 // 15746 // TypeError: Cannot convert object to primitive value 15747 // 15748 // For performance reasons, we don't catch this error here and allow it to propagate up the call 15749 // stack. Note that you'll get the same error in JavaScript if you try to access a property using 15750 // such a 'broken' object as a key. 15751 return name + ''; 15752} 15753 15754 15755var OPERATORS = createMap(); 15756forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; }); 15757var ESCAPE = {'n':'\n', 'f':'\f', 'r':'\r', 't':'\t', 'v':'\v', '\'':'\'', '"':'"'}; 15758 15759 15760///////////////////////////////////////// 15761 15762 15763/** 15764 * @constructor 15765 */ 15766var Lexer = function Lexer(options) { 15767 this.options = options; 15768}; 15769 15770Lexer.prototype = { 15771 constructor: Lexer, 15772 15773 lex: function(text) { 15774 this.text = text; 15775 this.index = 0; 15776 this.tokens = []; 15777 15778 while (this.index < this.text.length) { 15779 var ch = this.text.charAt(this.index); 15780 if (ch === '"' || ch === '\'') { 15781 this.readString(ch); 15782 } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) { 15783 this.readNumber(); 15784 } else if (this.isIdentifierStart(this.peekMultichar())) { 15785 this.readIdent(); 15786 } else if (this.is(ch, '(){}[].,;:?')) { 15787 this.tokens.push({index: this.index, text: ch}); 15788 this.index++; 15789 } else if (this.isWhitespace(ch)) { 15790 this.index++; 15791 } else { 15792 var ch2 = ch + this.peek(); 15793 var ch3 = ch2 + this.peek(2); 15794 var op1 = OPERATORS[ch]; 15795 var op2 = OPERATORS[ch2]; 15796 var op3 = OPERATORS[ch3]; 15797 if (op1 || op2 || op3) { 15798 var token = op3 ? ch3 : (op2 ? ch2 : ch); 15799 this.tokens.push({index: this.index, text: token, operator: true}); 15800 this.index += token.length; 15801 } else { 15802 this.throwError('Unexpected next character ', this.index, this.index + 1); 15803 } 15804 } 15805 } 15806 return this.tokens; 15807 }, 15808 15809 is: function(ch, chars) { 15810 return chars.indexOf(ch) !== -1; 15811 }, 15812 15813 peek: function(i) { 15814 var num = i || 1; 15815 return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false; 15816 }, 15817 15818 isNumber: function(ch) { 15819 return ('0' <= ch && ch <= '9') && typeof ch === 'string'; 15820 }, 15821 15822 isWhitespace: function(ch) { 15823 // IE treats non-breaking space as \u00A0 15824 return (ch === ' ' || ch === '\r' || ch === '\t' || 15825 ch === '\n' || ch === '\v' || ch === '\u00A0'); 15826 }, 15827 15828 isIdentifierStart: function(ch) { 15829 return this.options.isIdentifierStart ? 15830 this.options.isIdentifierStart(ch, this.codePointAt(ch)) : 15831 this.isValidIdentifierStart(ch); 15832 }, 15833 15834 isValidIdentifierStart: function(ch) { 15835 return ('a' <= ch && ch <= 'z' || 15836 'A' <= ch && ch <= 'Z' || 15837 '_' === ch || ch === '$'); 15838 }, 15839 15840 isIdentifierContinue: function(ch) { 15841 return this.options.isIdentifierContinue ? 15842 this.options.isIdentifierContinue(ch, this.codePointAt(ch)) : 15843 this.isValidIdentifierContinue(ch); 15844 }, 15845 15846 isValidIdentifierContinue: function(ch, cp) { 15847 return this.isValidIdentifierStart(ch, cp) || this.isNumber(ch); 15848 }, 15849 15850 codePointAt: function(ch) { 15851 if (ch.length === 1) return ch.charCodeAt(0); 15852 // eslint-disable-next-line no-bitwise 15853 return (ch.charCodeAt(0) << 10) + ch.charCodeAt(1) - 0x35FDC00; 15854 }, 15855 15856 peekMultichar: function() { 15857 var ch = this.text.charAt(this.index); 15858 var peek = this.peek(); 15859 if (!peek) { 15860 return ch; 15861 } 15862 var cp1 = ch.charCodeAt(0); 15863 var cp2 = peek.charCodeAt(0); 15864 if (cp1 >= 0xD800 && cp1 <= 0xDBFF && cp2 >= 0xDC00 && cp2 <= 0xDFFF) { 15865 return ch + peek; 15866 } 15867 return ch; 15868 }, 15869 15870 isExpOperator: function(ch) { 15871 return (ch === '-' || ch === '+' || this.isNumber(ch)); 15872 }, 15873 15874 throwError: function(error, start, end) { 15875 end = end || this.index; 15876 var colStr = (isDefined(start) 15877 ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']' 15878 : ' ' + end); 15879 throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].', 15880 error, colStr, this.text); 15881 }, 15882 15883 readNumber: function() { 15884 var number = ''; 15885 var start = this.index; 15886 while (this.index < this.text.length) { 15887 var ch = lowercase(this.text.charAt(this.index)); 15888 if (ch === '.' || this.isNumber(ch)) { 15889 number += ch; 15890 } else { 15891 var peekCh = this.peek(); 15892 if (ch === 'e' && this.isExpOperator(peekCh)) { 15893 number += ch; 15894 } else if (this.isExpOperator(ch) && 15895 peekCh && this.isNumber(peekCh) && 15896 number.charAt(number.length - 1) === 'e') { 15897 number += ch; 15898 } else if (this.isExpOperator(ch) && 15899 (!peekCh || !this.isNumber(peekCh)) && 15900 number.charAt(number.length - 1) === 'e') { 15901 this.throwError('Invalid exponent'); 15902 } else { 15903 break; 15904 } 15905 } 15906 this.index++; 15907 } 15908 this.tokens.push({ 15909 index: start, 15910 text: number, 15911 constant: true, 15912 value: Number(number) 15913 }); 15914 }, 15915 15916 readIdent: function() { 15917 var start = this.index; 15918 this.index += this.peekMultichar().length; 15919 while (this.index < this.text.length) { 15920 var ch = this.peekMultichar(); 15921 if (!this.isIdentifierContinue(ch)) { 15922 break; 15923 } 15924 this.index += ch.length; 15925 } 15926 this.tokens.push({ 15927 index: start, 15928 text: this.text.slice(start, this.index), 15929 identifier: true 15930 }); 15931 }, 15932 15933 readString: function(quote) { 15934 var start = this.index; 15935 this.index++; 15936 var string = ''; 15937 var rawString = quote; 15938 var escape = false; 15939 while (this.index < this.text.length) { 15940 var ch = this.text.charAt(this.index); 15941 rawString += ch; 15942 if (escape) { 15943 if (ch === 'u') { 15944 var hex = this.text.substring(this.index + 1, this.index + 5); 15945 if (!hex.match(/[\da-f]{4}/i)) { 15946 this.throwError('Invalid unicode escape [\\u' + hex + ']'); 15947 } 15948 this.index += 4; 15949 string += String.fromCharCode(parseInt(hex, 16)); 15950 } else { 15951 var rep = ESCAPE[ch]; 15952 string = string + (rep || ch); 15953 } 15954 escape = false; 15955 } else if (ch === '\\') { 15956 escape = true; 15957 } else if (ch === quote) { 15958 this.index++; 15959 this.tokens.push({ 15960 index: start, 15961 text: rawString, 15962 constant: true, 15963 value: string 15964 }); 15965 return; 15966 } else { 15967 string += ch; 15968 } 15969 this.index++; 15970 } 15971 this.throwError('Unterminated quote', start); 15972 } 15973}; 15974 15975var AST = function AST(lexer, options) { 15976 this.lexer = lexer; 15977 this.options = options; 15978}; 15979 15980AST.Program = 'Program'; 15981AST.ExpressionStatement = 'ExpressionStatement'; 15982AST.AssignmentExpression = 'AssignmentExpression'; 15983AST.ConditionalExpression = 'ConditionalExpression'; 15984AST.LogicalExpression = 'LogicalExpression'; 15985AST.BinaryExpression = 'BinaryExpression'; 15986AST.UnaryExpression = 'UnaryExpression'; 15987AST.CallExpression = 'CallExpression'; 15988AST.MemberExpression = 'MemberExpression'; 15989AST.Identifier = 'Identifier'; 15990AST.Literal = 'Literal'; 15991AST.ArrayExpression = 'ArrayExpression'; 15992AST.Property = 'Property'; 15993AST.ObjectExpression = 'ObjectExpression'; 15994AST.ThisExpression = 'ThisExpression'; 15995AST.LocalsExpression = 'LocalsExpression'; 15996 15997// Internal use only 15998AST.NGValueParameter = 'NGValueParameter'; 15999 16000AST.prototype = { 16001 ast: function(text) { 16002 this.text = text; 16003 this.tokens = this.lexer.lex(text); 16004 16005 var value = this.program(); 16006 16007 if (this.tokens.length !== 0) { 16008 this.throwError('is an unexpected token', this.tokens[0]); 16009 } 16010 16011 return value; 16012 }, 16013 16014 program: function() { 16015 var body = []; 16016 while (true) { 16017 if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']')) 16018 body.push(this.expressionStatement()); 16019 if (!this.expect(';')) { 16020 return { type: AST.Program, body: body}; 16021 } 16022 } 16023 }, 16024 16025 expressionStatement: function() { 16026 return { type: AST.ExpressionStatement, expression: this.filterChain() }; 16027 }, 16028 16029 filterChain: function() { 16030 var left = this.expression(); 16031 while (this.expect('|')) { 16032 left = this.filter(left); 16033 } 16034 return left; 16035 }, 16036 16037 expression: function() { 16038 return this.assignment(); 16039 }, 16040 16041 assignment: function() { 16042 var result = this.ternary(); 16043 if (this.expect('=')) { 16044 if (!isAssignable(result)) { 16045 throw $parseMinErr('lval', 'Trying to assign a value to a non l-value'); 16046 } 16047 16048 result = { type: AST.AssignmentExpression, left: result, right: this.assignment(), operator: '='}; 16049 } 16050 return result; 16051 }, 16052 16053 ternary: function() { 16054 var test = this.logicalOR(); 16055 var alternate; 16056 var consequent; 16057 if (this.expect('?')) { 16058 alternate = this.expression(); 16059 if (this.consume(':')) { 16060 consequent = this.expression(); 16061 return { type: AST.ConditionalExpression, test: test, alternate: alternate, consequent: consequent}; 16062 } 16063 } 16064 return test; 16065 }, 16066 16067 logicalOR: function() { 16068 var left = this.logicalAND(); 16069 while (this.expect('||')) { 16070 left = { type: AST.LogicalExpression, operator: '||', left: left, right: this.logicalAND() }; 16071 } 16072 return left; 16073 }, 16074 16075 logicalAND: function() { 16076 var left = this.equality(); 16077 while (this.expect('&&')) { 16078 left = { type: AST.LogicalExpression, operator: '&&', left: left, right: this.equality()}; 16079 } 16080 return left; 16081 }, 16082 16083 equality: function() { 16084 var left = this.relational(); 16085 var token; 16086 while ((token = this.expect('==','!=','===','!=='))) { 16087 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.relational() }; 16088 } 16089 return left; 16090 }, 16091 16092 relational: function() { 16093 var left = this.additive(); 16094 var token; 16095 while ((token = this.expect('<', '>', '<=', '>='))) { 16096 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.additive() }; 16097 } 16098 return left; 16099 }, 16100 16101 additive: function() { 16102 var left = this.multiplicative(); 16103 var token; 16104 while ((token = this.expect('+','-'))) { 16105 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.multiplicative() }; 16106 } 16107 return left; 16108 }, 16109 16110 multiplicative: function() { 16111 var left = this.unary(); 16112 var token; 16113 while ((token = this.expect('*','/','%'))) { 16114 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.unary() }; 16115 } 16116 return left; 16117 }, 16118 16119 unary: function() { 16120 var token; 16121 if ((token = this.expect('+', '-', '!'))) { 16122 return { type: AST.UnaryExpression, operator: token.text, prefix: true, argument: this.unary() }; 16123 } else { 16124 return this.primary(); 16125 } 16126 }, 16127 16128 primary: function() { 16129 var primary; 16130 if (this.expect('(')) { 16131 primary = this.filterChain(); 16132 this.consume(')'); 16133 } else if (this.expect('[')) { 16134 primary = this.arrayDeclaration(); 16135 } else if (this.expect('{')) { 16136 primary = this.object(); 16137 } else if (this.selfReferential.hasOwnProperty(this.peek().text)) { 16138 primary = copy(this.selfReferential[this.consume().text]); 16139 } else if (this.options.literals.hasOwnProperty(this.peek().text)) { 16140 primary = { type: AST.Literal, value: this.options.literals[this.consume().text]}; 16141 } else if (this.peek().identifier) { 16142 primary = this.identifier(); 16143 } else if (this.peek().constant) { 16144 primary = this.constant(); 16145 } else { 16146 this.throwError('not a primary expression', this.peek()); 16147 } 16148 16149 var next; 16150 while ((next = this.expect('(', '[', '.'))) { 16151 if (next.text === '(') { 16152 primary = {type: AST.CallExpression, callee: primary, arguments: this.parseArguments() }; 16153 this.consume(')'); 16154 } else if (next.text === '[') { 16155 primary = { type: AST.MemberExpression, object: primary, property: this.expression(), computed: true }; 16156 this.consume(']'); 16157 } else if (next.text === '.') { 16158 primary = { type: AST.MemberExpression, object: primary, property: this.identifier(), computed: false }; 16159 } else { 16160 this.throwError('IMPOSSIBLE'); 16161 } 16162 } 16163 return primary; 16164 }, 16165 16166 filter: function(baseExpression) { 16167 var args = [baseExpression]; 16168 var result = {type: AST.CallExpression, callee: this.identifier(), arguments: args, filter: true}; 16169 16170 while (this.expect(':')) { 16171 args.push(this.expression()); 16172 } 16173 16174 return result; 16175 }, 16176 16177 parseArguments: function() { 16178 var args = []; 16179 if (this.peekToken().text !== ')') { 16180 do { 16181 args.push(this.filterChain()); 16182 } while (this.expect(',')); 16183 } 16184 return args; 16185 }, 16186 16187 identifier: function() { 16188 var token = this.consume(); 16189 if (!token.identifier) { 16190 this.throwError('is not a valid identifier', token); 16191 } 16192 return { type: AST.Identifier, name: token.text }; 16193 }, 16194 16195 constant: function() { 16196 // TODO check that it is a constant 16197 return { type: AST.Literal, value: this.consume().value }; 16198 }, 16199 16200 arrayDeclaration: function() { 16201 var elements = []; 16202 if (this.peekToken().text !== ']') { 16203 do { 16204 if (this.peek(']')) { 16205 // Support trailing commas per ES5.1. 16206 break; 16207 } 16208 elements.push(this.expression()); 16209 } while (this.expect(',')); 16210 } 16211 this.consume(']'); 16212 16213 return { type: AST.ArrayExpression, elements: elements }; 16214 }, 16215 16216 object: function() { 16217 var properties = [], property; 16218 if (this.peekToken().text !== '}') { 16219 do { 16220 if (this.peek('}')) { 16221 // Support trailing commas per ES5.1. 16222 break; 16223 } 16224 property = {type: AST.Property, kind: 'init'}; 16225 if (this.peek().constant) { 16226 property.key = this.constant(); 16227 property.computed = false; 16228 this.consume(':'); 16229 property.value = this.expression(); 16230 } else if (this.peek().identifier) { 16231 property.key = this.identifier(); 16232 property.computed = false; 16233 if (this.peek(':')) { 16234 this.consume(':'); 16235 property.value = this.expression(); 16236 } else { 16237 property.value = property.key; 16238 } 16239 } else if (this.peek('[')) { 16240 this.consume('['); 16241 property.key = this.expression(); 16242 this.consume(']'); 16243 property.computed = true; 16244 this.consume(':'); 16245 property.value = this.expression(); 16246 } else { 16247 this.throwError('invalid key', this.peek()); 16248 } 16249 properties.push(property); 16250 } while (this.expect(',')); 16251 } 16252 this.consume('}'); 16253 16254 return {type: AST.ObjectExpression, properties: properties }; 16255 }, 16256 16257 throwError: function(msg, token) { 16258 throw $parseMinErr('syntax', 16259 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].', 16260 token.text, msg, (token.index + 1), this.text, this.text.substring(token.index)); 16261 }, 16262 16263 consume: function(e1) { 16264 if (this.tokens.length === 0) { 16265 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); 16266 } 16267 16268 var token = this.expect(e1); 16269 if (!token) { 16270 this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); 16271 } 16272 return token; 16273 }, 16274 16275 peekToken: function() { 16276 if (this.tokens.length === 0) { 16277 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); 16278 } 16279 return this.tokens[0]; 16280 }, 16281 16282 peek: function(e1, e2, e3, e4) { 16283 return this.peekAhead(0, e1, e2, e3, e4); 16284 }, 16285 16286 peekAhead: function(i, e1, e2, e3, e4) { 16287 if (this.tokens.length > i) { 16288 var token = this.tokens[i]; 16289 var t = token.text; 16290 if (t === e1 || t === e2 || t === e3 || t === e4 || 16291 (!e1 && !e2 && !e3 && !e4)) { 16292 return token; 16293 } 16294 } 16295 return false; 16296 }, 16297 16298 expect: function(e1, e2, e3, e4) { 16299 var token = this.peek(e1, e2, e3, e4); 16300 if (token) { 16301 this.tokens.shift(); 16302 return token; 16303 } 16304 return false; 16305 }, 16306 16307 selfReferential: { 16308 'this': {type: AST.ThisExpression }, 16309 '$locals': {type: AST.LocalsExpression } 16310 } 16311}; 16312 16313function ifDefined(v, d) { 16314 return typeof v !== 'undefined' ? v : d; 16315} 16316 16317function plusFn(l, r) { 16318 if (typeof l === 'undefined') return r; 16319 if (typeof r === 'undefined') return l; 16320 return l + r; 16321} 16322 16323function isStateless($filter, filterName) { 16324 var fn = $filter(filterName); 16325 return !fn.$stateful; 16326} 16327 16328var PURITY_ABSOLUTE = 1; 16329var PURITY_RELATIVE = 2; 16330 16331// Detect nodes which could depend on non-shallow state of objects 16332function isPure(node, parentIsPure) { 16333 switch (node.type) { 16334 // Computed members might invoke a stateful toString() 16335 case AST.MemberExpression: 16336 if (node.computed) { 16337 return false; 16338 } 16339 break; 16340 16341 // Unary always convert to primative 16342 case AST.UnaryExpression: 16343 return PURITY_ABSOLUTE; 16344 16345 // The binary + operator can invoke a stateful toString(). 16346 case AST.BinaryExpression: 16347 return node.operator !== '+' ? PURITY_ABSOLUTE : false; 16348 16349 // Functions / filters probably read state from within objects 16350 case AST.CallExpression: 16351 return false; 16352 } 16353 16354 return (undefined === parentIsPure) ? PURITY_RELATIVE : parentIsPure; 16355} 16356 16357function findConstantAndWatchExpressions(ast, $filter, parentIsPure) { 16358 var allConstants; 16359 var argsToWatch; 16360 var isStatelessFilter; 16361 16362 var astIsPure = ast.isPure = isPure(ast, parentIsPure); 16363 16364 switch (ast.type) { 16365 case AST.Program: 16366 allConstants = true; 16367 forEach(ast.body, function(expr) { 16368 findConstantAndWatchExpressions(expr.expression, $filter, astIsPure); 16369 allConstants = allConstants && expr.expression.constant; 16370 }); 16371 ast.constant = allConstants; 16372 break; 16373 case AST.Literal: 16374 ast.constant = true; 16375 ast.toWatch = []; 16376 break; 16377 case AST.UnaryExpression: 16378 findConstantAndWatchExpressions(ast.argument, $filter, astIsPure); 16379 ast.constant = ast.argument.constant; 16380 ast.toWatch = ast.argument.toWatch; 16381 break; 16382 case AST.BinaryExpression: 16383 findConstantAndWatchExpressions(ast.left, $filter, astIsPure); 16384 findConstantAndWatchExpressions(ast.right, $filter, astIsPure); 16385 ast.constant = ast.left.constant && ast.right.constant; 16386 ast.toWatch = ast.left.toWatch.concat(ast.right.toWatch); 16387 break; 16388 case AST.LogicalExpression: 16389 findConstantAndWatchExpressions(ast.left, $filter, astIsPure); 16390 findConstantAndWatchExpressions(ast.right, $filter, astIsPure); 16391 ast.constant = ast.left.constant && ast.right.constant; 16392 ast.toWatch = ast.constant ? [] : [ast]; 16393 break; 16394 case AST.ConditionalExpression: 16395 findConstantAndWatchExpressions(ast.test, $filter, astIsPure); 16396 findConstantAndWatchExpressions(ast.alternate, $filter, astIsPure); 16397 findConstantAndWatchExpressions(ast.consequent, $filter, astIsPure); 16398 ast.constant = ast.test.constant && ast.alternate.constant && ast.consequent.constant; 16399 ast.toWatch = ast.constant ? [] : [ast]; 16400 break; 16401 case AST.Identifier: 16402 ast.constant = false; 16403 ast.toWatch = [ast]; 16404 break; 16405 case AST.MemberExpression: 16406 findConstantAndWatchExpressions(ast.object, $filter, astIsPure); 16407 if (ast.computed) { 16408 findConstantAndWatchExpressions(ast.property, $filter, astIsPure); 16409 } 16410 ast.constant = ast.object.constant && (!ast.computed || ast.property.constant); 16411 ast.toWatch = ast.constant ? [] : [ast]; 16412 break; 16413 case AST.CallExpression: 16414 isStatelessFilter = ast.filter ? isStateless($filter, ast.callee.name) : false; 16415 allConstants = isStatelessFilter; 16416 argsToWatch = []; 16417 forEach(ast.arguments, function(expr) { 16418 findConstantAndWatchExpressions(expr, $filter, astIsPure); 16419 allConstants = allConstants && expr.constant; 16420 argsToWatch.push.apply(argsToWatch, expr.toWatch); 16421 }); 16422 ast.constant = allConstants; 16423 ast.toWatch = isStatelessFilter ? argsToWatch : [ast]; 16424 break; 16425 case AST.AssignmentExpression: 16426 findConstantAndWatchExpressions(ast.left, $filter, astIsPure); 16427 findConstantAndWatchExpressions(ast.right, $filter, astIsPure); 16428 ast.constant = ast.left.constant && ast.right.constant; 16429 ast.toWatch = [ast]; 16430 break; 16431 case AST.ArrayExpression: 16432 allConstants = true; 16433 argsToWatch = []; 16434 forEach(ast.elements, function(expr) { 16435 findConstantAndWatchExpressions(expr, $filter, astIsPure); 16436 allConstants = allConstants && expr.constant; 16437 argsToWatch.push.apply(argsToWatch, expr.toWatch); 16438 }); 16439 ast.constant = allConstants; 16440 ast.toWatch = argsToWatch; 16441 break; 16442 case AST.ObjectExpression: 16443 allConstants = true; 16444 argsToWatch = []; 16445 forEach(ast.properties, function(property) { 16446 findConstantAndWatchExpressions(property.value, $filter, astIsPure); 16447 allConstants = allConstants && property.value.constant; 16448 argsToWatch.push.apply(argsToWatch, property.value.toWatch); 16449 if (property.computed) { 16450 //`{[key]: value}` implicitly does `key.toString()` which may be non-pure 16451 findConstantAndWatchExpressions(property.key, $filter, /*parentIsPure=*/false); 16452 allConstants = allConstants && property.key.constant; 16453 argsToWatch.push.apply(argsToWatch, property.key.toWatch); 16454 } 16455 }); 16456 ast.constant = allConstants; 16457 ast.toWatch = argsToWatch; 16458 break; 16459 case AST.ThisExpression: 16460 ast.constant = false; 16461 ast.toWatch = []; 16462 break; 16463 case AST.LocalsExpression: 16464 ast.constant = false; 16465 ast.toWatch = []; 16466 break; 16467 } 16468} 16469 16470function getInputs(body) { 16471 if (body.length !== 1) return; 16472 var lastExpression = body[0].expression; 16473 var candidate = lastExpression.toWatch; 16474 if (candidate.length !== 1) return candidate; 16475 return candidate[0] !== lastExpression ? candidate : undefined; 16476} 16477 16478function isAssignable(ast) { 16479 return ast.type === AST.Identifier || ast.type === AST.MemberExpression; 16480} 16481 16482function assignableAST(ast) { 16483 if (ast.body.length === 1 && isAssignable(ast.body[0].expression)) { 16484 return {type: AST.AssignmentExpression, left: ast.body[0].expression, right: {type: AST.NGValueParameter}, operator: '='}; 16485 } 16486} 16487 16488function isLiteral(ast) { 16489 return ast.body.length === 0 || 16490 ast.body.length === 1 && ( 16491 ast.body[0].expression.type === AST.Literal || 16492 ast.body[0].expression.type === AST.ArrayExpression || 16493 ast.body[0].expression.type === AST.ObjectExpression); 16494} 16495 16496function isConstant(ast) { 16497 return ast.constant; 16498} 16499 16500function ASTCompiler($filter) { 16501 this.$filter = $filter; 16502} 16503 16504ASTCompiler.prototype = { 16505 compile: function(ast) { 16506 var self = this; 16507 this.state = { 16508 nextId: 0, 16509 filters: {}, 16510 fn: {vars: [], body: [], own: {}}, 16511 assign: {vars: [], body: [], own: {}}, 16512 inputs: [] 16513 }; 16514 findConstantAndWatchExpressions(ast, self.$filter); 16515 var extra = ''; 16516 var assignable; 16517 this.stage = 'assign'; 16518 if ((assignable = assignableAST(ast))) { 16519 this.state.computing = 'assign'; 16520 var result = this.nextId(); 16521 this.recurse(assignable, result); 16522 this.return_(result); 16523 extra = 'fn.assign=' + this.generateFunction('assign', 's,v,l'); 16524 } 16525 var toWatch = getInputs(ast.body); 16526 self.stage = 'inputs'; 16527 forEach(toWatch, function(watch, key) { 16528 var fnKey = 'fn' + key; 16529 self.state[fnKey] = {vars: [], body: [], own: {}}; 16530 self.state.computing = fnKey; 16531 var intoId = self.nextId(); 16532 self.recurse(watch, intoId); 16533 self.return_(intoId); 16534 self.state.inputs.push({name: fnKey, isPure: watch.isPure}); 16535 watch.watchId = key; 16536 }); 16537 this.state.computing = 'fn'; 16538 this.stage = 'main'; 16539 this.recurse(ast); 16540 var fnString = 16541 // The build and minification steps remove the string "use strict" from the code, but this is done using a regex. 16542 // This is a workaround for this until we do a better job at only removing the prefix only when we should. 16543 '"' + this.USE + ' ' + this.STRICT + '";\n' + 16544 this.filterPrefix() + 16545 'var fn=' + this.generateFunction('fn', 's,l,a,i') + 16546 extra + 16547 this.watchFns() + 16548 'return fn;'; 16549 16550 // eslint-disable-next-line no-new-func 16551 var fn = (new Function('$filter', 16552 'getStringValue', 16553 'ifDefined', 16554 'plus', 16555 fnString))( 16556 this.$filter, 16557 getStringValue, 16558 ifDefined, 16559 plusFn); 16560 this.state = this.stage = undefined; 16561 return fn; 16562 }, 16563 16564 USE: 'use', 16565 16566 STRICT: 'strict', 16567 16568 watchFns: function() { 16569 var result = []; 16570 var inputs = this.state.inputs; 16571 var self = this; 16572 forEach(inputs, function(input) { 16573 result.push('var ' + input.name + '=' + self.generateFunction(input.name, 's')); 16574 if (input.isPure) { 16575 result.push(input.name, '.isPure=' + JSON.stringify(input.isPure) + ';'); 16576 } 16577 }); 16578 if (inputs.length) { 16579 result.push('fn.inputs=[' + inputs.map(function(i) { return i.name; }).join(',') + '];'); 16580 } 16581 return result.join(''); 16582 }, 16583 16584 generateFunction: function(name, params) { 16585 return 'function(' + params + '){' + 16586 this.varsPrefix(name) + 16587 this.body(name) + 16588 '};'; 16589 }, 16590 16591 filterPrefix: function() { 16592 var parts = []; 16593 var self = this; 16594 forEach(this.state.filters, function(id, filter) { 16595 parts.push(id + '=$filter(' + self.escape(filter) + ')'); 16596 }); 16597 if (parts.length) return 'var ' + parts.join(',') + ';'; 16598 return ''; 16599 }, 16600 16601 varsPrefix: function(section) { 16602 return this.state[section].vars.length ? 'var ' + this.state[section].vars.join(',') + ';' : ''; 16603 }, 16604 16605 body: function(section) { 16606 return this.state[section].body.join(''); 16607 }, 16608 16609 recurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) { 16610 var left, right, self = this, args, expression, computed; 16611 recursionFn = recursionFn || noop; 16612 if (!skipWatchIdCheck && isDefined(ast.watchId)) { 16613 intoId = intoId || this.nextId(); 16614 this.if_('i', 16615 this.lazyAssign(intoId, this.computedMember('i', ast.watchId)), 16616 this.lazyRecurse(ast, intoId, nameId, recursionFn, create, true) 16617 ); 16618 return; 16619 } 16620 switch (ast.type) { 16621 case AST.Program: 16622 forEach(ast.body, function(expression, pos) { 16623 self.recurse(expression.expression, undefined, undefined, function(expr) { right = expr; }); 16624 if (pos !== ast.body.length - 1) { 16625 self.current().body.push(right, ';'); 16626 } else { 16627 self.return_(right); 16628 } 16629 }); 16630 break; 16631 case AST.Literal: 16632 expression = this.escape(ast.value); 16633 this.assign(intoId, expression); 16634 recursionFn(intoId || expression); 16635 break; 16636 case AST.UnaryExpression: 16637 this.recurse(ast.argument, undefined, undefined, function(expr) { right = expr; }); 16638 expression = ast.operator + '(' + this.ifDefined(right, 0) + ')'; 16639 this.assign(intoId, expression); 16640 recursionFn(expression); 16641 break; 16642 case AST.BinaryExpression: 16643 this.recurse(ast.left, undefined, undefined, function(expr) { left = expr; }); 16644 this.recurse(ast.right, undefined, undefined, function(expr) { right = expr; }); 16645 if (ast.operator === '+') { 16646 expression = this.plus(left, right); 16647 } else if (ast.operator === '-') { 16648 expression = this.ifDefined(left, 0) + ast.operator + this.ifDefined(right, 0); 16649 } else { 16650 expression = '(' + left + ')' + ast.operator + '(' + right + ')'; 16651 } 16652 this.assign(intoId, expression); 16653 recursionFn(expression); 16654 break; 16655 case AST.LogicalExpression: 16656 intoId = intoId || this.nextId(); 16657 self.recurse(ast.left, intoId); 16658 self.if_(ast.operator === '&&' ? intoId : self.not(intoId), self.lazyRecurse(ast.right, intoId)); 16659 recursionFn(intoId); 16660 break; 16661 case AST.ConditionalExpression: 16662 intoId = intoId || this.nextId(); 16663 self.recurse(ast.test, intoId); 16664 self.if_(intoId, self.lazyRecurse(ast.alternate, intoId), self.lazyRecurse(ast.consequent, intoId)); 16665 recursionFn(intoId); 16666 break; 16667 case AST.Identifier: 16668 intoId = intoId || this.nextId(); 16669 if (nameId) { 16670 nameId.context = self.stage === 'inputs' ? 's' : this.assign(this.nextId(), this.getHasOwnProperty('l', ast.name) + '?l:s'); 16671 nameId.computed = false; 16672 nameId.name = ast.name; 16673 } 16674 self.if_(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)), 16675 function() { 16676 self.if_(self.stage === 'inputs' || 's', function() { 16677 if (create && create !== 1) { 16678 self.if_( 16679 self.isNull(self.nonComputedMember('s', ast.name)), 16680 self.lazyAssign(self.nonComputedMember('s', ast.name), '{}')); 16681 } 16682 self.assign(intoId, self.nonComputedMember('s', ast.name)); 16683 }); 16684 }, intoId && self.lazyAssign(intoId, self.nonComputedMember('l', ast.name)) 16685 ); 16686 recursionFn(intoId); 16687 break; 16688 case AST.MemberExpression: 16689 left = nameId && (nameId.context = this.nextId()) || this.nextId(); 16690 intoId = intoId || this.nextId(); 16691 self.recurse(ast.object, left, undefined, function() { 16692 self.if_(self.notNull(left), function() { 16693 if (ast.computed) { 16694 right = self.nextId(); 16695 self.recurse(ast.property, right); 16696 self.getStringValue(right); 16697 if (create && create !== 1) { 16698 self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}')); 16699 } 16700 expression = self.computedMember(left, right); 16701 self.assign(intoId, expression); 16702 if (nameId) { 16703 nameId.computed = true; 16704 nameId.name = right; 16705 } 16706 } else { 16707 if (create && create !== 1) { 16708 self.if_(self.isNull(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}')); 16709 } 16710 expression = self.nonComputedMember(left, ast.property.name); 16711 self.assign(intoId, expression); 16712 if (nameId) { 16713 nameId.computed = false; 16714 nameId.name = ast.property.name; 16715 } 16716 } 16717 }, function() { 16718 self.assign(intoId, 'undefined'); 16719 }); 16720 recursionFn(intoId); 16721 }, !!create); 16722 break; 16723 case AST.CallExpression: 16724 intoId = intoId || this.nextId(); 16725 if (ast.filter) { 16726 right = self.filter(ast.callee.name); 16727 args = []; 16728 forEach(ast.arguments, function(expr) { 16729 var argument = self.nextId(); 16730 self.recurse(expr, argument); 16731 args.push(argument); 16732 }); 16733 expression = right + '(' + args.join(',') + ')'; 16734 self.assign(intoId, expression); 16735 recursionFn(intoId); 16736 } else { 16737 right = self.nextId(); 16738 left = {}; 16739 args = []; 16740 self.recurse(ast.callee, right, left, function() { 16741 self.if_(self.notNull(right), function() { 16742 forEach(ast.arguments, function(expr) { 16743 self.recurse(expr, ast.constant ? undefined : self.nextId(), undefined, function(argument) { 16744 args.push(argument); 16745 }); 16746 }); 16747 if (left.name) { 16748 expression = self.member(left.context, left.name, left.computed) + '(' + args.join(',') + ')'; 16749 } else { 16750 expression = right + '(' + args.join(',') + ')'; 16751 } 16752 self.assign(intoId, expression); 16753 }, function() { 16754 self.assign(intoId, 'undefined'); 16755 }); 16756 recursionFn(intoId); 16757 }); 16758 } 16759 break; 16760 case AST.AssignmentExpression: 16761 right = this.nextId(); 16762 left = {}; 16763 this.recurse(ast.left, undefined, left, function() { 16764 self.if_(self.notNull(left.context), function() { 16765 self.recurse(ast.right, right); 16766 expression = self.member(left.context, left.name, left.computed) + ast.operator + right; 16767 self.assign(intoId, expression); 16768 recursionFn(intoId || expression); 16769 }); 16770 }, 1); 16771 break; 16772 case AST.ArrayExpression: 16773 args = []; 16774 forEach(ast.elements, function(expr) { 16775 self.recurse(expr, ast.constant ? undefined : self.nextId(), undefined, function(argument) { 16776 args.push(argument); 16777 }); 16778 }); 16779 expression = '[' + args.join(',') + ']'; 16780 this.assign(intoId, expression); 16781 recursionFn(intoId || expression); 16782 break; 16783 case AST.ObjectExpression: 16784 args = []; 16785 computed = false; 16786 forEach(ast.properties, function(property) { 16787 if (property.computed) { 16788 computed = true; 16789 } 16790 }); 16791 if (computed) { 16792 intoId = intoId || this.nextId(); 16793 this.assign(intoId, '{}'); 16794 forEach(ast.properties, function(property) { 16795 if (property.computed) { 16796 left = self.nextId(); 16797 self.recurse(property.key, left); 16798 } else { 16799 left = property.key.type === AST.Identifier ? 16800 property.key.name : 16801 ('' + property.key.value); 16802 } 16803 right = self.nextId(); 16804 self.recurse(property.value, right); 16805 self.assign(self.member(intoId, left, property.computed), right); 16806 }); 16807 } else { 16808 forEach(ast.properties, function(property) { 16809 self.recurse(property.value, ast.constant ? undefined : self.nextId(), undefined, function(expr) { 16810 args.push(self.escape( 16811 property.key.type === AST.Identifier ? property.key.name : 16812 ('' + property.key.value)) + 16813 ':' + expr); 16814 }); 16815 }); 16816 expression = '{' + args.join(',') + '}'; 16817 this.assign(intoId, expression); 16818 } 16819 recursionFn(intoId || expression); 16820 break; 16821 case AST.ThisExpression: 16822 this.assign(intoId, 's'); 16823 recursionFn(intoId || 's'); 16824 break; 16825 case AST.LocalsExpression: 16826 this.assign(intoId, 'l'); 16827 recursionFn(intoId || 'l'); 16828 break; 16829 case AST.NGValueParameter: 16830 this.assign(intoId, 'v'); 16831 recursionFn(intoId || 'v'); 16832 break; 16833 } 16834 }, 16835 16836 getHasOwnProperty: function(element, property) { 16837 var key = element + '.' + property; 16838 var own = this.current().own; 16839 if (!own.hasOwnProperty(key)) { 16840 own[key] = this.nextId(false, element + '&&(' + this.escape(property) + ' in ' + element + ')'); 16841 } 16842 return own[key]; 16843 }, 16844 16845 assign: function(id, value) { 16846 if (!id) return; 16847 this.current().body.push(id, '=', value, ';'); 16848 return id; 16849 }, 16850 16851 filter: function(filterName) { 16852 if (!this.state.filters.hasOwnProperty(filterName)) { 16853 this.state.filters[filterName] = this.nextId(true); 16854 } 16855 return this.state.filters[filterName]; 16856 }, 16857 16858 ifDefined: function(id, defaultValue) { 16859 return 'ifDefined(' + id + ',' + this.escape(defaultValue) + ')'; 16860 }, 16861 16862 plus: function(left, right) { 16863 return 'plus(' + left + ',' + right + ')'; 16864 }, 16865 16866 return_: function(id) { 16867 this.current().body.push('return ', id, ';'); 16868 }, 16869 16870 if_: function(test, alternate, consequent) { 16871 if (test === true) { 16872 alternate(); 16873 } else { 16874 var body = this.current().body; 16875 body.push('if(', test, '){'); 16876 alternate(); 16877 body.push('}'); 16878 if (consequent) { 16879 body.push('else{'); 16880 consequent(); 16881 body.push('}'); 16882 } 16883 } 16884 }, 16885 16886 not: function(expression) { 16887 return '!(' + expression + ')'; 16888 }, 16889 16890 isNull: function(expression) { 16891 return expression + '==null'; 16892 }, 16893 16894 notNull: function(expression) { 16895 return expression + '!=null'; 16896 }, 16897 16898 nonComputedMember: function(left, right) { 16899 var SAFE_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/; 16900 var UNSAFE_CHARACTERS = /[^$_a-zA-Z0-9]/g; 16901 if (SAFE_IDENTIFIER.test(right)) { 16902 return left + '.' + right; 16903 } else { 16904 return left + '["' + right.replace(UNSAFE_CHARACTERS, this.stringEscapeFn) + '"]'; 16905 } 16906 }, 16907 16908 computedMember: function(left, right) { 16909 return left + '[' + right + ']'; 16910 }, 16911 16912 member: function(left, right, computed) { 16913 if (computed) return this.computedMember(left, right); 16914 return this.nonComputedMember(left, right); 16915 }, 16916 16917 getStringValue: function(item) { 16918 this.assign(item, 'getStringValue(' + item + ')'); 16919 }, 16920 16921 lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) { 16922 var self = this; 16923 return function() { 16924 self.recurse(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck); 16925 }; 16926 }, 16927 16928 lazyAssign: function(id, value) { 16929 var self = this; 16930 return function() { 16931 self.assign(id, value); 16932 }; 16933 }, 16934 16935 stringEscapeRegex: /[^ a-zA-Z0-9]/g, 16936 16937 stringEscapeFn: function(c) { 16938 return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4); 16939 }, 16940 16941 escape: function(value) { 16942 if (isString(value)) return '\'' + value.replace(this.stringEscapeRegex, this.stringEscapeFn) + '\''; 16943 if (isNumber(value)) return value.toString(); 16944 if (value === true) return 'true'; 16945 if (value === false) return 'false'; 16946 if (value === null) return 'null'; 16947 if (typeof value === 'undefined') return 'undefined'; 16948 16949 throw $parseMinErr('esc', 'IMPOSSIBLE'); 16950 }, 16951 16952 nextId: function(skip, init) { 16953 var id = 'v' + (this.state.nextId++); 16954 if (!skip) { 16955 this.current().vars.push(id + (init ? '=' + init : '')); 16956 } 16957 return id; 16958 }, 16959 16960 current: function() { 16961 return this.state[this.state.computing]; 16962 } 16963}; 16964 16965 16966function ASTInterpreter($filter) { 16967 this.$filter = $filter; 16968} 16969 16970ASTInterpreter.prototype = { 16971 compile: function(ast) { 16972 var self = this; 16973 findConstantAndWatchExpressions(ast, self.$filter); 16974 var assignable; 16975 var assign; 16976 if ((assignable = assignableAST(ast))) { 16977 assign = this.recurse(assignable); 16978 } 16979 var toWatch = getInputs(ast.body); 16980 var inputs; 16981 if (toWatch) { 16982 inputs = []; 16983 forEach(toWatch, function(watch, key) { 16984 var input = self.recurse(watch); 16985 input.isPure = watch.isPure; 16986 watch.input = input; 16987 inputs.push(input); 16988 watch.watchId = key; 16989 }); 16990 } 16991 var expressions = []; 16992 forEach(ast.body, function(expression) { 16993 expressions.push(self.recurse(expression.expression)); 16994 }); 16995 var fn = ast.body.length === 0 ? noop : 16996 ast.body.length === 1 ? expressions[0] : 16997 function(scope, locals) { 16998 var lastValue; 16999 forEach(expressions, function(exp) { 17000 lastValue = exp(scope, locals); 17001 }); 17002 return lastValue; 17003 }; 17004 if (assign) { 17005 fn.assign = function(scope, value, locals) { 17006 return assign(scope, locals, value); 17007 }; 17008 } 17009 if (inputs) { 17010 fn.inputs = inputs; 17011 } 17012 return fn; 17013 }, 17014 17015 recurse: function(ast, context, create) { 17016 var left, right, self = this, args; 17017 if (ast.input) { 17018 return this.inputs(ast.input, ast.watchId); 17019 } 17020 switch (ast.type) { 17021 case AST.Literal: 17022 return this.value(ast.value, context); 17023 case AST.UnaryExpression: 17024 right = this.recurse(ast.argument); 17025 return this['unary' + ast.operator](right, context); 17026 case AST.BinaryExpression: 17027 left = this.recurse(ast.left); 17028 right = this.recurse(ast.right); 17029 return this['binary' + ast.operator](left, right, context); 17030 case AST.LogicalExpression: 17031 left = this.recurse(ast.left); 17032 right = this.recurse(ast.right); 17033 return this['binary' + ast.operator](left, right, context); 17034 case AST.ConditionalExpression: 17035 return this['ternary?:']( 17036 this.recurse(ast.test), 17037 this.recurse(ast.alternate), 17038 this.recurse(ast.consequent), 17039 context 17040 ); 17041 case AST.Identifier: 17042 return self.identifier(ast.name, context, create); 17043 case AST.MemberExpression: 17044 left = this.recurse(ast.object, false, !!create); 17045 if (!ast.computed) { 17046 right = ast.property.name; 17047 } 17048 if (ast.computed) right = this.recurse(ast.property); 17049 return ast.computed ? 17050 this.computedMember(left, right, context, create) : 17051 this.nonComputedMember(left, right, context, create); 17052 case AST.CallExpression: 17053 args = []; 17054 forEach(ast.arguments, function(expr) { 17055 args.push(self.recurse(expr)); 17056 }); 17057 if (ast.filter) right = this.$filter(ast.callee.name); 17058 if (!ast.filter) right = this.recurse(ast.callee, true); 17059 return ast.filter ? 17060 function(scope, locals, assign, inputs) { 17061 var values = []; 17062 for (var i = 0; i < args.length; ++i) { 17063 values.push(args[i](scope, locals, assign, inputs)); 17064 } 17065 var value = right.apply(undefined, values, inputs); 17066 return context ? {context: undefined, name: undefined, value: value} : value; 17067 } : 17068 function(scope, locals, assign, inputs) { 17069 var rhs = right(scope, locals, assign, inputs); 17070 var value; 17071 if (rhs.value != null) { 17072 var values = []; 17073 for (var i = 0; i < args.length; ++i) { 17074 values.push(args[i](scope, locals, assign, inputs)); 17075 } 17076 value = rhs.value.apply(rhs.context, values); 17077 } 17078 return context ? {value: value} : value; 17079 }; 17080 case AST.AssignmentExpression: 17081 left = this.recurse(ast.left, true, 1); 17082 right = this.recurse(ast.right); 17083 return function(scope, locals, assign, inputs) { 17084 var lhs = left(scope, locals, assign, inputs); 17085 var rhs = right(scope, locals, assign, inputs); 17086 lhs.context[lhs.name] = rhs; 17087 return context ? {value: rhs} : rhs; 17088 }; 17089 case AST.ArrayExpression: 17090 args = []; 17091 forEach(ast.elements, function(expr) { 17092 args.push(self.recurse(expr)); 17093 }); 17094 return function(scope, locals, assign, inputs) { 17095 var value = []; 17096 for (var i = 0; i < args.length; ++i) { 17097 value.push(args[i](scope, locals, assign, inputs)); 17098 } 17099 return context ? {value: value} : value; 17100 }; 17101 case AST.ObjectExpression: 17102 args = []; 17103 forEach(ast.properties, function(property) { 17104 if (property.computed) { 17105 args.push({key: self.recurse(property.key), 17106 computed: true, 17107 value: self.recurse(property.value) 17108 }); 17109 } else { 17110 args.push({key: property.key.type === AST.Identifier ? 17111 property.key.name : 17112 ('' + property.key.value), 17113 computed: false, 17114 value: self.recurse(property.value) 17115 }); 17116 } 17117 }); 17118 return function(scope, locals, assign, inputs) { 17119 var value = {}; 17120 for (var i = 0; i < args.length; ++i) { 17121 if (args[i].computed) { 17122 value[args[i].key(scope, locals, assign, inputs)] = args[i].value(scope, locals, assign, inputs); 17123 } else { 17124 value[args[i].key] = args[i].value(scope, locals, assign, inputs); 17125 } 17126 } 17127 return context ? {value: value} : value; 17128 }; 17129 case AST.ThisExpression: 17130 return function(scope) { 17131 return context ? {value: scope} : scope; 17132 }; 17133 case AST.LocalsExpression: 17134 return function(scope, locals) { 17135 return context ? {value: locals} : locals; 17136 }; 17137 case AST.NGValueParameter: 17138 return function(scope, locals, assign) { 17139 return context ? {value: assign} : assign; 17140 }; 17141 } 17142 }, 17143 17144 'unary+': function(argument, context) { 17145 return function(scope, locals, assign, inputs) { 17146 var arg = argument(scope, locals, assign, inputs); 17147 if (isDefined(arg)) { 17148 arg = +arg; 17149 } else { 17150 arg = 0; 17151 } 17152 return context ? {value: arg} : arg; 17153 }; 17154 }, 17155 'unary-': function(argument, context) { 17156 return function(scope, locals, assign, inputs) { 17157 var arg = argument(scope, locals, assign, inputs); 17158 if (isDefined(arg)) { 17159 arg = -arg; 17160 } else { 17161 arg = -0; 17162 } 17163 return context ? {value: arg} : arg; 17164 }; 17165 }, 17166 'unary!': function(argument, context) { 17167 return function(scope, locals, assign, inputs) { 17168 var arg = !argument(scope, locals, assign, inputs); 17169 return context ? {value: arg} : arg; 17170 }; 17171 }, 17172 'binary+': function(left, right, context) { 17173 return function(scope, locals, assign, inputs) { 17174 var lhs = left(scope, locals, assign, inputs); 17175 var rhs = right(scope, locals, assign, inputs); 17176 var arg = plusFn(lhs, rhs); 17177 return context ? {value: arg} : arg; 17178 }; 17179 }, 17180 'binary-': function(left, right, context) { 17181 return function(scope, locals, assign, inputs) { 17182 var lhs = left(scope, locals, assign, inputs); 17183 var rhs = right(scope, locals, assign, inputs); 17184 var arg = (isDefined(lhs) ? lhs : 0) - (isDefined(rhs) ? rhs : 0); 17185 return context ? {value: arg} : arg; 17186 }; 17187 }, 17188 'binary*': function(left, right, context) { 17189 return function(scope, locals, assign, inputs) { 17190 var arg = left(scope, locals, assign, inputs) * right(scope, locals, assign, inputs); 17191 return context ? {value: arg} : arg; 17192 }; 17193 }, 17194 'binary/': function(left, right, context) { 17195 return function(scope, locals, assign, inputs) { 17196 var arg = left(scope, locals, assign, inputs) / right(scope, locals, assign, inputs); 17197 return context ? {value: arg} : arg; 17198 }; 17199 }, 17200 'binary%': function(left, right, context) { 17201 return function(scope, locals, assign, inputs) { 17202 var arg = left(scope, locals, assign, inputs) % right(scope, locals, assign, inputs); 17203 return context ? {value: arg} : arg; 17204 }; 17205 }, 17206 'binary===': function(left, right, context) { 17207 return function(scope, locals, assign, inputs) { 17208 var arg = left(scope, locals, assign, inputs) === right(scope, locals, assign, inputs); 17209 return context ? {value: arg} : arg; 17210 }; 17211 }, 17212 'binary!==': function(left, right, context) { 17213 return function(scope, locals, assign, inputs) { 17214 var arg = left(scope, locals, assign, inputs) !== right(scope, locals, assign, inputs); 17215 return context ? {value: arg} : arg; 17216 }; 17217 }, 17218 'binary==': function(left, right, context) { 17219 return function(scope, locals, assign, inputs) { 17220 // eslint-disable-next-line eqeqeq 17221 var arg = left(scope, locals, assign, inputs) == right(scope, locals, assign, inputs); 17222 return context ? {value: arg} : arg; 17223 }; 17224 }, 17225 'binary!=': function(left, right, context) { 17226 return function(scope, locals, assign, inputs) { 17227 // eslint-disable-next-line eqeqeq 17228 var arg = left(scope, locals, assign, inputs) != right(scope, locals, assign, inputs); 17229 return context ? {value: arg} : arg; 17230 }; 17231 }, 17232 'binary<': function(left, right, context) { 17233 return function(scope, locals, assign, inputs) { 17234 var arg = left(scope, locals, assign, inputs) < right(scope, locals, assign, inputs); 17235 return context ? {value: arg} : arg; 17236 }; 17237 }, 17238 'binary>': function(left, right, context) { 17239 return function(scope, locals, assign, inputs) { 17240 var arg = left(scope, locals, assign, inputs) > right(scope, locals, assign, inputs); 17241 return context ? {value: arg} : arg; 17242 }; 17243 }, 17244 'binary<=': function(left, right, context) { 17245 return function(scope, locals, assign, inputs) { 17246 var arg = left(scope, locals, assign, inputs) <= right(scope, locals, assign, inputs); 17247 return context ? {value: arg} : arg; 17248 }; 17249 }, 17250 'binary>=': function(left, right, context) { 17251 return function(scope, locals, assign, inputs) { 17252 var arg = left(scope, locals, assign, inputs) >= right(scope, locals, assign, inputs); 17253 return context ? {value: arg} : arg; 17254 }; 17255 }, 17256 'binary&&': function(left, right, context) { 17257 return function(scope, locals, assign, inputs) { 17258 var arg = left(scope, locals, assign, inputs) && right(scope, locals, assign, inputs); 17259 return context ? {value: arg} : arg; 17260 }; 17261 }, 17262 'binary||': function(left, right, context) { 17263 return function(scope, locals, assign, inputs) { 17264 var arg = left(scope, locals, assign, inputs) || right(scope, locals, assign, inputs); 17265 return context ? {value: arg} : arg; 17266 }; 17267 }, 17268 'ternary?:': function(test, alternate, consequent, context) { 17269 return function(scope, locals, assign, inputs) { 17270 var arg = test(scope, locals, assign, inputs) ? alternate(scope, locals, assign, inputs) : consequent(scope, locals, assign, inputs); 17271 return context ? {value: arg} : arg; 17272 }; 17273 }, 17274 value: function(value, context) { 17275 return function() { return context ? {context: undefined, name: undefined, value: value} : value; }; 17276 }, 17277 identifier: function(name, context, create) { 17278 return function(scope, locals, assign, inputs) { 17279 var base = locals && (name in locals) ? locals : scope; 17280 if (create && create !== 1 && base && base[name] == null) { 17281 base[name] = {}; 17282 } 17283 var value = base ? base[name] : undefined; 17284 if (context) { 17285 return {context: base, name: name, value: value}; 17286 } else { 17287 return value; 17288 } 17289 }; 17290 }, 17291 computedMember: function(left, right, context, create) { 17292 return function(scope, locals, assign, inputs) { 17293 var lhs = left(scope, locals, assign, inputs); 17294 var rhs; 17295 var value; 17296 if (lhs != null) { 17297 rhs = right(scope, locals, assign, inputs); 17298 rhs = getStringValue(rhs); 17299 if (create && create !== 1) { 17300 if (lhs && !(lhs[rhs])) { 17301 lhs[rhs] = {}; 17302 } 17303 } 17304 value = lhs[rhs]; 17305 } 17306 if (context) { 17307 return {context: lhs, name: rhs, value: value}; 17308 } else { 17309 return value; 17310 } 17311 }; 17312 }, 17313 nonComputedMember: function(left, right, context, create) { 17314 return function(scope, locals, assign, inputs) { 17315 var lhs = left(scope, locals, assign, inputs); 17316 if (create && create !== 1) { 17317 if (lhs && lhs[right] == null) { 17318 lhs[right] = {}; 17319 } 17320 } 17321 var value = lhs != null ? lhs[right] : undefined; 17322 if (context) { 17323 return {context: lhs, name: right, value: value}; 17324 } else { 17325 return value; 17326 } 17327 }; 17328 }, 17329 inputs: function(input, watchId) { 17330 return function(scope, value, locals, inputs) { 17331 if (inputs) return inputs[watchId]; 17332 return input(scope, value, locals); 17333 }; 17334 } 17335}; 17336 17337/** 17338 * @constructor 17339 */ 17340function Parser(lexer, $filter, options) { 17341 this.ast = new AST(lexer, options); 17342 this.astCompiler = options.csp ? new ASTInterpreter($filter) : 17343 new ASTCompiler($filter); 17344} 17345 17346Parser.prototype = { 17347 constructor: Parser, 17348 17349 parse: function(text) { 17350 var ast = this.getAst(text); 17351 var fn = this.astCompiler.compile(ast.ast); 17352 fn.literal = isLiteral(ast.ast); 17353 fn.constant = isConstant(ast.ast); 17354 fn.oneTime = ast.oneTime; 17355 return fn; 17356 }, 17357 17358 getAst: function(exp) { 17359 var oneTime = false; 17360 exp = exp.trim(); 17361 17362 if (exp.charAt(0) === ':' && exp.charAt(1) === ':') { 17363 oneTime = true; 17364 exp = exp.substring(2); 17365 } 17366 return { 17367 ast: this.ast.ast(exp), 17368 oneTime: oneTime 17369 }; 17370 } 17371}; 17372 17373function getValueOf(value) { 17374 return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value); 17375} 17376 17377/////////////////////////////////// 17378 17379/** 17380 * @ngdoc service 17381 * @name $parse 17382 * @kind function 17383 * 17384 * @description 17385 * 17386 * Converts AngularJS {@link guide/expression expression} into a function. 17387 * 17388 * ```js 17389 * var getter = $parse('user.name'); 17390 * var setter = getter.assign; 17391 * var context = {user:{name:'AngularJS'}}; 17392 * var locals = {user:{name:'local'}}; 17393 * 17394 * expect(getter(context)).toEqual('AngularJS'); 17395 * setter(context, 'newValue'); 17396 * expect(context.user.name).toEqual('newValue'); 17397 * expect(getter(context, locals)).toEqual('local'); 17398 * ``` 17399 * 17400 * 17401 * @param {string} expression String expression to compile. 17402 * @returns {function(context, locals)} a function which represents the compiled expression: 17403 * 17404 * * `context` â `{object}` â an object against which any expressions embedded in the strings 17405 * are evaluated against (typically a scope object). 17406 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 17407 * `context`. 17408 * 17409 * The returned function also has the following properties: 17410 * * `literal` â `{boolean}` â whether the expression's top-level node is a JavaScript 17411 * literal. 17412 * * `constant` â `{boolean}` â whether the expression is made entirely of JavaScript 17413 * constant literals. 17414 * * `assign` â `{?function(context, value)}` â if the expression is assignable, this will be 17415 * set to a function to change its value on the given context. 17416 * 17417 */ 17418 17419 17420/** 17421 * @ngdoc provider 17422 * @name $parseProvider 17423 * @this 17424 * 17425 * @description 17426 * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse} 17427 * service. 17428 */ 17429function $ParseProvider() { 17430 var cache = createMap(); 17431 var literals = { 17432 'true': true, 17433 'false': false, 17434 'null': null, 17435 'undefined': undefined 17436 }; 17437 var identStart, identContinue; 17438 17439 /** 17440 * @ngdoc method 17441 * @name $parseProvider#addLiteral 17442 * @description 17443 * 17444 * Configure $parse service to add literal values that will be present as literal at expressions. 17445 * 17446 * @param {string} literalName Token for the literal value. The literal name value must be a valid literal name. 17447 * @param {*} literalValue Value for this literal. All literal values must be primitives or `undefined`. 17448 * 17449 **/ 17450 this.addLiteral = function(literalName, literalValue) { 17451 literals[literalName] = literalValue; 17452 }; 17453 17454 /** 17455 * @ngdoc method 17456 * @name $parseProvider#setIdentifierFns 17457 * 17458 * @description 17459 * 17460 * Allows defining the set of characters that are allowed in AngularJS expressions. The function 17461 * `identifierStart` will get called to know if a given character is a valid character to be the 17462 * first character for an identifier. The function `identifierContinue` will get called to know if 17463 * a given character is a valid character to be a follow-up identifier character. The functions 17464 * `identifierStart` and `identifierContinue` will receive as arguments the single character to be 17465 * identifier and the character code point. These arguments will be `string` and `numeric`. Keep in 17466 * mind that the `string` parameter can be two characters long depending on the character 17467 * representation. It is expected for the function to return `true` or `false`, whether that 17468 * character is allowed or not. 17469 * 17470 * Since this function will be called extensively, keep the implementation of these functions fast, 17471 * as the performance of these functions have a direct impact on the expressions parsing speed. 17472 * 17473 * @param {function=} identifierStart The function that will decide whether the given character is 17474 * a valid identifier start character. 17475 * @param {function=} identifierContinue The function that will decide whether the given character is 17476 * a valid identifier continue character. 17477 */ 17478 this.setIdentifierFns = function(identifierStart, identifierContinue) { 17479 identStart = identifierStart; 17480 identContinue = identifierContinue; 17481 return this; 17482 }; 17483 17484 this.$get = ['$filter', function($filter) { 17485 var noUnsafeEval = csp().noUnsafeEval; 17486 var $parseOptions = { 17487 csp: noUnsafeEval, 17488 literals: copy(literals), 17489 isIdentifierStart: isFunction(identStart) && identStart, 17490 isIdentifierContinue: isFunction(identContinue) && identContinue 17491 }; 17492 $parse.$$getAst = $$getAst; 17493 return $parse; 17494 17495 function $parse(exp, interceptorFn) { 17496 var parsedExpression, cacheKey; 17497 17498 switch (typeof exp) { 17499 case 'string': 17500 exp = exp.trim(); 17501 cacheKey = exp; 17502 17503 parsedExpression = cache[cacheKey]; 17504 17505 if (!parsedExpression) { 17506 var lexer = new Lexer($parseOptions); 17507 var parser = new Parser(lexer, $filter, $parseOptions); 17508 parsedExpression = parser.parse(exp); 17509 17510 cache[cacheKey] = addWatchDelegate(parsedExpression); 17511 } 17512 return addInterceptor(parsedExpression, interceptorFn); 17513 17514 case 'function': 17515 return addInterceptor(exp, interceptorFn); 17516 17517 default: 17518 return addInterceptor(noop, interceptorFn); 17519 } 17520 } 17521 17522 function $$getAst(exp) { 17523 var lexer = new Lexer($parseOptions); 17524 var parser = new Parser(lexer, $filter, $parseOptions); 17525 return parser.getAst(exp).ast; 17526 } 17527 17528 function expressionInputDirtyCheck(newValue, oldValueOfValue, compareObjectIdentity) { 17529 17530 if (newValue == null || oldValueOfValue == null) { // null/undefined 17531 return newValue === oldValueOfValue; 17532 } 17533 17534 if (typeof newValue === 'object') { 17535 17536 // attempt to convert the value to a primitive type 17537 // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can 17538 // be cheaply dirty-checked 17539 newValue = getValueOf(newValue); 17540 17541 if (typeof newValue === 'object' && !compareObjectIdentity) { 17542 // objects/arrays are not supported - deep-watching them would be too expensive 17543 return false; 17544 } 17545 17546 // fall-through to the primitive equality check 17547 } 17548 17549 //Primitive or NaN 17550 // eslint-disable-next-line no-self-compare 17551 return newValue === oldValueOfValue || (newValue !== newValue && oldValueOfValue !== oldValueOfValue); 17552 } 17553 17554 function inputsWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) { 17555 var inputExpressions = parsedExpression.inputs; 17556 var lastResult; 17557 17558 if (inputExpressions.length === 1) { 17559 var oldInputValueOf = expressionInputDirtyCheck; // init to something unique so that equals check fails 17560 inputExpressions = inputExpressions[0]; 17561 return scope.$watch(function expressionInputWatch(scope) { 17562 var newInputValue = inputExpressions(scope); 17563 if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf, inputExpressions.isPure)) { 17564 lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]); 17565 oldInputValueOf = newInputValue && getValueOf(newInputValue); 17566 } 17567 return lastResult; 17568 }, listener, objectEquality, prettyPrintExpression); 17569 } 17570 17571 var oldInputValueOfValues = []; 17572 var oldInputValues = []; 17573 for (var i = 0, ii = inputExpressions.length; i < ii; i++) { 17574 oldInputValueOfValues[i] = expressionInputDirtyCheck; // init to something unique so that equals check fails 17575 oldInputValues[i] = null; 17576 } 17577 17578 return scope.$watch(function expressionInputsWatch(scope) { 17579 var changed = false; 17580 17581 for (var i = 0, ii = inputExpressions.length; i < ii; i++) { 17582 var newInputValue = inputExpressions[i](scope); 17583 if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i], inputExpressions[i].isPure))) { 17584 oldInputValues[i] = newInputValue; 17585 oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue); 17586 } 17587 } 17588 17589 if (changed) { 17590 lastResult = parsedExpression(scope, undefined, undefined, oldInputValues); 17591 } 17592 17593 return lastResult; 17594 }, listener, objectEquality, prettyPrintExpression); 17595 } 17596 17597 function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) { 17598 var isDone = parsedExpression.literal ? isAllDefined : isDefined; 17599 var unwatch, lastValue; 17600 17601 var exp = parsedExpression.$$intercepted || parsedExpression; 17602 var post = parsedExpression.$$interceptor || identity; 17603 17604 var useInputs = parsedExpression.inputs && !exp.inputs; 17605 17606 // Propagate the literal/inputs/constant attributes 17607 // ... but not oneTime since we are handling it 17608 oneTimeWatch.literal = parsedExpression.literal; 17609 oneTimeWatch.constant = parsedExpression.constant; 17610 oneTimeWatch.inputs = parsedExpression.inputs; 17611 17612 // Allow other delegates to run on this wrapped expression 17613 addWatchDelegate(oneTimeWatch); 17614 17615 unwatch = scope.$watch(oneTimeWatch, listener, objectEquality, prettyPrintExpression); 17616 17617 return unwatch; 17618 17619 function unwatchIfDone() { 17620 if (isDone(lastValue)) { 17621 unwatch(); 17622 } 17623 } 17624 17625 function oneTimeWatch(scope, locals, assign, inputs) { 17626 lastValue = useInputs && inputs ? inputs[0] : exp(scope, locals, assign, inputs); 17627 if (isDone(lastValue)) { 17628 scope.$$postDigest(unwatchIfDone); 17629 } 17630 return post(lastValue); 17631 } 17632 } 17633 17634 function isAllDefined(value) { 17635 var allDefined = true; 17636 forEach(value, function(val) { 17637 if (!isDefined(val)) allDefined = false; 17638 }); 17639 return allDefined; 17640 } 17641 17642 function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) { 17643 var unwatch = scope.$watch(function constantWatch(scope) { 17644 unwatch(); 17645 return parsedExpression(scope); 17646 }, listener, objectEquality); 17647 return unwatch; 17648 } 17649 17650 function addWatchDelegate(parsedExpression) { 17651 if (parsedExpression.constant) { 17652 parsedExpression.$$watchDelegate = constantWatchDelegate; 17653 } else if (parsedExpression.oneTime) { 17654 parsedExpression.$$watchDelegate = oneTimeWatchDelegate; 17655 } else if (parsedExpression.inputs) { 17656 parsedExpression.$$watchDelegate = inputsWatchDelegate; 17657 } 17658 17659 return parsedExpression; 17660 } 17661 17662 function chainInterceptors(first, second) { 17663 function chainedInterceptor(value) { 17664 return second(first(value)); 17665 } 17666 chainedInterceptor.$stateful = first.$stateful || second.$stateful; 17667 chainedInterceptor.$$pure = first.$$pure && second.$$pure; 17668 17669 return chainedInterceptor; 17670 } 17671 17672 function addInterceptor(parsedExpression, interceptorFn) { 17673 if (!interceptorFn) return parsedExpression; 17674 17675 // Extract any existing interceptors out of the parsedExpression 17676 // to ensure the original parsedExpression is always the $$intercepted 17677 if (parsedExpression.$$interceptor) { 17678 interceptorFn = chainInterceptors(parsedExpression.$$interceptor, interceptorFn); 17679 parsedExpression = parsedExpression.$$intercepted; 17680 } 17681 17682 var useInputs = false; 17683 17684 var fn = function interceptedExpression(scope, locals, assign, inputs) { 17685 var value = useInputs && inputs ? inputs[0] : parsedExpression(scope, locals, assign, inputs); 17686 return interceptorFn(value); 17687 }; 17688 17689 // Maintain references to the interceptor/intercepted 17690 fn.$$intercepted = parsedExpression; 17691 fn.$$interceptor = interceptorFn; 17692 17693 // Propagate the literal/oneTime/constant attributes 17694 fn.literal = parsedExpression.literal; 17695 fn.oneTime = parsedExpression.oneTime; 17696 fn.constant = parsedExpression.constant; 17697 17698 // Treat the interceptor like filters. 17699 // If it is not $stateful then only watch its inputs. 17700 // If the expression itself has no inputs then use the full expression as an input. 17701 if (!interceptorFn.$stateful) { 17702 useInputs = !parsedExpression.inputs; 17703 fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression]; 17704 17705 if (!interceptorFn.$$pure) { 17706 fn.inputs = fn.inputs.map(function(e) { 17707 // Remove the isPure flag of inputs when it is not absolute because they are now wrapped in a 17708 // non-pure interceptor function. 17709 if (e.isPure === PURITY_RELATIVE) { 17710 return function depurifier(s) { return e(s); }; 17711 } 17712 return e; 17713 }); 17714 } 17715 } 17716 17717 return addWatchDelegate(fn); 17718 } 17719 }]; 17720} 17721 17722/** 17723 * @ngdoc service 17724 * @name $q 17725 * @requires $rootScope 17726 * 17727 * @description 17728 * A service that helps you run functions asynchronously, and use their return values (or exceptions) 17729 * when they are done processing. 17730 * 17731 * This is a [Promises/A+](https://promisesaplus.com/)-compliant implementation of promises/deferred 17732 * objects inspired by [Kris Kowal's Q](https://github.com/kriskowal/q). 17733 * 17734 * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred 17735 * implementations, and the other which resembles ES6 (ES2015) promises to some degree. 17736 * 17737 * ## $q constructor 17738 * 17739 * The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver` 17740 * function as the first argument. This is similar to the native Promise implementation from ES6, 17741 * see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). 17742 * 17743 * While the constructor-style use is supported, not all of the supporting methods from ES6 promises are 17744 * available yet. 17745 * 17746 * It can be used like so: 17747 * 17748 * ```js 17749 * // for the purpose of this example let's assume that variables `$q` and `okToGreet` 17750 * // are available in the current lexical scope (they could have been injected or passed in). 17751 * 17752 * function asyncGreet(name) { 17753 * // perform some asynchronous operation, resolve or reject the promise when appropriate. 17754 * return $q(function(resolve, reject) { 17755 * setTimeout(function() { 17756 * if (okToGreet(name)) { 17757 * resolve('Hello, ' + name + '!'); 17758 * } else { 17759 * reject('Greeting ' + name + ' is not allowed.'); 17760 * } 17761 * }, 1000); 17762 * }); 17763 * } 17764 * 17765 * var promise = asyncGreet('Robin Hood'); 17766 * promise.then(function(greeting) { 17767 * alert('Success: ' + greeting); 17768 * }, function(reason) { 17769 * alert('Failed: ' + reason); 17770 * }); 17771 * ``` 17772 * 17773 * Note: progress/notify callbacks are not currently supported via the ES6-style interface. 17774 * 17775 * Note: unlike ES6 behavior, an exception thrown in the constructor function will NOT implicitly reject the promise. 17776 * 17777 * However, the more traditional CommonJS-style usage is still available, and documented below. 17778 * 17779 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an 17780 * interface for interacting with an object that represents the result of an action that is 17781 * performed asynchronously, and may or may not be finished at any given point in time. 17782 * 17783 * From the perspective of dealing with error handling, deferred and promise APIs are to 17784 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. 17785 * 17786 * ```js 17787 * // for the purpose of this example let's assume that variables `$q` and `okToGreet` 17788 * // are available in the current lexical scope (they could have been injected or passed in). 17789 * 17790 * function asyncGreet(name) { 17791 * var deferred = $q.defer(); 17792 * 17793 * setTimeout(function() { 17794 * deferred.notify('About to greet ' + name + '.'); 17795 * 17796 * if (okToGreet(name)) { 17797 * deferred.resolve('Hello, ' + name + '!'); 17798 * } else { 17799 * deferred.reject('Greeting ' + name + ' is not allowed.'); 17800 * } 17801 * }, 1000); 17802 * 17803 * return deferred.promise; 17804 * } 17805 * 17806 * var promise = asyncGreet('Robin Hood'); 17807 * promise.then(function(greeting) { 17808 * alert('Success: ' + greeting); 17809 * }, function(reason) { 17810 * alert('Failed: ' + reason); 17811 * }, function(update) { 17812 * alert('Got notification: ' + update); 17813 * }); 17814 * ``` 17815 * 17816 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff 17817 * comes in the way of guarantees that promise and deferred APIs make, see 17818 * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md. 17819 * 17820 * Additionally the promise api allows for composition that is very hard to do with the 17821 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. 17822 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the 17823 * section on serial or parallel joining of promises. 17824 * 17825 * ## The Deferred API 17826 * 17827 * A new instance of deferred is constructed by calling `$q.defer()`. 17828 * 17829 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs 17830 * that can be used for signaling the successful or unsuccessful completion, as well as the status 17831 * of the task. 17832 * 17833 * **Methods** 17834 * 17835 * - `resolve(value)` â resolves the derived promise with the `value`. If the value is a rejection 17836 * constructed via `$q.reject`, the promise will be rejected instead. 17837 * - `reject(reason)` â rejects the derived promise with the `reason`. This is equivalent to 17838 * resolving it with a rejection constructed via `$q.reject`. 17839 * - `notify(value)` - provides updates on the status of the promise's execution. This may be called 17840 * multiple times before the promise is either resolved or rejected. 17841 * 17842 * **Properties** 17843 * 17844 * - promise â `{Promise}` â promise object associated with this deferred. 17845 * 17846 * 17847 * ## The Promise API 17848 * 17849 * A new promise instance is created when a deferred instance is created and can be retrieved by 17850 * calling `deferred.promise`. 17851 * 17852 * The purpose of the promise object is to allow for interested parties to get access to the result 17853 * of the deferred task when it completes. 17854 * 17855 * **Methods** 17856 * 17857 * - `then(successCallback, [errorCallback], [notifyCallback])` â regardless of when the promise was or 17858 * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously 17859 * as soon as the result is available. The callbacks are called with a single argument: the result 17860 * or rejection reason. Additionally, the notify callback may be called zero or more times to 17861 * provide a progress indication, before the promise is resolved or rejected. 17862 * 17863 * This method *returns a new promise* which is resolved or rejected via the return value of the 17864 * `successCallback`, `errorCallback` (unless that value is a promise, in which case it is resolved 17865 * with the value which is resolved in that promise using 17866 * [promise chaining](http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promises-queues)). 17867 * It also notifies via the return value of the `notifyCallback` method. The promise cannot be 17868 * resolved or rejected from the notifyCallback method. The errorCallback and notifyCallback 17869 * arguments are optional. 17870 * 17871 * - `catch(errorCallback)` â shorthand for `promise.then(null, errorCallback)` 17872 * 17873 * - `finally(callback, notifyCallback)` â allows you to observe either the fulfillment or rejection of a promise, 17874 * but to do so without modifying the final value. This is useful to release resources or do some 17875 * clean-up that needs to be done whether the promise was rejected or resolved. See the [full 17876 * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for 17877 * more information. 17878 * 17879 * ## Chaining promises 17880 * 17881 * Because calling the `then` method of a promise returns a new derived promise, it is easily 17882 * possible to create a chain of promises: 17883 * 17884 * ```js 17885 * promiseB = promiseA.then(function(result) { 17886 * return result + 1; 17887 * }); 17888 * 17889 * // promiseB will be resolved immediately after promiseA is resolved and its value 17890 * // will be the result of promiseA incremented by 1 17891 * ``` 17892 * 17893 * It is possible to create chains of any length and since a promise can be resolved with another 17894 * promise (which will defer its resolution further), it is possible to pause/defer resolution of 17895 * the promises at any point in the chain. This makes it possible to implement powerful APIs like 17896 * $http's response interceptors. 17897 * 17898 * 17899 * ## Differences between Kris Kowal's Q and $q 17900 * 17901 * There are two main differences: 17902 * 17903 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation 17904 * mechanism in AngularJS, which means faster propagation of resolution or rejection into your 17905 * models and avoiding unnecessary browser repaints, which would result in flickering UI. 17906 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains 17907 * all the important functionality needed for common async tasks. 17908 * 17909 * ## Testing 17910 * 17911 * ```js 17912 * it('should simulate promise', inject(function($q, $rootScope) { 17913 * var deferred = $q.defer(); 17914 * var promise = deferred.promise; 17915 * var resolvedValue; 17916 * 17917 * promise.then(function(value) { resolvedValue = value; }); 17918 * expect(resolvedValue).toBeUndefined(); 17919 * 17920 * // Simulate resolving of promise 17921 * deferred.resolve(123); 17922 * // Note that the 'then' function does not get called synchronously. 17923 * // This is because we want the promise API to always be async, whether or not 17924 * // it got called synchronously or asynchronously. 17925 * expect(resolvedValue).toBeUndefined(); 17926 * 17927 * // Propagate promise resolution to 'then' functions using $apply(). 17928 * $rootScope.$apply(); 17929 * expect(resolvedValue).toEqual(123); 17930 * })); 17931 * ``` 17932 * 17933 * @param {function(function, function)} resolver Function which is responsible for resolving or 17934 * rejecting the newly created promise. The first parameter is a function which resolves the 17935 * promise, the second parameter is a function which rejects the promise. 17936 * 17937 * @returns {Promise} The newly created promise. 17938 */ 17939/** 17940 * @ngdoc provider 17941 * @name $qProvider 17942 * @this 17943 * 17944 * @description 17945 */ 17946function $QProvider() { 17947 var errorOnUnhandledRejections = true; 17948 this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { 17949 return qFactory(function(callback) { 17950 $rootScope.$evalAsync(callback); 17951 }, $exceptionHandler, errorOnUnhandledRejections); 17952 }]; 17953 17954 /** 17955 * @ngdoc method 17956 * @name $qProvider#errorOnUnhandledRejections 17957 * @kind function 17958 * 17959 * @description 17960 * Retrieves or overrides whether to generate an error when a rejected promise is not handled. 17961 * This feature is enabled by default. 17962 * 17963 * @param {boolean=} value Whether to generate an error when a rejected promise is not handled. 17964 * @returns {boolean|ng.$qProvider} Current value when called without a new value or self for 17965 * chaining otherwise. 17966 */ 17967 this.errorOnUnhandledRejections = function(value) { 17968 if (isDefined(value)) { 17969 errorOnUnhandledRejections = value; 17970 return this; 17971 } else { 17972 return errorOnUnhandledRejections; 17973 } 17974 }; 17975} 17976 17977/** @this */ 17978function $$QProvider() { 17979 var errorOnUnhandledRejections = true; 17980 this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) { 17981 return qFactory(function(callback) { 17982 $browser.defer(callback); 17983 }, $exceptionHandler, errorOnUnhandledRejections); 17984 }]; 17985 17986 this.errorOnUnhandledRejections = function(value) { 17987 if (isDefined(value)) { 17988 errorOnUnhandledRejections = value; 17989 return this; 17990 } else { 17991 return errorOnUnhandledRejections; 17992 } 17993 }; 17994} 17995 17996/** 17997 * Constructs a promise manager. 17998 * 17999 * @param {function(function)} nextTick Function for executing functions in the next turn. 18000 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for 18001 * debugging purposes. 18002 * @param {boolean=} errorOnUnhandledRejections Whether an error should be generated on unhandled 18003 * promises rejections. 18004 * @returns {object} Promise manager. 18005 */ 18006function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) { 18007 var $qMinErr = minErr('$q', TypeError); 18008 var queueSize = 0; 18009 var checkQueue = []; 18010 18011 /** 18012 * @ngdoc method 18013 * @name ng.$q#defer 18014 * @kind function 18015 * 18016 * @description 18017 * Creates a `Deferred` object which represents a task which will finish in the future. 18018 * 18019 * @returns {Deferred} Returns a new instance of deferred. 18020 */ 18021 function defer() { 18022 return new Deferred(); 18023 } 18024 18025 function Deferred() { 18026 var promise = this.promise = new Promise(); 18027 //Non prototype methods necessary to support unbound execution :/ 18028 this.resolve = function(val) { resolvePromise(promise, val); }; 18029 this.reject = function(reason) { rejectPromise(promise, reason); }; 18030 this.notify = function(progress) { notifyPromise(promise, progress); }; 18031 } 18032 18033 18034 function Promise() { 18035 this.$$state = { status: 0 }; 18036 } 18037 18038 extend(Promise.prototype, { 18039 then: function(onFulfilled, onRejected, progressBack) { 18040 if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) { 18041 return this; 18042 } 18043 var result = new Promise(); 18044 18045 this.$$state.pending = this.$$state.pending || []; 18046 this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]); 18047 if (this.$$state.status > 0) scheduleProcessQueue(this.$$state); 18048 18049 return result; 18050 }, 18051 18052 'catch': function(callback) { 18053 return this.then(null, callback); 18054 }, 18055 18056 'finally': function(callback, progressBack) { 18057 return this.then(function(value) { 18058 return handleCallback(value, resolve, callback); 18059 }, function(error) { 18060 return handleCallback(error, reject, callback); 18061 }, progressBack); 18062 } 18063 }); 18064 18065 function processQueue(state) { 18066 var fn, promise, pending; 18067 18068 pending = state.pending; 18069 state.processScheduled = false; 18070 state.pending = undefined; 18071 try { 18072 for (var i = 0, ii = pending.length; i < ii; ++i) { 18073 markQStateExceptionHandled(state); 18074 promise = pending[i][0]; 18075 fn = pending[i][state.status]; 18076 try { 18077 if (isFunction(fn)) { 18078 resolvePromise(promise, fn(state.value)); 18079 } else if (state.status === 1) { 18080 resolvePromise(promise, state.value); 18081 } else { 18082 rejectPromise(promise, state.value); 18083 } 18084 } catch (e) { 18085 rejectPromise(promise, e); 18086 // This error is explicitly marked for being passed to the $exceptionHandler 18087 if (e && e.$$passToExceptionHandler === true) { 18088 exceptionHandler(e); 18089 } 18090 } 18091 } 18092 } finally { 18093 --queueSize; 18094 if (errorOnUnhandledRejections && queueSize === 0) { 18095 nextTick(processChecks); 18096 } 18097 } 18098 } 18099 18100 function processChecks() { 18101 // eslint-disable-next-line no-unmodified-loop-condition 18102 while (!queueSize && checkQueue.length) { 18103 var toCheck = checkQueue.shift(); 18104 if (!isStateExceptionHandled(toCheck)) { 18105 markQStateExceptionHandled(toCheck); 18106 var errorMessage = 'Possibly unhandled rejection: ' + toDebugString(toCheck.value); 18107 if (isError(toCheck.value)) { 18108 exceptionHandler(toCheck.value, errorMessage); 18109 } else { 18110 exceptionHandler(errorMessage); 18111 } 18112 } 18113 } 18114 } 18115 18116 function scheduleProcessQueue(state) { 18117 if (errorOnUnhandledRejections && !state.pending && state.status === 2 && !isStateExceptionHandled(state)) { 18118 if (queueSize === 0 && checkQueue.length === 0) { 18119 nextTick(processChecks); 18120 } 18121 checkQueue.push(state); 18122 } 18123 if (state.processScheduled || !state.pending) return; 18124 state.processScheduled = true; 18125 ++queueSize; 18126 nextTick(function() { processQueue(state); }); 18127 } 18128 18129 function resolvePromise(promise, val) { 18130 if (promise.$$state.status) return; 18131 if (val === promise) { 18132 $$reject(promise, $qMinErr( 18133 'qcycle', 18134 'Expected promise to be resolved with value other than itself \'{0}\'', 18135 val)); 18136 } else { 18137 $$resolve(promise, val); 18138 } 18139 18140 } 18141 18142 function $$resolve(promise, val) { 18143 var then; 18144 var done = false; 18145 try { 18146 if (isObject(val) || isFunction(val)) then = val.then; 18147 if (isFunction(then)) { 18148 promise.$$state.status = -1; 18149 then.call(val, doResolve, doReject, doNotify); 18150 } else { 18151 promise.$$state.value = val; 18152 promise.$$state.status = 1; 18153 scheduleProcessQueue(promise.$$state); 18154 } 18155 } catch (e) { 18156 doReject(e); 18157 } 18158 18159 function doResolve(val) { 18160 if (done) return; 18161 done = true; 18162 $$resolve(promise, val); 18163 } 18164 function doReject(val) { 18165 if (done) return; 18166 done = true; 18167 $$reject(promise, val); 18168 } 18169 function doNotify(progress) { 18170 notifyPromise(promise, progress); 18171 } 18172 } 18173 18174 function rejectPromise(promise, reason) { 18175 if (promise.$$state.status) return; 18176 $$reject(promise, reason); 18177 } 18178 18179 function $$reject(promise, reason) { 18180 promise.$$state.value = reason; 18181 promise.$$state.status = 2; 18182 scheduleProcessQueue(promise.$$state); 18183 } 18184 18185 function notifyPromise(promise, progress) { 18186 var callbacks = promise.$$state.pending; 18187 18188 if ((promise.$$state.status <= 0) && callbacks && callbacks.length) { 18189 nextTick(function() { 18190 var callback, result; 18191 for (var i = 0, ii = callbacks.length; i < ii; i++) { 18192 result = callbacks[i][0]; 18193 callback = callbacks[i][3]; 18194 try { 18195 notifyPromise(result, isFunction(callback) ? callback(progress) : progress); 18196 } catch (e) { 18197 exceptionHandler(e); 18198 } 18199 } 18200 }); 18201 } 18202 } 18203 18204 /** 18205 * @ngdoc method 18206 * @name $q#reject 18207 * @kind function 18208 * 18209 * @description 18210 * Creates a promise that is resolved as rejected with the specified `reason`. This api should be 18211 * used to forward rejection in a chain of promises. If you are dealing with the last promise in 18212 * a promise chain, you don't need to worry about it. 18213 * 18214 * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of 18215 * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via 18216 * a promise error callback and you want to forward the error to the promise derived from the 18217 * current promise, you have to "rethrow" the error by returning a rejection constructed via 18218 * `reject`. 18219 * 18220 * ```js 18221 * promiseB = promiseA.then(function(result) { 18222 * // success: do something and resolve promiseB 18223 * // with the old or a new result 18224 * return result; 18225 * }, function(reason) { 18226 * // error: handle the error if possible and 18227 * // resolve promiseB with newPromiseOrValue, 18228 * // otherwise forward the rejection to promiseB 18229 * if (canHandle(reason)) { 18230 * // handle the error and recover 18231 * return newPromiseOrValue; 18232 * } 18233 * return $q.reject(reason); 18234 * }); 18235 * ``` 18236 * 18237 * @param {*} reason Constant, message, exception or an object representing the rejection reason. 18238 * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. 18239 */ 18240 function reject(reason) { 18241 var result = new Promise(); 18242 rejectPromise(result, reason); 18243 return result; 18244 } 18245 18246 function handleCallback(value, resolver, callback) { 18247 var callbackOutput = null; 18248 try { 18249 if (isFunction(callback)) callbackOutput = callback(); 18250 } catch (e) { 18251 return reject(e); 18252 } 18253 if (isPromiseLike(callbackOutput)) { 18254 return callbackOutput.then(function() { 18255 return resolver(value); 18256 }, reject); 18257 } else { 18258 return resolver(value); 18259 } 18260 } 18261 18262 /** 18263 * @ngdoc method 18264 * @name $q#when 18265 * @kind function 18266 * 18267 * @description 18268 * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. 18269 * This is useful when you are dealing with an object that might or might not be a promise, or if 18270 * the promise comes from a source that can't be trusted. 18271 * 18272 * @param {*} value Value or a promise 18273 * @param {Function=} successCallback 18274 * @param {Function=} errorCallback 18275 * @param {Function=} progressCallback 18276 * @returns {Promise} Returns a promise of the passed value or promise 18277 */ 18278 18279 18280 function when(value, callback, errback, progressBack) { 18281 var result = new Promise(); 18282 resolvePromise(result, value); 18283 return result.then(callback, errback, progressBack); 18284 } 18285 18286 /** 18287 * @ngdoc method 18288 * @name $q#resolve 18289 * @kind function 18290 * 18291 * @description 18292 * Alias of {@link ng.$q#when when} to maintain naming consistency with ES6. 18293 * 18294 * @param {*} value Value or a promise 18295 * @param {Function=} successCallback 18296 * @param {Function=} errorCallback 18297 * @param {Function=} progressCallback 18298 * @returns {Promise} Returns a promise of the passed value or promise 18299 */ 18300 var resolve = when; 18301 18302 /** 18303 * @ngdoc method 18304 * @name $q#all 18305 * @kind function 18306 * 18307 * @description 18308 * Combines multiple promises into a single promise that is resolved when all of the input 18309 * promises are resolved. 18310 * 18311 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises. 18312 * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values, 18313 * each value corresponding to the promise at the same index/key in the `promises` array/hash. 18314 * If any of the promises is resolved with a rejection, this resulting promise will be rejected 18315 * with the same rejection value. 18316 */ 18317 18318 function all(promises) { 18319 var result = new Promise(), 18320 counter = 0, 18321 results = isArray(promises) ? [] : {}; 18322 18323 forEach(promises, function(promise, key) { 18324 counter++; 18325 when(promise).then(function(value) { 18326 results[key] = value; 18327 if (!(--counter)) resolvePromise(result, results); 18328 }, function(reason) { 18329 rejectPromise(result, reason); 18330 }); 18331 }); 18332 18333 if (counter === 0) { 18334 resolvePromise(result, results); 18335 } 18336 18337 return result; 18338 } 18339 18340 /** 18341 * @ngdoc method 18342 * @name $q#race 18343 * @kind function 18344 * 18345 * @description 18346 * Returns a promise that resolves or rejects as soon as one of those promises 18347 * resolves or rejects, with the value or reason from that promise. 18348 * 18349 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises. 18350 * @returns {Promise} a promise that resolves or rejects as soon as one of the `promises` 18351 * resolves or rejects, with the value or reason from that promise. 18352 */ 18353 18354 function race(promises) { 18355 var deferred = defer(); 18356 18357 forEach(promises, function(promise) { 18358 when(promise).then(deferred.resolve, deferred.reject); 18359 }); 18360 18361 return deferred.promise; 18362 } 18363 18364 function $Q(resolver) { 18365 if (!isFunction(resolver)) { 18366 throw $qMinErr('norslvr', 'Expected resolverFn, got \'{0}\'', resolver); 18367 } 18368 18369 var promise = new Promise(); 18370 18371 function resolveFn(value) { 18372 resolvePromise(promise, value); 18373 } 18374 18375 function rejectFn(reason) { 18376 rejectPromise(promise, reason); 18377 } 18378 18379 resolver(resolveFn, rejectFn); 18380 18381 return promise; 18382 } 18383 18384 // Let's make the instanceof operator work for promises, so that 18385 // `new $q(fn) instanceof $q` would evaluate to true. 18386 $Q.prototype = Promise.prototype; 18387 18388 $Q.defer = defer; 18389 $Q.reject = reject; 18390 $Q.when = when; 18391 $Q.resolve = resolve; 18392 $Q.all = all; 18393 $Q.race = race; 18394 18395 return $Q; 18396} 18397 18398function isStateExceptionHandled(state) { 18399 return !!state.pur; 18400} 18401function markQStateExceptionHandled(state) { 18402 state.pur = true; 18403} 18404function markQExceptionHandled(q) { 18405 // Built-in `$q` promises will always have a `$$state` property. This check is to allow 18406 // overwriting `$q` with a different promise library (e.g. Bluebird + angular-bluebird-promises). 18407 // (Currently, this is the only method that might be called with a promise, even if it is not 18408 // created by the built-in `$q`.) 18409 if (q.$$state) { 18410 markQStateExceptionHandled(q.$$state); 18411 } 18412} 18413 18414/** @this */ 18415function $$RAFProvider() { //rAF 18416 this.$get = ['$window', '$timeout', function($window, $timeout) { 18417 var requestAnimationFrame = $window.requestAnimationFrame || 18418 $window.webkitRequestAnimationFrame; 18419 18420 var cancelAnimationFrame = $window.cancelAnimationFrame || 18421 $window.webkitCancelAnimationFrame || 18422 $window.webkitCancelRequestAnimationFrame; 18423 18424 var rafSupported = !!requestAnimationFrame; 18425 var raf = rafSupported 18426 ? function(fn) { 18427 var id = requestAnimationFrame(fn); 18428 return function() { 18429 cancelAnimationFrame(id); 18430 }; 18431 } 18432 : function(fn) { 18433 var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666 18434 return function() { 18435 $timeout.cancel(timer); 18436 }; 18437 }; 18438 18439 raf.supported = rafSupported; 18440 18441 return raf; 18442 }]; 18443} 18444 18445/** 18446 * DESIGN NOTES 18447 * 18448 * The design decisions behind the scope are heavily favored for speed and memory consumption. 18449 * 18450 * The typical use of scope is to watch the expressions, which most of the time return the same 18451 * value as last time so we optimize the operation. 18452 * 18453 * Closures construction is expensive in terms of speed as well as memory: 18454 * - No closures, instead use prototypical inheritance for API 18455 * - Internal state needs to be stored on scope directly, which means that private state is 18456 * exposed as $$____ properties 18457 * 18458 * Loop operations are optimized by using while(count--) { ... } 18459 * - This means that in order to keep the same order of execution as addition we have to add 18460 * items to the array at the beginning (unshift) instead of at the end (push) 18461 * 18462 * Child scopes are created and removed often 18463 * - Using an array would be slow since inserts in the middle are expensive; so we use linked lists 18464 * 18465 * There are fewer watches than observers. This is why you don't want the observer to be implemented 18466 * in the same way as watch. Watch requires return of the initialization function which is expensive 18467 * to construct. 18468 */ 18469 18470 18471/** 18472 * @ngdoc provider 18473 * @name $rootScopeProvider 18474 * @description 18475 * 18476 * Provider for the $rootScope service. 18477 */ 18478 18479/** 18480 * @ngdoc method 18481 * @name $rootScopeProvider#digestTtl 18482 * @description 18483 * 18484 * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and 18485 * assuming that the model is unstable. 18486 * 18487 * The current default is 10 iterations. 18488 * 18489 * In complex applications it's possible that the dependencies between `$watch`s will result in 18490 * several digest iterations. However if an application needs more than the default 10 digest 18491 * iterations for its model to stabilize then you should investigate what is causing the model to 18492 * continuously change during the digest. 18493 * 18494 * Increasing the TTL could have performance implications, so you should not change it without 18495 * proper justification. 18496 * 18497 * @param {number} limit The number of digest iterations. 18498 */ 18499 18500 18501/** 18502 * @ngdoc service 18503 * @name $rootScope 18504 * @this 18505 * 18506 * @description 18507 * 18508 * Every application has a single root {@link ng.$rootScope.Scope scope}. 18509 * All other scopes are descendant scopes of the root scope. Scopes provide separation 18510 * between the model and the view, via a mechanism for watching the model for changes. 18511 * They also provide event emission/broadcast and subscription facility. See the 18512 * {@link guide/scope developer guide on scopes}. 18513 */ 18514function $RootScopeProvider() { 18515 var TTL = 10; 18516 var $rootScopeMinErr = minErr('$rootScope'); 18517 var lastDirtyWatch = null; 18518 var applyAsyncId = null; 18519 18520 this.digestTtl = function(value) { 18521 if (arguments.length) { 18522 TTL = value; 18523 } 18524 return TTL; 18525 }; 18526 18527 function createChildScopeClass(parent) { 18528 function ChildScope() { 18529 this.$$watchers = this.$$nextSibling = 18530 this.$$childHead = this.$$childTail = null; 18531 this.$$listeners = {}; 18532 this.$$listenerCount = {}; 18533 this.$$watchersCount = 0; 18534 this.$id = nextUid(); 18535 this.$$ChildScope = null; 18536 this.$$suspended = false; 18537 } 18538 ChildScope.prototype = parent; 18539 return ChildScope; 18540 } 18541 18542 this.$get = ['$exceptionHandler', '$parse', '$browser', 18543 function($exceptionHandler, $parse, $browser) { 18544 18545 function destroyChildScope($event) { 18546 $event.currentScope.$$destroyed = true; 18547 } 18548 18549 function cleanUpScope($scope) { 18550 18551 // Support: IE 9 only 18552 if (msie === 9) { 18553 // There is a memory leak in IE9 if all child scopes are not disconnected 18554 // completely when a scope is destroyed. So this code will recurse up through 18555 // all this scopes children 18556 // 18557 // See issue https://github.com/angular/angular.js/issues/10706 18558 if ($scope.$$childHead) { 18559 cleanUpScope($scope.$$childHead); 18560 } 18561 if ($scope.$$nextSibling) { 18562 cleanUpScope($scope.$$nextSibling); 18563 } 18564 } 18565 18566 // The code below works around IE9 and V8's memory leaks 18567 // 18568 // See: 18569 // - https://code.google.com/p/v8/issues/detail?id=2073#c26 18570 // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909 18571 // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 18572 18573 $scope.$parent = $scope.$$nextSibling = $scope.$$prevSibling = $scope.$$childHead = 18574 $scope.$$childTail = $scope.$root = $scope.$$watchers = null; 18575 } 18576 18577 /** 18578 * @ngdoc type 18579 * @name $rootScope.Scope 18580 * 18581 * @description 18582 * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the 18583 * {@link auto.$injector $injector}. Child scopes are created using the 18584 * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when 18585 * compiled HTML template is executed.) See also the {@link guide/scope Scopes guide} for 18586 * an in-depth introduction and usage examples. 18587 * 18588 * 18589 * ## Inheritance 18590 * A scope can inherit from a parent scope, as in this example: 18591 * ```js 18592 var parent = $rootScope; 18593 var child = parent.$new(); 18594 18595 parent.salutation = "Hello"; 18596 expect(child.salutation).toEqual('Hello'); 18597 18598 child.salutation = "Welcome"; 18599 expect(child.salutation).toEqual('Welcome'); 18600 expect(parent.salutation).toEqual('Hello'); 18601 * ``` 18602 * 18603 * When interacting with `Scope` in tests, additional helper methods are available on the 18604 * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional 18605 * details. 18606 * 18607 * 18608 * @param {Object.<string, function()>=} providers Map of service factory which need to be 18609 * provided for the current scope. Defaults to {@link ng}. 18610 * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should 18611 * append/override services provided by `providers`. This is handy 18612 * when unit-testing and having the need to override a default 18613 * service. 18614 * @returns {Object} Newly created scope. 18615 * 18616 */ 18617 function Scope() { 18618 this.$id = nextUid(); 18619 this.$$phase = this.$parent = this.$$watchers = 18620 this.$$nextSibling = this.$$prevSibling = 18621 this.$$childHead = this.$$childTail = null; 18622 this.$root = this; 18623 this.$$destroyed = false; 18624 this.$$suspended = false; 18625 this.$$listeners = {}; 18626 this.$$listenerCount = {}; 18627 this.$$watchersCount = 0; 18628 this.$$isolateBindings = null; 18629 } 18630 18631 /** 18632 * @ngdoc property 18633 * @name $rootScope.Scope#$id 18634 * 18635 * @description 18636 * Unique scope ID (monotonically increasing) useful for debugging. 18637 */ 18638 18639 /** 18640 * @ngdoc property 18641 * @name $rootScope.Scope#$parent 18642 * 18643 * @description 18644 * Reference to the parent scope. 18645 */ 18646 18647 /** 18648 * @ngdoc property 18649 * @name $rootScope.Scope#$root 18650 * 18651 * @description 18652 * Reference to the root scope. 18653 */ 18654 18655 Scope.prototype = { 18656 constructor: Scope, 18657 /** 18658 * @ngdoc method 18659 * @name $rootScope.Scope#$new 18660 * @kind function 18661 * 18662 * @description 18663 * Creates a new child {@link ng.$rootScope.Scope scope}. 18664 * 18665 * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} event. 18666 * The scope can be removed from the scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}. 18667 * 18668 * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is 18669 * desired for the scope and its child scopes to be permanently detached from the parent and 18670 * thus stop participating in model change detection and listener notification by invoking. 18671 * 18672 * @param {boolean} isolate If true, then the scope does not prototypically inherit from the 18673 * parent scope. The scope is isolated, as it can not see parent scope properties. 18674 * When creating widgets, it is useful for the widget to not accidentally read parent 18675 * state. 18676 * 18677 * @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent` 18678 * of the newly created scope. Defaults to `this` scope if not provided. 18679 * This is used when creating a transclude scope to correctly place it 18680 * in the scope hierarchy while maintaining the correct prototypical 18681 * inheritance. 18682 * 18683 * @returns {Object} The newly created child scope. 18684 * 18685 */ 18686 $new: function(isolate, parent) { 18687 var child; 18688 18689 parent = parent || this; 18690 18691 if (isolate) { 18692 child = new Scope(); 18693 child.$root = this.$root; 18694 } else { 18695 // Only create a child scope class if somebody asks for one, 18696 // but cache it to allow the VM to optimize lookups. 18697 if (!this.$$ChildScope) { 18698 this.$$ChildScope = createChildScopeClass(this); 18699 } 18700 child = new this.$$ChildScope(); 18701 } 18702 child.$parent = parent; 18703 child.$$prevSibling = parent.$$childTail; 18704 if (parent.$$childHead) { 18705 parent.$$childTail.$$nextSibling = child; 18706 parent.$$childTail = child; 18707 } else { 18708 parent.$$childHead = parent.$$childTail = child; 18709 } 18710 18711 // When the new scope is not isolated or we inherit from `this`, and 18712 // the parent scope is destroyed, the property `$$destroyed` is inherited 18713 // prototypically. In all other cases, this property needs to be set 18714 // when the parent scope is destroyed. 18715 // The listener needs to be added after the parent is set 18716 if (isolate || parent !== this) child.$on('$destroy', destroyChildScope); 18717 18718 return child; 18719 }, 18720 18721 /** 18722 * @ngdoc method 18723 * @name $rootScope.Scope#$watch 18724 * @kind function 18725 * 18726 * @description 18727 * Registers a `listener` callback to be executed whenever the `watchExpression` changes. 18728 * 18729 * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest 18730 * $digest()} and should return the value that will be watched. (`watchExpression` should not change 18731 * its value when executed multiple times with the same input because it may be executed multiple 18732 * times by {@link ng.$rootScope.Scope#$digest $digest()}. That is, `watchExpression` should be 18733 * [idempotent](http://en.wikipedia.org/wiki/Idempotence).) 18734 * - The `listener` is called only when the value from the current `watchExpression` and the 18735 * previous call to `watchExpression` are not equal (with the exception of the initial run, 18736 * see below). Inequality is determined according to reference inequality, 18737 * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators) 18738 * via the `!==` Javascript operator, unless `objectEquality == true` 18739 * (see next point) 18740 * - When `objectEquality == true`, inequality of the `watchExpression` is determined 18741 * according to the {@link angular.equals} function. To save the value of the object for 18742 * later comparison, the {@link angular.copy} function is used. This therefore means that 18743 * watching complex objects will have adverse memory and performance implications. 18744 * - This should not be used to watch for changes in objects that are (or contain) 18745 * [File](https://developer.mozilla.org/docs/Web/API/File) objects due to limitations with {@link angular.copy `angular.copy`}. 18746 * - The watch `listener` may change the model, which may trigger other `listener`s to fire. 18747 * This is achieved by rerunning the watchers until no changes are detected. The rerun 18748 * iteration limit is 10 to prevent an infinite loop deadlock. 18749 * 18750 * 18751 * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called, 18752 * you can register a `watchExpression` function with no `listener`. (Be prepared for 18753 * multiple calls to your `watchExpression` because it will execute multiple times in a 18754 * single {@link ng.$rootScope.Scope#$digest $digest} cycle if a change is detected.) 18755 * 18756 * After a watcher is registered with the scope, the `listener` fn is called asynchronously 18757 * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the 18758 * watcher. In rare cases, this is undesirable because the listener is called when the result 18759 * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you 18760 * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the 18761 * listener was called due to initialization. 18762 * 18763 * 18764 * 18765 * @example 18766 * ```js 18767 // let's assume that scope was dependency injected as the $rootScope 18768 var scope = $rootScope; 18769 scope.name = 'misko'; 18770 scope.counter = 0; 18771 18772 expect(scope.counter).toEqual(0); 18773 scope.$watch('name', function(newValue, oldValue) { 18774 scope.counter = scope.counter + 1; 18775 }); 18776 expect(scope.counter).toEqual(0); 18777 18778 scope.$digest(); 18779 // the listener is always called during the first $digest loop after it was registered 18780 expect(scope.counter).toEqual(1); 18781 18782 scope.$digest(); 18783 // but now it will not be called unless the value changes 18784 expect(scope.counter).toEqual(1); 18785 18786 scope.name = 'adam'; 18787 scope.$digest(); 18788 expect(scope.counter).toEqual(2); 18789 18790 18791 18792 // Using a function as a watchExpression 18793 var food; 18794 scope.foodCounter = 0; 18795 expect(scope.foodCounter).toEqual(0); 18796 scope.$watch( 18797 // This function returns the value being watched. It is called for each turn of the $digest loop 18798 function() { return food; }, 18799 // This is the change listener, called when the value returned from the above function changes 18800 function(newValue, oldValue) { 18801 if ( newValue !== oldValue ) { 18802 // Only increment the counter if the value changed 18803 scope.foodCounter = scope.foodCounter + 1; 18804 } 18805 } 18806 ); 18807 // No digest has been run so the counter will be zero 18808 expect(scope.foodCounter).toEqual(0); 18809 18810 // Run the digest but since food has not changed count will still be zero 18811 scope.$digest(); 18812 expect(scope.foodCounter).toEqual(0); 18813 18814 // Update food and run digest. Now the counter will increment 18815 food = 'cheeseburger'; 18816 scope.$digest(); 18817 expect(scope.foodCounter).toEqual(1); 18818 18819 * ``` 18820 * 18821 * 18822 * 18823 * @param {(function()|string)} watchExpression Expression that is evaluated on each 18824 * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers 18825 * a call to the `listener`. 18826 * 18827 * - `string`: Evaluated as {@link guide/expression expression} 18828 * - `function(scope)`: called with current `scope` as a parameter. 18829 * @param {function(newVal, oldVal, scope)} listener Callback called whenever the value 18830 * of `watchExpression` changes. 18831 * 18832 * - `newVal` contains the current value of the `watchExpression` 18833 * - `oldVal` contains the previous value of the `watchExpression` 18834 * - `scope` refers to the current scope 18835 * @param {boolean=} [objectEquality=false] Compare for object equality using {@link angular.equals} instead of 18836 * comparing for reference equality. 18837 * @returns {function()} Returns a deregistration function for this listener. 18838 */ 18839 $watch: function(watchExp, listener, objectEquality, prettyPrintExpression) { 18840 var get = $parse(watchExp); 18841 var fn = isFunction(listener) ? listener : noop; 18842 18843 if (get.$$watchDelegate) { 18844 return get.$$watchDelegate(this, fn, objectEquality, get, watchExp); 18845 } 18846 var scope = this, 18847 array = scope.$$watchers, 18848 watcher = { 18849 fn: fn, 18850 last: initWatchVal, 18851 get: get, 18852 exp: prettyPrintExpression || watchExp, 18853 eq: !!objectEquality 18854 }; 18855 18856 lastDirtyWatch = null; 18857 18858 if (!array) { 18859 array = scope.$$watchers = []; 18860 array.$$digestWatchIndex = -1; 18861 } 18862 // we use unshift since we use a while loop in $digest for speed. 18863 // the while loop reads in reverse order. 18864 array.unshift(watcher); 18865 array.$$digestWatchIndex++; 18866 incrementWatchersCount(this, 1); 18867 18868 return function deregisterWatch() { 18869 var index = arrayRemove(array, watcher); 18870 if (index >= 0) { 18871 incrementWatchersCount(scope, -1); 18872 if (index < array.$$digestWatchIndex) { 18873 array.$$digestWatchIndex--; 18874 } 18875 } 18876 lastDirtyWatch = null; 18877 }; 18878 }, 18879 18880 /** 18881 * @ngdoc method 18882 * @name $rootScope.Scope#$watchGroup 18883 * @kind function 18884 * 18885 * @description 18886 * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`. 18887 * If any one expression in the collection changes the `listener` is executed. 18888 * 18889 * - The items in the `watchExpressions` array are observed via the standard `$watch` operation. Their return 18890 * values are examined for changes on every call to `$digest`. 18891 * - The `listener` is called whenever any expression in the `watchExpressions` array changes. 18892 * 18893 * @param {Array.<string|Function(scope)>} watchExpressions Array of expressions that will be individually 18894 * watched using {@link ng.$rootScope.Scope#$watch $watch()} 18895 * 18896 * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any 18897 * expression in `watchExpressions` changes 18898 * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching 18899 * those of `watchExpression` 18900 * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching 18901 * those of `watchExpression` 18902 * The `scope` refers to the current scope. 18903 * @returns {function()} Returns a de-registration function for all listeners. 18904 */ 18905 $watchGroup: function(watchExpressions, listener) { 18906 var oldValues = new Array(watchExpressions.length); 18907 var newValues = new Array(watchExpressions.length); 18908 var deregisterFns = []; 18909 var self = this; 18910 var changeReactionScheduled = false; 18911 var firstRun = true; 18912 18913 if (!watchExpressions.length) { 18914 // No expressions means we call the listener ASAP 18915 var shouldCall = true; 18916 self.$evalAsync(function() { 18917 if (shouldCall) listener(newValues, newValues, self); 18918 }); 18919 return function deregisterWatchGroup() { 18920 shouldCall = false; 18921 }; 18922 } 18923 18924 if (watchExpressions.length === 1) { 18925 // Special case size of one 18926 return this.$watch(watchExpressions[0], function watchGroupAction(value, oldValue, scope) { 18927 newValues[0] = value; 18928 oldValues[0] = oldValue; 18929 listener(newValues, (value === oldValue) ? newValues : oldValues, scope); 18930 }); 18931 } 18932 18933 forEach(watchExpressions, function(expr, i) { 18934 var unwatchFn = self.$watch(expr, function watchGroupSubAction(value) { 18935 newValues[i] = value; 18936 if (!changeReactionScheduled) { 18937 changeReactionScheduled = true; 18938 self.$evalAsync(watchGroupAction); 18939 } 18940 }); 18941 deregisterFns.push(unwatchFn); 18942 }); 18943 18944 function watchGroupAction() { 18945 changeReactionScheduled = false; 18946 18947 try { 18948 if (firstRun) { 18949 firstRun = false; 18950 listener(newValues, newValues, self); 18951 } else { 18952 listener(newValues, oldValues, self); 18953 } 18954 } finally { 18955 for (var i = 0; i < watchExpressions.length; i++) { 18956 oldValues[i] = newValues[i]; 18957 } 18958 } 18959 } 18960 18961 return function deregisterWatchGroup() { 18962 while (deregisterFns.length) { 18963 deregisterFns.shift()(); 18964 } 18965 }; 18966 }, 18967 18968 18969 /** 18970 * @ngdoc method 18971 * @name $rootScope.Scope#$watchCollection 18972 * @kind function 18973 * 18974 * @description 18975 * Shallow watches the properties of an object and fires whenever any of the properties change 18976 * (for arrays, this implies watching the array items; for object maps, this implies watching 18977 * the properties). If a change is detected, the `listener` callback is fired. 18978 * 18979 * - The `obj` collection is observed via standard $watch operation and is examined on every 18980 * call to $digest() to see if any items have been added, removed, or moved. 18981 * - The `listener` is called whenever anything within the `obj` has changed. Examples include 18982 * adding, removing, and moving items belonging to an object or array. 18983 * 18984 * 18985 * @example 18986 * ```js 18987 $scope.names = ['igor', 'matias', 'misko', 'james']; 18988 $scope.dataCount = 4; 18989 18990 $scope.$watchCollection('names', function(newNames, oldNames) { 18991 $scope.dataCount = newNames.length; 18992 }); 18993 18994 expect($scope.dataCount).toEqual(4); 18995 $scope.$digest(); 18996 18997 //still at 4 ... no changes 18998 expect($scope.dataCount).toEqual(4); 18999 19000 $scope.names.pop(); 19001 $scope.$digest(); 19002 19003 //now there's been a change 19004 expect($scope.dataCount).toEqual(3); 19005 * ``` 19006 * 19007 * 19008 * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The 19009 * expression value should evaluate to an object or an array which is observed on each 19010 * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the 19011 * collection will trigger a call to the `listener`. 19012 * 19013 * @param {function(newCollection, oldCollection, scope)} listener a callback function called 19014 * when a change is detected. 19015 * - The `newCollection` object is the newly modified data obtained from the `obj` expression 19016 * - The `oldCollection` object is a copy of the former collection data. 19017 * Due to performance considerations, the`oldCollection` value is computed only if the 19018 * `listener` function declares two or more arguments. 19019 * - The `scope` argument refers to the current scope. 19020 * 19021 * @returns {function()} Returns a de-registration function for this listener. When the 19022 * de-registration function is executed, the internal watch operation is terminated. 19023 */ 19024 $watchCollection: function(obj, listener) { 19025 // Mark the interceptor as 19026 // ... $$pure when literal since the instance will change when any input changes 19027 $watchCollectionInterceptor.$$pure = $parse(obj).literal; 19028 // ... $stateful when non-literal since we must read the state of the collection 19029 $watchCollectionInterceptor.$stateful = !$watchCollectionInterceptor.$$pure; 19030 19031 var self = this; 19032 // the current value, updated on each dirty-check run 19033 var newValue; 19034 // a shallow copy of the newValue from the last dirty-check run, 19035 // updated to match newValue during dirty-check run 19036 var oldValue; 19037 // a shallow copy of the newValue from when the last change happened 19038 var veryOldValue; 19039 // only track veryOldValue if the listener is asking for it 19040 var trackVeryOldValue = (listener.length > 1); 19041 var changeDetected = 0; 19042 var changeDetector = $parse(obj, $watchCollectionInterceptor); 19043 var internalArray = []; 19044 var internalObject = {}; 19045 var initRun = true; 19046 var oldLength = 0; 19047 19048 function $watchCollectionInterceptor(_value) { 19049 newValue = _value; 19050 var newLength, key, bothNaN, newItem, oldItem; 19051 19052 // If the new value is undefined, then return undefined as the watch may be a one-time watch 19053 if (isUndefined(newValue)) return; 19054 19055 if (!isObject(newValue)) { // if primitive 19056 if (oldValue !== newValue) { 19057 oldValue = newValue; 19058 changeDetected++; 19059 } 19060 } else if (isArrayLike(newValue)) { 19061 if (oldValue !== internalArray) { 19062 // we are transitioning from something which was not an array into array. 19063 oldValue = internalArray; 19064 oldLength = oldValue.length = 0; 19065 changeDetected++; 19066 } 19067 19068 newLength = newValue.length; 19069 19070 if (oldLength !== newLength) { 19071 // if lengths do not match we need to trigger change notification 19072 changeDetected++; 19073 oldValue.length = oldLength = newLength; 19074 } 19075 // copy the items to oldValue and look for changes. 19076 for (var i = 0; i < newLength; i++) { 19077 oldItem = oldValue[i]; 19078 newItem = newValue[i]; 19079 19080 // eslint-disable-next-line no-self-compare 19081 bothNaN = (oldItem !== oldItem) && (newItem !== newItem); 19082 if (!bothNaN && (oldItem !== newItem)) { 19083 changeDetected++; 19084 oldValue[i] = newItem; 19085 } 19086 } 19087 } else { 19088 if (oldValue !== internalObject) { 19089 // we are transitioning from something which was not an object into object. 19090 oldValue = internalObject = {}; 19091 oldLength = 0; 19092 changeDetected++; 19093 } 19094 // copy the items to oldValue and look for changes. 19095 newLength = 0; 19096 for (key in newValue) { 19097 if (hasOwnProperty.call(newValue, key)) { 19098 newLength++; 19099 newItem = newValue[key]; 19100 oldItem = oldValue[key]; 19101 19102 if (key in oldValue) { 19103 // eslint-disable-next-line no-self-compare 19104 bothNaN = (oldItem !== oldItem) && (newItem !== newItem); 19105 if (!bothNaN && (oldItem !== newItem)) { 19106 changeDetected++; 19107 oldValue[key] = newItem; 19108 } 19109 } else { 19110 oldLength++; 19111 oldValue[key] = newItem; 19112 changeDetected++; 19113 } 19114 } 19115 } 19116 if (oldLength > newLength) { 19117 // we used to have more keys, need to find them and destroy them. 19118 changeDetected++; 19119 for (key in oldValue) { 19120 if (!hasOwnProperty.call(newValue, key)) { 19121 oldLength--; 19122 delete oldValue[key]; 19123 } 19124 } 19125 } 19126 } 19127 return changeDetected; 19128 } 19129 19130 function $watchCollectionAction() { 19131 if (initRun) { 19132 initRun = false; 19133 listener(newValue, newValue, self); 19134 } else { 19135 listener(newValue, veryOldValue, self); 19136 } 19137 19138 // make a copy for the next time a collection is changed 19139 if (trackVeryOldValue) { 19140 if (!isObject(newValue)) { 19141 //primitive 19142 veryOldValue = newValue; 19143 } else if (isArrayLike(newValue)) { 19144 veryOldValue = new Array(newValue.length); 19145 for (var i = 0; i < newValue.length; i++) { 19146 veryOldValue[i] = newValue[i]; 19147 } 19148 } else { // if object 19149 veryOldValue = {}; 19150 for (var key in newValue) { 19151 if (hasOwnProperty.call(newValue, key)) { 19152 veryOldValue[key] = newValue[key]; 19153 } 19154 } 19155 } 19156 } 19157 } 19158 19159 return this.$watch(changeDetector, $watchCollectionAction); 19160 }, 19161 19162 /** 19163 * @ngdoc method 19164 * @name $rootScope.Scope#$digest 19165 * @kind function 19166 * 19167 * @description 19168 * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and 19169 * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change 19170 * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} 19171 * until no more listeners are firing. This means that it is possible to get into an infinite 19172 * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of 19173 * iterations exceeds 10. 19174 * 19175 * Usually, you don't call `$digest()` directly in 19176 * {@link ng.directive:ngController controllers} or in 19177 * {@link ng.$compileProvider#directive directives}. 19178 * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within 19179 * a {@link ng.$compileProvider#directive directive}), which will force a `$digest()`. 19180 * 19181 * If you want to be notified whenever `$digest()` is called, 19182 * you can register a `watchExpression` function with 19183 * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`. 19184 * 19185 * In unit tests, you may need to call `$digest()` to simulate the scope life cycle. 19186 * 19187 * @example 19188 * ```js 19189 var scope = ...; 19190 scope.name = 'misko'; 19191 scope.counter = 0; 19192 19193 expect(scope.counter).toEqual(0); 19194 scope.$watch('name', function(newValue, oldValue) { 19195 scope.counter = scope.counter + 1; 19196 }); 19197 expect(scope.counter).toEqual(0); 19198 19199 scope.$digest(); 19200 // the listener is always called during the first $digest loop after it was registered 19201 expect(scope.counter).toEqual(1); 19202 19203 scope.$digest(); 19204 // but now it will not be called unless the value changes 19205 expect(scope.counter).toEqual(1); 19206 19207 scope.name = 'adam'; 19208 scope.$digest(); 19209 expect(scope.counter).toEqual(2); 19210 * ``` 19211 * 19212 */ 19213 $digest: function() { 19214 var watch, value, last, fn, get, 19215 watchers, 19216 dirty, ttl = TTL, 19217 next, current, target = asyncQueue.length ? $rootScope : this, 19218 watchLog = [], 19219 logIdx, asyncTask; 19220 19221 beginPhase('$digest'); 19222 // Check for changes to browser url that happened in sync before the call to $digest 19223 $browser.$$checkUrlChange(); 19224 19225 if (this === $rootScope && applyAsyncId !== null) { 19226 // If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then 19227 // cancel the scheduled $apply and flush the queue of expressions to be evaluated. 19228 $browser.defer.cancel(applyAsyncId); 19229 flushApplyAsync(); 19230 } 19231 19232 lastDirtyWatch = null; 19233 19234 do { // "while dirty" loop 19235 dirty = false; 19236 current = target; 19237 19238 // It's safe for asyncQueuePosition to be a local variable here because this loop can't 19239 // be reentered recursively. Calling $digest from a function passed to $evalAsync would 19240 // lead to a '$digest already in progress' error. 19241 for (var asyncQueuePosition = 0; asyncQueuePosition < asyncQueue.length; asyncQueuePosition++) { 19242 try { 19243 asyncTask = asyncQueue[asyncQueuePosition]; 19244 fn = asyncTask.fn; 19245 fn(asyncTask.scope, asyncTask.locals); 19246 } catch (e) { 19247 $exceptionHandler(e); 19248 } 19249 lastDirtyWatch = null; 19250 } 19251 asyncQueue.length = 0; 19252 19253 traverseScopesLoop: 19254 do { // "traverse the scopes" loop 19255 if ((watchers = !current.$$suspended && current.$$watchers)) { 19256 // process our watches 19257 watchers.$$digestWatchIndex = watchers.length; 19258 while (watchers.$$digestWatchIndex--) { 19259 try { 19260 watch = watchers[watchers.$$digestWatchIndex]; 19261 // Most common watches are on primitives, in which case we can short 19262 // circuit it with === operator, only when === fails do we use .equals 19263 if (watch) { 19264 get = watch.get; 19265 if ((value = get(current)) !== (last = watch.last) && 19266 !(watch.eq 19267 ? equals(value, last) 19268 : (isNumberNaN(value) && isNumberNaN(last)))) { 19269 dirty = true; 19270 lastDirtyWatch = watch; 19271 watch.last = watch.eq ? copy(value, null) : value; 19272 fn = watch.fn; 19273 fn(value, ((last === initWatchVal) ? value : last), current); 19274 if (ttl < 5) { 19275 logIdx = 4 - ttl; 19276 if (!watchLog[logIdx]) watchLog[logIdx] = []; 19277 watchLog[logIdx].push({ 19278 msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp, 19279 newVal: value, 19280 oldVal: last 19281 }); 19282 } 19283 } else if (watch === lastDirtyWatch) { 19284 // If the most recently dirty watcher is now clean, short circuit since the remaining watchers 19285 // have already been tested. 19286 dirty = false; 19287 break traverseScopesLoop; 19288 } 19289 } 19290 } catch (e) { 19291 $exceptionHandler(e); 19292 } 19293 } 19294 } 19295 19296 // Insanity Warning: scope depth-first traversal 19297 // yes, this code is a bit crazy, but it works and we have tests to prove it! 19298 // this piece should be kept in sync with the traversal in $broadcast 19299 // (though it differs due to having the extra check for $$suspended and does not 19300 // check $$listenerCount) 19301 if (!(next = ((!current.$$suspended && current.$$watchersCount && current.$$childHead) || 19302 (current !== target && current.$$nextSibling)))) { 19303 while (current !== target && !(next = current.$$nextSibling)) { 19304 current = current.$parent; 19305 } 19306 } 19307 } while ((current = next)); 19308 19309 // `break traverseScopesLoop;` takes us to here 19310 19311 if ((dirty || asyncQueue.length) && !(ttl--)) { 19312 clearPhase(); 19313 throw $rootScopeMinErr('infdig', 19314 '{0} $digest() iterations reached. Aborting!\n' + 19315 'Watchers fired in the last 5 iterations: {1}', 19316 TTL, watchLog); 19317 } 19318 19319 } while (dirty || asyncQueue.length); 19320 19321 clearPhase(); 19322 19323 // postDigestQueuePosition isn't local here because this loop can be reentered recursively. 19324 while (postDigestQueuePosition < postDigestQueue.length) { 19325 try { 19326 postDigestQueue[postDigestQueuePosition++](); 19327 } catch (e) { 19328 $exceptionHandler(e); 19329 } 19330 } 19331 postDigestQueue.length = postDigestQueuePosition = 0; 19332 19333 // Check for changes to browser url that happened during the $digest 19334 // (for which no event is fired; e.g. via `history.pushState()`) 19335 $browser.$$checkUrlChange(); 19336 }, 19337 19338 /** 19339 * @ngdoc method 19340 * @name $rootScope.Scope#$suspend 19341 * @kind function 19342 * 19343 * @description 19344 * Suspend watchers of this scope subtree so that they will not be invoked during digest. 19345 * 19346 * This can be used to optimize your application when you know that running those watchers 19347 * is redundant. 19348 * 19349 * **Warning** 19350 * 19351 * Suspending scopes from the digest cycle can have unwanted and difficult to debug results. 19352 * Only use this approach if you are confident that you know what you are doing and have 19353 * ample tests to ensure that bindings get updated as you expect. 19354 * 19355 * Some of the things to consider are: 19356 * 19357 * * Any external event on a directive/component will not trigger a digest while the hosting 19358 * scope is suspended - even if the event handler calls `$apply()` or `$rootScope.$digest()`. 19359 * * Transcluded content exists on a scope that inherits from outside a directive but exists 19360 * as a child of the directive's containing scope. If the containing scope is suspended the 19361 * transcluded scope will also be suspended, even if the scope from which the transcluded 19362 * scope inherits is not suspended. 19363 * * Multiple directives trying to manage the suspended status of a scope can confuse each other: 19364 * * A call to `$suspend()` on an already suspended scope is a no-op. 19365 * * A call to `$resume()` on a non-suspended scope is a no-op. 19366 * * If two directives suspend a scope, then one of them resumes the scope, the scope will no 19367 * longer be suspended. This could result in the other directive believing a scope to be 19368 * suspended when it is not. 19369 * * If a parent scope is suspended then all its descendants will be also excluded from future 19370 * digests whether or not they have been suspended themselves. Note that this also applies to 19371 * isolate child scopes. 19372 * * Calling `$digest()` directly on a descendant of a suspended scope will still run the watchers 19373 * for that scope and its descendants. When digesting we only check whether the current scope is 19374 * locally suspended, rather than checking whether it has a suspended ancestor. 19375 * * Calling `$resume()` on a scope that has a suspended ancestor will not cause the scope to be 19376 * included in future digests until all its ancestors have been resumed. 19377 * * Resolved promises, e.g. from explicit `$q` deferreds and `$http` calls, trigger `$apply()` 19378 * against the `$rootScope` and so will still trigger a global digest even if the promise was 19379 * initiated by a component that lives on a suspended scope. 19380 */ 19381 $suspend: function() { 19382 this.$$suspended = true; 19383 }, 19384 19385 /** 19386 * @ngdoc method 19387 * @name $rootScope.Scope#$isSuspended 19388 * @kind function 19389 * 19390 * @description 19391 * Call this method to determine if this scope has been explicitly suspended. It will not 19392 * tell you whether an ancestor has been suspended. 19393 * To determine if this scope will be excluded from a digest triggered at the $rootScope, 19394 * for example, you must check all its ancestors: 19395 * 19396 * ``` 19397 * function isExcludedFromDigest(scope) { 19398 * while(scope) { 19399 * if (scope.$isSuspended()) return true; 19400 * scope = scope.$parent; 19401 * } 19402 * return false; 19403 * ``` 19404 * 19405 * Be aware that a scope may not be included in digests if it has a suspended ancestor, 19406 * even if `$isSuspended()` returns false. 19407 * 19408 * @returns true if the current scope has been suspended. 19409 */ 19410 $isSuspended: function() { 19411 return this.$$suspended; 19412 }, 19413 19414 /** 19415 * @ngdoc method 19416 * @name $rootScope.Scope#$resume 19417 * @kind function 19418 * 19419 * @description 19420 * Resume watchers of this scope subtree in case it was suspended. 19421 * 19422 * See {@link $rootScope.Scope#$suspend} for information about the dangers of using this approach. 19423 */ 19424 $resume: function() { 19425 this.$$suspended = false; 19426 }, 19427 19428 /** 19429 * @ngdoc event 19430 * @name $rootScope.Scope#$destroy 19431 * @eventType broadcast on scope being destroyed 19432 * 19433 * @description 19434 * Broadcasted when a scope and its children are being destroyed. 19435 * 19436 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 19437 * clean up DOM bindings before an element is removed from the DOM. 19438 */ 19439 19440 /** 19441 * @ngdoc method 19442 * @name $rootScope.Scope#$destroy 19443 * @kind function 19444 * 19445 * @description 19446 * Removes the current scope (and all of its children) from the parent scope. Removal implies 19447 * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer 19448 * propagate to the current scope and its children. Removal also implies that the current 19449 * scope is eligible for garbage collection. 19450 * 19451 * The `$destroy()` is usually used by directives such as 19452 * {@link ng.directive:ngRepeat ngRepeat} for managing the 19453 * unrolling of the loop. 19454 * 19455 * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope. 19456 * Application code can register a `$destroy` event handler that will give it a chance to 19457 * perform any necessary cleanup. 19458 * 19459 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 19460 * clean up DOM bindings before an element is removed from the DOM. 19461 */ 19462 $destroy: function() { 19463 // We can't destroy a scope that has been already destroyed. 19464 if (this.$$destroyed) return; 19465 var parent = this.$parent; 19466 19467 this.$broadcast('$destroy'); 19468 this.$$destroyed = true; 19469 19470 if (this === $rootScope) { 19471 //Remove handlers attached to window when $rootScope is removed 19472 $browser.$$applicationDestroyed(); 19473 } 19474 19475 incrementWatchersCount(this, -this.$$watchersCount); 19476 for (var eventName in this.$$listenerCount) { 19477 decrementListenerCount(this, this.$$listenerCount[eventName], eventName); 19478 } 19479 19480 // sever all the references to parent scopes (after this cleanup, the current scope should 19481 // not be retained by any of our references and should be eligible for garbage collection) 19482 if (parent && parent.$$childHead === this) parent.$$childHead = this.$$nextSibling; 19483 if (parent && parent.$$childTail === this) parent.$$childTail = this.$$prevSibling; 19484 if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; 19485 if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; 19486 19487 // Disable listeners, watchers and apply/digest methods 19488 this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop; 19489 this.$on = this.$watch = this.$watchGroup = function() { return noop; }; 19490 this.$$listeners = {}; 19491 19492 // Disconnect the next sibling to prevent `cleanUpScope` destroying those too 19493 this.$$nextSibling = null; 19494 cleanUpScope(this); 19495 }, 19496 19497 /** 19498 * @ngdoc method 19499 * @name $rootScope.Scope#$eval 19500 * @kind function 19501 * 19502 * @description 19503 * Executes the `expression` on the current scope and returns the result. Any exceptions in 19504 * the expression are propagated (uncaught). This is useful when evaluating AngularJS 19505 * expressions. 19506 * 19507 * @example 19508 * ```js 19509 var scope = ng.$rootScope.Scope(); 19510 scope.a = 1; 19511 scope.b = 2; 19512 19513 expect(scope.$eval('a+b')).toEqual(3); 19514 expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3); 19515 * ``` 19516 * 19517 * @param {(string|function())=} expression An AngularJS expression to be executed. 19518 * 19519 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 19520 * - `function(scope)`: execute the function with the current `scope` parameter. 19521 * 19522 * @param {(object)=} locals Local variables object, useful for overriding values in scope. 19523 * @returns {*} The result of evaluating the expression. 19524 */ 19525 $eval: function(expr, locals) { 19526 return $parse(expr)(this, locals); 19527 }, 19528 19529 /** 19530 * @ngdoc method 19531 * @name $rootScope.Scope#$evalAsync 19532 * @kind function 19533 * 19534 * @description 19535 * Executes the expression on the current scope at a later point in time. 19536 * 19537 * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only 19538 * that: 19539 * 19540 * - it will execute after the function that scheduled the evaluation (preferably before DOM 19541 * rendering). 19542 * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after 19543 * `expression` execution. 19544 * 19545 * Any exceptions from the execution of the expression are forwarded to the 19546 * {@link ng.$exceptionHandler $exceptionHandler} service. 19547 * 19548 * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle 19549 * will be scheduled. However, it is encouraged to always call code that changes the model 19550 * from within an `$apply` call. That includes code evaluated via `$evalAsync`. 19551 * 19552 * @param {(string|function())=} expression An AngularJS expression to be executed. 19553 * 19554 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 19555 * - `function(scope)`: execute the function with the current `scope` parameter. 19556 * 19557 * @param {(object)=} locals Local variables object, useful for overriding values in scope. 19558 */ 19559 $evalAsync: function(expr, locals) { 19560 // if we are outside of an $digest loop and this is the first time we are scheduling async 19561 // task also schedule async auto-flush 19562 if (!$rootScope.$$phase && !asyncQueue.length) { 19563 $browser.defer(function() { 19564 if (asyncQueue.length) { 19565 $rootScope.$digest(); 19566 } 19567 }, null, '$evalAsync'); 19568 } 19569 19570 asyncQueue.push({scope: this, fn: $parse(expr), locals: locals}); 19571 }, 19572 19573 $$postDigest: function(fn) { 19574 postDigestQueue.push(fn); 19575 }, 19576 19577 /** 19578 * @ngdoc method 19579 * @name $rootScope.Scope#$apply 19580 * @kind function 19581 * 19582 * @description 19583 * `$apply()` is used to execute an expression in AngularJS from outside of the AngularJS 19584 * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). 19585 * Because we are calling into the AngularJS framework we need to perform proper scope life 19586 * cycle of {@link ng.$exceptionHandler exception handling}, 19587 * {@link ng.$rootScope.Scope#$digest executing watches}. 19588 * 19589 * **Life cycle: Pseudo-Code of `$apply()`** 19590 * 19591 * ```js 19592 function $apply(expr) { 19593 try { 19594 return $eval(expr); 19595 } catch (e) { 19596 $exceptionHandler(e); 19597 } finally { 19598 $root.$digest(); 19599 } 19600 } 19601 * ``` 19602 * 19603 * 19604 * Scope's `$apply()` method transitions through the following stages: 19605 * 19606 * 1. The {@link guide/expression expression} is executed using the 19607 * {@link ng.$rootScope.Scope#$eval $eval()} method. 19608 * 2. Any exceptions from the execution of the expression are forwarded to the 19609 * {@link ng.$exceptionHandler $exceptionHandler} service. 19610 * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the 19611 * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method. 19612 * 19613 * 19614 * @param {(string|function())=} exp An AngularJS expression to be executed. 19615 * 19616 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 19617 * - `function(scope)`: execute the function with current `scope` parameter. 19618 * 19619 * @returns {*} The result of evaluating the expression. 19620 */ 19621 $apply: function(expr) { 19622 try { 19623 beginPhase('$apply'); 19624 try { 19625 return this.$eval(expr); 19626 } finally { 19627 clearPhase(); 19628 } 19629 } catch (e) { 19630 $exceptionHandler(e); 19631 } finally { 19632 try { 19633 $rootScope.$digest(); 19634 } catch (e) { 19635 $exceptionHandler(e); 19636 // eslint-disable-next-line no-unsafe-finally 19637 throw e; 19638 } 19639 } 19640 }, 19641 19642 /** 19643 * @ngdoc method 19644 * @name $rootScope.Scope#$applyAsync 19645 * @kind function 19646 * 19647 * @description 19648 * Schedule the invocation of $apply to occur at a later time. The actual time difference 19649 * varies across browsers, but is typically around ~10 milliseconds. 19650 * 19651 * This can be used to queue up multiple expressions which need to be evaluated in the same 19652 * digest. 19653 * 19654 * @param {(string|function())=} exp An AngularJS expression to be executed. 19655 * 19656 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 19657 * - `function(scope)`: execute the function with current `scope` parameter. 19658 */ 19659 $applyAsync: function(expr) { 19660 var scope = this; 19661 if (expr) { 19662 applyAsyncQueue.push($applyAsyncExpression); 19663 } 19664 expr = $parse(expr); 19665 scheduleApplyAsync(); 19666 19667 function $applyAsyncExpression() { 19668 scope.$eval(expr); 19669 } 19670 }, 19671 19672 /** 19673 * @ngdoc method 19674 * @name $rootScope.Scope#$on 19675 * @kind function 19676 * 19677 * @description 19678 * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for 19679 * discussion of event life cycle. 19680 * 19681 * The event listener function format is: `function(event, args...)`. The `event` object 19682 * passed into the listener has the following attributes: 19683 * 19684 * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or 19685 * `$broadcast`-ed. 19686 * - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the 19687 * event propagates through the scope hierarchy, this property is set to null. 19688 * - `name` - `{string}`: name of the event. 19689 * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel 19690 * further event propagation (available only for events that were `$emit`-ed). 19691 * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag 19692 * to true. 19693 * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called. 19694 * 19695 * @param {string} name Event name to listen on. 19696 * @param {function(event, ...args)} listener Function to call when the event is emitted. 19697 * @returns {function()} Returns a deregistration function for this listener. 19698 */ 19699 $on: function(name, listener) { 19700 var namedListeners = this.$$listeners[name]; 19701 if (!namedListeners) { 19702 this.$$listeners[name] = namedListeners = []; 19703 } 19704 namedListeners.push(listener); 19705 19706 var current = this; 19707 do { 19708 if (!current.$$listenerCount[name]) { 19709 current.$$listenerCount[name] = 0; 19710 } 19711 current.$$listenerCount[name]++; 19712 } while ((current = current.$parent)); 19713 19714 var self = this; 19715 return function() { 19716 var indexOfListener = namedListeners.indexOf(listener); 19717 if (indexOfListener !== -1) { 19718 // Use delete in the hope of the browser deallocating the memory for the array entry, 19719 // while not shifting the array indexes of other listeners. 19720 // See issue https://github.com/angular/angular.js/issues/16135 19721 delete namedListeners[indexOfListener]; 19722 decrementListenerCount(self, 1, name); 19723 } 19724 }; 19725 }, 19726 19727 19728 /** 19729 * @ngdoc method 19730 * @name $rootScope.Scope#$emit 19731 * @kind function 19732 * 19733 * @description 19734 * Dispatches an event `name` upwards through the scope hierarchy notifying the 19735 * registered {@link ng.$rootScope.Scope#$on} listeners. 19736 * 19737 * The event life cycle starts at the scope on which `$emit` was called. All 19738 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 19739 * notified. Afterwards, the event traverses upwards toward the root scope and calls all 19740 * registered listeners along the way. The event will stop propagating if one of the listeners 19741 * cancels it. 19742 * 19743 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 19744 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 19745 * 19746 * @param {string} name Event name to emit. 19747 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 19748 * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}). 19749 */ 19750 $emit: function(name, args) { 19751 var empty = [], 19752 namedListeners, 19753 scope = this, 19754 stopPropagation = false, 19755 event = { 19756 name: name, 19757 targetScope: scope, 19758 stopPropagation: function() {stopPropagation = true;}, 19759 preventDefault: function() { 19760 event.defaultPrevented = true; 19761 }, 19762 defaultPrevented: false 19763 }, 19764 listenerArgs = concat([event], arguments, 1), 19765 i, length; 19766 19767 do { 19768 namedListeners = scope.$$listeners[name] || empty; 19769 event.currentScope = scope; 19770 for (i = 0, length = namedListeners.length; i < length; i++) { 19771 19772 // if listeners were deregistered, defragment the array 19773 if (!namedListeners[i]) { 19774 namedListeners.splice(i, 1); 19775 i--; 19776 length--; 19777 continue; 19778 } 19779 try { 19780 //allow all listeners attached to the current scope to run 19781 namedListeners[i].apply(null, listenerArgs); 19782 } catch (e) { 19783 $exceptionHandler(e); 19784 } 19785 } 19786 //if any listener on the current scope stops propagation, prevent bubbling 19787 if (stopPropagation) { 19788 break; 19789 } 19790 //traverse upwards 19791 scope = scope.$parent; 19792 } while (scope); 19793 19794 event.currentScope = null; 19795 19796 return event; 19797 }, 19798 19799 19800 /** 19801 * @ngdoc method 19802 * @name $rootScope.Scope#$broadcast 19803 * @kind function 19804 * 19805 * @description 19806 * Dispatches an event `name` downwards to all child scopes (and their children) notifying the 19807 * registered {@link ng.$rootScope.Scope#$on} listeners. 19808 * 19809 * The event life cycle starts at the scope on which `$broadcast` was called. All 19810 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 19811 * notified. Afterwards, the event propagates to all direct and indirect scopes of the current 19812 * scope and calls all registered listeners along the way. The event cannot be canceled. 19813 * 19814 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 19815 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 19816 * 19817 * @param {string} name Event name to broadcast. 19818 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 19819 * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on} 19820 */ 19821 $broadcast: function(name, args) { 19822 var target = this, 19823 current = target, 19824 next = target, 19825 event = { 19826 name: name, 19827 targetScope: target, 19828 preventDefault: function() { 19829 event.defaultPrevented = true; 19830 }, 19831 defaultPrevented: false 19832 }; 19833 19834 if (!target.$$listenerCount[name]) return event; 19835 19836 var listenerArgs = concat([event], arguments, 1), 19837 listeners, i, length; 19838 19839 //down while you can, then up and next sibling or up and next sibling until back at root 19840 while ((current = next)) { 19841 event.currentScope = current; 19842 listeners = current.$$listeners[name] || []; 19843 for (i = 0, length = listeners.length; i < length; i++) { 19844 // if listeners were deregistered, defragment the array 19845 if (!listeners[i]) { 19846 listeners.splice(i, 1); 19847 i--; 19848 length--; 19849 continue; 19850 } 19851 19852 try { 19853 listeners[i].apply(null, listenerArgs); 19854 } catch (e) { 19855 $exceptionHandler(e); 19856 } 19857 } 19858 19859 // Insanity Warning: scope depth-first traversal 19860 // yes, this code is a bit crazy, but it works and we have tests to prove it! 19861 // this piece should be kept in sync with the traversal in $digest 19862 // (though it differs due to having the extra check for $$listenerCount and 19863 // does not check $$suspended) 19864 if (!(next = ((current.$$listenerCount[name] && current.$$childHead) || 19865 (current !== target && current.$$nextSibling)))) { 19866 while (current !== target && !(next = current.$$nextSibling)) { 19867 current = current.$parent; 19868 } 19869 } 19870 } 19871 19872 event.currentScope = null; 19873 return event; 19874 } 19875 }; 19876 19877 var $rootScope = new Scope(); 19878 19879 //The internal queues. Expose them on the $rootScope for debugging/testing purposes. 19880 var asyncQueue = $rootScope.$$asyncQueue = []; 19881 var postDigestQueue = $rootScope.$$postDigestQueue = []; 19882 var applyAsyncQueue = $rootScope.$$applyAsyncQueue = []; 19883 19884 var postDigestQueuePosition = 0; 19885 19886 return $rootScope; 19887 19888 19889 function beginPhase(phase) { 19890 if ($rootScope.$$phase) { 19891 throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase); 19892 } 19893 19894 $rootScope.$$phase = phase; 19895 } 19896 19897 function clearPhase() { 19898 $rootScope.$$phase = null; 19899 } 19900 19901 function incrementWatchersCount(current, count) { 19902 do { 19903 current.$$watchersCount += count; 19904 } while ((current = current.$parent)); 19905 } 19906 19907 function decrementListenerCount(current, count, name) { 19908 do { 19909 current.$$listenerCount[name] -= count; 19910 19911 if (current.$$listenerCount[name] === 0) { 19912 delete current.$$listenerCount[name]; 19913 } 19914 } while ((current = current.$parent)); 19915 } 19916 19917 /** 19918 * function used as an initial value for watchers. 19919 * because it's unique we can easily tell it apart from other values 19920 */ 19921 function initWatchVal() {} 19922 19923 function flushApplyAsync() { 19924 while (applyAsyncQueue.length) { 19925 try { 19926 applyAsyncQueue.shift()(); 19927 } catch (e) { 19928 $exceptionHandler(e); 19929 } 19930 } 19931 applyAsyncId = null; 19932 } 19933 19934 function scheduleApplyAsync() { 19935 if (applyAsyncId === null) { 19936 applyAsyncId = $browser.defer(function() { 19937 $rootScope.$apply(flushApplyAsync); 19938 }, null, '$applyAsync'); 19939 } 19940 } 19941 }]; 19942} 19943 19944/** 19945 * @ngdoc service 19946 * @name $rootElement 19947 * 19948 * @description 19949 * The root element of AngularJS application. This is either the element where {@link 19950 * ng.directive:ngApp ngApp} was declared or the element passed into 19951 * {@link angular.bootstrap}. The element represents the root element of application. It is also the 19952 * location where the application's {@link auto.$injector $injector} service gets 19953 * published, and can be retrieved using `$rootElement.injector()`. 19954 */ 19955 19956 19957// the implementation is in angular.bootstrap 19958 19959/** 19960 * @this 19961 * @description 19962 * Private service to sanitize uris for links and images. Used by $compile and $sanitize. 19963 */ 19964function $$SanitizeUriProvider() { 19965 19966 var aHrefSanitizationTrustedUrlList = /^\s*(https?|s?ftp|mailto|tel|file):/, 19967 imgSrcSanitizationTrustedUrlList = /^\s*((https?|ftp|file|blob):|data:image\/)/; 19968 19969 /** 19970 * @description 19971 * Retrieves or overrides the default regular expression that is used for determining trusted safe 19972 * urls during a[href] sanitization. 19973 * 19974 * The sanitization is a security measure aimed at prevent XSS attacks via HTML anchor links. 19975 * 19976 * Any url due to be assigned to an `a[href]` attribute via interpolation is marked as requiring 19977 * the $sce.URL security context. When interpolation occurs a call is made to `$sce.trustAsUrl(url)` 19978 * which in turn may call `$$sanitizeUri(url, isMedia)` to sanitize the potentially malicious URL. 19979 * 19980 * If the URL matches the `aHrefSanitizationTrustedUrlList` regular expression, it is returned unchanged. 19981 * 19982 * If there is no match the URL is returned prefixed with `'unsafe:'` to ensure that when it is written 19983 * to the DOM it is inactive and potentially malicious code will not be executed. 19984 * 19985 * @param {RegExp=} regexp New regexp to trust urls with. 19986 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 19987 * chaining otherwise. 19988 */ 19989 this.aHrefSanitizationTrustedUrlList = function(regexp) { 19990 if (isDefined(regexp)) { 19991 aHrefSanitizationTrustedUrlList = regexp; 19992 return this; 19993 } 19994 return aHrefSanitizationTrustedUrlList; 19995 }; 19996 19997 19998 /** 19999 * @description 20000 * Retrieves or overrides the default regular expression that is used for determining trusted safe 20001 * urls during img[src] sanitization. 20002 * 20003 * The sanitization is a security measure aimed at prevent XSS attacks via HTML image src links. 20004 * 20005 * Any URL due to be assigned to an `img[src]` attribute via interpolation is marked as requiring 20006 * the $sce.MEDIA_URL security context. When interpolation occurs a call is made to 20007 * `$sce.trustAsMediaUrl(url)` which in turn may call `$$sanitizeUri(url, isMedia)` to sanitize 20008 * the potentially malicious URL. 20009 * 20010 * If the URL matches the `imgSrcSanitizationTrustedUrlList` regular expression, it is returned 20011 * unchanged. 20012 * 20013 * If there is no match the URL is returned prefixed with `'unsafe:'` to ensure that when it is written 20014 * to the DOM it is inactive and potentially malicious code will not be executed. 20015 * 20016 * @param {RegExp=} regexp New regexp to trust urls with. 20017 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 20018 * chaining otherwise. 20019 */ 20020 this.imgSrcSanitizationTrustedUrlList = function(regexp) { 20021 if (isDefined(regexp)) { 20022 imgSrcSanitizationTrustedUrlList = regexp; 20023 return this; 20024 } 20025 return imgSrcSanitizationTrustedUrlList; 20026 }; 20027 20028 this.$get = function() { 20029 return function sanitizeUri(uri, isMediaUrl) { 20030 // if (!uri) return uri; 20031 var regex = isMediaUrl ? imgSrcSanitizationTrustedUrlList : aHrefSanitizationTrustedUrlList; 20032 var normalizedVal = urlResolve(uri && uri.trim()).href; 20033 if (normalizedVal !== '' && !normalizedVal.match(regex)) { 20034 return 'unsafe:' + normalizedVal; 20035 } 20036 return uri; 20037 }; 20038 }; 20039} 20040 20041/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 20042 * Any commits to this file should be reviewed with security in mind. * 20043 * Changes to this file can potentially create security vulnerabilities. * 20044 * An approval from 2 Core members with history of modifying * 20045 * this file is required. * 20046 * * 20047 * Does the change somehow allow for arbitrary javascript to be executed? * 20048 * Or allows for someone to change the prototype of built-in objects? * 20049 * Or gives undesired access to variables likes document or window? * 20050 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 20051 20052/* exported $SceProvider, $SceDelegateProvider */ 20053 20054var $sceMinErr = minErr('$sce'); 20055 20056var SCE_CONTEXTS = { 20057 // HTML is used when there's HTML rendered (e.g. ng-bind-html, iframe srcdoc binding). 20058 HTML: 'html', 20059 20060 // Style statements or stylesheets. Currently unused in AngularJS. 20061 CSS: 'css', 20062 20063 // An URL used in a context where it refers to the source of media, which are not expected to be run 20064 // as scripts, such as an image, audio, video, etc. 20065 MEDIA_URL: 'mediaUrl', 20066 20067 // An URL used in a context where it does not refer to a resource that loads code. 20068 // A value that can be trusted as a URL can also trusted as a MEDIA_URL. 20069 URL: 'url', 20070 20071 // RESOURCE_URL is a subtype of URL used where the referred-to resource could be interpreted as 20072 // code. (e.g. ng-include, script src binding, templateUrl) 20073 // A value that can be trusted as a RESOURCE_URL, can also trusted as a URL and a MEDIA_URL. 20074 RESOURCE_URL: 'resourceUrl', 20075 20076 // Script. Currently unused in AngularJS. 20077 JS: 'js' 20078}; 20079 20080// Helper functions follow. 20081 20082var UNDERSCORE_LOWERCASE_REGEXP = /_([a-z])/g; 20083 20084function snakeToCamel(name) { 20085 return name 20086 .replace(UNDERSCORE_LOWERCASE_REGEXP, fnCamelCaseReplace); 20087} 20088 20089function adjustMatcher(matcher) { 20090 if (matcher === 'self') { 20091 return matcher; 20092 } else if (isString(matcher)) { 20093 // Strings match exactly except for 2 wildcards - '*' and '**'. 20094 // '*' matches any character except those from the set ':/.?&'. 20095 // '**' matches any character (like .* in a RegExp). 20096 // More than 2 *'s raises an error as it's ill defined. 20097 if (matcher.indexOf('***') > -1) { 20098 throw $sceMinErr('iwcard', 20099 'Illegal sequence *** in string matcher. String: {0}', matcher); 20100 } 20101 matcher = escapeForRegexp(matcher). 20102 replace(/\\\*\\\*/g, '.*'). 20103 replace(/\\\*/g, '[^:/.?&;]*'); 20104 return new RegExp('^' + matcher + '$'); 20105 } else if (isRegExp(matcher)) { 20106 // The only other type of matcher allowed is a Regexp. 20107 // Match entire URL / disallow partial matches. 20108 // Flags are reset (i.e. no global, ignoreCase or multiline) 20109 return new RegExp('^' + matcher.source + '$'); 20110 } else { 20111 throw $sceMinErr('imatcher', 20112 'Matchers may only be "self", string patterns or RegExp objects'); 20113 } 20114} 20115 20116 20117function adjustMatchers(matchers) { 20118 var adjustedMatchers = []; 20119 if (isDefined(matchers)) { 20120 forEach(matchers, function(matcher) { 20121 adjustedMatchers.push(adjustMatcher(matcher)); 20122 }); 20123 } 20124 return adjustedMatchers; 20125} 20126 20127 20128/** 20129 * @ngdoc service 20130 * @name $sceDelegate 20131 * @kind function 20132 * 20133 * @description 20134 * 20135 * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict 20136 * Contextual Escaping (SCE)} services to AngularJS. 20137 * 20138 * For an overview of this service and the functionnality it provides in AngularJS, see the main 20139 * page for {@link ng.$sce SCE}. The current page is targeted for developers who need to alter how 20140 * SCE works in their application, which shouldn't be needed in most cases. 20141 * 20142 * <div class="alert alert-danger"> 20143 * AngularJS strongly relies on contextual escaping for the security of bindings: disabling or 20144 * modifying this might cause cross site scripting (XSS) vulnerabilities. For libraries owners, 20145 * changes to this service will also influence users, so be extra careful and document your changes. 20146 * </div> 20147 * 20148 * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of 20149 * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is 20150 * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to 20151 * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things 20152 * work because `$sce` delegates to `$sceDelegate` for these operations. 20153 * 20154 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service. 20155 * 20156 * The default instance of `$sceDelegate` should work out of the box with little pain. While you 20157 * can override it completely to change the behavior of `$sce`, the common case would 20158 * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting 20159 * your own trusted and banned resource lists for trusting URLs used for loading AngularJS resources 20160 * such as templates. Refer {@link ng.$sceDelegateProvider#trustedResourceUrlList 20161 * $sceDelegateProvider.trustedResourceUrlList} and {@link 20162 * ng.$sceDelegateProvider#bannedResourceUrlList $sceDelegateProvider.bannedResourceUrlList} 20163 */ 20164 20165/** 20166 * @ngdoc provider 20167 * @name $sceDelegateProvider 20168 * @this 20169 * 20170 * @description 20171 * 20172 * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate 20173 * $sceDelegate service}, used as a delegate for {@link ng.$sce Strict Contextual Escaping (SCE)}. 20174 * 20175 * The `$sceDelegateProvider` allows one to get/set the `trustedResourceUrlList` and 20176 * `bannedResourceUrlList` used to ensure that the URLs used for sourcing AngularJS templates and 20177 * other script-running URLs are safe (all places that use the `$sce.RESOURCE_URL` context). See 20178 * {@link ng.$sceDelegateProvider#trustedResourceUrlList 20179 * $sceDelegateProvider.trustedResourceUrlList} and 20180 * {@link ng.$sceDelegateProvider#bannedResourceUrlList $sceDelegateProvider.bannedResourceUrlList}, 20181 * 20182 * For the general details about this service in AngularJS, read the main page for {@link ng.$sce 20183 * Strict Contextual Escaping (SCE)}. 20184 * 20185 * **Example**: Consider the following case. <a name="example"></a> 20186 * 20187 * - your app is hosted at url `http://myapp.example.com/` 20188 * - but some of your templates are hosted on other domains you control such as 20189 * `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc. 20190 * - and you have an open redirect at `http://myapp.example.com/clickThru?...`. 20191 * 20192 * Here is what a secure configuration for this scenario might look like: 20193 * 20194 * ``` 20195 * angular.module('myApp', []).config(function($sceDelegateProvider) { 20196 * $sceDelegateProvider.trustedResourceUrlList([ 20197 * // Allow same origin resource loads. 20198 * 'self', 20199 * // Allow loading from our assets domain. Notice the difference between * and **. 20200 * 'http://srv*.assets.example.com/**' 20201 * ]); 20202 * 20203 * // The banned resource URL list overrides the trusted resource URL list so the open redirect 20204 * // here is blocked. 20205 * $sceDelegateProvider.bannedResourceUrlList([ 20206 * 'http://myapp.example.com/clickThru**' 20207 * ]); 20208 * }); 20209 * ``` 20210 * Note that an empty trusted resource URL list will block every resource URL from being loaded, and will require 20211 * you to manually mark each one as trusted with `$sce.trustAsResourceUrl`. However, templates 20212 * requested by {@link ng.$templateRequest $templateRequest} that are present in 20213 * {@link ng.$templateCache $templateCache} will not go through this check. If you have a mechanism 20214 * to populate your templates in that cache at config time, then it is a good idea to remove 'self' 20215 * from the trusted resource URL lsit. This helps to mitigate the security impact of certain types 20216 * of issues, like for instance attacker-controlled `ng-includes`. 20217 */ 20218 20219function $SceDelegateProvider() { 20220 this.SCE_CONTEXTS = SCE_CONTEXTS; 20221 20222 // Resource URLs can also be trusted by policy. 20223 var trustedResourceUrlList = ['self'], 20224 bannedResourceUrlList = []; 20225 20226 /** 20227 * @ngdoc method 20228 * @name $sceDelegateProvider#trustedResourceUrlList 20229 * @kind function 20230 * 20231 * @param {Array=} trustedResourceUrlList When provided, replaces the trustedResourceUrlList with 20232 * the value provided. This must be an array or null. A snapshot of this array is used so 20233 * further changes to the array are ignored. 20234 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 20235 * allowed in this array. 20236 * 20237 * @return {Array} The currently set trusted resource URL array. 20238 * 20239 * @description 20240 * Sets/Gets the list trusted of resource URLs. 20241 * 20242 * The **default value** when no `trustedResourceUrlList` has been explicitly set is `['self']` 20243 * allowing only same origin resource requests. 20244 * 20245 * <div class="alert alert-warning"> 20246 * **Note:** the default `trustedResourceUrlList` of 'self' is not recommended if your app shares 20247 * its origin with other apps! It is a good idea to limit it to only your application's directory. 20248 * </div> 20249 */ 20250 this.trustedResourceUrlList = function(value) { 20251 if (arguments.length) { 20252 trustedResourceUrlList = adjustMatchers(value); 20253 } 20254 return trustedResourceUrlList; 20255 }; 20256 20257 /** 20258 * @ngdoc method 20259 * @name $sceDelegateProvider#resourceUrlWhitelist 20260 * @kind function 20261 * 20262 * @deprecated 20263 * sinceVersion="1.8.1" 20264 * 20265 * This method is deprecated. Use {@link $sceDelegateProvider#trustedResourceUrlList 20266 * trustedResourceUrlList} instead. 20267 */ 20268 Object.defineProperty(this, 'resourceUrlWhitelist', { 20269 get: function() { 20270 return this.trustedResourceUrlList; 20271 }, 20272 set: function(value) { 20273 this.trustedResourceUrlList = value; 20274 } 20275 }); 20276 20277 /** 20278 * @ngdoc method 20279 * @name $sceDelegateProvider#bannedResourceUrlList 20280 * @kind function 20281 * 20282 * @param {Array=} bannedResourceUrlList When provided, replaces the `bannedResourceUrlList` with 20283 * the value provided. This must be an array or null. A snapshot of this array is used so 20284 * further changes to the array are ignored.</p><p> 20285 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 20286 * allowed in this array.</p><p> 20287 * The typical usage for the `bannedResourceUrlList` is to **block 20288 * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as 20289 * these would otherwise be trusted but actually return content from the redirected domain. 20290 * </p><p> 20291 * Finally, **the banned resource URL list overrides the trusted resource URL list** and has 20292 * the final say. 20293 * 20294 * @return {Array} The currently set `bannedResourceUrlList` array. 20295 * 20296 * @description 20297 * Sets/Gets the `bannedResourceUrlList` of trusted resource URLs. 20298 * 20299 * The **default value** when no trusted resource URL list has been explicitly set is the empty 20300 * array (i.e. there is no `bannedResourceUrlList`.) 20301 */ 20302 this.bannedResourceUrlList = function(value) { 20303 if (arguments.length) { 20304 bannedResourceUrlList = adjustMatchers(value); 20305 } 20306 return bannedResourceUrlList; 20307 }; 20308 20309 /** 20310 * @ngdoc method 20311 * @name $sceDelegateProvider#resourceUrlBlacklist 20312 * @kind function 20313 * 20314 * @deprecated 20315 * sinceVersion="1.8.1" 20316 * 20317 * This method is deprecated. Use {@link $sceDelegateProvider#bannedResourceUrlList 20318 * bannedResourceUrlList} instead. 20319 */ 20320 Object.defineProperty(this, 'resourceUrlBlacklist', { 20321 get: function() { 20322 return this.bannedResourceUrlList; 20323 }, 20324 set: function(value) { 20325 this.bannedResourceUrlList = value; 20326 } 20327 }); 20328 20329 this.$get = ['$injector', '$$sanitizeUri', function($injector, $$sanitizeUri) { 20330 20331 var htmlSanitizer = function htmlSanitizer(html) { 20332 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 20333 }; 20334 20335 if ($injector.has('$sanitize')) { 20336 htmlSanitizer = $injector.get('$sanitize'); 20337 } 20338 20339 20340 function matchUrl(matcher, parsedUrl) { 20341 if (matcher === 'self') { 20342 return urlIsSameOrigin(parsedUrl) || urlIsSameOriginAsBaseUrl(parsedUrl); 20343 } else { 20344 // definitely a regex. See adjustMatchers() 20345 return !!matcher.exec(parsedUrl.href); 20346 } 20347 } 20348 20349 function isResourceUrlAllowedByPolicy(url) { 20350 var parsedUrl = urlResolve(url.toString()); 20351 var i, n, allowed = false; 20352 // Ensure that at least one item from the trusted resource URL list allows this url. 20353 for (i = 0, n = trustedResourceUrlList.length; i < n; i++) { 20354 if (matchUrl(trustedResourceUrlList[i], parsedUrl)) { 20355 allowed = true; 20356 break; 20357 } 20358 } 20359 if (allowed) { 20360 // Ensure that no item from the banned resource URL list has blocked this url. 20361 for (i = 0, n = bannedResourceUrlList.length; i < n; i++) { 20362 if (matchUrl(bannedResourceUrlList[i], parsedUrl)) { 20363 allowed = false; 20364 break; 20365 } 20366 } 20367 } 20368 return allowed; 20369 } 20370 20371 function generateHolderType(Base) { 20372 var holderType = function TrustedValueHolderType(trustedValue) { 20373 this.$$unwrapTrustedValue = function() { 20374 return trustedValue; 20375 }; 20376 }; 20377 if (Base) { 20378 holderType.prototype = new Base(); 20379 } 20380 holderType.prototype.valueOf = function sceValueOf() { 20381 return this.$$unwrapTrustedValue(); 20382 }; 20383 holderType.prototype.toString = function sceToString() { 20384 return this.$$unwrapTrustedValue().toString(); 20385 }; 20386 return holderType; 20387 } 20388 20389 var trustedValueHolderBase = generateHolderType(), 20390 byType = {}; 20391 20392 byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase); 20393 byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase); 20394 byType[SCE_CONTEXTS.MEDIA_URL] = generateHolderType(trustedValueHolderBase); 20395 byType[SCE_CONTEXTS.URL] = generateHolderType(byType[SCE_CONTEXTS.MEDIA_URL]); 20396 byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase); 20397 byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]); 20398 20399 /** 20400 * @ngdoc method 20401 * @name $sceDelegate#trustAs 20402 * 20403 * @description 20404 * Returns a trusted representation of the parameter for the specified context. This trusted 20405 * object will later on be used as-is, without any security check, by bindings or directives 20406 * that require this security context. 20407 * For instance, marking a string as trusted for the `$sce.HTML` context will entirely bypass 20408 * the potential `$sanitize` call in corresponding `$sce.HTML` bindings or directives, such as 20409 * `ng-bind-html`. Note that in most cases you won't need to call this function: if you have the 20410 * sanitizer loaded, passing the value itself will render all the HTML that does not pose a 20411 * security risk. 20412 * 20413 * See {@link ng.$sceDelegate#getTrusted getTrusted} for the function that will consume those 20414 * trusted values, and {@link ng.$sce $sce} for general documentation about strict contextual 20415 * escaping. 20416 * 20417 * @param {string} type The context in which this value is safe for use, e.g. `$sce.URL`, 20418 * `$sce.RESOURCE_URL`, `$sce.HTML`, `$sce.JS` or `$sce.CSS`. 20419 * 20420 * @param {*} value The value that should be considered trusted. 20421 * @return {*} A trusted representation of value, that can be used in the given context. 20422 */ 20423 function trustAs(type, trustedValue) { 20424 var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 20425 if (!Constructor) { 20426 throw $sceMinErr('icontext', 20427 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}', 20428 type, trustedValue); 20429 } 20430 if (trustedValue === null || isUndefined(trustedValue) || trustedValue === '') { 20431 return trustedValue; 20432 } 20433 // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting 20434 // mutable objects, we ensure here that the value passed in is actually a string. 20435 if (typeof trustedValue !== 'string') { 20436 throw $sceMinErr('itype', 20437 'Attempted to trust a non-string value in a content requiring a string: Context: {0}', 20438 type); 20439 } 20440 return new Constructor(trustedValue); 20441 } 20442 20443 /** 20444 * @ngdoc method 20445 * @name $sceDelegate#valueOf 20446 * 20447 * @description 20448 * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs 20449 * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link 20450 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. 20451 * 20452 * If the passed parameter is not a value that had been returned by {@link 20453 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, it must be returned as-is. 20454 * 20455 * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} 20456 * call or anything else. 20457 * @return {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs 20458 * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns 20459 * `value` unchanged. 20460 */ 20461 function valueOf(maybeTrusted) { 20462 if (maybeTrusted instanceof trustedValueHolderBase) { 20463 return maybeTrusted.$$unwrapTrustedValue(); 20464 } else { 20465 return maybeTrusted; 20466 } 20467 } 20468 20469 /** 20470 * @ngdoc method 20471 * @name $sceDelegate#getTrusted 20472 * 20473 * @description 20474 * Given an object and a security context in which to assign it, returns a value that's safe to 20475 * use in this context, which was represented by the parameter. To do so, this function either 20476 * unwraps the safe type it has been given (for instance, a {@link ng.$sceDelegate#trustAs 20477 * `$sceDelegate.trustAs`} result), or it might try to sanitize the value given, depending on 20478 * the context and sanitizer availablility. 20479 * 20480 * The contexts that can be sanitized are $sce.MEDIA_URL, $sce.URL and $sce.HTML. The first two are available 20481 * by default, and the third one relies on the `$sanitize` service (which may be loaded through 20482 * the `ngSanitize` module). Furthermore, for $sce.RESOURCE_URL context, a plain string may be 20483 * accepted if the resource url policy defined by {@link ng.$sceDelegateProvider#trustedResourceUrlList 20484 * `$sceDelegateProvider.trustedResourceUrlList`} and {@link ng.$sceDelegateProvider#bannedResourceUrlList 20485 * `$sceDelegateProvider.bannedResourceUrlList`} accepts that resource. 20486 * 20487 * This function will throw if the safe type isn't appropriate for this context, or if the 20488 * value given cannot be accepted in the context (which might be caused by sanitization not 20489 * being available, or the value not being recognized as safe). 20490 * 20491 * <div class="alert alert-danger"> 20492 * Disabling auto-escaping is extremely dangerous, it usually creates a Cross Site Scripting 20493 * (XSS) vulnerability in your application. 20494 * </div> 20495 * 20496 * @param {string} type The context in which this value is to be used (such as `$sce.HTML`). 20497 * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs 20498 * `$sceDelegate.trustAs`} call, or anything else (which will not be considered trusted.) 20499 * @return {*} A version of the value that's safe to use in the given context, or throws an 20500 * exception if this is impossible. 20501 */ 20502 function getTrusted(type, maybeTrusted) { 20503 if (maybeTrusted === null || isUndefined(maybeTrusted) || maybeTrusted === '') { 20504 return maybeTrusted; 20505 } 20506 var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 20507 // If maybeTrusted is a trusted class instance or subclass instance, then unwrap and return 20508 // as-is. 20509 if (constructor && maybeTrusted instanceof constructor) { 20510 return maybeTrusted.$$unwrapTrustedValue(); 20511 } 20512 20513 // If maybeTrusted is a trusted class instance but not of the correct trusted type 20514 // then unwrap it and allow it to pass through to the rest of the checks 20515 if (isFunction(maybeTrusted.$$unwrapTrustedValue)) { 20516 maybeTrusted = maybeTrusted.$$unwrapTrustedValue(); 20517 } 20518 20519 // If we get here, then we will either sanitize the value or throw an exception. 20520 if (type === SCE_CONTEXTS.MEDIA_URL || type === SCE_CONTEXTS.URL) { 20521 // we attempt to sanitize non-resource URLs 20522 return $$sanitizeUri(maybeTrusted.toString(), type === SCE_CONTEXTS.MEDIA_URL); 20523 } else if (type === SCE_CONTEXTS.RESOURCE_URL) { 20524 if (isResourceUrlAllowedByPolicy(maybeTrusted)) { 20525 return maybeTrusted; 20526 } else { 20527 throw $sceMinErr('insecurl', 20528 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}', 20529 maybeTrusted.toString()); 20530 } 20531 } else if (type === SCE_CONTEXTS.HTML) { 20532 // htmlSanitizer throws its own error when no sanitizer is available. 20533 return htmlSanitizer(maybeTrusted); 20534 } 20535 // Default error when the $sce service has no way to make the input safe. 20536 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 20537 } 20538 20539 return { trustAs: trustAs, 20540 getTrusted: getTrusted, 20541 valueOf: valueOf }; 20542 }]; 20543} 20544 20545 20546/** 20547 * @ngdoc provider 20548 * @name $sceProvider 20549 * @this 20550 * 20551 * @description 20552 * 20553 * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service. 20554 * - enable/disable Strict Contextual Escaping (SCE) in a module 20555 * - override the default implementation with a custom delegate 20556 * 20557 * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}. 20558 */ 20559 20560/** 20561 * @ngdoc service 20562 * @name $sce 20563 * @kind function 20564 * 20565 * @description 20566 * 20567 * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS. 20568 * 20569 * ## Strict Contextual Escaping 20570 * 20571 * Strict Contextual Escaping (SCE) is a mode in which AngularJS constrains bindings to only render 20572 * trusted values. Its goal is to assist in writing code in a way that (a) is secure by default, and 20573 * (b) makes auditing for security vulnerabilities such as XSS, clickjacking, etc. a lot easier. 20574 * 20575 * ### Overview 20576 * 20577 * To systematically block XSS security bugs, AngularJS treats all values as untrusted by default in 20578 * HTML or sensitive URL bindings. When binding untrusted values, AngularJS will automatically 20579 * run security checks on them (sanitizations, trusted URL resource, depending on context), or throw 20580 * when it cannot guarantee the security of the result. That behavior depends strongly on contexts: 20581 * HTML can be sanitized, but template URLs cannot, for instance. 20582 * 20583 * To illustrate this, consider the `ng-bind-html` directive. It renders its value directly as HTML: 20584 * we call that the *context*. When given an untrusted input, AngularJS will attempt to sanitize it 20585 * before rendering if a sanitizer is available, and throw otherwise. To bypass sanitization and 20586 * render the input as-is, you will need to mark it as trusted for that context before attempting 20587 * to bind it. 20588 * 20589 * As of version 1.2, AngularJS ships with SCE enabled by default. 20590 * 20591 * ### In practice 20592 * 20593 * Here's an example of a binding in a privileged context: 20594 * 20595 * ``` 20596 * <input ng-model="userHtml" aria-label="User input"> 20597 * <div ng-bind-html="userHtml"></div> 20598 * ``` 20599 * 20600 * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE 20601 * disabled, this application allows the user to render arbitrary HTML into the DIV, which would 20602 * be an XSS security bug. In a more realistic example, one may be rendering user comments, blog 20603 * articles, etc. via bindings. (HTML is just one example of a context where rendering user 20604 * controlled input creates security vulnerabilities.) 20605 * 20606 * For the case of HTML, you might use a library, either on the client side, or on the server side, 20607 * to sanitize unsafe HTML before binding to the value and rendering it in the document. 20608 * 20609 * How would you ensure that every place that used these types of bindings was bound to a value that 20610 * was sanitized by your library (or returned as safe for rendering by your server?) How can you 20611 * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some 20612 * properties/fields and forgot to update the binding to the sanitized value? 20613 * 20614 * To be secure by default, AngularJS makes sure bindings go through that sanitization, or 20615 * any similar validation process, unless there's a good reason to trust the given value in this 20616 * context. That trust is formalized with a function call. This means that as a developer, you 20617 * can assume all untrusted bindings are safe. Then, to audit your code for binding security issues, 20618 * you just need to ensure the values you mark as trusted indeed are safe - because they were 20619 * received from your server, sanitized by your library, etc. You can organize your codebase to 20620 * help with this - perhaps allowing only the files in a specific directory to do this. 20621 * Ensuring that the internal API exposed by that code doesn't markup arbitrary values as safe then 20622 * becomes a more manageable task. 20623 * 20624 * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs} 20625 * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to 20626 * build the trusted versions of your values. 20627 * 20628 * ### How does it work? 20629 * 20630 * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted 20631 * $sce.getTrusted(context, value)} rather than to the value directly. Think of this function as 20632 * a way to enforce the required security context in your data sink. Directives use {@link 20633 * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs 20634 * the {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. Also, 20635 * when binding without directives, AngularJS will understand the context of your bindings 20636 * automatically. 20637 * 20638 * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link 20639 * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly 20640 * simplified): 20641 * 20642 * ``` 20643 * var ngBindHtmlDirective = ['$sce', function($sce) { 20644 * return function(scope, element, attr) { 20645 * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) { 20646 * element.html(value || ''); 20647 * }); 20648 * }; 20649 * }]; 20650 * ``` 20651 * 20652 * ### Impact on loading templates 20653 * 20654 * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as 20655 * `templateUrl`'s specified by {@link guide/directive directives}. 20656 * 20657 * By default, AngularJS only loads templates from the same domain and protocol as the application 20658 * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl 20659 * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or 20660 * protocols, you may either add them to the {@link ng.$sceDelegateProvider#trustedResourceUrlList 20661 * trustedResourceUrlList} or {@link ng.$sce#trustAsResourceUrl wrap them} into trusted values. 20662 * 20663 * *Please note*: 20664 * The browser's 20665 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 20666 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 20667 * policy apply in addition to this and may further restrict whether the template is successfully 20668 * loaded. This means that without the right CORS policy, loading templates from a different domain 20669 * won't work on all browsers. Also, loading templates from `file://` URL does not work on some 20670 * browsers. 20671 * 20672 * ### This feels like too much overhead 20673 * 20674 * It's important to remember that SCE only applies to interpolation expressions. 20675 * 20676 * If your expressions are constant literals, they're automatically trusted and you don't need to 20677 * call `$sce.trustAs` on them (e.g. 20678 * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works (remember to include the 20679 * `ngSanitize` module). The `$sceDelegate` will also use the `$sanitize` service if it is available 20680 * when binding untrusted values to `$sce.HTML` context. 20681 * AngularJS provides an implementation in `angular-sanitize.js`, and if you 20682 * wish to use it, you will also need to depend on the {@link ngSanitize `ngSanitize`} module in 20683 * your application. 20684 * 20685 * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load 20686 * templates in `ng-include` from your application's domain without having to even know about SCE. 20687 * It blocks loading templates from other domains or loading templates over http from an https 20688 * served document. You can change these by setting your own custom {@link 20689 * ng.$sceDelegateProvider#trustedResourceUrlList trusted resource URL list} and {@link 20690 * ng.$sceDelegateProvider#bannedResourceUrlList banned resource URL list} for matching such URLs. 20691 * 20692 * This significantly reduces the overhead. It is far easier to pay the small overhead and have an 20693 * application that's secure and can be audited to verify that with much more ease than bolting 20694 * security onto an application later. 20695 * 20696 * <a name="contexts"></a> 20697 * ### What trusted context types are supported? 20698 * 20699 * | Context | Notes | 20700 * |---------------------|----------------| 20701 * | `$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. | 20702 * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. | 20703 * | `$sce.MEDIA_URL` | For URLs that are safe to render as media. Is automatically converted from string by sanitizing when needed. | 20704 * | `$sce.URL` | For URLs that are safe to follow as links. Is automatically converted from string by sanitizing when needed. Note that `$sce.URL` makes a stronger statement about the URL than `$sce.MEDIA_URL` does and therefore contexts requiring values trusted for `$sce.URL` can be used anywhere that values trusted for `$sce.MEDIA_URL` are required.| 20705 * | `$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` (e.g. `IFRAME`, `OBJECT`, etc.) <br><br>Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` or `$sce.MEDIA_URL` do and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` or `$sce.MEDIA_URL` are required. <br><br> The {@link $sceDelegateProvider#trustedResourceUrlList $sceDelegateProvider#trustedResourceUrlList()} and {@link $sceDelegateProvider#bannedResourceUrlList $sceDelegateProvider#bannedResourceUrlList()} can be used to restrict trusted origins for `RESOURCE_URL` | 20706 * | `$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. | 20707 * 20708 * 20709 * <div class="alert alert-warning"> 20710 * Be aware that, before AngularJS 1.7.0, `a[href]` and `img[src]` used to sanitize their 20711 * interpolated values directly rather than rely upon {@link ng.$sce#getTrusted `$sce.getTrusted`}. 20712 * 20713 * **As of 1.7.0, this is no longer the case.** 20714 * 20715 * Now such interpolations are marked as requiring `$sce.URL` (for `a[href]`) or `$sce.MEDIA_URL` 20716 * (for `img[src]`), so that the sanitization happens (via `$sce.getTrusted...`) when the `$interpolate` 20717 * service evaluates the expressions. 20718 * </div> 20719 * 20720 * There are no CSS or JS context bindings in AngularJS currently, so their corresponding `$sce.trustAs` 20721 * functions aren't useful yet. This might evolve. 20722 * 20723 * ### Format of items in {@link ng.$sceDelegateProvider#trustedResourceUrlList trustedResourceUrlList}/{@link ng.$sceDelegateProvider#bannedResourceUrlList bannedResourceUrlList} <a name="resourceUrlPatternItem"></a> 20724 * 20725 * Each element in these arrays must be one of the following: 20726 * 20727 * - **'self'** 20728 * - The special **string**, `'self'`, can be used to match against all URLs of the **same 20729 * domain** as the application document using the **same protocol**. 20730 * - **String** (except the special value `'self'`) 20731 * - The string is matched against the full *normalized / absolute URL* of the resource 20732 * being tested (substring matches are not good enough.) 20733 * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters 20734 * match themselves. 20735 * - `*`: matches zero or more occurrences of any character other than one of the following 6 20736 * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and '`;`'. It's a useful wildcard for use 20737 * for matching resource URL lists. 20738 * - `**`: matches zero or more occurrences of *any* character. As such, it's not 20739 * appropriate for use in a scheme, domain, etc. as it would match too much. (e.g. 20740 * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might 20741 * not have been the intention.) Its usage at the very end of the path is ok. (e.g. 20742 * http://foo.example.com/templates/**). 20743 * - **RegExp** (*see caveat below*) 20744 * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax 20745 * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to 20746 * accidentally introduce a bug when one updates a complex expression (imho, all regexes should 20747 * have good test coverage). For instance, the use of `.` in the regex is correct only in a 20748 * small number of cases. A `.` character in the regex used when matching the scheme or a 20749 * subdomain could be matched against a `:` or literal `.` that was likely not intended. It 20750 * is highly recommended to use the string patterns and only fall back to regular expressions 20751 * as a last resort. 20752 * - The regular expression must be an instance of RegExp (i.e. not a string.) It is 20753 * matched against the **entire** *normalized / absolute URL* of the resource being tested 20754 * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags 20755 * present on the RegExp (such as multiline, global, ignoreCase) are ignored. 20756 * - If you are generating your JavaScript from some other templating engine (not 20757 * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)), 20758 * remember to escape your regular expression (and be aware that you might need more than 20759 * one level of escaping depending on your templating engine and the way you interpolated 20760 * the value.) Do make use of your platform's escaping mechanism as it might be good 20761 * enough before coding your own. E.g. Ruby has 20762 * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape) 20763 * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape). 20764 * Javascript lacks a similar built in function for escaping. Take a look at Google 20765 * Closure library's [goog.string.regExpEscape(s)]( 20766 * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962). 20767 * 20768 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example. 20769 * 20770 * ### Show me an example using SCE. 20771 * 20772 * <example module="mySceApp" deps="angular-sanitize.js" name="sce-service"> 20773 * <file name="index.html"> 20774 * <div ng-controller="AppController as myCtrl"> 20775 * <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br> 20776 * <b>User comments</b><br> 20777 * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when 20778 * $sanitize is available. If $sanitize isn't available, this results in an error instead of an 20779 * exploit. 20780 * <div class="well"> 20781 * <div ng-repeat="userComment in myCtrl.userComments"> 20782 * <b>{{userComment.name}}</b>: 20783 * <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span> 20784 * <br> 20785 * </div> 20786 * </div> 20787 * </div> 20788 * </file> 20789 * 20790 * <file name="script.js"> 20791 * angular.module('mySceApp', ['ngSanitize']) 20792 * .controller('AppController', ['$http', '$templateCache', '$sce', 20793 * function AppController($http, $templateCache, $sce) { 20794 * var self = this; 20795 * $http.get('test_data.json', {cache: $templateCache}).then(function(response) { 20796 * self.userComments = response.data; 20797 * }); 20798 * self.explicitlyTrustedHtml = $sce.trustAsHtml( 20799 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 20800 * 'sanitization."">Hover over this text.</span>'); 20801 * }]); 20802 * </file> 20803 * 20804 * <file name="test_data.json"> 20805 * [ 20806 * { "name": "Alice", 20807 * "htmlComment": 20808 * "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>" 20809 * }, 20810 * { "name": "Bob", 20811 * "htmlComment": "<i>Yes!</i> Am I the only other one?" 20812 * } 20813 * ] 20814 * </file> 20815 * 20816 * <file name="protractor.js" type="protractor"> 20817 * describe('SCE doc demo', function() { 20818 * it('should sanitize untrusted values', function() { 20819 * expect(element.all(by.css('.htmlComment')).first().getAttribute('innerHTML')) 20820 * .toBe('<span>Is <i>anyone</i> reading this?</span>'); 20821 * }); 20822 * 20823 * it('should NOT sanitize explicitly trusted values', function() { 20824 * expect(element(by.id('explicitlyTrustedHtml')).getAttribute('innerHTML')).toBe( 20825 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 20826 * 'sanitization."">Hover over this text.</span>'); 20827 * }); 20828 * }); 20829 * </file> 20830 * </example> 20831 * 20832 * 20833 * 20834 * ## Can I disable SCE completely? 20835 * 20836 * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits 20837 * for little coding overhead. It will be much harder to take an SCE disabled application and 20838 * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE 20839 * for cases where you have a lot of existing code that was written before SCE was introduced and 20840 * you're migrating them a module at a time. Also do note that this is an app-wide setting, so if 20841 * you are writing a library, you will cause security bugs applications using it. 20842 * 20843 * That said, here's how you can completely disable SCE: 20844 * 20845 * ``` 20846 * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) { 20847 * // Completely disable SCE. For demonstration purposes only! 20848 * // Do not use in new projects or libraries. 20849 * $sceProvider.enabled(false); 20850 * }); 20851 * ``` 20852 * 20853 */ 20854 20855function $SceProvider() { 20856 var enabled = true; 20857 20858 /** 20859 * @ngdoc method 20860 * @name $sceProvider#enabled 20861 * @kind function 20862 * 20863 * @param {boolean=} value If provided, then enables/disables SCE application-wide. 20864 * @return {boolean} True if SCE is enabled, false otherwise. 20865 * 20866 * @description 20867 * Enables/disables SCE and returns the current value. 20868 */ 20869 this.enabled = function(value) { 20870 if (arguments.length) { 20871 enabled = !!value; 20872 } 20873 return enabled; 20874 }; 20875 20876 20877 /* Design notes on the default implementation for SCE. 20878 * 20879 * The API contract for the SCE delegate 20880 * ------------------------------------- 20881 * The SCE delegate object must provide the following 3 methods: 20882 * 20883 * - trustAs(contextEnum, value) 20884 * This method is used to tell the SCE service that the provided value is OK to use in the 20885 * contexts specified by contextEnum. It must return an object that will be accepted by 20886 * getTrusted() for a compatible contextEnum and return this value. 20887 * 20888 * - valueOf(value) 20889 * For values that were not produced by trustAs(), return them as is. For values that were 20890 * produced by trustAs(), return the corresponding input value to trustAs. Basically, if 20891 * trustAs is wrapping the given values into some type, this operation unwraps it when given 20892 * such a value. 20893 * 20894 * - getTrusted(contextEnum, value) 20895 * This function should return the value that is safe to use in the context specified by 20896 * contextEnum or throw and exception otherwise. 20897 * 20898 * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be 20899 * opaque or wrapped in some holder object. That happens to be an implementation detail. For 20900 * instance, an implementation could maintain a registry of all trusted objects by context. In 20901 * such a case, trustAs() would return the same object that was passed in. getTrusted() would 20902 * return the same object passed in if it was found in the registry under a compatible context or 20903 * throw an exception otherwise. An implementation might only wrap values some of the time based 20904 * on some criteria. getTrusted() might return a value and not throw an exception for special 20905 * constants or objects even if not wrapped. All such implementations fulfill this contract. 20906 * 20907 * 20908 * A note on the inheritance model for SCE contexts 20909 * ------------------------------------------------ 20910 * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This 20911 * is purely an implementation details. 20912 * 20913 * The contract is simply this: 20914 * 20915 * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value) 20916 * will also succeed. 20917 * 20918 * Inheritance happens to capture this in a natural way. In some future, we may not use 20919 * inheritance anymore. That is OK because no code outside of sce.js and sceSpecs.js would need to 20920 * be aware of this detail. 20921 */ 20922 20923 this.$get = ['$parse', '$sceDelegate', function( 20924 $parse, $sceDelegate) { 20925 // Support: IE 9-11 only 20926 // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow 20927 // the "expression(javascript expression)" syntax which is insecure. 20928 if (enabled && msie < 8) { 20929 throw $sceMinErr('iequirks', 20930 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' + 20931 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' + 20932 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); 20933 } 20934 20935 var sce = shallowCopy(SCE_CONTEXTS); 20936 20937 /** 20938 * @ngdoc method 20939 * @name $sce#isEnabled 20940 * @kind function 20941 * 20942 * @return {Boolean} True if SCE is enabled, false otherwise. If you want to set the value, you 20943 * have to do it at module config time on {@link ng.$sceProvider $sceProvider}. 20944 * 20945 * @description 20946 * Returns a boolean indicating if SCE is enabled. 20947 */ 20948 sce.isEnabled = function() { 20949 return enabled; 20950 }; 20951 sce.trustAs = $sceDelegate.trustAs; 20952 sce.getTrusted = $sceDelegate.getTrusted; 20953 sce.valueOf = $sceDelegate.valueOf; 20954 20955 if (!enabled) { 20956 sce.trustAs = sce.getTrusted = function(type, value) { return value; }; 20957 sce.valueOf = identity; 20958 } 20959 20960 /** 20961 * @ngdoc method 20962 * @name $sce#parseAs 20963 * 20964 * @description 20965 * Converts AngularJS {@link guide/expression expression} into a function. This is like {@link 20966 * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it 20967 * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*, 20968 * *result*)} 20969 * 20970 * @param {string} type The SCE context in which this result will be used. 20971 * @param {string} expression String expression to compile. 20972 * @return {function(context, locals)} A function which represents the compiled expression: 20973 * 20974 * * `context` â `{object}` â an object against which any expressions embedded in the 20975 * strings are evaluated against (typically a scope object). 20976 * * `locals` â `{object=}` â local variables context object, useful for overriding values 20977 * in `context`. 20978 */ 20979 sce.parseAs = function sceParseAs(type, expr) { 20980 var parsed = $parse(expr); 20981 if (parsed.literal && parsed.constant) { 20982 return parsed; 20983 } else { 20984 return $parse(expr, function(value) { 20985 return sce.getTrusted(type, value); 20986 }); 20987 } 20988 }; 20989 20990 /** 20991 * @ngdoc method 20992 * @name $sce#trustAs 20993 * 20994 * @description 20995 * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, returns a 20996 * wrapped object that represents your value, and the trust you have in its safety for the given 20997 * context. AngularJS can then use that value as-is in bindings of the specified secure context. 20998 * This is used in bindings for `ng-bind-html`, `ng-include`, and most `src` attribute 20999 * interpolations. See {@link ng.$sce $sce} for strict contextual escaping. 21000 * 21001 * @param {string} type The context in which this value is safe for use, e.g. `$sce.URL`, 21002 * `$sce.RESOURCE_URL`, `$sce.HTML`, `$sce.JS` or `$sce.CSS`. 21003 * 21004 * @param {*} value The value that that should be considered trusted. 21005 * @return {*} A wrapped version of value that can be used as a trusted variant of your `value` 21006 * in the context you specified. 21007 */ 21008 21009 /** 21010 * @ngdoc method 21011 * @name $sce#trustAsHtml 21012 * 21013 * @description 21014 * Shorthand method. `$sce.trustAsHtml(value)` â 21015 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`} 21016 * 21017 * @param {*} value The value to mark as trusted for `$sce.HTML` context. 21018 * @return {*} A wrapped version of value that can be used as a trusted variant of your `value` 21019 * in `$sce.HTML` context (like `ng-bind-html`). 21020 */ 21021 21022 /** 21023 * @ngdoc method 21024 * @name $sce#trustAsCss 21025 * 21026 * @description 21027 * Shorthand method. `$sce.trustAsCss(value)` â 21028 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.CSS, value)`} 21029 * 21030 * @param {*} value The value to mark as trusted for `$sce.CSS` context. 21031 * @return {*} A wrapped version of value that can be used as a trusted variant 21032 * of your `value` in `$sce.CSS` context. This context is currently unused, so there are 21033 * almost no reasons to use this function so far. 21034 */ 21035 21036 /** 21037 * @ngdoc method 21038 * @name $sce#trustAsUrl 21039 * 21040 * @description 21041 * Shorthand method. `$sce.trustAsUrl(value)` â 21042 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`} 21043 * 21044 * @param {*} value The value to mark as trusted for `$sce.URL` context. 21045 * @return {*} A wrapped version of value that can be used as a trusted variant of your `value` 21046 * in `$sce.URL` context. That context is currently unused, so there are almost no reasons 21047 * to use this function so far. 21048 */ 21049 21050 /** 21051 * @ngdoc method 21052 * @name $sce#trustAsResourceUrl 21053 * 21054 * @description 21055 * Shorthand method. `$sce.trustAsResourceUrl(value)` â 21056 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`} 21057 * 21058 * @param {*} value The value to mark as trusted for `$sce.RESOURCE_URL` context. 21059 * @return {*} A wrapped version of value that can be used as a trusted variant of your `value` 21060 * in `$sce.RESOURCE_URL` context (template URLs in `ng-include`, most `src` attribute 21061 * bindings, ...) 21062 */ 21063 21064 /** 21065 * @ngdoc method 21066 * @name $sce#trustAsJs 21067 * 21068 * @description 21069 * Shorthand method. `$sce.trustAsJs(value)` â 21070 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`} 21071 * 21072 * @param {*} value The value to mark as trusted for `$sce.JS` context. 21073 * @return {*} A wrapped version of value that can be used as a trusted variant of your `value` 21074 * in `$sce.JS` context. That context is currently unused, so there are almost no reasons to 21075 * use this function so far. 21076 */ 21077 21078 /** 21079 * @ngdoc method 21080 * @name $sce#getTrusted 21081 * 21082 * @description 21083 * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such, 21084 * takes any input, and either returns a value that's safe to use in the specified context, 21085 * or throws an exception. This function is aware of trusted values created by the `trustAs` 21086 * function and its shorthands, and when contexts are appropriate, returns the unwrapped value 21087 * as-is. Finally, this function can also throw when there is no way to turn `maybeTrusted` in a 21088 * safe value (e.g., no sanitization is available or possible.) 21089 * 21090 * @param {string} type The context in which this value is to be used. 21091 * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs 21092 * `$sce.trustAs`} call, or anything else (which will not be considered trusted.) 21093 * @return {*} A version of the value that's safe to use in the given context, or throws an 21094 * exception if this is impossible. 21095 */ 21096 21097 /** 21098 * @ngdoc method 21099 * @name $sce#getTrustedHtml 21100 * 21101 * @description 21102 * Shorthand method. `$sce.getTrustedHtml(value)` â 21103 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`} 21104 * 21105 * @param {*} value The value to pass to `$sce.getTrusted`. 21106 * @return {*} The return value of `$sce.getTrusted($sce.HTML, value)` 21107 */ 21108 21109 /** 21110 * @ngdoc method 21111 * @name $sce#getTrustedCss 21112 * 21113 * @description 21114 * Shorthand method. `$sce.getTrustedCss(value)` â 21115 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`} 21116 * 21117 * @param {*} value The value to pass to `$sce.getTrusted`. 21118 * @return {*} The return value of `$sce.getTrusted($sce.CSS, value)` 21119 */ 21120 21121 /** 21122 * @ngdoc method 21123 * @name $sce#getTrustedUrl 21124 * 21125 * @description 21126 * Shorthand method. `$sce.getTrustedUrl(value)` â 21127 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`} 21128 * 21129 * @param {*} value The value to pass to `$sce.getTrusted`. 21130 * @return {*} The return value of `$sce.getTrusted($sce.URL, value)` 21131 */ 21132 21133 /** 21134 * @ngdoc method 21135 * @name $sce#getTrustedResourceUrl 21136 * 21137 * @description 21138 * Shorthand method. `$sce.getTrustedResourceUrl(value)` â 21139 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`} 21140 * 21141 * @param {*} value The value to pass to `$sceDelegate.getTrusted`. 21142 * @return {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)` 21143 */ 21144 21145 /** 21146 * @ngdoc method 21147 * @name $sce#getTrustedJs 21148 * 21149 * @description 21150 * Shorthand method. `$sce.getTrustedJs(value)` â 21151 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`} 21152 * 21153 * @param {*} value The value to pass to `$sce.getTrusted`. 21154 * @return {*} The return value of `$sce.getTrusted($sce.JS, value)` 21155 */ 21156 21157 /** 21158 * @ngdoc method 21159 * @name $sce#parseAsHtml 21160 * 21161 * @description 21162 * Shorthand method. `$sce.parseAsHtml(expression string)` â 21163 * {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`} 21164 * 21165 * @param {string} expression String expression to compile. 21166 * @return {function(context, locals)} A function which represents the compiled expression: 21167 * 21168 * * `context` â `{object}` â an object against which any expressions embedded in the 21169 * strings are evaluated against (typically a scope object). 21170 * * `locals` â `{object=}` â local variables context object, useful for overriding values 21171 * in `context`. 21172 */ 21173 21174 /** 21175 * @ngdoc method 21176 * @name $sce#parseAsCss 21177 * 21178 * @description 21179 * Shorthand method. `$sce.parseAsCss(value)` â 21180 * {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`} 21181 * 21182 * @param {string} expression String expression to compile. 21183 * @return {function(context, locals)} A function which represents the compiled expression: 21184 * 21185 * * `context` â `{object}` â an object against which any expressions embedded in the 21186 * strings are evaluated against (typically a scope object). 21187 * * `locals` â `{object=}` â local variables context object, useful for overriding values 21188 * in `context`. 21189 */ 21190 21191 /** 21192 * @ngdoc method 21193 * @name $sce#parseAsUrl 21194 * 21195 * @description 21196 * Shorthand method. `$sce.parseAsUrl(value)` â 21197 * {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`} 21198 * 21199 * @param {string} expression String expression to compile. 21200 * @return {function(context, locals)} A function which represents the compiled expression: 21201 * 21202 * * `context` â `{object}` â an object against which any expressions embedded in the 21203 * strings are evaluated against (typically a scope object). 21204 * * `locals` â `{object=}` â local variables context object, useful for overriding values 21205 * in `context`. 21206 */ 21207 21208 /** 21209 * @ngdoc method 21210 * @name $sce#parseAsResourceUrl 21211 * 21212 * @description 21213 * Shorthand method. `$sce.parseAsResourceUrl(value)` â 21214 * {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`} 21215 * 21216 * @param {string} expression String expression to compile. 21217 * @return {function(context, locals)} A function which represents the compiled expression: 21218 * 21219 * * `context` â `{object}` â an object against which any expressions embedded in the 21220 * strings are evaluated against (typically a scope object). 21221 * * `locals` â `{object=}` â local variables context object, useful for overriding values 21222 * in `context`. 21223 */ 21224 21225 /** 21226 * @ngdoc method 21227 * @name $sce#parseAsJs 21228 * 21229 * @description 21230 * Shorthand method. `$sce.parseAsJs(value)` â 21231 * {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`} 21232 * 21233 * @param {string} expression String expression to compile. 21234 * @return {function(context, locals)} A function which represents the compiled expression: 21235 * 21236 * * `context` â `{object}` â an object against which any expressions embedded in the 21237 * strings are evaluated against (typically a scope object). 21238 * * `locals` â `{object=}` â local variables context object, useful for overriding values 21239 * in `context`. 21240 */ 21241 21242 // Shorthand delegations. 21243 var parse = sce.parseAs, 21244 getTrusted = sce.getTrusted, 21245 trustAs = sce.trustAs; 21246 21247 forEach(SCE_CONTEXTS, function(enumValue, name) { 21248 var lName = lowercase(name); 21249 sce[snakeToCamel('parse_as_' + lName)] = function(expr) { 21250 return parse(enumValue, expr); 21251 }; 21252 sce[snakeToCamel('get_trusted_' + lName)] = function(value) { 21253 return getTrusted(enumValue, value); 21254 }; 21255 sce[snakeToCamel('trust_as_' + lName)] = function(value) { 21256 return trustAs(enumValue, value); 21257 }; 21258 }); 21259 21260 return sce; 21261 }]; 21262} 21263 21264/* exported $SnifferProvider */ 21265 21266/** 21267 * !!! This is an undocumented "private" service !!! 21268 * 21269 * @name $sniffer 21270 * @requires $window 21271 * @requires $document 21272 * @this 21273 * 21274 * @property {boolean} history Does the browser support html5 history api ? 21275 * @property {boolean} transitions Does the browser support CSS transition events ? 21276 * @property {boolean} animations Does the browser support CSS animation events ? 21277 * 21278 * @description 21279 * This is very simple implementation of testing browser's features. 21280 */ 21281function $SnifferProvider() { 21282 this.$get = ['$window', '$document', function($window, $document) { 21283 var eventSupport = {}, 21284 // Chrome Packaged Apps are not allowed to access `history.pushState`. 21285 // If not sandboxed, they can be detected by the presence of `chrome.app.runtime` 21286 // (see https://developer.chrome.com/apps/api_index). If sandboxed, they can be detected by 21287 // the presence of an extension runtime ID and the absence of other Chrome runtime APIs 21288 // (see https://developer.chrome.com/apps/manifest/sandbox). 21289 // (NW.js apps have access to Chrome APIs, but do support `history`.) 21290 isNw = $window.nw && $window.nw.process, 21291 isChromePackagedApp = 21292 !isNw && 21293 $window.chrome && 21294 ($window.chrome.app && $window.chrome.app.runtime || 21295 !$window.chrome.app && $window.chrome.runtime && $window.chrome.runtime.id), 21296 hasHistoryPushState = !isChromePackagedApp && $window.history && $window.history.pushState, 21297 android = 21298 toInt((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), 21299 boxee = /Boxee/i.test(($window.navigator || {}).userAgent), 21300 document = $document[0] || {}, 21301 bodyStyle = document.body && document.body.style, 21302 transitions = false, 21303 animations = false; 21304 21305 if (bodyStyle) { 21306 // Support: Android <5, Blackberry Browser 10, default Chrome in Android 4.4.x 21307 // Mentioned browsers need a -webkit- prefix for transitions & animations. 21308 transitions = !!('transition' in bodyStyle || 'webkitTransition' in bodyStyle); 21309 animations = !!('animation' in bodyStyle || 'webkitAnimation' in bodyStyle); 21310 } 21311 21312 21313 return { 21314 // Android has history.pushState, but it does not update location correctly 21315 // so let's not use the history API at all. 21316 // http://code.google.com/p/android/issues/detail?id=17471 21317 // https://github.com/angular/angular.js/issues/904 21318 21319 // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has 21320 // so let's not use the history API also 21321 // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined 21322 history: !!(hasHistoryPushState && !(android < 4) && !boxee), 21323 hasEvent: function(event) { 21324 // Support: IE 9-11 only 21325 // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have 21326 // it. In particular the event is not fired when backspace or delete key are pressed or 21327 // when cut operation is performed. 21328 // IE10+ implements 'input' event but it erroneously fires under various situations, 21329 // e.g. when placeholder changes, or a form is focused. 21330 if (event === 'input' && msie) return false; 21331 21332 if (isUndefined(eventSupport[event])) { 21333 var divElm = document.createElement('div'); 21334 eventSupport[event] = 'on' + event in divElm; 21335 } 21336 21337 return eventSupport[event]; 21338 }, 21339 csp: csp(), 21340 transitions: transitions, 21341 animations: animations, 21342 android: android 21343 }; 21344 }]; 21345} 21346 21347/** 21348 * ! This is a private undocumented service ! 21349 * 21350 * @name $$taskTrackerFactory 21351 * @description 21352 * A function to create `TaskTracker` instances. 21353 * 21354 * A `TaskTracker` can keep track of pending tasks (grouped by type) and can notify interested 21355 * parties when all pending tasks (or tasks of a specific type) have been completed. 21356 * 21357 * @param {$log} log - A logger instance (such as `$log`). Used to log error during callback 21358 * execution. 21359 * 21360 * @this 21361 */ 21362function $$TaskTrackerFactoryProvider() { 21363 this.$get = valueFn(function(log) { return new TaskTracker(log); }); 21364} 21365 21366function TaskTracker(log) { 21367 var self = this; 21368 var taskCounts = {}; 21369 var taskCallbacks = []; 21370 21371 var ALL_TASKS_TYPE = self.ALL_TASKS_TYPE = '$$all$$'; 21372 var DEFAULT_TASK_TYPE = self.DEFAULT_TASK_TYPE = '$$default$$'; 21373 21374 /** 21375 * Execute the specified function and decrement the appropriate `taskCounts` counter. 21376 * If the counter reaches 0, all corresponding `taskCallbacks` are executed. 21377 * 21378 * @param {Function} fn - The function to execute. 21379 * @param {string=} [taskType=DEFAULT_TASK_TYPE] - The type of task that is being completed. 21380 */ 21381 self.completeTask = completeTask; 21382 21383 /** 21384 * Increase the task count for the specified task type (or the default task type if non is 21385 * specified). 21386 * 21387 * @param {string=} [taskType=DEFAULT_TASK_TYPE] - The type of task whose count will be increased. 21388 */ 21389 self.incTaskCount = incTaskCount; 21390 21391 /** 21392 * Execute the specified callback when all pending tasks have been completed. 21393 * 21394 * If there are no pending tasks, the callback is executed immediately. You can optionally limit 21395 * the tasks that will be waited for to a specific type, by passing a `taskType`. 21396 * 21397 * @param {function} callback - The function to call when there are no pending tasks. 21398 * @param {string=} [taskType=ALL_TASKS_TYPE] - The type of tasks that will be waited for. 21399 */ 21400 self.notifyWhenNoPendingTasks = notifyWhenNoPendingTasks; 21401 21402 function completeTask(fn, taskType) { 21403 taskType = taskType || DEFAULT_TASK_TYPE; 21404 21405 try { 21406 fn(); 21407 } finally { 21408 decTaskCount(taskType); 21409 21410 var countForType = taskCounts[taskType]; 21411 var countForAll = taskCounts[ALL_TASKS_TYPE]; 21412 21413 // If at least one of the queues (`ALL_TASKS_TYPE` or `taskType`) is empty, run callbacks. 21414 if (!countForAll || !countForType) { 21415 var getNextCallback = !countForAll ? getLastCallback : getLastCallbackForType; 21416 var nextCb; 21417 21418 while ((nextCb = getNextCallback(taskType))) { 21419 try { 21420 nextCb(); 21421 } catch (e) { 21422 log.error(e); 21423 } 21424 } 21425 } 21426 } 21427 } 21428 21429 function decTaskCount(taskType) { 21430 taskType = taskType || DEFAULT_TASK_TYPE; 21431 if (taskCounts[taskType]) { 21432 taskCounts[taskType]--; 21433 taskCounts[ALL_TASKS_TYPE]--; 21434 } 21435 } 21436 21437 function getLastCallback() { 21438 var cbInfo = taskCallbacks.pop(); 21439 return cbInfo && cbInfo.cb; 21440 } 21441 21442 function getLastCallbackForType(taskType) { 21443 for (var i = taskCallbacks.length - 1; i >= 0; --i) { 21444 var cbInfo = taskCallbacks[i]; 21445 if (cbInfo.type === taskType) { 21446 taskCallbacks.splice(i, 1); 21447 return cbInfo.cb; 21448 } 21449 } 21450 } 21451 21452 function incTaskCount(taskType) { 21453 taskType = taskType || DEFAULT_TASK_TYPE; 21454 taskCounts[taskType] = (taskCounts[taskType] || 0) + 1; 21455 taskCounts[ALL_TASKS_TYPE] = (taskCounts[ALL_TASKS_TYPE] || 0) + 1; 21456 } 21457 21458 function notifyWhenNoPendingTasks(callback, taskType) { 21459 taskType = taskType || ALL_TASKS_TYPE; 21460 if (!taskCounts[taskType]) { 21461 callback(); 21462 } else { 21463 taskCallbacks.push({type: taskType, cb: callback}); 21464 } 21465 } 21466} 21467 21468var $templateRequestMinErr = minErr('$templateRequest'); 21469 21470/** 21471 * @ngdoc provider 21472 * @name $templateRequestProvider 21473 * @this 21474 * 21475 * @description 21476 * Used to configure the options passed to the {@link $http} service when making a template request. 21477 * 21478 * For example, it can be used for specifying the "Accept" header that is sent to the server, when 21479 * requesting a template. 21480 */ 21481function $TemplateRequestProvider() { 21482 21483 var httpOptions; 21484 21485 /** 21486 * @ngdoc method 21487 * @name $templateRequestProvider#httpOptions 21488 * @description 21489 * The options to be passed to the {@link $http} service when making the request. 21490 * You can use this to override options such as the "Accept" header for template requests. 21491 * 21492 * The {@link $templateRequest} will set the `cache` and the `transformResponse` properties of the 21493 * options if not overridden here. 21494 * 21495 * @param {string=} value new value for the {@link $http} options. 21496 * @returns {string|self} Returns the {@link $http} options when used as getter and self if used as setter. 21497 */ 21498 this.httpOptions = function(val) { 21499 if (val) { 21500 httpOptions = val; 21501 return this; 21502 } 21503 return httpOptions; 21504 }; 21505 21506 /** 21507 * @ngdoc service 21508 * @name $templateRequest 21509 * 21510 * @description 21511 * The `$templateRequest` service runs security checks then downloads the provided template using 21512 * `$http` and, upon success, stores the contents inside of `$templateCache`. If the HTTP request 21513 * fails or the response data of the HTTP request is empty, a `$compile` error will be thrown (the 21514 * exception can be thwarted by setting the 2nd parameter of the function to true). Note that the 21515 * contents of `$templateCache` are trusted, so the call to `$sce.getTrustedUrl(tpl)` is omitted 21516 * when `tpl` is of type string and `$templateCache` has the matching entry. 21517 * 21518 * If you want to pass custom options to the `$http` service, such as setting the Accept header you 21519 * can configure this via {@link $templateRequestProvider#httpOptions}. 21520 * 21521 * `$templateRequest` is used internally by {@link $compile}, {@link ngRoute.$route}, and directives such 21522 * as {@link ngInclude} to download and cache templates. 21523 * 21524 * 3rd party modules should use `$templateRequest` if their services or directives are loading 21525 * templates. 21526 * 21527 * @param {string|TrustedResourceUrl} tpl The HTTP request template URL 21528 * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty 21529 * 21530 * @return {Promise} a promise for the HTTP response data of the given URL. 21531 * 21532 * @property {number} totalPendingRequests total amount of pending template requests being downloaded. 21533 */ 21534 this.$get = ['$exceptionHandler', '$templateCache', '$http', '$q', '$sce', 21535 function($exceptionHandler, $templateCache, $http, $q, $sce) { 21536 21537 function handleRequestFn(tpl, ignoreRequestError) { 21538 handleRequestFn.totalPendingRequests++; 21539 21540 // We consider the template cache holds only trusted templates, so 21541 // there's no need to go through adding the template again to the trusted 21542 // resources for keys that already are included in there. This also makes 21543 // AngularJS accept any script directive, no matter its name. However, we 21544 // still need to unwrap trusted types. 21545 if (!isString(tpl) || isUndefined($templateCache.get(tpl))) { 21546 tpl = $sce.getTrustedResourceUrl(tpl); 21547 } 21548 21549 var transformResponse = $http.defaults && $http.defaults.transformResponse; 21550 21551 if (isArray(transformResponse)) { 21552 transformResponse = transformResponse.filter(function(transformer) { 21553 return transformer !== defaultHttpResponseTransform; 21554 }); 21555 } else if (transformResponse === defaultHttpResponseTransform) { 21556 transformResponse = null; 21557 } 21558 21559 return $http.get(tpl, extend({ 21560 cache: $templateCache, 21561 transformResponse: transformResponse 21562 }, httpOptions)) 21563 .finally(function() { 21564 handleRequestFn.totalPendingRequests--; 21565 }) 21566 .then(function(response) { 21567 return $templateCache.put(tpl, response.data); 21568 }, handleError); 21569 21570 function handleError(resp) { 21571 if (!ignoreRequestError) { 21572 resp = $templateRequestMinErr('tpload', 21573 'Failed to load template: {0} (HTTP status: {1} {2})', 21574 tpl, resp.status, resp.statusText); 21575 21576 $exceptionHandler(resp); 21577 } 21578 21579 return $q.reject(resp); 21580 } 21581 } 21582 21583 handleRequestFn.totalPendingRequests = 0; 21584 21585 return handleRequestFn; 21586 } 21587 ]; 21588} 21589 21590/** @this */ 21591function $$TestabilityProvider() { 21592 this.$get = ['$rootScope', '$browser', '$location', 21593 function($rootScope, $browser, $location) { 21594 21595 /** 21596 * @name $testability 21597 * 21598 * @description 21599 * The private $$testability service provides a collection of methods for use when debugging 21600 * or by automated test and debugging tools. 21601 */ 21602 var testability = {}; 21603 21604 /** 21605 * @name $$testability#findBindings 21606 * 21607 * @description 21608 * Returns an array of elements that are bound (via ng-bind or {{}}) 21609 * to expressions matching the input. 21610 * 21611 * @param {Element} element The element root to search from. 21612 * @param {string} expression The binding expression to match. 21613 * @param {boolean} opt_exactMatch If true, only returns exact matches 21614 * for the expression. Filters and whitespace are ignored. 21615 */ 21616 testability.findBindings = function(element, expression, opt_exactMatch) { 21617 var bindings = element.getElementsByClassName('ng-binding'); 21618 var matches = []; 21619 forEach(bindings, function(binding) { 21620 var dataBinding = angular.element(binding).data('$binding'); 21621 if (dataBinding) { 21622 forEach(dataBinding, function(bindingName) { 21623 if (opt_exactMatch) { 21624 var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)'); 21625 if (matcher.test(bindingName)) { 21626 matches.push(binding); 21627 } 21628 } else { 21629 if (bindingName.indexOf(expression) !== -1) { 21630 matches.push(binding); 21631 } 21632 } 21633 }); 21634 } 21635 }); 21636 return matches; 21637 }; 21638 21639 /** 21640 * @name $$testability#findModels 21641 * 21642 * @description 21643 * Returns an array of elements that are two-way found via ng-model to 21644 * expressions matching the input. 21645 * 21646 * @param {Element} element The element root to search from. 21647 * @param {string} expression The model expression to match. 21648 * @param {boolean} opt_exactMatch If true, only returns exact matches 21649 * for the expression. 21650 */ 21651 testability.findModels = function(element, expression, opt_exactMatch) { 21652 var prefixes = ['ng-', 'data-ng-', 'ng\\:']; 21653 for (var p = 0; p < prefixes.length; ++p) { 21654 var attributeEquals = opt_exactMatch ? '=' : '*='; 21655 var selector = '[' + prefixes[p] + 'model' + attributeEquals + '"' + expression + '"]'; 21656 var elements = element.querySelectorAll(selector); 21657 if (elements.length) { 21658 return elements; 21659 } 21660 } 21661 }; 21662 21663 /** 21664 * @name $$testability#getLocation 21665 * 21666 * @description 21667 * Shortcut for getting the location in a browser agnostic way. Returns 21668 * the path, search, and hash. (e.g. /path?a=b#hash) 21669 */ 21670 testability.getLocation = function() { 21671 return $location.url(); 21672 }; 21673 21674 /** 21675 * @name $$testability#setLocation 21676 * 21677 * @description 21678 * Shortcut for navigating to a location without doing a full page reload. 21679 * 21680 * @param {string} url The location url (path, search and hash, 21681 * e.g. /path?a=b#hash) to go to. 21682 */ 21683 testability.setLocation = function(url) { 21684 if (url !== $location.url()) { 21685 $location.url(url); 21686 $rootScope.$digest(); 21687 } 21688 }; 21689 21690 /** 21691 * @name $$testability#whenStable 21692 * 21693 * @description 21694 * Calls the callback when all pending tasks are completed. 21695 * 21696 * Types of tasks waited for include: 21697 * - Pending timeouts (via {@link $timeout}). 21698 * - Pending HTTP requests (via {@link $http}). 21699 * - In-progress route transitions (via {@link $route}). 21700 * - Pending tasks scheduled via {@link $rootScope#$applyAsync}. 21701 * - Pending tasks scheduled via {@link $rootScope#$evalAsync}. 21702 * These include tasks scheduled via `$evalAsync()` indirectly (such as {@link $q} promises). 21703 * 21704 * @param {function} callback 21705 */ 21706 testability.whenStable = function(callback) { 21707 $browser.notifyWhenNoOutstandingRequests(callback); 21708 }; 21709 21710 return testability; 21711 }]; 21712} 21713 21714var $timeoutMinErr = minErr('$timeout'); 21715 21716/** @this */ 21717function $TimeoutProvider() { 21718 this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler', 21719 function($rootScope, $browser, $q, $$q, $exceptionHandler) { 21720 21721 var deferreds = {}; 21722 21723 21724 /** 21725 * @ngdoc service 21726 * @name $timeout 21727 * 21728 * @description 21729 * AngularJS's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch 21730 * block and delegates any exceptions to 21731 * {@link ng.$exceptionHandler $exceptionHandler} service. 21732 * 21733 * The return value of calling `$timeout` is a promise, which will be resolved when 21734 * the delay has passed and the timeout function, if provided, is executed. 21735 * 21736 * To cancel a timeout request, call `$timeout.cancel(promise)`. 21737 * 21738 * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to 21739 * synchronously flush the queue of deferred functions. 21740 * 21741 * If you only want a promise that will be resolved after some specified delay 21742 * then you can call `$timeout` without the `fn` function. 21743 * 21744 * @param {function()=} fn A function, whose execution should be delayed. 21745 * @param {number=} [delay=0] Delay in milliseconds. 21746 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 21747 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 21748 * @param {...*=} Pass additional parameters to the executed function. 21749 * @returns {Promise} Promise that will be resolved when the timeout is reached. The promise 21750 * will be resolved with the return value of the `fn` function. 21751 * 21752 */ 21753 function timeout(fn, delay, invokeApply) { 21754 if (!isFunction(fn)) { 21755 invokeApply = delay; 21756 delay = fn; 21757 fn = noop; 21758 } 21759 21760 var args = sliceArgs(arguments, 3), 21761 skipApply = (isDefined(invokeApply) && !invokeApply), 21762 deferred = (skipApply ? $$q : $q).defer(), 21763 promise = deferred.promise, 21764 timeoutId; 21765 21766 timeoutId = $browser.defer(function() { 21767 try { 21768 deferred.resolve(fn.apply(null, args)); 21769 } catch (e) { 21770 deferred.reject(e); 21771 $exceptionHandler(e); 21772 } finally { 21773 delete deferreds[promise.$$timeoutId]; 21774 } 21775 21776 if (!skipApply) $rootScope.$apply(); 21777 }, delay, '$timeout'); 21778 21779 promise.$$timeoutId = timeoutId; 21780 deferreds[timeoutId] = deferred; 21781 21782 return promise; 21783 } 21784 21785 21786 /** 21787 * @ngdoc method 21788 * @name $timeout#cancel 21789 * 21790 * @description 21791 * Cancels a task associated with the `promise`. As a result of this, the promise will be 21792 * resolved with a rejection. 21793 * 21794 * @param {Promise=} promise Promise returned by the `$timeout` function. 21795 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 21796 * canceled. 21797 */ 21798 timeout.cancel = function(promise) { 21799 if (!promise) return false; 21800 21801 if (!promise.hasOwnProperty('$$timeoutId')) { 21802 throw $timeoutMinErr('badprom', 21803 '`$timeout.cancel()` called with a promise that was not generated by `$timeout()`.'); 21804 } 21805 21806 if (!deferreds.hasOwnProperty(promise.$$timeoutId)) return false; 21807 21808 var id = promise.$$timeoutId; 21809 var deferred = deferreds[id]; 21810 21811 // Timeout cancels should not report an unhandled promise. 21812 markQExceptionHandled(deferred.promise); 21813 deferred.reject('canceled'); 21814 delete deferreds[id]; 21815 21816 return $browser.defer.cancel(id); 21817 }; 21818 21819 return timeout; 21820 }]; 21821} 21822 21823// NOTE: The usage of window and document instead of $window and $document here is 21824// deliberate. This service depends on the specific behavior of anchor nodes created by the 21825// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and 21826// cause us to break tests. In addition, when the browser resolves a URL for XHR, it 21827// doesn't know about mocked locations and resolves URLs to the real document - which is 21828// exactly the behavior needed here. There is little value is mocking these out for this 21829// service. 21830var urlParsingNode = window.document.createElement('a'); 21831var originUrl = urlResolve(window.location.href); 21832var baseUrlParsingNode; 21833 21834urlParsingNode.href = 'http://[::1]'; 21835 21836// Support: IE 9-11 only, Edge 16-17 only (fixed in 18 Preview) 21837// IE/Edge don't wrap IPv6 addresses' hostnames in square brackets 21838// when parsed out of an anchor element. 21839var ipv6InBrackets = urlParsingNode.hostname === '[::1]'; 21840 21841/** 21842 * 21843 * Implementation Notes for non-IE browsers 21844 * ---------------------------------------- 21845 * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, 21846 * results both in the normalizing and parsing of the URL. Normalizing means that a relative 21847 * URL will be resolved into an absolute URL in the context of the application document. 21848 * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related 21849 * properties are all populated to reflect the normalized URL. This approach has wide 21850 * compatibility - Safari 1+, Mozilla 1+ etc. See 21851 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 21852 * 21853 * Implementation Notes for IE 21854 * --------------------------- 21855 * IE <= 10 normalizes the URL when assigned to the anchor node similar to the other 21856 * browsers. However, the parsed components will not be set if the URL assigned did not specify 21857 * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We 21858 * work around that by performing the parsing in a 2nd step by taking a previously normalized 21859 * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the 21860 * properties such as protocol, hostname, port, etc. 21861 * 21862 * References: 21863 * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement 21864 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 21865 * http://url.spec.whatwg.org/#urlutils 21866 * https://github.com/angular/angular.js/pull/2902 21867 * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ 21868 * 21869 * @kind function 21870 * @param {string|object} url The URL to be parsed. If `url` is not a string, it will be returned 21871 * unchanged. 21872 * @description Normalizes and parses a URL. 21873 * @returns {object} Returns the normalized URL as a dictionary. 21874 * 21875 * | member name | Description | 21876 * |---------------|------------------------------------------------------------------------| 21877 * | href | A normalized version of the provided URL if it was not an absolute URL | 21878 * | protocol | The protocol without the trailing colon | 21879 * | host | The host and port (if the port is non-default) of the normalizedUrl | 21880 * | search | The search params, minus the question mark | 21881 * | hash | The hash string, minus the hash symbol | 21882 * | hostname | The hostname | 21883 * | port | The port, without ":" | 21884 * | pathname | The pathname, beginning with "/" | 21885 * 21886 */ 21887function urlResolve(url) { 21888 if (!isString(url)) return url; 21889 21890 var href = url; 21891 21892 // Support: IE 9-11 only 21893 if (msie) { 21894 // Normalize before parse. Refer Implementation Notes on why this is 21895 // done in two steps on IE. 21896 urlParsingNode.setAttribute('href', href); 21897 href = urlParsingNode.href; 21898 } 21899 21900 urlParsingNode.setAttribute('href', href); 21901 21902 var hostname = urlParsingNode.hostname; 21903 21904 if (!ipv6InBrackets && hostname.indexOf(':') > -1) { 21905 hostname = '[' + hostname + ']'; 21906 } 21907 21908 return { 21909 href: urlParsingNode.href, 21910 protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', 21911 host: urlParsingNode.host, 21912 search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', 21913 hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', 21914 hostname: hostname, 21915 port: urlParsingNode.port, 21916 pathname: (urlParsingNode.pathname.charAt(0) === '/') 21917 ? urlParsingNode.pathname 21918 : '/' + urlParsingNode.pathname 21919 }; 21920} 21921 21922/** 21923 * Parse a request URL and determine whether this is a same-origin request as the application 21924 * document. 21925 * 21926 * @param {string|object} requestUrl The url of the request as a string that will be resolved 21927 * or a parsed URL object. 21928 * @returns {boolean} Whether the request is for the same origin as the application document. 21929 */ 21930function urlIsSameOrigin(requestUrl) { 21931 return urlsAreSameOrigin(requestUrl, originUrl); 21932} 21933 21934/** 21935 * Parse a request URL and determine whether it is same-origin as the current document base URL. 21936 * 21937 * Note: The base URL is usually the same as the document location (`location.href`) but can 21938 * be overriden by using the `<base>` tag. 21939 * 21940 * @param {string|object} requestUrl The url of the request as a string that will be resolved 21941 * or a parsed URL object. 21942 * @returns {boolean} Whether the URL is same-origin as the document base URL. 21943 */ 21944function urlIsSameOriginAsBaseUrl(requestUrl) { 21945 return urlsAreSameOrigin(requestUrl, getBaseUrl()); 21946} 21947 21948/** 21949 * Create a function that can check a URL's origin against a list of allowed/trusted origins. 21950 * The current location's origin is implicitly trusted. 21951 * 21952 * @param {string[]} trustedOriginUrls - A list of URLs (strings), whose origins are trusted. 21953 * 21954 * @returns {Function} - A function that receives a URL (string or parsed URL object) and returns 21955 * whether it is of an allowed origin. 21956 */ 21957function urlIsAllowedOriginFactory(trustedOriginUrls) { 21958 var parsedAllowedOriginUrls = [originUrl].concat(trustedOriginUrls.map(urlResolve)); 21959 21960 /** 21961 * Check whether the specified URL (string or parsed URL object) has an origin that is allowed 21962 * based on a list of trusted-origin URLs. The current location's origin is implicitly 21963 * trusted. 21964 * 21965 * @param {string|Object} requestUrl - The URL to be checked (provided as a string that will be 21966 * resolved or a parsed URL object). 21967 * 21968 * @returns {boolean} - Whether the specified URL is of an allowed origin. 21969 */ 21970 return function urlIsAllowedOrigin(requestUrl) { 21971 var parsedUrl = urlResolve(requestUrl); 21972 return parsedAllowedOriginUrls.some(urlsAreSameOrigin.bind(null, parsedUrl)); 21973 }; 21974} 21975 21976/** 21977 * Determine if two URLs share the same origin. 21978 * 21979 * @param {string|Object} url1 - First URL to compare as a string or a normalized URL in the form of 21980 * a dictionary object returned by `urlResolve()`. 21981 * @param {string|object} url2 - Second URL to compare as a string or a normalized URL in the form 21982 * of a dictionary object returned by `urlResolve()`. 21983 * 21984 * @returns {boolean} - True if both URLs have the same origin, and false otherwise. 21985 */ 21986function urlsAreSameOrigin(url1, url2) { 21987 url1 = urlResolve(url1); 21988 url2 = urlResolve(url2); 21989 21990 return (url1.protocol === url2.protocol && 21991 url1.host === url2.host); 21992} 21993 21994/** 21995 * Returns the current document base URL. 21996 * @returns {string} 21997 */ 21998function getBaseUrl() { 21999 if (window.document.baseURI) { 22000 return window.document.baseURI; 22001 } 22002 22003 // `document.baseURI` is available everywhere except IE 22004 if (!baseUrlParsingNode) { 22005 baseUrlParsingNode = window.document.createElement('a'); 22006 baseUrlParsingNode.href = '.'; 22007 22008 // Work-around for IE bug described in Implementation Notes. The fix in `urlResolve()` is not 22009 // suitable here because we need to track changes to the base URL. 22010 baseUrlParsingNode = baseUrlParsingNode.cloneNode(false); 22011 } 22012 return baseUrlParsingNode.href; 22013} 22014 22015/** 22016 * @ngdoc service 22017 * @name $window 22018 * @this 22019 * 22020 * @description 22021 * A reference to the browser's `window` object. While `window` 22022 * is globally available in JavaScript, it causes testability problems, because 22023 * it is a global variable. In AngularJS we always refer to it through the 22024 * `$window` service, so it may be overridden, removed or mocked for testing. 22025 * 22026 * Expressions, like the one defined for the `ngClick` directive in the example 22027 * below, are evaluated with respect to the current scope. Therefore, there is 22028 * no risk of inadvertently coding in a dependency on a global value in such an 22029 * expression. 22030 * 22031 * @example 22032 <example module="windowExample" name="window-service"> 22033 <file name="index.html"> 22034 <script> 22035 angular.module('windowExample', []) 22036 .controller('ExampleController', ['$scope', '$window', function($scope, $window) { 22037 $scope.greeting = 'Hello, World!'; 22038 $scope.doGreeting = function(greeting) { 22039 $window.alert(greeting); 22040 }; 22041 }]); 22042 </script> 22043 <div ng-controller="ExampleController"> 22044 <input type="text" ng-model="greeting" aria-label="greeting" /> 22045 <button ng-click="doGreeting(greeting)">ALERT</button> 22046 </div> 22047 </file> 22048 <file name="protractor.js" type="protractor"> 22049 it('should display the greeting in the input box', function() { 22050 element(by.model('greeting')).sendKeys('Hello, E2E Tests'); 22051 // If we click the button it will block the test runner 22052 // element(':button').click(); 22053 }); 22054 </file> 22055 </example> 22056 */ 22057function $WindowProvider() { 22058 this.$get = valueFn(window); 22059} 22060 22061/** 22062 * @name $$cookieReader 22063 * @requires $document 22064 * 22065 * @description 22066 * This is a private service for reading cookies used by $http and ngCookies 22067 * 22068 * @return {Object} a key/value map of the current cookies 22069 */ 22070function $$CookieReader($document) { 22071 var rawDocument = $document[0] || {}; 22072 var lastCookies = {}; 22073 var lastCookieString = ''; 22074 22075 function safeGetCookie(rawDocument) { 22076 try { 22077 return rawDocument.cookie || ''; 22078 } catch (e) { 22079 return ''; 22080 } 22081 } 22082 22083 function safeDecodeURIComponent(str) { 22084 try { 22085 return decodeURIComponent(str); 22086 } catch (e) { 22087 return str; 22088 } 22089 } 22090 22091 return function() { 22092 var cookieArray, cookie, i, index, name; 22093 var currentCookieString = safeGetCookie(rawDocument); 22094 22095 if (currentCookieString !== lastCookieString) { 22096 lastCookieString = currentCookieString; 22097 cookieArray = lastCookieString.split('; '); 22098 lastCookies = {}; 22099 22100 for (i = 0; i < cookieArray.length; i++) { 22101 cookie = cookieArray[i]; 22102 index = cookie.indexOf('='); 22103 if (index > 0) { //ignore nameless cookies 22104 name = safeDecodeURIComponent(cookie.substring(0, index)); 22105 // the first value that is seen for a cookie is the most 22106 // specific one. values for the same cookie name that 22107 // follow are for less specific paths. 22108 if (isUndefined(lastCookies[name])) { 22109 lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1)); 22110 } 22111 } 22112 } 22113 } 22114 return lastCookies; 22115 }; 22116} 22117 22118$$CookieReader.$inject = ['$document']; 22119 22120/** @this */ 22121function $$CookieReaderProvider() { 22122 this.$get = $$CookieReader; 22123} 22124 22125/* global currencyFilter: true, 22126 dateFilter: true, 22127 filterFilter: true, 22128 jsonFilter: true, 22129 limitToFilter: true, 22130 lowercaseFilter: true, 22131 numberFilter: true, 22132 orderByFilter: true, 22133 uppercaseFilter: true, 22134 */ 22135 22136/** 22137 * @ngdoc provider 22138 * @name $filterProvider 22139 * @description 22140 * 22141 * Filters are just functions which transform input to an output. However filters need to be 22142 * Dependency Injected. To achieve this a filter definition consists of a factory function which is 22143 * annotated with dependencies and is responsible for creating a filter function. 22144 * 22145 * <div class="alert alert-warning"> 22146 * **Note:** Filter names must be valid AngularJS {@link expression} identifiers, such as `uppercase` or `orderBy`. 22147 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 22148 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 22149 * (`myapp_subsection_filterx`). 22150 * </div> 22151 * 22152 * ```js 22153 * // Filter registration 22154 * function MyModule($provide, $filterProvider) { 22155 * // create a service to demonstrate injection (not always needed) 22156 * $provide.value('greet', function(name){ 22157 * return 'Hello ' + name + '!'; 22158 * }); 22159 * 22160 * // register a filter factory which uses the 22161 * // greet service to demonstrate DI. 22162 * $filterProvider.register('greet', function(greet){ 22163 * // return the filter function which uses the greet service 22164 * // to generate salutation 22165 * return function(text) { 22166 * // filters need to be forgiving so check input validity 22167 * return text && greet(text) || text; 22168 * }; 22169 * }); 22170 * } 22171 * ``` 22172 * 22173 * The filter function is registered with the `$injector` under the filter name suffix with 22174 * `Filter`. 22175 * 22176 * ```js 22177 * it('should be the same instance', inject( 22178 * function($filterProvider) { 22179 * $filterProvider.register('reverse', function(){ 22180 * return ...; 22181 * }); 22182 * }, 22183 * function($filter, reverseFilter) { 22184 * expect($filter('reverse')).toBe(reverseFilter); 22185 * }); 22186 * ``` 22187 * 22188 * 22189 * For more information about how AngularJS filters work, and how to create your own filters, see 22190 * {@link guide/filter Filters} in the AngularJS Developer Guide. 22191 */ 22192 22193/** 22194 * @ngdoc service 22195 * @name $filter 22196 * @kind function 22197 * @description 22198 * Filters are used for formatting data displayed to the user. 22199 * 22200 * They can be used in view templates, controllers or services. AngularJS comes 22201 * with a collection of [built-in filters](api/ng/filter), but it is easy to 22202 * define your own as well. 22203 * 22204 * The general syntax in templates is as follows: 22205 * 22206 * ```html 22207 * {{ expression [| filter_name[:parameter_value] ... ] }} 22208 * ``` 22209 * 22210 * @param {String} name Name of the filter function to retrieve 22211 * @return {Function} the filter function 22212 * @example 22213 <example name="$filter" module="filterExample"> 22214 <file name="index.html"> 22215 <div ng-controller="MainCtrl"> 22216 <h3>{{ originalText }}</h3> 22217 <h3>{{ filteredText }}</h3> 22218 </div> 22219 </file> 22220 22221 <file name="script.js"> 22222 angular.module('filterExample', []) 22223 .controller('MainCtrl', function($scope, $filter) { 22224 $scope.originalText = 'hello'; 22225 $scope.filteredText = $filter('uppercase')($scope.originalText); 22226 }); 22227 </file> 22228 </example> 22229 */ 22230$FilterProvider.$inject = ['$provide']; 22231/** @this */ 22232function $FilterProvider($provide) { 22233 var suffix = 'Filter'; 22234 22235 /** 22236 * @ngdoc method 22237 * @name $filterProvider#register 22238 * @param {string|Object} name Name of the filter function, or an object map of filters where 22239 * the keys are the filter names and the values are the filter factories. 22240 * 22241 * <div class="alert alert-warning"> 22242 * **Note:** Filter names must be valid AngularJS {@link expression} identifiers, such as `uppercase` or `orderBy`. 22243 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 22244 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 22245 * (`myapp_subsection_filterx`). 22246 * </div> 22247 * @param {Function} factory If the first argument was a string, a factory function for the filter to be registered. 22248 * @returns {Object} Registered filter instance, or if a map of filters was provided then a map 22249 * of the registered filter instances. 22250 */ 22251 function register(name, factory) { 22252 if (isObject(name)) { 22253 var filters = {}; 22254 forEach(name, function(filter, key) { 22255 filters[key] = register(key, filter); 22256 }); 22257 return filters; 22258 } else { 22259 return $provide.factory(name + suffix, factory); 22260 } 22261 } 22262 this.register = register; 22263 22264 this.$get = ['$injector', function($injector) { 22265 return function(name) { 22266 return $injector.get(name + suffix); 22267 }; 22268 }]; 22269 22270 //////////////////////////////////////// 22271 22272 /* global 22273 currencyFilter: false, 22274 dateFilter: false, 22275 filterFilter: false, 22276 jsonFilter: false, 22277 limitToFilter: false, 22278 lowercaseFilter: false, 22279 numberFilter: false, 22280 orderByFilter: false, 22281 uppercaseFilter: false 22282 */ 22283 22284 register('currency', currencyFilter); 22285 register('date', dateFilter); 22286 register('filter', filterFilter); 22287 register('json', jsonFilter); 22288 register('limitTo', limitToFilter); 22289 register('lowercase', lowercaseFilter); 22290 register('number', numberFilter); 22291 register('orderBy', orderByFilter); 22292 register('uppercase', uppercaseFilter); 22293} 22294 22295/** 22296 * @ngdoc filter 22297 * @name filter 22298 * @kind function 22299 * 22300 * @description 22301 * Selects a subset of items from `array` and returns it as a new array. 22302 * 22303 * @param {Array} array The source array. 22304 * <div class="alert alert-info"> 22305 * **Note**: If the array contains objects that reference themselves, filtering is not possible. 22306 * </div> 22307 * @param {string|Object|function()} expression The predicate to be used for selecting items from 22308 * `array`. 22309 * 22310 * Can be one of: 22311 * 22312 * - `string`: The string is used for matching against the contents of the `array`. All strings or 22313 * objects with string properties in `array` that match this string will be returned. This also 22314 * applies to nested object properties. 22315 * The predicate can be negated by prefixing the string with `!`. 22316 * 22317 * - `Object`: A pattern object can be used to filter specific properties on objects contained 22318 * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items 22319 * which have property `name` containing "M" and property `phone` containing "1". A special 22320 * property name (`$` by default) can be used (e.g. as in `{$: "text"}`) to accept a match 22321 * against any property of the object or its nested object properties. That's equivalent to the 22322 * simple substring match with a `string` as described above. The special property name can be 22323 * overwritten, using the `anyPropertyKey` parameter. 22324 * The predicate can be negated by prefixing the string with `!`. 22325 * For example `{name: "!M"}` predicate will return an array of items which have property `name` 22326 * not containing "M". 22327 * 22328 * Note that a named property will match properties on the same level only, while the special 22329 * `$` property will match properties on the same level or deeper. E.g. an array item like 22330 * `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but 22331 * **will** be matched by `{$: 'John'}`. 22332 * 22333 * - `function(value, index, array)`: A predicate function can be used to write arbitrary filters. 22334 * The function is called for each element of the array, with the element, its index, and 22335 * the entire array itself as arguments. 22336 * 22337 * The final result is an array of those elements that the predicate returned true for. 22338 * 22339 * @param {function(actual, expected)|true|false} [comparator] Comparator which is used in 22340 * determining if values retrieved using `expression` (when it is not a function) should be 22341 * considered a match based on the expected value (from the filter expression) and actual 22342 * value (from the object in the array). 22343 * 22344 * Can be one of: 22345 * 22346 * - `function(actual, expected)`: 22347 * The function will be given the object value and the predicate value to compare and 22348 * should return true if both values should be considered equal. 22349 * 22350 * - `true`: A shorthand for `function(actual, expected) { return angular.equals(actual, expected)}`. 22351 * This is essentially strict comparison of expected and actual. 22352 * 22353 * - `false`: A short hand for a function which will look for a substring match in a case 22354 * insensitive way. Primitive values are converted to strings. Objects are not compared against 22355 * primitives, unless they have a custom `toString` method (e.g. `Date` objects). 22356 * 22357 * 22358 * Defaults to `false`. 22359 * 22360 * @param {string} [anyPropertyKey] The special property name that matches against any property. 22361 * By default `$`. 22362 * 22363 * @example 22364 <example name="filter-filter"> 22365 <file name="index.html"> 22366 <div ng-init="friends = [{name:'John', phone:'555-1276'}, 22367 {name:'Mary', phone:'800-BIG-MARY'}, 22368 {name:'Mike', phone:'555-4321'}, 22369 {name:'Adam', phone:'555-5678'}, 22370 {name:'Julie', phone:'555-8765'}, 22371 {name:'Juliette', phone:'555-5678'}]"></div> 22372 22373 <label>Search: <input ng-model="searchText"></label> 22374 <table id="searchTextResults"> 22375 <tr><th>Name</th><th>Phone</th></tr> 22376 <tr ng-repeat="friend in friends | filter:searchText"> 22377 <td>{{friend.name}}</td> 22378 <td>{{friend.phone}}</td> 22379 </tr> 22380 </table> 22381 <hr> 22382 <label>Any: <input ng-model="search.$"></label> <br> 22383 <label>Name only <input ng-model="search.name"></label><br> 22384 <label>Phone only <input ng-model="search.phone"></label><br> 22385 <label>Equality <input type="checkbox" ng-model="strict"></label><br> 22386 <table id="searchObjResults"> 22387 <tr><th>Name</th><th>Phone</th></tr> 22388 <tr ng-repeat="friendObj in friends | filter:search:strict"> 22389 <td>{{friendObj.name}}</td> 22390 <td>{{friendObj.phone}}</td> 22391 </tr> 22392 </table> 22393 </file> 22394 <file name="protractor.js" type="protractor"> 22395 var expectFriendNames = function(expectedNames, key) { 22396 element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) { 22397 arr.forEach(function(wd, i) { 22398 expect(wd.getText()).toMatch(expectedNames[i]); 22399 }); 22400 }); 22401 }; 22402 22403 it('should search across all fields when filtering with a string', function() { 22404 var searchText = element(by.model('searchText')); 22405 searchText.clear(); 22406 searchText.sendKeys('m'); 22407 expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend'); 22408 22409 searchText.clear(); 22410 searchText.sendKeys('76'); 22411 expectFriendNames(['John', 'Julie'], 'friend'); 22412 }); 22413 22414 it('should search in specific fields when filtering with a predicate object', function() { 22415 var searchAny = element(by.model('search.$')); 22416 searchAny.clear(); 22417 searchAny.sendKeys('i'); 22418 expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj'); 22419 }); 22420 it('should use a equal comparison when comparator is true', function() { 22421 var searchName = element(by.model('search.name')); 22422 var strict = element(by.model('strict')); 22423 searchName.clear(); 22424 searchName.sendKeys('Julie'); 22425 strict.click(); 22426 expectFriendNames(['Julie'], 'friendObj'); 22427 }); 22428 </file> 22429 </example> 22430 */ 22431 22432function filterFilter() { 22433 return function(array, expression, comparator, anyPropertyKey) { 22434 if (!isArrayLike(array)) { 22435 if (array == null) { 22436 return array; 22437 } else { 22438 throw minErr('filter')('notarray', 'Expected array but received: {0}', array); 22439 } 22440 } 22441 22442 anyPropertyKey = anyPropertyKey || '$'; 22443 var expressionType = getTypeForFilter(expression); 22444 var predicateFn; 22445 var matchAgainstAnyProp; 22446 22447 switch (expressionType) { 22448 case 'function': 22449 predicateFn = expression; 22450 break; 22451 case 'boolean': 22452 case 'null': 22453 case 'number': 22454 case 'string': 22455 matchAgainstAnyProp = true; 22456 // falls through 22457 case 'object': 22458 predicateFn = createPredicateFn(expression, comparator, anyPropertyKey, matchAgainstAnyProp); 22459 break; 22460 default: 22461 return array; 22462 } 22463 22464 return Array.prototype.filter.call(array, predicateFn); 22465 }; 22466} 22467 22468// Helper functions for `filterFilter` 22469function createPredicateFn(expression, comparator, anyPropertyKey, matchAgainstAnyProp) { 22470 var shouldMatchPrimitives = isObject(expression) && (anyPropertyKey in expression); 22471 var predicateFn; 22472 22473 if (comparator === true) { 22474 comparator = equals; 22475 } else if (!isFunction(comparator)) { 22476 comparator = function(actual, expected) { 22477 if (isUndefined(actual)) { 22478 // No substring matching against `undefined` 22479 return false; 22480 } 22481 if ((actual === null) || (expected === null)) { 22482 // No substring matching against `null`; only match against `null` 22483 return actual === expected; 22484 } 22485 if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual))) { 22486 // Should not compare primitives against objects, unless they have custom `toString` method 22487 return false; 22488 } 22489 22490 actual = lowercase('' + actual); 22491 expected = lowercase('' + expected); 22492 return actual.indexOf(expected) !== -1; 22493 }; 22494 } 22495 22496 predicateFn = function(item) { 22497 if (shouldMatchPrimitives && !isObject(item)) { 22498 return deepCompare(item, expression[anyPropertyKey], comparator, anyPropertyKey, false); 22499 } 22500 return deepCompare(item, expression, comparator, anyPropertyKey, matchAgainstAnyProp); 22501 }; 22502 22503 return predicateFn; 22504} 22505 22506function deepCompare(actual, expected, comparator, anyPropertyKey, matchAgainstAnyProp, dontMatchWholeObject) { 22507 var actualType = getTypeForFilter(actual); 22508 var expectedType = getTypeForFilter(expected); 22509 22510 if ((expectedType === 'string') && (expected.charAt(0) === '!')) { 22511 return !deepCompare(actual, expected.substring(1), comparator, anyPropertyKey, matchAgainstAnyProp); 22512 } else if (isArray(actual)) { 22513 // In case `actual` is an array, consider it a match 22514 // if ANY of it's items matches `expected` 22515 return actual.some(function(item) { 22516 return deepCompare(item, expected, comparator, anyPropertyKey, matchAgainstAnyProp); 22517 }); 22518 } 22519 22520 switch (actualType) { 22521 case 'object': 22522 var key; 22523 if (matchAgainstAnyProp) { 22524 for (key in actual) { 22525 // Under certain, rare, circumstances, key may not be a string and `charAt` will be undefined 22526 // See: https://github.com/angular/angular.js/issues/15644 22527 if (key.charAt && (key.charAt(0) !== '$') && 22528 deepCompare(actual[key], expected, comparator, anyPropertyKey, true)) { 22529 return true; 22530 } 22531 } 22532 return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, anyPropertyKey, false); 22533 } else if (expectedType === 'object') { 22534 for (key in expected) { 22535 var expectedVal = expected[key]; 22536 if (isFunction(expectedVal) || isUndefined(expectedVal)) { 22537 continue; 22538 } 22539 22540 var matchAnyProperty = key === anyPropertyKey; 22541 var actualVal = matchAnyProperty ? actual : actual[key]; 22542 if (!deepCompare(actualVal, expectedVal, comparator, anyPropertyKey, matchAnyProperty, matchAnyProperty)) { 22543 return false; 22544 } 22545 } 22546 return true; 22547 } else { 22548 return comparator(actual, expected); 22549 } 22550 case 'function': 22551 return false; 22552 default: 22553 return comparator(actual, expected); 22554 } 22555} 22556 22557// Used for easily differentiating between `null` and actual `object` 22558function getTypeForFilter(val) { 22559 return (val === null) ? 'null' : typeof val; 22560} 22561 22562var MAX_DIGITS = 22; 22563var DECIMAL_SEP = '.'; 22564var ZERO_CHAR = '0'; 22565 22566/** 22567 * @ngdoc filter 22568 * @name currency 22569 * @kind function 22570 * 22571 * @description 22572 * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default 22573 * symbol for current locale is used. 22574 * 22575 * @param {number} amount Input to filter. 22576 * @param {string=} symbol Currency symbol or identifier to be displayed. 22577 * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale 22578 * @returns {string} Formatted number. 22579 * 22580 * 22581 * @example 22582 <example module="currencyExample" name="currency-filter"> 22583 <file name="index.html"> 22584 <script> 22585 angular.module('currencyExample', []) 22586 .controller('ExampleController', ['$scope', function($scope) { 22587 $scope.amount = 1234.56; 22588 }]); 22589 </script> 22590 <div ng-controller="ExampleController"> 22591 <input type="number" ng-model="amount" aria-label="amount"> <br> 22592 default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br> 22593 custom currency identifier (USD$): <span id="currency-custom">{{amount | currency:"USD$"}}</span><br> 22594 no fractions (0): <span id="currency-no-fractions">{{amount | currency:"USD$":0}}</span> 22595 </div> 22596 </file> 22597 <file name="protractor.js" type="protractor"> 22598 it('should init with 1234.56', function() { 22599 expect(element(by.id('currency-default')).getText()).toBe('$1,234.56'); 22600 expect(element(by.id('currency-custom')).getText()).toBe('USD$1,234.56'); 22601 expect(element(by.id('currency-no-fractions')).getText()).toBe('USD$1,235'); 22602 }); 22603 it('should update', function() { 22604 if (browser.params.browser === 'safari') { 22605 // Safari does not understand the minus key. See 22606 // https://github.com/angular/protractor/issues/481 22607 return; 22608 } 22609 element(by.model('amount')).clear(); 22610 element(by.model('amount')).sendKeys('-1234'); 22611 expect(element(by.id('currency-default')).getText()).toBe('-$1,234.00'); 22612 expect(element(by.id('currency-custom')).getText()).toBe('-USD$1,234.00'); 22613 expect(element(by.id('currency-no-fractions')).getText()).toBe('-USD$1,234'); 22614 }); 22615 </file> 22616 </example> 22617 */ 22618currencyFilter.$inject = ['$locale']; 22619function currencyFilter($locale) { 22620 var formats = $locale.NUMBER_FORMATS; 22621 return function(amount, currencySymbol, fractionSize) { 22622 if (isUndefined(currencySymbol)) { 22623 currencySymbol = formats.CURRENCY_SYM; 22624 } 22625 22626 if (isUndefined(fractionSize)) { 22627 fractionSize = formats.PATTERNS[1].maxFrac; 22628 } 22629 22630 // If the currency symbol is empty, trim whitespace around the symbol 22631 var currencySymbolRe = !currencySymbol ? /\s*\u00A4\s*/g : /\u00A4/g; 22632 22633 // if null or undefined pass it through 22634 return (amount == null) 22635 ? amount 22636 : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize). 22637 replace(currencySymbolRe, currencySymbol); 22638 }; 22639} 22640 22641/** 22642 * @ngdoc filter 22643 * @name number 22644 * @kind function 22645 * 22646 * @description 22647 * Formats a number as text. 22648 * 22649 * If the input is null or undefined, it will just be returned. 22650 * If the input is infinite (Infinity or -Infinity), the Infinity symbol 'â' or '-â' is returned, respectively. 22651 * If the input is not a number an empty string is returned. 22652 * 22653 * 22654 * @param {number|string} number Number to format. 22655 * @param {(number|string)=} fractionSize Number of decimal places to round the number to. 22656 * If this is not provided then the fraction size is computed from the current locale's number 22657 * formatting pattern. In the case of the default locale, it will be 3. 22658 * @returns {string} Number rounded to `fractionSize` appropriately formatted based on the current 22659 * locale (e.g., in the en_US locale it will have "." as the decimal separator and 22660 * include "," group separators after each third digit). 22661 * 22662 * @example 22663 <example module="numberFilterExample" name="number-filter"> 22664 <file name="index.html"> 22665 <script> 22666 angular.module('numberFilterExample', []) 22667 .controller('ExampleController', ['$scope', function($scope) { 22668 $scope.val = 1234.56789; 22669 }]); 22670 </script> 22671 <div ng-controller="ExampleController"> 22672 <label>Enter number: <input ng-model='val'></label><br> 22673 Default formatting: <span id='number-default'>{{val | number}}</span><br> 22674 No fractions: <span>{{val | number:0}}</span><br> 22675 Negative number: <span>{{-val | number:4}}</span> 22676 </div> 22677 </file> 22678 <file name="protractor.js" type="protractor"> 22679 it('should format numbers', function() { 22680 expect(element(by.id('number-default')).getText()).toBe('1,234.568'); 22681 expect(element(by.binding('val | number:0')).getText()).toBe('1,235'); 22682 expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679'); 22683 }); 22684 22685 it('should update', function() { 22686 element(by.model('val')).clear(); 22687 element(by.model('val')).sendKeys('3374.333'); 22688 expect(element(by.id('number-default')).getText()).toBe('3,374.333'); 22689 expect(element(by.binding('val | number:0')).getText()).toBe('3,374'); 22690 expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330'); 22691 }); 22692 </file> 22693 </example> 22694 */ 22695numberFilter.$inject = ['$locale']; 22696function numberFilter($locale) { 22697 var formats = $locale.NUMBER_FORMATS; 22698 return function(number, fractionSize) { 22699 22700 // if null or undefined pass it through 22701 return (number == null) 22702 ? number 22703 : formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP, 22704 fractionSize); 22705 }; 22706} 22707 22708/** 22709 * Parse a number (as a string) into three components that can be used 22710 * for formatting the number. 22711 * 22712 * (Significant bits of this parse algorithm came from https://github.com/MikeMcl/big.js/) 22713 * 22714 * @param {string} numStr The number to parse 22715 * @return {object} An object describing this number, containing the following keys: 22716 * - d : an array of digits containing leading zeros as necessary 22717 * - i : the number of the digits in `d` that are to the left of the decimal point 22718 * - e : the exponent for numbers that would need more than `MAX_DIGITS` digits in `d` 22719 * 22720 */ 22721function parse(numStr) { 22722 var exponent = 0, digits, numberOfIntegerDigits; 22723 var i, j, zeros; 22724 22725 // Decimal point? 22726 if ((numberOfIntegerDigits = numStr.indexOf(DECIMAL_SEP)) > -1) { 22727 numStr = numStr.replace(DECIMAL_SEP, ''); 22728 } 22729 22730 // Exponential form? 22731 if ((i = numStr.search(/e/i)) > 0) { 22732 // Work out the exponent. 22733 if (numberOfIntegerDigits < 0) numberOfIntegerDigits = i; 22734 numberOfIntegerDigits += +numStr.slice(i + 1); 22735 numStr = numStr.substring(0, i); 22736 } else if (numberOfIntegerDigits < 0) { 22737 // There was no decimal point or exponent so it is an integer. 22738 numberOfIntegerDigits = numStr.length; 22739 } 22740 22741 // Count the number of leading zeros. 22742 for (i = 0; numStr.charAt(i) === ZERO_CHAR; i++) { /* empty */ } 22743 22744 if (i === (zeros = numStr.length)) { 22745 // The digits are all zero. 22746 digits = [0]; 22747 numberOfIntegerDigits = 1; 22748 } else { 22749 // Count the number of trailing zeros 22750 zeros--; 22751 while (numStr.charAt(zeros) === ZERO_CHAR) zeros--; 22752 22753 // Trailing zeros are insignificant so ignore them 22754 numberOfIntegerDigits -= i; 22755 digits = []; 22756 // Convert string to array of digits without leading/trailing zeros. 22757 for (j = 0; i <= zeros; i++, j++) { 22758 digits[j] = +numStr.charAt(i); 22759 } 22760 } 22761 22762 // If the number overflows the maximum allowed digits then use an exponent. 22763 if (numberOfIntegerDigits > MAX_DIGITS) { 22764 digits = digits.splice(0, MAX_DIGITS - 1); 22765 exponent = numberOfIntegerDigits - 1; 22766 numberOfIntegerDigits = 1; 22767 } 22768 22769 return { d: digits, e: exponent, i: numberOfIntegerDigits }; 22770} 22771 22772/** 22773 * Round the parsed number to the specified number of decimal places 22774 * This function changed the parsedNumber in-place 22775 */ 22776function roundNumber(parsedNumber, fractionSize, minFrac, maxFrac) { 22777 var digits = parsedNumber.d; 22778 var fractionLen = digits.length - parsedNumber.i; 22779 22780 // determine fractionSize if it is not specified; `+fractionSize` converts it to a number 22781 fractionSize = (isUndefined(fractionSize)) ? Math.min(Math.max(minFrac, fractionLen), maxFrac) : +fractionSize; 22782 22783 // The index of the digit to where rounding is to occur 22784 var roundAt = fractionSize + parsedNumber.i; 22785 var digit = digits[roundAt]; 22786 22787 if (roundAt > 0) { 22788 // Drop fractional digits beyond `roundAt` 22789 digits.splice(Math.max(parsedNumber.i, roundAt)); 22790 22791 // Set non-fractional digits beyond `roundAt` to 0 22792 for (var j = roundAt; j < digits.length; j++) { 22793 digits[j] = 0; 22794 } 22795 } else { 22796 // We rounded to zero so reset the parsedNumber 22797 fractionLen = Math.max(0, fractionLen); 22798 parsedNumber.i = 1; 22799 digits.length = Math.max(1, roundAt = fractionSize + 1); 22800 digits[0] = 0; 22801 for (var i = 1; i < roundAt; i++) digits[i] = 0; 22802 } 22803 22804 if (digit >= 5) { 22805 if (roundAt - 1 < 0) { 22806 for (var k = 0; k > roundAt; k--) { 22807 digits.unshift(0); 22808 parsedNumber.i++; 22809 } 22810 digits.unshift(1); 22811 parsedNumber.i++; 22812 } else { 22813 digits[roundAt - 1]++; 22814 } 22815 } 22816 22817 // Pad out with zeros to get the required fraction length 22818 for (; fractionLen < Math.max(0, fractionSize); fractionLen++) digits.push(0); 22819 22820 22821 // Do any carrying, e.g. a digit was rounded up to 10 22822 var carry = digits.reduceRight(function(carry, d, i, digits) { 22823 d = d + carry; 22824 digits[i] = d % 10; 22825 return Math.floor(d / 10); 22826 }, 0); 22827 if (carry) { 22828 digits.unshift(carry); 22829 parsedNumber.i++; 22830 } 22831} 22832 22833/** 22834 * Format a number into a string 22835 * @param {number} number The number to format 22836 * @param {{ 22837 * minFrac, // the minimum number of digits required in the fraction part of the number 22838 * maxFrac, // the maximum number of digits required in the fraction part of the number 22839 * gSize, // number of digits in each group of separated digits 22840 * lgSize, // number of digits in the last group of digits before the decimal separator 22841 * negPre, // the string to go in front of a negative number (e.g. `-` or `(`)) 22842 * posPre, // the string to go in front of a positive number 22843 * negSuf, // the string to go after a negative number (e.g. `)`) 22844 * posSuf // the string to go after a positive number 22845 * }} pattern 22846 * @param {string} groupSep The string to separate groups of number (e.g. `,`) 22847 * @param {string} decimalSep The string to act as the decimal separator (e.g. `.`) 22848 * @param {[type]} fractionSize The size of the fractional part of the number 22849 * @return {string} The number formatted as a string 22850 */ 22851function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { 22852 22853 if (!(isString(number) || isNumber(number)) || isNaN(number)) return ''; 22854 22855 var isInfinity = !isFinite(number); 22856 var isZero = false; 22857 var numStr = Math.abs(number) + '', 22858 formattedText = '', 22859 parsedNumber; 22860 22861 if (isInfinity) { 22862 formattedText = '\u221e'; 22863 } else { 22864 parsedNumber = parse(numStr); 22865 22866 roundNumber(parsedNumber, fractionSize, pattern.minFrac, pattern.maxFrac); 22867 22868 var digits = parsedNumber.d; 22869 var integerLen = parsedNumber.i; 22870 var exponent = parsedNumber.e; 22871 var decimals = []; 22872 isZero = digits.reduce(function(isZero, d) { return isZero && !d; }, true); 22873 22874 // pad zeros for small numbers 22875 while (integerLen < 0) { 22876 digits.unshift(0); 22877 integerLen++; 22878 } 22879 22880 // extract decimals digits 22881 if (integerLen > 0) { 22882 decimals = digits.splice(integerLen, digits.length); 22883 } else { 22884 decimals = digits; 22885 digits = [0]; 22886 } 22887 22888 // format the integer digits with grouping separators 22889 var groups = []; 22890 if (digits.length >= pattern.lgSize) { 22891 groups.unshift(digits.splice(-pattern.lgSize, digits.length).join('')); 22892 } 22893 while (digits.length > pattern.gSize) { 22894 groups.unshift(digits.splice(-pattern.gSize, digits.length).join('')); 22895 } 22896 if (digits.length) { 22897 groups.unshift(digits.join('')); 22898 } 22899 formattedText = groups.join(groupSep); 22900 22901 // append the decimal digits 22902 if (decimals.length) { 22903 formattedText += decimalSep + decimals.join(''); 22904 } 22905 22906 if (exponent) { 22907 formattedText += 'e+' + exponent; 22908 } 22909 } 22910 if (number < 0 && !isZero) { 22911 return pattern.negPre + formattedText + pattern.negSuf; 22912 } else { 22913 return pattern.posPre + formattedText + pattern.posSuf; 22914 } 22915} 22916 22917function padNumber(num, digits, trim, negWrap) { 22918 var neg = ''; 22919 if (num < 0 || (negWrap && num <= 0)) { 22920 if (negWrap) { 22921 num = -num + 1; 22922 } else { 22923 num = -num; 22924 neg = '-'; 22925 } 22926 } 22927 num = '' + num; 22928 while (num.length < digits) num = ZERO_CHAR + num; 22929 if (trim) { 22930 num = num.substr(num.length - digits); 22931 } 22932 return neg + num; 22933} 22934 22935 22936function dateGetter(name, size, offset, trim, negWrap) { 22937 offset = offset || 0; 22938 return function(date) { 22939 var value = date['get' + name](); 22940 if (offset > 0 || value > -offset) { 22941 value += offset; 22942 } 22943 if (value === 0 && offset === -12) value = 12; 22944 return padNumber(value, size, trim, negWrap); 22945 }; 22946} 22947 22948function dateStrGetter(name, shortForm, standAlone) { 22949 return function(date, formats) { 22950 var value = date['get' + name](); 22951 var propPrefix = (standAlone ? 'STANDALONE' : '') + (shortForm ? 'SHORT' : ''); 22952 var get = uppercase(propPrefix + name); 22953 22954 return formats[get][value]; 22955 }; 22956} 22957 22958function timeZoneGetter(date, formats, offset) { 22959 var zone = -1 * offset; 22960 var paddedZone = (zone >= 0) ? '+' : ''; 22961 22962 paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) + 22963 padNumber(Math.abs(zone % 60), 2); 22964 22965 return paddedZone; 22966} 22967 22968function getFirstThursdayOfYear(year) { 22969 // 0 = index of January 22970 var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay(); 22971 // 4 = index of Thursday (+1 to account for 1st = 5) 22972 // 11 = index of *next* Thursday (+1 account for 1st = 12) 22973 return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst); 22974} 22975 22976function getThursdayThisWeek(datetime) { 22977 return new Date(datetime.getFullYear(), datetime.getMonth(), 22978 // 4 = index of Thursday 22979 datetime.getDate() + (4 - datetime.getDay())); 22980} 22981 22982function weekGetter(size) { 22983 return function(date) { 22984 var firstThurs = getFirstThursdayOfYear(date.getFullYear()), 22985 thisThurs = getThursdayThisWeek(date); 22986 22987 var diff = +thisThurs - +firstThurs, 22988 result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week 22989 22990 return padNumber(result, size); 22991 }; 22992} 22993 22994function ampmGetter(date, formats) { 22995 return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; 22996} 22997 22998function eraGetter(date, formats) { 22999 return date.getFullYear() <= 0 ? formats.ERAS[0] : formats.ERAS[1]; 23000} 23001 23002function longEraGetter(date, formats) { 23003 return date.getFullYear() <= 0 ? formats.ERANAMES[0] : formats.ERANAMES[1]; 23004} 23005 23006var DATE_FORMATS = { 23007 yyyy: dateGetter('FullYear', 4, 0, false, true), 23008 yy: dateGetter('FullYear', 2, 0, true, true), 23009 y: dateGetter('FullYear', 1, 0, false, true), 23010 MMMM: dateStrGetter('Month'), 23011 MMM: dateStrGetter('Month', true), 23012 MM: dateGetter('Month', 2, 1), 23013 M: dateGetter('Month', 1, 1), 23014 LLLL: dateStrGetter('Month', false, true), 23015 dd: dateGetter('Date', 2), 23016 d: dateGetter('Date', 1), 23017 HH: dateGetter('Hours', 2), 23018 H: dateGetter('Hours', 1), 23019 hh: dateGetter('Hours', 2, -12), 23020 h: dateGetter('Hours', 1, -12), 23021 mm: dateGetter('Minutes', 2), 23022 m: dateGetter('Minutes', 1), 23023 ss: dateGetter('Seconds', 2), 23024 s: dateGetter('Seconds', 1), 23025 // while ISO 8601 requires fractions to be prefixed with `.` or `,` 23026 // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions 23027 sss: dateGetter('Milliseconds', 3), 23028 EEEE: dateStrGetter('Day'), 23029 EEE: dateStrGetter('Day', true), 23030 a: ampmGetter, 23031 Z: timeZoneGetter, 23032 ww: weekGetter(2), 23033 w: weekGetter(1), 23034 G: eraGetter, 23035 GG: eraGetter, 23036 GGG: eraGetter, 23037 GGGG: longEraGetter 23038}; 23039 23040var DATE_FORMATS_SPLIT = /((?:[^yMLdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|m+|s+|a|Z|G+|w+))([\s\S]*)/, 23041 NUMBER_STRING = /^-?\d+$/; 23042 23043/** 23044 * @ngdoc filter 23045 * @name date 23046 * @kind function 23047 * 23048 * @description 23049 * Formats `date` to a string based on the requested `format`. 23050 * 23051 * `format` string can be composed of the following elements: 23052 * 23053 * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) 23054 * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) 23055 * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) 23056 * * `'MMMM'`: Month in year (January-December) 23057 * * `'MMM'`: Month in year (Jan-Dec) 23058 * * `'MM'`: Month in year, padded (01-12) 23059 * * `'M'`: Month in year (1-12) 23060 * * `'LLLL'`: Stand-alone month in year (January-December) 23061 * * `'dd'`: Day in month, padded (01-31) 23062 * * `'d'`: Day in month (1-31) 23063 * * `'EEEE'`: Day in Week,(Sunday-Saturday) 23064 * * `'EEE'`: Day in Week, (Sun-Sat) 23065 * * `'HH'`: Hour in day, padded (00-23) 23066 * * `'H'`: Hour in day (0-23) 23067 * * `'hh'`: Hour in AM/PM, padded (01-12) 23068 * * `'h'`: Hour in AM/PM, (1-12) 23069 * * `'mm'`: Minute in hour, padded (00-59) 23070 * * `'m'`: Minute in hour (0-59) 23071 * * `'ss'`: Second in minute, padded (00-59) 23072 * * `'s'`: Second in minute (0-59) 23073 * * `'sss'`: Millisecond in second, padded (000-999) 23074 * * `'a'`: AM/PM marker 23075 * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) 23076 * * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year 23077 * * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year 23078 * * `'G'`, `'GG'`, `'GGG'`: The abbreviated form of the era string (e.g. 'AD') 23079 * * `'GGGG'`: The long form of the era string (e.g. 'Anno Domini') 23080 * 23081 * `format` string can also be one of the following predefined 23082 * {@link guide/i18n localizable formats}: 23083 * 23084 * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale 23085 * (e.g. Sep 3, 2010 12:05:08 PM) 23086 * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 PM) 23087 * * `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` for en_US locale 23088 * (e.g. Friday, September 3, 2010) 23089 * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010) 23090 * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010) 23091 * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10) 23092 * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 PM) 23093 * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 PM) 23094 * 23095 * `format` string can contain literal values. These need to be escaped by surrounding with single quotes (e.g. 23096 * `"h 'in the morning'"`). In order to output a single quote, escape it - i.e., two single quotes in a sequence 23097 * (e.g. `"h 'o''clock'"`). 23098 * 23099 * Any other characters in the `format` string will be output as-is. 23100 * 23101 * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or 23102 * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its 23103 * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is 23104 * specified in the string input, the time is considered to be in the local timezone. 23105 * @param {string=} format Formatting rules (see Description). If not specified, 23106 * `mediumDate` is used. 23107 * @param {string=} timezone Timezone to be used for formatting. It understands UTC/GMT and the 23108 * continental US time zone abbreviations, but for general use, use a time zone offset, for 23109 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian) 23110 * If not specified, the timezone of the browser will be used. 23111 * @returns {string} Formatted string or the input if input is not recognized as date/millis. 23112 * 23113 * @example 23114 <example name="filter-date"> 23115 <file name="index.html"> 23116 <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>: 23117 <span>{{1288323623006 | date:'medium'}}</span><br> 23118 <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>: 23119 <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br> 23120 <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>: 23121 <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br> 23122 <span ng-non-bindable>{{1288323623006 | date:"MM/dd/yyyy 'at' h:mma"}}</span>: 23123 <span>{{'1288323623006' | date:"MM/dd/yyyy 'at' h:mma"}}</span><br> 23124 </file> 23125 <file name="protractor.js" type="protractor"> 23126 it('should format date', function() { 23127 expect(element(by.binding("1288323623006 | date:'medium'")).getText()). 23128 toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/); 23129 expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()). 23130 toMatch(/2010-10-2\d \d{2}:\d{2}:\d{2} (-|\+)?\d{4}/); 23131 expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()). 23132 toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/); 23133 expect(element(by.binding("'1288323623006' | date:\"MM/dd/yyyy 'at' h:mma\"")).getText()). 23134 toMatch(/10\/2\d\/2010 at \d{1,2}:\d{2}(AM|PM)/); 23135 }); 23136 </file> 23137 </example> 23138 */ 23139dateFilter.$inject = ['$locale']; 23140function dateFilter($locale) { 23141 23142 23143 var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; 23144 // 1 2 3 4 5 6 7 8 9 10 11 23145 function jsonStringToDate(string) { 23146 var match; 23147 if ((match = string.match(R_ISO8601_STR))) { 23148 var date = new Date(0), 23149 tzHour = 0, 23150 tzMin = 0, 23151 dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear, 23152 timeSetter = match[8] ? date.setUTCHours : date.setHours; 23153 23154 if (match[9]) { 23155 tzHour = toInt(match[9] + match[10]); 23156 tzMin = toInt(match[9] + match[11]); 23157 } 23158 dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3])); 23159 var h = toInt(match[4] || 0) - tzHour; 23160 var m = toInt(match[5] || 0) - tzMin; 23161 var s = toInt(match[6] || 0); 23162 var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000); 23163 timeSetter.call(date, h, m, s, ms); 23164 return date; 23165 } 23166 return string; 23167 } 23168 23169 23170 return function(date, format, timezone) { 23171 var text = '', 23172 parts = [], 23173 fn, match; 23174 23175 format = format || 'mediumDate'; 23176 format = $locale.DATETIME_FORMATS[format] || format; 23177 if (isString(date)) { 23178 date = NUMBER_STRING.test(date) ? toInt(date) : jsonStringToDate(date); 23179 } 23180 23181 if (isNumber(date)) { 23182 date = new Date(date); 23183 } 23184 23185 if (!isDate(date) || !isFinite(date.getTime())) { 23186 return date; 23187 } 23188 23189 while (format) { 23190 match = DATE_FORMATS_SPLIT.exec(format); 23191 if (match) { 23192 parts = concat(parts, match, 1); 23193 format = parts.pop(); 23194 } else { 23195 parts.push(format); 23196 format = null; 23197 } 23198 } 23199 23200 var dateTimezoneOffset = date.getTimezoneOffset(); 23201 if (timezone) { 23202 dateTimezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset); 23203 date = convertTimezoneToLocal(date, timezone, true); 23204 } 23205 forEach(parts, function(value) { 23206 fn = DATE_FORMATS[value]; 23207 text += fn ? fn(date, $locale.DATETIME_FORMATS, dateTimezoneOffset) 23208 : value === '\'\'' ? '\'' : value.replace(/(^'|'$)/g, '').replace(/''/g, '\''); 23209 }); 23210 23211 return text; 23212 }; 23213} 23214 23215 23216/** 23217 * @ngdoc filter 23218 * @name json 23219 * @kind function 23220 * 23221 * @description 23222 * Allows you to convert a JavaScript object into JSON string. 23223 * 23224 * This filter is mostly useful for debugging. When using the double curly {{value}} notation 23225 * the binding is automatically converted to JSON. 23226 * 23227 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. 23228 * @param {number=} spacing The number of spaces to use per indentation, defaults to 2. 23229 * @returns {string} JSON string. 23230 * 23231 * 23232 * @example 23233 <example name="filter-json"> 23234 <file name="index.html"> 23235 <pre id="default-spacing">{{ {'name':'value'} | json }}</pre> 23236 <pre id="custom-spacing">{{ {'name':'value'} | json:4 }}</pre> 23237 </file> 23238 <file name="protractor.js" type="protractor"> 23239 it('should jsonify filtered objects', function() { 23240 expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n {2}"name": ?"value"\n}/); 23241 expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n {4}"name": ?"value"\n}/); 23242 }); 23243 </file> 23244 </example> 23245 * 23246 */ 23247function jsonFilter() { 23248 return function(object, spacing) { 23249 if (isUndefined(spacing)) { 23250 spacing = 2; 23251 } 23252 return toJson(object, spacing); 23253 }; 23254} 23255 23256 23257/** 23258 * @ngdoc filter 23259 * @name lowercase 23260 * @kind function 23261 * @description 23262 * Converts string to lowercase. 23263 * 23264 * See the {@link ng.uppercase uppercase filter documentation} for a functionally identical example. 23265 * 23266 * @see angular.lowercase 23267 */ 23268var lowercaseFilter = valueFn(lowercase); 23269 23270 23271/** 23272 * @ngdoc filter 23273 * @name uppercase 23274 * @kind function 23275 * @description 23276 * Converts string to uppercase. 23277 * @example 23278 <example module="uppercaseFilterExample" name="filter-uppercase"> 23279 <file name="index.html"> 23280 <script> 23281 angular.module('uppercaseFilterExample', []) 23282 .controller('ExampleController', ['$scope', function($scope) { 23283 $scope.title = 'This is a title'; 23284 }]); 23285 </script> 23286 <div ng-controller="ExampleController"> 23287 <!-- This title should be formatted normally --> 23288 <h1>{{title}}</h1> 23289 <!-- This title should be capitalized --> 23290 <h1>{{title | uppercase}}</h1> 23291 </div> 23292 </file> 23293 </example> 23294 */ 23295var uppercaseFilter = valueFn(uppercase); 23296 23297/** 23298 * @ngdoc filter 23299 * @name limitTo 23300 * @kind function 23301 * 23302 * @description 23303 * Creates a new array or string containing only a specified number of elements. The elements are 23304 * taken from either the beginning or the end of the source array, string or number, as specified by 23305 * the value and sign (positive or negative) of `limit`. Other array-like objects are also supported 23306 * (e.g. array subclasses, NodeLists, jqLite/jQuery collections etc). If a number is used as input, 23307 * it is converted to a string. 23308 * 23309 * @param {Array|ArrayLike|string|number} input - Array/array-like, string or number to be limited. 23310 * @param {string|number} limit - The length of the returned array or string. If the `limit` number 23311 * is positive, `limit` number of items from the beginning of the source array/string are copied. 23312 * If the number is negative, `limit` number of items from the end of the source array/string 23313 * are copied. The `limit` will be trimmed if it exceeds `array.length`. If `limit` is undefined, 23314 * the input will be returned unchanged. 23315 * @param {(string|number)=} begin - Index at which to begin limitation. As a negative index, 23316 * `begin` indicates an offset from the end of `input`. Defaults to `0`. 23317 * @returns {Array|string} A new sub-array or substring of length `limit` or less if the input had 23318 * less than `limit` elements. 23319 * 23320 * @example 23321 <example module="limitToExample" name="limit-to-filter"> 23322 <file name="index.html"> 23323 <script> 23324 angular.module('limitToExample', []) 23325 .controller('ExampleController', ['$scope', function($scope) { 23326 $scope.numbers = [1,2,3,4,5,6,7,8,9]; 23327 $scope.letters = "abcdefghi"; 23328 $scope.longNumber = 2345432342; 23329 $scope.numLimit = 3; 23330 $scope.letterLimit = 3; 23331 $scope.longNumberLimit = 3; 23332 }]); 23333 </script> 23334 <div ng-controller="ExampleController"> 23335 <label> 23336 Limit {{numbers}} to: 23337 <input type="number" step="1" ng-model="numLimit"> 23338 </label> 23339 <p>Output numbers: {{ numbers | limitTo:numLimit }}</p> 23340 <label> 23341 Limit {{letters}} to: 23342 <input type="number" step="1" ng-model="letterLimit"> 23343 </label> 23344 <p>Output letters: {{ letters | limitTo:letterLimit }}</p> 23345 <label> 23346 Limit {{longNumber}} to: 23347 <input type="number" step="1" ng-model="longNumberLimit"> 23348 </label> 23349 <p>Output long number: {{ longNumber | limitTo:longNumberLimit }}</p> 23350 </div> 23351 </file> 23352 <file name="protractor.js" type="protractor"> 23353 var numLimitInput = element(by.model('numLimit')); 23354 var letterLimitInput = element(by.model('letterLimit')); 23355 var longNumberLimitInput = element(by.model('longNumberLimit')); 23356 var limitedNumbers = element(by.binding('numbers | limitTo:numLimit')); 23357 var limitedLetters = element(by.binding('letters | limitTo:letterLimit')); 23358 var limitedLongNumber = element(by.binding('longNumber | limitTo:longNumberLimit')); 23359 23360 it('should limit the number array to first three items', function() { 23361 expect(numLimitInput.getAttribute('value')).toBe('3'); 23362 expect(letterLimitInput.getAttribute('value')).toBe('3'); 23363 expect(longNumberLimitInput.getAttribute('value')).toBe('3'); 23364 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]'); 23365 expect(limitedLetters.getText()).toEqual('Output letters: abc'); 23366 expect(limitedLongNumber.getText()).toEqual('Output long number: 234'); 23367 }); 23368 23369 // There is a bug in safari and protractor that doesn't like the minus key 23370 // it('should update the output when -3 is entered', function() { 23371 // numLimitInput.clear(); 23372 // numLimitInput.sendKeys('-3'); 23373 // letterLimitInput.clear(); 23374 // letterLimitInput.sendKeys('-3'); 23375 // longNumberLimitInput.clear(); 23376 // longNumberLimitInput.sendKeys('-3'); 23377 // expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]'); 23378 // expect(limitedLetters.getText()).toEqual('Output letters: ghi'); 23379 // expect(limitedLongNumber.getText()).toEqual('Output long number: 342'); 23380 // }); 23381 23382 it('should not exceed the maximum size of input array', function() { 23383 numLimitInput.clear(); 23384 numLimitInput.sendKeys('100'); 23385 letterLimitInput.clear(); 23386 letterLimitInput.sendKeys('100'); 23387 longNumberLimitInput.clear(); 23388 longNumberLimitInput.sendKeys('100'); 23389 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]'); 23390 expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi'); 23391 expect(limitedLongNumber.getText()).toEqual('Output long number: 2345432342'); 23392 }); 23393 </file> 23394 </example> 23395*/ 23396function limitToFilter() { 23397 return function(input, limit, begin) { 23398 if (Math.abs(Number(limit)) === Infinity) { 23399 limit = Number(limit); 23400 } else { 23401 limit = toInt(limit); 23402 } 23403 if (isNumberNaN(limit)) return input; 23404 23405 if (isNumber(input)) input = input.toString(); 23406 if (!isArrayLike(input)) return input; 23407 23408 begin = (!begin || isNaN(begin)) ? 0 : toInt(begin); 23409 begin = (begin < 0) ? Math.max(0, input.length + begin) : begin; 23410 23411 if (limit >= 0) { 23412 return sliceFn(input, begin, begin + limit); 23413 } else { 23414 if (begin === 0) { 23415 return sliceFn(input, limit, input.length); 23416 } else { 23417 return sliceFn(input, Math.max(0, begin + limit), begin); 23418 } 23419 } 23420 }; 23421} 23422 23423function sliceFn(input, begin, end) { 23424 if (isString(input)) return input.slice(begin, end); 23425 23426 return slice.call(input, begin, end); 23427} 23428 23429/** 23430 * @ngdoc filter 23431 * @name orderBy 23432 * @kind function 23433 * 23434 * @description 23435 * Returns an array containing the items from the specified `collection`, ordered by a `comparator` 23436 * function based on the values computed using the `expression` predicate. 23437 * 23438 * For example, `[{id: 'foo'}, {id: 'bar'}] | orderBy:'id'` would result in 23439 * `[{id: 'bar'}, {id: 'foo'}]`. 23440 * 23441 * The `collection` can be an Array or array-like object (e.g. NodeList, jQuery object, TypedArray, 23442 * String, etc). 23443 * 23444 * The `expression` can be a single predicate, or a list of predicates each serving as a tie-breaker 23445 * for the preceding one. The `expression` is evaluated against each item and the output is used 23446 * for comparing with other items. 23447 * 23448 * You can change the sorting order by setting `reverse` to `true`. By default, items are sorted in 23449 * ascending order. 23450 * 23451 * The comparison is done using the `comparator` function. If none is specified, a default, built-in 23452 * comparator is used (see below for details - in a nutshell, it compares numbers numerically and 23453 * strings alphabetically). 23454 * 23455 * ### Under the hood 23456 * 23457 * Ordering the specified `collection` happens in two phases: 23458 * 23459 * 1. All items are passed through the predicate (or predicates), and the returned values are saved 23460 * along with their type (`string`, `number` etc). For example, an item `{label: 'foo'}`, passed 23461 * through a predicate that extracts the value of the `label` property, would be transformed to: 23462 * ``` 23463 * { 23464 * value: 'foo', 23465 * type: 'string', 23466 * index: ... 23467 * } 23468 * ``` 23469 * **Note:** `null` values use `'null'` as their type. 23470 * 2. The comparator function is used to sort the items, based on the derived values, types and 23471 * indices. 23472 * 23473 * If you use a custom comparator, it will be called with pairs of objects of the form 23474 * `{value: ..., type: '...', index: ...}` and is expected to return `0` if the objects are equal 23475 * (as far as the comparator is concerned), `-1` if the 1st one should be ranked higher than the 23476 * second, or `1` otherwise. 23477 * 23478 * In order to ensure that the sorting will be deterministic across platforms, if none of the 23479 * specified predicates can distinguish between two items, `orderBy` will automatically introduce a 23480 * dummy predicate that returns the item's index as `value`. 23481 * (If you are using a custom comparator, make sure it can handle this predicate as well.) 23482 * 23483 * If a custom comparator still can't distinguish between two items, then they will be sorted based 23484 * on their index using the built-in comparator. 23485 * 23486 * Finally, in an attempt to simplify things, if a predicate returns an object as the extracted 23487 * value for an item, `orderBy` will try to convert that object to a primitive value, before passing 23488 * it to the comparator. The following rules govern the conversion: 23489 * 23490 * 1. If the object has a `valueOf()` method that returns a primitive, its return value will be 23491 * used instead.<br /> 23492 * (If the object has a `valueOf()` method that returns another object, then the returned object 23493 * will be used in subsequent steps.) 23494 * 2. If the object has a custom `toString()` method (i.e. not the one inherited from `Object`) that 23495 * returns a primitive, its return value will be used instead.<br /> 23496 * (If the object has a `toString()` method that returns another object, then the returned object 23497 * will be used in subsequent steps.) 23498 * 3. No conversion; the object itself is used. 23499 * 23500 * ### The default comparator 23501 * 23502 * The default, built-in comparator should be sufficient for most usecases. In short, it compares 23503 * numbers numerically, strings alphabetically (and case-insensitively), for objects falls back to 23504 * using their index in the original collection, sorts values of different types by type and puts 23505 * `undefined` and `null` values at the end of the sorted list. 23506 * 23507 * More specifically, it follows these steps to determine the relative order of items: 23508 * 23509 * 1. If the compared values are of different types: 23510 * - If one of the values is undefined, consider it "greater than" the other. 23511 * - Else if one of the values is null, consider it "greater than" the other. 23512 * - Else compare the types themselves alphabetically. 23513 * 2. If both values are of type `string`, compare them alphabetically in a case- and 23514 * locale-insensitive way. 23515 * 3. If both values are objects, compare their indices instead. 23516 * 4. Otherwise, return: 23517 * - `0`, if the values are equal (by strict equality comparison, i.e. using `===`). 23518 * - `-1`, if the 1st value is "less than" the 2nd value (compared using the `<` operator). 23519 * - `1`, otherwise. 23520 * 23521 * **Note:** If you notice numbers not being sorted as expected, make sure they are actually being 23522 * saved as numbers and not strings. 23523 * **Note:** For the purpose of sorting, `null` and `undefined` are considered "greater than" 23524 * any other value (with undefined "greater than" null). This effectively means that `null` 23525 * and `undefined` values end up at the end of a list sorted in ascending order. 23526 * **Note:** `null` values use `'null'` as their type to be able to distinguish them from objects. 23527 * 23528 * @param {Array|ArrayLike} collection - The collection (array or array-like object) to sort. 23529 * @param {(Function|string|Array.<Function|string>)=} expression - A predicate (or list of 23530 * predicates) to be used by the comparator to determine the order of elements. 23531 * 23532 * Can be one of: 23533 * 23534 * - `Function`: A getter function. This function will be called with each item as argument and 23535 * the return value will be used for sorting. 23536 * - `string`: An AngularJS expression. This expression will be evaluated against each item and the 23537 * result will be used for sorting. For example, use `'label'` to sort by a property called 23538 * `label` or `'label.substring(0, 3)'` to sort by the first 3 characters of the `label` 23539 * property.<br /> 23540 * (The result of a constant expression is interpreted as a property name to be used for 23541 * comparison. For example, use `'"special name"'` (note the extra pair of quotes) to sort by a 23542 * property called `special name`.)<br /> 23543 * An expression can be optionally prefixed with `+` or `-` to control the sorting direction, 23544 * ascending or descending. For example, `'+label'` or `'-label'`. If no property is provided, 23545 * (e.g. `'+'` or `'-'`), the collection element itself is used in comparisons. 23546 * - `Array`: An array of function and/or string predicates. If a predicate cannot determine the 23547 * relative order of two items, the next predicate is used as a tie-breaker. 23548 * 23549 * **Note:** If the predicate is missing or empty then it defaults to `'+'`. 23550 * 23551 * @param {boolean=} reverse - If `true`, reverse the sorting order. 23552 * @param {(Function)=} comparator - The comparator function used to determine the relative order of 23553 * value pairs. If omitted, the built-in comparator will be used. 23554 * 23555 * @returns {Array} - The sorted array. 23556 * 23557 * 23558 * @example 23559 * ### Ordering a table with `ngRepeat` 23560 * 23561 * The example below demonstrates a simple {@link ngRepeat ngRepeat}, where the data is sorted by 23562 * age in descending order (expression is set to `'-age'`). The `comparator` is not set, which means 23563 * it defaults to the built-in comparator. 23564 * 23565 <example name="orderBy-static" module="orderByExample1"> 23566 <file name="index.html"> 23567 <div ng-controller="ExampleController"> 23568 <table class="friends"> 23569 <tr> 23570 <th>Name</th> 23571 <th>Phone Number</th> 23572 <th>Age</th> 23573 </tr> 23574 <tr ng-repeat="friend in friends | orderBy:'-age'"> 23575 <td>{{friend.name}}</td> 23576 <td>{{friend.phone}}</td> 23577 <td>{{friend.age}}</td> 23578 </tr> 23579 </table> 23580 </div> 23581 </file> 23582 <file name="script.js"> 23583 angular.module('orderByExample1', []) 23584 .controller('ExampleController', ['$scope', function($scope) { 23585 $scope.friends = [ 23586 {name: 'John', phone: '555-1212', age: 10}, 23587 {name: 'Mary', phone: '555-9876', age: 19}, 23588 {name: 'Mike', phone: '555-4321', age: 21}, 23589 {name: 'Adam', phone: '555-5678', age: 35}, 23590 {name: 'Julie', phone: '555-8765', age: 29} 23591 ]; 23592 }]); 23593 </file> 23594 <file name="style.css"> 23595 .friends { 23596 border-collapse: collapse; 23597 } 23598 23599 .friends th { 23600 border-bottom: 1px solid; 23601 } 23602 .friends td, .friends th { 23603 border-left: 1px solid; 23604 padding: 5px 10px; 23605 } 23606 .friends td:first-child, .friends th:first-child { 23607 border-left: none; 23608 } 23609 </file> 23610 <file name="protractor.js" type="protractor"> 23611 // Element locators 23612 var names = element.all(by.repeater('friends').column('friend.name')); 23613 23614 it('should sort friends by age in reverse order', function() { 23615 expect(names.get(0).getText()).toBe('Adam'); 23616 expect(names.get(1).getText()).toBe('Julie'); 23617 expect(names.get(2).getText()).toBe('Mike'); 23618 expect(names.get(3).getText()).toBe('Mary'); 23619 expect(names.get(4).getText()).toBe('John'); 23620 }); 23621 </file> 23622 </example> 23623 * <hr /> 23624 * 23625 * @example 23626 * ### Changing parameters dynamically 23627 * 23628 * All parameters can be changed dynamically. The next example shows how you can make the columns of 23629 * a table sortable, by binding the `expression` and `reverse` parameters to scope properties. 23630 * 23631 <example name="orderBy-dynamic" module="orderByExample2"> 23632 <file name="index.html"> 23633 <div ng-controller="ExampleController"> 23634 <pre>Sort by = {{propertyName}}; reverse = {{reverse}}</pre> 23635 <hr/> 23636 <button ng-click="propertyName = null; reverse = false">Set to unsorted</button> 23637 <hr/> 23638 <table class="friends"> 23639 <tr> 23640 <th> 23641 <button ng-click="sortBy('name')">Name</button> 23642 <span class="sortorder" ng-show="propertyName === 'name'" ng-class="{reverse: reverse}"></span> 23643 </th> 23644 <th> 23645 <button ng-click="sortBy('phone')">Phone Number</button> 23646 <span class="sortorder" ng-show="propertyName === 'phone'" ng-class="{reverse: reverse}"></span> 23647 </th> 23648 <th> 23649 <button ng-click="sortBy('age')">Age</button> 23650 <span class="sortorder" ng-show="propertyName === 'age'" ng-class="{reverse: reverse}"></span> 23651 </th> 23652 </tr> 23653 <tr ng-repeat="friend in friends | orderBy:propertyName:reverse"> 23654 <td>{{friend.name}}</td> 23655 <td>{{friend.phone}}</td> 23656 <td>{{friend.age}}</td> 23657 </tr> 23658 </table> 23659 </div> 23660 </file> 23661 <file name="script.js"> 23662 angular.module('orderByExample2', []) 23663 .controller('ExampleController', ['$scope', function($scope) { 23664 var friends = [ 23665 {name: 'John', phone: '555-1212', age: 10}, 23666 {name: 'Mary', phone: '555-9876', age: 19}, 23667 {name: 'Mike', phone: '555-4321', age: 21}, 23668 {name: 'Adam', phone: '555-5678', age: 35}, 23669 {name: 'Julie', phone: '555-8765', age: 29} 23670 ]; 23671 23672 $scope.propertyName = 'age'; 23673 $scope.reverse = true; 23674 $scope.friends = friends; 23675 23676 $scope.sortBy = function(propertyName) { 23677 $scope.reverse = ($scope.propertyName === propertyName) ? !$scope.reverse : false; 23678 $scope.propertyName = propertyName; 23679 }; 23680 }]); 23681 </file> 23682 <file name="style.css"> 23683 .friends { 23684 border-collapse: collapse; 23685 } 23686 23687 .friends th { 23688 border-bottom: 1px solid; 23689 } 23690 .friends td, .friends th { 23691 border-left: 1px solid; 23692 padding: 5px 10px; 23693 } 23694 .friends td:first-child, .friends th:first-child { 23695 border-left: none; 23696 } 23697 23698 .sortorder:after { 23699 content: '\25b2'; // BLACK UP-POINTING TRIANGLE 23700 } 23701 .sortorder.reverse:after { 23702 content: '\25bc'; // BLACK DOWN-POINTING TRIANGLE 23703 } 23704 </file> 23705 <file name="protractor.js" type="protractor"> 23706 // Element locators 23707 var unsortButton = element(by.partialButtonText('unsorted')); 23708 var nameHeader = element(by.partialButtonText('Name')); 23709 var phoneHeader = element(by.partialButtonText('Phone')); 23710 var ageHeader = element(by.partialButtonText('Age')); 23711 var firstName = element(by.repeater('friends').column('friend.name').row(0)); 23712 var lastName = element(by.repeater('friends').column('friend.name').row(4)); 23713 23714 it('should sort friends by some property, when clicking on the column header', function() { 23715 expect(firstName.getText()).toBe('Adam'); 23716 expect(lastName.getText()).toBe('John'); 23717 23718 phoneHeader.click(); 23719 expect(firstName.getText()).toBe('John'); 23720 expect(lastName.getText()).toBe('Mary'); 23721 23722 nameHeader.click(); 23723 expect(firstName.getText()).toBe('Adam'); 23724 expect(lastName.getText()).toBe('Mike'); 23725 23726 ageHeader.click(); 23727 expect(firstName.getText()).toBe('John'); 23728 expect(lastName.getText()).toBe('Adam'); 23729 }); 23730 23731 it('should sort friends in reverse order, when clicking on the same column', function() { 23732 expect(firstName.getText()).toBe('Adam'); 23733 expect(lastName.getText()).toBe('John'); 23734 23735 ageHeader.click(); 23736 expect(firstName.getText()).toBe('John'); 23737 expect(lastName.getText()).toBe('Adam'); 23738 23739 ageHeader.click(); 23740 expect(firstName.getText()).toBe('Adam'); 23741 expect(lastName.getText()).toBe('John'); 23742 }); 23743 23744 it('should restore the original order, when clicking "Set to unsorted"', function() { 23745 expect(firstName.getText()).toBe('Adam'); 23746 expect(lastName.getText()).toBe('John'); 23747 23748 unsortButton.click(); 23749 expect(firstName.getText()).toBe('John'); 23750 expect(lastName.getText()).toBe('Julie'); 23751 }); 23752 </file> 23753 </example> 23754 * <hr /> 23755 * 23756 * @example 23757 * ### Using `orderBy` inside a controller 23758 * 23759 * It is also possible to call the `orderBy` filter manually, by injecting `orderByFilter`, and 23760 * calling it with the desired parameters. (Alternatively, you could inject the `$filter` factory 23761 * and retrieve the `orderBy` filter with `$filter('orderBy')`.) 23762 * 23763 <example name="orderBy-call-manually" module="orderByExample3"> 23764 <file name="index.html"> 23765 <div ng-controller="ExampleController"> 23766 <pre>Sort by = {{propertyName}}; reverse = {{reverse}}</pre> 23767 <hr/> 23768 <button ng-click="sortBy(null)">Set to unsorted</button> 23769 <hr/> 23770 <table class="friends"> 23771 <tr> 23772 <th> 23773 <button ng-click="sortBy('name')">Name</button> 23774 <span class="sortorder" ng-show="propertyName === 'name'" ng-class="{reverse: reverse}"></span> 23775 </th> 23776 <th> 23777 <button ng-click="sortBy('phone')">Phone Number</button> 23778 <span class="sortorder" ng-show="propertyName === 'phone'" ng-class="{reverse: reverse}"></span> 23779 </th> 23780 <th> 23781 <button ng-click="sortBy('age')">Age</button> 23782 <span class="sortorder" ng-show="propertyName === 'age'" ng-class="{reverse: reverse}"></span> 23783 </th> 23784 </tr> 23785 <tr ng-repeat="friend in friends"> 23786 <td>{{friend.name}}</td> 23787 <td>{{friend.phone}}</td> 23788 <td>{{friend.age}}</td> 23789 </tr> 23790 </table> 23791 </div> 23792 </file> 23793 <file name="script.js"> 23794 angular.module('orderByExample3', []) 23795 .controller('ExampleController', ['$scope', 'orderByFilter', function($scope, orderBy) { 23796 var friends = [ 23797 {name: 'John', phone: '555-1212', age: 10}, 23798 {name: 'Mary', phone: '555-9876', age: 19}, 23799 {name: 'Mike', phone: '555-4321', age: 21}, 23800 {name: 'Adam', phone: '555-5678', age: 35}, 23801 {name: 'Julie', phone: '555-8765', age: 29} 23802 ]; 23803 23804 $scope.propertyName = 'age'; 23805 $scope.reverse = true; 23806 $scope.friends = orderBy(friends, $scope.propertyName, $scope.reverse); 23807 23808 $scope.sortBy = function(propertyName) { 23809 $scope.reverse = (propertyName !== null && $scope.propertyName === propertyName) 23810 ? !$scope.reverse : false; 23811 $scope.propertyName = propertyName; 23812 $scope.friends = orderBy(friends, $scope.propertyName, $scope.reverse); 23813 }; 23814 }]); 23815 </file> 23816 <file name="style.css"> 23817 .friends { 23818 border-collapse: collapse; 23819 } 23820 23821 .friends th { 23822 border-bottom: 1px solid; 23823 } 23824 .friends td, .friends th { 23825 border-left: 1px solid; 23826 padding: 5px 10px; 23827 } 23828 .friends td:first-child, .friends th:first-child { 23829 border-left: none; 23830 } 23831 23832 .sortorder:after { 23833 content: '\25b2'; // BLACK UP-POINTING TRIANGLE 23834 } 23835 .sortorder.reverse:after { 23836 content: '\25bc'; // BLACK DOWN-POINTING TRIANGLE 23837 } 23838 </file> 23839 <file name="protractor.js" type="protractor"> 23840 // Element locators 23841 var unsortButton = element(by.partialButtonText('unsorted')); 23842 var nameHeader = element(by.partialButtonText('Name')); 23843 var phoneHeader = element(by.partialButtonText('Phone')); 23844 var ageHeader = element(by.partialButtonText('Age')); 23845 var firstName = element(by.repeater('friends').column('friend.name').row(0)); 23846 var lastName = element(by.repeater('friends').column('friend.name').row(4)); 23847 23848 it('should sort friends by some property, when clicking on the column header', function() { 23849 expect(firstName.getText()).toBe('Adam'); 23850 expect(lastName.getText()).toBe('John'); 23851 23852 phoneHeader.click(); 23853 expect(firstName.getText()).toBe('John'); 23854 expect(lastName.getText()).toBe('Mary'); 23855 23856 nameHeader.click(); 23857 expect(firstName.getText()).toBe('Adam'); 23858 expect(lastName.getText()).toBe('Mike'); 23859 23860 ageHeader.click(); 23861 expect(firstName.getText()).toBe('John'); 23862 expect(lastName.getText()).toBe('Adam'); 23863 }); 23864 23865 it('should sort friends in reverse order, when clicking on the same column', function() { 23866 expect(firstName.getText()).toBe('Adam'); 23867 expect(lastName.getText()).toBe('John'); 23868 23869 ageHeader.click(); 23870 expect(firstName.getText()).toBe('John'); 23871 expect(lastName.getText()).toBe('Adam'); 23872 23873 ageHeader.click(); 23874 expect(firstName.getText()).toBe('Adam'); 23875 expect(lastName.getText()).toBe('John'); 23876 }); 23877 23878 it('should restore the original order, when clicking "Set to unsorted"', function() { 23879 expect(firstName.getText()).toBe('Adam'); 23880 expect(lastName.getText()).toBe('John'); 23881 23882 unsortButton.click(); 23883 expect(firstName.getText()).toBe('John'); 23884 expect(lastName.getText()).toBe('Julie'); 23885 }); 23886 </file> 23887 </example> 23888 * <hr /> 23889 * 23890 * @example 23891 * ### Using a custom comparator 23892 * 23893 * If you have very specific requirements about the way items are sorted, you can pass your own 23894 * comparator function. For example, you might need to compare some strings in a locale-sensitive 23895 * way. (When specifying a custom comparator, you also need to pass a value for the `reverse` 23896 * argument - passing `false` retains the default sorting order, i.e. ascending.) 23897 * 23898 <example name="orderBy-custom-comparator" module="orderByExample4"> 23899 <file name="index.html"> 23900 <div ng-controller="ExampleController"> 23901 <div class="friends-container custom-comparator"> 23902 <h3>Locale-sensitive Comparator</h3> 23903 <table class="friends"> 23904 <tr> 23905 <th>Name</th> 23906 <th>Favorite Letter</th> 23907 </tr> 23908 <tr ng-repeat="friend in friends | orderBy:'favoriteLetter':false:localeSensitiveComparator"> 23909 <td>{{friend.name}}</td> 23910 <td>{{friend.favoriteLetter}}</td> 23911 </tr> 23912 </table> 23913 </div> 23914 <div class="friends-container default-comparator"> 23915 <h3>Default Comparator</h3> 23916 <table class="friends"> 23917 <tr> 23918 <th>Name</th> 23919 <th>Favorite Letter</th> 23920 </tr> 23921 <tr ng-repeat="friend in friends | orderBy:'favoriteLetter'"> 23922 <td>{{friend.name}}</td> 23923 <td>{{friend.favoriteLetter}}</td> 23924 </tr> 23925 </table> 23926 </div> 23927 </div> 23928 </file> 23929 <file name="script.js"> 23930 angular.module('orderByExample4', []) 23931 .controller('ExampleController', ['$scope', function($scope) { 23932 $scope.friends = [ 23933 {name: 'John', favoriteLetter: 'Ã'}, 23934 {name: 'Mary', favoriteLetter: 'Ã'}, 23935 {name: 'Mike', favoriteLetter: 'Ã'}, 23936 {name: 'Adam', favoriteLetter: 'H'}, 23937 {name: 'Julie', favoriteLetter: 'Z'} 23938 ]; 23939 23940 $scope.localeSensitiveComparator = function(v1, v2) { 23941 // If we don't get strings, just compare by index 23942 if (v1.type !== 'string' || v2.type !== 'string') { 23943 return (v1.index < v2.index) ? -1 : 1; 23944 } 23945 23946 // Compare strings alphabetically, taking locale into account 23947 return v1.value.localeCompare(v2.value); 23948 }; 23949 }]); 23950 </file> 23951 <file name="style.css"> 23952 .friends-container { 23953 display: inline-block; 23954 margin: 0 30px; 23955 } 23956 23957 .friends { 23958 border-collapse: collapse; 23959 } 23960 23961 .friends th { 23962 border-bottom: 1px solid; 23963 } 23964 .friends td, .friends th { 23965 border-left: 1px solid; 23966 padding: 5px 10px; 23967 } 23968 .friends td:first-child, .friends th:first-child { 23969 border-left: none; 23970 } 23971 </file> 23972 <file name="protractor.js" type="protractor"> 23973 // Element locators 23974 var container = element(by.css('.custom-comparator')); 23975 var names = container.all(by.repeater('friends').column('friend.name')); 23976 23977 it('should sort friends by favorite letter (in correct alphabetical order)', function() { 23978 expect(names.get(0).getText()).toBe('John'); 23979 expect(names.get(1).getText()).toBe('Adam'); 23980 expect(names.get(2).getText()).toBe('Mike'); 23981 expect(names.get(3).getText()).toBe('Mary'); 23982 expect(names.get(4).getText()).toBe('Julie'); 23983 }); 23984 </file> 23985 </example> 23986 * 23987 */ 23988orderByFilter.$inject = ['$parse']; 23989function orderByFilter($parse) { 23990 return function(array, sortPredicate, reverseOrder, compareFn) { 23991 23992 if (array == null) return array; 23993 if (!isArrayLike(array)) { 23994 throw minErr('orderBy')('notarray', 'Expected array but received: {0}', array); 23995 } 23996 23997 if (!isArray(sortPredicate)) { sortPredicate = [sortPredicate]; } 23998 if (sortPredicate.length === 0) { sortPredicate = ['+']; } 23999 24000 var predicates = processPredicates(sortPredicate); 24001 24002 var descending = reverseOrder ? -1 : 1; 24003 24004 // Define the `compare()` function. Use a default comparator if none is specified. 24005 var compare = isFunction(compareFn) ? compareFn : defaultCompare; 24006 24007 // The next three lines are a version of a Swartzian Transform idiom from Perl 24008 // (sometimes called the Decorate-Sort-Undecorate idiom) 24009 // See https://en.wikipedia.org/wiki/Schwartzian_transform 24010 var compareValues = Array.prototype.map.call(array, getComparisonObject); 24011 compareValues.sort(doComparison); 24012 array = compareValues.map(function(item) { return item.value; }); 24013 24014 return array; 24015 24016 function getComparisonObject(value, index) { 24017 // NOTE: We are adding an extra `tieBreaker` value based on the element's index. 24018 // This will be used to keep the sort stable when none of the input predicates can 24019 // distinguish between two elements. 24020 return { 24021 value: value, 24022 tieBreaker: {value: index, type: 'number', index: index}, 24023 predicateValues: predicates.map(function(predicate) { 24024 return getPredicateValue(predicate.get(value), index); 24025 }) 24026 }; 24027 } 24028 24029 function doComparison(v1, v2) { 24030 for (var i = 0, ii = predicates.length; i < ii; i++) { 24031 var result = compare(v1.predicateValues[i], v2.predicateValues[i]); 24032 if (result) { 24033 return result * predicates[i].descending * descending; 24034 } 24035 } 24036 24037 return (compare(v1.tieBreaker, v2.tieBreaker) || defaultCompare(v1.tieBreaker, v2.tieBreaker)) * descending; 24038 } 24039 }; 24040 24041 function processPredicates(sortPredicates) { 24042 return sortPredicates.map(function(predicate) { 24043 var descending = 1, get = identity; 24044 24045 if (isFunction(predicate)) { 24046 get = predicate; 24047 } else if (isString(predicate)) { 24048 if ((predicate.charAt(0) === '+' || predicate.charAt(0) === '-')) { 24049 descending = predicate.charAt(0) === '-' ? -1 : 1; 24050 predicate = predicate.substring(1); 24051 } 24052 if (predicate !== '') { 24053 get = $parse(predicate); 24054 if (get.constant) { 24055 var key = get(); 24056 get = function(value) { return value[key]; }; 24057 } 24058 } 24059 } 24060 return {get: get, descending: descending}; 24061 }); 24062 } 24063 24064 function isPrimitive(value) { 24065 switch (typeof value) { 24066 case 'number': /* falls through */ 24067 case 'boolean': /* falls through */ 24068 case 'string': 24069 return true; 24070 default: 24071 return false; 24072 } 24073 } 24074 24075 function objectValue(value) { 24076 // If `valueOf` is a valid function use that 24077 if (isFunction(value.valueOf)) { 24078 value = value.valueOf(); 24079 if (isPrimitive(value)) return value; 24080 } 24081 // If `toString` is a valid function and not the one from `Object.prototype` use that 24082 if (hasCustomToString(value)) { 24083 value = value.toString(); 24084 if (isPrimitive(value)) return value; 24085 } 24086 24087 return value; 24088 } 24089 24090 function getPredicateValue(value, index) { 24091 var type = typeof value; 24092 if (value === null) { 24093 type = 'null'; 24094 } else if (type === 'object') { 24095 value = objectValue(value); 24096 } 24097 return {value: value, type: type, index: index}; 24098 } 24099 24100 function defaultCompare(v1, v2) { 24101 var result = 0; 24102 var type1 = v1.type; 24103 var type2 = v2.type; 24104 24105 if (type1 === type2) { 24106 var value1 = v1.value; 24107 var value2 = v2.value; 24108 24109 if (type1 === 'string') { 24110 // Compare strings case-insensitively 24111 value1 = value1.toLowerCase(); 24112 value2 = value2.toLowerCase(); 24113 } else if (type1 === 'object') { 24114 // For basic objects, use the position of the object 24115 // in the collection instead of the value 24116 if (isObject(value1)) value1 = v1.index; 24117 if (isObject(value2)) value2 = v2.index; 24118 } 24119 24120 if (value1 !== value2) { 24121 result = value1 < value2 ? -1 : 1; 24122 } 24123 } else { 24124 result = (type1 === 'undefined') ? 1 : 24125 (type2 === 'undefined') ? -1 : 24126 (type1 === 'null') ? 1 : 24127 (type2 === 'null') ? -1 : 24128 (type1 < type2) ? -1 : 1; 24129 } 24130 24131 return result; 24132 } 24133} 24134 24135function ngDirective(directive) { 24136 if (isFunction(directive)) { 24137 directive = { 24138 link: directive 24139 }; 24140 } 24141 directive.restrict = directive.restrict || 'AC'; 24142 return valueFn(directive); 24143} 24144 24145/** 24146 * @ngdoc directive 24147 * @name a 24148 * @restrict E 24149 * 24150 * @description 24151 * Modifies the default behavior of the html a tag so that the default action is prevented when 24152 * the href attribute is empty. 24153 * 24154 * For dynamically creating `href` attributes for a tags, see the {@link ng.ngHref `ngHref`} directive. 24155 */ 24156var htmlAnchorDirective = valueFn({ 24157 restrict: 'E', 24158 compile: function(element, attr) { 24159 if (!attr.href && !attr.xlinkHref) { 24160 return function(scope, element) { 24161 // If the linked element is not an anchor tag anymore, do nothing 24162 if (element[0].nodeName.toLowerCase() !== 'a') return; 24163 24164 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. 24165 var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? 24166 'xlink:href' : 'href'; 24167 element.on('click', function(event) { 24168 // if we have no href url, then don't navigate anywhere. 24169 if (!element.attr(href)) { 24170 event.preventDefault(); 24171 } 24172 }); 24173 }; 24174 } 24175 } 24176}); 24177 24178/** 24179 * @ngdoc directive 24180 * @name ngHref 24181 * @restrict A 24182 * @priority 99 24183 * 24184 * @description 24185 * Using AngularJS markup like `{{hash}}` in an href attribute will 24186 * make the link go to the wrong URL if the user clicks it before 24187 * AngularJS has a chance to replace the `{{hash}}` markup with its 24188 * value. Until AngularJS replaces the markup the link will be broken 24189 * and will most likely return a 404 error. The `ngHref` directive 24190 * solves this problem. 24191 * 24192 * The wrong way to write it: 24193 * ```html 24194 * <a href="http://www.gravatar.com/avatar/{{hash}}">link1</a> 24195 * ``` 24196 * 24197 * The correct way to write it: 24198 * ```html 24199 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}">link1</a> 24200 * ``` 24201 * 24202 * @element A 24203 * @param {template} ngHref any string which can contain `{{}}` markup. 24204 * 24205 * @example 24206 * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes 24207 * in links and their different behaviors: 24208 <example name="ng-href"> 24209 <file name="index.html"> 24210 <input ng-model="value" /><br /> 24211 <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br /> 24212 <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br /> 24213 <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br /> 24214 <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br /> 24215 <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br /> 24216 <a id="link-6" ng-href="{{value}}">link</a> (link, change location) 24217 </file> 24218 <file name="protractor.js" type="protractor"> 24219 it('should execute ng-click but not reload when href without value', function() { 24220 element(by.id('link-1')).click(); 24221 expect(element(by.model('value')).getAttribute('value')).toEqual('1'); 24222 expect(element(by.id('link-1')).getAttribute('href')).toBe(''); 24223 }); 24224 24225 it('should execute ng-click but not reload when href empty string', function() { 24226 element(by.id('link-2')).click(); 24227 expect(element(by.model('value')).getAttribute('value')).toEqual('2'); 24228 expect(element(by.id('link-2')).getAttribute('href')).toBe(''); 24229 }); 24230 24231 it('should execute ng-click and change url when ng-href specified', function() { 24232 expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/); 24233 24234 element(by.id('link-3')).click(); 24235 24236 // At this point, we navigate away from an AngularJS page, so we need 24237 // to use browser.driver to get the base webdriver. 24238 24239 browser.wait(function() { 24240 return browser.driver.getCurrentUrl().then(function(url) { 24241 return url.match(/\/123$/); 24242 }); 24243 }, 5000, 'page should navigate to /123'); 24244 }); 24245 24246 it('should execute ng-click but not reload when href empty string and name specified', function() { 24247 element(by.id('link-4')).click(); 24248 expect(element(by.model('value')).getAttribute('value')).toEqual('4'); 24249 expect(element(by.id('link-4')).getAttribute('href')).toBe(''); 24250 }); 24251 24252 it('should execute ng-click but not reload when no href but name specified', function() { 24253 element(by.id('link-5')).click(); 24254 expect(element(by.model('value')).getAttribute('value')).toEqual('5'); 24255 expect(element(by.id('link-5')).getAttribute('href')).toBe(null); 24256 }); 24257 24258 it('should only change url when only ng-href', function() { 24259 element(by.model('value')).clear(); 24260 element(by.model('value')).sendKeys('6'); 24261 expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/); 24262 24263 element(by.id('link-6')).click(); 24264 24265 // At this point, we navigate away from an AngularJS page, so we need 24266 // to use browser.driver to get the base webdriver. 24267 browser.wait(function() { 24268 return browser.driver.getCurrentUrl().then(function(url) { 24269 return url.match(/\/6$/); 24270 }); 24271 }, 5000, 'page should navigate to /6'); 24272 }); 24273 </file> 24274 </example> 24275 */ 24276 24277/** 24278 * @ngdoc directive 24279 * @name ngSrc 24280 * @restrict A 24281 * @priority 99 24282 * 24283 * @description 24284 * Using AngularJS markup like `{{hash}}` in a `src` attribute doesn't 24285 * work right: The browser will fetch from the URL with the literal 24286 * text `{{hash}}` until AngularJS replaces the expression inside 24287 * `{{hash}}`. The `ngSrc` directive solves this problem. 24288 * 24289 * The buggy way to write it: 24290 * ```html 24291 * <img src="http://www.gravatar.com/avatar/{{hash}}" alt="Description"/> 24292 * ``` 24293 * 24294 * The correct way to write it: 24295 * ```html 24296 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}" alt="Description" /> 24297 * ``` 24298 * 24299 * @element IMG 24300 * @param {template} ngSrc any string which can contain `{{}}` markup. 24301 */ 24302 24303/** 24304 * @ngdoc directive 24305 * @name ngSrcset 24306 * @restrict A 24307 * @priority 99 24308 * 24309 * @description 24310 * Using AngularJS markup like `{{hash}}` in a `srcset` attribute doesn't 24311 * work right: The browser will fetch from the URL with the literal 24312 * text `{{hash}}` until AngularJS replaces the expression inside 24313 * `{{hash}}`. The `ngSrcset` directive solves this problem. 24314 * 24315 * The buggy way to write it: 24316 * ```html 24317 * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description"/> 24318 * ``` 24319 * 24320 * The correct way to write it: 24321 * ```html 24322 * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description" /> 24323 * ``` 24324 * 24325 * @element IMG 24326 * @param {template} ngSrcset any string which can contain `{{}}` markup. 24327 */ 24328 24329/** 24330 * @ngdoc directive 24331 * @name ngDisabled 24332 * @restrict A 24333 * @priority 100 24334 * 24335 * @description 24336 * 24337 * This directive sets the `disabled` attribute on the element (typically a form control, 24338 * e.g. `input`, `button`, `select` etc.) if the 24339 * {@link guide/expression expression} inside `ngDisabled` evaluates to truthy. 24340 * 24341 * A special directive is necessary because we cannot use interpolation inside the `disabled` 24342 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 24343 * 24344 * @example 24345 <example name="ng-disabled"> 24346 <file name="index.html"> 24347 <label>Click me to toggle: <input type="checkbox" ng-model="checked"></label><br/> 24348 <button ng-model="button" ng-disabled="checked">Button</button> 24349 </file> 24350 <file name="protractor.js" type="protractor"> 24351 it('should toggle button', function() { 24352 expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy(); 24353 element(by.model('checked')).click(); 24354 expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy(); 24355 }); 24356 </file> 24357 </example> 24358 * 24359 * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, 24360 * then the `disabled` attribute will be set on the element 24361 */ 24362 24363 24364/** 24365 * @ngdoc directive 24366 * @name ngChecked 24367 * @restrict A 24368 * @priority 100 24369 * 24370 * @description 24371 * Sets the `checked` attribute on the element, if the expression inside `ngChecked` is truthy. 24372 * 24373 * Note that this directive should not be used together with {@link ngModel `ngModel`}, 24374 * as this can lead to unexpected behavior. 24375 * 24376 * A special directive is necessary because we cannot use interpolation inside the `checked` 24377 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 24378 * 24379 * @example 24380 <example name="ng-checked"> 24381 <file name="index.html"> 24382 <label>Check me to check both: <input type="checkbox" ng-model="leader"></label><br/> 24383 <input id="checkFollower" type="checkbox" ng-checked="leader" aria-label="Follower input"> 24384 </file> 24385 <file name="protractor.js" type="protractor"> 24386 it('should check both checkBoxes', function() { 24387 expect(element(by.id('checkFollower')).getAttribute('checked')).toBeFalsy(); 24388 element(by.model('leader')).click(); 24389 expect(element(by.id('checkFollower')).getAttribute('checked')).toBeTruthy(); 24390 }); 24391 </file> 24392 </example> 24393 * 24394 * @element INPUT 24395 * @param {expression} ngChecked If the {@link guide/expression expression} is truthy, 24396 * then the `checked` attribute will be set on the element 24397 */ 24398 24399 24400/** 24401 * @ngdoc directive 24402 * @name ngReadonly 24403 * @restrict A 24404 * @priority 100 24405 * 24406 * @description 24407 * 24408 * Sets the `readonly` attribute on the element, if the expression inside `ngReadonly` is truthy. 24409 * Note that `readonly` applies only to `input` elements with specific types. [See the input docs on 24410 * MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-readonly) for more information. 24411 * 24412 * A special directive is necessary because we cannot use interpolation inside the `readonly` 24413 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 24414 * 24415 * @example 24416 <example name="ng-readonly"> 24417 <file name="index.html"> 24418 <label>Check me to make text readonly: <input type="checkbox" ng-model="checked"></label><br/> 24419 <input type="text" ng-readonly="checked" value="I'm AngularJS" aria-label="Readonly field" /> 24420 </file> 24421 <file name="protractor.js" type="protractor"> 24422 it('should toggle readonly attr', function() { 24423 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy(); 24424 element(by.model('checked')).click(); 24425 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy(); 24426 }); 24427 </file> 24428 </example> 24429 * 24430 * @element INPUT 24431 * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy, 24432 * then special attribute "readonly" will be set on the element 24433 */ 24434 24435 24436/** 24437 * @ngdoc directive 24438 * @name ngSelected 24439 * @restrict A 24440 * @priority 100 24441 * 24442 * @description 24443 * 24444 * Sets the `selected` attribute on the element, if the expression inside `ngSelected` is truthy. 24445 * 24446 * A special directive is necessary because we cannot use interpolation inside the `selected` 24447 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 24448 * 24449 * <div class="alert alert-warning"> 24450 * **Note:** `ngSelected` does not interact with the `select` and `ngModel` directives, it only 24451 * sets the `selected` attribute on the element. If you are using `ngModel` on the select, you 24452 * should not use `ngSelected` on the options, as `ngModel` will set the select value and 24453 * selected options. 24454 * </div> 24455 * 24456 * @example 24457 <example name="ng-selected"> 24458 <file name="index.html"> 24459 <label>Check me to select: <input type="checkbox" ng-model="selected"></label><br/> 24460 <select aria-label="ngSelected demo"> 24461 <option>Hello!</option> 24462 <option id="greet" ng-selected="selected">Greetings!</option> 24463 </select> 24464 </file> 24465 <file name="protractor.js" type="protractor"> 24466 it('should select Greetings!', function() { 24467 expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy(); 24468 element(by.model('selected')).click(); 24469 expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy(); 24470 }); 24471 </file> 24472 </example> 24473 * 24474 * @element OPTION 24475 * @param {expression} ngSelected If the {@link guide/expression expression} is truthy, 24476 * then special attribute "selected" will be set on the element 24477 */ 24478 24479/** 24480 * @ngdoc directive 24481 * @name ngOpen 24482 * @restrict A 24483 * @priority 100 24484 * 24485 * @description 24486 * 24487 * Sets the `open` attribute on the element, if the expression inside `ngOpen` is truthy. 24488 * 24489 * A special directive is necessary because we cannot use interpolation inside the `open` 24490 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 24491 * 24492 * ## A note about browser compatibility 24493 * 24494 * Internet Explorer and Edge do not support the `details` element, it is 24495 * recommended to use {@link ng.ngShow} and {@link ng.ngHide} instead. 24496 * 24497 * @example 24498 <example name="ng-open"> 24499 <file name="index.html"> 24500 <label>Toggle details: <input type="checkbox" ng-model="open"></label><br/> 24501 <details id="details" ng-open="open"> 24502 <summary>List</summary> 24503 <ul> 24504 <li>Apple</li> 24505 <li>Orange</li> 24506 <li>Durian</li> 24507 </ul> 24508 </details> 24509 </file> 24510 <file name="protractor.js" type="protractor"> 24511 it('should toggle open', function() { 24512 expect(element(by.id('details')).getAttribute('open')).toBeFalsy(); 24513 element(by.model('open')).click(); 24514 expect(element(by.id('details')).getAttribute('open')).toBeTruthy(); 24515 }); 24516 </file> 24517 </example> 24518 * 24519 * @element DETAILS 24520 * @param {expression} ngOpen If the {@link guide/expression expression} is truthy, 24521 * then special attribute "open" will be set on the element 24522 */ 24523 24524var ngAttributeAliasDirectives = {}; 24525 24526// boolean attrs are evaluated 24527forEach(BOOLEAN_ATTR, function(propName, attrName) { 24528 // binding to multiple is not supported 24529 if (propName === 'multiple') return; 24530 24531 function defaultLinkFn(scope, element, attr) { 24532 scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { 24533 attr.$set(attrName, !!value); 24534 }); 24535 } 24536 24537 var normalized = directiveNormalize('ng-' + attrName); 24538 var linkFn = defaultLinkFn; 24539 24540 if (propName === 'checked') { 24541 linkFn = function(scope, element, attr) { 24542 // ensuring ngChecked doesn't interfere with ngModel when both are set on the same input 24543 if (attr.ngModel !== attr[normalized]) { 24544 defaultLinkFn(scope, element, attr); 24545 } 24546 }; 24547 } 24548 24549 ngAttributeAliasDirectives[normalized] = function() { 24550 return { 24551 restrict: 'A', 24552 priority: 100, 24553 link: linkFn 24554 }; 24555 }; 24556}); 24557 24558// aliased input attrs are evaluated 24559forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) { 24560 ngAttributeAliasDirectives[ngAttr] = function() { 24561 return { 24562 priority: 100, 24563 link: function(scope, element, attr) { 24564 //special case ngPattern when a literal regular expression value 24565 //is used as the expression (this way we don't have to watch anything). 24566 if (ngAttr === 'ngPattern' && attr.ngPattern.charAt(0) === '/') { 24567 var match = attr.ngPattern.match(REGEX_STRING_REGEXP); 24568 if (match) { 24569 attr.$set('ngPattern', new RegExp(match[1], match[2])); 24570 return; 24571 } 24572 } 24573 24574 scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) { 24575 attr.$set(ngAttr, value); 24576 }); 24577 } 24578 }; 24579 }; 24580}); 24581 24582// ng-src, ng-srcset, ng-href are interpolated 24583forEach(['src', 'srcset', 'href'], function(attrName) { 24584 var normalized = directiveNormalize('ng-' + attrName); 24585 ngAttributeAliasDirectives[normalized] = ['$sce', function($sce) { 24586 return { 24587 priority: 99, // it needs to run after the attributes are interpolated 24588 link: function(scope, element, attr) { 24589 var propName = attrName, 24590 name = attrName; 24591 24592 if (attrName === 'href' && 24593 toString.call(element.prop('href')) === '[object SVGAnimatedString]') { 24594 name = 'xlinkHref'; 24595 attr.$attr[name] = 'xlink:href'; 24596 propName = null; 24597 } 24598 24599 // We need to sanitize the url at least once, in case it is a constant 24600 // non-interpolated attribute. 24601 attr.$set(normalized, $sce.getTrustedMediaUrl(attr[normalized])); 24602 24603 attr.$observe(normalized, function(value) { 24604 if (!value) { 24605 if (attrName === 'href') { 24606 attr.$set(name, null); 24607 } 24608 return; 24609 } 24610 24611 attr.$set(name, value); 24612 24613 // Support: IE 9-11 only 24614 // On IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist 24615 // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need 24616 // to set the property as well to achieve the desired effect. 24617 // We use attr[attrName] value since $set might have sanitized the url. 24618 if (msie && propName) element.prop(propName, attr[name]); 24619 }); 24620 } 24621 }; 24622 }]; 24623}); 24624 24625/* global -nullFormCtrl, -PENDING_CLASS, -SUBMITTED_CLASS 24626 */ 24627var nullFormCtrl = { 24628 $addControl: noop, 24629 $getControls: valueFn([]), 24630 $$renameControl: nullFormRenameControl, 24631 $removeControl: noop, 24632 $setValidity: noop, 24633 $setDirty: noop, 24634 $setPristine: noop, 24635 $setSubmitted: noop, 24636 $$setSubmitted: noop 24637}, 24638PENDING_CLASS = 'ng-pending', 24639SUBMITTED_CLASS = 'ng-submitted'; 24640 24641function nullFormRenameControl(control, name) { 24642 control.$name = name; 24643} 24644 24645/** 24646 * @ngdoc type 24647 * @name form.FormController 24648 * 24649 * @property {boolean} $pristine True if user has not interacted with the form yet. 24650 * @property {boolean} $dirty True if user has already interacted with the form. 24651 * @property {boolean} $valid True if all of the containing forms and controls are valid. 24652 * @property {boolean} $invalid True if at least one containing control or form is invalid. 24653 * @property {boolean} $submitted True if user has submitted the form even if its invalid. 24654 * 24655 * @property {Object} $pending An object hash, containing references to controls or forms with 24656 * pending validators, where: 24657 * 24658 * - keys are validations tokens (error names). 24659 * - values are arrays of controls or forms that have a pending validator for the given error name. 24660 * 24661 * See {@link form.FormController#$error $error} for a list of built-in validation tokens. 24662 * 24663 * @property {Object} $error An object hash, containing references to controls or forms with failing 24664 * validators, where: 24665 * 24666 * - keys are validation tokens (error names), 24667 * - values are arrays of controls or forms that have a failing validator for the given error name. 24668 * 24669 * Built-in validation tokens: 24670 * - `email` 24671 * - `max` 24672 * - `maxlength` 24673 * - `min` 24674 * - `minlength` 24675 * - `number` 24676 * - `pattern` 24677 * - `required` 24678 * - `url` 24679 * - `date` 24680 * - `datetimelocal` 24681 * - `time` 24682 * - `week` 24683 * - `month` 24684 * 24685 * @description 24686 * `FormController` keeps track of all its controls and nested forms as well as the state of them, 24687 * such as being valid/invalid or dirty/pristine. 24688 * 24689 * Each {@link ng.directive:form form} directive creates an instance 24690 * of `FormController`. 24691 * 24692 */ 24693//asks for $scope to fool the BC controller module 24694FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate']; 24695function FormController($element, $attrs, $scope, $animate, $interpolate) { 24696 this.$$controls = []; 24697 24698 // init state 24699 this.$error = {}; 24700 this.$$success = {}; 24701 this.$pending = undefined; 24702 this.$name = $interpolate($attrs.name || $attrs.ngForm || '')($scope); 24703 this.$dirty = false; 24704 this.$pristine = true; 24705 this.$valid = true; 24706 this.$invalid = false; 24707 this.$submitted = false; 24708 this.$$parentForm = nullFormCtrl; 24709 24710 this.$$element = $element; 24711 this.$$animate = $animate; 24712 24713 setupValidity(this); 24714} 24715 24716FormController.prototype = { 24717 /** 24718 * @ngdoc method 24719 * @name form.FormController#$rollbackViewValue 24720 * 24721 * @description 24722 * Rollback all form controls pending updates to the `$modelValue`. 24723 * 24724 * Updates may be pending by a debounced event or because the input is waiting for a some future 24725 * event defined in `ng-model-options`. This method is typically needed by the reset button of 24726 * a form that uses `ng-model-options` to pend updates. 24727 */ 24728 $rollbackViewValue: function() { 24729 forEach(this.$$controls, function(control) { 24730 control.$rollbackViewValue(); 24731 }); 24732 }, 24733 24734 /** 24735 * @ngdoc method 24736 * @name form.FormController#$commitViewValue 24737 * 24738 * @description 24739 * Commit all form controls pending updates to the `$modelValue`. 24740 * 24741 * Updates may be pending by a debounced event or because the input is waiting for a some future 24742 * event defined in `ng-model-options`. This method is rarely needed as `NgModelController` 24743 * usually handles calling this in response to input events. 24744 */ 24745 $commitViewValue: function() { 24746 forEach(this.$$controls, function(control) { 24747 control.$commitViewValue(); 24748 }); 24749 }, 24750 24751 /** 24752 * @ngdoc method 24753 * @name form.FormController#$addControl 24754 * @param {object} control control object, either a {@link form.FormController} or an 24755 * {@link ngModel.NgModelController} 24756 * 24757 * @description 24758 * Register a control with the form. Input elements using ngModelController do this automatically 24759 * when they are linked. 24760 * 24761 * Note that the current state of the control will not be reflected on the new parent form. This 24762 * is not an issue with normal use, as freshly compiled and linked controls are in a `$pristine` 24763 * state. 24764 * 24765 * However, if the method is used programmatically, for example by adding dynamically created controls, 24766 * or controls that have been previously removed without destroying their corresponding DOM element, 24767 * it's the developers responsibility to make sure the current state propagates to the parent form. 24768 * 24769 * For example, if an input control is added that is already `$dirty` and has `$error` properties, 24770 * calling `$setDirty()` and `$validate()` afterwards will propagate the state to the parent form. 24771 */ 24772 $addControl: function(control) { 24773 // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored 24774 // and not added to the scope. Now we throw an error. 24775 assertNotHasOwnProperty(control.$name, 'input'); 24776 this.$$controls.push(control); 24777 24778 if (control.$name) { 24779 this[control.$name] = control; 24780 } 24781 24782 control.$$parentForm = this; 24783 }, 24784 24785 /** 24786 * @ngdoc method 24787 * @name form.FormController#$getControls 24788 * @returns {Array} the controls that are currently part of this form 24789 * 24790 * @description 24791 * This method returns a **shallow copy** of the controls that are currently part of this form. 24792 * The controls can be instances of {@link form.FormController `FormController`} 24793 * ({@link ngForm "child-forms"}) and of {@link ngModel.NgModelController `NgModelController`}. 24794 * If you need access to the controls of child-forms, you have to call `$getControls()` 24795 * recursively on them. 24796 * This can be used for example to iterate over all controls to validate them. 24797 * 24798 * The controls can be accessed normally, but adding to, or removing controls from the array has 24799 * no effect on the form. Instead, use {@link form.FormController#$addControl `$addControl()`} and 24800 * {@link form.FormController#$removeControl `$removeControl()`} for this use-case. 24801 * Likewise, adding a control to, or removing a control from the form is not reflected 24802 * in the shallow copy. That means you should get a fresh copy from `$getControls()` every time 24803 * you need access to the controls. 24804 */ 24805 $getControls: function() { 24806 return shallowCopy(this.$$controls); 24807 }, 24808 24809 // Private API: rename a form control 24810 $$renameControl: function(control, newName) { 24811 var oldName = control.$name; 24812 24813 if (this[oldName] === control) { 24814 delete this[oldName]; 24815 } 24816 this[newName] = control; 24817 control.$name = newName; 24818 }, 24819 24820 /** 24821 * @ngdoc method 24822 * @name form.FormController#$removeControl 24823 * @param {object} control control object, either a {@link form.FormController} or an 24824 * {@link ngModel.NgModelController} 24825 * 24826 * @description 24827 * Deregister a control from the form. 24828 * 24829 * Input elements using ngModelController do this automatically when they are destroyed. 24830 * 24831 * Note that only the removed control's validation state (`$errors`etc.) will be removed from the 24832 * form. `$dirty`, `$submitted` states will not be changed, because the expected behavior can be 24833 * different from case to case. For example, removing the only `$dirty` control from a form may or 24834 * may not mean that the form is still `$dirty`. 24835 */ 24836 $removeControl: function(control) { 24837 if (control.$name && this[control.$name] === control) { 24838 delete this[control.$name]; 24839 } 24840 forEach(this.$pending, function(value, name) { 24841 // eslint-disable-next-line no-invalid-this 24842 this.$setValidity(name, null, control); 24843 }, this); 24844 forEach(this.$error, function(value, name) { 24845 // eslint-disable-next-line no-invalid-this 24846 this.$setValidity(name, null, control); 24847 }, this); 24848 forEach(this.$$success, function(value, name) { 24849 // eslint-disable-next-line no-invalid-this 24850 this.$setValidity(name, null, control); 24851 }, this); 24852 24853 arrayRemove(this.$$controls, control); 24854 control.$$parentForm = nullFormCtrl; 24855 }, 24856 24857 /** 24858 * @ngdoc method 24859 * @name form.FormController#$setDirty 24860 * 24861 * @description 24862 * Sets the form to a dirty state. 24863 * 24864 * This method can be called to add the 'ng-dirty' class and set the form to a dirty 24865 * state (ng-dirty class). This method will also propagate to parent forms. 24866 */ 24867 $setDirty: function() { 24868 this.$$animate.removeClass(this.$$element, PRISTINE_CLASS); 24869 this.$$animate.addClass(this.$$element, DIRTY_CLASS); 24870 this.$dirty = true; 24871 this.$pristine = false; 24872 this.$$parentForm.$setDirty(); 24873 }, 24874 24875 /** 24876 * @ngdoc method 24877 * @name form.FormController#$setPristine 24878 * 24879 * @description 24880 * Sets the form to its pristine state. 24881 * 24882 * This method sets the form's `$pristine` state to true, the `$dirty` state to false, removes 24883 * the `ng-dirty` class and adds the `ng-pristine` class. Additionally, it sets the `$submitted` 24884 * state to false. 24885 * 24886 * This method will also propagate to all the controls contained in this form. 24887 * 24888 * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after 24889 * saving or resetting it. 24890 */ 24891 $setPristine: function() { 24892 this.$$animate.setClass(this.$$element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS); 24893 this.$dirty = false; 24894 this.$pristine = true; 24895 this.$submitted = false; 24896 forEach(this.$$controls, function(control) { 24897 control.$setPristine(); 24898 }); 24899 }, 24900 24901 /** 24902 * @ngdoc method 24903 * @name form.FormController#$setUntouched 24904 * 24905 * @description 24906 * Sets the form to its untouched state. 24907 * 24908 * This method can be called to remove the 'ng-touched' class and set the form controls to their 24909 * untouched state (ng-untouched class). 24910 * 24911 * Setting a form controls back to their untouched state is often useful when setting the form 24912 * back to its pristine state. 24913 */ 24914 $setUntouched: function() { 24915 forEach(this.$$controls, function(control) { 24916 control.$setUntouched(); 24917 }); 24918 }, 24919 24920 /** 24921 * @ngdoc method 24922 * @name form.FormController#$setSubmitted 24923 * 24924 * @description 24925 * Sets the form to its `$submitted` state. This will also set `$submitted` on all child and 24926 * parent forms of the form. 24927 */ 24928 $setSubmitted: function() { 24929 var rootForm = this; 24930 while (rootForm.$$parentForm && (rootForm.$$parentForm !== nullFormCtrl)) { 24931 rootForm = rootForm.$$parentForm; 24932 } 24933 rootForm.$$setSubmitted(); 24934 }, 24935 24936 $$setSubmitted: function() { 24937 this.$$animate.addClass(this.$$element, SUBMITTED_CLASS); 24938 this.$submitted = true; 24939 forEach(this.$$controls, function(control) { 24940 if (control.$$setSubmitted) { 24941 control.$$setSubmitted(); 24942 } 24943 }); 24944 } 24945}; 24946 24947/** 24948 * @ngdoc method 24949 * @name form.FormController#$setValidity 24950 * 24951 * @description 24952 * Change the validity state of the form, and notify the parent form (if any). 24953 * 24954 * Application developers will rarely need to call this method directly. It is used internally, by 24955 * {@link ngModel.NgModelController#$setValidity NgModelController.$setValidity()}, to propagate a 24956 * control's validity state to the parent `FormController`. 24957 * 24958 * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be 24959 * assigned to either `$error[validationErrorKey]` or `$pending[validationErrorKey]` (for 24960 * unfulfilled `$asyncValidators`), so that it is available for data-binding. The 24961 * `validationErrorKey` should be in camelCase and will get converted into dash-case for 24962 * class name. Example: `myError` will result in `ng-valid-my-error` and 24963 * `ng-invalid-my-error` classes and can be bound to as `{{ someForm.$error.myError }}`. 24964 * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending 24965 * (undefined), or skipped (null). Pending is used for unfulfilled `$asyncValidators`. 24966 * Skipped is used by AngularJS when validators do not run because of parse errors and when 24967 * `$asyncValidators` do not run because any of the `$validators` failed. 24968 * @param {NgModelController | FormController} controller - The controller whose validity state is 24969 * triggering the change. 24970 */ 24971addSetValidityMethod({ 24972 clazz: FormController, 24973 set: function(object, property, controller) { 24974 var list = object[property]; 24975 if (!list) { 24976 object[property] = [controller]; 24977 } else { 24978 var index = list.indexOf(controller); 24979 if (index === -1) { 24980 list.push(controller); 24981 } 24982 } 24983 }, 24984 unset: function(object, property, controller) { 24985 var list = object[property]; 24986 if (!list) { 24987 return; 24988 } 24989 arrayRemove(list, controller); 24990 if (list.length === 0) { 24991 delete object[property]; 24992 } 24993 } 24994}); 24995 24996/** 24997 * @ngdoc directive 24998 * @name ngForm 24999 * @restrict EAC 25000 * 25001 * @description 25002 * Helper directive that makes it possible to create control groups inside a 25003 * {@link ng.directive:form `form`} directive. 25004 * These "child forms" can be used, for example, to determine the validity of a sub-group of 25005 * controls. 25006 * 25007 * <div class="alert alert-danger"> 25008 * **Note**: `ngForm` cannot be used as a replacement for `<form>`, because it lacks its 25009 * [built-in HTML functionality](https://html.spec.whatwg.org/#the-form-element). 25010 * Specifically, you cannot submit `ngForm` like a `<form>` tag. That means, 25011 * you cannot send data to the server with `ngForm`, or integrate it with 25012 * {@link ng.directive:ngSubmit `ngSubmit`}. 25013 * </div> 25014 * 25015 * @param {string=} ngForm|name Name of the form. If specified, the form controller will 25016 * be published into the related scope, under this name. 25017 * 25018 */ 25019 25020 /** 25021 * @ngdoc directive 25022 * @name form 25023 * @restrict E 25024 * 25025 * @description 25026 * Directive that instantiates 25027 * {@link form.FormController FormController}. 25028 * 25029 * If the `name` attribute is specified, the form controller is published onto the current scope under 25030 * this name. 25031 * 25032 * ## Alias: {@link ng.directive:ngForm `ngForm`} 25033 * 25034 * In AngularJS, forms can be nested. This means that the outer form is valid when all of the child 25035 * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so 25036 * AngularJS provides the {@link ng.directive:ngForm `ngForm`} directive, which behaves identically to 25037 * `form` but can be nested. Nested forms can be useful, for example, if the validity of a sub-group 25038 * of controls needs to be determined. 25039 * 25040 * ## CSS classes 25041 * - `ng-valid` is set if the form is valid. 25042 * - `ng-invalid` is set if the form is invalid. 25043 * - `ng-pending` is set if the form is pending. 25044 * - `ng-pristine` is set if the form is pristine. 25045 * - `ng-dirty` is set if the form is dirty. 25046 * - `ng-submitted` is set if the form was submitted. 25047 * 25048 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 25049 * 25050 * 25051 * ## Submitting a form and preventing the default action 25052 * 25053 * Since the role of forms in client-side AngularJS applications is different than in classical 25054 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full 25055 * page reload that sends the data to the server. Instead some javascript logic should be triggered 25056 * to handle the form submission in an application-specific way. 25057 * 25058 * For this reason, AngularJS prevents the default action (form submission to the server) unless the 25059 * `<form>` element has an `action` attribute specified. 25060 * 25061 * You can use one of the following two ways to specify what javascript method should be called when 25062 * a form is submitted: 25063 * 25064 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element 25065 * - {@link ng.directive:ngClick ngClick} directive on the first 25066 * button or input field of type submit (input[type=submit]) 25067 * 25068 * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit} 25069 * or {@link ng.directive:ngClick ngClick} directives. 25070 * This is because of the following form submission rules in the HTML specification: 25071 * 25072 * - If a form has only one input field then hitting enter in this field triggers form submit 25073 * (`ngSubmit`) 25074 * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter 25075 * doesn't trigger submit 25076 * - if a form has one or more input fields and one or more buttons or input[type=submit] then 25077 * hitting enter in any of the input fields will trigger the click handler on the *first* button or 25078 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`) 25079 * 25080 * Any pending `ngModelOptions` changes will take place immediately when an enclosing form is 25081 * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` 25082 * to have access to the updated model. 25083 * 25084 * @animations 25085 * Animations in ngForm are triggered when any of the associated CSS classes are added and removed. 25086 * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any 25087 * other validations that are performed within the form. Animations in ngForm are similar to how 25088 * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well 25089 * as JS animations. 25090 * 25091 * The following example shows a simple way to utilize CSS transitions to style a form element 25092 * that has been rendered as invalid after it has been validated: 25093 * 25094 * <pre> 25095 * //be sure to include ngAnimate as a module to hook into more 25096 * //advanced animations 25097 * .my-form { 25098 * transition:0.5s linear all; 25099 * background: white; 25100 * } 25101 * .my-form.ng-invalid { 25102 * background: red; 25103 * color:white; 25104 * } 25105 * </pre> 25106 * 25107 * @example 25108 <example name="ng-form" deps="angular-animate.js" animations="true" fixBase="true" module="formExample"> 25109 <file name="index.html"> 25110 <script> 25111 angular.module('formExample', []) 25112 .controller('FormController', ['$scope', function($scope) { 25113 $scope.userType = 'guest'; 25114 }]); 25115 </script> 25116 <style> 25117 .my-form { 25118 transition:all linear 0.5s; 25119 background: transparent; 25120 } 25121 .my-form.ng-invalid { 25122 background: red; 25123 } 25124 </style> 25125 <form name="myForm" ng-controller="FormController" class="my-form"> 25126 userType: <input name="input" ng-model="userType" required> 25127 <span class="error" ng-show="myForm.input.$error.required">Required!</span><br> 25128 <code>userType = {{userType}}</code><br> 25129 <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br> 25130 <code>myForm.input.$error = {{myForm.input.$error}}</code><br> 25131 <code>myForm.$valid = {{myForm.$valid}}</code><br> 25132 <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br> 25133 </form> 25134 </file> 25135 <file name="protractor.js" type="protractor"> 25136 it('should initialize to model', function() { 25137 var userType = element(by.binding('userType')); 25138 var valid = element(by.binding('myForm.input.$valid')); 25139 25140 expect(userType.getText()).toContain('guest'); 25141 expect(valid.getText()).toContain('true'); 25142 }); 25143 25144 it('should be invalid if empty', function() { 25145 var userType = element(by.binding('userType')); 25146 var valid = element(by.binding('myForm.input.$valid')); 25147 var userInput = element(by.model('userType')); 25148 25149 userInput.clear(); 25150 userInput.sendKeys(''); 25151 25152 expect(userType.getText()).toEqual('userType ='); 25153 expect(valid.getText()).toContain('false'); 25154 }); 25155 </file> 25156 </example> 25157 * 25158 * @param {string=} name Name of the form. If specified, the form controller will be published into 25159 * related scope, under this name. 25160 */ 25161var formDirectiveFactory = function(isNgForm) { 25162 return ['$timeout', '$parse', function($timeout, $parse) { 25163 var formDirective = { 25164 name: 'form', 25165 restrict: isNgForm ? 'EAC' : 'E', 25166 require: ['form', '^^?form'], //first is the form's own ctrl, second is an optional parent form 25167 controller: FormController, 25168 compile: function ngFormCompile(formElement, attr) { 25169 // Setup initial state of the control 25170 formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS); 25171 25172 var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false); 25173 25174 return { 25175 pre: function ngFormPreLink(scope, formElement, attr, ctrls) { 25176 var controller = ctrls[0]; 25177 25178 // if `action` attr is not present on the form, prevent the default action (submission) 25179 if (!('action' in attr)) { 25180 // we can't use jq events because if a form is destroyed during submission the default 25181 // action is not prevented. see #1238 25182 // 25183 // IE 9 is not affected because it doesn't fire a submit event and try to do a full 25184 // page reload if the form was destroyed by submission of the form via a click handler 25185 // on a button in the form. Looks like an IE9 specific bug. 25186 var handleFormSubmission = function(event) { 25187 scope.$apply(function() { 25188 controller.$commitViewValue(); 25189 controller.$setSubmitted(); 25190 }); 25191 25192 event.preventDefault(); 25193 }; 25194 25195 formElement[0].addEventListener('submit', handleFormSubmission); 25196 25197 // unregister the preventDefault listener so that we don't not leak memory but in a 25198 // way that will achieve the prevention of the default action. 25199 formElement.on('$destroy', function() { 25200 $timeout(function() { 25201 formElement[0].removeEventListener('submit', handleFormSubmission); 25202 }, 0, false); 25203 }); 25204 } 25205 25206 var parentFormCtrl = ctrls[1] || controller.$$parentForm; 25207 parentFormCtrl.$addControl(controller); 25208 25209 var setter = nameAttr ? getSetter(controller.$name) : noop; 25210 25211 if (nameAttr) { 25212 setter(scope, controller); 25213 attr.$observe(nameAttr, function(newValue) { 25214 if (controller.$name === newValue) return; 25215 setter(scope, undefined); 25216 controller.$$parentForm.$$renameControl(controller, newValue); 25217 setter = getSetter(controller.$name); 25218 setter(scope, controller); 25219 }); 25220 } 25221 formElement.on('$destroy', function() { 25222 controller.$$parentForm.$removeControl(controller); 25223 setter(scope, undefined); 25224 extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards 25225 }); 25226 } 25227 }; 25228 } 25229 }; 25230 25231 return formDirective; 25232 25233 function getSetter(expression) { 25234 if (expression === '') { 25235 //create an assignable expression, so forms with an empty name can be renamed later 25236 return $parse('this[""]').assign; 25237 } 25238 return $parse(expression).assign || noop; 25239 } 25240 }]; 25241}; 25242 25243var formDirective = formDirectiveFactory(); 25244var ngFormDirective = formDirectiveFactory(true); 25245 25246 25247 25248// helper methods 25249function setupValidity(instance) { 25250 instance.$$classCache = {}; 25251 instance.$$classCache[INVALID_CLASS] = !(instance.$$classCache[VALID_CLASS] = instance.$$element.hasClass(VALID_CLASS)); 25252} 25253function addSetValidityMethod(context) { 25254 var clazz = context.clazz, 25255 set = context.set, 25256 unset = context.unset; 25257 25258 clazz.prototype.$setValidity = function(validationErrorKey, state, controller) { 25259 if (isUndefined(state)) { 25260 createAndSet(this, '$pending', validationErrorKey, controller); 25261 } else { 25262 unsetAndCleanup(this, '$pending', validationErrorKey, controller); 25263 } 25264 if (!isBoolean(state)) { 25265 unset(this.$error, validationErrorKey, controller); 25266 unset(this.$$success, validationErrorKey, controller); 25267 } else { 25268 if (state) { 25269 unset(this.$error, validationErrorKey, controller); 25270 set(this.$$success, validationErrorKey, controller); 25271 } else { 25272 set(this.$error, validationErrorKey, controller); 25273 unset(this.$$success, validationErrorKey, controller); 25274 } 25275 } 25276 if (this.$pending) { 25277 cachedToggleClass(this, PENDING_CLASS, true); 25278 this.$valid = this.$invalid = undefined; 25279 toggleValidationCss(this, '', null); 25280 } else { 25281 cachedToggleClass(this, PENDING_CLASS, false); 25282 this.$valid = isObjectEmpty(this.$error); 25283 this.$invalid = !this.$valid; 25284 toggleValidationCss(this, '', this.$valid); 25285 } 25286 25287 // re-read the state as the set/unset methods could have 25288 // combined state in this.$error[validationError] (used for forms), 25289 // where setting/unsetting only increments/decrements the value, 25290 // and does not replace it. 25291 var combinedState; 25292 if (this.$pending && this.$pending[validationErrorKey]) { 25293 combinedState = undefined; 25294 } else if (this.$error[validationErrorKey]) { 25295 combinedState = false; 25296 } else if (this.$$success[validationErrorKey]) { 25297 combinedState = true; 25298 } else { 25299 combinedState = null; 25300 } 25301 25302 toggleValidationCss(this, validationErrorKey, combinedState); 25303 this.$$parentForm.$setValidity(validationErrorKey, combinedState, this); 25304 }; 25305 25306 function createAndSet(ctrl, name, value, controller) { 25307 if (!ctrl[name]) { 25308 ctrl[name] = {}; 25309 } 25310 set(ctrl[name], value, controller); 25311 } 25312 25313 function unsetAndCleanup(ctrl, name, value, controller) { 25314 if (ctrl[name]) { 25315 unset(ctrl[name], value, controller); 25316 } 25317 if (isObjectEmpty(ctrl[name])) { 25318 ctrl[name] = undefined; 25319 } 25320 } 25321 25322 function cachedToggleClass(ctrl, className, switchValue) { 25323 if (switchValue && !ctrl.$$classCache[className]) { 25324 ctrl.$$animate.addClass(ctrl.$$element, className); 25325 ctrl.$$classCache[className] = true; 25326 } else if (!switchValue && ctrl.$$classCache[className]) { 25327 ctrl.$$animate.removeClass(ctrl.$$element, className); 25328 ctrl.$$classCache[className] = false; 25329 } 25330 } 25331 25332 function toggleValidationCss(ctrl, validationErrorKey, isValid) { 25333 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; 25334 25335 cachedToggleClass(ctrl, VALID_CLASS + validationErrorKey, isValid === true); 25336 cachedToggleClass(ctrl, INVALID_CLASS + validationErrorKey, isValid === false); 25337 } 25338} 25339 25340function isObjectEmpty(obj) { 25341 if (obj) { 25342 for (var prop in obj) { 25343 if (obj.hasOwnProperty(prop)) { 25344 return false; 25345 } 25346 } 25347 } 25348 return true; 25349} 25350 25351/* global 25352 VALID_CLASS: false, 25353 INVALID_CLASS: false, 25354 PRISTINE_CLASS: false, 25355 DIRTY_CLASS: false, 25356 ngModelMinErr: false 25357*/ 25358 25359// Regex code was initially obtained from SO prior to modification: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231 25360var 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)$/; 25361// See valid URLs in RFC3987 (http://tools.ietf.org/html/rfc3987) 25362// Note: We are being more lenient, because browsers are too. 25363// 1. Scheme 25364// 2. Slashes 25365// 3. Username 25366// 4. Password 25367// 5. Hostname 25368// 6. Port 25369// 7. Path 25370// 8. Query 25371// 9. Fragment 25372// 1111111111111111 222 333333 44444 55555555555555555555555 666 77777777 8888888 999 25373var URL_REGEXP = /^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i; 25374// eslint-disable-next-line max-len 25375var 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])?)*$/; 25376var NUMBER_REGEXP = /^\s*(-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/; 25377var DATE_REGEXP = /^(\d{4,})-(\d{2})-(\d{2})$/; 25378var DATETIMELOCAL_REGEXP = /^(\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; 25379var WEEK_REGEXP = /^(\d{4,})-W(\d\d)$/; 25380var MONTH_REGEXP = /^(\d{4,})-(\d\d)$/; 25381var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; 25382 25383var PARTIAL_VALIDATION_EVENTS = 'keydown wheel mousedown'; 25384var PARTIAL_VALIDATION_TYPES = createMap(); 25385forEach('date,datetime-local,month,time,week'.split(','), function(type) { 25386 PARTIAL_VALIDATION_TYPES[type] = true; 25387}); 25388 25389var inputType = { 25390 25391 /** 25392 * @ngdoc input 25393 * @name input[text] 25394 * 25395 * @description 25396 * Standard HTML text input with AngularJS data binding, inherited by most of the `input` elements. 25397 * 25398 * 25399 * @param {string} ngModel Assignable AngularJS expression to data-bind to. 25400 * @param {string=} name Property name of the form under which the control is published. 25401 * @param {string=} required Adds `required` validation error key if the value is not entered. 25402 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 25403 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 25404 * `required` when you want to data-bind to the `required` attribute. 25405 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 25406 * minlength. 25407 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 25408 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 25409 * any length. 25410 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 25411 * that contains the regular expression body that will be converted to a regular expression 25412 * as in the ngPattern directive. 25413 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 25414 * does not match a RegExp found by evaluating the AngularJS expression given in the attribute value. 25415 * If the expression evaluates to a RegExp object, then this is used directly. 25416 * If the expression evaluates to a string, then it will be converted to a RegExp 25417 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 25418 * `new RegExp('^abc$')`.<br /> 25419 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 25420 * start at the index of the last search's match, thus not taking the whole input value into 25421 * account. 25422 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user 25423 * interaction with the input element. 25424 * @param {boolean=} [ngTrim=true] If set to false AngularJS will not automatically trim the input. 25425 * This parameter is ignored for input[type=password] controls, which will never trim the 25426 * input. 25427 * 25428 * @example 25429 <example name="text-input-directive" module="textInputExample"> 25430 <file name="index.html"> 25431 <script> 25432 angular.module('textInputExample', []) 25433 .controller('ExampleController', ['$scope', function($scope) { 25434 $scope.example = { 25435 text: 'guest', 25436 word: /^\s*\w*\s*$/ 25437 }; 25438 }]); 25439 </script> 25440 <form name="myForm" ng-controller="ExampleController"> 25441 <label>Single word: 25442 <input type="text" name="input" ng-model="example.text" 25443 ng-pattern="example.word" required ng-trim="false"> 25444 </label> 25445 <div role="alert"> 25446 <span class="error" ng-show="myForm.input.$error.required"> 25447 Required!</span> 25448 <span class="error" ng-show="myForm.input.$error.pattern"> 25449 Single word only!</span> 25450 </div> 25451 <code>text = {{example.text}}</code><br/> 25452 <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br/> 25453 <code>myForm.input.$error = {{myForm.input.$error}}</code><br/> 25454 <code>myForm.$valid = {{myForm.$valid}}</code><br/> 25455 <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br/> 25456 </form> 25457 </file> 25458 <file name="protractor.js" type="protractor"> 25459 var text = element(by.binding('example.text')); 25460 var valid = element(by.binding('myForm.input.$valid')); 25461 var input = element(by.model('example.text')); 25462 25463 it('should initialize to model', function() { 25464 expect(text.getText()).toContain('guest'); 25465 expect(valid.getText()).toContain('true'); 25466 }); 25467 25468 it('should be invalid if empty', function() { 25469 input.clear(); 25470 input.sendKeys(''); 25471 25472 expect(text.getText()).toEqual('text ='); 25473 expect(valid.getText()).toContain('false'); 25474 }); 25475 25476 it('should be invalid if multi word', function() { 25477 input.clear(); 25478 input.sendKeys('hello world'); 25479 25480 expect(valid.getText()).toContain('false'); 25481 }); 25482 </file> 25483 </example> 25484 */ 25485 'text': textInputType, 25486 25487 /** 25488 * @ngdoc input 25489 * @name input[date] 25490 * 25491 * @description 25492 * Input with date validation and transformation. In browsers that do not yet support 25493 * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601 25494 * date format (yyyy-MM-dd), for example: `2009-01-06`. Since many 25495 * modern browsers do not yet support this input type, it is important to provide cues to users on the 25496 * expected input format via a placeholder or label. 25497 * 25498 * The model must always be a Date object, otherwise AngularJS will throw an error. 25499 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 25500 * 25501 * The timezone to be used to read/write the `Date` instance in the model can be defined using 25502 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 25503 * 25504 * @param {string} ngModel Assignable AngularJS expression to data-bind to. 25505 * @param {string=} name Property name of the form under which the control is published. 25506 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a 25507 * valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute 25508 * (e.g. `min="{{minDate | date:'yyyy-MM-dd'}}"`). Note that `min` will also add native HTML5 25509 * constraint validation. 25510 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be 25511 * a valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute 25512 * (e.g. `max="{{maxDate | date:'yyyy-MM-dd'}}"`). Note that `max` will also add native HTML5 25513 * constraint validation. 25514 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO date string 25515 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 25516 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO date string 25517 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 25518 * @param {string=} required Sets `required` validation error key if the value is not entered. 25519 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 25520 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 25521 * `required` when you want to data-bind to the `required` attribute. 25522 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user 25523 * interaction with the input element. 25524 * 25525 * @example 25526 <example name="date-input-directive" module="dateInputExample"> 25527 <file name="index.html"> 25528 <script> 25529 angular.module('dateInputExample', []) 25530 .controller('DateController', ['$scope', function($scope) { 25531 $scope.example = { 25532 value: new Date(2013, 9, 22) 25533 }; 25534 }]); 25535 </script> 25536 <form name="myForm" ng-controller="DateController as dateCtrl"> 25537 <label for="exampleInput">Pick a date in 2013:</label> 25538 <input type="date" id="exampleInput" name="input" ng-model="example.value" 25539 placeholder="yyyy-MM-dd" min="2013-01-01" max="2013-12-31" required /> 25540 <div role="alert"> 25541 <span class="error" ng-show="myForm.input.$error.required"> 25542 Required!</span> 25543 <span class="error" ng-show="myForm.input.$error.date"> 25544 Not a valid date!</span> 25545 </div> 25546 <tt>value = {{example.value | date: "yyyy-MM-dd"}}</tt><br/> 25547 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 25548 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 25549 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 25550 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 25551 </form> 25552 </file> 25553 <file name="protractor.js" type="protractor"> 25554 var value = element(by.binding('example.value | date: "yyyy-MM-dd"')); 25555 var valid = element(by.binding('myForm.input.$valid')); 25556 25557 // currently protractor/webdriver does not support 25558 // sending keys to all known HTML5 input controls 25559 // for various browsers (see https://github.com/angular/protractor/issues/562). 25560 function setInput(val) { 25561 // set the value of the element and force validation. 25562 var scr = "var ipt = document.getElementById('exampleInput'); " + 25563 "ipt.value = '" + val + "';" + 25564 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 25565 browser.executeScript(scr); 25566 } 25567 25568 it('should initialize to model', function() { 25569 expect(value.getText()).toContain('2013-10-22'); 25570 expect(valid.getText()).toContain('myForm.input.$valid = true'); 25571 }); 25572 25573 it('should be invalid if empty', function() { 25574 setInput(''); 25575 expect(value.getText()).toEqual('value ='); 25576 expect(valid.getText()).toContain('myForm.input.$valid = false'); 25577 }); 25578 25579 it('should be invalid if over max', function() { 25580 setInput('2015-01-01'); 25581 expect(value.getText()).toContain(''); 25582 expect(valid.getText()).toContain('myForm.input.$valid = false'); 25583 }); 25584 </file> 25585 </example> 25586 */ 25587 'date': createDateInputType('date', DATE_REGEXP, 25588 createDateParser(DATE_REGEXP, ['yyyy', 'MM', 'dd']), 25589 'yyyy-MM-dd'), 25590 25591 /** 25592 * @ngdoc input 25593 * @name input[datetime-local] 25594 * 25595 * @description 25596 * Input with datetime validation and transformation. In browsers that do not yet support 25597 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 25598 * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`. 25599 * 25600 * The model must always be a Date object, otherwise AngularJS will throw an error. 25601 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 25602 * 25603 * The timezone to be used to read/write the `Date` instance in the model can be defined using 25604 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 25605 * 25606 * The format of the displayed time can be adjusted with the 25607 * {@link ng.directive:ngModelOptions#ngModelOptions-arguments ngModelOptions} `timeSecondsFormat` 25608 * and `timeStripZeroSeconds`. 25609 * 25610 * @param {string} ngModel Assignable AngularJS expression to data-bind to. 25611 * @param {string=} name Property name of the form under which the control is published. 25612 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 25613 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation 25614 * inside this attribute (e.g. `min="{{minDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`). 25615 * Note that `min` will also add native HTML5 constraint validation. 25616 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 25617 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation 25618 * inside this attribute (e.g. `max="{{maxDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`). 25619 * Note that `max` will also add native HTML5 constraint validation. 25620 * @param {(date|string)=} ngMin Sets the `min` validation error key to the Date / ISO datetime string 25621 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 25622 * @param {(date|string)=} ngMax Sets the `max` validation error key to the Date / ISO datetime string 25623 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 25624 * @param {string=} required Sets `required` validation error key if the value is not entered. 25625 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 25626 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 25627 * `required` when you want to data-bind to the `required` attribute. 25628 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user 25629 * interaction with the input element. 25630 * 25631 * @example 25632 <example name="datetimelocal-input-directive" module="dateExample"> 25633 <file name="index.html"> 25634 <script> 25635 angular.module('dateExample', []) 25636 .controller('DateController', ['$scope', function($scope) { 25637 $scope.example = { 25638 value: new Date(2010, 11, 28, 14, 57) 25639 }; 25640 }]); 25641 </script> 25642 <form name="myForm" ng-controller="DateController as dateCtrl"> 25643 <label for="exampleInput">Pick a date between in 2013:</label> 25644 <input type="datetime-local" id="exampleInput" name="input" ng-model="example.value" 25645 placeholder="yyyy-MM-ddTHH:mm:ss" min="2001-01-01T00:00:00" max="2013-12-31T00:00:00" required /> 25646 <div role="alert"> 25647 <span class="error" ng-show="myForm.input.$error.required"> 25648 Required!</span> 25649 <span class="error" ng-show="myForm.input.$error.datetimelocal"> 25650 Not a valid date!</span> 25651 </div> 25652 <tt>value = {{example.value | date: "yyyy-MM-ddTHH:mm:ss"}}</tt><br/> 25653 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 25654 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 25655 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 25656 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 25657 </form> 25658 </file> 25659 <file name="protractor.js" type="protractor"> 25660 var value = element(by.binding('example.value | date: "yyyy-MM-ddTHH:mm:ss"')); 25661 var valid = element(by.binding('myForm.input.$valid')); 25662 25663 // currently protractor/webdriver does not support 25664 // sending keys to all known HTML5 input controls 25665 // for various browsers (https://github.com/angular/protractor/issues/562). 25666 function setInput(val) { 25667 // set the value of the element and force validation. 25668 var scr = "var ipt = document.getElementById('exampleInput'); " + 25669 "ipt.value = '" + val + "';" + 25670 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 25671 browser.executeScript(scr); 25672 } 25673 25674 it('should initialize to model', function() { 25675 expect(value.getText()).toContain('2010-12-28T14:57:00'); 25676 expect(valid.getText()).toContain('myForm.input.$valid = true'); 25677 }); 25678 25679 it('should be invalid if empty', function() { 25680 setInput(''); 25681 expect(value.getText()).toEqual('value ='); 25682 expect(valid.getText()).toContain('myForm.input.$valid = false'); 25683 }); 25684 25685 it('should be invalid if over max', function() { 25686 setInput('2015-01-01T23:59:00'); 25687 expect(value.getText()).toContain(''); 25688 expect(valid.getText()).toContain('myForm.input.$valid = false'); 25689 }); 25690 </file> 25691 </example> 25692 */ 25693 'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP, 25694 createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss', 'sss']), 25695 'yyyy-MM-ddTHH:mm:ss.sss'), 25696 25697 /** 25698 * @ngdoc input 25699 * @name input[time] 25700 * 25701 * @description 25702 * Input with time validation and transformation. In browsers that do not yet support 25703 * the HTML5 time input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 25704 * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a 25705 * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`. 25706 * 25707 * The model must always be a Date object, otherwise AngularJS will throw an error. 25708 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 25709 * 25710 * The timezone to be used to read/write the `Date` instance in the model can be defined using 25711 * {@link ng.directive:ngModelOptions#ngModelOptions-arguments ngModelOptions}. By default, 25712 * this is the timezone of the browser. 25713 * 25714 * The format of the displayed time can be adjusted with the 25715 * {@link ng.directive:ngModelOptions#ngModelOptions-arguments ngModelOptions} `timeSecondsFormat` 25716 * and `timeStripZeroSeconds`. 25717 * 25718 * @param {string} ngModel Assignable AngularJS expression to data-bind to. 25719 * @param {string=} name Property name of the form under which the control is published. 25720 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 25721 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this 25722 * attribute (e.g. `min="{{minTime | date:'HH:mm:ss'}}"`). Note that `min` will also add 25723 * native HTML5 constraint validation. 25724 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 25725 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this 25726 * attribute (e.g. `max="{{maxTime | date:'HH:mm:ss'}}"`). Note that `max` will also add 25727 * native HTML5 constraint validation. 25728 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO time string the 25729 * `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 25730 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO time string the 25731 * `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 25732 * @param {string=} required Sets `required` validation error key if the value is not entered. 25733 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 25734 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 25735 * `required` when you want to data-bind to the `required` attribute. 25736 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user 25737 * interaction with the input element. 25738 * 25739 * @example 25740 <example name="time-input-directive" module="timeExample"> 25741 <file name="index.html"> 25742 <script> 25743 angular.module('timeExample', []) 25744 .controller('DateController', ['$scope', function($scope) { 25745 $scope.example = { 25746 value: new Date(1970, 0, 1, 14, 57, 0) 25747 }; 25748 }]); 25749 </script> 25750 <form name="myForm" ng-controller="DateController as dateCtrl"> 25751 <label for="exampleInput">Pick a time between 8am and 5pm:</label> 25752 <input type="time" id="exampleInput" name="input" ng-model="example.value" 25753 placeholder="HH:mm:ss" min="08:00:00" max="17:00:00" required /> 25754 <div role="alert"> 25755 <span class="error" ng-show="myForm.input.$error.required"> 25756 Required!</span> 25757 <span class="error" ng-show="myForm.input.$error.time"> 25758 Not a valid date!</span> 25759 </div> 25760 <tt>value = {{example.value | date: "HH:mm:ss"}}</tt><br/> 25761 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 25762 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 25763 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 25764 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 25765 </form> 25766 </file> 25767 <file name="protractor.js" type="protractor"> 25768 var value = element(by.binding('example.value | date: "HH:mm:ss"')); 25769 var valid = element(by.binding('myForm.input.$valid')); 25770 25771 // currently protractor/webdriver does not support 25772 // sending keys to all known HTML5 input controls 25773 // for various browsers (https://github.com/angular/protractor/issues/562). 25774 function setInput(val) { 25775 // set the value of the element and force validation. 25776 var scr = "var ipt = document.getElementById('exampleInput'); " + 25777 "ipt.value = '" + val + "';" + 25778 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 25779 browser.executeScript(scr); 25780 } 25781 25782 it('should initialize to model', function() { 25783 expect(value.getText()).toContain('14:57:00'); 25784 expect(valid.getText()).toContain('myForm.input.$valid = true'); 25785 }); 25786 25787 it('should be invalid if empty', function() { 25788 setInput(''); 25789 expect(value.getText()).toEqual('value ='); 25790 expect(valid.getText()).toContain('myForm.input.$valid = false'); 25791 }); 25792 25793 it('should be invalid if over max', function() { 25794 setInput('23:59:00'); 25795 expect(value.getText()).toContain(''); 25796 expect(valid.getText()).toContain('myForm.input.$valid = false'); 25797 }); 25798 </file> 25799 </example> 25800 */ 25801 'time': createDateInputType('time', TIME_REGEXP, 25802 createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss', 'sss']), 25803 'HH:mm:ss.sss'), 25804 25805 /** 25806 * @ngdoc input 25807 * @name input[week] 25808 * 25809 * @description 25810 * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support 25811 * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 25812 * week format (yyyy-W##), for example: `2013-W02`. 25813 * 25814 * The model must always be a Date object, otherwise AngularJS will throw an error. 25815 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 25816 * 25817 * The value of the resulting Date object will be set to Thursday at 00:00:00 of the requested week, 25818 * due to ISO-8601 week numbering standards. Information on ISO's system for numbering the weeks of the 25819 * year can be found at: https://en.wikipedia.org/wiki/ISO_8601#Week_dates 25820 * 25821 * The timezone to be used to read/write the `Date` instance in the model can be defined using 25822 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 25823 * 25824 * @param {string} ngModel Assignable AngularJS expression to data-bind to. 25825 * @param {string=} name Property name of the form under which the control is published. 25826 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 25827 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this 25828 * attribute (e.g. `min="{{minWeek | date:'yyyy-Www'}}"`). Note that `min` will also add 25829 * native HTML5 constraint validation. 25830 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 25831 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this 25832 * attribute (e.g. `max="{{maxWeek | date:'yyyy-Www'}}"`). Note that `max` will also add 25833 * native HTML5 constraint validation. 25834 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string 25835 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 25836 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string 25837 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 25838 * @param {string=} required Sets `required` validation error key if the value is not entered. 25839 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 25840 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 25841 * `required` when you want to data-bind to the `required` attribute. 25842 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user 25843 * interaction with the input element. 25844 * 25845 * @example 25846 <example name="week-input-directive" module="weekExample"> 25847 <file name="index.html"> 25848 <script> 25849 angular.module('weekExample', []) 25850 .controller('DateController', ['$scope', function($scope) { 25851 $scope.example = { 25852 value: new Date(2013, 0, 3) 25853 }; 25854 }]); 25855 </script> 25856 <form name="myForm" ng-controller="DateController as dateCtrl"> 25857 <label>Pick a date between in 2013: 25858 <input id="exampleInput" type="week" name="input" ng-model="example.value" 25859 placeholder="YYYY-W##" min="2012-W32" 25860 max="2013-W52" required /> 25861 </label> 25862 <div role="alert"> 25863 <span class="error" ng-show="myForm.input.$error.required"> 25864 Required!</span> 25865 <span class="error" ng-show="myForm.input.$error.week"> 25866 Not a valid date!</span> 25867 </div> 25868 <tt>value = {{example.value | date: "yyyy-Www"}}</tt><br/> 25869 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 25870 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 25871 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 25872 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 25873 </form> 25874 </file> 25875 <file name="protractor.js" type="protractor"> 25876 var value = element(by.binding('example.value | date: "yyyy-Www"')); 25877 var valid = element(by.binding('myForm.input.$valid')); 25878 25879 // currently protractor/webdriver does not support 25880 // sending keys to all known HTML5 input controls 25881 // for various browsers (https://github.com/angular/protractor/issues/562). 25882 function setInput(val) { 25883 // set the value of the element and force validation. 25884 var scr = "var ipt = document.getElementById('exampleInput'); " + 25885 "ipt.value = '" + val + "';" + 25886 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 25887 browser.executeScript(scr); 25888 } 25889 25890 it('should initialize to model', function() { 25891 expect(value.getText()).toContain('2013-W01'); 25892 expect(valid.getText()).toContain('myForm.input.$valid = true'); 25893 }); 25894 25895 it('should be invalid if empty', function() { 25896 setInput(''); 25897 expect(value.getText()).toEqual('value ='); 25898 expect(valid.getText()).toContain('myForm.input.$valid = false'); 25899 }); 25900 25901 it('should be invalid if over max', function() { 25902 setInput('2015-W01'); 25903 expect(value.getText()).toContain(''); 25904 expect(valid.getText()).toContain('myForm.input.$valid = false'); 25905 }); 25906 </file> 25907 </example> 25908 */ 25909 'week': createDateInputType('week', WEEK_REGEXP, weekParser, 'yyyy-Www'), 25910 25911 /** 25912 * @ngdoc input 25913 * @name input[month] 25914 * 25915 * @description 25916 * Input with month validation and transformation. In browsers that do not yet support 25917 * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 25918 * month format (yyyy-MM), for example: `2009-01`. 25919 * 25920 * The model must always be a Date object, otherwise AngularJS will throw an error. 25921 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 25922 * If the model is not set to the first of the month, the next view to model update will set it 25923 * to the first of the month. 25924 * 25925 * The timezone to be used to read/write the `Date` instance in the model can be defined using 25926 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 25927 * 25928 * @param {string} ngModel Assignable AngularJS expression to data-bind to. 25929 * @param {string=} name Property name of the form under which the control is published. 25930 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 25931 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this 25932 * attribute (e.g. `min="{{minMonth | date:'yyyy-MM'}}"`). Note that `min` will also add 25933 * native HTML5 constraint validation. 25934 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 25935 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this 25936 * attribute (e.g. `max="{{maxMonth | date:'yyyy-MM'}}"`). Note that `max` will also add 25937 * native HTML5 constraint validation. 25938 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string 25939 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 25940 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string 25941 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 25942 25943 * @param {string=} required Sets `required` validation error key if the value is not entered. 25944 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 25945 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 25946 * `required` when you want to data-bind to the `required` attribute. 25947 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user 25948 * interaction with the input element. 25949 * 25950 * @example 25951 <example name="month-input-directive" module="monthExample"> 25952 <file name="index.html"> 25953 <script> 25954 angular.module('monthExample', []) 25955 .controller('DateController', ['$scope', function($scope) { 25956 $scope.example = { 25957 value: new Date(2013, 9, 1) 25958 }; 25959 }]); 25960 </script> 25961 <form name="myForm" ng-controller="DateController as dateCtrl"> 25962 <label for="exampleInput">Pick a month in 2013:</label> 25963 <input id="exampleInput" type="month" name="input" ng-model="example.value" 25964 placeholder="yyyy-MM" min="2013-01" max="2013-12" required /> 25965 <div role="alert"> 25966 <span class="error" ng-show="myForm.input.$error.required"> 25967 Required!</span> 25968 <span class="error" ng-show="myForm.input.$error.month"> 25969 Not a valid month!</span> 25970 </div> 25971 <tt>value = {{example.value | date: "yyyy-MM"}}</tt><br/> 25972 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 25973 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 25974 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 25975 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 25976 </form> 25977 </file> 25978 <file name="protractor.js" type="protractor"> 25979 var value = element(by.binding('example.value | date: "yyyy-MM"')); 25980 var valid = element(by.binding('myForm.input.$valid')); 25981 25982 // currently protractor/webdriver does not support 25983 // sending keys to all known HTML5 input controls 25984 // for various browsers (https://github.com/angular/protractor/issues/562). 25985 function setInput(val) { 25986 // set the value of the element and force validation. 25987 var scr = "var ipt = document.getElementById('exampleInput'); " + 25988 "ipt.value = '" + val + "';" + 25989 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 25990 browser.executeScript(scr); 25991 } 25992 25993 it('should initialize to model', function() { 25994 expect(value.getText()).toContain('2013-10'); 25995 expect(valid.getText()).toContain('myForm.input.$valid = true'); 25996 }); 25997 25998 it('should be invalid if empty', function() { 25999 setInput(''); 26000 expect(value.getText()).toEqual('value ='); 26001 expect(valid.getText()).toContain('myForm.input.$valid = false'); 26002 }); 26003 26004 it('should be invalid if over max', function() { 26005 setInput('2015-01'); 26006 expect(value.getText()).toContain(''); 26007 expect(valid.getText()).toContain('myForm.input.$valid = false'); 26008 }); 26009 </file> 26010 </example> 26011 */ 26012 'month': createDateInputType('month', MONTH_REGEXP, 26013 createDateParser(MONTH_REGEXP, ['yyyy', 'MM']), 26014 'yyyy-MM'), 26015 26016 /** 26017 * @ngdoc input 26018 * @name input[number] 26019 * 26020 * @description 26021 * Text input with number validation and transformation. Sets the `number` validation 26022 * error if not a valid number. 26023 * 26024 * <div class="alert alert-warning"> 26025 * The model must always be of type `number` otherwise AngularJS will throw an error. 26026 * Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt} 26027 * error docs for more information and an example of how to convert your model if necessary. 26028 * </div> 26029 * 26030 * 26031 * 26032 * @knownIssue 26033 * 26034 * ### HTML5 constraint validation and `allowInvalid` 26035 * 26036 * In browsers that follow the 26037 * [HTML5 specification](https://html.spec.whatwg.org/multipage/forms.html#number-state-%28type=number%29), 26038 * `input[number]` does not work as expected with {@link ngModelOptions `ngModelOptions.allowInvalid`}. 26039 * If a non-number is entered in the input, the browser will report the value as an empty string, 26040 * which means the view / model values in `ngModel` and subsequently the scope value 26041 * will also be an empty string. 26042 * 26043 * @knownIssue 26044 * 26045 * ### Large numbers and `step` validation 26046 * 26047 * The `step` validation will not work correctly for very large numbers (e.g. 9999999999) due to 26048 * Javascript's arithmetic limitations. If you need to handle large numbers, purpose-built 26049 * libraries (e.g. https://github.com/MikeMcl/big.js/), can be included into AngularJS by 26050 * {@link guide/forms#modifying-built-in-validators overwriting the validators} 26051 * for `number` and / or `step`, or by {@link guide/forms#custom-validation applying custom validators} 26052 * to an `input[text]` element. The source for `input[number]` type can be used as a starting 26053 * point for both implementations. 26054 * 26055 * @param {string} ngModel Assignable AngularJS expression to data-bind to. 26056 * @param {string=} name Property name of the form under which the control is published. 26057 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 26058 * Can be interpolated. 26059 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 26060 * Can be interpolated. 26061 * @param {string=} ngMin Like `min`, sets the `min` validation error key if the value entered is less than `ngMin`, 26062 * but does not trigger HTML5 native validation. Takes an expression. 26063 * @param {string=} ngMax Like `max`, sets the `max` validation error key if the value entered is greater than `ngMax`, 26064 * but does not trigger HTML5 native validation. Takes an expression. 26065 * @param {string=} step Sets the `step` validation error key if the value entered does not fit the `step` constraint. 26066 * Can be interpolated. 26067 * @param {string=} ngStep Like `step`, sets the `step` validation error key if the value entered does not fit the `ngStep` constraint, 26068 * but does not trigger HTML5 native validation. Takes an expression. 26069 * @param {string=} required Sets `required` validation error key if the value is not entered. 26070 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 26071 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 26072 * `required` when you want to data-bind to the `required` attribute. 26073 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 26074 * minlength. 26075 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 26076 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 26077 * any length. 26078 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 26079 * that contains the regular expression body that will be converted to a regular expression 26080 * as in the ngPattern directive. 26081 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 26082 * does not match a RegExp found by evaluating the AngularJS expression given in the attribute value. 26083 * If the expression evaluates to a RegExp object, then this is used directly. 26084 * If the expression evaluates to a string, then it will be converted to a RegExp 26085 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 26086 * `new RegExp('^abc$')`.<br /> 26087 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 26088 * start at the index of the last search's match, thus not taking the whole input value into 26089 * account. 26090 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user 26091 * interaction with the input element. 26092 * 26093 * @example 26094 <example name="number-input-directive" module="numberExample"> 26095 <file name="index.html"> 26096 <script> 26097 angular.module('numberExample', []) 26098 .controller('ExampleController', ['$scope', function($scope) { 26099 $scope.example = { 26100 value: 12 26101 }; 26102 }]); 26103 </script> 26104 <form name="myForm" ng-controller="ExampleController"> 26105 <label>Number: 26106 <input type="number" name="input" ng-model="example.value" 26107 min="0" max="99" required> 26108 </label> 26109 <div role="alert"> 26110 <span class="error" ng-show="myForm.input.$error.required"> 26111 Required!</span> 26112 <span class="error" ng-show="myForm.input.$error.number"> 26113 Not valid number!</span> 26114 </div> 26115 <tt>value = {{example.value}}</tt><br/> 26116 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 26117 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 26118 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 26119 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 26120 </form> 26121 </file> 26122 <file name="protractor.js" type="protractor"> 26123 var value = element(by.binding('example.value')); 26124 var valid = element(by.binding('myForm.input.$valid')); 26125 var input = element(by.model('example.value')); 26126 26127 it('should initialize to model', function() { 26128 expect(value.getText()).toContain('12'); 26129 expect(valid.getText()).toContain('true'); 26130 }); 26131 26132 it('should be invalid if empty', function() { 26133 input.clear(); 26134 input.sendKeys(''); 26135 expect(value.getText()).toEqual('value ='); 26136 expect(valid.getText()).toContain('false'); 26137 }); 26138 26139 it('should be invalid if over max', function() { 26140 input.clear(); 26141 input.sendKeys('123'); 26142 expect(value.getText()).toEqual('value ='); 26143 expect(valid.getText()).toContain('false'); 26144 }); 26145 </file> 26146 </example> 26147 */ 26148 'number': numberInputType, 26149 26150 26151 /** 26152 * @ngdoc input 26153 * @name input[url] 26154 * 26155 * @description 26156 * Text input with URL validation. Sets the `url` validation error key if the content is not a 26157 * valid URL. 26158 * 26159 * <div class="alert alert-warning"> 26160 * **Note:** `input[url]` uses a regex to validate urls that is derived from the regex 26161 * used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify 26162 * the built-in validators (see the {@link guide/forms Forms guide}) 26163 * </div> 26164 * 26165 * @param {string} ngModel Assignable AngularJS expression to data-bind to. 26166 * @param {string=} name Property name of the form under which the control is published. 26167 * @param {string=} required Sets `required` validation error key if the value is not entered. 26168 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 26169 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 26170 * `required` when you want to data-bind to the `required` attribute. 26171 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 26172 * minlength. 26173 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 26174 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 26175 * any length. 26176 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 26177 * that contains the regular expression body that will be converted to a regular expression 26178 * as in the ngPattern directive. 26179 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 26180 * does not match a RegExp found by evaluating the AngularJS expression given in the attribute value. 26181 * If the expression evaluates to a RegExp object, then this is used directly. 26182 * If the expression evaluates to a string, then it will be converted to a RegExp 26183 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 26184 * `new RegExp('^abc$')`.<br /> 26185 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 26186 * start at the index of the last search's match, thus not taking the whole input value into 26187 * account. 26188 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user 26189 * interaction with the input element. 26190 * 26191 * @example 26192 <example name="url-input-directive" module="urlExample"> 26193 <file name="index.html"> 26194 <script> 26195 angular.module('urlExample', []) 26196 .controller('ExampleController', ['$scope', function($scope) { 26197 $scope.url = { 26198 text: 'http://google.com' 26199 }; 26200 }]); 26201 </script> 26202 <form name="myForm" ng-controller="ExampleController"> 26203 <label>URL: 26204 <input type="url" name="input" ng-model="url.text" required> 26205 <label> 26206 <div role="alert"> 26207 <span class="error" ng-show="myForm.input.$error.required"> 26208 Required!</span> 26209 <span class="error" ng-show="myForm.input.$error.url"> 26210 Not valid url!</span> 26211 </div> 26212 <tt>text = {{url.text}}</tt><br/> 26213 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 26214 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 26215 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 26216 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 26217 <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/> 26218 </form> 26219 </file> 26220 <file name="protractor.js" type="protractor"> 26221 var text = element(by.binding('url.text')); 26222 var valid = element(by.binding('myForm.input.$valid')); 26223 var input = element(by.model('url.text')); 26224 26225 it('should initialize to model', function() { 26226 expect(text.getText()).toContain('http://google.com'); 26227 expect(valid.getText()).toContain('true'); 26228 }); 26229 26230 it('should be invalid if empty', function() { 26231 input.clear(); 26232 input.sendKeys(''); 26233 26234 expect(text.getText()).toEqual('text ='); 26235 expect(valid.getText()).toContain('false'); 26236 }); 26237 26238 it('should be invalid if not url', function() { 26239 input.clear(); 26240 input.sendKeys('box'); 26241 26242 expect(valid.getText()).toContain('false'); 26243 }); 26244 </file> 26245 </example> 26246 */ 26247 'url': urlInputType, 26248 26249 26250 /** 26251 * @ngdoc input 26252 * @name input[email] 26253 * 26254 * @description 26255 * Text input with email validation. Sets the `email` validation error key if not a valid email 26256 * address. 26257 * 26258 * <div class="alert alert-warning"> 26259 * **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex 26260 * used in Chromium, which may not fulfill your app's requirements. 26261 * If you need stricter (e.g. requiring a top-level domain), or more relaxed validation 26262 * (e.g. allowing IPv6 address literals) you can use `ng-pattern` or 26263 * modify the built-in validators (see the {@link guide/forms Forms guide}). 26264 * </div> 26265 * 26266 * @param {string} ngModel Assignable AngularJS expression to data-bind to. 26267 * @param {string=} name Property name of the form under which the control is published. 26268 * @param {string=} required Sets `required` validation error key if the value is not entered. 26269 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 26270 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 26271 * `required` when you want to data-bind to the `required` attribute. 26272 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 26273 * minlength. 26274 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 26275 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 26276 * any length. 26277 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 26278 * that contains the regular expression body that will be converted to a regular expression 26279 * as in the ngPattern directive. 26280 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 26281 * does not match a RegExp found by evaluating the AngularJS expression given in the attribute value. 26282 * If the expression evaluates to a RegExp object, then this is used directly. 26283 * If the expression evaluates to a string, then it will be converted to a RegExp 26284 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 26285 * `new RegExp('^abc$')`.<br /> 26286 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 26287 * start at the index of the last search's match, thus not taking the whole input value into 26288 * account. 26289 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user 26290 * interaction with the input element. 26291 * 26292 * @example 26293 <example name="email-input-directive" module="emailExample"> 26294 <file name="index.html"> 26295 <script> 26296 angular.module('emailExample', []) 26297 .controller('ExampleController', ['$scope', function($scope) { 26298 $scope.email = { 26299 text: '[email protected]' 26300 }; 26301 }]); 26302 </script> 26303 <form name="myForm" ng-controller="ExampleController"> 26304 <label>Email: 26305 <input type="email" name="input" ng-model="email.text" required> 26306 </label> 26307 <div role="alert"> 26308 <span class="error" ng-show="myForm.input.$error.required"> 26309 Required!</span> 26310 <span class="error" ng-show="myForm.input.$error.email"> 26311 Not valid email!</span> 26312 </div> 26313 <tt>text = {{email.text}}</tt><br/> 26314 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 26315 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 26316 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 26317 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 26318 <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/> 26319 </form> 26320 </file> 26321 <file name="protractor.js" type="protractor"> 26322 var text = element(by.binding('email.text')); 26323 var valid = element(by.binding('myForm.input.$valid')); 26324 var input = element(by.model('email.text')); 26325 26326 it('should initialize to model', function() { 26327 expect(text.getText()).toContain('[email protected]'); 26328 expect(valid.getText()).toContain('true'); 26329 }); 26330 26331 it('should be invalid if empty', function() { 26332 input.clear(); 26333 input.sendKeys(''); 26334 expect(text.getText()).toEqual('text ='); 26335 expect(valid.getText()).toContain('false'); 26336 }); 26337 26338 it('should be invalid if not email', function() { 26339 input.clear(); 26340 input.sendKeys('xxx'); 26341 26342 expect(valid.getText()).toContain('false'); 26343 }); 26344 </file> 26345 </example> 26346 */ 26347 'email': emailInputType, 26348 26349 26350 /** 26351 * @ngdoc input 26352 * @name input[radio] 26353 * 26354 * @description 26355 * HTML radio button. 26356 * 26357 * **Note:**<br> 26358 * All inputs controlled by {@link ngModel ngModel} (including those of type `radio`) will use the 26359 * value of their `name` attribute to determine the property under which their 26360 * {@link ngModel.NgModelController NgModelController} will be published on the parent 26361 * {@link form.FormController FormController}. Thus, if you use the same `name` for multiple 26362 * inputs of a form (e.g. a group of radio inputs), only _one_ `NgModelController` will be 26363 * published on the parent `FormController` under that name. The rest of the controllers will 26364 * continue to work as expected, but you won't be able to access them as properties on the parent 26365 * `FormController`. 26366 * 26367 * <div class="alert alert-info"> 26368 * <p> 26369 * In plain HTML forms, the `name` attribute is used to identify groups of radio inputs, so 26370 * that the browser can manage their state (checked/unchecked) based on the state of other 26371 * inputs in the same group. 26372 * </p> 26373 * <p> 26374 * In AngularJS forms, this is not necessary. The input's state will be updated based on the 26375 * value of the underlying model data. 26376 * </p> 26377 * </div> 26378 * 26379 * <div class="alert alert-success"> 26380 * If you omit the `name` attribute on a radio input, `ngModel` will automatically assign it a 26381 * unique name. 26382 * </div> 26383 * 26384 * @param {string} ngModel Assignable AngularJS expression to data-bind to. 26385 * @param {string} value The value to which the `ngModel` expression should be set when selected. 26386 * Note that `value` only supports `string` values, i.e. the scope model needs to be a string, 26387 * too. Use `ngValue` if you need complex models (`number`, `object`, ...). 26388 * @param {string=} name Property name of the form under which the control is published. 26389 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user 26390 * interaction with the input element. 26391 * @param {string} ngValue AngularJS expression to which `ngModel` will be be set when the radio 26392 * is selected. Should be used instead of the `value` attribute if you need 26393 * a non-string `ngModel` (`boolean`, `array`, ...). 26394 * 26395 * @example 26396 <example name="radio-input-directive" module="radioExample"> 26397 <file name="index.html"> 26398 <script> 26399 angular.module('radioExample', []) 26400 .controller('ExampleController', ['$scope', function($scope) { 26401 $scope.color = { 26402 name: 'blue' 26403 }; 26404 $scope.specialValue = { 26405 "id": "12345", 26406 "value": "green" 26407 }; 26408 }]); 26409 </script> 26410 <form name="myForm" ng-controller="ExampleController"> 26411 <label> 26412 <input type="radio" ng-model="color.name" value="red"> 26413 Red 26414 </label><br/> 26415 <label> 26416 <input type="radio" ng-model="color.name" ng-value="specialValue"> 26417 Green 26418 </label><br/> 26419 <label> 26420 <input type="radio" ng-model="color.name" value="blue"> 26421 Blue 26422 </label><br/> 26423 <tt>color = {{color.name | json}}</tt><br/> 26424 </form> 26425 Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`. 26426 </file> 26427 <file name="protractor.js" type="protractor"> 26428 it('should change state', function() { 26429 var inputs = element.all(by.model('color.name')); 26430 var color = element(by.binding('color.name')); 26431 26432 expect(color.getText()).toContain('blue'); 26433 26434 inputs.get(0).click(); 26435 expect(color.getText()).toContain('red'); 26436 26437 inputs.get(1).click(); 26438 expect(color.getText()).toContain('green'); 26439 }); 26440 </file> 26441 </example> 26442 */ 26443 'radio': radioInputType, 26444 26445 /** 26446 * @ngdoc input 26447 * @name input[range] 26448 * 26449 * @description 26450 * Native range input with validation and transformation. 26451 * 26452 * The model for the range input must always be a `Number`. 26453 * 26454 * IE9 and other browsers that do not support the `range` type fall back 26455 * to a text input without any default values for `min`, `max` and `step`. Model binding, 26456 * validation and number parsing are nevertheless supported. 26457 * 26458 * Browsers that support range (latest Chrome, Safari, Firefox, Edge) treat `input[range]` 26459 * in a way that never allows the input to hold an invalid value. That means: 26460 * - any non-numerical value is set to `(max + min) / 2`. 26461 * - any numerical value that is less than the current min val, or greater than the current max val 26462 * is set to the min / max val respectively. 26463 * - additionally, the current `step` is respected, so the nearest value that satisfies a step 26464 * is used. 26465 * 26466 * See the [HTML Spec on input[type=range]](https://www.w3.org/TR/html5/forms.html#range-state-(type=range)) 26467 * for more info. 26468 * 26469 * This has the following consequences for AngularJS: 26470 * 26471 * Since the element value should always reflect the current model value, a range input 26472 * will set the bound ngModel expression to the value that the browser has set for the 26473 * input element. For example, in the following input `<input type="range" ng-model="model.value">`, 26474 * if the application sets `model.value = null`, the browser will set the input to `'50'`. 26475 * AngularJS will then set the model to `50`, to prevent input and model value being out of sync. 26476 * 26477 * That means the model for range will immediately be set to `50` after `ngModel` has been 26478 * initialized. It also means a range input can never have the required error. 26479 * 26480 * This does not only affect changes to the model value, but also to the values of the `min`, 26481 * `max`, and `step` attributes. When these change in a way that will cause the browser to modify 26482 * the input value, AngularJS will also update the model value. 26483 * 26484 * Automatic value adjustment also means that a range input element can never have the `required`, 26485 * `min`, or `max` errors. 26486 * 26487 * However, `step` is currently only fully implemented by Firefox. Other browsers have problems 26488 * when the step value changes dynamically - they do not adjust the element value correctly, but 26489 * instead may set the `stepMismatch` error. If that's the case, the AngularJS will set the `step` 26490 * error on the input, and set the model to `undefined`. 26491 * 26492 * Note that `input[range]` is not compatible with`ngMax`, `ngMin`, and `ngStep`, because they do 26493 * not set the `min` and `max` attributes, which means that the browser won't automatically adjust 26494 * the input value based on their values, and will always assume min = 0, max = 100, and step = 1. 26495 * 26496 * @param {string} ngModel Assignable AngularJS expression to data-bind to. 26497 * @param {string=} name Property name of the form under which the control is published. 26498 * @param {string=} min Sets the `min` validation to ensure that the value entered is greater 26499 * than `min`. Can be interpolated. 26500 * @param {string=} max Sets the `max` validation to ensure that the value entered is less than `max`. 26501 * Can be interpolated. 26502 * @param {string=} step Sets the `step` validation to ensure that the value entered matches the `step` 26503 * Can be interpolated. 26504 * @param {expression=} ngChange AngularJS expression to be executed when the ngModel value changes due 26505 * to user interaction with the input element. 26506 * @param {expression=} ngChecked If the expression is truthy, then the `checked` attribute will be set on the 26507 * element. **Note** : `ngChecked` should not be used alongside `ngModel`. 26508 * Checkout {@link ng.directive:ngChecked ngChecked} for usage. 26509 * 26510 * @example 26511 <example name="range-input-directive" module="rangeExample"> 26512 <file name="index.html"> 26513 <script> 26514 angular.module('rangeExample', []) 26515 .controller('ExampleController', ['$scope', function($scope) { 26516 $scope.value = 75; 26517 $scope.min = 10; 26518 $scope.max = 90; 26519 }]); 26520 </script> 26521 <form name="myForm" ng-controller="ExampleController"> 26522 26523 Model as range: <input type="range" name="range" ng-model="value" min="{{min}}" max="{{max}}"> 26524 <hr> 26525 Model as number: <input type="number" ng-model="value"><br> 26526 Min: <input type="number" ng-model="min"><br> 26527 Max: <input type="number" ng-model="max"><br> 26528 value = <code>{{value}}</code><br/> 26529 myForm.range.$valid = <code>{{myForm.range.$valid}}</code><br/> 26530 myForm.range.$error = <code>{{myForm.range.$error}}</code> 26531 </form> 26532 </file> 26533 </example> 26534 26535 * ## Range Input with ngMin & ngMax attributes 26536 26537 * @example 26538 <example name="range-input-directive-ng" module="rangeExample"> 26539 <file name="index.html"> 26540 <script> 26541 angular.module('rangeExample', []) 26542 .controller('ExampleController', ['$scope', function($scope) { 26543 $scope.value = 75; 26544 $scope.min = 10; 26545 $scope.max = 90; 26546 }]); 26547 </script> 26548 <form name="myForm" ng-controller="ExampleController"> 26549 Model as range: <input type="range" name="range" ng-model="value" ng-min="min" ng-max="max"> 26550 <hr> 26551 Model as number: <input type="number" ng-model="value"><br> 26552 Min: <input type="number" ng-model="min"><br> 26553 Max: <input type="number" ng-model="max"><br> 26554 value = <code>{{value}}</code><br/> 26555 myForm.range.$valid = <code>{{myForm.range.$valid}}</code><br/> 26556 myForm.range.$error = <code>{{myForm.range.$error}}</code> 26557 </form> 26558 </file> 26559 </example> 26560 26561 */ 26562 'range': rangeInputType, 26563 26564 /** 26565 * @ngdoc input 26566 * @name input[checkbox] 26567 * 26568 * @description 26569 * HTML checkbox. 26570 * 26571 * @param {string} ngModel Assignable AngularJS expression to data-bind to. 26572 * @param {string=} name Property name of the form under which the control is published. 26573 * @param {expression=} ngTrueValue The value to which the expression should be set when selected. 26574 * @param {expression=} ngFalseValue The value to which the expression should be set when not selected. 26575 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user 26576 * interaction with the input element. 26577 * 26578 * @example 26579 <example name="checkbox-input-directive" module="checkboxExample"> 26580 <file name="index.html"> 26581 <script> 26582 angular.module('checkboxExample', []) 26583 .controller('ExampleController', ['$scope', function($scope) { 26584 $scope.checkboxModel = { 26585 value1 : true, 26586 value2 : 'YES' 26587 }; 26588 }]); 26589 </script> 26590 <form name="myForm" ng-controller="ExampleController"> 26591 <label>Value1: 26592 <input type="checkbox" ng-model="checkboxModel.value1"> 26593 </label><br/> 26594 <label>Value2: 26595 <input type="checkbox" ng-model="checkboxModel.value2" 26596 ng-true-value="'YES'" ng-false-value="'NO'"> 26597 </label><br/> 26598 <tt>value1 = {{checkboxModel.value1}}</tt><br/> 26599 <tt>value2 = {{checkboxModel.value2}}</tt><br/> 26600 </form> 26601 </file> 26602 <file name="protractor.js" type="protractor"> 26603 it('should change state', function() { 26604 var value1 = element(by.binding('checkboxModel.value1')); 26605 var value2 = element(by.binding('checkboxModel.value2')); 26606 26607 expect(value1.getText()).toContain('true'); 26608 expect(value2.getText()).toContain('YES'); 26609 26610 element(by.model('checkboxModel.value1')).click(); 26611 element(by.model('checkboxModel.value2')).click(); 26612 26613 expect(value1.getText()).toContain('false'); 26614 expect(value2.getText()).toContain('NO'); 26615 }); 26616 </file> 26617 </example> 26618 */ 26619 'checkbox': checkboxInputType, 26620 26621 'hidden': noop, 26622 'button': noop, 26623 'submit': noop, 26624 'reset': noop, 26625 'file': noop 26626}; 26627 26628function stringBasedInputType(ctrl) { 26629 ctrl.$formatters.push(function(value) { 26630 return ctrl.$isEmpty(value) ? value : value.toString(); 26631 }); 26632} 26633 26634function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { 26635 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 26636 stringBasedInputType(ctrl); 26637} 26638 26639function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) { 26640 var type = lowercase(element[0].type); 26641 26642 // In composition mode, users are still inputting intermediate text buffer, 26643 // hold the listener until composition is done. 26644 // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent 26645 if (!$sniffer.android) { 26646 var composing = false; 26647 26648 element.on('compositionstart', function() { 26649 composing = true; 26650 }); 26651 26652 // Support: IE9+ 26653 element.on('compositionupdate', function(ev) { 26654 // End composition when ev.data is empty string on 'compositionupdate' event. 26655 // When the input de-focusses (e.g. by clicking away), IE triggers 'compositionupdate' 26656 // instead of 'compositionend'. 26657 if (isUndefined(ev.data) || ev.data === '') { 26658 composing = false; 26659 } 26660 }); 26661 26662 element.on('compositionend', function() { 26663 composing = false; 26664 listener(); 26665 }); 26666 } 26667 26668 var timeout; 26669 26670 var listener = function(ev) { 26671 if (timeout) { 26672 $browser.defer.cancel(timeout); 26673 timeout = null; 26674 } 26675 if (composing) return; 26676 var value = element.val(), 26677 event = ev && ev.type; 26678 26679 // By default we will trim the value 26680 // If the attribute ng-trim exists we will avoid trimming 26681 // If input type is 'password', the value is never trimmed 26682 if (type !== 'password' && (!attr.ngTrim || attr.ngTrim !== 'false')) { 26683 value = trim(value); 26684 } 26685 26686 // If a control is suffering from bad input (due to native validators), browsers discard its 26687 // value, so it may be necessary to revalidate (by calling $setViewValue again) even if the 26688 // control's value is the same empty value twice in a row. 26689 if (ctrl.$viewValue !== value || (value === '' && ctrl.$$hasNativeValidators)) { 26690 ctrl.$setViewValue(value, event); 26691 } 26692 }; 26693 26694 // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the 26695 // input event on backspace, delete or cut 26696 if ($sniffer.hasEvent('input')) { 26697 element.on('input', listener); 26698 } else { 26699 var deferListener = function(ev, input, origValue) { 26700 if (!timeout) { 26701 timeout = $browser.defer(function() { 26702 timeout = null; 26703 if (!input || input.value !== origValue) { 26704 listener(ev); 26705 } 26706 }); 26707 } 26708 }; 26709 26710 element.on('keydown', /** @this */ function(event) { 26711 var key = event.keyCode; 26712 26713 // ignore 26714 // command modifiers arrows 26715 if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; 26716 26717 deferListener(event, this, this.value); 26718 }); 26719 26720 // if user modifies input value using context menu in IE, we need "paste", "cut" and "drop" events to catch it 26721 if ($sniffer.hasEvent('paste')) { 26722 element.on('paste cut drop', deferListener); 26723 } 26724 } 26725 26726 // if user paste into input using mouse on older browser 26727 // or form autocomplete on newer browser, we need "change" event to catch it 26728 element.on('change', listener); 26729 26730 // Some native input types (date-family) have the ability to change validity without 26731 // firing any input/change events. 26732 // For these event types, when native validators are present and the browser supports the type, 26733 // check for validity changes on various DOM events. 26734 if (PARTIAL_VALIDATION_TYPES[type] && ctrl.$$hasNativeValidators && type === attr.type) { 26735 element.on(PARTIAL_VALIDATION_EVENTS, /** @this */ function(ev) { 26736 if (!timeout) { 26737 var validity = this[VALIDITY_STATE_PROPERTY]; 26738 var origBadInput = validity.badInput; 26739 var origTypeMismatch = validity.typeMismatch; 26740 timeout = $browser.defer(function() { 26741 timeout = null; 26742 if (validity.badInput !== origBadInput || validity.typeMismatch !== origTypeMismatch) { 26743 listener(ev); 26744 } 26745 }); 26746 } 26747 }); 26748 } 26749 26750 ctrl.$render = function() { 26751 // Workaround for Firefox validation #12102. 26752 var value = ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue; 26753 if (element.val() !== value) { 26754 element.val(value); 26755 } 26756 }; 26757} 26758 26759function weekParser(isoWeek, existingDate) { 26760 if (isDate(isoWeek)) { 26761 return isoWeek; 26762 } 26763 26764 if (isString(isoWeek)) { 26765 WEEK_REGEXP.lastIndex = 0; 26766 var parts = WEEK_REGEXP.exec(isoWeek); 26767 if (parts) { 26768 var year = +parts[1], 26769 week = +parts[2], 26770 hours = 0, 26771 minutes = 0, 26772 seconds = 0, 26773 milliseconds = 0, 26774 firstThurs = getFirstThursdayOfYear(year), 26775 addDays = (week - 1) * 7; 26776 26777 if (existingDate) { 26778 hours = existingDate.getHours(); 26779 minutes = existingDate.getMinutes(); 26780 seconds = existingDate.getSeconds(); 26781 milliseconds = existingDate.getMilliseconds(); 26782 } 26783 26784 return new Date(year, 0, firstThurs.getDate() + addDays, hours, minutes, seconds, milliseconds); 26785 } 26786 } 26787 26788 return NaN; 26789} 26790 26791function createDateParser(regexp, mapping) { 26792 return function(iso, previousDate) { 26793 var parts, map; 26794 26795 if (isDate(iso)) { 26796 return iso; 26797 } 26798 26799 if (isString(iso)) { 26800 // When a date is JSON'ified to wraps itself inside of an extra 26801 // set of double quotes. This makes the date parsing code unable 26802 // to match the date string and parse it as a date. 26803 if (iso.charAt(0) === '"' && iso.charAt(iso.length - 1) === '"') { 26804 iso = iso.substring(1, iso.length - 1); 26805 } 26806 if (ISO_DATE_REGEXP.test(iso)) { 26807 return new Date(iso); 26808 } 26809 regexp.lastIndex = 0; 26810 parts = regexp.exec(iso); 26811 26812 if (parts) { 26813 parts.shift(); 26814 if (previousDate) { 26815 map = { 26816 yyyy: previousDate.getFullYear(), 26817 MM: previousDate.getMonth() + 1, 26818 dd: previousDate.getDate(), 26819 HH: previousDate.getHours(), 26820 mm: previousDate.getMinutes(), 26821 ss: previousDate.getSeconds(), 26822 sss: previousDate.getMilliseconds() / 1000 26823 }; 26824 } else { 26825 map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 }; 26826 } 26827 26828 forEach(parts, function(part, index) { 26829 if (index < mapping.length) { 26830 map[mapping[index]] = +part; 26831 } 26832 }); 26833 26834 var date = new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss * 1000 || 0); 26835 if (map.yyyy < 100) { 26836 // In the constructor, 2-digit years map to 1900-1999. 26837 // Use `setFullYear()` to set the correct year. 26838 date.setFullYear(map.yyyy); 26839 } 26840 26841 return date; 26842 } 26843 } 26844 26845 return NaN; 26846 }; 26847} 26848 26849function createDateInputType(type, regexp, parseDate, format) { 26850 return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) { 26851 badInputChecker(scope, element, attr, ctrl, type); 26852 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 26853 26854 var isTimeType = type === 'time' || type === 'datetimelocal'; 26855 var previousDate; 26856 var previousTimezone; 26857 26858 ctrl.$parsers.push(function(value) { 26859 if (ctrl.$isEmpty(value)) return null; 26860 26861 if (regexp.test(value)) { 26862 // Note: We cannot read ctrl.$modelValue, as there might be a different 26863 // parser/formatter in the processing chain so that the model 26864 // contains some different data format! 26865 return parseDateAndConvertTimeZoneToLocal(value, previousDate); 26866 } 26867 ctrl.$$parserName = type; 26868 return undefined; 26869 }); 26870 26871 ctrl.$formatters.push(function(value) { 26872 if (value && !isDate(value)) { 26873 throw ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value); 26874 } 26875 if (isValidDate(value)) { 26876 previousDate = value; 26877 var timezone = ctrl.$options.getOption('timezone'); 26878 26879 if (timezone) { 26880 previousTimezone = timezone; 26881 previousDate = convertTimezoneToLocal(previousDate, timezone, true); 26882 } 26883 26884 return formatter(value, timezone); 26885 } else { 26886 previousDate = null; 26887 previousTimezone = null; 26888 return ''; 26889 } 26890 }); 26891 26892 if (isDefined(attr.min) || attr.ngMin) { 26893 var minVal = attr.min || $parse(attr.ngMin)(scope); 26894 var parsedMinVal = parseObservedDateValue(minVal); 26895 26896 ctrl.$validators.min = function(value) { 26897 return !isValidDate(value) || isUndefined(parsedMinVal) || parseDate(value) >= parsedMinVal; 26898 }; 26899 attr.$observe('min', function(val) { 26900 if (val !== minVal) { 26901 parsedMinVal = parseObservedDateValue(val); 26902 minVal = val; 26903 ctrl.$validate(); 26904 } 26905 }); 26906 } 26907 26908 if (isDefined(attr.max) || attr.ngMax) { 26909 var maxVal = attr.max || $parse(attr.ngMax)(scope); 26910 var parsedMaxVal = parseObservedDateValue(maxVal); 26911 26912 ctrl.$validators.max = function(value) { 26913 return !isValidDate(value) || isUndefined(parsedMaxVal) || parseDate(value) <= parsedMaxVal; 26914 }; 26915 attr.$observe('max', function(val) { 26916 if (val !== maxVal) { 26917 parsedMaxVal = parseObservedDateValue(val); 26918 maxVal = val; 26919 ctrl.$validate(); 26920 } 26921 }); 26922 } 26923 26924 function isValidDate(value) { 26925 // Invalid Date: getTime() returns NaN 26926 return value && !(value.getTime && value.getTime() !== value.getTime()); 26927 } 26928 26929 function parseObservedDateValue(val) { 26930 return isDefined(val) && !isDate(val) ? parseDateAndConvertTimeZoneToLocal(val) || undefined : val; 26931 } 26932 26933 function parseDateAndConvertTimeZoneToLocal(value, previousDate) { 26934 var timezone = ctrl.$options.getOption('timezone'); 26935 26936 if (previousTimezone && previousTimezone !== timezone) { 26937 // If the timezone has changed, adjust the previousDate to the default timezone 26938 // so that the new date is converted with the correct timezone offset 26939 previousDate = addDateMinutes(previousDate, timezoneToOffset(previousTimezone)); 26940 } 26941 26942 var parsedDate = parseDate(value, previousDate); 26943 26944 if (!isNaN(parsedDate) && timezone) { 26945 parsedDate = convertTimezoneToLocal(parsedDate, timezone); 26946 } 26947 return parsedDate; 26948 } 26949 26950 function formatter(value, timezone) { 26951 var targetFormat = format; 26952 26953 if (isTimeType && isString(ctrl.$options.getOption('timeSecondsFormat'))) { 26954 targetFormat = format 26955 .replace('ss.sss', ctrl.$options.getOption('timeSecondsFormat')) 26956 .replace(/:$/, ''); 26957 } 26958 26959 var formatted = $filter('date')(value, targetFormat, timezone); 26960 26961 if (isTimeType && ctrl.$options.getOption('timeStripZeroSeconds')) { 26962 formatted = formatted.replace(/(?::00)?(?:\.000)?$/, ''); 26963 } 26964 26965 return formatted; 26966 } 26967 }; 26968} 26969 26970function badInputChecker(scope, element, attr, ctrl, parserName) { 26971 var node = element[0]; 26972 var nativeValidation = ctrl.$$hasNativeValidators = isObject(node.validity); 26973 if (nativeValidation) { 26974 ctrl.$parsers.push(function(value) { 26975 var validity = element.prop(VALIDITY_STATE_PROPERTY) || {}; 26976 if (validity.badInput || validity.typeMismatch) { 26977 ctrl.$$parserName = parserName; 26978 return undefined; 26979 } 26980 26981 return value; 26982 }); 26983 } 26984} 26985 26986function numberFormatterParser(ctrl) { 26987 ctrl.$parsers.push(function(value) { 26988 if (ctrl.$isEmpty(value)) return null; 26989 if (NUMBER_REGEXP.test(value)) return parseFloat(value); 26990 26991 ctrl.$$parserName = 'number'; 26992 return undefined; 26993 }); 26994 26995 ctrl.$formatters.push(function(value) { 26996 if (!ctrl.$isEmpty(value)) { 26997 if (!isNumber(value)) { 26998 throw ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value); 26999 } 27000 value = value.toString(); 27001 } 27002 return value; 27003 }); 27004} 27005 27006function parseNumberAttrVal(val) { 27007 if (isDefined(val) && !isNumber(val)) { 27008 val = parseFloat(val); 27009 } 27010 return !isNumberNaN(val) ? val : undefined; 27011} 27012 27013function isNumberInteger(num) { 27014 // See http://stackoverflow.com/questions/14636536/how-to-check-if-a-variable-is-an-integer-in-javascript#14794066 27015 // (minus the assumption that `num` is a number) 27016 27017 // eslint-disable-next-line no-bitwise 27018 return (num | 0) === num; 27019} 27020 27021function countDecimals(num) { 27022 var numString = num.toString(); 27023 var decimalSymbolIndex = numString.indexOf('.'); 27024 27025 if (decimalSymbolIndex === -1) { 27026 if (-1 < num && num < 1) { 27027 // It may be in the exponential notation format (`1e-X`) 27028 var match = /e-(\d+)$/.exec(numString); 27029 27030 if (match) { 27031 return Number(match[1]); 27032 } 27033 } 27034 27035 return 0; 27036 } 27037 27038 return numString.length - decimalSymbolIndex - 1; 27039} 27040 27041function isValidForStep(viewValue, stepBase, step) { 27042 // At this point `stepBase` and `step` are expected to be non-NaN values 27043 // and `viewValue` is expected to be a valid stringified number. 27044 var value = Number(viewValue); 27045 27046 var isNonIntegerValue = !isNumberInteger(value); 27047 var isNonIntegerStepBase = !isNumberInteger(stepBase); 27048 var isNonIntegerStep = !isNumberInteger(step); 27049 27050 // Due to limitations in Floating Point Arithmetic (e.g. `0.3 - 0.2 !== 0.1` or 27051 // `0.5 % 0.1 !== 0`), we need to convert all numbers to integers. 27052 if (isNonIntegerValue || isNonIntegerStepBase || isNonIntegerStep) { 27053 var valueDecimals = isNonIntegerValue ? countDecimals(value) : 0; 27054 var stepBaseDecimals = isNonIntegerStepBase ? countDecimals(stepBase) : 0; 27055 var stepDecimals = isNonIntegerStep ? countDecimals(step) : 0; 27056 27057 var decimalCount = Math.max(valueDecimals, stepBaseDecimals, stepDecimals); 27058 var multiplier = Math.pow(10, decimalCount); 27059 27060 value = value * multiplier; 27061 stepBase = stepBase * multiplier; 27062 step = step * multiplier; 27063 27064 if (isNonIntegerValue) value = Math.round(value); 27065 if (isNonIntegerStepBase) stepBase = Math.round(stepBase); 27066 if (isNonIntegerStep) step = Math.round(step); 27067 } 27068 27069 return (value - stepBase) % step === 0; 27070} 27071 27072function numberInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) { 27073 badInputChecker(scope, element, attr, ctrl, 'number'); 27074 numberFormatterParser(ctrl); 27075 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 27076 27077 var parsedMinVal; 27078 27079 if (isDefined(attr.min) || attr.ngMin) { 27080 var minVal = attr.min || $parse(attr.ngMin)(scope); 27081 parsedMinVal = parseNumberAttrVal(minVal); 27082 27083 ctrl.$validators.min = function(modelValue, viewValue) { 27084 return ctrl.$isEmpty(viewValue) || isUndefined(parsedMinVal) || viewValue >= parsedMinVal; 27085 }; 27086 27087 attr.$observe('min', function(val) { 27088 if (val !== minVal) { 27089 parsedMinVal = parseNumberAttrVal(val); 27090 minVal = val; 27091 // TODO(matsko): implement validateLater to reduce number of validations 27092 ctrl.$validate(); 27093 } 27094 }); 27095 } 27096 27097 if (isDefined(attr.max) || attr.ngMax) { 27098 var maxVal = attr.max || $parse(attr.ngMax)(scope); 27099 var parsedMaxVal = parseNumberAttrVal(maxVal); 27100 27101 ctrl.$validators.max = function(modelValue, viewValue) { 27102 return ctrl.$isEmpty(viewValue) || isUndefined(parsedMaxVal) || viewValue <= parsedMaxVal; 27103 }; 27104 27105 attr.$observe('max', function(val) { 27106 if (val !== maxVal) { 27107 parsedMaxVal = parseNumberAttrVal(val); 27108 maxVal = val; 27109 // TODO(matsko): implement validateLater to reduce number of validations 27110 ctrl.$validate(); 27111 } 27112 }); 27113 } 27114 27115 if (isDefined(attr.step) || attr.ngStep) { 27116 var stepVal = attr.step || $parse(attr.ngStep)(scope); 27117 var parsedStepVal = parseNumberAttrVal(stepVal); 27118 27119 ctrl.$validators.step = function(modelValue, viewValue) { 27120 return ctrl.$isEmpty(viewValue) || isUndefined(parsedStepVal) || 27121 isValidForStep(viewValue, parsedMinVal || 0, parsedStepVal); 27122 }; 27123 27124 attr.$observe('step', function(val) { 27125 // TODO(matsko): implement validateLater to reduce number of validations 27126 if (val !== stepVal) { 27127 parsedStepVal = parseNumberAttrVal(val); 27128 stepVal = val; 27129 ctrl.$validate(); 27130 } 27131 27132 }); 27133 27134 } 27135} 27136 27137function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) { 27138 badInputChecker(scope, element, attr, ctrl, 'range'); 27139 numberFormatterParser(ctrl); 27140 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 27141 27142 var supportsRange = ctrl.$$hasNativeValidators && element[0].type === 'range', 27143 minVal = supportsRange ? 0 : undefined, 27144 maxVal = supportsRange ? 100 : undefined, 27145 stepVal = supportsRange ? 1 : undefined, 27146 validity = element[0].validity, 27147 hasMinAttr = isDefined(attr.min), 27148 hasMaxAttr = isDefined(attr.max), 27149 hasStepAttr = isDefined(attr.step); 27150 27151 var originalRender = ctrl.$render; 27152 27153 ctrl.$render = supportsRange && isDefined(validity.rangeUnderflow) && isDefined(validity.rangeOverflow) ? 27154 //Browsers that implement range will set these values automatically, but reading the adjusted values after 27155 //$render would cause the min / max validators to be applied with the wrong value 27156 function rangeRender() { 27157 originalRender(); 27158 ctrl.$setViewValue(element.val()); 27159 } : 27160 originalRender; 27161 27162 if (hasMinAttr) { 27163 minVal = parseNumberAttrVal(attr.min); 27164 27165 ctrl.$validators.min = supportsRange ? 27166 // Since all browsers set the input to a valid value, we don't need to check validity 27167 function noopMinValidator() { return true; } : 27168 // non-support browsers validate the min val 27169 function minValidator(modelValue, viewValue) { 27170 return ctrl.$isEmpty(viewValue) || isUndefined(minVal) || viewValue >= minVal; 27171 }; 27172 27173 setInitialValueAndObserver('min', minChange); 27174 } 27175 27176 if (hasMaxAttr) { 27177 maxVal = parseNumberAttrVal(attr.max); 27178 27179 ctrl.$validators.max = supportsRange ? 27180 // Since all browsers set the input to a valid value, we don't need to check validity 27181 function noopMaxValidator() { return true; } : 27182 // non-support browsers validate the max val 27183 function maxValidator(modelValue, viewValue) { 27184 return ctrl.$isEmpty(viewValue) || isUndefined(maxVal) || viewValue <= maxVal; 27185 }; 27186 27187 setInitialValueAndObserver('max', maxChange); 27188 } 27189 27190 if (hasStepAttr) { 27191 stepVal = parseNumberAttrVal(attr.step); 27192 27193 ctrl.$validators.step = supportsRange ? 27194 function nativeStepValidator() { 27195 // Currently, only FF implements the spec on step change correctly (i.e. adjusting the 27196 // input element value to a valid value). It's possible that other browsers set the stepMismatch 27197 // validity error instead, so we can at least report an error in that case. 27198 return !validity.stepMismatch; 27199 } : 27200 // ngStep doesn't set the setp attr, so the browser doesn't adjust the input value as setting step would 27201 function stepValidator(modelValue, viewValue) { 27202 return ctrl.$isEmpty(viewValue) || isUndefined(stepVal) || 27203 isValidForStep(viewValue, minVal || 0, stepVal); 27204 }; 27205 27206 setInitialValueAndObserver('step', stepChange); 27207 } 27208 27209 function setInitialValueAndObserver(htmlAttrName, changeFn) { 27210 // interpolated attributes set the attribute value only after a digest, but we need the 27211 // attribute value when the input is first rendered, so that the browser can adjust the 27212 // input value based on the min/max value 27213 element.attr(htmlAttrName, attr[htmlAttrName]); 27214 var oldVal = attr[htmlAttrName]; 27215 attr.$observe(htmlAttrName, function wrappedObserver(val) { 27216 if (val !== oldVal) { 27217 oldVal = val; 27218 changeFn(val); 27219 } 27220 }); 27221 } 27222 27223 function minChange(val) { 27224 minVal = parseNumberAttrVal(val); 27225 // ignore changes before model is initialized 27226 if (isNumberNaN(ctrl.$modelValue)) { 27227 return; 27228 } 27229 27230 if (supportsRange) { 27231 var elVal = element.val(); 27232 // IE11 doesn't set the el val correctly if the minVal is greater than the element value 27233 if (minVal > elVal) { 27234 elVal = minVal; 27235 element.val(elVal); 27236 } 27237 ctrl.$setViewValue(elVal); 27238 } else { 27239 // TODO(matsko): implement validateLater to reduce number of validations 27240 ctrl.$validate(); 27241 } 27242 } 27243 27244 function maxChange(val) { 27245 maxVal = parseNumberAttrVal(val); 27246 // ignore changes before model is initialized 27247 if (isNumberNaN(ctrl.$modelValue)) { 27248 return; 27249 } 27250 27251 if (supportsRange) { 27252 var elVal = element.val(); 27253 // IE11 doesn't set the el val correctly if the maxVal is less than the element value 27254 if (maxVal < elVal) { 27255 element.val(maxVal); 27256 // IE11 and Chrome don't set the value to the minVal when max < min 27257 elVal = maxVal < minVal ? minVal : maxVal; 27258 } 27259 ctrl.$setViewValue(elVal); 27260 } else { 27261 // TODO(matsko): implement validateLater to reduce number of validations 27262 ctrl.$validate(); 27263 } 27264 } 27265 27266 function stepChange(val) { 27267 stepVal = parseNumberAttrVal(val); 27268 // ignore changes before model is initialized 27269 if (isNumberNaN(ctrl.$modelValue)) { 27270 return; 27271 } 27272 27273 // Some browsers don't adjust the input value correctly, but set the stepMismatch error 27274 if (!supportsRange) { 27275 // TODO(matsko): implement validateLater to reduce number of validations 27276 ctrl.$validate(); 27277 } else if (ctrl.$viewValue !== element.val()) { 27278 ctrl.$setViewValue(element.val()); 27279 } 27280 } 27281} 27282 27283function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { 27284 // Note: no badInputChecker here by purpose as `url` is only a validation 27285 // in browsers, i.e. we can always read out input.value even if it is not valid! 27286 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 27287 stringBasedInputType(ctrl); 27288 27289 ctrl.$validators.url = function(modelValue, viewValue) { 27290 var value = modelValue || viewValue; 27291 return ctrl.$isEmpty(value) || URL_REGEXP.test(value); 27292 }; 27293} 27294 27295function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { 27296 // Note: no badInputChecker here by purpose as `url` is only a validation 27297 // in browsers, i.e. we can always read out input.value even if it is not valid! 27298 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 27299 stringBasedInputType(ctrl); 27300 27301 ctrl.$validators.email = function(modelValue, viewValue) { 27302 var value = modelValue || viewValue; 27303 return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value); 27304 }; 27305} 27306 27307function radioInputType(scope, element, attr, ctrl) { 27308 var doTrim = !attr.ngTrim || trim(attr.ngTrim) !== 'false'; 27309 // make the name unique, if not defined 27310 if (isUndefined(attr.name)) { 27311 element.attr('name', nextUid()); 27312 } 27313 27314 var listener = function(ev) { 27315 var value; 27316 if (element[0].checked) { 27317 value = attr.value; 27318 if (doTrim) { 27319 value = trim(value); 27320 } 27321 ctrl.$setViewValue(value, ev && ev.type); 27322 } 27323 }; 27324 27325 element.on('change', listener); 27326 27327 ctrl.$render = function() { 27328 var value = attr.value; 27329 if (doTrim) { 27330 value = trim(value); 27331 } 27332 element[0].checked = (value === ctrl.$viewValue); 27333 }; 27334 27335 attr.$observe('value', ctrl.$render); 27336} 27337 27338function parseConstantExpr($parse, context, name, expression, fallback) { 27339 var parseFn; 27340 if (isDefined(expression)) { 27341 parseFn = $parse(expression); 27342 if (!parseFn.constant) { 27343 throw ngModelMinErr('constexpr', 'Expected constant expression for `{0}`, but saw ' + 27344 '`{1}`.', name, expression); 27345 } 27346 return parseFn(context); 27347 } 27348 return fallback; 27349} 27350 27351function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) { 27352 var trueValue = parseConstantExpr($parse, scope, 'ngTrueValue', attr.ngTrueValue, true); 27353 var falseValue = parseConstantExpr($parse, scope, 'ngFalseValue', attr.ngFalseValue, false); 27354 27355 var listener = function(ev) { 27356 ctrl.$setViewValue(element[0].checked, ev && ev.type); 27357 }; 27358 27359 element.on('change', listener); 27360 27361 ctrl.$render = function() { 27362 element[0].checked = ctrl.$viewValue; 27363 }; 27364 27365 // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false` 27366 // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert 27367 // it to a boolean. 27368 ctrl.$isEmpty = function(value) { 27369 return value === false; 27370 }; 27371 27372 ctrl.$formatters.push(function(value) { 27373 return equals(value, trueValue); 27374 }); 27375 27376 ctrl.$parsers.push(function(value) { 27377 return value ? trueValue : falseValue; 27378 }); 27379} 27380 27381 27382/** 27383 * @ngdoc directive 27384 * @name textarea 27385 * @restrict E 27386 * 27387 * @description 27388 * HTML textarea element control with AngularJS data-binding. The data-binding and validation 27389 * properties of this element are exactly the same as those of the 27390 * {@link ng.directive:input input element}. 27391 * 27392 * @param {string} ngModel Assignable AngularJS expression to data-bind to. 27393 * @param {string=} name Property name of the form under which the control is published. 27394 * @param {string=} required Sets `required` validation error key if the value is not entered. 27395 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 27396 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 27397 * `required` when you want to data-bind to the `required` attribute. 27398 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 27399 * minlength. 27400 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 27401 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any 27402 * length. 27403 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 27404 * does not match a RegExp found by evaluating the AngularJS expression given in the attribute value. 27405 * If the expression evaluates to a RegExp object, then this is used directly. 27406 * If the expression evaluates to a string, then it will be converted to a RegExp 27407 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 27408 * `new RegExp('^abc$')`.<br /> 27409 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 27410 * start at the index of the last search's match, thus not taking the whole input value into 27411 * account. 27412 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user 27413 * interaction with the input element. 27414 * @param {boolean=} [ngTrim=true] If set to false AngularJS will not automatically trim the input. 27415 * 27416 * @knownIssue 27417 * 27418 * When specifying the `placeholder` attribute of `<textarea>`, Internet Explorer will temporarily 27419 * insert the placeholder value as the textarea's content. If the placeholder value contains 27420 * interpolation (`{{ ... }}`), an error will be logged in the console when AngularJS tries to update 27421 * the value of the by-then-removed text node. This doesn't affect the functionality of the 27422 * textarea, but can be undesirable. 27423 * 27424 * You can work around this Internet Explorer issue by using `ng-attr-placeholder` instead of 27425 * `placeholder` on textareas, whenever you need interpolation in the placeholder value. You can 27426 * find more details on `ngAttr` in the 27427 * [Interpolation](guide/interpolation#-ngattr-for-binding-to-arbitrary-attributes) section of the 27428 * Developer Guide. 27429 */ 27430 27431 27432/** 27433 * @ngdoc directive 27434 * @name input 27435 * @restrict E 27436 * 27437 * @description 27438 * HTML input element control. When used together with {@link ngModel `ngModel`}, it provides data-binding, 27439 * input state control, and validation. 27440 * Input control follows HTML5 input types and polyfills the HTML5 validation behavior for older browsers. 27441 * 27442 * <div class="alert alert-warning"> 27443 * **Note:** Not every feature offered is available for all input types. 27444 * Specifically, data binding and event handling via `ng-model` is unsupported for `input[file]`. 27445 * </div> 27446 * 27447 * @param {string} ngModel Assignable AngularJS expression to data-bind to. 27448 * @param {string=} name Property name of the form under which the control is published. 27449 * @param {string=} required Sets `required` validation error key if the value is not entered. 27450 * @param {boolean=} ngRequired Sets `required` attribute if set to true 27451 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 27452 * minlength. 27453 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 27454 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any 27455 * length. 27456 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 27457 * value does not match a RegExp found by evaluating the AngularJS expression given in the attribute value. 27458 * If the expression evaluates to a RegExp object, then this is used directly. 27459 * If the expression evaluates to a string, then it will be converted to a RegExp 27460 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 27461 * `new RegExp('^abc$')`.<br /> 27462 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 27463 * start at the index of the last search's match, thus not taking the whole input value into 27464 * account. 27465 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user 27466 * interaction with the input element. 27467 * @param {boolean=} [ngTrim=true] If set to false AngularJS will not automatically trim the input. 27468 * This parameter is ignored for input[type=password] controls, which will never trim the 27469 * input. 27470 * 27471 * @example 27472 <example name="input-directive" module="inputExample"> 27473 <file name="index.html"> 27474 <script> 27475 angular.module('inputExample', []) 27476 .controller('ExampleController', ['$scope', function($scope) { 27477 $scope.user = {name: 'guest', last: 'visitor'}; 27478 }]); 27479 </script> 27480 <div ng-controller="ExampleController"> 27481 <form name="myForm"> 27482 <label> 27483 User name: 27484 <input type="text" name="userName" ng-model="user.name" required> 27485 </label> 27486 <div role="alert"> 27487 <span class="error" ng-show="myForm.userName.$error.required"> 27488 Required!</span> 27489 </div> 27490 <label> 27491 Last name: 27492 <input type="text" name="lastName" ng-model="user.last" 27493 ng-minlength="3" ng-maxlength="10"> 27494 </label> 27495 <div role="alert"> 27496 <span class="error" ng-show="myForm.lastName.$error.minlength"> 27497 Too short!</span> 27498 <span class="error" ng-show="myForm.lastName.$error.maxlength"> 27499 Too long!</span> 27500 </div> 27501 </form> 27502 <hr> 27503 <tt>user = {{user}}</tt><br/> 27504 <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br/> 27505 <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br/> 27506 <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br/> 27507 <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br/> 27508 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 27509 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 27510 <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br/> 27511 <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br/> 27512 </div> 27513 </file> 27514 <file name="protractor.js" type="protractor"> 27515 var user = element(by.exactBinding('user')); 27516 var userNameValid = element(by.binding('myForm.userName.$valid')); 27517 var lastNameValid = element(by.binding('myForm.lastName.$valid')); 27518 var lastNameError = element(by.binding('myForm.lastName.$error')); 27519 var formValid = element(by.binding('myForm.$valid')); 27520 var userNameInput = element(by.model('user.name')); 27521 var userLastInput = element(by.model('user.last')); 27522 27523 it('should initialize to model', function() { 27524 expect(user.getText()).toContain('{"name":"guest","last":"visitor"}'); 27525 expect(userNameValid.getText()).toContain('true'); 27526 expect(formValid.getText()).toContain('true'); 27527 }); 27528 27529 it('should be invalid if empty when required', function() { 27530 userNameInput.clear(); 27531 userNameInput.sendKeys(''); 27532 27533 expect(user.getText()).toContain('{"last":"visitor"}'); 27534 expect(userNameValid.getText()).toContain('false'); 27535 expect(formValid.getText()).toContain('false'); 27536 }); 27537 27538 it('should be valid if empty when min length is set', function() { 27539 userLastInput.clear(); 27540 userLastInput.sendKeys(''); 27541 27542 expect(user.getText()).toContain('{"name":"guest","last":""}'); 27543 expect(lastNameValid.getText()).toContain('true'); 27544 expect(formValid.getText()).toContain('true'); 27545 }); 27546 27547 it('should be invalid if less than required min length', function() { 27548 userLastInput.clear(); 27549 userLastInput.sendKeys('xx'); 27550 27551 expect(user.getText()).toContain('{"name":"guest"}'); 27552 expect(lastNameValid.getText()).toContain('false'); 27553 expect(lastNameError.getText()).toContain('minlength'); 27554 expect(formValid.getText()).toContain('false'); 27555 }); 27556 27557 it('should be invalid if longer than max length', function() { 27558 userLastInput.clear(); 27559 userLastInput.sendKeys('some ridiculously long name'); 27560 27561 expect(user.getText()).toContain('{"name":"guest"}'); 27562 expect(lastNameValid.getText()).toContain('false'); 27563 expect(lastNameError.getText()).toContain('maxlength'); 27564 expect(formValid.getText()).toContain('false'); 27565 }); 27566 </file> 27567 </example> 27568 */ 27569var inputDirective = ['$browser', '$sniffer', '$filter', '$parse', 27570 function($browser, $sniffer, $filter, $parse) { 27571 return { 27572 restrict: 'E', 27573 require: ['?ngModel'], 27574 link: { 27575 pre: function(scope, element, attr, ctrls) { 27576 if (ctrls[0]) { 27577 (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer, 27578 $browser, $filter, $parse); 27579 } 27580 } 27581 } 27582 }; 27583}]; 27584 27585 27586var hiddenInputBrowserCacheDirective = function() { 27587 var valueProperty = { 27588 configurable: true, 27589 enumerable: false, 27590 get: function() { 27591 return this.getAttribute('value') || ''; 27592 }, 27593 set: function(val) { 27594 this.setAttribute('value', val); 27595 } 27596 }; 27597 27598 return { 27599 restrict: 'E', 27600 priority: 200, 27601 compile: function(_, attr) { 27602 if (lowercase(attr.type) !== 'hidden') { 27603 return; 27604 } 27605 27606 return { 27607 pre: function(scope, element, attr, ctrls) { 27608 var node = element[0]; 27609 27610 // Support: Edge 27611 // Moving the DOM around prevents autofillling 27612 if (node.parentNode) { 27613 node.parentNode.insertBefore(node, node.nextSibling); 27614 } 27615 27616 // Support: FF, IE 27617 // Avoiding direct assignment to .value prevents autofillling 27618 if (Object.defineProperty) { 27619 Object.defineProperty(node, 'value', valueProperty); 27620 } 27621 } 27622 }; 27623 } 27624 }; 27625}; 27626 27627 27628 27629var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; 27630/** 27631 * @ngdoc directive 27632 * @name ngValue 27633 * @restrict A 27634 * @priority 100 27635 * 27636 * @description 27637 * Binds the given expression to the value of the element. 27638 * 27639 * It is mainly used on {@link input[radio] `input[radio]`} and option elements, 27640 * so that when the element is selected, the {@link ngModel `ngModel`} of that element (or its 27641 * {@link select `select`} parent element) is set to the bound value. It is especially useful 27642 * for dynamically generated lists using {@link ngRepeat `ngRepeat`}, as shown below. 27643 * 27644 * It can also be used to achieve one-way binding of a given expression to an input element 27645 * such as an `input[text]` or a `textarea`, when that element does not use ngModel. 27646 * 27647 * @element ANY 27648 * @param {string=} ngValue AngularJS expression, whose value will be bound to the `value` attribute 27649 * and `value` property of the element. 27650 * 27651 * @example 27652 <example name="ngValue-directive" module="valueExample"> 27653 <file name="index.html"> 27654 <script> 27655 angular.module('valueExample', []) 27656 .controller('ExampleController', ['$scope', function($scope) { 27657 $scope.names = ['pizza', 'unicorns', 'robots']; 27658 $scope.my = { favorite: 'unicorns' }; 27659 }]); 27660 </script> 27661 <form ng-controller="ExampleController"> 27662 <h2>Which is your favorite?</h2> 27663 <label ng-repeat="name in names" for="{{name}}"> 27664 {{name}} 27665 <input type="radio" 27666 ng-model="my.favorite" 27667 ng-value="name" 27668 id="{{name}}" 27669 name="favorite"> 27670 </label> 27671 <div>You chose {{my.favorite}}</div> 27672 </form> 27673 </file> 27674 <file name="protractor.js" type="protractor"> 27675 var favorite = element(by.binding('my.favorite')); 27676 27677 it('should initialize to model', function() { 27678 expect(favorite.getText()).toContain('unicorns'); 27679 }); 27680 it('should bind the values to the inputs', function() { 27681 element.all(by.model('my.favorite')).get(0).click(); 27682 expect(favorite.getText()).toContain('pizza'); 27683 }); 27684 </file> 27685 </example> 27686 */ 27687var ngValueDirective = function() { 27688 /** 27689 * inputs use the value attribute as their default value if the value property is not set. 27690 * Once the value property has been set (by adding input), it will not react to changes to 27691 * the value attribute anymore. Setting both attribute and property fixes this behavior, and 27692 * makes it possible to use ngValue as a sort of one-way bind. 27693 */ 27694 function updateElementValue(element, attr, value) { 27695 // Support: IE9 only 27696 // In IE9 values are converted to string (e.g. `input.value = null` results in `input.value === 'null'`). 27697 var propValue = isDefined(value) ? value : (msie === 9) ? '' : null; 27698 element.prop('value', propValue); 27699 attr.$set('value', value); 27700 } 27701 27702 return { 27703 restrict: 'A', 27704 priority: 100, 27705 compile: function(tpl, tplAttr) { 27706 if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) { 27707 return function ngValueConstantLink(scope, elm, attr) { 27708 var value = scope.$eval(attr.ngValue); 27709 updateElementValue(elm, attr, value); 27710 }; 27711 } else { 27712 return function ngValueLink(scope, elm, attr) { 27713 scope.$watch(attr.ngValue, function valueWatchAction(value) { 27714 updateElementValue(elm, attr, value); 27715 }); 27716 }; 27717 } 27718 } 27719 }; 27720}; 27721 27722/** 27723 * @ngdoc directive 27724 * @name ngBind 27725 * @restrict AC 27726 * 27727 * @description 27728 * The `ngBind` attribute tells AngularJS to replace the text content of the specified HTML element 27729 * with the value of a given expression, and to update the text content when the value of that 27730 * expression changes. 27731 * 27732 * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like 27733 * `{{ expression }}` which is similar but less verbose. 27734 * 27735 * It is preferable to use `ngBind` instead of `{{ expression }}` if a template is momentarily 27736 * displayed by the browser in its raw state before AngularJS compiles it. Since `ngBind` is an 27737 * element attribute, it makes the bindings invisible to the user while the page is loading. 27738 * 27739 * An alternative solution to this problem would be using the 27740 * {@link ng.directive:ngCloak ngCloak} directive. 27741 * 27742 * 27743 * @element ANY 27744 * @param {expression} ngBind {@link guide/expression Expression} to evaluate. 27745 * 27746 * @example 27747 * Enter a name in the Live Preview text box; the greeting below the text box changes instantly. 27748 <example module="bindExample" name="ng-bind"> 27749 <file name="index.html"> 27750 <script> 27751 angular.module('bindExample', []) 27752 .controller('ExampleController', ['$scope', function($scope) { 27753 $scope.name = 'Whirled'; 27754 }]); 27755 </script> 27756 <div ng-controller="ExampleController"> 27757 <label>Enter name: <input type="text" ng-model="name"></label><br> 27758 Hello <span ng-bind="name"></span>! 27759 </div> 27760 </file> 27761 <file name="protractor.js" type="protractor"> 27762 it('should check ng-bind', function() { 27763 var nameInput = element(by.model('name')); 27764 27765 expect(element(by.binding('name')).getText()).toBe('Whirled'); 27766 nameInput.clear(); 27767 nameInput.sendKeys('world'); 27768 expect(element(by.binding('name')).getText()).toBe('world'); 27769 }); 27770 </file> 27771 </example> 27772 */ 27773var ngBindDirective = ['$compile', function($compile) { 27774 return { 27775 restrict: 'AC', 27776 compile: function ngBindCompile(templateElement) { 27777 $compile.$$addBindingClass(templateElement); 27778 return function ngBindLink(scope, element, attr) { 27779 $compile.$$addBindingInfo(element, attr.ngBind); 27780 element = element[0]; 27781 scope.$watch(attr.ngBind, function ngBindWatchAction(value) { 27782 element.textContent = stringify(value); 27783 }); 27784 }; 27785 } 27786 }; 27787}]; 27788 27789 27790/** 27791 * @ngdoc directive 27792 * @name ngBindTemplate 27793 * 27794 * @description 27795 * The `ngBindTemplate` directive specifies that the element 27796 * text content should be replaced with the interpolation of the template 27797 * in the `ngBindTemplate` attribute. 27798 * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}` 27799 * expressions. This directive is needed since some HTML elements 27800 * (such as TITLE and OPTION) cannot contain SPAN elements. 27801 * 27802 * @element ANY 27803 * @param {string} ngBindTemplate template of form 27804 * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval. 27805 * 27806 * @example 27807 * Try it here: enter text in text box and watch the greeting change. 27808 <example module="bindExample" name="ng-bind-template"> 27809 <file name="index.html"> 27810 <script> 27811 angular.module('bindExample', []) 27812 .controller('ExampleController', ['$scope', function($scope) { 27813 $scope.salutation = 'Hello'; 27814 $scope.name = 'World'; 27815 }]); 27816 </script> 27817 <div ng-controller="ExampleController"> 27818 <label>Salutation: <input type="text" ng-model="salutation"></label><br> 27819 <label>Name: <input type="text" ng-model="name"></label><br> 27820 <pre ng-bind-template="{{salutation}} {{name}}!"></pre> 27821 </div> 27822 </file> 27823 <file name="protractor.js" type="protractor"> 27824 it('should check ng-bind', function() { 27825 var salutationElem = element(by.binding('salutation')); 27826 var salutationInput = element(by.model('salutation')); 27827 var nameInput = element(by.model('name')); 27828 27829 expect(salutationElem.getText()).toBe('Hello World!'); 27830 27831 salutationInput.clear(); 27832 salutationInput.sendKeys('Greetings'); 27833 nameInput.clear(); 27834 nameInput.sendKeys('user'); 27835 27836 expect(salutationElem.getText()).toBe('Greetings user!'); 27837 }); 27838 </file> 27839 </example> 27840 */ 27841var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate, $compile) { 27842 return { 27843 compile: function ngBindTemplateCompile(templateElement) { 27844 $compile.$$addBindingClass(templateElement); 27845 return function ngBindTemplateLink(scope, element, attr) { 27846 var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate)); 27847 $compile.$$addBindingInfo(element, interpolateFn.expressions); 27848 element = element[0]; 27849 attr.$observe('ngBindTemplate', function(value) { 27850 element.textContent = isUndefined(value) ? '' : value; 27851 }); 27852 }; 27853 } 27854 }; 27855}]; 27856 27857 27858/** 27859 * @ngdoc directive 27860 * @name ngBindHtml 27861 * 27862 * @description 27863 * Evaluates the expression and inserts the resulting HTML into the element in a secure way. By default, 27864 * the resulting HTML content will be sanitized using the {@link ngSanitize.$sanitize $sanitize} service. 27865 * To utilize this functionality, ensure that `$sanitize` is available, for example, by including {@link 27866 * ngSanitize} in your module's dependencies (not in core AngularJS). In order to use {@link ngSanitize} 27867 * in your module's dependencies, you need to include "angular-sanitize.js" in your application. 27868 * 27869 * You may also bypass sanitization for values you know are safe. To do so, bind to 27870 * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example 27871 * under {@link ng.$sce#show-me-an-example-using-sce- Strict Contextual Escaping (SCE)}. 27872 * 27873 * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you 27874 * will have an exception (instead of an exploit.) 27875 * 27876 * @element ANY 27877 * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. 27878 * 27879 * @example 27880 27881 <example module="bindHtmlExample" deps="angular-sanitize.js" name="ng-bind-html"> 27882 <file name="index.html"> 27883 <div ng-controller="ExampleController"> 27884 <p ng-bind-html="myHTML"></p> 27885 </div> 27886 </file> 27887 27888 <file name="script.js"> 27889 angular.module('bindHtmlExample', ['ngSanitize']) 27890 .controller('ExampleController', ['$scope', function($scope) { 27891 $scope.myHTML = 27892 'I am an <code>HTML</code>string with ' + 27893 '<a href="#">links!</a> and other <em>stuff</em>'; 27894 }]); 27895 </file> 27896 27897 <file name="protractor.js" type="protractor"> 27898 it('should check ng-bind-html', function() { 27899 expect(element(by.binding('myHTML')).getText()).toBe( 27900 'I am an HTMLstring with links! and other stuff'); 27901 }); 27902 </file> 27903 </example> 27904 */ 27905var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse, $compile) { 27906 return { 27907 restrict: 'A', 27908 compile: function ngBindHtmlCompile(tElement, tAttrs) { 27909 var ngBindHtmlGetter = $parse(tAttrs.ngBindHtml); 27910 var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function sceValueOf(val) { 27911 // Unwrap the value to compare the actual inner safe value, not the wrapper object. 27912 return $sce.valueOf(val); 27913 }); 27914 $compile.$$addBindingClass(tElement); 27915 27916 return function ngBindHtmlLink(scope, element, attr) { 27917 $compile.$$addBindingInfo(element, attr.ngBindHtml); 27918 27919 scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() { 27920 // The watched value is the unwrapped value. To avoid re-escaping, use the direct getter. 27921 var value = ngBindHtmlGetter(scope); 27922 element.html($sce.getTrustedHtml(value) || ''); 27923 }); 27924 }; 27925 } 27926 }; 27927}]; 27928 27929/** 27930 * @ngdoc directive 27931 * @name ngChange 27932 * @restrict A 27933 * 27934 * @description 27935 * Evaluate the given expression when the user changes the input. 27936 * The expression is evaluated immediately, unlike the JavaScript onchange event 27937 * which only triggers at the end of a change (usually, when the user leaves the 27938 * form element or presses the return key). 27939 * 27940 * The `ngChange` expression is only evaluated when a change in the input value causes 27941 * a new value to be committed to the model. 27942 * 27943 * It will not be evaluated: 27944 * * if the value returned from the `$parsers` transformation pipeline has not changed 27945 * * if the input has continued to be invalid since the model will stay `null` 27946 * * if the model is changed programmatically and not by a change to the input value 27947 * 27948 * 27949 * Note, this directive requires `ngModel` to be present. 27950 * 27951 * @element ANY 27952 * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change 27953 * in input value. 27954 * 27955 * @example 27956 * <example name="ngChange-directive" module="changeExample"> 27957 * <file name="index.html"> 27958 * <script> 27959 * angular.module('changeExample', []) 27960 * .controller('ExampleController', ['$scope', function($scope) { 27961 * $scope.counter = 0; 27962 * $scope.change = function() { 27963 * $scope.counter++; 27964 * }; 27965 * }]); 27966 * </script> 27967 * <div ng-controller="ExampleController"> 27968 * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" /> 27969 * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" /> 27970 * <label for="ng-change-example2">Confirmed</label><br /> 27971 * <tt>debug = {{confirmed}}</tt><br/> 27972 * <tt>counter = {{counter}}</tt><br/> 27973 * </div> 27974 * </file> 27975 * <file name="protractor.js" type="protractor"> 27976 * var counter = element(by.binding('counter')); 27977 * var debug = element(by.binding('confirmed')); 27978 * 27979 * it('should evaluate the expression if changing from view', function() { 27980 * expect(counter.getText()).toContain('0'); 27981 * 27982 * element(by.id('ng-change-example1')).click(); 27983 * 27984 * expect(counter.getText()).toContain('1'); 27985 * expect(debug.getText()).toContain('true'); 27986 * }); 27987 * 27988 * it('should not evaluate the expression if changing from model', function() { 27989 * element(by.id('ng-change-example2')).click(); 27990 27991 * expect(counter.getText()).toContain('0'); 27992 * expect(debug.getText()).toContain('true'); 27993 * }); 27994 * </file> 27995 * </example> 27996 */ 27997var ngChangeDirective = valueFn({ 27998 restrict: 'A', 27999 require: 'ngModel', 28000 link: function(scope, element, attr, ctrl) { 28001 ctrl.$viewChangeListeners.push(function() { 28002 scope.$eval(attr.ngChange); 28003 }); 28004 } 28005}); 28006 28007/* exported 28008 ngClassDirective, 28009 ngClassEvenDirective, 28010 ngClassOddDirective 28011*/ 28012 28013function classDirective(name, selector) { 28014 name = 'ngClass' + name; 28015 var indexWatchExpression; 28016 28017 return ['$parse', function($parse) { 28018 return { 28019 restrict: 'AC', 28020 link: function(scope, element, attr) { 28021 var classCounts = element.data('$classCounts'); 28022 var oldModulo = true; 28023 var oldClassString; 28024 28025 if (!classCounts) { 28026 // Use createMap() to prevent class assumptions involving property 28027 // names in Object.prototype 28028 classCounts = createMap(); 28029 element.data('$classCounts', classCounts); 28030 } 28031 28032 if (name !== 'ngClass') { 28033 if (!indexWatchExpression) { 28034 indexWatchExpression = $parse('$index', function moduloTwo($index) { 28035 // eslint-disable-next-line no-bitwise 28036 return $index & 1; 28037 }); 28038 } 28039 28040 scope.$watch(indexWatchExpression, ngClassIndexWatchAction); 28041 } 28042 28043 scope.$watch($parse(attr[name], toClassString), ngClassWatchAction); 28044 28045 function addClasses(classString) { 28046 classString = digestClassCounts(split(classString), 1); 28047 attr.$addClass(classString); 28048 } 28049 28050 function removeClasses(classString) { 28051 classString = digestClassCounts(split(classString), -1); 28052 attr.$removeClass(classString); 28053 } 28054 28055 function updateClasses(oldClassString, newClassString) { 28056 var oldClassArray = split(oldClassString); 28057 var newClassArray = split(newClassString); 28058 28059 var toRemoveArray = arrayDifference(oldClassArray, newClassArray); 28060 var toAddArray = arrayDifference(newClassArray, oldClassArray); 28061 28062 var toRemoveString = digestClassCounts(toRemoveArray, -1); 28063 var toAddString = digestClassCounts(toAddArray, 1); 28064 28065 attr.$addClass(toAddString); 28066 attr.$removeClass(toRemoveString); 28067 } 28068 28069 function digestClassCounts(classArray, count) { 28070 var classesToUpdate = []; 28071 28072 forEach(classArray, function(className) { 28073 if (count > 0 || classCounts[className]) { 28074 classCounts[className] = (classCounts[className] || 0) + count; 28075 if (classCounts[className] === +(count > 0)) { 28076 classesToUpdate.push(className); 28077 } 28078 } 28079 }); 28080 28081 return classesToUpdate.join(' '); 28082 } 28083 28084 function ngClassIndexWatchAction(newModulo) { 28085 // This watch-action should run before the `ngClassWatchAction()`, thus it 28086 // adds/removes `oldClassString`. If the `ngClass` expression has changed as well, the 28087 // `ngClassWatchAction()` will update the classes. 28088 if (newModulo === selector) { 28089 addClasses(oldClassString); 28090 } else { 28091 removeClasses(oldClassString); 28092 } 28093 28094 oldModulo = newModulo; 28095 } 28096 28097 function ngClassWatchAction(newClassString) { 28098 if (oldModulo === selector) { 28099 updateClasses(oldClassString, newClassString); 28100 } 28101 28102 oldClassString = newClassString; 28103 } 28104 } 28105 }; 28106 }]; 28107 28108 // Helpers 28109 function arrayDifference(tokens1, tokens2) { 28110 if (!tokens1 || !tokens1.length) return []; 28111 if (!tokens2 || !tokens2.length) return tokens1; 28112 28113 var values = []; 28114 28115 outer: 28116 for (var i = 0; i < tokens1.length; i++) { 28117 var token = tokens1[i]; 28118 for (var j = 0; j < tokens2.length; j++) { 28119 if (token === tokens2[j]) continue outer; 28120 } 28121 values.push(token); 28122 } 28123 28124 return values; 28125 } 28126 28127 function split(classString) { 28128 return classString && classString.split(' '); 28129 } 28130 28131 function toClassString(classValue) { 28132 if (!classValue) return classValue; 28133 28134 var classString = classValue; 28135 28136 if (isArray(classValue)) { 28137 classString = classValue.map(toClassString).join(' '); 28138 } else if (isObject(classValue)) { 28139 classString = Object.keys(classValue). 28140 filter(function(key) { return classValue[key]; }). 28141 join(' '); 28142 } else if (!isString(classValue)) { 28143 classString = classValue + ''; 28144 } 28145 28146 return classString; 28147 } 28148} 28149 28150/** 28151 * @ngdoc directive 28152 * @name ngClass 28153 * @restrict AC 28154 * @element ANY 28155 * 28156 * @description 28157 * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding 28158 * an expression that represents all classes to be added. 28159 * 28160 * The directive operates in three different ways, depending on which of three types the expression 28161 * evaluates to: 28162 * 28163 * 1. If the expression evaluates to a string, the string should be one or more space-delimited class 28164 * names. 28165 * 28166 * 2. If the expression evaluates to an object, then for each key-value pair of the 28167 * object with a truthy value the corresponding key is used as a class name. 28168 * 28169 * 3. If the expression evaluates to an array, each element of the array should either be a string as in 28170 * type 1 or an object as in type 2. This means that you can mix strings and objects together in an array 28171 * to give you more control over what CSS classes appear. See the code below for an example of this. 28172 * 28173 * 28174 * The directive won't add duplicate classes if a particular class was already set. 28175 * 28176 * When the expression changes, the previously added classes are removed and only then are the 28177 * new classes added. 28178 * 28179 * @knownIssue 28180 * You should not use {@link guide/interpolation interpolation} in the value of the `class` 28181 * attribute, when using the `ngClass` directive on the same element. 28182 * See {@link guide/interpolation#known-issues here} for more info. 28183 * 28184 * @animations 28185 * | Animation | Occurs | 28186 * |----------------------------------|-------------------------------------| 28187 * | {@link ng.$animate#addClass addClass} | just before the class is applied to the element | 28188 * | {@link ng.$animate#removeClass removeClass} | just before the class is removed from the element | 28189 * | {@link ng.$animate#setClass setClass} | just before classes are added and classes are removed from the element at the same time | 28190 * 28191 * ### ngClass and pre-existing CSS3 Transitions/Animations 28192 The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure. 28193 Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder 28194 any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure 28195 to view the step by step details of {@link $animate#addClass $animate.addClass} and 28196 {@link $animate#removeClass $animate.removeClass}. 28197 * 28198 * @param {expression} ngClass {@link guide/expression Expression} to eval. The result 28199 * of the evaluation can be a string representing space delimited class 28200 * names, an array, or a map of class names to boolean values. In the case of a map, the 28201 * names of the properties whose values are truthy will be added as css classes to the 28202 * element. 28203 * 28204 * @example 28205 * ### Basic 28206 <example name="ng-class"> 28207 <file name="index.html"> 28208 <p ng-class="{strike: deleted, bold: important, 'has-error': error}">Map Syntax Example</p> 28209 <label> 28210 <input type="checkbox" ng-model="deleted"> 28211 deleted (apply "strike" class) 28212 </label><br> 28213 <label> 28214 <input type="checkbox" ng-model="important"> 28215 important (apply "bold" class) 28216 </label><br> 28217 <label> 28218 <input type="checkbox" ng-model="error"> 28219 error (apply "has-error" class) 28220 </label> 28221 <hr> 28222 <p ng-class="style">Using String Syntax</p> 28223 <input type="text" ng-model="style" 28224 placeholder="Type: bold strike red" aria-label="Type: bold strike red"> 28225 <hr> 28226 <p ng-class="[style1, style2, style3]">Using Array Syntax</p> 28227 <input ng-model="style1" 28228 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red"><br> 28229 <input ng-model="style2" 28230 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 2"><br> 28231 <input ng-model="style3" 28232 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 3"><br> 28233 <hr> 28234 <p ng-class="[style4, {orange: warning}]">Using Array and Map Syntax</p> 28235 <input ng-model="style4" placeholder="Type: bold, strike" aria-label="Type: bold, strike"><br> 28236 <label><input type="checkbox" ng-model="warning"> warning (apply "orange" class)</label> 28237 </file> 28238 <file name="style.css"> 28239 .strike { 28240 text-decoration: line-through; 28241 } 28242 .bold { 28243 font-weight: bold; 28244 } 28245 .red { 28246 color: red; 28247 } 28248 .has-error { 28249 color: red; 28250 background-color: yellow; 28251 } 28252 .orange { 28253 color: orange; 28254 } 28255 </file> 28256 <file name="protractor.js" type="protractor"> 28257 var ps = element.all(by.css('p')); 28258 28259 it('should let you toggle the class', function() { 28260 28261 expect(ps.first().getAttribute('class')).not.toMatch(/bold/); 28262 expect(ps.first().getAttribute('class')).not.toMatch(/has-error/); 28263 28264 element(by.model('important')).click(); 28265 expect(ps.first().getAttribute('class')).toMatch(/bold/); 28266 28267 element(by.model('error')).click(); 28268 expect(ps.first().getAttribute('class')).toMatch(/has-error/); 28269 }); 28270 28271 it('should let you toggle string example', function() { 28272 expect(ps.get(1).getAttribute('class')).toBe(''); 28273 element(by.model('style')).clear(); 28274 element(by.model('style')).sendKeys('red'); 28275 expect(ps.get(1).getAttribute('class')).toBe('red'); 28276 }); 28277 28278 it('array example should have 3 classes', function() { 28279 expect(ps.get(2).getAttribute('class')).toBe(''); 28280 element(by.model('style1')).sendKeys('bold'); 28281 element(by.model('style2')).sendKeys('strike'); 28282 element(by.model('style3')).sendKeys('red'); 28283 expect(ps.get(2).getAttribute('class')).toBe('bold strike red'); 28284 }); 28285 28286 it('array with map example should have 2 classes', function() { 28287 expect(ps.last().getAttribute('class')).toBe(''); 28288 element(by.model('style4')).sendKeys('bold'); 28289 element(by.model('warning')).click(); 28290 expect(ps.last().getAttribute('class')).toBe('bold orange'); 28291 }); 28292 </file> 28293 </example> 28294 28295 @example 28296 ### Animations 28297 28298 The example below demonstrates how to perform animations using ngClass. 28299 28300 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-class"> 28301 <file name="index.html"> 28302 <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'"> 28303 <input id="clearbtn" type="button" value="clear" ng-click="myVar=''"> 28304 <br> 28305 <span class="base-class" ng-class="myVar">Sample Text</span> 28306 </file> 28307 <file name="style.css"> 28308 .base-class { 28309 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 28310 } 28311 28312 .base-class.my-class { 28313 color: red; 28314 font-size:3em; 28315 } 28316 </file> 28317 <file name="protractor.js" type="protractor"> 28318 it('should check ng-class', function() { 28319 expect(element(by.css('.base-class')).getAttribute('class')).not. 28320 toMatch(/my-class/); 28321 28322 element(by.id('setbtn')).click(); 28323 28324 expect(element(by.css('.base-class')).getAttribute('class')). 28325 toMatch(/my-class/); 28326 28327 element(by.id('clearbtn')).click(); 28328 28329 expect(element(by.css('.base-class')).getAttribute('class')).not. 28330 toMatch(/my-class/); 28331 }); 28332 </file> 28333 </example> 28334 */ 28335var ngClassDirective = classDirective('', true); 28336 28337/** 28338 * @ngdoc directive 28339 * @name ngClassOdd 28340 * @restrict AC 28341 * 28342 * @description 28343 * The `ngClassOdd` and `ngClassEven` directives work exactly as 28344 * {@link ng.directive:ngClass ngClass}, except they work in 28345 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 28346 * 28347 * This directive can be applied only within the scope of an 28348 * {@link ng.directive:ngRepeat ngRepeat}. 28349 * 28350 * @animations 28351 * | Animation | Occurs | 28352 * |----------------------------------|-------------------------------------| 28353 * | {@link ng.$animate#addClass addClass} | just before the class is applied to the element | 28354 * | {@link ng.$animate#removeClass removeClass} | just before the class is removed from the element | 28355 * 28356 * @element ANY 28357 * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result 28358 * of the evaluation can be a string representing space delimited class names or an array. 28359 * 28360 * @example 28361 <example name="ng-class-odd"> 28362 <file name="index.html"> 28363 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 28364 <li ng-repeat="name in names"> 28365 <span ng-class-odd="'odd'" ng-class-even="'even'"> 28366 {{name}} 28367 </span> 28368 </li> 28369 </ol> 28370 </file> 28371 <file name="style.css"> 28372 .odd { 28373 color: red; 28374 } 28375 .even { 28376 color: blue; 28377 } 28378 </file> 28379 <file name="protractor.js" type="protractor"> 28380 it('should check ng-class-odd and ng-class-even', function() { 28381 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 28382 toMatch(/odd/); 28383 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 28384 toMatch(/even/); 28385 }); 28386 </file> 28387 </example> 28388 * 28389 * <hr /> 28390 * @example 28391 * An example on how to implement animations using `ngClassOdd`: 28392 * 28393 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-class-odd-animate"> 28394 <file name="index.html"> 28395 <div ng-init="items=['Item 3', 'Item 2', 'Item 1', 'Item 0']"> 28396 <button ng-click="items.unshift('Item ' + items.length)">Add item</button> 28397 <hr /> 28398 <table> 28399 <tr ng-repeat="item in items" ng-class-odd="'odd'"> 28400 <td>{{ item }}</td> 28401 </tr> 28402 </table> 28403 </div> 28404 </file> 28405 <file name="style.css"> 28406 .odd { 28407 background: rgba(255, 255, 0, 0.25); 28408 } 28409 28410 .odd-add, .odd-remove { 28411 transition: 1.5s; 28412 } 28413 </file> 28414 <file name="protractor.js" type="protractor"> 28415 it('should add new entries to the beginning of the list', function() { 28416 var button = element(by.buttonText('Add item')); 28417 var rows = element.all(by.repeater('item in items')); 28418 28419 expect(rows.count()).toBe(4); 28420 expect(rows.get(0).getText()).toBe('Item 3'); 28421 expect(rows.get(1).getText()).toBe('Item 2'); 28422 28423 button.click(); 28424 28425 expect(rows.count()).toBe(5); 28426 expect(rows.get(0).getText()).toBe('Item 4'); 28427 expect(rows.get(1).getText()).toBe('Item 3'); 28428 }); 28429 28430 it('should add odd class to odd entries', function() { 28431 var button = element(by.buttonText('Add item')); 28432 var rows = element.all(by.repeater('item in items')); 28433 28434 expect(rows.get(0).getAttribute('class')).toMatch(/odd/); 28435 expect(rows.get(1).getAttribute('class')).not.toMatch(/odd/); 28436 28437 button.click(); 28438 28439 expect(rows.get(0).getAttribute('class')).toMatch(/odd/); 28440 expect(rows.get(1).getAttribute('class')).not.toMatch(/odd/); 28441 }); 28442 </file> 28443 </example> 28444 */ 28445var ngClassOddDirective = classDirective('Odd', 0); 28446 28447/** 28448 * @ngdoc directive 28449 * @name ngClassEven 28450 * @restrict AC 28451 * 28452 * @description 28453 * The `ngClassOdd` and `ngClassEven` directives work exactly as 28454 * {@link ng.directive:ngClass ngClass}, except they work in 28455 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 28456 * 28457 * This directive can be applied only within the scope of an 28458 * {@link ng.directive:ngRepeat ngRepeat}. 28459 * 28460 * @animations 28461 * | Animation | Occurs | 28462 * |----------------------------------|-------------------------------------| 28463 * | {@link ng.$animate#addClass addClass} | just before the class is applied to the element | 28464 * | {@link ng.$animate#removeClass removeClass} | just before the class is removed from the element | 28465 * 28466 * @element ANY 28467 * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The 28468 * result of the evaluation can be a string representing space delimited class names or an array. 28469 * 28470 * @example 28471 <example name="ng-class-even"> 28472 <file name="index.html"> 28473 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 28474 <li ng-repeat="name in names"> 28475 <span ng-class-odd="'odd'" ng-class-even="'even'"> 28476 {{name}} 28477 </span> 28478 </li> 28479 </ol> 28480 </file> 28481 <file name="style.css"> 28482 .odd { 28483 color: red; 28484 } 28485 .even { 28486 color: blue; 28487 } 28488 </file> 28489 <file name="protractor.js" type="protractor"> 28490 it('should check ng-class-odd and ng-class-even', function() { 28491 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 28492 toMatch(/odd/); 28493 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 28494 toMatch(/even/); 28495 }); 28496 </file> 28497 </example> 28498 * 28499 * <hr /> 28500 * @example 28501 * An example on how to implement animations using `ngClassEven`: 28502 * 28503 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-class-even-animate"> 28504 <file name="index.html"> 28505 <div ng-init="items=['Item 3', 'Item 2', 'Item 1', 'Item 0']"> 28506 <button ng-click="items.unshift('Item ' + items.length)">Add item</button> 28507 <hr /> 28508 <table> 28509 <tr ng-repeat="item in items" ng-class-even="'even'"> 28510 <td>{{ item }}</td> 28511 </tr> 28512 </table> 28513 </div> 28514 </file> 28515 <file name="style.css"> 28516 .even { 28517 background: rgba(255, 255, 0, 0.25); 28518 } 28519 28520 .even-add, .even-remove { 28521 transition: 1.5s; 28522 } 28523 </file> 28524 <file name="protractor.js" type="protractor"> 28525 it('should add new entries to the beginning of the list', function() { 28526 var button = element(by.buttonText('Add item')); 28527 var rows = element.all(by.repeater('item in items')); 28528 28529 expect(rows.count()).toBe(4); 28530 expect(rows.get(0).getText()).toBe('Item 3'); 28531 expect(rows.get(1).getText()).toBe('Item 2'); 28532 28533 button.click(); 28534 28535 expect(rows.count()).toBe(5); 28536 expect(rows.get(0).getText()).toBe('Item 4'); 28537 expect(rows.get(1).getText()).toBe('Item 3'); 28538 }); 28539 28540 it('should add even class to even entries', function() { 28541 var button = element(by.buttonText('Add item')); 28542 var rows = element.all(by.repeater('item in items')); 28543 28544 expect(rows.get(0).getAttribute('class')).not.toMatch(/even/); 28545 expect(rows.get(1).getAttribute('class')).toMatch(/even/); 28546 28547 button.click(); 28548 28549 expect(rows.get(0).getAttribute('class')).not.toMatch(/even/); 28550 expect(rows.get(1).getAttribute('class')).toMatch(/even/); 28551 }); 28552 </file> 28553 </example> 28554 */ 28555var ngClassEvenDirective = classDirective('Even', 1); 28556 28557/** 28558 * @ngdoc directive 28559 * @name ngCloak 28560 * @restrict AC 28561 * 28562 * @description 28563 * The `ngCloak` directive is used to prevent the AngularJS html template from being briefly 28564 * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this 28565 * directive to avoid the undesirable flicker effect caused by the html template display. 28566 * 28567 * The directive can be applied to the `<body>` element, but the preferred usage is to apply 28568 * multiple `ngCloak` directives to small portions of the page to permit progressive rendering 28569 * of the browser view. 28570 * 28571 * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and 28572 * `angular.min.js`. 28573 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 28574 * 28575 * ```css 28576 * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { 28577 * display: none !important; 28578 * } 28579 * ``` 28580 * 28581 * When this css rule is loaded by the browser, all html elements (including their children) that 28582 * are tagged with the `ngCloak` directive are hidden. When AngularJS encounters this directive 28583 * during the compilation of the template it deletes the `ngCloak` element attribute, making 28584 * the compiled element visible. 28585 * 28586 * For the best result, the `angular.js` script must be loaded in the head section of the html 28587 * document; alternatively, the css rule above must be included in the external stylesheet of the 28588 * application. 28589 * 28590 * @element ANY 28591 * 28592 * @example 28593 <example name="ng-cloak"> 28594 <file name="index.html"> 28595 <div id="template1" ng-cloak>{{ 'hello' }}</div> 28596 <div id="template2" class="ng-cloak">{{ 'world' }}</div> 28597 </file> 28598 <file name="protractor.js" type="protractor"> 28599 it('should remove the template directive and css class', function() { 28600 expect($('#template1').getAttribute('ng-cloak')). 28601 toBeNull(); 28602 expect($('#template2').getAttribute('ng-cloak')). 28603 toBeNull(); 28604 }); 28605 </file> 28606 </example> 28607 * 28608 */ 28609var ngCloakDirective = ngDirective({ 28610 compile: function(element, attr) { 28611 attr.$set('ngCloak', undefined); 28612 element.removeClass('ng-cloak'); 28613 } 28614}); 28615 28616/** 28617 * @ngdoc directive 28618 * @name ngController 28619 * 28620 * @description 28621 * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular 28622 * supports the principles behind the Model-View-Controller design pattern. 28623 * 28624 * MVC components in angular: 28625 * 28626 * * Model â Models are the properties of a scope; scopes are attached to the DOM where scope properties 28627 * are accessed through bindings. 28628 * * View â The template (HTML with data bindings) that is rendered into the View. 28629 * * Controller â The `ngController` directive specifies a Controller class; the class contains business 28630 * logic behind the application to decorate the scope with functions and values 28631 * 28632 * Note that you can also attach controllers to the DOM by declaring it in a route definition 28633 * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller 28634 * again using `ng-controller` in the template itself. This will cause the controller to be attached 28635 * and executed twice. 28636 * 28637 * @element ANY 28638 * @scope 28639 * @priority 500 28640 * @param {expression} ngController Name of a constructor function registered with the current 28641 * {@link ng.$controllerProvider $controllerProvider} or an {@link guide/expression expression} 28642 * that on the current scope evaluates to a constructor function. 28643 * 28644 * The controller instance can be published into a scope property by specifying 28645 * `ng-controller="as propertyName"`. 28646 * 28647 * @example 28648 * Here is a simple form for editing user contact information. Adding, removing, clearing, and 28649 * greeting are methods declared on the controller (see source tab). These methods can 28650 * easily be called from the AngularJS markup. Any changes to the data are automatically reflected 28651 * in the View without the need for a manual update. 28652 * 28653 * Two different declaration styles are included below: 28654 * 28655 * * one binds methods and properties directly onto the controller using `this`: 28656 * `ng-controller="SettingsController1 as settings"` 28657 * * one injects `$scope` into the controller: 28658 * `ng-controller="SettingsController2"` 28659 * 28660 * The second option is more common in the AngularJS community, and is generally used in boilerplates 28661 * and in this guide. However, there are advantages to binding properties directly to the controller 28662 * and avoiding scope. 28663 * 28664 * * Using `controller as` makes it obvious which controller you are accessing in the template when 28665 * multiple controllers apply to an element. 28666 * * If you are writing your controllers as classes you have easier access to the properties and 28667 * methods, which will appear on the scope, from inside the controller code. 28668 * * Since there is always a `.` in the bindings, you don't have to worry about prototypal 28669 * inheritance masking primitives. 28670 * 28671 * This example demonstrates the `controller as` syntax. 28672 * 28673 * <example name="ngControllerAs" module="controllerAsExample"> 28674 * <file name="index.html"> 28675 * <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings"> 28676 * <label>Name: <input type="text" ng-model="settings.name"/></label> 28677 * <button ng-click="settings.greet()">greet</button><br/> 28678 * Contact: 28679 * <ul> 28680 * <li ng-repeat="contact in settings.contacts"> 28681 * <select ng-model="contact.type" aria-label="Contact method" id="select_{{$index}}"> 28682 * <option>phone</option> 28683 * <option>email</option> 28684 * </select> 28685 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" /> 28686 * <button ng-click="settings.clearContact(contact)">clear</button> 28687 * <button ng-click="settings.removeContact(contact)" aria-label="Remove">X</button> 28688 * </li> 28689 * <li><button ng-click="settings.addContact()">add</button></li> 28690 * </ul> 28691 * </div> 28692 * </file> 28693 * <file name="app.js"> 28694 * angular.module('controllerAsExample', []) 28695 * .controller('SettingsController1', SettingsController1); 28696 * 28697 * function SettingsController1() { 28698 * this.name = 'John Smith'; 28699 * this.contacts = [ 28700 * {type: 'phone', value: '408 555 1212'}, 28701 * {type: 'email', value: '[email protected]'} 28702 * ]; 28703 * } 28704 * 28705 * SettingsController1.prototype.greet = function() { 28706 * alert(this.name); 28707 * }; 28708 * 28709 * SettingsController1.prototype.addContact = function() { 28710 * this.contacts.push({type: 'email', value: '[email protected]'}); 28711 * }; 28712 * 28713 * SettingsController1.prototype.removeContact = function(contactToRemove) { 28714 * var index = this.contacts.indexOf(contactToRemove); 28715 * this.contacts.splice(index, 1); 28716 * }; 28717 * 28718 * SettingsController1.prototype.clearContact = function(contact) { 28719 * contact.type = 'phone'; 28720 * contact.value = ''; 28721 * }; 28722 * </file> 28723 * <file name="protractor.js" type="protractor"> 28724 * it('should check controller as', function() { 28725 * var container = element(by.id('ctrl-as-exmpl')); 28726 * expect(container.element(by.model('settings.name')) 28727 * .getAttribute('value')).toBe('John Smith'); 28728 * 28729 * var firstRepeat = 28730 * container.element(by.repeater('contact in settings.contacts').row(0)); 28731 * var secondRepeat = 28732 * container.element(by.repeater('contact in settings.contacts').row(1)); 28733 * 28734 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 28735 * .toBe('408 555 1212'); 28736 * 28737 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) 28738 * .toBe('[email protected]'); 28739 * 28740 * firstRepeat.element(by.buttonText('clear')).click(); 28741 * 28742 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 28743 * .toBe(''); 28744 * 28745 * container.element(by.buttonText('add')).click(); 28746 * 28747 * expect(container.element(by.repeater('contact in settings.contacts').row(2)) 28748 * .element(by.model('contact.value')) 28749 * .getAttribute('value')) 28750 * .toBe('[email protected]'); 28751 * }); 28752 * </file> 28753 * </example> 28754 * 28755 * This example demonstrates the "attach to `$scope`" style of controller. 28756 * 28757 * <example name="ngController" module="controllerExample"> 28758 * <file name="index.html"> 28759 * <div id="ctrl-exmpl" ng-controller="SettingsController2"> 28760 * <label>Name: <input type="text" ng-model="name"/></label> 28761 * <button ng-click="greet()">greet</button><br/> 28762 * Contact: 28763 * <ul> 28764 * <li ng-repeat="contact in contacts"> 28765 * <select ng-model="contact.type" id="select_{{$index}}"> 28766 * <option>phone</option> 28767 * <option>email</option> 28768 * </select> 28769 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" /> 28770 * <button ng-click="clearContact(contact)">clear</button> 28771 * <button ng-click="removeContact(contact)">X</button> 28772 * </li> 28773 * <li>[ <button ng-click="addContact()">add</button> ]</li> 28774 * </ul> 28775 * </div> 28776 * </file> 28777 * <file name="app.js"> 28778 * angular.module('controllerExample', []) 28779 * .controller('SettingsController2', ['$scope', SettingsController2]); 28780 * 28781 * function SettingsController2($scope) { 28782 * $scope.name = 'John Smith'; 28783 * $scope.contacts = [ 28784 * {type:'phone', value:'408 555 1212'}, 28785 * {type:'email', value:'[email protected]'} 28786 * ]; 28787 * 28788 * $scope.greet = function() { 28789 * alert($scope.name); 28790 * }; 28791 * 28792 * $scope.addContact = function() { 28793 * $scope.contacts.push({type:'email', value:'[email protected]'}); 28794 * }; 28795 * 28796 * $scope.removeContact = function(contactToRemove) { 28797 * var index = $scope.contacts.indexOf(contactToRemove); 28798 * $scope.contacts.splice(index, 1); 28799 * }; 28800 * 28801 * $scope.clearContact = function(contact) { 28802 * contact.type = 'phone'; 28803 * contact.value = ''; 28804 * }; 28805 * } 28806 * </file> 28807 * <file name="protractor.js" type="protractor"> 28808 * it('should check controller', function() { 28809 * var container = element(by.id('ctrl-exmpl')); 28810 * 28811 * expect(container.element(by.model('name')) 28812 * .getAttribute('value')).toBe('John Smith'); 28813 * 28814 * var firstRepeat = 28815 * container.element(by.repeater('contact in contacts').row(0)); 28816 * var secondRepeat = 28817 * container.element(by.repeater('contact in contacts').row(1)); 28818 * 28819 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 28820 * .toBe('408 555 1212'); 28821 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) 28822 * .toBe('[email protected]'); 28823 * 28824 * firstRepeat.element(by.buttonText('clear')).click(); 28825 * 28826 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 28827 * .toBe(''); 28828 * 28829 * container.element(by.buttonText('add')).click(); 28830 * 28831 * expect(container.element(by.repeater('contact in contacts').row(2)) 28832 * .element(by.model('contact.value')) 28833 * .getAttribute('value')) 28834 * .toBe('[email protected]'); 28835 * }); 28836 * </file> 28837 *</example> 28838 28839 */ 28840var ngControllerDirective = [function() { 28841 return { 28842 restrict: 'A', 28843 scope: true, 28844 controller: '@', 28845 priority: 500 28846 }; 28847}]; 28848 28849/** 28850 * @ngdoc directive 28851 * @name ngCsp 28852 * 28853 * @restrict A 28854 * @element ANY 28855 * @description 28856 * 28857 * AngularJS has some features that can conflict with certain restrictions that are applied when using 28858 * [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) rules. 28859 * 28860 * If you intend to implement CSP with these rules then you must tell AngularJS not to use these 28861 * features. 28862 * 28863 * This is necessary when developing things like Google Chrome Extensions or Universal Windows Apps. 28864 * 28865 * 28866 * The following default rules in CSP affect AngularJS: 28867 * 28868 * * The use of `eval()`, `Function(string)` and similar functions to dynamically create and execute 28869 * code from strings is forbidden. AngularJS makes use of this in the {@link $parse} service to 28870 * provide a 30% increase in the speed of evaluating AngularJS expressions. (This CSP rule can be 28871 * disabled with the CSP keyword `unsafe-eval`, but it is generally not recommended as it would 28872 * weaken the protections offered by CSP.) 28873 * 28874 * * The use of inline resources, such as inline `<script>` and `<style>` elements, are forbidden. 28875 * This prevents apps from injecting custom styles directly into the document. AngularJS makes use of 28876 * this to include some CSS rules (e.g. {@link ngCloak} and {@link ngHide}). To make these 28877 * directives work when a CSP rule is blocking inline styles, you must link to the `angular-csp.css` 28878 * in your HTML manually. (This CSP rule can be disabled with the CSP keyword `unsafe-inline`, but 28879 * it is generally not recommended as it would weaken the protections offered by CSP.) 28880 * 28881 * If you do not provide `ngCsp` then AngularJS tries to autodetect if CSP is blocking dynamic code 28882 * creation from strings (e.g., `unsafe-eval` not specified in CSP header) and automatically 28883 * deactivates this feature in the {@link $parse} service. This autodetection, however, triggers a 28884 * CSP error to be logged in the console: 28885 * 28886 * ``` 28887 * Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of 28888 * script in the following Content Security Policy directive: "default-src 'self'". Note that 28889 * 'script-src' was not explicitly set, so 'default-src' is used as a fallback. 28890 * ``` 28891 * 28892 * This error is harmless but annoying. To prevent the error from showing up, put the `ngCsp` 28893 * directive on an element of the HTML document that appears before the `<script>` tag that loads 28894 * the `angular.js` file. 28895 * 28896 * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.* 28897 * 28898 * You can specify which of the CSP related AngularJS features should be deactivated by providing 28899 * a value for the `ng-csp` attribute. The options are as follows: 28900 * 28901 * * no-inline-style: this stops AngularJS from injecting CSS styles into the DOM 28902 * 28903 * * no-unsafe-eval: this stops AngularJS from optimizing $parse with unsafe eval of strings 28904 * 28905 * You can use these values in the following combinations: 28906 * 28907 * 28908 * * No declaration means that AngularJS will assume that you can do inline styles, but it will do 28909 * a runtime check for unsafe-eval. E.g. `<body>`. This is backwardly compatible with previous 28910 * versions of AngularJS. 28911 * 28912 * * A simple `ng-csp` (or `data-ng-csp`) attribute will tell AngularJS to deactivate both inline 28913 * styles and unsafe eval. E.g. `<body ng-csp>`. This is backwardly compatible with previous 28914 * versions of AngularJS. 28915 * 28916 * * Specifying only `no-unsafe-eval` tells AngularJS that we must not use eval, but that we can 28917 * inject inline styles. E.g. `<body ng-csp="no-unsafe-eval">`. 28918 * 28919 * * Specifying only `no-inline-style` tells AngularJS that we must not inject styles, but that we can 28920 * run eval - no automatic check for unsafe eval will occur. E.g. `<body ng-csp="no-inline-style">` 28921 * 28922 * * Specifying both `no-unsafe-eval` and `no-inline-style` tells AngularJS that we must not inject 28923 * styles nor use eval, which is the same as an empty: ng-csp. 28924 * E.g.`<body ng-csp="no-inline-style;no-unsafe-eval">` 28925 * 28926 * @example 28927 * 28928 * This example shows how to apply the `ngCsp` directive to the `html` tag. 28929 ```html 28930 <!doctype html> 28931 <html ng-app ng-csp> 28932 ... 28933 ... 28934 </html> 28935 ``` 28936 28937 <!-- Note: the `.csp` suffix in the example name triggers CSP mode in our http server! --> 28938 <example name="example.csp" module="cspExample" ng-csp="true"> 28939 <file name="index.html"> 28940 <div ng-controller="MainController as ctrl"> 28941 <div> 28942 <button ng-click="ctrl.inc()" id="inc">Increment</button> 28943 <span id="counter"> 28944 {{ctrl.counter}} 28945 </span> 28946 </div> 28947 28948 <div> 28949 <button ng-click="ctrl.evil()" id="evil">Evil</button> 28950 <span id="evilError"> 28951 {{ctrl.evilError}} 28952 </span> 28953 </div> 28954 </div> 28955 </file> 28956 <file name="script.js"> 28957 angular.module('cspExample', []) 28958 .controller('MainController', function MainController() { 28959 this.counter = 0; 28960 this.inc = function() { 28961 this.counter++; 28962 }; 28963 this.evil = function() { 28964 try { 28965 eval('1+2'); // eslint-disable-line no-eval 28966 } catch (e) { 28967 this.evilError = e.message; 28968 } 28969 }; 28970 }); 28971 </file> 28972 <file name="protractor.js" type="protractor"> 28973 var util, webdriver; 28974 28975 var incBtn = element(by.id('inc')); 28976 var counter = element(by.id('counter')); 28977 var evilBtn = element(by.id('evil')); 28978 var evilError = element(by.id('evilError')); 28979 28980 function getAndClearSevereErrors() { 28981 return browser.manage().logs().get('browser').then(function(browserLog) { 28982 return browserLog.filter(function(logEntry) { 28983 return logEntry.level.value > webdriver.logging.Level.WARNING.value; 28984 }); 28985 }); 28986 } 28987 28988 function clearErrors() { 28989 getAndClearSevereErrors(); 28990 } 28991 28992 function expectNoErrors() { 28993 getAndClearSevereErrors().then(function(filteredLog) { 28994 expect(filteredLog.length).toEqual(0); 28995 if (filteredLog.length) { 28996 console.log('browser console errors: ' + util.inspect(filteredLog)); 28997 } 28998 }); 28999 } 29000 29001 function expectError(regex) { 29002 getAndClearSevereErrors().then(function(filteredLog) { 29003 var found = false; 29004 filteredLog.forEach(function(log) { 29005 if (log.message.match(regex)) { 29006 found = true; 29007 } 29008 }); 29009 if (!found) { 29010 throw new Error('expected an error that matches ' + regex); 29011 } 29012 }); 29013 } 29014 29015 beforeEach(function() { 29016 util = require('util'); 29017 webdriver = require('selenium-webdriver'); 29018 }); 29019 29020 // For now, we only test on Chrome, 29021 // as Safari does not load the page with Protractor's injected scripts, 29022 // and Firefox webdriver always disables content security policy (#6358) 29023 if (browser.params.browser !== 'chrome') { 29024 return; 29025 } 29026 29027 it('should not report errors when the page is loaded', function() { 29028 // clear errors so we are not dependent on previous tests 29029 clearErrors(); 29030 // Need to reload the page as the page is already loaded when 29031 // we come here 29032 browser.driver.getCurrentUrl().then(function(url) { 29033 browser.get(url); 29034 }); 29035 expectNoErrors(); 29036 }); 29037 29038 it('should evaluate expressions', function() { 29039 expect(counter.getText()).toEqual('0'); 29040 incBtn.click(); 29041 expect(counter.getText()).toEqual('1'); 29042 expectNoErrors(); 29043 }); 29044 29045 it('should throw and report an error when using "eval"', function() { 29046 evilBtn.click(); 29047 expect(evilError.getText()).toMatch(/Content Security Policy/); 29048 expectError(/Content Security Policy/); 29049 }); 29050 </file> 29051 </example> 29052 */ 29053 29054// `ngCsp` is not implemented as a proper directive any more, because we need it be processed while 29055// we bootstrap the app (before `$parse` is instantiated). For this reason, we just have the `csp()` 29056// fn that looks for the `ng-csp` attribute anywhere in the current doc. 29057 29058/** 29059 * @ngdoc directive 29060 * @name ngClick 29061 * @restrict A 29062 * @element ANY 29063 * @priority 0 29064 * 29065 * @description 29066 * The ngClick directive allows you to specify custom behavior when 29067 * an element is clicked. 29068 * 29069 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon 29070 * click. ({@link guide/expression#-event- Event object is available as `$event`}) 29071 * 29072 * @example 29073 <example name="ng-click"> 29074 <file name="index.html"> 29075 <button ng-click="count = count + 1" ng-init="count=0"> 29076 Increment 29077 </button> 29078 <span> 29079 count: {{count}} 29080 </span> 29081 </file> 29082 <file name="protractor.js" type="protractor"> 29083 it('should check ng-click', function() { 29084 expect(element(by.binding('count')).getText()).toMatch('0'); 29085 element(by.css('button')).click(); 29086 expect(element(by.binding('count')).getText()).toMatch('1'); 29087 }); 29088 </file> 29089 </example> 29090 */ 29091/* 29092 * A collection of directives that allows creation of custom event handlers that are defined as 29093 * AngularJS expressions and are compiled and executed within the current scope. 29094 */ 29095var ngEventDirectives = {}; 29096 29097// For events that might fire synchronously during DOM manipulation 29098// we need to execute their event handlers asynchronously using $evalAsync, 29099// so that they are not executed in an inconsistent state. 29100var forceAsyncEvents = { 29101 'blur': true, 29102 'focus': true 29103}; 29104forEach( 29105 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '), 29106 function(eventName) { 29107 var directiveName = directiveNormalize('ng-' + eventName); 29108 ngEventDirectives[directiveName] = ['$parse', '$rootScope', '$exceptionHandler', function($parse, $rootScope, $exceptionHandler) { 29109 return createEventDirective($parse, $rootScope, $exceptionHandler, directiveName, eventName, forceAsyncEvents[eventName]); 29110 }]; 29111 } 29112); 29113 29114function createEventDirective($parse, $rootScope, $exceptionHandler, directiveName, eventName, forceAsync) { 29115 return { 29116 restrict: 'A', 29117 compile: function($element, attr) { 29118 // NOTE: 29119 // We expose the powerful `$event` object on the scope that provides access to the Window, 29120 // etc. This is OK, because expressions are not sandboxed any more (and the expression 29121 // sandbox was never meant to be a security feature anyway). 29122 var fn = $parse(attr[directiveName]); 29123 return function ngEventHandler(scope, element) { 29124 element.on(eventName, function(event) { 29125 var callback = function() { 29126 fn(scope, {$event: event}); 29127 }; 29128 29129 if (!$rootScope.$$phase) { 29130 scope.$apply(callback); 29131 } else if (forceAsync) { 29132 scope.$evalAsync(callback); 29133 } else { 29134 try { 29135 callback(); 29136 } catch (error) { 29137 $exceptionHandler(error); 29138 } 29139 } 29140 }); 29141 }; 29142 } 29143 }; 29144} 29145 29146/** 29147 * @ngdoc directive 29148 * @name ngDblclick 29149 * @restrict A 29150 * @element ANY 29151 * @priority 0 29152 * 29153 * @description 29154 * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event. 29155 * 29156 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon 29157 * a dblclick. (The Event object is available as `$event`) 29158 * 29159 * @example 29160 <example name="ng-dblclick"> 29161 <file name="index.html"> 29162 <button ng-dblclick="count = count + 1" ng-init="count=0"> 29163 Increment (on double click) 29164 </button> 29165 count: {{count}} 29166 </file> 29167 </example> 29168 */ 29169 29170 29171/** 29172 * @ngdoc directive 29173 * @name ngMousedown 29174 * @restrict A 29175 * @element ANY 29176 * @priority 0 29177 * 29178 * @description 29179 * The ngMousedown directive allows you to specify custom behavior on mousedown event. 29180 * 29181 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon 29182 * mousedown. ({@link guide/expression#-event- Event object is available as `$event`}) 29183 * 29184 * @example 29185 <example name="ng-mousedown"> 29186 <file name="index.html"> 29187 <button ng-mousedown="count = count + 1" ng-init="count=0"> 29188 Increment (on mouse down) 29189 </button> 29190 count: {{count}} 29191 </file> 29192 </example> 29193 */ 29194 29195 29196/** 29197 * @ngdoc directive 29198 * @name ngMouseup 29199 * @restrict A 29200 * @element ANY 29201 * @priority 0 29202 * 29203 * @description 29204 * Specify custom behavior on mouseup event. 29205 * 29206 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon 29207 * mouseup. ({@link guide/expression#-event- Event object is available as `$event`}) 29208 * 29209 * @example 29210 <example name="ng-mouseup"> 29211 <file name="index.html"> 29212 <button ng-mouseup="count = count + 1" ng-init="count=0"> 29213 Increment (on mouse up) 29214 </button> 29215 count: {{count}} 29216 </file> 29217 </example> 29218 */ 29219 29220/** 29221 * @ngdoc directive 29222 * @name ngMouseover 29223 * @restrict A 29224 * @element ANY 29225 * @priority 0 29226 * 29227 * @description 29228 * Specify custom behavior on mouseover event. 29229 * 29230 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon 29231 * mouseover. ({@link guide/expression#-event- Event object is available as `$event`}) 29232 * 29233 * @example 29234 <example name="ng-mouseover"> 29235 <file name="index.html"> 29236 <button ng-mouseover="count = count + 1" ng-init="count=0"> 29237 Increment (when mouse is over) 29238 </button> 29239 count: {{count}} 29240 </file> 29241 </example> 29242 */ 29243 29244 29245/** 29246 * @ngdoc directive 29247 * @name ngMouseenter 29248 * @restrict A 29249 * @element ANY 29250 * @priority 0 29251 * 29252 * @description 29253 * Specify custom behavior on mouseenter event. 29254 * 29255 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon 29256 * mouseenter. ({@link guide/expression#-event- Event object is available as `$event`}) 29257 * 29258 * @example 29259 <example name="ng-mouseenter"> 29260 <file name="index.html"> 29261 <button ng-mouseenter="count = count + 1" ng-init="count=0"> 29262 Increment (when mouse enters) 29263 </button> 29264 count: {{count}} 29265 </file> 29266 </example> 29267 */ 29268 29269 29270/** 29271 * @ngdoc directive 29272 * @name ngMouseleave 29273 * @restrict A 29274 * @element ANY 29275 * @priority 0 29276 * 29277 * @description 29278 * Specify custom behavior on mouseleave event. 29279 * 29280 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon 29281 * mouseleave. ({@link guide/expression#-event- Event object is available as `$event`}) 29282 * 29283 * @example 29284 <example name="ng-mouseleave"> 29285 <file name="index.html"> 29286 <button ng-mouseleave="count = count + 1" ng-init="count=0"> 29287 Increment (when mouse leaves) 29288 </button> 29289 count: {{count}} 29290 </file> 29291 </example> 29292 */ 29293 29294 29295/** 29296 * @ngdoc directive 29297 * @name ngMousemove 29298 * @restrict A 29299 * @element ANY 29300 * @priority 0 29301 * 29302 * @description 29303 * Specify custom behavior on mousemove event. 29304 * 29305 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon 29306 * mousemove. ({@link guide/expression#-event- Event object is available as `$event`}) 29307 * 29308 * @example 29309 <example name="ng-mousemove"> 29310 <file name="index.html"> 29311 <button ng-mousemove="count = count + 1" ng-init="count=0"> 29312 Increment (when mouse moves) 29313 </button> 29314 count: {{count}} 29315 </file> 29316 </example> 29317 */ 29318 29319 29320/** 29321 * @ngdoc directive 29322 * @name ngKeydown 29323 * @restrict A 29324 * @element ANY 29325 * @priority 0 29326 * 29327 * @description 29328 * Specify custom behavior on keydown event. 29329 * 29330 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon 29331 * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 29332 * 29333 * @example 29334 <example name="ng-keydown"> 29335 <file name="index.html"> 29336 <input ng-keydown="count = count + 1" ng-init="count=0"> 29337 key down count: {{count}} 29338 </file> 29339 </example> 29340 */ 29341 29342 29343/** 29344 * @ngdoc directive 29345 * @name ngKeyup 29346 * @restrict A 29347 * @element ANY 29348 * @priority 0 29349 * 29350 * @description 29351 * Specify custom behavior on keyup event. 29352 * 29353 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon 29354 * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 29355 * 29356 * @example 29357 <example name="ng-keyup"> 29358 <file name="index.html"> 29359 <p>Typing in the input box below updates the key count</p> 29360 <input ng-keyup="count = count + 1" ng-init="count=0"> key up count: {{count}} 29361 29362 <p>Typing in the input box below updates the keycode</p> 29363 <input ng-keyup="event=$event"> 29364 <p>event keyCode: {{ event.keyCode }}</p> 29365 <p>event altKey: {{ event.altKey }}</p> 29366 </file> 29367 </example> 29368 */ 29369 29370 29371/** 29372 * @ngdoc directive 29373 * @name ngKeypress 29374 * @restrict A 29375 * @element ANY 29376 * 29377 * @description 29378 * Specify custom behavior on keypress event. 29379 * 29380 * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon 29381 * keypress. ({@link guide/expression#-event- Event object is available as `$event`} 29382 * and can be interrogated for keyCode, altKey, etc.) 29383 * 29384 * @example 29385 <example name="ng-keypress"> 29386 <file name="index.html"> 29387 <input ng-keypress="count = count + 1" ng-init="count=0"> 29388 key press count: {{count}} 29389 </file> 29390 </example> 29391 */ 29392 29393 29394/** 29395 * @ngdoc directive 29396 * @name ngSubmit 29397 * @restrict A 29398 * @element form 29399 * @priority 0 29400 * 29401 * @description 29402 * Enables binding AngularJS expressions to onsubmit events. 29403 * 29404 * Additionally it prevents the default action (which for form means sending the request to the 29405 * server and reloading the current page), but only if the form does not contain `action`, 29406 * `data-action`, or `x-action` attributes. 29407 * 29408 * <div class="alert alert-warning"> 29409 * **Warning:** Be careful not to cause "double-submission" by using both the `ngClick` and 29410 * `ngSubmit` handlers together. See the 29411 * {@link form#submitting-a-form-and-preventing-the-default-action `form` directive documentation} 29412 * for a detailed discussion of when `ngSubmit` may be triggered. 29413 * </div> 29414 * 29415 * @param {expression} ngSubmit {@link guide/expression Expression} to eval. 29416 * ({@link guide/expression#-event- Event object is available as `$event`}) 29417 * 29418 * @example 29419 <example module="submitExample" name="ng-submit"> 29420 <file name="index.html"> 29421 <script> 29422 angular.module('submitExample', []) 29423 .controller('ExampleController', ['$scope', function($scope) { 29424 $scope.list = []; 29425 $scope.text = 'hello'; 29426 $scope.submit = function() { 29427 if ($scope.text) { 29428 $scope.list.push(this.text); 29429 $scope.text = ''; 29430 } 29431 }; 29432 }]); 29433 </script> 29434 <form ng-submit="submit()" ng-controller="ExampleController"> 29435 Enter text and hit enter: 29436 <input type="text" ng-model="text" name="text" /> 29437 <input type="submit" id="submit" value="Submit" /> 29438 <pre>list={{list}}</pre> 29439 </form> 29440 </file> 29441 <file name="protractor.js" type="protractor"> 29442 it('should check ng-submit', function() { 29443 expect(element(by.binding('list')).getText()).toBe('list=[]'); 29444 element(by.css('#submit')).click(); 29445 expect(element(by.binding('list')).getText()).toContain('hello'); 29446 expect(element(by.model('text')).getAttribute('value')).toBe(''); 29447 }); 29448 it('should ignore empty strings', function() { 29449 expect(element(by.binding('list')).getText()).toBe('list=[]'); 29450 element(by.css('#submit')).click(); 29451 element(by.css('#submit')).click(); 29452 expect(element(by.binding('list')).getText()).toContain('hello'); 29453 }); 29454 </file> 29455 </example> 29456 */ 29457 29458/** 29459 * @ngdoc directive 29460 * @name ngFocus 29461 * @restrict A 29462 * @element window, input, select, textarea, a 29463 * @priority 0 29464 * 29465 * @description 29466 * Specify custom behavior on focus event. 29467 * 29468 * Note: As the `focus` event is executed synchronously when calling `input.focus()` 29469 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired 29470 * during an `$apply` to ensure a consistent state. 29471 * 29472 * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon 29473 * focus. ({@link guide/expression#-event- Event object is available as `$event`}) 29474 * 29475 * @example 29476 * See {@link ng.directive:ngClick ngClick} 29477 */ 29478 29479/** 29480 * @ngdoc directive 29481 * @name ngBlur 29482 * @restrict A 29483 * @element window, input, select, textarea, a 29484 * @priority 0 29485 * 29486 * @description 29487 * Specify custom behavior on blur event. 29488 * 29489 * A [blur event](https://developer.mozilla.org/en-US/docs/Web/Events/blur) fires when 29490 * an element has lost focus. 29491 * 29492 * Note: As the `blur` event is executed synchronously also during DOM manipulations 29493 * (e.g. removing a focussed input), 29494 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired 29495 * during an `$apply` to ensure a consistent state. 29496 * 29497 * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon 29498 * blur. ({@link guide/expression#-event- Event object is available as `$event`}) 29499 * 29500 * @example 29501 * See {@link ng.directive:ngClick ngClick} 29502 */ 29503 29504/** 29505 * @ngdoc directive 29506 * @name ngCopy 29507 * @restrict A 29508 * @element window, input, select, textarea, a 29509 * @priority 0 29510 * 29511 * @description 29512 * Specify custom behavior on copy event. 29513 * 29514 * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon 29515 * copy. ({@link guide/expression#-event- Event object is available as `$event`}) 29516 * 29517 * @example 29518 <example name="ng-copy"> 29519 <file name="index.html"> 29520 <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value"> 29521 copied: {{copied}} 29522 </file> 29523 </example> 29524 */ 29525 29526/** 29527 * @ngdoc directive 29528 * @name ngCut 29529 * @restrict A 29530 * @element window, input, select, textarea, a 29531 * @priority 0 29532 * 29533 * @description 29534 * Specify custom behavior on cut event. 29535 * 29536 * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon 29537 * cut. ({@link guide/expression#-event- Event object is available as `$event`}) 29538 * 29539 * @example 29540 <example name="ng-cut"> 29541 <file name="index.html"> 29542 <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value"> 29543 cut: {{cut}} 29544 </file> 29545 </example> 29546 */ 29547 29548/** 29549 * @ngdoc directive 29550 * @name ngPaste 29551 * @restrict A 29552 * @element window, input, select, textarea, a 29553 * @priority 0 29554 * 29555 * @description 29556 * Specify custom behavior on paste event. 29557 * 29558 * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon 29559 * paste. ({@link guide/expression#-event- Event object is available as `$event`}) 29560 * 29561 * @example 29562 <example name="ng-paste"> 29563 <file name="index.html"> 29564 <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'> 29565 pasted: {{paste}} 29566 </file> 29567 </example> 29568 */ 29569 29570/** 29571 * @ngdoc directive 29572 * @name ngIf 29573 * @restrict A 29574 * @multiElement 29575 * 29576 * @description 29577 * The `ngIf` directive removes or recreates a portion of the DOM tree based on an 29578 * {expression}. If the expression assigned to `ngIf` evaluates to a false 29579 * value then the element is removed from the DOM, otherwise a clone of the 29580 * element is reinserted into the DOM. 29581 * 29582 * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the 29583 * element in the DOM rather than changing its visibility via the `display` css property. A common 29584 * case when this difference is significant is when using css selectors that rely on an element's 29585 * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classes. 29586 * 29587 * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope 29588 * is created when the element is restored. The scope created within `ngIf` inherits from 29589 * its parent scope using 29590 * [prototypal inheritance](https://github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance). 29591 * An important implication of this is if `ngModel` is used within `ngIf` to bind to 29592 * a javascript primitive defined in the parent scope. In this case any modifications made to the 29593 * variable within the child scope will override (hide) the value in the parent scope. 29594 * 29595 * Also, `ngIf` recreates elements using their compiled state. An example of this behavior 29596 * is if an element's class attribute is directly modified after it's compiled, using something like 29597 * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element 29598 * the added class will be lost because the original compiled state is used to regenerate the element. 29599 * 29600 * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter` 29601 * and `leave` effects. 29602 * 29603 * @animations 29604 * | Animation | Occurs | 29605 * |----------------------------------|-------------------------------------| 29606 * | {@link ng.$animate#enter enter} | just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container | 29607 * | {@link ng.$animate#leave leave} | just before the `ngIf` contents are removed from the DOM | 29608 * 29609 * @element ANY 29610 * @scope 29611 * @priority 600 29612 * @param {expression} ngIf If the {@link guide/expression expression} is falsy then 29613 * the element is removed from the DOM tree. If it is truthy a copy of the compiled 29614 * element is added to the DOM tree. 29615 * 29616 * @example 29617 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-if"> 29618 <file name="index.html"> 29619 <label>Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /></label><br/> 29620 Show when checked: 29621 <span ng-if="checked" class="animate-if"> 29622 This is removed when the checkbox is unchecked. 29623 </span> 29624 </file> 29625 <file name="animations.css"> 29626 .animate-if { 29627 background:white; 29628 border:1px solid black; 29629 padding:10px; 29630 } 29631 29632 .animate-if.ng-enter, .animate-if.ng-leave { 29633 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 29634 } 29635 29636 .animate-if.ng-enter, 29637 .animate-if.ng-leave.ng-leave-active { 29638 opacity:0; 29639 } 29640 29641 .animate-if.ng-leave, 29642 .animate-if.ng-enter.ng-enter-active { 29643 opacity:1; 29644 } 29645 </file> 29646 </example> 29647 */ 29648var ngIfDirective = ['$animate', '$compile', function($animate, $compile) { 29649 return { 29650 multiElement: true, 29651 transclude: 'element', 29652 priority: 600, 29653 terminal: true, 29654 restrict: 'A', 29655 $$tlb: true, 29656 link: function($scope, $element, $attr, ctrl, $transclude) { 29657 var block, childScope, previousElements; 29658 $scope.$watch($attr.ngIf, function ngIfWatchAction(value) { 29659 29660 if (value) { 29661 if (!childScope) { 29662 $transclude(function(clone, newScope) { 29663 childScope = newScope; 29664 clone[clone.length++] = $compile.$$createComment('end ngIf', $attr.ngIf); 29665 // Note: We only need the first/last node of the cloned nodes. 29666 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 29667 // by a directive with templateUrl when its template arrives. 29668 block = { 29669 clone: clone 29670 }; 29671 $animate.enter(clone, $element.parent(), $element); 29672 }); 29673 } 29674 } else { 29675 if (previousElements) { 29676 previousElements.remove(); 29677 previousElements = null; 29678 } 29679 if (childScope) { 29680 childScope.$destroy(); 29681 childScope = null; 29682 } 29683 if (block) { 29684 previousElements = getBlockNodes(block.clone); 29685 $animate.leave(previousElements).done(function(response) { 29686 if (response !== false) previousElements = null; 29687 }); 29688 block = null; 29689 } 29690 } 29691 }); 29692 } 29693 }; 29694}]; 29695 29696/** 29697 * @ngdoc directive 29698 * @name ngInclude 29699 * @restrict ECA 29700 * @scope 29701 * @priority -400 29702 * 29703 * @description 29704 * Fetches, compiles and includes an external HTML fragment. 29705 * 29706 * By default, the template URL is restricted to the same domain and protocol as the 29707 * application document. This is done by calling {@link $sce#getTrustedResourceUrl 29708 * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols 29709 * you may either add them to your {@link ng.$sceDelegateProvider#trustedResourceUrlList trusted 29710 * resource URL list} or {@link $sce#trustAsResourceUrl wrap them} as trusted values. Refer to 29711 * AngularJS's {@link ng.$sce Strict Contextual Escaping}. 29712 * 29713 * In addition, the browser's 29714 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 29715 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 29716 * policy may further restrict whether the template is successfully loaded. 29717 * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://` 29718 * access on some browsers. 29719 * 29720 * @animations 29721 * | Animation | Occurs | 29722 * |----------------------------------|-------------------------------------| 29723 * | {@link ng.$animate#enter enter} | when the expression changes, on the new include | 29724 * | {@link ng.$animate#leave leave} | when the expression changes, on the old include | 29725 * 29726 * The enter and leave animation occur concurrently. 29727 * 29728 * @param {string} ngInclude|src AngularJS expression evaluating to URL. If the source is a string constant, 29729 * make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`. 29730 * @param {string=} onload Expression to evaluate when a new partial is loaded. 29731 * <div class="alert alert-warning"> 29732 * **Note:** When using onload on SVG elements in IE11, the browser will try to call 29733 * a function with the name on the window element, which will usually throw a 29734 * "function is undefined" error. To fix this, you can instead use `data-onload` or a 29735 * different form that {@link guide/directive#normalization matches} `onload`. 29736 * </div> 29737 * 29738 * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll 29739 * $anchorScroll} to scroll the viewport after the content is loaded. 29740 * 29741 * - If the attribute is not set, disable scrolling. 29742 * - If the attribute is set without value, enable scrolling. 29743 * - Otherwise enable scrolling only if the expression evaluates to truthy value. 29744 * 29745 * @example 29746 <example module="includeExample" deps="angular-animate.js" animations="true" name="ng-include"> 29747 <file name="index.html"> 29748 <div ng-controller="ExampleController"> 29749 <select ng-model="template" ng-options="t.name for t in templates"> 29750 <option value="">(blank)</option> 29751 </select> 29752 url of the template: <code>{{template.url}}</code> 29753 <hr/> 29754 <div class="slide-animate-container"> 29755 <div class="slide-animate" ng-include="template.url"></div> 29756 </div> 29757 </div> 29758 </file> 29759 <file name="script.js"> 29760 angular.module('includeExample', ['ngAnimate']) 29761 .controller('ExampleController', ['$scope', function($scope) { 29762 $scope.templates = 29763 [{ name: 'template1.html', url: 'template1.html'}, 29764 { name: 'template2.html', url: 'template2.html'}]; 29765 $scope.template = $scope.templates[0]; 29766 }]); 29767 </file> 29768 <file name="template1.html"> 29769 Content of template1.html 29770 </file> 29771 <file name="template2.html"> 29772 Content of template2.html 29773 </file> 29774 <file name="animations.css"> 29775 .slide-animate-container { 29776 position:relative; 29777 background:white; 29778 border:1px solid black; 29779 height:40px; 29780 overflow:hidden; 29781 } 29782 29783 .slide-animate { 29784 padding:10px; 29785 } 29786 29787 .slide-animate.ng-enter, .slide-animate.ng-leave { 29788 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 29789 29790 position:absolute; 29791 top:0; 29792 left:0; 29793 right:0; 29794 bottom:0; 29795 display:block; 29796 padding:10px; 29797 } 29798 29799 .slide-animate.ng-enter { 29800 top:-50px; 29801 } 29802 .slide-animate.ng-enter.ng-enter-active { 29803 top:0; 29804 } 29805 29806 .slide-animate.ng-leave { 29807 top:0; 29808 } 29809 .slide-animate.ng-leave.ng-leave-active { 29810 top:50px; 29811 } 29812 </file> 29813 <file name="protractor.js" type="protractor"> 29814 var templateSelect = element(by.model('template')); 29815 var includeElem = element(by.css('[ng-include]')); 29816 29817 it('should load template1.html', function() { 29818 expect(includeElem.getText()).toMatch(/Content of template1.html/); 29819 }); 29820 29821 it('should load template2.html', function() { 29822 if (browser.params.browser === 'firefox') { 29823 // Firefox can't handle using selects 29824 // See https://github.com/angular/protractor/issues/480 29825 return; 29826 } 29827 templateSelect.click(); 29828 templateSelect.all(by.css('option')).get(2).click(); 29829 expect(includeElem.getText()).toMatch(/Content of template2.html/); 29830 }); 29831 29832 it('should change to blank', function() { 29833 if (browser.params.browser === 'firefox') { 29834 // Firefox can't handle using selects 29835 return; 29836 } 29837 templateSelect.click(); 29838 templateSelect.all(by.css('option')).get(0).click(); 29839 expect(includeElem.isPresent()).toBe(false); 29840 }); 29841 </file> 29842 </example> 29843 */ 29844 29845 29846/** 29847 * @ngdoc event 29848 * @name ngInclude#$includeContentRequested 29849 * @eventType emit on the scope ngInclude was declared in 29850 * @description 29851 * Emitted every time the ngInclude content is requested. 29852 * 29853 * @param {Object} angularEvent Synthetic event object. 29854 * @param {String} src URL of content to load. 29855 */ 29856 29857 29858/** 29859 * @ngdoc event 29860 * @name ngInclude#$includeContentLoaded 29861 * @eventType emit on the current ngInclude scope 29862 * @description 29863 * Emitted every time the ngInclude content is reloaded. 29864 * 29865 * @param {Object} angularEvent Synthetic event object. 29866 * @param {String} src URL of content to load. 29867 */ 29868 29869 29870/** 29871 * @ngdoc event 29872 * @name ngInclude#$includeContentError 29873 * @eventType emit on the scope ngInclude was declared in 29874 * @description 29875 * Emitted when a template HTTP request yields an erroneous response (status < 200 || status > 299) 29876 * 29877 * @param {Object} angularEvent Synthetic event object. 29878 * @param {String} src URL of content to load. 29879 */ 29880var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate', 29881 function($templateRequest, $anchorScroll, $animate) { 29882 return { 29883 restrict: 'ECA', 29884 priority: 400, 29885 terminal: true, 29886 transclude: 'element', 29887 controller: angular.noop, 29888 compile: function(element, attr) { 29889 var srcExp = attr.ngInclude || attr.src, 29890 onloadExp = attr.onload || '', 29891 autoScrollExp = attr.autoscroll; 29892 29893 return function(scope, $element, $attr, ctrl, $transclude) { 29894 var changeCounter = 0, 29895 currentScope, 29896 previousElement, 29897 currentElement; 29898 29899 var cleanupLastIncludeContent = function() { 29900 if (previousElement) { 29901 previousElement.remove(); 29902 previousElement = null; 29903 } 29904 if (currentScope) { 29905 currentScope.$destroy(); 29906 currentScope = null; 29907 } 29908 if (currentElement) { 29909 $animate.leave(currentElement).done(function(response) { 29910 if (response !== false) previousElement = null; 29911 }); 29912 previousElement = currentElement; 29913 currentElement = null; 29914 } 29915 }; 29916 29917 scope.$watch(srcExp, function ngIncludeWatchAction(src) { 29918 var afterAnimation = function(response) { 29919 if (response !== false && isDefined(autoScrollExp) && 29920 (!autoScrollExp || scope.$eval(autoScrollExp))) { 29921 $anchorScroll(); 29922 } 29923 }; 29924 var thisChangeId = ++changeCounter; 29925 29926 if (src) { 29927 //set the 2nd param to true to ignore the template request error so that the inner 29928 //contents and scope can be cleaned up. 29929 $templateRequest(src, true).then(function(response) { 29930 if (scope.$$destroyed) return; 29931 29932 if (thisChangeId !== changeCounter) return; 29933 var newScope = scope.$new(); 29934 ctrl.template = response; 29935 29936 // Note: This will also link all children of ng-include that were contained in the original 29937 // html. If that content contains controllers, ... they could pollute/change the scope. 29938 // However, using ng-include on an element with additional content does not make sense... 29939 // Note: We can't remove them in the cloneAttchFn of $transclude as that 29940 // function is called before linking the content, which would apply child 29941 // directives to non existing elements. 29942 var clone = $transclude(newScope, function(clone) { 29943 cleanupLastIncludeContent(); 29944 $animate.enter(clone, null, $element).done(afterAnimation); 29945 }); 29946 29947 currentScope = newScope; 29948 currentElement = clone; 29949 29950 currentScope.$emit('$includeContentLoaded', src); 29951 scope.$eval(onloadExp); 29952 }, function() { 29953 if (scope.$$destroyed) return; 29954 29955 if (thisChangeId === changeCounter) { 29956 cleanupLastIncludeContent(); 29957 scope.$emit('$includeContentError', src); 29958 } 29959 }); 29960 scope.$emit('$includeContentRequested', src); 29961 } else { 29962 cleanupLastIncludeContent(); 29963 ctrl.template = null; 29964 } 29965 }); 29966 }; 29967 } 29968 }; 29969}]; 29970 29971// This directive is called during the $transclude call of the first `ngInclude` directive. 29972// It will replace and compile the content of the element with the loaded template. 29973// We need this directive so that the element content is already filled when 29974// the link function of another directive on the same element as ngInclude 29975// is called. 29976var ngIncludeFillContentDirective = ['$compile', 29977 function($compile) { 29978 return { 29979 restrict: 'ECA', 29980 priority: -400, 29981 require: 'ngInclude', 29982 link: function(scope, $element, $attr, ctrl) { 29983 if (toString.call($element[0]).match(/SVG/)) { 29984 // WebKit: https://bugs.webkit.org/show_bug.cgi?id=135698 --- SVG elements do not 29985 // support innerHTML, so detect this here and try to generate the contents 29986 // specially. 29987 $element.empty(); 29988 $compile(jqLiteBuildFragment(ctrl.template, window.document).childNodes)(scope, 29989 function namespaceAdaptedClone(clone) { 29990 $element.append(clone); 29991 }, {futureParentElement: $element}); 29992 return; 29993 } 29994 29995 $element.html(ctrl.template); 29996 $compile($element.contents())(scope); 29997 } 29998 }; 29999 }]; 30000 30001/** 30002 * @ngdoc directive 30003 * @name ngInit 30004 * @restrict AC 30005 * @priority 450 30006 * @element ANY 30007 * 30008 * @param {expression} ngInit {@link guide/expression Expression} to eval. 30009 * 30010 * @description 30011 * The `ngInit` directive allows you to evaluate an expression in the 30012 * current scope. 30013 * 30014 * <div class="alert alert-danger"> 30015 * This directive can be abused to add unnecessary amounts of logic into your templates. 30016 * There are only a few appropriate uses of `ngInit`: 30017 * <ul> 30018 * <li>aliasing special properties of {@link ng.directive:ngRepeat `ngRepeat`}, 30019 * as seen in the demo below.</li> 30020 * <li>initializing data during development, or for examples, as seen throughout these docs.</li> 30021 * <li>injecting data via server side scripting.</li> 30022 * </ul> 30023 * 30024 * Besides these few cases, you should use {@link guide/component Components} or 30025 * {@link guide/controller Controllers} rather than `ngInit` to initialize values on a scope. 30026 * </div> 30027 * 30028 * <div class="alert alert-warning"> 30029 * **Note**: If you have assignment in `ngInit` along with a {@link ng.$filter `filter`}, make 30030 * sure you have parentheses to ensure correct operator precedence: 30031 * <pre class="prettyprint"> 30032 * `<div ng-init="test1 = ($index | toString)"></div>` 30033 * </pre> 30034 * </div> 30035 * 30036 * @example 30037 <example module="initExample" name="ng-init"> 30038 <file name="index.html"> 30039 <script> 30040 angular.module('initExample', []) 30041 .controller('ExampleController', ['$scope', function($scope) { 30042 $scope.list = [['a', 'b'], ['c', 'd']]; 30043 }]); 30044 </script> 30045 <div ng-controller="ExampleController"> 30046 <div ng-repeat="innerList in list" ng-init="outerIndex = $index"> 30047 <div ng-repeat="value in innerList" ng-init="innerIndex = $index"> 30048 <span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span> 30049 </div> 30050 </div> 30051 </div> 30052 </file> 30053 <file name="protractor.js" type="protractor"> 30054 it('should alias index positions', function() { 30055 var elements = element.all(by.css('.example-init')); 30056 expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;'); 30057 expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;'); 30058 expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;'); 30059 expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;'); 30060 }); 30061 </file> 30062 </example> 30063 */ 30064var ngInitDirective = ngDirective({ 30065 priority: 450, 30066 compile: function() { 30067 return { 30068 pre: function(scope, element, attrs) { 30069 scope.$eval(attrs.ngInit); 30070 } 30071 }; 30072 } 30073}); 30074 30075/** 30076 * @ngdoc directive 30077 * @name ngList 30078 * @restrict A 30079 * @priority 100 30080 * 30081 * @param {string=} ngList optional delimiter that should be used to split the value. 30082 * 30083 * @description 30084 * Text input that converts between a delimited string and an array of strings. The default 30085 * delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom 30086 * delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`. 30087 * 30088 * The behaviour of the directive is affected by the use of the `ngTrim` attribute. 30089 * * If `ngTrim` is set to `"false"` then whitespace around both the separator and each 30090 * list item is respected. This implies that the user of the directive is responsible for 30091 * dealing with whitespace but also allows you to use whitespace as a delimiter, such as a 30092 * tab or newline character. 30093 * * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected 30094 * when joining the list items back together) and whitespace around each list item is stripped 30095 * before it is added to the model. 30096 * 30097 * @example 30098 * ### Validation 30099 * 30100 * <example name="ngList-directive" module="listExample"> 30101 * <file name="app.js"> 30102 * angular.module('listExample', []) 30103 * .controller('ExampleController', ['$scope', function($scope) { 30104 * $scope.names = ['morpheus', 'neo', 'trinity']; 30105 * }]); 30106 * </file> 30107 * <file name="index.html"> 30108 * <form name="myForm" ng-controller="ExampleController"> 30109 * <label>List: <input name="namesInput" ng-model="names" ng-list required></label> 30110 * <span role="alert"> 30111 * <span class="error" ng-show="myForm.namesInput.$error.required"> 30112 * Required!</span> 30113 * </span> 30114 * <br> 30115 * <tt>names = {{names}}</tt><br/> 30116 * <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/> 30117 * <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/> 30118 * <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 30119 * <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 30120 * </form> 30121 * </file> 30122 * <file name="protractor.js" type="protractor"> 30123 * var listInput = element(by.model('names')); 30124 * var names = element(by.exactBinding('names')); 30125 * var valid = element(by.binding('myForm.namesInput.$valid')); 30126 * var error = element(by.css('span.error')); 30127 * 30128 * it('should initialize to model', function() { 30129 * expect(names.getText()).toContain('["morpheus","neo","trinity"]'); 30130 * expect(valid.getText()).toContain('true'); 30131 * expect(error.getCssValue('display')).toBe('none'); 30132 * }); 30133 * 30134 * it('should be invalid if empty', function() { 30135 * listInput.clear(); 30136 * listInput.sendKeys(''); 30137 * 30138 * expect(names.getText()).toContain(''); 30139 * expect(valid.getText()).toContain('false'); 30140 * expect(error.getCssValue('display')).not.toBe('none'); 30141 * }); 30142 * </file> 30143 * </example> 30144 * 30145 * @example 30146 * ### Splitting on newline 30147 * 30148 * <example name="ngList-directive-newlines"> 30149 * <file name="index.html"> 30150 * <textarea ng-model="list" ng-list=" " ng-trim="false"></textarea> 30151 * <pre>{{ list | json }}</pre> 30152 * </file> 30153 * <file name="protractor.js" type="protractor"> 30154 * it("should split the text by newlines", function() { 30155 * var listInput = element(by.model('list')); 30156 * var output = element(by.binding('list | json')); 30157 * listInput.sendKeys('abc\ndef\nghi'); 30158 * expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]'); 30159 * }); 30160 * </file> 30161 * </example> 30162 * 30163 */ 30164var ngListDirective = function() { 30165 return { 30166 restrict: 'A', 30167 priority: 100, 30168 require: 'ngModel', 30169 link: function(scope, element, attr, ctrl) { 30170 var ngList = attr.ngList || ', '; 30171 var trimValues = attr.ngTrim !== 'false'; 30172 var separator = trimValues ? trim(ngList) : ngList; 30173 30174 var parse = function(viewValue) { 30175 // If the viewValue is invalid (say required but empty) it will be `undefined` 30176 if (isUndefined(viewValue)) return; 30177 30178 var list = []; 30179 30180 if (viewValue) { 30181 forEach(viewValue.split(separator), function(value) { 30182 if (value) list.push(trimValues ? trim(value) : value); 30183 }); 30184 } 30185 30186 return list; 30187 }; 30188 30189 ctrl.$parsers.push(parse); 30190 ctrl.$formatters.push(function(value) { 30191 if (isArray(value)) { 30192 return value.join(ngList); 30193 } 30194 30195 return undefined; 30196 }); 30197 30198 // Override the standard $isEmpty because an empty array means the input is empty. 30199 ctrl.$isEmpty = function(value) { 30200 return !value || !value.length; 30201 }; 30202 } 30203 }; 30204}; 30205 30206/* global VALID_CLASS: true, 30207 INVALID_CLASS: true, 30208 PRISTINE_CLASS: true, 30209 DIRTY_CLASS: true, 30210 UNTOUCHED_CLASS: true, 30211 TOUCHED_CLASS: true, 30212 PENDING_CLASS: true, 30213 addSetValidityMethod: true, 30214 setupValidity: true, 30215 defaultModelOptions: false 30216*/ 30217 30218 30219var VALID_CLASS = 'ng-valid', 30220 INVALID_CLASS = 'ng-invalid', 30221 PRISTINE_CLASS = 'ng-pristine', 30222 DIRTY_CLASS = 'ng-dirty', 30223 UNTOUCHED_CLASS = 'ng-untouched', 30224 TOUCHED_CLASS = 'ng-touched', 30225 EMPTY_CLASS = 'ng-empty', 30226 NOT_EMPTY_CLASS = 'ng-not-empty'; 30227 30228var ngModelMinErr = minErr('ngModel'); 30229 30230/** 30231 * @ngdoc type 30232 * @name ngModel.NgModelController 30233 * @property {*} $viewValue The actual value from the control's view. For `input` elements, this is a 30234 * String. See {@link ngModel.NgModelController#$setViewValue} for information about when the $viewValue 30235 * is set. 30236 * 30237 * @property {*} $modelValue The value in the model that the control is bound to. 30238 * 30239 * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever 30240 * the control updates the ngModelController with a new {@link ngModel.NgModelController#$viewValue 30241 `$viewValue`} from the DOM, usually via user input. 30242 See {@link ngModel.NgModelController#$setViewValue `$setViewValue()`} for a detailed lifecycle explanation. 30243 Note that the `$parsers` are not called when the bound ngModel expression changes programmatically. 30244 30245 The functions are called in array order, each passing 30246 its return value through to the next. The last return value is forwarded to the 30247 {@link ngModel.NgModelController#$validators `$validators`} collection. 30248 30249 Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue 30250 `$viewValue`}. 30251 30252 Returning `undefined` from a parser means a parse error occurred. In that case, 30253 no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel` 30254 will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`} 30255 is set to `true`. The parse error is stored in `ngModel.$error.parse`. 30256 30257 This simple example shows a parser that would convert text input value to lowercase: 30258 * ```js 30259 * function parse(value) { 30260 * if (value) { 30261 * return value.toLowerCase(); 30262 * } 30263 * } 30264 * ngModelController.$parsers.push(parse); 30265 * ``` 30266 30267 * 30268 * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever 30269 the bound ngModel expression changes programmatically. The `$formatters` are not called when the 30270 value of the control is changed by user interaction. 30271 30272 Formatters are used to format / convert the {@link ngModel.NgModelController#$modelValue 30273 `$modelValue`} for display in the control. 30274 30275 The functions are called in reverse array order, each passing the value through to the 30276 next. The last return value is used as the actual DOM value. 30277 30278 This simple example shows a formatter that would convert the model value to uppercase: 30279 30280 * ```js 30281 * function format(value) { 30282 * if (value) { 30283 * return value.toUpperCase(); 30284 * } 30285 * } 30286 * ngModel.$formatters.push(format); 30287 * ``` 30288 * 30289 * @property {Object.<string, function>} $validators A collection of validators that are applied 30290 * whenever the model value changes. The key value within the object refers to the name of the 30291 * validator while the function refers to the validation operation. The validation operation is 30292 * provided with the model value as an argument and must return a true or false value depending 30293 * on the response of that validation. 30294 * 30295 * ```js 30296 * ngModel.$validators.validCharacters = function(modelValue, viewValue) { 30297 * var value = modelValue || viewValue; 30298 * return /[0-9]+/.test(value) && 30299 * /[a-z]+/.test(value) && 30300 * /[A-Z]+/.test(value) && 30301 * /\W+/.test(value); 30302 * }; 30303 * ``` 30304 * 30305 * @property {Object.<string, function>} $asyncValidators A collection of validations that are expected to 30306 * perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided 30307 * is expected to return a promise when it is run during the model validation process. Once the promise 30308 * is delivered then the validation status will be set to true when fulfilled and false when rejected. 30309 * When the asynchronous validators are triggered, each of the validators will run in parallel and the model 30310 * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator 30311 * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators 30312 * will only run once all synchronous validators have passed. 30313 * 30314 * Please note that if $http is used then it is important that the server returns a success HTTP response code 30315 * in order to fulfill the validation and a status level of `4xx` in order to reject the validation. 30316 * 30317 * ```js 30318 * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) { 30319 * var value = modelValue || viewValue; 30320 * 30321 * // Lookup user by username 30322 * return $http.get('/api/users/' + value). 30323 * then(function resolved() { 30324 * //username exists, this means validation fails 30325 * return $q.reject('exists'); 30326 * }, function rejected() { 30327 * //username does not exist, therefore this validation passes 30328 * return true; 30329 * }); 30330 * }; 30331 * ``` 30332 * 30333 * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever 30334 * a change to {@link ngModel.NgModelController#$viewValue `$viewValue`} has caused a change 30335 * to {@link ngModel.NgModelController#$modelValue `$modelValue`}. 30336 * It is called with no arguments, and its return value is ignored. 30337 * This can be used in place of additional $watches against the model value. 30338 * 30339 * @property {Object} $error An object hash with all failing validator ids as keys. 30340 * @property {Object} $pending An object hash with all pending validator ids as keys. 30341 * 30342 * @property {boolean} $untouched True if control has not lost focus yet. 30343 * @property {boolean} $touched True if control has lost focus. 30344 * @property {boolean} $pristine True if user has not interacted with the control yet. 30345 * @property {boolean} $dirty True if user has already interacted with the control. 30346 * @property {boolean} $valid True if there is no error. 30347 * @property {boolean} $invalid True if at least one error on the control. 30348 * @property {string} $name The name attribute of the control. 30349 * 30350 * @description 30351 * 30352 * `NgModelController` provides API for the {@link ngModel `ngModel`} directive. 30353 * The controller contains services for data-binding, validation, CSS updates, and value formatting 30354 * and parsing. It purposefully does not contain any logic which deals with DOM rendering or 30355 * listening to DOM events. 30356 * Such DOM related logic should be provided by other directives which make use of 30357 * `NgModelController` for data-binding to control elements. 30358 * AngularJS provides this DOM logic for most {@link input `input`} elements. 30359 * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example 30360 * custom control example} that uses `ngModelController` to bind to `contenteditable` elements. 30361 * 30362 * @example 30363 * ### Custom Control Example 30364 * This example shows how to use `NgModelController` with a custom control to achieve 30365 * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) 30366 * collaborate together to achieve the desired result. 30367 * 30368 * `contenteditable` is an HTML5 attribute, which tells the browser to let the element 30369 * contents be edited in place by the user. 30370 * 30371 * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize} 30372 * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`). 30373 * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks 30374 * that content using the `$sce` service. 30375 * 30376 * <example name="NgModelController" module="customControl" deps="angular-sanitize.js"> 30377 <file name="style.css"> 30378 [contenteditable] { 30379 border: 1px solid black; 30380 background-color: white; 30381 min-height: 20px; 30382 } 30383 30384 .ng-invalid { 30385 border: 1px solid red; 30386 } 30387 30388 </file> 30389 <file name="script.js"> 30390 angular.module('customControl', ['ngSanitize']). 30391 directive('contenteditable', ['$sce', function($sce) { 30392 return { 30393 restrict: 'A', // only activate on element attribute 30394 require: '?ngModel', // get a hold of NgModelController 30395 link: function(scope, element, attrs, ngModel) { 30396 if (!ngModel) return; // do nothing if no ng-model 30397 30398 // Specify how UI should be updated 30399 ngModel.$render = function() { 30400 element.html($sce.getTrustedHtml(ngModel.$viewValue || '')); 30401 }; 30402 30403 // Listen for change events to enable binding 30404 element.on('blur keyup change', function() { 30405 scope.$evalAsync(read); 30406 }); 30407 read(); // initialize 30408 30409 // Write data to the model 30410 function read() { 30411 var html = element.html(); 30412 // When we clear the content editable the browser leaves a <br> behind 30413 // If strip-br attribute is provided then we strip this out 30414 if (attrs.stripBr && html === '<br>') { 30415 html = ''; 30416 } 30417 ngModel.$setViewValue(html); 30418 } 30419 } 30420 }; 30421 }]); 30422 </file> 30423 <file name="index.html"> 30424 <form name="myForm"> 30425 <div contenteditable 30426 name="myWidget" ng-model="userContent" 30427 strip-br="true" 30428 required>Change me!</div> 30429 <span ng-show="myForm.myWidget.$error.required">Required!</span> 30430 <hr> 30431 <textarea ng-model="userContent" aria-label="Dynamic textarea"></textarea> 30432 </form> 30433 </file> 30434 <file name="protractor.js" type="protractor"> 30435 it('should data-bind and become invalid', function() { 30436 if (browser.params.browser === 'safari' || browser.params.browser === 'firefox') { 30437 // SafariDriver can't handle contenteditable 30438 // and Firefox driver can't clear contenteditables very well 30439 return; 30440 } 30441 var contentEditable = element(by.css('[contenteditable]')); 30442 var content = 'Change me!'; 30443 30444 expect(contentEditable.getText()).toEqual(content); 30445 30446 contentEditable.clear(); 30447 contentEditable.sendKeys(protractor.Key.BACK_SPACE); 30448 expect(contentEditable.getText()).toEqual(''); 30449 expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/); 30450 }); 30451 </file> 30452 * </example> 30453 * 30454 * 30455 */ 30456NgModelController.$inject = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$q', '$interpolate']; 30457function NgModelController($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $q, $interpolate) { 30458 this.$viewValue = Number.NaN; 30459 this.$modelValue = Number.NaN; 30460 this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity. 30461 this.$validators = {}; 30462 this.$asyncValidators = {}; 30463 this.$parsers = []; 30464 this.$formatters = []; 30465 this.$viewChangeListeners = []; 30466 this.$untouched = true; 30467 this.$touched = false; 30468 this.$pristine = true; 30469 this.$dirty = false; 30470 this.$valid = true; 30471 this.$invalid = false; 30472 this.$error = {}; // keep invalid keys here 30473 this.$$success = {}; // keep valid keys here 30474 this.$pending = undefined; // keep pending keys here 30475 this.$name = $interpolate($attr.name || '', false)($scope); 30476 this.$$parentForm = nullFormCtrl; 30477 this.$options = defaultModelOptions; 30478 this.$$updateEvents = ''; 30479 // Attach the correct context to the event handler function for updateOn 30480 this.$$updateEventHandler = this.$$updateEventHandler.bind(this); 30481 30482 this.$$parsedNgModel = $parse($attr.ngModel); 30483 this.$$parsedNgModelAssign = this.$$parsedNgModel.assign; 30484 this.$$ngModelGet = this.$$parsedNgModel; 30485 this.$$ngModelSet = this.$$parsedNgModelAssign; 30486 this.$$pendingDebounce = null; 30487 this.$$parserValid = undefined; 30488 this.$$parserName = 'parse'; 30489 30490 this.$$currentValidationRunId = 0; 30491 30492 this.$$scope = $scope; 30493 this.$$rootScope = $scope.$root; 30494 this.$$attr = $attr; 30495 this.$$element = $element; 30496 this.$$animate = $animate; 30497 this.$$timeout = $timeout; 30498 this.$$parse = $parse; 30499 this.$$q = $q; 30500 this.$$exceptionHandler = $exceptionHandler; 30501 30502 setupValidity(this); 30503 setupModelWatcher(this); 30504} 30505 30506NgModelController.prototype = { 30507 $$initGetterSetters: function() { 30508 if (this.$options.getOption('getterSetter')) { 30509 var invokeModelGetter = this.$$parse(this.$$attr.ngModel + '()'), 30510 invokeModelSetter = this.$$parse(this.$$attr.ngModel + '($$$p)'); 30511 30512 this.$$ngModelGet = function($scope) { 30513 var modelValue = this.$$parsedNgModel($scope); 30514 if (isFunction(modelValue)) { 30515 modelValue = invokeModelGetter($scope); 30516 } 30517 return modelValue; 30518 }; 30519 this.$$ngModelSet = function($scope, newValue) { 30520 if (isFunction(this.$$parsedNgModel($scope))) { 30521 invokeModelSetter($scope, {$$$p: newValue}); 30522 } else { 30523 this.$$parsedNgModelAssign($scope, newValue); 30524 } 30525 }; 30526 } else if (!this.$$parsedNgModel.assign) { 30527 throw ngModelMinErr('nonassign', 'Expression \'{0}\' is non-assignable. Element: {1}', 30528 this.$$attr.ngModel, startingTag(this.$$element)); 30529 } 30530 }, 30531 30532 30533 /** 30534 * @ngdoc method 30535 * @name ngModel.NgModelController#$render 30536 * 30537 * @description 30538 * Called when the view needs to be updated. It is expected that the user of the ng-model 30539 * directive will implement this method. 30540 * 30541 * The `$render()` method is invoked in the following situations: 30542 * 30543 * * `$rollbackViewValue()` is called. If we are rolling back the view value to the last 30544 * committed value then `$render()` is called to update the input control. 30545 * * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and 30546 * the `$viewValue` are different from last time. 30547 * 30548 * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of 30549 * `$modelValue` and `$viewValue` are actually different from their previous values. If `$modelValue` 30550 * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be 30551 * invoked if you only change a property on the objects. 30552 */ 30553 $render: noop, 30554 30555 /** 30556 * @ngdoc method 30557 * @name ngModel.NgModelController#$isEmpty 30558 * 30559 * @description 30560 * This is called when we need to determine if the value of an input is empty. 30561 * 30562 * For instance, the required directive does this to work out if the input has data or not. 30563 * 30564 * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`. 30565 * 30566 * You can override this for input directives whose concept of being empty is different from the 30567 * default. The `checkboxInputType` directive does this because in its case a value of `false` 30568 * implies empty. 30569 * 30570 * @param {*} value The value of the input to check for emptiness. 30571 * @returns {boolean} True if `value` is "empty". 30572 */ 30573 $isEmpty: function(value) { 30574 // eslint-disable-next-line no-self-compare 30575 return isUndefined(value) || value === '' || value === null || value !== value; 30576 }, 30577 30578 $$updateEmptyClasses: function(value) { 30579 if (this.$isEmpty(value)) { 30580 this.$$animate.removeClass(this.$$element, NOT_EMPTY_CLASS); 30581 this.$$animate.addClass(this.$$element, EMPTY_CLASS); 30582 } else { 30583 this.$$animate.removeClass(this.$$element, EMPTY_CLASS); 30584 this.$$animate.addClass(this.$$element, NOT_EMPTY_CLASS); 30585 } 30586 }, 30587 30588 /** 30589 * @ngdoc method 30590 * @name ngModel.NgModelController#$setPristine 30591 * 30592 * @description 30593 * Sets the control to its pristine state. 30594 * 30595 * This method can be called to remove the `ng-dirty` class and set the control to its pristine 30596 * state (`ng-pristine` class). A model is considered to be pristine when the control 30597 * has not been changed from when first compiled. 30598 */ 30599 $setPristine: function() { 30600 this.$dirty = false; 30601 this.$pristine = true; 30602 this.$$animate.removeClass(this.$$element, DIRTY_CLASS); 30603 this.$$animate.addClass(this.$$element, PRISTINE_CLASS); 30604 }, 30605 30606 /** 30607 * @ngdoc method 30608 * @name ngModel.NgModelController#$setDirty 30609 * 30610 * @description 30611 * Sets the control to its dirty state. 30612 * 30613 * This method can be called to remove the `ng-pristine` class and set the control to its dirty 30614 * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed 30615 * from when first compiled. 30616 */ 30617 $setDirty: function() { 30618 this.$dirty = true; 30619 this.$pristine = false; 30620 this.$$animate.removeClass(this.$$element, PRISTINE_CLASS); 30621 this.$$animate.addClass(this.$$element, DIRTY_CLASS); 30622 this.$$parentForm.$setDirty(); 30623 }, 30624 30625 /** 30626 * @ngdoc method 30627 * @name ngModel.NgModelController#$setUntouched 30628 * 30629 * @description 30630 * Sets the control to its untouched state. 30631 * 30632 * This method can be called to remove the `ng-touched` class and set the control to its 30633 * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched 30634 * by default, however this function can be used to restore that state if the model has 30635 * already been touched by the user. 30636 */ 30637 $setUntouched: function() { 30638 this.$touched = false; 30639 this.$untouched = true; 30640 this.$$animate.setClass(this.$$element, UNTOUCHED_CLASS, TOUCHED_CLASS); 30641 }, 30642 30643 /** 30644 * @ngdoc method 30645 * @name ngModel.NgModelController#$setTouched 30646 * 30647 * @description 30648 * Sets the control to its touched state. 30649 * 30650 * This method can be called to remove the `ng-untouched` class and set the control to its 30651 * touched state (`ng-touched` class). A model is considered to be touched when the user has 30652 * first focused the control element and then shifted focus away from the control (blur event). 30653 */ 30654 $setTouched: function() { 30655 this.$touched = true; 30656 this.$untouched = false; 30657 this.$$animate.setClass(this.$$element, TOUCHED_CLASS, UNTOUCHED_CLASS); 30658 }, 30659 30660 /** 30661 * @ngdoc method 30662 * @name ngModel.NgModelController#$rollbackViewValue 30663 * 30664 * @description 30665 * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`, 30666 * which may be caused by a pending debounced event or because the input is waiting for some 30667 * future event. 30668 * 30669 * If you have an input that uses `ng-model-options` to set up debounced updates or updates that 30670 * depend on special events such as `blur`, there can be a period when the `$viewValue` is out of 30671 * sync with the ngModel's `$modelValue`. 30672 * 30673 * In this case, you can use `$rollbackViewValue()` to manually cancel the debounced / future update 30674 * and reset the input to the last committed view value. 30675 * 30676 * It is also possible that you run into difficulties if you try to update the ngModel's `$modelValue` 30677 * programmatically before these debounced/future events have resolved/occurred, because AngularJS's 30678 * dirty checking mechanism is not able to tell whether the model has actually changed or not. 30679 * 30680 * The `$rollbackViewValue()` method should be called before programmatically changing the model of an 30681 * input which may have such events pending. This is important in order to make sure that the 30682 * input field will be updated with the new model value and any pending operations are cancelled. 30683 * 30684 * @example 30685 * <example name="ng-model-cancel-update" module="cancel-update-example"> 30686 * <file name="app.js"> 30687 * angular.module('cancel-update-example', []) 30688 * 30689 * .controller('CancelUpdateController', ['$scope', function($scope) { 30690 * $scope.model = {value1: '', value2: ''}; 30691 * 30692 * $scope.setEmpty = function(e, value, rollback) { 30693 * if (e.keyCode === 27) { 30694 * e.preventDefault(); 30695 * if (rollback) { 30696 * $scope.myForm[value].$rollbackViewValue(); 30697 * } 30698 * $scope.model[value] = ''; 30699 * } 30700 * }; 30701 * }]); 30702 * </file> 30703 * <file name="index.html"> 30704 * <div ng-controller="CancelUpdateController"> 30705 * <p>Both of these inputs are only updated if they are blurred. Hitting escape should 30706 * empty them. Follow these steps and observe the difference:</p> 30707 * <ol> 30708 * <li>Type something in the input. You will see that the model is not yet updated</li> 30709 * <li>Press the Escape key. 30710 * <ol> 30711 * <li> In the first example, nothing happens, because the model is already '', and no 30712 * update is detected. If you blur the input, the model will be set to the current view. 30713 * </li> 30714 * <li> In the second example, the pending update is cancelled, and the input is set back 30715 * to the last committed view value (''). Blurring the input does nothing. 30716 * </li> 30717 * </ol> 30718 * </li> 30719 * </ol> 30720 * 30721 * <form name="myForm" ng-model-options="{ updateOn: 'blur' }"> 30722 * <div> 30723 * <p id="inputDescription1">Without $rollbackViewValue():</p> 30724 * <input name="value1" aria-describedby="inputDescription1" ng-model="model.value1" 30725 * ng-keydown="setEmpty($event, 'value1')"> 30726 * value1: "{{ model.value1 }}" 30727 * </div> 30728 * 30729 * <div> 30730 * <p id="inputDescription2">With $rollbackViewValue():</p> 30731 * <input name="value2" aria-describedby="inputDescription2" ng-model="model.value2" 30732 * ng-keydown="setEmpty($event, 'value2', true)"> 30733 * value2: "{{ model.value2 }}" 30734 * </div> 30735 * </form> 30736 * </div> 30737 * </file> 30738 <file name="style.css"> 30739 div { 30740 display: table-cell; 30741 } 30742 div:nth-child(1) { 30743 padding-right: 30px; 30744 } 30745 30746 </file> 30747 * </example> 30748 */ 30749 $rollbackViewValue: function() { 30750 this.$$timeout.cancel(this.$$pendingDebounce); 30751 this.$viewValue = this.$$lastCommittedViewValue; 30752 this.$render(); 30753 }, 30754 30755 /** 30756 * @ngdoc method 30757 * @name ngModel.NgModelController#$validate 30758 * 30759 * @description 30760 * Runs each of the registered validators (first synchronous validators and then 30761 * asynchronous validators). 30762 * If the validity changes to invalid, the model will be set to `undefined`, 30763 * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`. 30764 * If the validity changes to valid, it will set the model to the last available valid 30765 * `$modelValue`, i.e. either the last parsed value or the last value set from the scope. 30766 */ 30767 $validate: function() { 30768 30769 // ignore $validate before model is initialized 30770 if (isNumberNaN(this.$modelValue)) { 30771 return; 30772 } 30773 30774 var viewValue = this.$$lastCommittedViewValue; 30775 // Note: we use the $$rawModelValue as $modelValue might have been 30776 // set to undefined during a view -> model update that found validation 30777 // errors. We can't parse the view here, since that could change 30778 // the model although neither viewValue nor the model on the scope changed 30779 var modelValue = this.$$rawModelValue; 30780 30781 var prevValid = this.$valid; 30782 var prevModelValue = this.$modelValue; 30783 30784 var allowInvalid = this.$options.getOption('allowInvalid'); 30785 30786 var that = this; 30787 this.$$runValidators(modelValue, viewValue, function(allValid) { 30788 // If there was no change in validity, don't update the model 30789 // This prevents changing an invalid modelValue to undefined 30790 if (!allowInvalid && prevValid !== allValid) { 30791 // Note: Don't check this.$valid here, as we could have 30792 // external validators (e.g. calculated on the server), 30793 // that just call $setValidity and need the model value 30794 // to calculate their validity. 30795 that.$modelValue = allValid ? modelValue : undefined; 30796 30797 if (that.$modelValue !== prevModelValue) { 30798 that.$$writeModelToScope(); 30799 } 30800 } 30801 }); 30802 }, 30803 30804 $$runValidators: function(modelValue, viewValue, doneCallback) { 30805 this.$$currentValidationRunId++; 30806 var localValidationRunId = this.$$currentValidationRunId; 30807 var that = this; 30808 30809 // check parser error 30810 if (!processParseErrors()) { 30811 validationDone(false); 30812 return; 30813 } 30814 if (!processSyncValidators()) { 30815 validationDone(false); 30816 return; 30817 } 30818 processAsyncValidators(); 30819 30820 function processParseErrors() { 30821 var errorKey = that.$$parserName; 30822 30823 if (isUndefined(that.$$parserValid)) { 30824 setValidity(errorKey, null); 30825 } else { 30826 if (!that.$$parserValid) { 30827 forEach(that.$validators, function(v, name) { 30828 setValidity(name, null); 30829 }); 30830 forEach(that.$asyncValidators, function(v, name) { 30831 setValidity(name, null); 30832 }); 30833 } 30834 30835 // Set the parse error last, to prevent unsetting it, should a $validators key == parserName 30836 setValidity(errorKey, that.$$parserValid); 30837 return that.$$parserValid; 30838 } 30839 return true; 30840 } 30841 30842 function processSyncValidators() { 30843 var syncValidatorsValid = true; 30844 forEach(that.$validators, function(validator, name) { 30845 var result = Boolean(validator(modelValue, viewValue)); 30846 syncValidatorsValid = syncValidatorsValid && result; 30847 setValidity(name, result); 30848 }); 30849 if (!syncValidatorsValid) { 30850 forEach(that.$asyncValidators, function(v, name) { 30851 setValidity(name, null); 30852 }); 30853 return false; 30854 } 30855 return true; 30856 } 30857 30858 function processAsyncValidators() { 30859 var validatorPromises = []; 30860 var allValid = true; 30861 forEach(that.$asyncValidators, function(validator, name) { 30862 var promise = validator(modelValue, viewValue); 30863 if (!isPromiseLike(promise)) { 30864 throw ngModelMinErr('nopromise', 30865 'Expected asynchronous validator to return a promise but got \'{0}\' instead.', promise); 30866 } 30867 setValidity(name, undefined); 30868 validatorPromises.push(promise.then(function() { 30869 setValidity(name, true); 30870 }, function() { 30871 allValid = false; 30872 setValidity(name, false); 30873 })); 30874 }); 30875 if (!validatorPromises.length) { 30876 validationDone(true); 30877 } else { 30878 that.$$q.all(validatorPromises).then(function() { 30879 validationDone(allValid); 30880 }, noop); 30881 } 30882 } 30883 30884 function setValidity(name, isValid) { 30885 if (localValidationRunId === that.$$currentValidationRunId) { 30886 that.$setValidity(name, isValid); 30887 } 30888 } 30889 30890 function validationDone(allValid) { 30891 if (localValidationRunId === that.$$currentValidationRunId) { 30892 30893 doneCallback(allValid); 30894 } 30895 } 30896 }, 30897 30898 /** 30899 * @ngdoc method 30900 * @name ngModel.NgModelController#$commitViewValue 30901 * 30902 * @description 30903 * Commit a pending update to the `$modelValue`. 30904 * 30905 * Updates may be pending by a debounced event or because the input is waiting for a some future 30906 * event defined in `ng-model-options`. this method is rarely needed as `NgModelController` 30907 * usually handles calling this in response to input events. 30908 */ 30909 $commitViewValue: function() { 30910 var viewValue = this.$viewValue; 30911 30912 this.$$timeout.cancel(this.$$pendingDebounce); 30913 30914 // If the view value has not changed then we should just exit, except in the case where there is 30915 // a native validator on the element. In this case the validation state may have changed even though 30916 // the viewValue has stayed empty. 30917 if (this.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !this.$$hasNativeValidators)) { 30918 return; 30919 } 30920 this.$$updateEmptyClasses(viewValue); 30921 this.$$lastCommittedViewValue = viewValue; 30922 30923 // change to dirty 30924 if (this.$pristine) { 30925 this.$setDirty(); 30926 } 30927 this.$$parseAndValidate(); 30928 }, 30929 30930 $$parseAndValidate: function() { 30931 var viewValue = this.$$lastCommittedViewValue; 30932 var modelValue = viewValue; 30933 var that = this; 30934 30935 this.$$parserValid = isUndefined(modelValue) ? undefined : true; 30936 30937 // Reset any previous parse error 30938 this.$setValidity(this.$$parserName, null); 30939 this.$$parserName = 'parse'; 30940 30941 if (this.$$parserValid) { 30942 for (var i = 0; i < this.$parsers.length; i++) { 30943 modelValue = this.$parsers[i](modelValue); 30944 if (isUndefined(modelValue)) { 30945 this.$$parserValid = false; 30946 break; 30947 } 30948 } 30949 } 30950 if (isNumberNaN(this.$modelValue)) { 30951 // this.$modelValue has not been touched yet... 30952 this.$modelValue = this.$$ngModelGet(this.$$scope); 30953 } 30954 var prevModelValue = this.$modelValue; 30955 var allowInvalid = this.$options.getOption('allowInvalid'); 30956 this.$$rawModelValue = modelValue; 30957 30958 if (allowInvalid) { 30959 this.$modelValue = modelValue; 30960 writeToModelIfNeeded(); 30961 } 30962 30963 // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date. 30964 // This can happen if e.g. $setViewValue is called from inside a parser 30965 this.$$runValidators(modelValue, this.$$lastCommittedViewValue, function(allValid) { 30966 if (!allowInvalid) { 30967 // Note: Don't check this.$valid here, as we could have 30968 // external validators (e.g. calculated on the server), 30969 // that just call $setValidity and need the model value 30970 // to calculate their validity. 30971 that.$modelValue = allValid ? modelValue : undefined; 30972 writeToModelIfNeeded(); 30973 } 30974 }); 30975 30976 function writeToModelIfNeeded() { 30977 if (that.$modelValue !== prevModelValue) { 30978 that.$$writeModelToScope(); 30979 } 30980 } 30981 }, 30982 30983 $$writeModelToScope: function() { 30984 this.$$ngModelSet(this.$$scope, this.$modelValue); 30985 forEach(this.$viewChangeListeners, function(listener) { 30986 try { 30987 listener(); 30988 } catch (e) { 30989 // eslint-disable-next-line no-invalid-this 30990 this.$$exceptionHandler(e); 30991 } 30992 }, this); 30993 }, 30994 30995 /** 30996 * @ngdoc method 30997 * @name ngModel.NgModelController#$setViewValue 30998 * 30999 * @description 31000 * Update the view value. 31001 * 31002 * This method should be called when a control wants to change the view value; typically, 31003 * this is done from within a DOM event handler. For example, the {@link ng.directive:input input} 31004 * directive calls it when the value of the input changes and {@link ng.directive:select select} 31005 * calls it when an option is selected. 31006 * 31007 * When `$setViewValue` is called, the new `value` will be staged for committing through the `$parsers` 31008 * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged 31009 * value is sent directly for processing through the `$parsers` pipeline. After this, the `$validators` and 31010 * `$asyncValidators` are called and the value is applied to `$modelValue`. 31011 * Finally, the value is set to the **expression** specified in the `ng-model` attribute and 31012 * all the registered change listeners, in the `$viewChangeListeners` list are called. 31013 * 31014 * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn` 31015 * and the `default` trigger is not listed, all those actions will remain pending until one of the 31016 * `updateOn` events is triggered on the DOM element. 31017 * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions} 31018 * directive is used with a custom debounce for this particular event. 31019 * Note that a `$digest` is only triggered once the `updateOn` events are fired, or if `debounce` 31020 * is specified, once the timer runs out. 31021 * 31022 * When used with standard inputs, the view value will always be a string (which is in some cases 31023 * parsed into another type, such as a `Date` object for `input[date]`.) 31024 * However, custom controls might also pass objects to this method. In this case, we should make 31025 * a copy of the object before passing it to `$setViewValue`. This is because `ngModel` does not 31026 * perform a deep watch of objects, it only looks for a change of identity. If you only change 31027 * the property of the object then ngModel will not realize that the object has changed and 31028 * will not invoke the `$parsers` and `$validators` pipelines. For this reason, you should 31029 * not change properties of the copy once it has been passed to `$setViewValue`. 31030 * Otherwise you may cause the model value on the scope to change incorrectly. 31031 * 31032 * <div class="alert alert-info"> 31033 * In any case, the value passed to the method should always reflect the current value 31034 * of the control. For example, if you are calling `$setViewValue` for an input element, 31035 * you should pass the input DOM value. Otherwise, the control and the scope model become 31036 * out of sync. It's also important to note that `$setViewValue` does not call `$render` or change 31037 * the control's DOM value in any way. If we want to change the control's DOM value 31038 * programmatically, we should update the `ngModel` scope expression. Its new value will be 31039 * picked up by the model controller, which will run it through the `$formatters`, `$render` it 31040 * to update the DOM, and finally call `$validate` on it. 31041 * </div> 31042 * 31043 * @param {*} value value from the view. 31044 * @param {string} trigger Event that triggered the update. 31045 */ 31046 $setViewValue: function(value, trigger) { 31047 this.$viewValue = value; 31048 if (this.$options.getOption('updateOnDefault')) { 31049 this.$$debounceViewValueCommit(trigger); 31050 } 31051 }, 31052 31053 $$debounceViewValueCommit: function(trigger) { 31054 var debounceDelay = this.$options.getOption('debounce'); 31055 31056 if (isNumber(debounceDelay[trigger])) { 31057 debounceDelay = debounceDelay[trigger]; 31058 } else if (isNumber(debounceDelay['default']) && 31059 this.$options.getOption('updateOn').indexOf(trigger) === -1 31060 ) { 31061 debounceDelay = debounceDelay['default']; 31062 } else if (isNumber(debounceDelay['*'])) { 31063 debounceDelay = debounceDelay['*']; 31064 } 31065 31066 this.$$timeout.cancel(this.$$pendingDebounce); 31067 var that = this; 31068 if (debounceDelay > 0) { // this fails if debounceDelay is an object 31069 this.$$pendingDebounce = this.$$timeout(function() { 31070 that.$commitViewValue(); 31071 }, debounceDelay); 31072 } else if (this.$$rootScope.$$phase) { 31073 this.$commitViewValue(); 31074 } else { 31075 this.$$scope.$apply(function() { 31076 that.$commitViewValue(); 31077 }); 31078 } 31079 }, 31080 31081 /** 31082 * @ngdoc method 31083 * 31084 * @name ngModel.NgModelController#$overrideModelOptions 31085 * 31086 * @description 31087 * 31088 * Override the current model options settings programmatically. 31089 * 31090 * The previous `ModelOptions` value will not be modified. Instead, a 31091 * new `ModelOptions` object will inherit from the previous one overriding 31092 * or inheriting settings that are defined in the given parameter. 31093 * 31094 * See {@link ngModelOptions} for information about what options can be specified 31095 * and how model option inheritance works. 31096 * 31097 * <div class="alert alert-warning"> 31098 * **Note:** this function only affects the options set on the `ngModelController`, 31099 * and not the options on the {@link ngModelOptions} directive from which they might have been 31100 * obtained initially. 31101 * </div> 31102 * 31103 * <div class="alert alert-danger"> 31104 * **Note:** it is not possible to override the `getterSetter` option. 31105 * </div> 31106 * 31107 * @param {Object} options a hash of settings to override the previous options 31108 * 31109 */ 31110 $overrideModelOptions: function(options) { 31111 this.$options = this.$options.createChild(options); 31112 this.$$setUpdateOnEvents(); 31113 }, 31114 31115 /** 31116 * @ngdoc method 31117 * 31118 * @name ngModel.NgModelController#$processModelValue 31119 31120 * @description 31121 * 31122 * Runs the model -> view pipeline on the current 31123 * {@link ngModel.NgModelController#$modelValue $modelValue}. 31124 * 31125 * The following actions are performed by this method: 31126 * 31127 * - the `$modelValue` is run through the {@link ngModel.NgModelController#$formatters $formatters} 31128 * and the result is set to the {@link ngModel.NgModelController#$viewValue $viewValue} 31129 * - the `ng-empty` or `ng-not-empty` class is set on the element 31130 * - if the `$viewValue` has changed: 31131 * - {@link ngModel.NgModelController#$render $render} is called on the control 31132 * - the {@link ngModel.NgModelController#$validators $validators} are run and 31133 * the validation status is set. 31134 * 31135 * This method is called by ngModel internally when the bound scope value changes. 31136 * Application developers usually do not have to call this function themselves. 31137 * 31138 * This function can be used when the `$viewValue` or the rendered DOM value are not correctly 31139 * formatted and the `$modelValue` must be run through the `$formatters` again. 31140 * 31141 * @example 31142 * Consider a text input with an autocomplete list (for fruit), where the items are 31143 * objects with a name and an id. 31144 * A user enters `ap` and then selects `Apricot` from the list. 31145 * Based on this, the autocomplete widget will call `$setViewValue({name: 'Apricot', id: 443})`, 31146 * but the rendered value will still be `ap`. 31147 * The widget can then call `ctrl.$processModelValue()` to run the model -> view 31148 * pipeline again, which formats the object to the string `Apricot`, 31149 * then updates the `$viewValue`, and finally renders it in the DOM. 31150 * 31151 * <example module="inputExample" name="ng-model-process"> 31152 <file name="index.html"> 31153 <div ng-controller="inputController" style="display: flex;"> 31154 <div style="margin-right: 30px;"> 31155 Search Fruit: 31156 <basic-autocomplete items="items" on-select="selectedFruit = item"></basic-autocomplete> 31157 </div> 31158 <div> 31159 Model:<br> 31160 <pre>{{selectedFruit | json}}</pre> 31161 </div> 31162 </div> 31163 </file> 31164 <file name="app.js"> 31165 angular.module('inputExample', []) 31166 .controller('inputController', function($scope) { 31167 $scope.items = [ 31168 {name: 'Apricot', id: 443}, 31169 {name: 'Clementine', id: 972}, 31170 {name: 'Durian', id: 169}, 31171 {name: 'Jackfruit', id: 982}, 31172 {name: 'Strawberry', id: 863} 31173 ]; 31174 }) 31175 .component('basicAutocomplete', { 31176 bindings: { 31177 items: '<', 31178 onSelect: '&' 31179 }, 31180 templateUrl: 'autocomplete.html', 31181 controller: function($element, $scope) { 31182 var that = this; 31183 var ngModel; 31184 31185 that.$postLink = function() { 31186 ngModel = $element.find('input').controller('ngModel'); 31187 31188 ngModel.$formatters.push(function(value) { 31189 return (value && value.name) || value; 31190 }); 31191 31192 ngModel.$parsers.push(function(value) { 31193 var match = value; 31194 for (var i = 0; i < that.items.length; i++) { 31195 if (that.items[i].name === value) { 31196 match = that.items[i]; 31197 break; 31198 } 31199 } 31200 31201 return match; 31202 }); 31203 }; 31204 31205 that.selectItem = function(item) { 31206 ngModel.$setViewValue(item); 31207 ngModel.$processModelValue(); 31208 that.onSelect({item: item}); 31209 }; 31210 } 31211 }); 31212 </file> 31213 <file name="autocomplete.html"> 31214 <div> 31215 <input type="search" ng-model="$ctrl.searchTerm" /> 31216 <ul> 31217 <li ng-repeat="item in $ctrl.items | filter:$ctrl.searchTerm"> 31218 <button ng-click="$ctrl.selectItem(item)">{{ item.name }}</button> 31219 </li> 31220 </ul> 31221 </div> 31222 </file> 31223 * </example> 31224 * 31225 */ 31226 $processModelValue: function() { 31227 var viewValue = this.$$format(); 31228 31229 if (this.$viewValue !== viewValue) { 31230 this.$$updateEmptyClasses(viewValue); 31231 this.$viewValue = this.$$lastCommittedViewValue = viewValue; 31232 this.$render(); 31233 // It is possible that model and view value have been updated during render 31234 this.$$runValidators(this.$modelValue, this.$viewValue, noop); 31235 } 31236 }, 31237 31238 /** 31239 * This method is called internally to run the $formatters on the $modelValue 31240 */ 31241 $$format: function() { 31242 var formatters = this.$formatters, 31243 idx = formatters.length; 31244 31245 var viewValue = this.$modelValue; 31246 while (idx--) { 31247 viewValue = formatters[idx](viewValue); 31248 } 31249 31250 return viewValue; 31251 }, 31252 31253 /** 31254 * This method is called internally when the bound scope value changes. 31255 */ 31256 $$setModelValue: function(modelValue) { 31257 this.$modelValue = this.$$rawModelValue = modelValue; 31258 this.$$parserValid = undefined; 31259 this.$processModelValue(); 31260 }, 31261 31262 $$setUpdateOnEvents: function() { 31263 if (this.$$updateEvents) { 31264 this.$$element.off(this.$$updateEvents, this.$$updateEventHandler); 31265 } 31266 31267 this.$$updateEvents = this.$options.getOption('updateOn'); 31268 if (this.$$updateEvents) { 31269 this.$$element.on(this.$$updateEvents, this.$$updateEventHandler); 31270 } 31271 }, 31272 31273 $$updateEventHandler: function(ev) { 31274 this.$$debounceViewValueCommit(ev && ev.type); 31275 } 31276}; 31277 31278function setupModelWatcher(ctrl) { 31279 // model -> value 31280 // Note: we cannot use a normal scope.$watch as we want to detect the following: 31281 // 1. scope value is 'a' 31282 // 2. user enters 'b' 31283 // 3. ng-change kicks in and reverts scope value to 'a' 31284 // -> scope value did not change since the last digest as 31285 // ng-change executes in apply phase 31286 // 4. view should be changed back to 'a' 31287 ctrl.$$scope.$watch(function ngModelWatch(scope) { 31288 var modelValue = ctrl.$$ngModelGet(scope); 31289 31290 // if scope model value and ngModel value are out of sync 31291 // This cannot be moved to the action function, because it would not catch the 31292 // case where the model is changed in the ngChange function or the model setter 31293 if (modelValue !== ctrl.$modelValue && 31294 // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator 31295 // eslint-disable-next-line no-self-compare 31296 (ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue) 31297 ) { 31298 ctrl.$$setModelValue(modelValue); 31299 } 31300 31301 return modelValue; 31302 }); 31303} 31304 31305/** 31306 * @ngdoc method 31307 * @name ngModel.NgModelController#$setValidity 31308 * 31309 * @description 31310 * Change the validity state, and notify the form. 31311 * 31312 * This method can be called within $parsers/$formatters or a custom validation implementation. 31313 * However, in most cases it should be sufficient to use the `ngModel.$validators` and 31314 * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically. 31315 * 31316 * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be assigned 31317 * to either `$error[validationErrorKey]` or `$pending[validationErrorKey]` 31318 * (for unfulfilled `$asyncValidators`), so that it is available for data-binding. 31319 * The `validationErrorKey` should be in camelCase and will get converted into dash-case 31320 * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` 31321 * classes and can be bound to as `{{ someForm.someControl.$error.myError }}`. 31322 * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined), 31323 * or skipped (null). Pending is used for unfulfilled `$asyncValidators`. 31324 * Skipped is used by AngularJS when validators do not run because of parse errors and 31325 * when `$asyncValidators` do not run because any of the `$validators` failed. 31326 */ 31327addSetValidityMethod({ 31328 clazz: NgModelController, 31329 set: function(object, property) { 31330 object[property] = true; 31331 }, 31332 unset: function(object, property) { 31333 delete object[property]; 31334 } 31335}); 31336 31337 31338/** 31339 * @ngdoc directive 31340 * @name ngModel 31341 * @restrict A 31342 * @priority 1 31343 * @param {expression} ngModel assignable {@link guide/expression Expression} to bind to. 31344 * 31345 * @description 31346 * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a 31347 * property on the scope using {@link ngModel.NgModelController NgModelController}, 31348 * which is created and exposed by this directive. 31349 * 31350 * `ngModel` is responsible for: 31351 * 31352 * - Binding the view into the model, which other directives such as `input`, `textarea` or `select` 31353 * require. 31354 * - Providing validation behavior (i.e. required, number, email, url). 31355 * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors). 31356 * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, 31357 * `ng-untouched`, `ng-empty`, `ng-not-empty`) including animations. 31358 * - Registering the control with its parent {@link ng.directive:form form}. 31359 * 31360 * Note: `ngModel` will try to bind to the property given by evaluating the expression on the 31361 * current scope. If the property doesn't already exist on this scope, it will be created 31362 * implicitly and added to the scope. 31363 * 31364 * For best practices on using `ngModel`, see: 31365 * 31366 * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes) 31367 * 31368 * For basic examples, how to use `ngModel`, see: 31369 * 31370 * - {@link ng.directive:input input} 31371 * - {@link input[text] text} 31372 * - {@link input[checkbox] checkbox} 31373 * - {@link input[radio] radio} 31374 * - {@link input[number] number} 31375 * - {@link input[email] email} 31376 * - {@link input[url] url} 31377 * - {@link input[date] date} 31378 * - {@link input[datetime-local] datetime-local} 31379 * - {@link input[time] time} 31380 * - {@link input[month] month} 31381 * - {@link input[week] week} 31382 * - {@link ng.directive:select select} 31383 * - {@link ng.directive:textarea textarea} 31384 * 31385 * ## Complex Models (objects or collections) 31386 * 31387 * By default, `ngModel` watches the model by reference, not value. This is important to know when 31388 * binding inputs to models that are objects (e.g. `Date`) or collections (e.g. arrays). If only properties of the 31389 * object or collection change, `ngModel` will not be notified and so the input will not be re-rendered. 31390 * 31391 * The model must be assigned an entirely new object or collection before a re-rendering will occur. 31392 * 31393 * Some directives have options that will cause them to use a custom `$watchCollection` on the model expression 31394 * - for example, `ngOptions` will do so when a `track by` clause is included in the comprehension expression or 31395 * if the select is given the `multiple` attribute. 31396 * 31397 * The `$watchCollection()` method only does a shallow comparison, meaning that changing properties deeper than the 31398 * first level of the object (or only changing the properties of an item in the collection if it's an array) will still 31399 * not trigger a re-rendering of the model. 31400 * 31401 * ## CSS classes 31402 * The following CSS classes are added and removed on the associated input/select/textarea element 31403 * depending on the validity of the model. 31404 * 31405 * - `ng-valid`: the model is valid 31406 * - `ng-invalid`: the model is invalid 31407 * - `ng-valid-[key]`: for each valid key added by `$setValidity` 31408 * - `ng-invalid-[key]`: for each invalid key added by `$setValidity` 31409 * - `ng-pristine`: the control hasn't been interacted with yet 31410 * - `ng-dirty`: the control has been interacted with 31411 * - `ng-touched`: the control has been blurred 31412 * - `ng-untouched`: the control hasn't been blurred 31413 * - `ng-pending`: any `$asyncValidators` are unfulfilled 31414 * - `ng-empty`: the view does not contain a value or the value is deemed "empty", as defined 31415 * by the {@link ngModel.NgModelController#$isEmpty} method 31416 * - `ng-not-empty`: the view contains a non-empty value 31417 * 31418 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 31419 * 31420 * @animations 31421 * Animations within models are triggered when any of the associated CSS classes are added and removed 31422 * on the input element which is attached to the model. These classes include: `.ng-pristine`, `.ng-dirty`, 31423 * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself. 31424 * The animations that are triggered within ngModel are similar to how they work in ngClass and 31425 * animations can be hooked into using CSS transitions, keyframes as well as JS animations. 31426 * 31427 * The following example shows a simple way to utilize CSS transitions to style an input element 31428 * that has been rendered as invalid after it has been validated: 31429 * 31430 * <pre> 31431 * //be sure to include ngAnimate as a module to hook into more 31432 * //advanced animations 31433 * .my-input { 31434 * transition:0.5s linear all; 31435 * background: white; 31436 * } 31437 * .my-input.ng-invalid { 31438 * background: red; 31439 * color:white; 31440 * } 31441 * </pre> 31442 * 31443 * @example 31444 * ### Basic Usage 31445 * <example deps="angular-animate.js" animations="true" fixBase="true" module="inputExample" name="ng-model"> 31446 <file name="index.html"> 31447 <script> 31448 angular.module('inputExample', []) 31449 .controller('ExampleController', ['$scope', function($scope) { 31450 $scope.val = '1'; 31451 }]); 31452 </script> 31453 <style> 31454 .my-input { 31455 transition:all linear 0.5s; 31456 background: transparent; 31457 } 31458 .my-input.ng-invalid { 31459 color:white; 31460 background: red; 31461 } 31462 </style> 31463 <p id="inputDescription"> 31464 Update input to see transitions when valid/invalid. 31465 Integer is a valid value. 31466 </p> 31467 <form name="testForm" ng-controller="ExampleController"> 31468 <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input" 31469 aria-describedby="inputDescription" /> 31470 </form> 31471 </file> 31472 * </example> 31473 * 31474 * @example 31475 * ### Binding to a getter/setter 31476 * 31477 * Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a 31478 * function that returns a representation of the model when called with zero arguments, and sets 31479 * the internal state of a model when called with an argument. It's sometimes useful to use this 31480 * for models that have an internal representation that's different from what the model exposes 31481 * to the view. 31482 * 31483 * <div class="alert alert-success"> 31484 * **Best Practice:** It's best to keep getters fast because AngularJS is likely to call them more 31485 * frequently than other parts of your code. 31486 * </div> 31487 * 31488 * You use this behavior by adding `ng-model-options="{ getterSetter: true }"` to an element that 31489 * has `ng-model` attached to it. You can also add `ng-model-options="{ getterSetter: true }"` to 31490 * a `<form>`, which will enable this behavior for all `<input>`s within it. See 31491 * {@link ng.directive:ngModelOptions `ngModelOptions`} for more. 31492 * 31493 * The following example shows how to use `ngModel` with a getter/setter: 31494 * 31495 * @example 31496 * <example name="ngModel-getter-setter" module="getterSetterExample"> 31497 <file name="index.html"> 31498 <div ng-controller="ExampleController"> 31499 <form name="userForm"> 31500 <label>Name: 31501 <input type="text" name="userName" 31502 ng-model="user.name" 31503 ng-model-options="{ getterSetter: true }" /> 31504 </label> 31505 </form> 31506 <pre>user.name = <span ng-bind="user.name()"></span></pre> 31507 </div> 31508 </file> 31509 <file name="app.js"> 31510 angular.module('getterSetterExample', []) 31511 .controller('ExampleController', ['$scope', function($scope) { 31512 var _name = 'Brian'; 31513 $scope.user = { 31514 name: function(newName) { 31515 // Note that newName can be undefined for two reasons: 31516 // 1. Because it is called as a getter and thus called with no arguments 31517 // 2. Because the property should actually be set to undefined. This happens e.g. if the 31518 // input is invalid 31519 return arguments.length ? (_name = newName) : _name; 31520 } 31521 }; 31522 }]); 31523 </file> 31524 * </example> 31525 */ 31526var ngModelDirective = ['$rootScope', function($rootScope) { 31527 return { 31528 restrict: 'A', 31529 require: ['ngModel', '^?form', '^?ngModelOptions'], 31530 controller: NgModelController, 31531 // Prelink needs to run before any input directive 31532 // so that we can set the NgModelOptions in NgModelController 31533 // before anyone else uses it. 31534 priority: 1, 31535 compile: function ngModelCompile(element) { 31536 // Setup initial state of the control 31537 element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS); 31538 31539 return { 31540 pre: function ngModelPreLink(scope, element, attr, ctrls) { 31541 var modelCtrl = ctrls[0], 31542 formCtrl = ctrls[1] || modelCtrl.$$parentForm, 31543 optionsCtrl = ctrls[2]; 31544 31545 if (optionsCtrl) { 31546 modelCtrl.$options = optionsCtrl.$options; 31547 } 31548 31549 modelCtrl.$$initGetterSetters(); 31550 31551 // notify others, especially parent forms 31552 formCtrl.$addControl(modelCtrl); 31553 31554 attr.$observe('name', function(newValue) { 31555 if (modelCtrl.$name !== newValue) { 31556 modelCtrl.$$parentForm.$$renameControl(modelCtrl, newValue); 31557 } 31558 }); 31559 31560 scope.$on('$destroy', function() { 31561 modelCtrl.$$parentForm.$removeControl(modelCtrl); 31562 }); 31563 }, 31564 post: function ngModelPostLink(scope, element, attr, ctrls) { 31565 var modelCtrl = ctrls[0]; 31566 modelCtrl.$$setUpdateOnEvents(); 31567 31568 function setTouched() { 31569 modelCtrl.$setTouched(); 31570 } 31571 31572 element.on('blur', function() { 31573 if (modelCtrl.$touched) return; 31574 31575 if ($rootScope.$$phase) { 31576 scope.$evalAsync(setTouched); 31577 } else { 31578 scope.$apply(setTouched); 31579 } 31580 }); 31581 } 31582 }; 31583 } 31584 }; 31585}]; 31586 31587/* exported defaultModelOptions */ 31588var defaultModelOptions; 31589var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/; 31590 31591/** 31592 * @ngdoc type 31593 * @name ModelOptions 31594 * @description 31595 * A container for the options set by the {@link ngModelOptions} directive 31596 */ 31597function ModelOptions(options) { 31598 this.$$options = options; 31599} 31600 31601ModelOptions.prototype = { 31602 31603 /** 31604 * @ngdoc method 31605 * @name ModelOptions#getOption 31606 * @param {string} name the name of the option to retrieve 31607 * @returns {*} the value of the option 31608 * @description 31609 * Returns the value of the given option 31610 */ 31611 getOption: function(name) { 31612 return this.$$options[name]; 31613 }, 31614 31615 /** 31616 * @ngdoc method 31617 * @name ModelOptions#createChild 31618 * @param {Object} options a hash of options for the new child that will override the parent's options 31619 * @return {ModelOptions} a new `ModelOptions` object initialized with the given options. 31620 */ 31621 createChild: function(options) { 31622 var inheritAll = false; 31623 31624 // make a shallow copy 31625 options = extend({}, options); 31626 31627 // Inherit options from the parent if specified by the value `"$inherit"` 31628 forEach(options, /** @this */ function(option, key) { 31629 if (option === '$inherit') { 31630 if (key === '*') { 31631 inheritAll = true; 31632 } else { 31633 options[key] = this.$$options[key]; 31634 // `updateOn` is special so we must also inherit the `updateOnDefault` option 31635 if (key === 'updateOn') { 31636 options.updateOnDefault = this.$$options.updateOnDefault; 31637 } 31638 } 31639 } else { 31640 if (key === 'updateOn') { 31641 // If the `updateOn` property contains the `default` event then we have to remove 31642 // it from the event list and set the `updateOnDefault` flag. 31643 options.updateOnDefault = false; 31644 options[key] = trim(option.replace(DEFAULT_REGEXP, function() { 31645 options.updateOnDefault = true; 31646 return ' '; 31647 })); 31648 } 31649 } 31650 }, this); 31651 31652 if (inheritAll) { 31653 // We have a property of the form: `"*": "$inherit"` 31654 delete options['*']; 31655 defaults(options, this.$$options); 31656 } 31657 31658 // Finally add in any missing defaults 31659 defaults(options, defaultModelOptions.$$options); 31660 31661 return new ModelOptions(options); 31662 } 31663}; 31664 31665 31666defaultModelOptions = new ModelOptions({ 31667 updateOn: '', 31668 updateOnDefault: true, 31669 debounce: 0, 31670 getterSetter: false, 31671 allowInvalid: false, 31672 timezone: null 31673}); 31674 31675 31676/** 31677 * @ngdoc directive 31678 * @name ngModelOptions 31679 * @restrict A 31680 * @priority 10 31681 * 31682 * @description 31683 * This directive allows you to modify the behaviour of {@link ngModel} directives within your 31684 * application. You can specify an `ngModelOptions` directive on any element. All {@link ngModel} 31685 * directives will use the options of their nearest `ngModelOptions` ancestor. 31686 * 31687 * The `ngModelOptions` settings are found by evaluating the value of the attribute directive as 31688 * an AngularJS expression. This expression should evaluate to an object, whose properties contain 31689 * the settings. For example: `<div ng-model-options="{ debounce: 100 }"`. 31690 * 31691 * ## Inheriting Options 31692 * 31693 * You can specify that an `ngModelOptions` setting should be inherited from a parent `ngModelOptions` 31694 * directive by giving it the value of `"$inherit"`. 31695 * Then it will inherit that setting from the first `ngModelOptions` directive found by traversing up the 31696 * DOM tree. If there is no ancestor element containing an `ngModelOptions` directive then default settings 31697 * will be used. 31698 * 31699 * For example given the following fragment of HTML 31700 * 31701 * 31702 * ```html 31703 * <div ng-model-options="{ allowInvalid: true, debounce: 200 }"> 31704 * <form ng-model-options="{ updateOn: 'blur', allowInvalid: '$inherit' }"> 31705 * <input ng-model-options="{ updateOn: 'default', allowInvalid: '$inherit' }" /> 31706 * </form> 31707 * </div> 31708 * ``` 31709 * 31710 * the `input` element will have the following settings 31711 * 31712 * ```js 31713 * { allowInvalid: true, updateOn: 'default', debounce: 0 } 31714 * ``` 31715 * 31716 * Notice that the `debounce` setting was not inherited and used the default value instead. 31717 * 31718 * You can specify that all undefined settings are automatically inherited from an ancestor by 31719 * including a property with key of `"*"` and value of `"$inherit"`. 31720 * 31721 * For example given the following fragment of HTML 31722 * 31723 * 31724 * ```html 31725 * <div ng-model-options="{ allowInvalid: true, debounce: 200 }"> 31726 * <form ng-model-options="{ updateOn: 'blur', "*": '$inherit' }"> 31727 * <input ng-model-options="{ updateOn: 'default', "*": '$inherit' }" /> 31728 * </form> 31729 * </div> 31730 * ``` 31731 * 31732 * the `input` element will have the following settings 31733 * 31734 * ```js 31735 * { allowInvalid: true, updateOn: 'default', debounce: 200 } 31736 * ``` 31737 * 31738 * Notice that the `debounce` setting now inherits the value from the outer `<div>` element. 31739 * 31740 * If you are creating a reusable component then you should be careful when using `"*": "$inherit"` 31741 * since you may inadvertently inherit a setting in the future that changes the behavior of your component. 31742 * 31743 * 31744 * ## Triggering and debouncing model updates 31745 * 31746 * The `updateOn` and `debounce` properties allow you to specify a custom list of events that will 31747 * trigger a model update and/or a debouncing delay so that the actual update only takes place when 31748 * a timer expires; this timer will be reset after another change takes place. 31749 * 31750 * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might 31751 * be different from the value in the actual model. This means that if you update the model you 31752 * should also invoke {@link ngModel.NgModelController#$rollbackViewValue} on the relevant input field in 31753 * order to make sure it is synchronized with the model and that any debounced action is canceled. 31754 * 31755 * The easiest way to reference the control's {@link ngModel.NgModelController#$rollbackViewValue} 31756 * method is by making sure the input is placed inside a form that has a `name` attribute. This is 31757 * important because `form` controllers are published to the related scope under the name in their 31758 * `name` attribute. 31759 * 31760 * Any pending changes will take place immediately when an enclosing form is submitted via the 31761 * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` 31762 * to have access to the updated model. 31763 * 31764 * ### Overriding immediate updates 31765 * 31766 * The following example shows how to override immediate updates. Changes on the inputs within the 31767 * form will update the model only when the control loses focus (blur event). If `escape` key is 31768 * pressed while the input field is focused, the value is reset to the value in the current model. 31769 * 31770 * <example name="ngModelOptions-directive-blur" module="optionsExample"> 31771 * <file name="index.html"> 31772 * <div ng-controller="ExampleController"> 31773 * <form name="userForm"> 31774 * <label> 31775 * Name: 31776 * <input type="text" name="userName" 31777 * ng-model="user.name" 31778 * ng-model-options="{ updateOn: 'blur' }" 31779 * ng-keyup="cancel($event)" /> 31780 * </label><br /> 31781 * <label> 31782 * Other data: 31783 * <input type="text" ng-model="user.data" /> 31784 * </label><br /> 31785 * </form> 31786 * <pre>user.name = <span ng-bind="user.name"></span></pre> 31787 * </div> 31788 * </file> 31789 * <file name="app.js"> 31790 * angular.module('optionsExample', []) 31791 * .controller('ExampleController', ['$scope', function($scope) { 31792 * $scope.user = { name: 'say', data: '' }; 31793 * 31794 * $scope.cancel = function(e) { 31795 * if (e.keyCode === 27) { 31796 * $scope.userForm.userName.$rollbackViewValue(); 31797 * } 31798 * }; 31799 * }]); 31800 * </file> 31801 * <file name="protractor.js" type="protractor"> 31802 * var model = element(by.binding('user.name')); 31803 * var input = element(by.model('user.name')); 31804 * var other = element(by.model('user.data')); 31805 * 31806 * it('should allow custom events', function() { 31807 * input.sendKeys(' hello'); 31808 * input.click(); 31809 * expect(model.getText()).toEqual('say'); 31810 * other.click(); 31811 * expect(model.getText()).toEqual('say hello'); 31812 * }); 31813 * 31814 * it('should $rollbackViewValue when model changes', function() { 31815 * input.sendKeys(' hello'); 31816 * expect(input.getAttribute('value')).toEqual('say hello'); 31817 * input.sendKeys(protractor.Key.ESCAPE); 31818 * expect(input.getAttribute('value')).toEqual('say'); 31819 * other.click(); 31820 * expect(model.getText()).toEqual('say'); 31821 * }); 31822 * </file> 31823 * </example> 31824 * 31825 * ### Debouncing updates 31826 * 31827 * The next example shows how to debounce model changes. Model will be updated only 1 sec after last change. 31828 * If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty. 31829 * 31830 * <example name="ngModelOptions-directive-debounce" module="optionsExample"> 31831 * <file name="index.html"> 31832 * <div ng-controller="ExampleController"> 31833 * <form name="userForm"> 31834 * Name: 31835 * <input type="text" name="userName" 31836 * ng-model="user.name" 31837 * ng-model-options="{ debounce: 1000 }" /> 31838 * <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button><br /> 31839 * </form> 31840 * <pre>user.name = <span ng-bind="user.name"></span></pre> 31841 * </div> 31842 * </file> 31843 * <file name="app.js"> 31844 * angular.module('optionsExample', []) 31845 * .controller('ExampleController', ['$scope', function($scope) { 31846 * $scope.user = { name: 'say' }; 31847 * }]); 31848 * </file> 31849 * </example> 31850 * 31851 * ### Default events, extra triggers, and catch-all debounce values 31852 * 31853 * This example shows the relationship between "default" update events and 31854 * additional `updateOn` triggers. 31855 * 31856 * `default` events are those that are bound to the control, and when fired, update the `$viewValue` 31857 * via {@link ngModel.NgModelController#$setViewValue $setViewValue}. Every event that is not listed 31858 * in `updateOn` is considered a "default" event, since different control types have different 31859 * default events. 31860 * 31861 * The control in this example updates by "default", "click", and "blur", with different `debounce` 31862 * values. You can see that "click" doesn't have an individual `debounce` value - 31863 * therefore it uses the `*` debounce value. 31864 * 31865 * There is also a button that calls {@link ngModel.NgModelController#$setViewValue $setViewValue} 31866 * directly with a "custom" event. Since "custom" is not defined in the `updateOn` list, 31867 * it is considered a "default" event and will update the 31868 * control if "default" is defined in `updateOn`, and will receive the "default" debounce value. 31869 * Note that this is just to illustrate how custom controls would possibly call `$setViewValue`. 31870 * 31871 * You can change the `updateOn` and `debounce` configuration to test different scenarios. This 31872 * is done with {@link ngModel.NgModelController#$overrideModelOptions $overrideModelOptions}. 31873 * 31874 <example name="ngModelOptions-advanced" module="optionsExample"> 31875 <file name="index.html"> 31876 <model-update-demo></model-update-demo> 31877 </file> 31878 <file name="app.js"> 31879 angular.module('optionsExample', []) 31880 .component('modelUpdateDemo', { 31881 templateUrl: 'template.html', 31882 controller: function() { 31883 this.name = 'Chinua'; 31884 31885 this.options = { 31886 updateOn: 'default blur click', 31887 debounce: { 31888 default: 2000, 31889 blur: 0, 31890 '*': 1000 31891 } 31892 }; 31893 31894 this.updateEvents = function() { 31895 var eventList = this.options.updateOn.split(' '); 31896 eventList.push('*'); 31897 var events = {}; 31898 31899 for (var i = 0; i < eventList.length; i++) { 31900 events[eventList[i]] = this.options.debounce[eventList[i]]; 31901 } 31902 31903 this.events = events; 31904 }; 31905 31906 this.updateOptions = function() { 31907 var options = angular.extend(this.options, { 31908 updateOn: Object.keys(this.events).join(' ').replace('*', ''), 31909 debounce: this.events 31910 }); 31911 31912 this.form.input.$overrideModelOptions(options); 31913 }; 31914 31915 // Initialize the event form 31916 this.updateEvents(); 31917 } 31918 }); 31919 </file> 31920 <file name="template.html"> 31921 <form name="$ctrl.form"> 31922 Input: <input type="text" name="input" ng-model="$ctrl.name" ng-model-options="$ctrl.options" /> 31923 </form> 31924 Model: <tt>{{$ctrl.name}}</tt> 31925 <hr> 31926 <button ng-click="$ctrl.form.input.$setViewValue('some value', 'custom')">Trigger setViewValue with 'some value' and 'custom' event</button> 31927 31928 <hr> 31929 <form ng-submit="$ctrl.updateOptions()"> 31930 <b>updateOn</b><br> 31931 <input type="text" ng-model="$ctrl.options.updateOn" ng-change="$ctrl.updateEvents()" ng-model-options="{debounce: 500}"> 31932 31933 <table> 31934 <tr> 31935 <th>Option</th> 31936 <th>Debounce value</th> 31937 </tr> 31938 <tr ng-repeat="(key, value) in $ctrl.events"> 31939 <td>{{key}}</td> 31940 <td><input type="number" ng-model="$ctrl.events[key]" /></td> 31941 </tr> 31942 </table> 31943 31944 <br> 31945 <input type="submit" value="Update options"> 31946 </form> 31947 </file> 31948 </example> 31949 * 31950 * 31951 * ## Model updates and validation 31952 * 31953 * The default behaviour in `ngModel` is that the model value is set to `undefined` when the 31954 * validation determines that the value is invalid. By setting the `allowInvalid` property to true, 31955 * the model will still be updated even if the value is invalid. 31956 * 31957 * 31958 * ## Connecting to the scope 31959 * 31960 * By setting the `getterSetter` property to true you are telling ngModel that the `ngModel` expression 31961 * on the scope refers to a "getter/setter" function rather than the value itself. 31962 * 31963 * The following example shows how to bind to getter/setters: 31964 * 31965 * <example name="ngModelOptions-directive-getter-setter" module="getterSetterExample"> 31966 * <file name="index.html"> 31967 * <div ng-controller="ExampleController"> 31968 * <form name="userForm"> 31969 * <label> 31970 * Name: 31971 * <input type="text" name="userName" 31972 * ng-model="user.name" 31973 * ng-model-options="{ getterSetter: true }" /> 31974 * </label> 31975 * </form> 31976 * <pre>user.name = <span ng-bind="user.name()"></span></pre> 31977 * </div> 31978 * </file> 31979 * <file name="app.js"> 31980 * angular.module('getterSetterExample', []) 31981 * .controller('ExampleController', ['$scope', function($scope) { 31982 * var _name = 'Brian'; 31983 * $scope.user = { 31984 * name: function(newName) { 31985 * return angular.isDefined(newName) ? (_name = newName) : _name; 31986 * } 31987 * }; 31988 * }]); 31989 * </file> 31990 * </example> 31991 * 31992 * 31993 * ## Programmatically changing options 31994 * 31995 * The `ngModelOptions` expression is only evaluated once when the directive is linked; it is not 31996 * watched for changes. However, it is possible to override the options on a single 31997 * {@link ngModel.NgModelController} instance with 31998 * {@link ngModel.NgModelController#$overrideModelOptions `NgModelController#$overrideModelOptions()`}. 31999 * See also the example for 32000 * {@link ngModelOptions#default-events-extra-triggers-and-catch-all-debounce-values 32001 * Default events, extra triggers, and catch-all debounce values}. 32002 * 32003 * 32004 * ## Specifying timezones 32005 * 32006 * You can specify the timezone that date/time input directives expect by providing its name in the 32007 * `timezone` property. 32008 * 32009 * 32010 * ## Formatting the value of `time` and `datetime-local` 32011 * 32012 * With the options `timeSecondsFormat` and `timeStripZeroSeconds` it is possible to adjust the value 32013 * that is displayed in the control. Note that browsers may apply their own formatting 32014 * in the user interface. 32015 * 32016 <example name="ngModelOptions-time-format" module="timeExample"> 32017 <file name="index.html"> 32018 <time-example></time-example> 32019 </file> 32020 <file name="script.js"> 32021 angular.module('timeExample', []) 32022 .component('timeExample', { 32023 templateUrl: 'timeExample.html', 32024 controller: function() { 32025 this.time = new Date(1970, 0, 1, 14, 57, 0); 32026 32027 this.options = { 32028 timeSecondsFormat: 'ss', 32029 timeStripZeroSeconds: true 32030 }; 32031 32032 this.optionChange = function() { 32033 this.timeForm.timeFormatted.$overrideModelOptions(this.options); 32034 this.time = new Date(this.time); 32035 }; 32036 } 32037 }); 32038 </file> 32039 <file name="timeExample.html"> 32040 <form name="$ctrl.timeForm"> 32041 <strong>Default</strong>: 32042 <input type="time" ng-model="$ctrl.time" step="any" /><br> 32043 <strong>With options</strong>: 32044 <input type="time" name="timeFormatted" ng-model="$ctrl.time" step="any" ng-model-options="$ctrl.options" /> 32045 <br> 32046 32047 Options:<br> 32048 <code>timeSecondsFormat</code>: 32049 <input 32050 type="text" 32051 ng-model="$ctrl.options.timeSecondsFormat" 32052 ng-change="$ctrl.optionChange()"> 32053 <br> 32054 <code>timeStripZeroSeconds</code>: 32055 <input 32056 type="checkbox" 32057 ng-model="$ctrl.options.timeStripZeroSeconds" 32058 ng-change="$ctrl.optionChange()"> 32059 </form> 32060 </file> 32061 * </example> 32062 * 32063 * @param {Object} ngModelOptions options to apply to {@link ngModel} directives on this element and 32064 * and its descendents. 32065 * 32066 * **General options**: 32067 * 32068 * - `updateOn`: string specifying which event should the input be bound to. You can set several 32069 * events using an space delimited list. There is a special event called `default` that 32070 * matches the default events belonging to the control. These are the events that are bound to 32071 * the control, and when fired, update the `$viewValue` via `$setViewValue`. 32072 * 32073 * `ngModelOptions` considers every event that is not listed in `updateOn` a "default" event, 32074 * since different control types use different default events. 32075 * 32076 * See also the section {@link ngModelOptions#triggering-and-debouncing-model-updates 32077 * Triggering and debouncing model updates}. 32078 * 32079 * - `debounce`: integer value which contains the debounce model update value in milliseconds. A 32080 * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a 32081 * custom value for each event. For example: 32082 * ``` 32083 * ng-model-options="{ 32084 * updateOn: 'default blur', 32085 * debounce: { 'default': 500, 'blur': 0 } 32086 * }" 32087 * ``` 32088 * You can use the `*` key to specify a debounce value that applies to all events that are not 32089 * specifically listed. In the following example, `mouseup` would have a debounce delay of 1000: 32090 * ``` 32091 * ng-model-options="{ 32092 * updateOn: 'default blur mouseup', 32093 * debounce: { 'default': 500, 'blur': 0, '*': 1000 } 32094 * }" 32095 * ``` 32096 * - `allowInvalid`: boolean value which indicates that the model can be set with values that did 32097 * not validate correctly instead of the default behavior of setting the model to undefined. 32098 * - `getterSetter`: boolean value which determines whether or not to treat functions bound to 32099 * `ngModel` as getters/setters. 32100 * 32101 * 32102 * **Input-type specific options**: 32103 * 32104 * - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for 32105 * `<input type="date" />`, `<input type="time" />`, ... . It understands UTC/GMT and the 32106 * continental US time zone abbreviations, but for general use, use a time zone offset, for 32107 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian) 32108 * If not specified, the timezone of the browser will be used. 32109 * Note that changing the timezone will have no effect on the current date, and is only applied after 32110 * the next input / model change. 32111 * 32112 * - `timeSecondsFormat`: Defines if the `time` and `datetime-local` types should show seconds and 32113 * milliseconds. The option follows the format string of {@link date date filter}. 32114 * By default, the options is `undefined` which is equal to `'ss.sss'` (seconds and milliseconds). 32115 * The other options are `'ss'` (strips milliseconds), and `''` (empty string), which strips both 32116 * seconds and milliseconds. 32117 * Note that browsers that support `time` and `datetime-local` require the hour and minutes 32118 * part of the time string, and may show the value differently in the user interface. 32119 * {@link ngModelOptions#formatting-the-value-of-time-and-datetime-local- See the example}. 32120 * 32121 * - `timeStripZeroSeconds`: Defines if the `time` and `datetime-local` types should strip the 32122 * seconds and milliseconds from the formatted value if they are zero. This option is applied 32123 * after `timeSecondsFormat`. 32124 * This option can be used to make the formatting consistent over different browsers, as some 32125 * browsers with support for `time` will natively hide the milliseconds and 32126 * seconds if they are zero, but others won't, and browsers that don't implement these input 32127 * types will always show the full string. 32128 * {@link ngModelOptions#formatting-the-value-of-time-and-datetime-local- See the example}. 32129 * 32130 */ 32131var ngModelOptionsDirective = function() { 32132 NgModelOptionsController.$inject = ['$attrs', '$scope']; 32133 function NgModelOptionsController($attrs, $scope) { 32134 this.$$attrs = $attrs; 32135 this.$$scope = $scope; 32136 } 32137 NgModelOptionsController.prototype = { 32138 $onInit: function() { 32139 var parentOptions = this.parentCtrl ? this.parentCtrl.$options : defaultModelOptions; 32140 var modelOptionsDefinition = this.$$scope.$eval(this.$$attrs.ngModelOptions); 32141 32142 this.$options = parentOptions.createChild(modelOptionsDefinition); 32143 } 32144 }; 32145 32146 return { 32147 restrict: 'A', 32148 // ngModelOptions needs to run before ngModel and input directives 32149 priority: 10, 32150 require: {parentCtrl: '?^^ngModelOptions'}, 32151 bindToController: true, 32152 controller: NgModelOptionsController 32153 }; 32154}; 32155 32156 32157// shallow copy over values from `src` that are not already specified on `dst` 32158function defaults(dst, src) { 32159 forEach(src, function(value, key) { 32160 if (!isDefined(dst[key])) { 32161 dst[key] = value; 32162 } 32163 }); 32164} 32165 32166/** 32167 * @ngdoc directive 32168 * @name ngNonBindable 32169 * @restrict AC 32170 * @priority 1000 32171 * @element ANY 32172 * 32173 * @description 32174 * The `ngNonBindable` directive tells AngularJS not to compile or bind the contents of the current 32175 * DOM element, including directives on the element itself that have a lower priority than 32176 * `ngNonBindable`. This is useful if the element contains what appears to be AngularJS directives 32177 * and bindings but which should be ignored by AngularJS. This could be the case if you have a site 32178 * that displays snippets of code, for instance. 32179 * 32180 * @example 32181 * In this example there are two locations where a simple interpolation binding (`{{}}`) is present, 32182 * but the one wrapped in `ngNonBindable` is left alone. 32183 * 32184 <example name="ng-non-bindable"> 32185 <file name="index.html"> 32186 <div>Normal: {{1 + 2}}</div> 32187 <div ng-non-bindable>Ignored: {{1 + 2}}</div> 32188 </file> 32189 <file name="protractor.js" type="protractor"> 32190 it('should check ng-non-bindable', function() { 32191 expect(element(by.binding('1 + 2')).getText()).toContain('3'); 32192 expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/); 32193 }); 32194 </file> 32195 </example> 32196 */ 32197var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); 32198 32199/* exported ngOptionsDirective */ 32200 32201/* global jqLiteRemove */ 32202 32203var ngOptionsMinErr = minErr('ngOptions'); 32204 32205/** 32206 * @ngdoc directive 32207 * @name ngOptions 32208 * @restrict A 32209 * 32210 * @description 32211 * 32212 * The `ngOptions` attribute can be used to dynamically generate a list of `<option>` 32213 * elements for the `<select>` element using the array or object obtained by evaluating the 32214 * `ngOptions` comprehension expression. 32215 * 32216 * In many cases, {@link ng.directive:ngRepeat ngRepeat} can be used on `<option>` elements instead of 32217 * `ngOptions` to achieve a similar result. However, `ngOptions` provides some benefits: 32218 * - more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the 32219 * comprehension expression 32220 * - reduced memory consumption by not creating a new scope for each repeated instance 32221 * - increased render speed by creating the options in a documentFragment instead of individually 32222 * 32223 * When an item in the `<select>` menu is selected, the array element or object property 32224 * represented by the selected option will be bound to the model identified by the `ngModel` 32225 * directive. 32226 * 32227 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can 32228 * be nested into the `<select>` element. This element will then represent the `null` or "not selected" 32229 * option. See example below for demonstration. 32230 * 32231 * ## Complex Models (objects or collections) 32232 * 32233 * By default, `ngModel` watches the model by reference, not value. This is important to know when 32234 * binding the select to a model that is an object or a collection. 32235 * 32236 * One issue occurs if you want to preselect an option. For example, if you set 32237 * the model to an object that is equal to an object in your collection, `ngOptions` won't be able to set the selection, 32238 * because the objects are not identical. So by default, you should always reference the item in your collection 32239 * for preselections, e.g.: `$scope.selected = $scope.collection[3]`. 32240 * 32241 * Another solution is to use a `track by` clause, because then `ngOptions` will track the identity 32242 * of the item not by reference, but by the result of the `track by` expression. For example, if your 32243 * collection items have an id property, you would `track by item.id`. 32244 * 32245 * A different issue with objects or collections is that ngModel won't detect if an object property or 32246 * a collection item changes. For that reason, `ngOptions` additionally watches the model using 32247 * `$watchCollection`, when the expression contains a `track by` clause or the the select has the `multiple` attribute. 32248 * This allows ngOptions to trigger a re-rendering of the options even if the actual object/collection 32249 * has not changed identity, but only a property on the object or an item in the collection changes. 32250 * 32251 * Note that `$watchCollection` does a shallow comparison of the properties of the object (or the items in the collection 32252 * if the model is an array). This means that changing a property deeper than the first level inside the 32253 * object/collection will not trigger a re-rendering. 32254 * 32255 * ## `select` **`as`** 32256 * 32257 * Using `select` **`as`** will bind the result of the `select` expression to the model, but 32258 * the value of the `<select>` and `<option>` html elements will be either the index (for array data sources) 32259 * or property name (for object data sources) of the value within the collection. If a **`track by`** expression 32260 * is used, the result of that expression will be set as the value of the `option` and `select` elements. 32261 * 32262 * 32263 * ### `select` **`as`** and **`track by`** 32264 * 32265 * <div class="alert alert-warning"> 32266 * Be careful when using `select` **`as`** and **`track by`** in the same expression. 32267 * </div> 32268 * 32269 * Given this array of items on the $scope: 32270 * 32271 * ```js 32272 * $scope.items = [{ 32273 * id: 1, 32274 * label: 'aLabel', 32275 * subItem: { name: 'aSubItem' } 32276 * }, { 32277 * id: 2, 32278 * label: 'bLabel', 32279 * subItem: { name: 'bSubItem' } 32280 * }]; 32281 * ``` 32282 * 32283 * This will work: 32284 * 32285 * ```html 32286 * <select ng-options="item as item.label for item in items track by item.id" ng-model="selected"></select> 32287 * ``` 32288 * ```js 32289 * $scope.selected = $scope.items[0]; 32290 * ``` 32291 * 32292 * but this will not work: 32293 * 32294 * ```html 32295 * <select ng-options="item.subItem as item.label for item in items track by item.id" ng-model="selected"></select> 32296 * ``` 32297 * ```js 32298 * $scope.selected = $scope.items[0].subItem; 32299 * ``` 32300 * 32301 * In both examples, the **`track by`** expression is applied successfully to each `item` in the 32302 * `items` array. Because the selected option has been set programmatically in the controller, the 32303 * **`track by`** expression is also applied to the `ngModel` value. In the first example, the 32304 * `ngModel` value is `items[0]` and the **`track by`** expression evaluates to `items[0].id` with 32305 * no issue. In the second example, the `ngModel` value is `items[0].subItem` and the **`track by`** 32306 * expression evaluates to `items[0].subItem.id` (which is undefined). As a result, the model value 32307 * is not matched against any `<option>` and the `<select>` appears as having no selected value. 32308 * 32309 * 32310 * @param {string} ngModel Assignable AngularJS expression to data-bind to. 32311 * @param {comprehension_expression} ngOptions in one of the following forms: 32312 * 32313 * * for array data sources: 32314 * * `label` **`for`** `value` **`in`** `array` 32315 * * `select` **`as`** `label` **`for`** `value` **`in`** `array` 32316 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` 32317 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` 32318 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` 32319 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` 32320 * * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr` 32321 * (for including a filter with `track by`) 32322 * * for object data sources: 32323 * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 32324 * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 32325 * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object` 32326 * * `label` **`disable when`** `disable` **`for (`**`key`**`,`** `value`**`) in`** `object` 32327 * * `select` **`as`** `label` **`group by`** `group` 32328 * **`for` `(`**`key`**`,`** `value`**`) in`** `object` 32329 * * `select` **`as`** `label` **`disable when`** `disable` 32330 * **`for` `(`**`key`**`,`** `value`**`) in`** `object` 32331 * 32332 * Where: 32333 * 32334 * * `array` / `object`: an expression which evaluates to an array / object to iterate over. 32335 * * `value`: local variable which will refer to each item in the `array` or each property value 32336 * of `object` during iteration. 32337 * * `key`: local variable which will refer to a property name in `object` during iteration. 32338 * * `label`: The result of this expression will be the label for `<option>` element. The 32339 * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`). 32340 * * `select`: The result of this expression will be bound to the model of the parent `<select>` 32341 * element. If not specified, `select` expression will default to `value`. 32342 * * `group`: The result of this expression will be used to group options using the `<optgroup>` 32343 * DOM element. 32344 * * `disable`: The result of this expression will be used to disable the rendered `<option>` 32345 * element. Return `true` to disable. 32346 * * `trackexpr`: Used when working with an array of objects. The result of this expression will be 32347 * used to identify the objects in the array. The `trackexpr` will most likely refer to the 32348 * `value` variable (e.g. `value.propertyName`). With this the selection is preserved 32349 * even when the options are recreated (e.g. reloaded from the server). 32350 * @param {string=} name Property name of the form under which the control is published. 32351 * @param {string=} required The control is considered valid only if value is entered. 32352 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 32353 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 32354 * `required` when you want to data-bind to the `required` attribute. 32355 * @param {string=} ngAttrSize sets the size of the select element dynamically. Uses the 32356 * {@link guide/interpolation#-ngattr-for-binding-to-arbitrary-attributes ngAttr} directive. 32357 * 32358 * @example 32359 <example module="selectExample" name="select"> 32360 <file name="index.html"> 32361 <script> 32362 angular.module('selectExample', []) 32363 .controller('ExampleController', ['$scope', function($scope) { 32364 $scope.colors = [ 32365 {name:'black', shade:'dark'}, 32366 {name:'white', shade:'light', notAnOption: true}, 32367 {name:'red', shade:'dark'}, 32368 {name:'blue', shade:'dark', notAnOption: true}, 32369 {name:'yellow', shade:'light', notAnOption: false} 32370 ]; 32371 $scope.myColor = $scope.colors[2]; // red 32372 }]); 32373 </script> 32374 <div ng-controller="ExampleController"> 32375 <ul> 32376 <li ng-repeat="color in colors"> 32377 <label>Name: <input ng-model="color.name"></label> 32378 <label><input type="checkbox" ng-model="color.notAnOption"> Disabled?</label> 32379 <button ng-click="colors.splice($index, 1)" aria-label="Remove">X</button> 32380 </li> 32381 <li> 32382 <button ng-click="colors.push({})">add</button> 32383 </li> 32384 </ul> 32385 <hr/> 32386 <label>Color (null not allowed): 32387 <select ng-model="myColor" ng-options="color.name for color in colors"></select> 32388 </label><br/> 32389 <label>Color (null allowed): 32390 <span class="nullable"> 32391 <select ng-model="myColor" ng-options="color.name for color in colors"> 32392 <option value="">-- choose color --</option> 32393 </select> 32394 </span></label><br/> 32395 32396 <label>Color grouped by shade: 32397 <select ng-model="myColor" ng-options="color.name group by color.shade for color in colors"> 32398 </select> 32399 </label><br/> 32400 32401 <label>Color grouped by shade, with some disabled: 32402 <select ng-model="myColor" 32403 ng-options="color.name group by color.shade disable when color.notAnOption for color in colors"> 32404 </select> 32405 </label><br/> 32406 32407 32408 32409 Select <button ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</button>. 32410 <br/> 32411 <hr/> 32412 Currently selected: {{ {selected_color:myColor} }} 32413 <div style="border:solid 1px black; height:20px" 32414 ng-style="{'background-color':myColor.name}"> 32415 </div> 32416 </div> 32417 </file> 32418 <file name="protractor.js" type="protractor"> 32419 it('should check ng-options', function() { 32420 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red'); 32421 element.all(by.model('myColor')).first().click(); 32422 element.all(by.css('select[ng-model="myColor"] option')).first().click(); 32423 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black'); 32424 element(by.css('.nullable select[ng-model="myColor"]')).click(); 32425 element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click(); 32426 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null'); 32427 }); 32428 </file> 32429 </example> 32430 */ 32431 32432/* eslint-disable max-len */ 32433// //00001111111111000000000002222222222000000000000000000000333333333300000000000000000000000004444444444400000000000005555555555555000000000666666666666600000007777777777777000000000000000888888888800000000000000000009999999999 32434var 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]+?))?$/; 32435 // 1: value expression (valueFn) 32436 // 2: label expression (displayFn) 32437 // 3: group by expression (groupByFn) 32438 // 4: disable when expression (disableWhenFn) 32439 // 5: array item variable name 32440 // 6: object item key variable name 32441 // 7: object item value variable name 32442 // 8: collection expression 32443 // 9: track by expression 32444/* eslint-enable */ 32445 32446 32447var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile, $document, $parse) { 32448 32449 function parseOptionsExpression(optionsExp, selectElement, scope) { 32450 32451 var match = optionsExp.match(NG_OPTIONS_REGEXP); 32452 if (!(match)) { 32453 throw ngOptionsMinErr('iexp', 32454 'Expected expression in form of ' + 32455 '\'_select_ (as _label_)? for (_key_,)?_value_ in _collection_\'' + 32456 ' but got \'{0}\'. Element: {1}', 32457 optionsExp, startingTag(selectElement)); 32458 } 32459 32460 // Extract the parts from the ngOptions expression 32461 32462 // The variable name for the value of the item in the collection 32463 var valueName = match[5] || match[7]; 32464 // The variable name for the key of the item in the collection 32465 var keyName = match[6]; 32466 32467 // An expression that generates the viewValue for an option if there is a label expression 32468 var selectAs = / as /.test(match[0]) && match[1]; 32469 // An expression that is used to track the id of each object in the options collection 32470 var trackBy = match[9]; 32471 // An expression that generates the viewValue for an option if there is no label expression 32472 var valueFn = $parse(match[2] ? match[1] : valueName); 32473 var selectAsFn = selectAs && $parse(selectAs); 32474 var viewValueFn = selectAsFn || valueFn; 32475 var trackByFn = trackBy && $parse(trackBy); 32476 32477 // Get the value by which we are going to track the option 32478 // if we have a trackFn then use that (passing scope and locals) 32479 // otherwise just hash the given viewValue 32480 var getTrackByValueFn = trackBy ? 32481 function(value, locals) { return trackByFn(scope, locals); } : 32482 function getHashOfValue(value) { return hashKey(value); }; 32483 var getTrackByValue = function(value, key) { 32484 return getTrackByValueFn(value, getLocals(value, key)); 32485 }; 32486 32487 var displayFn = $parse(match[2] || match[1]); 32488 var groupByFn = $parse(match[3] || ''); 32489 var disableWhenFn = $parse(match[4] || ''); 32490 var valuesFn = $parse(match[8]); 32491 32492 var locals = {}; 32493 var getLocals = keyName ? function(value, key) { 32494 locals[keyName] = key; 32495 locals[valueName] = value; 32496 return locals; 32497 } : function(value) { 32498 locals[valueName] = value; 32499 return locals; 32500 }; 32501 32502 32503 function Option(selectValue, viewValue, label, group, disabled) { 32504 this.selectValue = selectValue; 32505 this.viewValue = viewValue; 32506 this.label = label; 32507 this.group = group; 32508 this.disabled = disabled; 32509 } 32510 32511 function getOptionValuesKeys(optionValues) { 32512 var optionValuesKeys; 32513 32514 if (!keyName && isArrayLike(optionValues)) { 32515 optionValuesKeys = optionValues; 32516 } else { 32517 // if object, extract keys, in enumeration order, unsorted 32518 optionValuesKeys = []; 32519 for (var itemKey in optionValues) { 32520 if (optionValues.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') { 32521 optionValuesKeys.push(itemKey); 32522 } 32523 } 32524 } 32525 return optionValuesKeys; 32526 } 32527 32528 return { 32529 trackBy: trackBy, 32530 getTrackByValue: getTrackByValue, 32531 getWatchables: $parse(valuesFn, function(optionValues) { 32532 // Create a collection of things that we would like to watch (watchedArray) 32533 // so that they can all be watched using a single $watchCollection 32534 // that only runs the handler once if anything changes 32535 var watchedArray = []; 32536 optionValues = optionValues || []; 32537 32538 var optionValuesKeys = getOptionValuesKeys(optionValues); 32539 var optionValuesLength = optionValuesKeys.length; 32540 for (var index = 0; index < optionValuesLength; index++) { 32541 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index]; 32542 var value = optionValues[key]; 32543 32544 var locals = getLocals(value, key); 32545 var selectValue = getTrackByValueFn(value, locals); 32546 watchedArray.push(selectValue); 32547 32548 // Only need to watch the displayFn if there is a specific label expression 32549 if (match[2] || match[1]) { 32550 var label = displayFn(scope, locals); 32551 watchedArray.push(label); 32552 } 32553 32554 // Only need to watch the disableWhenFn if there is a specific disable expression 32555 if (match[4]) { 32556 var disableWhen = disableWhenFn(scope, locals); 32557 watchedArray.push(disableWhen); 32558 } 32559 } 32560 return watchedArray; 32561 }), 32562 32563 getOptions: function() { 32564 32565 var optionItems = []; 32566 var selectValueMap = {}; 32567 32568 // The option values were already computed in the `getWatchables` fn, 32569 // which must have been called to trigger `getOptions` 32570 var optionValues = valuesFn(scope) || []; 32571 var optionValuesKeys = getOptionValuesKeys(optionValues); 32572 var optionValuesLength = optionValuesKeys.length; 32573 32574 for (var index = 0; index < optionValuesLength; index++) { 32575 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index]; 32576 var value = optionValues[key]; 32577 var locals = getLocals(value, key); 32578 var viewValue = viewValueFn(scope, locals); 32579 var selectValue = getTrackByValueFn(viewValue, locals); 32580 var label = displayFn(scope, locals); 32581 var group = groupByFn(scope, locals); 32582 var disabled = disableWhenFn(scope, locals); 32583 var optionItem = new Option(selectValue, viewValue, label, group, disabled); 32584 32585 optionItems.push(optionItem); 32586 selectValueMap[selectValue] = optionItem; 32587 } 32588 32589 return { 32590 items: optionItems, 32591 selectValueMap: selectValueMap, 32592 getOptionFromViewValue: function(value) { 32593 return selectValueMap[getTrackByValue(value)]; 32594 }, 32595 getViewValueFromOption: function(option) { 32596 // If the viewValue could be an object that may be mutated by the application, 32597 // we need to make a copy and not return the reference to the value on the option. 32598 return trackBy ? copy(option.viewValue) : option.viewValue; 32599 } 32600 }; 32601 } 32602 }; 32603 } 32604 32605 32606 // Support: IE 9 only 32607 // We can't just jqLite('<option>') since jqLite is not smart enough 32608 // to create it in <select> and IE barfs otherwise. 32609 var optionTemplate = window.document.createElement('option'), 32610 optGroupTemplate = window.document.createElement('optgroup'); 32611 32612 function ngOptionsPostLink(scope, selectElement, attr, ctrls) { 32613 32614 var selectCtrl = ctrls[0]; 32615 var ngModelCtrl = ctrls[1]; 32616 var multiple = attr.multiple; 32617 32618 // The emptyOption allows the application developer to provide their own custom "empty" 32619 // option when the viewValue does not match any of the option values. 32620 for (var i = 0, children = selectElement.children(), ii = children.length; i < ii; i++) { 32621 if (children[i].value === '') { 32622 selectCtrl.hasEmptyOption = true; 32623 selectCtrl.emptyOption = children.eq(i); 32624 break; 32625 } 32626 } 32627 32628 // The empty option will be compiled and rendered before we first generate the options 32629 selectElement.empty(); 32630 32631 var providedEmptyOption = !!selectCtrl.emptyOption; 32632 32633 var unknownOption = jqLite(optionTemplate.cloneNode(false)); 32634 unknownOption.val('?'); 32635 32636 var options; 32637 var ngOptions = parseOptionsExpression(attr.ngOptions, selectElement, scope); 32638 // This stores the newly created options before they are appended to the select. 32639 // Since the contents are removed from the fragment when it is appended, 32640 // we only need to create it once. 32641 var listFragment = $document[0].createDocumentFragment(); 32642 32643 // Overwrite the implementation. ngOptions doesn't use hashes 32644 selectCtrl.generateUnknownOptionValue = function(val) { 32645 return '?'; 32646 }; 32647 32648 // Update the controller methods for multiple selectable options 32649 if (!multiple) { 32650 32651 selectCtrl.writeValue = function writeNgOptionsValue(value) { 32652 // The options might not be defined yet when ngModel tries to render 32653 if (!options) return; 32654 32655 var selectedOption = selectElement[0].options[selectElement[0].selectedIndex]; 32656 var option = options.getOptionFromViewValue(value); 32657 32658 // Make sure to remove the selected attribute from the previously selected option 32659 // Otherwise, screen readers might get confused 32660 if (selectedOption) selectedOption.removeAttribute('selected'); 32661 32662 if (option) { 32663 // Don't update the option when it is already selected. 32664 // For example, the browser will select the first option by default. In that case, 32665 // most properties are set automatically - except the `selected` attribute, which we 32666 // set always 32667 32668 if (selectElement[0].value !== option.selectValue) { 32669 selectCtrl.removeUnknownOption(); 32670 32671 selectElement[0].value = option.selectValue; 32672 option.element.selected = true; 32673 } 32674 32675 option.element.setAttribute('selected', 'selected'); 32676 } else { 32677 selectCtrl.selectUnknownOrEmptyOption(value); 32678 } 32679 }; 32680 32681 selectCtrl.readValue = function readNgOptionsValue() { 32682 32683 var selectedOption = options.selectValueMap[selectElement.val()]; 32684 32685 if (selectedOption && !selectedOption.disabled) { 32686 selectCtrl.unselectEmptyOption(); 32687 selectCtrl.removeUnknownOption(); 32688 return options.getViewValueFromOption(selectedOption); 32689 } 32690 return null; 32691 }; 32692 32693 // If we are using `track by` then we must watch the tracked value on the model 32694 // since ngModel only watches for object identity change 32695 // FIXME: When a user selects an option, this watch will fire needlessly 32696 if (ngOptions.trackBy) { 32697 scope.$watch( 32698 function() { return ngOptions.getTrackByValue(ngModelCtrl.$viewValue); }, 32699 function() { ngModelCtrl.$render(); } 32700 ); 32701 } 32702 32703 } else { 32704 32705 selectCtrl.writeValue = function writeNgOptionsMultiple(values) { 32706 // The options might not be defined yet when ngModel tries to render 32707 if (!options) return; 32708 32709 // Only set `<option>.selected` if necessary, in order to prevent some browsers from 32710 // scrolling to `<option>` elements that are outside the `<select>` element's viewport. 32711 var selectedOptions = values && values.map(getAndUpdateSelectedOption) || []; 32712 32713 options.items.forEach(function(option) { 32714 if (option.element.selected && !includes(selectedOptions, option)) { 32715 option.element.selected = false; 32716 } 32717 }); 32718 }; 32719 32720 32721 selectCtrl.readValue = function readNgOptionsMultiple() { 32722 var selectedValues = selectElement.val() || [], 32723 selections = []; 32724 32725 forEach(selectedValues, function(value) { 32726 var option = options.selectValueMap[value]; 32727 if (option && !option.disabled) selections.push(options.getViewValueFromOption(option)); 32728 }); 32729 32730 return selections; 32731 }; 32732 32733 // If we are using `track by` then we must watch these tracked values on the model 32734 // since ngModel only watches for object identity change 32735 if (ngOptions.trackBy) { 32736 32737 scope.$watchCollection(function() { 32738 if (isArray(ngModelCtrl.$viewValue)) { 32739 return ngModelCtrl.$viewValue.map(function(value) { 32740 return ngOptions.getTrackByValue(value); 32741 }); 32742 } 32743 }, function() { 32744 ngModelCtrl.$render(); 32745 }); 32746 32747 } 32748 } 32749 32750 if (providedEmptyOption) { 32751 32752 // compile the element since there might be bindings in it 32753 $compile(selectCtrl.emptyOption)(scope); 32754 32755 selectElement.prepend(selectCtrl.emptyOption); 32756 32757 if (selectCtrl.emptyOption[0].nodeType === NODE_TYPE_COMMENT) { 32758 // This means the empty option has currently no actual DOM node, probably because 32759 // it has been modified by a transclusion directive. 32760 selectCtrl.hasEmptyOption = false; 32761 32762 // Redefine the registerOption function, which will catch 32763 // options that are added by ngIf etc. (rendering of the node is async because of 32764 // lazy transclusion) 32765 selectCtrl.registerOption = function(optionScope, optionEl) { 32766 if (optionEl.val() === '') { 32767 selectCtrl.hasEmptyOption = true; 32768 selectCtrl.emptyOption = optionEl; 32769 selectCtrl.emptyOption.removeClass('ng-scope'); 32770 // This ensures the new empty option is selected if previously no option was selected 32771 ngModelCtrl.$render(); 32772 32773 optionEl.on('$destroy', function() { 32774 var needsRerender = selectCtrl.$isEmptyOptionSelected(); 32775 32776 selectCtrl.hasEmptyOption = false; 32777 selectCtrl.emptyOption = undefined; 32778 32779 if (needsRerender) ngModelCtrl.$render(); 32780 }); 32781 } 32782 }; 32783 32784 } else { 32785 // remove the class, which is added automatically because we recompile the element and it 32786 // becomes the compilation root 32787 selectCtrl.emptyOption.removeClass('ng-scope'); 32788 } 32789 32790 } 32791 32792 // We will re-render the option elements if the option values or labels change 32793 scope.$watchCollection(ngOptions.getWatchables, updateOptions); 32794 32795 // ------------------------------------------------------------------ // 32796 32797 function addOptionElement(option, parent) { 32798 var optionElement = optionTemplate.cloneNode(false); 32799 parent.appendChild(optionElement); 32800 updateOptionElement(option, optionElement); 32801 } 32802 32803 function getAndUpdateSelectedOption(viewValue) { 32804 var option = options.getOptionFromViewValue(viewValue); 32805 var element = option && option.element; 32806 32807 if (element && !element.selected) element.selected = true; 32808 32809 return option; 32810 } 32811 32812 function updateOptionElement(option, element) { 32813 option.element = element; 32814 element.disabled = option.disabled; 32815 // Support: IE 11 only, Edge 12-13 only 32816 // NOTE: The label must be set before the value, otherwise IE 11 & Edge create unresponsive 32817 // selects in certain circumstances when multiple selects are next to each other and display 32818 // the option list in listbox style, i.e. the select is [multiple], or specifies a [size]. 32819 // See https://github.com/angular/angular.js/issues/11314 for more info. 32820 // This is unfortunately untestable with unit / e2e tests 32821 if (option.label !== element.label) { 32822 element.label = option.label; 32823 element.textContent = option.label; 32824 } 32825 element.value = option.selectValue; 32826 } 32827 32828 function updateOptions() { 32829 var previousValue = options && selectCtrl.readValue(); 32830 32831 // We must remove all current options, but cannot simply set innerHTML = null 32832 // since the providedEmptyOption might have an ngIf on it that inserts comments which we 32833 // must preserve. 32834 // Instead, iterate over the current option elements and remove them or their optgroup 32835 // parents 32836 if (options) { 32837 32838 for (var i = options.items.length - 1; i >= 0; i--) { 32839 var option = options.items[i]; 32840 if (isDefined(option.group)) { 32841 jqLiteRemove(option.element.parentNode); 32842 } else { 32843 jqLiteRemove(option.element); 32844 } 32845 } 32846 } 32847 32848 options = ngOptions.getOptions(); 32849 32850 var groupElementMap = {}; 32851 32852 options.items.forEach(function addOption(option) { 32853 var groupElement; 32854 32855 if (isDefined(option.group)) { 32856 32857 // This option is to live in a group 32858 // See if we have already created this group 32859 groupElement = groupElementMap[option.group]; 32860 32861 if (!groupElement) { 32862 32863 groupElement = optGroupTemplate.cloneNode(false); 32864 listFragment.appendChild(groupElement); 32865 32866 // Update the label on the group element 32867 // "null" is special cased because of Safari 32868 groupElement.label = option.group === null ? 'null' : option.group; 32869 32870 // Store it for use later 32871 groupElementMap[option.group] = groupElement; 32872 } 32873 32874 addOptionElement(option, groupElement); 32875 32876 } else { 32877 32878 // This option is not in a group 32879 addOptionElement(option, listFragment); 32880 } 32881 }); 32882 32883 selectElement[0].appendChild(listFragment); 32884 32885 ngModelCtrl.$render(); 32886 32887 // Check to see if the value has changed due to the update to the options 32888 if (!ngModelCtrl.$isEmpty(previousValue)) { 32889 var nextValue = selectCtrl.readValue(); 32890 var isNotPrimitive = ngOptions.trackBy || multiple; 32891 if (isNotPrimitive ? !equals(previousValue, nextValue) : previousValue !== nextValue) { 32892 ngModelCtrl.$setViewValue(nextValue); 32893 ngModelCtrl.$render(); 32894 } 32895 } 32896 } 32897 } 32898 32899 return { 32900 restrict: 'A', 32901 terminal: true, 32902 require: ['select', 'ngModel'], 32903 link: { 32904 pre: function ngOptionsPreLink(scope, selectElement, attr, ctrls) { 32905 // Deactivate the SelectController.register method to prevent 32906 // option directives from accidentally registering themselves 32907 // (and unwanted $destroy handlers etc.) 32908 ctrls[0].registerOption = noop; 32909 }, 32910 post: ngOptionsPostLink 32911 } 32912 }; 32913}]; 32914 32915/** 32916 * @ngdoc directive 32917 * @name ngPluralize 32918 * @restrict EA 32919 * 32920 * @description 32921 * `ngPluralize` is a directive that displays messages according to en-US localization rules. 32922 * These rules are bundled with angular.js, but can be overridden 32923 * (see {@link guide/i18n AngularJS i18n} dev guide). You configure ngPluralize directive 32924 * by specifying the mappings between 32925 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 32926 * and the strings to be displayed. 32927 * 32928 * ## Plural categories and explicit number rules 32929 * There are two 32930 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 32931 * in AngularJS's default en-US locale: "one" and "other". 32932 * 32933 * While a plural category may match many numbers (for example, in en-US locale, "other" can match 32934 * any number that is not 1), an explicit number rule can only match one number. For example, the 32935 * explicit number rule for "3" matches the number 3. There are examples of plural categories 32936 * and explicit number rules throughout the rest of this documentation. 32937 * 32938 * ## Configuring ngPluralize 32939 * You configure ngPluralize by providing 2 attributes: `count` and `when`. 32940 * You can also provide an optional attribute, `offset`. 32941 * 32942 * The value of the `count` attribute can be either a string or an {@link guide/expression 32943 * AngularJS expression}; these are evaluated on the current scope for its bound value. 32944 * 32945 * The `when` attribute specifies the mappings between plural categories and the actual 32946 * string to be displayed. The value of the attribute should be a JSON object. 32947 * 32948 * The following example shows how to configure ngPluralize: 32949 * 32950 * ```html 32951 * <ng-pluralize count="personCount" 32952 when="{'0': 'Nobody is viewing.', 32953 * 'one': '1 person is viewing.', 32954 * 'other': '{} people are viewing.'}"> 32955 * </ng-pluralize> 32956 *``` 32957 * 32958 * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not 32959 * specify this rule, 0 would be matched to the "other" category and "0 people are viewing" 32960 * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for 32961 * other numbers, for example 12, so that instead of showing "12 people are viewing", you can 32962 * show "a dozen people are viewing". 32963 * 32964 * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted 32965 * into pluralized strings. In the previous example, AngularJS will replace `{}` with 32966 * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder 32967 * for <span ng-non-bindable>{{numberExpression}}</span>. 32968 * 32969 * If no rule is defined for a category, then an empty string is displayed and a warning is generated. 32970 * Note that some locales define more categories than `one` and `other`. For example, fr-fr defines `few` and `many`. 32971 * 32972 * ## Configuring ngPluralize with offset 32973 * The `offset` attribute allows further customization of pluralized text, which can result in 32974 * a better user experience. For example, instead of the message "4 people are viewing this document", 32975 * you might display "John, Kate and 2 others are viewing this document". 32976 * The offset attribute allows you to offset a number by any desired value. 32977 * Let's take a look at an example: 32978 * 32979 * ```html 32980 * <ng-pluralize count="personCount" offset=2 32981 * when="{'0': 'Nobody is viewing.', 32982 * '1': '{{person1}} is viewing.', 32983 * '2': '{{person1}} and {{person2}} are viewing.', 32984 * 'one': '{{person1}}, {{person2}} and one other person are viewing.', 32985 * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 32986 * </ng-pluralize> 32987 * ``` 32988 * 32989 * Notice that we are still using two plural categories(one, other), but we added 32990 * three explicit number rules 0, 1 and 2. 32991 * When one person, perhaps John, views the document, "John is viewing" will be shown. 32992 * When three people view the document, no explicit number rule is found, so 32993 * an offset of 2 is taken off 3, and AngularJS uses 1 to decide the plural category. 32994 * In this case, plural category 'one' is matched and "John, Mary and one other person are viewing" 32995 * is shown. 32996 * 32997 * Note that when you specify offsets, you must provide explicit number rules for 32998 * numbers from 0 up to and including the offset. If you use an offset of 3, for example, 32999 * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for 33000 * plural categories "one" and "other". 33001 * 33002 * @param {string|expression} count The variable to be bound to. 33003 * @param {string} when The mapping between plural category to its corresponding strings. 33004 * @param {number=} offset Offset to deduct from the total number. 33005 * 33006 * @example 33007 <example module="pluralizeExample" name="ng-pluralize"> 33008 <file name="index.html"> 33009 <script> 33010 angular.module('pluralizeExample', []) 33011 .controller('ExampleController', ['$scope', function($scope) { 33012 $scope.person1 = 'Igor'; 33013 $scope.person2 = 'Misko'; 33014 $scope.personCount = 1; 33015 }]); 33016 </script> 33017 <div ng-controller="ExampleController"> 33018 <label>Person 1:<input type="text" ng-model="person1" value="Igor" /></label><br/> 33019 <label>Person 2:<input type="text" ng-model="person2" value="Misko" /></label><br/> 33020 <label>Number of People:<input type="text" ng-model="personCount" value="1" /></label><br/> 33021 33022 <!--- Example with simple pluralization rules for en locale ---> 33023 Without Offset: 33024 <ng-pluralize count="personCount" 33025 when="{'0': 'Nobody is viewing.', 33026 'one': '1 person is viewing.', 33027 'other': '{} people are viewing.'}"> 33028 </ng-pluralize><br> 33029 33030 <!--- Example with offset ---> 33031 With Offset(2): 33032 <ng-pluralize count="personCount" offset=2 33033 when="{'0': 'Nobody is viewing.', 33034 '1': '{{person1}} is viewing.', 33035 '2': '{{person1}} and {{person2}} are viewing.', 33036 'one': '{{person1}}, {{person2}} and one other person are viewing.', 33037 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 33038 </ng-pluralize> 33039 </div> 33040 </file> 33041 <file name="protractor.js" type="protractor"> 33042 it('should show correct pluralized string', function() { 33043 var withoutOffset = element.all(by.css('ng-pluralize')).get(0); 33044 var withOffset = element.all(by.css('ng-pluralize')).get(1); 33045 var countInput = element(by.model('personCount')); 33046 33047 expect(withoutOffset.getText()).toEqual('1 person is viewing.'); 33048 expect(withOffset.getText()).toEqual('Igor is viewing.'); 33049 33050 countInput.clear(); 33051 countInput.sendKeys('0'); 33052 33053 expect(withoutOffset.getText()).toEqual('Nobody is viewing.'); 33054 expect(withOffset.getText()).toEqual('Nobody is viewing.'); 33055 33056 countInput.clear(); 33057 countInput.sendKeys('2'); 33058 33059 expect(withoutOffset.getText()).toEqual('2 people are viewing.'); 33060 expect(withOffset.getText()).toEqual('Igor and Misko are viewing.'); 33061 33062 countInput.clear(); 33063 countInput.sendKeys('3'); 33064 33065 expect(withoutOffset.getText()).toEqual('3 people are viewing.'); 33066 expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.'); 33067 33068 countInput.clear(); 33069 countInput.sendKeys('4'); 33070 33071 expect(withoutOffset.getText()).toEqual('4 people are viewing.'); 33072 expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.'); 33073 }); 33074 it('should show data-bound names', function() { 33075 var withOffset = element.all(by.css('ng-pluralize')).get(1); 33076 var personCount = element(by.model('personCount')); 33077 var person1 = element(by.model('person1')); 33078 var person2 = element(by.model('person2')); 33079 personCount.clear(); 33080 personCount.sendKeys('4'); 33081 person1.clear(); 33082 person1.sendKeys('Di'); 33083 person2.clear(); 33084 person2.sendKeys('Vojta'); 33085 expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.'); 33086 }); 33087 </file> 33088 </example> 33089 */ 33090var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, $interpolate, $log) { 33091 var BRACE = /{}/g, 33092 IS_WHEN = /^when(Minus)?(.+)$/; 33093 33094 return { 33095 link: function(scope, element, attr) { 33096 var numberExp = attr.count, 33097 whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs 33098 offset = attr.offset || 0, 33099 whens = scope.$eval(whenExp) || {}, 33100 whensExpFns = {}, 33101 startSymbol = $interpolate.startSymbol(), 33102 endSymbol = $interpolate.endSymbol(), 33103 braceReplacement = startSymbol + numberExp + '-' + offset + endSymbol, 33104 watchRemover = angular.noop, 33105 lastCount; 33106 33107 forEach(attr, function(expression, attributeName) { 33108 var tmpMatch = IS_WHEN.exec(attributeName); 33109 if (tmpMatch) { 33110 var whenKey = (tmpMatch[1] ? '-' : '') + lowercase(tmpMatch[2]); 33111 whens[whenKey] = element.attr(attr.$attr[attributeName]); 33112 } 33113 }); 33114 forEach(whens, function(expression, key) { 33115 whensExpFns[key] = $interpolate(expression.replace(BRACE, braceReplacement)); 33116 33117 }); 33118 33119 scope.$watch(numberExp, function ngPluralizeWatchAction(newVal) { 33120 var count = parseFloat(newVal); 33121 var countIsNaN = isNumberNaN(count); 33122 33123 if (!countIsNaN && !(count in whens)) { 33124 // If an explicit number rule such as 1, 2, 3... is defined, just use it. 33125 // Otherwise, check it against pluralization rules in $locale service. 33126 count = $locale.pluralCat(count - offset); 33127 } 33128 33129 // If both `count` and `lastCount` are NaN, we don't need to re-register a watch. 33130 // In JS `NaN !== NaN`, so we have to explicitly check. 33131 if ((count !== lastCount) && !(countIsNaN && isNumberNaN(lastCount))) { 33132 watchRemover(); 33133 var whenExpFn = whensExpFns[count]; 33134 if (isUndefined(whenExpFn)) { 33135 if (newVal != null) { 33136 $log.debug('ngPluralize: no rule defined for \'' + count + '\' in ' + whenExp); 33137 } 33138 watchRemover = noop; 33139 updateElementText(); 33140 } else { 33141 watchRemover = scope.$watch(whenExpFn, updateElementText); 33142 } 33143 lastCount = count; 33144 } 33145 }); 33146 33147 function updateElementText(newText) { 33148 element.text(newText || ''); 33149 } 33150 } 33151 }; 33152}]; 33153 33154/** 33155 * @ngdoc directive 33156 * @name ngRef 33157 * @restrict A 33158 * 33159 * @description 33160 * The `ngRef` attribute tells AngularJS to assign the controller of a component (or a directive) 33161 * to the given property in the current scope. It is also possible to add the jqlite-wrapped DOM 33162 * element to the scope. 33163 * 33164 * If the element with `ngRef` is destroyed `null` is assigned to the property. 33165 * 33166 * Note that if you want to assign from a child into the parent scope, you must initialize the 33167 * target property on the parent scope, otherwise `ngRef` will assign on the child scope. 33168 * This commonly happens when assigning elements or components wrapped in {@link ngIf} or 33169 * {@link ngRepeat}. See the second example below. 33170 * 33171 * 33172 * @element ANY 33173 * @param {string} ngRef property name - A valid AngularJS expression identifier to which the 33174 * controller or jqlite-wrapped DOM element will be bound. 33175 * @param {string=} ngRefRead read value - The name of a directive (or component) on this element, 33176 * or the special string `$element`. If a name is provided, `ngRef` will 33177 * assign the matching controller. If `$element` is provided, the element 33178 * itself is assigned (even if a controller is available). 33179 * 33180 * 33181 * @example 33182 * ### Simple toggle 33183 * This example shows how the controller of the component toggle 33184 * is reused in the template through the scope to use its logic. 33185 * <example name="ng-ref-component" module="myApp"> 33186 * <file name="index.html"> 33187 * <my-toggle ng-ref="myToggle"></my-toggle> 33188 * <button ng-click="myToggle.toggle()">Toggle</button> 33189 * <div ng-show="myToggle.isOpen()"> 33190 * You are using a component in the same template to show it. 33191 * </div> 33192 * </file> 33193 * <file name="index.js"> 33194 * angular.module('myApp', []) 33195 * .component('myToggle', { 33196 * controller: function ToggleController() { 33197 * var opened = false; 33198 * this.isOpen = function() { return opened; }; 33199 * this.toggle = function() { opened = !opened; }; 33200 * } 33201 * }); 33202 * </file> 33203 * <file name="protractor.js" type="protractor"> 33204 * it('should publish the toggle into the scope', function() { 33205 * var toggle = element(by.buttonText('Toggle')); 33206 * expect(toggle.evaluate('myToggle.isOpen()')).toEqual(false); 33207 * toggle.click(); 33208 * expect(toggle.evaluate('myToggle.isOpen()')).toEqual(true); 33209 * }); 33210 * </file> 33211 * </example> 33212 * 33213 * @example 33214 * ### ngRef inside scopes 33215 * This example shows how `ngRef` works with child scopes. The `ngRepeat`-ed `myWrapper` components 33216 * are assigned to the scope of `myRoot`, because the `toggles` property has been initialized. 33217 * The repeated `myToggle` components are published to the child scopes created by `ngRepeat`. 33218 * `ngIf` behaves similarly - the assignment of `myToggle` happens in the `ngIf` child scope, 33219 * because the target property has not been initialized on the `myRoot` component controller. 33220 * 33221 * <example name="ng-ref-scopes" module="myApp"> 33222 * <file name="index.html"> 33223 * <my-root></my-root> 33224 * </file> 33225 * <file name="index.js"> 33226 * angular.module('myApp', []) 33227 * .component('myRoot', { 33228 * templateUrl: 'root.html', 33229 * controller: function() { 33230 * this.wrappers = []; // initialize the array so that the wrappers are assigned into the parent scope 33231 * } 33232 * }) 33233 * .component('myToggle', { 33234 * template: '<strong>myToggle</strong><button ng-click="$ctrl.toggle()" ng-transclude></button>', 33235 * transclude: true, 33236 * controller: function ToggleController() { 33237 * var opened = false; 33238 * this.isOpen = function() { return opened; }; 33239 * this.toggle = function() { opened = !opened; }; 33240 * } 33241 * }) 33242 * .component('myWrapper', { 33243 * transclude: true, 33244 * template: '<strong>myWrapper</strong>' + 33245 * '<div>ngRepeatToggle.isOpen(): {{$ctrl.ngRepeatToggle.isOpen() | json}}</div>' + 33246 * '<my-toggle ng-ref="$ctrl.ngRepeatToggle"><ng-transclude></ng-transclude></my-toggle>' 33247 * }); 33248 * </file> 33249 * <file name="root.html"> 33250 * <strong>myRoot</strong> 33251 * <my-toggle ng-ref="$ctrl.outerToggle">Outer Toggle</my-toggle> 33252 * <div>outerToggle.isOpen(): {{$ctrl.outerToggle.isOpen() | json}}</div> 33253 * <div><em>wrappers assigned to root</em><br> 33254 * <div ng-repeat="wrapper in $ctrl.wrappers"> 33255 * wrapper.ngRepeatToggle.isOpen(): {{wrapper.ngRepeatToggle.isOpen() | json}} 33256 * </div> 33257 * 33258 * <ul> 33259 * <li ng-repeat="(index, value) in [1,2,3]"> 33260 * <strong>ngRepeat</strong> 33261 * <div>outerToggle.isOpen(): {{$ctrl.outerToggle.isOpen() | json}}</div> 33262 * <my-wrapper ng-ref="$ctrl.wrappers[index]">ngRepeat Toggle {{$index + 1}}</my-wrapper> 33263 * </li> 33264 * </ul> 33265 * 33266 * <div>ngIfToggle.isOpen(): {{ngIfToggle.isOpen()}} // This is always undefined because it's 33267 * assigned to the child scope created by ngIf. 33268 * </div> 33269 * <div ng-if="true"> 33270 <strong>ngIf</strong> 33271 * <my-toggle ng-ref="ngIfToggle">ngIf Toggle</my-toggle> 33272 * <div>ngIfToggle.isOpen(): {{ngIfToggle.isOpen() | json}}</div> 33273 * <div>outerToggle.isOpen(): {{$ctrl.outerToggle.isOpen() | json}}</div> 33274 * </div> 33275 * </file> 33276 * <file name="styles.css"> 33277 * ul { 33278 * list-style: none; 33279 * padding-left: 0; 33280 * } 33281 * 33282 * li[ng-repeat] { 33283 * background: lightgreen; 33284 * padding: 8px; 33285 * margin: 8px; 33286 * } 33287 * 33288 * [ng-if] { 33289 * background: lightgrey; 33290 * padding: 8px; 33291 * } 33292 * 33293 * my-root { 33294 * background: lightgoldenrodyellow; 33295 * padding: 8px; 33296 * display: block; 33297 * } 33298 * 33299 * my-wrapper { 33300 * background: lightsalmon; 33301 * padding: 8px; 33302 * display: block; 33303 * } 33304 * 33305 * my-toggle { 33306 * background: lightblue; 33307 * padding: 8px; 33308 * display: block; 33309 * } 33310 * </file> 33311 * <file name="protractor.js" type="protractor"> 33312 * var OuterToggle = function() { 33313 * this.toggle = function() { 33314 * element(by.buttonText('Outer Toggle')).click(); 33315 * }; 33316 * this.isOpen = function() { 33317 * return element.all(by.binding('outerToggle.isOpen()')).first().getText(); 33318 * }; 33319 * }; 33320 * var NgRepeatToggle = function(i) { 33321 * var parent = element.all(by.repeater('(index, value) in [1,2,3]')).get(i - 1); 33322 * this.toggle = function() { 33323 * element(by.buttonText('ngRepeat Toggle ' + i)).click(); 33324 * }; 33325 * this.isOpen = function() { 33326 * return parent.element(by.binding('ngRepeatToggle.isOpen() | json')).getText(); 33327 * }; 33328 * this.isOuterOpen = function() { 33329 * return parent.element(by.binding('outerToggle.isOpen() | json')).getText(); 33330 * }; 33331 * }; 33332 * var NgRepeatToggles = function() { 33333 * var toggles = [1,2,3].map(function(i) { return new NgRepeatToggle(i); }); 33334 * this.forEach = function(fn) { 33335 * toggles.forEach(fn); 33336 * }; 33337 * this.isOuterOpen = function(i) { 33338 * return toggles[i - 1].isOuterOpen(); 33339 * }; 33340 * }; 33341 * var NgIfToggle = function() { 33342 * var parent = element(by.css('[ng-if]')); 33343 * this.toggle = function() { 33344 * element(by.buttonText('ngIf Toggle')).click(); 33345 * }; 33346 * this.isOpen = function() { 33347 * return by.binding('ngIfToggle.isOpen() | json').getText(); 33348 * }; 33349 * this.isOuterOpen = function() { 33350 * return parent.element(by.binding('outerToggle.isOpen() | json')).getText(); 33351 * }; 33352 * }; 33353 * 33354 * it('should toggle the outer toggle', function() { 33355 * var outerToggle = new OuterToggle(); 33356 * expect(outerToggle.isOpen()).toEqual('outerToggle.isOpen(): false'); 33357 * outerToggle.toggle(); 33358 * expect(outerToggle.isOpen()).toEqual('outerToggle.isOpen(): true'); 33359 * }); 33360 * 33361 * it('should toggle all outer toggles', function() { 33362 * var outerToggle = new OuterToggle(); 33363 * var repeatToggles = new NgRepeatToggles(); 33364 * var ifToggle = new NgIfToggle(); 33365 * expect(outerToggle.isOpen()).toEqual('outerToggle.isOpen(): false'); 33366 * expect(repeatToggles.isOuterOpen(1)).toEqual('outerToggle.isOpen(): false'); 33367 * expect(repeatToggles.isOuterOpen(2)).toEqual('outerToggle.isOpen(): false'); 33368 * expect(repeatToggles.isOuterOpen(3)).toEqual('outerToggle.isOpen(): false'); 33369 * expect(ifToggle.isOuterOpen()).toEqual('outerToggle.isOpen(): false'); 33370 * outerToggle.toggle(); 33371 * expect(outerToggle.isOpen()).toEqual('outerToggle.isOpen(): true'); 33372 * expect(repeatToggles.isOuterOpen(1)).toEqual('outerToggle.isOpen(): true'); 33373 * expect(repeatToggles.isOuterOpen(2)).toEqual('outerToggle.isOpen(): true'); 33374 * expect(repeatToggles.isOuterOpen(3)).toEqual('outerToggle.isOpen(): true'); 33375 * expect(ifToggle.isOuterOpen()).toEqual('outerToggle.isOpen(): true'); 33376 * }); 33377 * 33378 * it('should toggle each repeat iteration separately', function() { 33379 * var repeatToggles = new NgRepeatToggles(); 33380 * 33381 * repeatToggles.forEach(function(repeatToggle) { 33382 * expect(repeatToggle.isOpen()).toEqual('ngRepeatToggle.isOpen(): false'); 33383 * expect(repeatToggle.isOuterOpen()).toEqual('outerToggle.isOpen(): false'); 33384 * repeatToggle.toggle(); 33385 * expect(repeatToggle.isOpen()).toEqual('ngRepeatToggle.isOpen(): true'); 33386 * expect(repeatToggle.isOuterOpen()).toEqual('outerToggle.isOpen(): false'); 33387 * }); 33388 * }); 33389 * </file> 33390 * </example> 33391 * 33392 */ 33393 33394var ngRefMinErr = minErr('ngRef'); 33395 33396var ngRefDirective = ['$parse', function($parse) { 33397 return { 33398 priority: -1, // Needed for compatibility with element transclusion on the same element 33399 restrict: 'A', 33400 compile: function(tElement, tAttrs) { 33401 // Get the expected controller name, converts <data-some-thing> into "someThing" 33402 var controllerName = directiveNormalize(nodeName_(tElement)); 33403 33404 // Get the expression for value binding 33405 var getter = $parse(tAttrs.ngRef); 33406 var setter = getter.assign || function() { 33407 throw ngRefMinErr('nonassign', 'Expression in ngRef="{0}" is non-assignable!', tAttrs.ngRef); 33408 }; 33409 33410 return function(scope, element, attrs) { 33411 var refValue; 33412 33413 if (attrs.hasOwnProperty('ngRefRead')) { 33414 if (attrs.ngRefRead === '$element') { 33415 refValue = element; 33416 } else { 33417 refValue = element.data('$' + attrs.ngRefRead + 'Controller'); 33418 33419 if (!refValue) { 33420 throw ngRefMinErr( 33421 'noctrl', 33422 'The controller for ngRefRead="{0}" could not be found on ngRef="{1}"', 33423 attrs.ngRefRead, 33424 tAttrs.ngRef 33425 ); 33426 } 33427 } 33428 } else { 33429 refValue = element.data('$' + controllerName + 'Controller'); 33430 } 33431 33432 refValue = refValue || element; 33433 33434 setter(scope, refValue); 33435 33436 // when the element is removed, remove it (nullify it) 33437 element.on('$destroy', function() { 33438 // only remove it if value has not changed, 33439 // because animations (and other procedures) may duplicate elements 33440 if (getter(scope) === refValue) { 33441 setter(scope, null); 33442 } 33443 }); 33444 }; 33445 } 33446 }; 33447}]; 33448 33449/* exported ngRepeatDirective */ 33450 33451/** 33452 * @ngdoc directive 33453 * @name ngRepeat 33454 * @multiElement 33455 * @restrict A 33456 * 33457 * @description 33458 * The `ngRepeat` directive instantiates a template once per item from a collection. Each template 33459 * instance gets its own scope, where the given loop variable is set to the current collection item, 33460 * and `$index` is set to the item index or key. 33461 * 33462 * Special properties are exposed on the local scope of each template instance, including: 33463 * 33464 * | Variable | Type | Details | 33465 * |-----------|-----------------|-----------------------------------------------------------------------------| 33466 * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) | 33467 * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. | 33468 * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. | 33469 * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. | 33470 * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). | 33471 * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). | 33472 * 33473 * <div class="alert alert-info"> 33474 * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}. 33475 * This may be useful when, for instance, nesting ngRepeats. 33476 * </div> 33477 * 33478 * 33479 * ## Iterating over object properties 33480 * 33481 * It is possible to get `ngRepeat` to iterate over the properties of an object using the following 33482 * syntax: 33483 * 33484 * ```js 33485 * <div ng-repeat="(key, value) in myObj"> ... </div> 33486 * ``` 33487 * 33488 * However, there are a few limitations compared to array iteration: 33489 * 33490 * - The JavaScript specification does not define the order of keys 33491 * returned for an object, so AngularJS relies on the order returned by the browser 33492 * when running `for key in myObj`. Browsers generally follow the strategy of providing 33493 * keys in the order in which they were defined, although there are exceptions when keys are deleted 33494 * and reinstated. See the 33495 * [MDN page on `delete` for more info](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_notes). 33496 * 33497 * - `ngRepeat` will silently *ignore* object keys starting with `$`, because 33498 * it's a prefix used by AngularJS for public (`$`) and private (`$$`) properties. 33499 * 33500 * - The built-in filters {@link ng.orderBy orderBy} and {@link ng.filter filter} do not work with 33501 * objects, and will throw an error if used with one. 33502 * 33503 * If you are hitting any of these limitations, the recommended workaround is to convert your object into an array 33504 * that is sorted into the order that you prefer before providing it to `ngRepeat`. You could 33505 * do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter) 33506 * or implement a `$watch` on the object yourself. 33507 * 33508 * 33509 * ## Tracking and Duplicates 33510 * 33511 * `ngRepeat` uses {@link $rootScope.Scope#$watchCollection $watchCollection} to detect changes in 33512 * the collection. When a change happens, `ngRepeat` then makes the corresponding changes to the DOM: 33513 * 33514 * * When an item is added, a new instance of the template is added to the DOM. 33515 * * When an item is removed, its template instance is removed from the DOM. 33516 * * When items are reordered, their respective templates are reordered in the DOM. 33517 * 33518 * To minimize creation of DOM elements, `ngRepeat` uses a function 33519 * to "keep track" of all items in the collection and their corresponding DOM elements. 33520 * For example, if an item is added to the collection, `ngRepeat` will know that all other items 33521 * already have DOM elements, and will not re-render them. 33522 * 33523 * All different types of tracking functions, their syntax, and their support for duplicate 33524 * items in collections can be found in the 33525 * {@link ngRepeat#ngRepeat-arguments ngRepeat expression description}. 33526 * 33527 * <div class="alert alert-success"> 33528 * **Best Practice:** If you are working with objects that have a unique identifier property, you 33529 * should track by this identifier instead of the object instance, 33530 * e.g. `item in items track by item.id`. 33531 * Should you reload your data later, `ngRepeat` will not have to rebuild the DOM elements for items 33532 * it has already rendered, even if the JavaScript objects in the collection have been substituted 33533 * for new ones. For large collections, this significantly improves rendering performance. 33534 * </div> 33535 * 33536 * ### Effects of DOM Element re-use 33537 * 33538 * When DOM elements are re-used, ngRepeat updates the scope for the element, which will 33539 * automatically update any active bindings on the template. However, other 33540 * functionality will not be updated, because the element is not re-created: 33541 * 33542 * - Directives are not re-compiled 33543 * - {@link guide/expression#one-time-binding one-time expressions} on the repeated template are not 33544 * updated if they have stabilized. 33545 * 33546 * The above affects all kinds of element re-use due to tracking, but may be especially visible 33547 * when tracking by `$index` due to the way ngRepeat re-uses elements. 33548 * 33549 * The following example shows the effects of different actions with tracking: 33550 33551 <example module="ngRepeat" name="ngRepeat-tracking" deps="angular-animate.js" animations="true"> 33552 <file name="script.js"> 33553 angular.module('ngRepeat', ['ngAnimate']).controller('repeatController', function($scope) { 33554 var friends = [ 33555 {name:'John', age:25}, 33556 {name:'Mary', age:40}, 33557 {name:'Peter', age:85} 33558 ]; 33559 33560 $scope.removeFirst = function() { 33561 $scope.friends.shift(); 33562 }; 33563 33564 $scope.updateAge = function() { 33565 $scope.friends.forEach(function(el) { 33566 el.age = el.age + 5; 33567 }); 33568 }; 33569 33570 $scope.copy = function() { 33571 $scope.friends = angular.copy($scope.friends); 33572 }; 33573 33574 $scope.reset = function() { 33575 $scope.friends = angular.copy(friends); 33576 }; 33577 33578 $scope.reset(); 33579 }); 33580 </file> 33581 <file name="index.html"> 33582 <div ng-controller="repeatController"> 33583 <ol> 33584 <li>When you click "Update Age", only the first list updates the age, because all others have 33585 a one-time binding on the age property. If you then click "Copy", the current friend list 33586 is copied, and now the second list updates the age, because the identity of the collection items 33587 has changed and the list must be re-rendered. The 3rd and 4th list stay the same, because all the 33588 items are already known according to their tracking functions. 33589 </li> 33590 <li>When you click "Remove First", the 4th list has the wrong age on both remaining items. This is 33591 due to tracking by $index: when the first collection item is removed, ngRepeat reuses the first 33592 DOM element for the new first collection item, and so on. Since the age property is one-time 33593 bound, the value remains from the collection item which was previously at this index. 33594 </li> 33595 </ol> 33596 33597 <button ng-click="removeFirst()">Remove First</button> 33598 <button ng-click="updateAge()">Update Age</button> 33599 <button ng-click="copy()">Copy</button> 33600 <br><button ng-click="reset()">Reset List</button> 33601 <br> 33602 <code>track by $id(friend)</code> (default): 33603 <ul class="example-animate-container"> 33604 <li class="animate-repeat" ng-repeat="friend in friends"> 33605 {{friend.name}} is {{friend.age}} years old. 33606 </li> 33607 </ul> 33608 <code>track by $id(friend)</code> (default), with age one-time binding: 33609 <ul class="example-animate-container"> 33610 <li class="animate-repeat" ng-repeat="friend in friends"> 33611 {{friend.name}} is {{::friend.age}} years old. 33612 </li> 33613 </ul> 33614 <code>track by friend.name</code>, with age one-time binding: 33615 <ul class="example-animate-container"> 33616 <li class="animate-repeat" ng-repeat="friend in friends track by friend.name"> 33617 {{friend.name}} is {{::friend.age}} years old. 33618 </li> 33619 </ul> 33620 <code>track by $index</code>, with age one-time binding: 33621 <ul class="example-animate-container"> 33622 <li class="animate-repeat" ng-repeat="friend in friends track by $index"> 33623 {{friend.name}} is {{::friend.age}} years old. 33624 </li> 33625 </ul> 33626 </div> 33627 </file> 33628 <file name="animations.css"> 33629 .example-animate-container { 33630 background:white; 33631 border:1px solid black; 33632 list-style:none; 33633 margin:0; 33634 padding:0 10px; 33635 } 33636 33637 .animate-repeat { 33638 line-height:30px; 33639 list-style:none; 33640 box-sizing:border-box; 33641 } 33642 33643 .animate-repeat.ng-move, 33644 .animate-repeat.ng-enter, 33645 .animate-repeat.ng-leave { 33646 transition:all linear 0.5s; 33647 } 33648 33649 .animate-repeat.ng-leave.ng-leave-active, 33650 .animate-repeat.ng-move, 33651 .animate-repeat.ng-enter { 33652 opacity:0; 33653 max-height:0; 33654 } 33655 33656 .animate-repeat.ng-leave, 33657 .animate-repeat.ng-move.ng-move-active, 33658 .animate-repeat.ng-enter.ng-enter-active { 33659 opacity:1; 33660 max-height:30px; 33661 } 33662 </file> 33663 </example> 33664 33665 * 33666 * ## Special repeat start and end points 33667 * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending 33668 * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively. 33669 * 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) 33670 * up to and including the ending HTML tag where **ng-repeat-end** is placed. 33671 * 33672 * The example below makes use of this feature: 33673 * ```html 33674 * <header ng-repeat-start="item in items"> 33675 * Header {{ item }} 33676 * </header> 33677 * <div class="body"> 33678 * Body {{ item }} 33679 * </div> 33680 * <footer ng-repeat-end> 33681 * Footer {{ item }} 33682 * </footer> 33683 * ``` 33684 * 33685 * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to: 33686 * ```html 33687 * <header> 33688 * Header A 33689 * </header> 33690 * <div class="body"> 33691 * Body A 33692 * </div> 33693 * <footer> 33694 * Footer A 33695 * </footer> 33696 * <header> 33697 * Header B 33698 * </header> 33699 * <div class="body"> 33700 * Body B 33701 * </div> 33702 * <footer> 33703 * Footer B 33704 * </footer> 33705 * ``` 33706 * 33707 * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such 33708 * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**). 33709 * 33710 * @animations 33711 * | Animation | Occurs | 33712 * |----------------------------------|-------------------------------------| 33713 * | {@link ng.$animate#enter enter} | when a new item is added to the list or when an item is revealed after a filter | 33714 * | {@link ng.$animate#leave leave} | when an item is removed from the list or when an item is filtered out | 33715 * | {@link ng.$animate#move move } | when an adjacent item is filtered out causing a reorder or when the item contents are reordered | 33716 * 33717 * See the example below for defining CSS animations with ngRepeat. 33718 * 33719 * @element ANY 33720 * @scope 33721 * @priority 1000 33722 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These 33723 * formats are currently supported: 33724 * 33725 * * `variable in expression` â where variable is the user defined loop variable and `expression` 33726 * is a scope expression giving the collection to enumerate. 33727 * 33728 * For example: `album in artist.albums`. 33729 * 33730 * * `(key, value) in expression` â where `key` and `value` can be any user defined identifiers, 33731 * and `expression` is the scope expression giving the collection to enumerate. 33732 * 33733 * For example: `(name, age) in {'adam':10, 'amalie':12}`. 33734 * 33735 * * `variable in expression track by tracking_expression` â You can also provide an optional tracking expression 33736 * which can be used to associate the objects in the collection with the DOM elements. If no tracking expression 33737 * is specified, ng-repeat associates elements by identity. It is an error to have 33738 * more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are 33739 * mapped to the same DOM element, which is not possible.) 33740 * 33741 * *Default tracking: $id()*: `item in items` is equivalent to `item in items track by $id(item)`. 33742 * This implies that the DOM elements will be associated by item identity in the collection. 33743 * 33744 * The built-in `$id()` function can be used to assign a unique 33745 * `$$hashKey` property to each item in the collection. This property is then used as a key to associated DOM elements 33746 * with the corresponding item in the collection by identity. Moving the same object would move 33747 * the DOM element in the same way in the DOM. 33748 * Note that the default id function does not support duplicate primitive values (`number`, `string`), 33749 * but supports duplictae non-primitive values (`object`) that are *equal* in shape. 33750 * 33751 * *Custom Expression*: It is possible to use any AngularJS expression to compute the tracking 33752 * id, for example with a function, or using a property on the collection items. 33753 * `item in items track by item.id` is a typical pattern when the items have a unique identifier, 33754 * e.g. database id. In this case the object identity does not matter. Two objects are considered 33755 * equivalent as long as their `id` property is same. 33756 * Tracking by unique identifier is the most performant way and should be used whenever possible. 33757 * 33758 * *$index*: This special property tracks the collection items by their index, and 33759 * re-uses the DOM elements that match that index, e.g. `item in items track by $index`. This can 33760 * be used for a performance improvement if no unique identfier is available and the identity of 33761 * the collection items cannot be easily computed. It also allows duplicates. 33762 * 33763 * <div class="alert alert-warning"> 33764 * <strong>Note:</strong> Re-using DOM elements can have unforeseen effects. Read the 33765 * {@link ngRepeat#tracking-and-duplicates section on tracking and duplicates} for 33766 * more info. 33767 * </div> 33768 * 33769 * <div class="alert alert-warning"> 33770 * <strong>Note:</strong> the `track by` expression must come last - after any filters, and the alias expression: 33771 * `item in items | filter:searchText as results track by item.id` 33772 * </div> 33773 * 33774 * * `variable in expression as alias_expression` â You can also provide an optional alias expression which will then store the 33775 * intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message 33776 * when a filter is active on the repeater, but the filtered result set is empty. 33777 * 33778 * For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after 33779 * the items have been processed through the filter. 33780 * 33781 * Please note that `as [variable name]` is not an operator but rather a part of ngRepeat 33782 * micro-syntax so it can be used only after all filters (and not as operator, inside an expression). 33783 * 33784 * For example: `item in items | filter : x | orderBy : order | limitTo : limit as results track by item.id` . 33785 * 33786 * @example 33787 * This example uses `ngRepeat` to display a list of people. A filter is used to restrict the displayed 33788 * results by name or by age. New (entering) and removed (leaving) items are animated. 33789 <example module="ngRepeat" name="ngRepeat" deps="angular-animate.js" animations="true"> 33790 <file name="index.html"> 33791 <div ng-controller="repeatController"> 33792 I have {{friends.length}} friends. They are: 33793 <input type="search" ng-model="q" placeholder="filter friends..." aria-label="filter friends" /> 33794 <ul class="example-animate-container"> 33795 <li class="animate-repeat" ng-repeat="friend in friends | filter:q as results track by friend.name"> 33796 [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. 33797 </li> 33798 <li class="animate-repeat" ng-if="results.length === 0"> 33799 <strong>No results found...</strong> 33800 </li> 33801 </ul> 33802 </div> 33803 </file> 33804 <file name="script.js"> 33805 angular.module('ngRepeat', ['ngAnimate']).controller('repeatController', function($scope) { 33806 $scope.friends = [ 33807 {name:'John', age:25, gender:'boy'}, 33808 {name:'Jessie', age:30, gender:'girl'}, 33809 {name:'Johanna', age:28, gender:'girl'}, 33810 {name:'Joy', age:15, gender:'girl'}, 33811 {name:'Mary', age:28, gender:'girl'}, 33812 {name:'Peter', age:95, gender:'boy'}, 33813 {name:'Sebastian', age:50, gender:'boy'}, 33814 {name:'Erika', age:27, gender:'girl'}, 33815 {name:'Patrick', age:40, gender:'boy'}, 33816 {name:'Samantha', age:60, gender:'girl'} 33817 ]; 33818 }); 33819 </file> 33820 <file name="animations.css"> 33821 .example-animate-container { 33822 background:white; 33823 border:1px solid black; 33824 list-style:none; 33825 margin:0; 33826 padding:0 10px; 33827 } 33828 33829 .animate-repeat { 33830 line-height:30px; 33831 list-style:none; 33832 box-sizing:border-box; 33833 } 33834 33835 .animate-repeat.ng-move, 33836 .animate-repeat.ng-enter, 33837 .animate-repeat.ng-leave { 33838 transition:all linear 0.5s; 33839 } 33840 33841 .animate-repeat.ng-leave.ng-leave-active, 33842 .animate-repeat.ng-move, 33843 .animate-repeat.ng-enter { 33844 opacity:0; 33845 max-height:0; 33846 } 33847 33848 .animate-repeat.ng-leave, 33849 .animate-repeat.ng-move.ng-move-active, 33850 .animate-repeat.ng-enter.ng-enter-active { 33851 opacity:1; 33852 max-height:30px; 33853 } 33854 </file> 33855 <file name="protractor.js" type="protractor"> 33856 var friends = element.all(by.repeater('friend in friends')); 33857 33858 it('should render initial data set', function() { 33859 expect(friends.count()).toBe(10); 33860 expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.'); 33861 expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.'); 33862 expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.'); 33863 expect(element(by.binding('friends.length')).getText()) 33864 .toMatch("I have 10 friends. They are:"); 33865 }); 33866 33867 it('should update repeater when filter predicate changes', function() { 33868 expect(friends.count()).toBe(10); 33869 33870 element(by.model('q')).sendKeys('ma'); 33871 33872 expect(friends.count()).toBe(2); 33873 expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.'); 33874 expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.'); 33875 }); 33876 </file> 33877 </example> 33878 */ 33879var ngRepeatDirective = ['$parse', '$animate', '$compile', function($parse, $animate, $compile) { 33880 var NG_REMOVED = '$$NG_REMOVED'; 33881 var ngRepeatMinErr = minErr('ngRepeat'); 33882 33883 var updateScope = function(scope, index, valueIdentifier, value, keyIdentifier, key, arrayLength) { 33884 // TODO(perf): generate setters to shave off ~40ms or 1-1.5% 33885 scope[valueIdentifier] = value; 33886 if (keyIdentifier) scope[keyIdentifier] = key; 33887 scope.$index = index; 33888 scope.$first = (index === 0); 33889 scope.$last = (index === (arrayLength - 1)); 33890 scope.$middle = !(scope.$first || scope.$last); 33891 // eslint-disable-next-line no-bitwise 33892 scope.$odd = !(scope.$even = (index & 1) === 0); 33893 }; 33894 33895 var getBlockStart = function(block) { 33896 return block.clone[0]; 33897 }; 33898 33899 var getBlockEnd = function(block) { 33900 return block.clone[block.clone.length - 1]; 33901 }; 33902 33903 var trackByIdArrayFn = function($scope, key, value) { 33904 return hashKey(value); 33905 }; 33906 33907 var trackByIdObjFn = function($scope, key) { 33908 return key; 33909 }; 33910 33911 return { 33912 restrict: 'A', 33913 multiElement: true, 33914 transclude: 'element', 33915 priority: 1000, 33916 terminal: true, 33917 $$tlb: true, 33918 compile: function ngRepeatCompile($element, $attr) { 33919 var expression = $attr.ngRepeat; 33920 var ngRepeatEndComment = $compile.$$createComment('end ngRepeat', expression); 33921 33922 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*$/); 33923 33924 if (!match) { 33925 throw ngRepeatMinErr('iexp', 'Expected expression in form of \'_item_ in _collection_[ track by _id_]\' but got \'{0}\'.', 33926 expression); 33927 } 33928 33929 var lhs = match[1]; 33930 var rhs = match[2]; 33931 var aliasAs = match[3]; 33932 var trackByExp = match[4]; 33933 33934 match = lhs.match(/^(?:(\s*[$\w]+)|\(\s*([$\w]+)\s*,\s*([$\w]+)\s*\))$/); 33935 33936 if (!match) { 33937 throw ngRepeatMinErr('iidexp', '\'_item_\' in \'_item_ in _collection_\' should be an identifier or \'(_key_, _value_)\' expression, but got \'{0}\'.', 33938 lhs); 33939 } 33940 var valueIdentifier = match[3] || match[1]; 33941 var keyIdentifier = match[2]; 33942 33943 if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) || 33944 /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) { 33945 throw ngRepeatMinErr('badident', 'alias \'{0}\' is invalid --- must be a valid JS identifier which is not a reserved name.', 33946 aliasAs); 33947 } 33948 33949 var trackByIdExpFn; 33950 33951 if (trackByExp) { 33952 var hashFnLocals = {$id: hashKey}; 33953 var trackByExpGetter = $parse(trackByExp); 33954 33955 trackByIdExpFn = function($scope, key, value, index) { 33956 // assign key, value, and $index to the locals so that they can be used in hash functions 33957 if (keyIdentifier) hashFnLocals[keyIdentifier] = key; 33958 hashFnLocals[valueIdentifier] = value; 33959 hashFnLocals.$index = index; 33960 return trackByExpGetter($scope, hashFnLocals); 33961 }; 33962 } 33963 33964 return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) { 33965 33966 // Store a list of elements from previous run. This is a hash where key is the item from the 33967 // iterator, and the value is objects with following properties. 33968 // - scope: bound scope 33969 // - clone: previous element. 33970 // - index: position 33971 // 33972 // We are using no-proto object so that we don't need to guard against inherited props via 33973 // hasOwnProperty. 33974 var lastBlockMap = createMap(); 33975 33976 //watch props 33977 $scope.$watchCollection(rhs, function ngRepeatAction(collection) { 33978 var index, length, 33979 previousNode = $element[0], // node that cloned nodes should be inserted after 33980 // initialized to the comment node anchor 33981 nextNode, 33982 // Same as lastBlockMap but it has the current state. It will become the 33983 // lastBlockMap on the next iteration. 33984 nextBlockMap = createMap(), 33985 collectionLength, 33986 key, value, // key/value of iteration 33987 trackById, 33988 trackByIdFn, 33989 collectionKeys, 33990 block, // last object information {scope, element, id} 33991 nextBlockOrder, 33992 elementsToRemove; 33993 33994 if (aliasAs) { 33995 $scope[aliasAs] = collection; 33996 } 33997 33998 if (isArrayLike(collection)) { 33999 collectionKeys = collection; 34000 trackByIdFn = trackByIdExpFn || trackByIdArrayFn; 34001 } else { 34002 trackByIdFn = trackByIdExpFn || trackByIdObjFn; 34003 // if object, extract keys, in enumeration order, unsorted 34004 collectionKeys = []; 34005 for (var itemKey in collection) { 34006 if (hasOwnProperty.call(collection, itemKey) && itemKey.charAt(0) !== '$') { 34007 collectionKeys.push(itemKey); 34008 } 34009 } 34010 } 34011 34012 collectionLength = collectionKeys.length; 34013 nextBlockOrder = new Array(collectionLength); 34014 34015 // locate existing items 34016 for (index = 0; index < collectionLength; index++) { 34017 key = (collection === collectionKeys) ? index : collectionKeys[index]; 34018 value = collection[key]; 34019 trackById = trackByIdFn($scope, key, value, index); 34020 if (lastBlockMap[trackById]) { 34021 // found previously seen block 34022 block = lastBlockMap[trackById]; 34023 delete lastBlockMap[trackById]; 34024 nextBlockMap[trackById] = block; 34025 nextBlockOrder[index] = block; 34026 } else if (nextBlockMap[trackById]) { 34027 // if collision detected. restore lastBlockMap and throw an error 34028 forEach(nextBlockOrder, function(block) { 34029 if (block && block.scope) lastBlockMap[block.id] = block; 34030 }); 34031 throw ngRepeatMinErr('dupes', 34032 'Duplicates in a repeater are not allowed. Use \'track by\' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}', 34033 expression, trackById, value); 34034 } else { 34035 // new never before seen block 34036 nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined}; 34037 nextBlockMap[trackById] = true; 34038 } 34039 } 34040 34041 // Clear the value property from the hashFnLocals object to prevent a reference to the last value 34042 // being leaked into the ngRepeatCompile function scope 34043 if (hashFnLocals) { 34044 hashFnLocals[valueIdentifier] = undefined; 34045 } 34046 34047 // remove leftover items 34048 for (var blockKey in lastBlockMap) { 34049 block = lastBlockMap[blockKey]; 34050 elementsToRemove = getBlockNodes(block.clone); 34051 $animate.leave(elementsToRemove); 34052 if (elementsToRemove[0].parentNode) { 34053 // if the element was not removed yet because of pending animation, mark it as deleted 34054 // so that we can ignore it later 34055 for (index = 0, length = elementsToRemove.length; index < length; index++) { 34056 elementsToRemove[index][NG_REMOVED] = true; 34057 } 34058 } 34059 block.scope.$destroy(); 34060 } 34061 34062 // we are not using forEach for perf reasons (trying to avoid #call) 34063 for (index = 0; index < collectionLength; index++) { 34064 key = (collection === collectionKeys) ? index : collectionKeys[index]; 34065 value = collection[key]; 34066 block = nextBlockOrder[index]; 34067 34068 if (block.scope) { 34069 // if we have already seen this object, then we need to reuse the 34070 // associated scope/element 34071 34072 nextNode = previousNode; 34073 34074 // skip nodes that are already pending removal via leave animation 34075 do { 34076 nextNode = nextNode.nextSibling; 34077 } while (nextNode && nextNode[NG_REMOVED]); 34078 34079 if (getBlockStart(block) !== nextNode) { 34080 // existing item which got moved 34081 $animate.move(getBlockNodes(block.clone), null, previousNode); 34082 } 34083 previousNode = getBlockEnd(block); 34084 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); 34085 } else { 34086 // new item which we don't know about 34087 $transclude(function ngRepeatTransclude(clone, scope) { 34088 block.scope = scope; 34089 // http://jsperf.com/clone-vs-createcomment 34090 var endNode = ngRepeatEndComment.cloneNode(false); 34091 clone[clone.length++] = endNode; 34092 34093 $animate.enter(clone, null, previousNode); 34094 previousNode = endNode; 34095 // Note: We only need the first/last node of the cloned nodes. 34096 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 34097 // by a directive with templateUrl when its template arrives. 34098 block.clone = clone; 34099 nextBlockMap[block.id] = block; 34100 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); 34101 }); 34102 } 34103 } 34104 lastBlockMap = nextBlockMap; 34105 }); 34106 }; 34107 } 34108 }; 34109}]; 34110 34111var NG_HIDE_CLASS = 'ng-hide'; 34112var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate'; 34113/** 34114 * @ngdoc directive 34115 * @name ngShow 34116 * @multiElement 34117 * 34118 * @description 34119 * The `ngShow` directive shows or hides the given HTML element based on the expression provided to 34120 * the `ngShow` attribute. 34121 * 34122 * The element is shown or hidden by removing or adding the `.ng-hide` CSS class onto the element. 34123 * The `.ng-hide` CSS class is predefined in AngularJS and sets the display style to none (using an 34124 * `!important` flag). For CSP mode please add `angular-csp.css` to your HTML file (see 34125 * {@link ng.directive:ngCsp ngCsp}). 34126 * 34127 * ```html 34128 * <!-- when $scope.myValue is truthy (element is visible) --> 34129 * <div ng-show="myValue"></div> 34130 * 34131 * <!-- when $scope.myValue is falsy (element is hidden) --> 34132 * <div ng-show="myValue" class="ng-hide"></div> 34133 * ``` 34134 * 34135 * When the `ngShow` expression evaluates to a falsy value then the `.ng-hide` CSS class is added 34136 * to the class attribute on the element causing it to become hidden. When truthy, the `.ng-hide` 34137 * CSS class is removed from the element causing the element not to appear hidden. 34138 * 34139 * ## Why is `!important` used? 34140 * 34141 * You may be wondering why `!important` is used for the `.ng-hide` CSS class. This is because the 34142 * `.ng-hide` selector can be easily overridden by heavier selectors. For example, something as 34143 * simple as changing the display style on a HTML list item would make hidden elements appear 34144 * visible. This also becomes a bigger issue when dealing with CSS frameworks. 34145 * 34146 * By using `!important`, the show and hide behavior will work as expected despite any clash between 34147 * CSS selector specificity (when `!important` isn't used with any conflicting styles). If a 34148 * developer chooses to override the styling to change how to hide an element then it is just a 34149 * matter of using `!important` in their own CSS code. 34150 * 34151 * ### Overriding `.ng-hide` 34152 * 34153 * By default, the `.ng-hide` class will style the element with `display: none !important`. If you 34154 * wish to change the hide behavior with `ngShow`/`ngHide`, you can simply overwrite the styles for 34155 * the `.ng-hide` CSS class. Note that the selector that needs to be used is actually 34156 * `.ng-hide:not(.ng-hide-animate)` to cope with extra animation classes that can be added. 34157 * 34158 * ```css 34159 * .ng-hide:not(.ng-hide-animate) { 34160 * /* These are just alternative ways of hiding an element */ 34161 * display: block!important; 34162 * position: absolute; 34163 * top: -9999px; 34164 * left: -9999px; 34165 * } 34166 * ``` 34167 * 34168 * By default you don't need to override anything in CSS and the animations will work around the 34169 * display style. 34170 * 34171 * @animations 34172 * | Animation | Occurs | 34173 * |-----------------------------------------------------|---------------------------------------------------------------------------------------------------------------| 34174 * | {@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. | 34175 * | {@link $animate#removeClass removeClass} `.ng-hide` | After the `ngShow` expression evaluates to a truthy value and just before contents are set to visible. | 34176 * 34177 * Animations in `ngShow`/`ngHide` work with the show and hide events that are triggered when the 34178 * directive expression is true and false. This system works like the animation system present with 34179 * `ngClass` except that you must also include the `!important` flag to override the display 34180 * property so that the elements are not actually hidden during the animation. 34181 * 34182 * ```css 34183 * /* A working example can be found at the bottom of this page. */ 34184 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 34185 * transition: all 0.5s linear; 34186 * } 34187 * 34188 * .my-element.ng-hide-add { ... } 34189 * .my-element.ng-hide-add.ng-hide-add-active { ... } 34190 * .my-element.ng-hide-remove { ... } 34191 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 34192 * ``` 34193 * 34194 * Keep in mind that, as of AngularJS version 1.3, there is no need to change the display property 34195 * to block during animation states - ngAnimate will automatically handle the style toggling for you. 34196 * 34197 * @element ANY 34198 * @param {expression} ngShow If the {@link guide/expression expression} is truthy/falsy then the 34199 * element is shown/hidden respectively. 34200 * 34201 * @example 34202 * A simple example, animating the element's opacity: 34203 * 34204 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-show-simple"> 34205 <file name="index.html"> 34206 Show: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br /> 34207 <div class="check-element animate-show-hide" ng-show="checked"> 34208 I show up when your checkbox is checked. 34209 </div> 34210 </file> 34211 <file name="animations.css"> 34212 .animate-show-hide.ng-hide { 34213 opacity: 0; 34214 } 34215 34216 .animate-show-hide.ng-hide-add, 34217 .animate-show-hide.ng-hide-remove { 34218 transition: all linear 0.5s; 34219 } 34220 34221 .check-element { 34222 border: 1px solid black; 34223 opacity: 1; 34224 padding: 10px; 34225 } 34226 </file> 34227 <file name="protractor.js" type="protractor"> 34228 it('should check ngShow', function() { 34229 var checkbox = element(by.model('checked')); 34230 var checkElem = element(by.css('.check-element')); 34231 34232 expect(checkElem.isDisplayed()).toBe(false); 34233 checkbox.click(); 34234 expect(checkElem.isDisplayed()).toBe(true); 34235 }); 34236 </file> 34237 </example> 34238 * 34239 * <hr /> 34240 * @example 34241 * A more complex example, featuring different show/hide animations: 34242 * 34243 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-show-complex"> 34244 <file name="index.html"> 34245 Show: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br /> 34246 <div class="check-element funky-show-hide" ng-show="checked"> 34247 I show up when your checkbox is checked. 34248 </div> 34249 </file> 34250 <file name="animations.css"> 34251 body { 34252 overflow: hidden; 34253 perspective: 1000px; 34254 } 34255 34256 .funky-show-hide.ng-hide-add { 34257 transform: rotateZ(0); 34258 transform-origin: right; 34259 transition: all 0.5s ease-in-out; 34260 } 34261 34262 .funky-show-hide.ng-hide-add.ng-hide-add-active { 34263 transform: rotateZ(-135deg); 34264 } 34265 34266 .funky-show-hide.ng-hide-remove { 34267 transform: rotateY(90deg); 34268 transform-origin: left; 34269 transition: all 0.5s ease; 34270 } 34271 34272 .funky-show-hide.ng-hide-remove.ng-hide-remove-active { 34273 transform: rotateY(0); 34274 } 34275 34276 .check-element { 34277 border: 1px solid black; 34278 opacity: 1; 34279 padding: 10px; 34280 } 34281 </file> 34282 <file name="protractor.js" type="protractor"> 34283 it('should check ngShow', function() { 34284 var checkbox = element(by.model('checked')); 34285 var checkElem = element(by.css('.check-element')); 34286 34287 expect(checkElem.isDisplayed()).toBe(false); 34288 checkbox.click(); 34289 expect(checkElem.isDisplayed()).toBe(true); 34290 }); 34291 </file> 34292 </example> 34293 * 34294 * @knownIssue 34295 * 34296 * ### Flickering when using ngShow to toggle between elements 34297 * 34298 * When using {@link ngShow} and / or {@link ngHide} to toggle between elements, it can 34299 * happen that both the element to show and the element to hide are visible for a very short time. 34300 * 34301 * This usually happens when the {@link ngAnimate ngAnimate module} is included, but no actual animations 34302 * are defined for {@link ngShow} / {@link ngHide}. Internet Explorer is affected more often than 34303 * other browsers. 34304 * 34305 * There are several way to mitigate this problem: 34306 * 34307 * - {@link guide/animations#how-to-selectively-enable-disable-and-skip-animations Disable animations on the affected elements}. 34308 * - Use {@link ngIf} or {@link ngSwitch} instead of {@link ngShow} / {@link ngHide}. 34309 * - Use the special CSS selector `ng-hide.ng-hide-animate` to set `{display: none}` or similar on the affected elements. 34310 * - Use `ng-class="{'ng-hide': expression}` instead of instead of {@link ngShow} / {@link ngHide}. 34311 * - Define an animation on the affected elements. 34312 */ 34313var ngShowDirective = ['$animate', function($animate) { 34314 return { 34315 restrict: 'A', 34316 multiElement: true, 34317 link: function(scope, element, attr) { 34318 scope.$watch(attr.ngShow, function ngShowWatchAction(value) { 34319 // we're adding a temporary, animation-specific class for ng-hide since this way 34320 // we can control when the element is actually displayed on screen without having 34321 // to have a global/greedy CSS selector that breaks when other animations are run. 34322 // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845 34323 $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, { 34324 tempClasses: NG_HIDE_IN_PROGRESS_CLASS 34325 }); 34326 }); 34327 } 34328 }; 34329}]; 34330 34331 34332/** 34333 * @ngdoc directive 34334 * @name ngHide 34335 * @multiElement 34336 * 34337 * @description 34338 * The `ngHide` directive shows or hides the given HTML element based on the expression provided to 34339 * the `ngHide` attribute. 34340 * 34341 * The element is shown or hidden by removing or adding the `.ng-hide` CSS class onto the element. 34342 * The `.ng-hide` CSS class is predefined in AngularJS and sets the display style to none (using an 34343 * `!important` flag). For CSP mode please add `angular-csp.css` to your HTML file (see 34344 * {@link ng.directive:ngCsp ngCsp}). 34345 * 34346 * ```html 34347 * <!-- when $scope.myValue is truthy (element is hidden) --> 34348 * <div ng-hide="myValue" class="ng-hide"></div> 34349 * 34350 * <!-- when $scope.myValue is falsy (element is visible) --> 34351 * <div ng-hide="myValue"></div> 34352 * ``` 34353 * 34354 * When the `ngHide` expression evaluates to a truthy value then the `.ng-hide` CSS class is added 34355 * to the class attribute on the element causing it to become hidden. When falsy, the `.ng-hide` 34356 * CSS class is removed from the element causing the element not to appear hidden. 34357 * 34358 * ## Why is `!important` used? 34359 * 34360 * You may be wondering why `!important` is used for the `.ng-hide` CSS class. This is because the 34361 * `.ng-hide` selector can be easily overridden by heavier selectors. For example, something as 34362 * simple as changing the display style on a HTML list item would make hidden elements appear 34363 * visible. This also becomes a bigger issue when dealing with CSS frameworks. 34364 * 34365 * By using `!important`, the show and hide behavior will work as expected despite any clash between 34366 * CSS selector specificity (when `!important` isn't used with any conflicting styles). If a 34367 * developer chooses to override the styling to change how to hide an element then it is just a 34368 * matter of using `!important` in their own CSS code. 34369 * 34370 * ### Overriding `.ng-hide` 34371 * 34372 * By default, the `.ng-hide` class will style the element with `display: none !important`. If you 34373 * wish to change the hide behavior with `ngShow`/`ngHide`, you can simply overwrite the styles for 34374 * the `.ng-hide` CSS class. Note that the selector that needs to be used is actually 34375 * `.ng-hide:not(.ng-hide-animate)` to cope with extra animation classes that can be added. 34376 * 34377 * ```css 34378 * .ng-hide:not(.ng-hide-animate) { 34379 * /* These are just alternative ways of hiding an element */ 34380 * display: block!important; 34381 * position: absolute; 34382 * top: -9999px; 34383 * left: -9999px; 34384 * } 34385 * ``` 34386 * 34387 * By default you don't need to override in CSS anything and the animations will work around the 34388 * display style. 34389 * 34390 * @animations 34391 * | Animation | Occurs | 34392 * |-----------------------------------------------------|------------------------------------------------------------------------------------------------------------| 34393 * | {@link $animate#addClass addClass} `.ng-hide` | After the `ngHide` expression evaluates to a truthy value and just before the contents are set to hidden. | 34394 * | {@link $animate#removeClass removeClass} `.ng-hide` | After the `ngHide` expression evaluates to a non truthy value and just before contents are set to visible. | 34395 * 34396 * Animations in `ngShow`/`ngHide` work with the show and hide events that are triggered when the 34397 * directive expression is true and false. This system works like the animation system present with 34398 * `ngClass` except that you must also include the `!important` flag to override the display 34399 * property so that the elements are not actually hidden during the animation. 34400 * 34401 * ```css 34402 * /* A working example can be found at the bottom of this page. */ 34403 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 34404 * transition: all 0.5s linear; 34405 * } 34406 * 34407 * .my-element.ng-hide-add { ... } 34408 * .my-element.ng-hide-add.ng-hide-add-active { ... } 34409 * .my-element.ng-hide-remove { ... } 34410 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 34411 * ``` 34412 * 34413 * Keep in mind that, as of AngularJS version 1.3, there is no need to change the display property 34414 * to block during animation states - ngAnimate will automatically handle the style toggling for you. 34415 * 34416 * @element ANY 34417 * @param {expression} ngHide If the {@link guide/expression expression} is truthy/falsy then the 34418 * element is hidden/shown respectively. 34419 * 34420 * @example 34421 * A simple example, animating the element's opacity: 34422 * 34423 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-hide-simple"> 34424 <file name="index.html"> 34425 Hide: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br /> 34426 <div class="check-element animate-show-hide" ng-hide="checked"> 34427 I hide when your checkbox is checked. 34428 </div> 34429 </file> 34430 <file name="animations.css"> 34431 .animate-show-hide.ng-hide { 34432 opacity: 0; 34433 } 34434 34435 .animate-show-hide.ng-hide-add, 34436 .animate-show-hide.ng-hide-remove { 34437 transition: all linear 0.5s; 34438 } 34439 34440 .check-element { 34441 border: 1px solid black; 34442 opacity: 1; 34443 padding: 10px; 34444 } 34445 </file> 34446 <file name="protractor.js" type="protractor"> 34447 it('should check ngHide', function() { 34448 var checkbox = element(by.model('checked')); 34449 var checkElem = element(by.css('.check-element')); 34450 34451 expect(checkElem.isDisplayed()).toBe(true); 34452 checkbox.click(); 34453 expect(checkElem.isDisplayed()).toBe(false); 34454 }); 34455 </file> 34456 </example> 34457 * 34458 * <hr /> 34459 * @example 34460 * A more complex example, featuring different show/hide animations: 34461 * 34462 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-hide-complex"> 34463 <file name="index.html"> 34464 Hide: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br /> 34465 <div class="check-element funky-show-hide" ng-hide="checked"> 34466 I hide when your checkbox is checked. 34467 </div> 34468 </file> 34469 <file name="animations.css"> 34470 body { 34471 overflow: hidden; 34472 perspective: 1000px; 34473 } 34474 34475 .funky-show-hide.ng-hide-add { 34476 transform: rotateZ(0); 34477 transform-origin: right; 34478 transition: all 0.5s ease-in-out; 34479 } 34480 34481 .funky-show-hide.ng-hide-add.ng-hide-add-active { 34482 transform: rotateZ(-135deg); 34483 } 34484 34485 .funky-show-hide.ng-hide-remove { 34486 transform: rotateY(90deg); 34487 transform-origin: left; 34488 transition: all 0.5s ease; 34489 } 34490 34491 .funky-show-hide.ng-hide-remove.ng-hide-remove-active { 34492 transform: rotateY(0); 34493 } 34494 34495 .check-element { 34496 border: 1px solid black; 34497 opacity: 1; 34498 padding: 10px; 34499 } 34500 </file> 34501 <file name="protractor.js" type="protractor"> 34502 it('should check ngHide', function() { 34503 var checkbox = element(by.model('checked')); 34504 var checkElem = element(by.css('.check-element')); 34505 34506 expect(checkElem.isDisplayed()).toBe(true); 34507 checkbox.click(); 34508 expect(checkElem.isDisplayed()).toBe(false); 34509 }); 34510 </file> 34511 </example> 34512 * 34513 * @knownIssue 34514 * 34515 * ### Flickering when using ngHide to toggle between elements 34516 * 34517 * When using {@link ngShow} and / or {@link ngHide} to toggle between elements, it can 34518 * happen that both the element to show and the element to hide are visible for a very short time. 34519 * 34520 * This usually happens when the {@link ngAnimate ngAnimate module} is included, but no actual animations 34521 * are defined for {@link ngShow} / {@link ngHide}. Internet Explorer is affected more often than 34522 * other browsers. 34523 * 34524 * There are several way to mitigate this problem: 34525 * 34526 * - {@link guide/animations#how-to-selectively-enable-disable-and-skip-animations Disable animations on the affected elements}. 34527 * - Use {@link ngIf} or {@link ngSwitch} instead of {@link ngShow} / {@link ngHide}. 34528 * - Use the special CSS selector `ng-hide.ng-hide-animate` to set `{display: none}` or similar on the affected elements. 34529 * - Use `ng-class="{'ng-hide': expression}` instead of instead of {@link ngShow} / {@link ngHide}. 34530 * - Define an animation on the affected elements. 34531 */ 34532var ngHideDirective = ['$animate', function($animate) { 34533 return { 34534 restrict: 'A', 34535 multiElement: true, 34536 link: function(scope, element, attr) { 34537 scope.$watch(attr.ngHide, function ngHideWatchAction(value) { 34538 // The comment inside of the ngShowDirective explains why we add and 34539 // remove a temporary class for the show/hide animation 34540 $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, { 34541 tempClasses: NG_HIDE_IN_PROGRESS_CLASS 34542 }); 34543 }); 34544 } 34545 }; 34546}]; 34547 34548/** 34549 * @ngdoc directive 34550 * @name ngStyle 34551 * @restrict AC 34552 * 34553 * @description 34554 * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally. 34555 * 34556 * @knownIssue 34557 * You should not use {@link guide/interpolation interpolation} in the value of the `style` 34558 * attribute, when using the `ngStyle` directive on the same element. 34559 * See {@link guide/interpolation#known-issues here} for more info. 34560 * 34561 * @element ANY 34562 * @param {expression} ngStyle 34563 * 34564 * {@link guide/expression Expression} which evals to an 34565 * object whose keys are CSS style names and values are corresponding values for those CSS 34566 * keys. 34567 * 34568 * Since some CSS style names are not valid keys for an object, they must be quoted. 34569 * See the 'background-color' style in the example below. 34570 * 34571 * @example 34572 <example name="ng-style"> 34573 <file name="index.html"> 34574 <input type="button" value="set color" ng-click="myStyle={color:'red'}"> 34575 <input type="button" value="set background" ng-click="myStyle={'background-color':'blue'}"> 34576 <input type="button" value="clear" ng-click="myStyle={}"> 34577 <br/> 34578 <span ng-style="myStyle">Sample Text</span> 34579 <pre>myStyle={{myStyle}}</pre> 34580 </file> 34581 <file name="style.css"> 34582 span { 34583 color: black; 34584 } 34585 </file> 34586 <file name="protractor.js" type="protractor"> 34587 var colorSpan = element(by.css('span')); 34588 34589 it('should check ng-style', function() { 34590 expect(colorSpan.getCssValue('color')).toMatch(/rgba\(0, 0, 0, 1\)|rgb\(0, 0, 0\)/); 34591 element(by.css('input[value=\'set color\']')).click(); 34592 expect(colorSpan.getCssValue('color')).toMatch(/rgba\(255, 0, 0, 1\)|rgb\(255, 0, 0\)/); 34593 element(by.css('input[value=clear]')).click(); 34594 expect(colorSpan.getCssValue('color')).toMatch(/rgba\(0, 0, 0, 1\)|rgb\(0, 0, 0\)/); 34595 }); 34596 </file> 34597 </example> 34598 */ 34599var ngStyleDirective = ngDirective(function(scope, element, attr) { 34600 scope.$watchCollection(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) { 34601 if (oldStyles && (newStyles !== oldStyles)) { 34602 forEach(oldStyles, function(val, style) { element.css(style, ''); }); 34603 } 34604 if (newStyles) element.css(newStyles); 34605 }); 34606}); 34607 34608/** 34609 * @ngdoc directive 34610 * @name ngSwitch 34611 * @restrict EA 34612 * 34613 * @description 34614 * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression. 34615 * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location 34616 * as specified in the template. 34617 * 34618 * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it 34619 * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element 34620 * matches the value obtained from the evaluated expression. In other words, you define a container element 34621 * (where you place the directive), place an expression on the **`on="..."` attribute** 34622 * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place 34623 * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on 34624 * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default 34625 * attribute is displayed. 34626 * 34627 * <div class="alert alert-info"> 34628 * Be aware that the attribute values to match against cannot be expressions. They are interpreted 34629 * as literal string values to match against. 34630 * For example, **`ng-switch-when="someVal"`** will match against the string `"someVal"` not against the 34631 * value of the expression `$scope.someVal`. 34632 * </div> 34633 34634 * @animations 34635 * | Animation | Occurs | 34636 * |----------------------------------|-------------------------------------| 34637 * | {@link ng.$animate#enter enter} | after the ngSwitch contents change and the matched child element is placed inside the container | 34638 * | {@link ng.$animate#leave leave} | after the ngSwitch contents change and just before the former contents are removed from the DOM | 34639 * 34640 * @usage 34641 * 34642 * ``` 34643 * <ANY ng-switch="expression"> 34644 * <ANY ng-switch-when="matchValue1">...</ANY> 34645 * <ANY ng-switch-when="matchValue2">...</ANY> 34646 * <ANY ng-switch-default>...</ANY> 34647 * </ANY> 34648 * ``` 34649 * 34650 * 34651 * @scope 34652 * @priority 1200 34653 * @param {*} ngSwitch|on expression to match against <code>ng-switch-when</code>. 34654 * On child elements add: 34655 * 34656 * * `ngSwitchWhen`: the case statement to match against. If match then this 34657 * case will be displayed. If the same match appears multiple times, all the 34658 * elements will be displayed. It is possible to associate multiple values to 34659 * the same `ngSwitchWhen` by defining the optional attribute 34660 * `ngSwitchWhenSeparator`. The separator will be used to split the value of 34661 * the `ngSwitchWhen` attribute into multiple tokens, and the element will show 34662 * if any of the `ngSwitch` evaluates to any of these tokens. 34663 * * `ngSwitchDefault`: the default case when no other case match. If there 34664 * are multiple default cases, all of them will be displayed when no other 34665 * case match. 34666 * 34667 * 34668 * @example 34669 <example module="switchExample" deps="angular-animate.js" animations="true" name="ng-switch"> 34670 <file name="index.html"> 34671 <div ng-controller="ExampleController"> 34672 <select ng-model="selection" ng-options="item for item in items"> 34673 </select> 34674 <code>selection={{selection}}</code> 34675 <hr/> 34676 <div class="animate-switch-container" 34677 ng-switch on="selection"> 34678 <div class="animate-switch" ng-switch-when="settings|options" ng-switch-when-separator="|">Settings Div</div> 34679 <div class="animate-switch" ng-switch-when="home">Home Span</div> 34680 <div class="animate-switch" ng-switch-default>default</div> 34681 </div> 34682 </div> 34683 </file> 34684 <file name="script.js"> 34685 angular.module('switchExample', ['ngAnimate']) 34686 .controller('ExampleController', ['$scope', function($scope) { 34687 $scope.items = ['settings', 'home', 'options', 'other']; 34688 $scope.selection = $scope.items[0]; 34689 }]); 34690 </file> 34691 <file name="animations.css"> 34692 .animate-switch-container { 34693 position:relative; 34694 background:white; 34695 border:1px solid black; 34696 height:40px; 34697 overflow:hidden; 34698 } 34699 34700 .animate-switch { 34701 padding:10px; 34702 } 34703 34704 .animate-switch.ng-animate { 34705 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 34706 34707 position:absolute; 34708 top:0; 34709 left:0; 34710 right:0; 34711 bottom:0; 34712 } 34713 34714 .animate-switch.ng-leave.ng-leave-active, 34715 .animate-switch.ng-enter { 34716 top:-50px; 34717 } 34718 .animate-switch.ng-leave, 34719 .animate-switch.ng-enter.ng-enter-active { 34720 top:0; 34721 } 34722 </file> 34723 <file name="protractor.js" type="protractor"> 34724 var switchElem = element(by.css('[ng-switch]')); 34725 var select = element(by.model('selection')); 34726 34727 it('should start in settings', function() { 34728 expect(switchElem.getText()).toMatch(/Settings Div/); 34729 }); 34730 it('should change to home', function() { 34731 select.all(by.css('option')).get(1).click(); 34732 expect(switchElem.getText()).toMatch(/Home Span/); 34733 }); 34734 it('should change to settings via "options"', function() { 34735 select.all(by.css('option')).get(2).click(); 34736 expect(switchElem.getText()).toMatch(/Settings Div/); 34737 }); 34738 it('should select default', function() { 34739 select.all(by.css('option')).get(3).click(); 34740 expect(switchElem.getText()).toMatch(/default/); 34741 }); 34742 </file> 34743 </example> 34744 */ 34745var ngSwitchDirective = ['$animate', '$compile', function($animate, $compile) { 34746 return { 34747 require: 'ngSwitch', 34748 34749 // asks for $scope to fool the BC controller module 34750 controller: ['$scope', function NgSwitchController() { 34751 this.cases = {}; 34752 }], 34753 link: function(scope, element, attr, ngSwitchController) { 34754 var watchExpr = attr.ngSwitch || attr.on, 34755 selectedTranscludes = [], 34756 selectedElements = [], 34757 previousLeaveAnimations = [], 34758 selectedScopes = []; 34759 34760 var spliceFactory = function(array, index) { 34761 return function(response) { 34762 if (response !== false) array.splice(index, 1); 34763 }; 34764 }; 34765 34766 scope.$watch(watchExpr, function ngSwitchWatchAction(value) { 34767 var i, ii; 34768 34769 // Start with the last, in case the array is modified during the loop 34770 while (previousLeaveAnimations.length) { 34771 $animate.cancel(previousLeaveAnimations.pop()); 34772 } 34773 34774 for (i = 0, ii = selectedScopes.length; i < ii; ++i) { 34775 var selected = getBlockNodes(selectedElements[i].clone); 34776 selectedScopes[i].$destroy(); 34777 var runner = previousLeaveAnimations[i] = $animate.leave(selected); 34778 runner.done(spliceFactory(previousLeaveAnimations, i)); 34779 } 34780 34781 selectedElements.length = 0; 34782 selectedScopes.length = 0; 34783 34784 if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) { 34785 forEach(selectedTranscludes, function(selectedTransclude) { 34786 selectedTransclude.transclude(function(caseElement, selectedScope) { 34787 selectedScopes.push(selectedScope); 34788 var anchor = selectedTransclude.element; 34789 caseElement[caseElement.length++] = $compile.$$createComment('end ngSwitchWhen'); 34790 var block = { clone: caseElement }; 34791 34792 selectedElements.push(block); 34793 $animate.enter(caseElement, anchor.parent(), anchor); 34794 }); 34795 }); 34796 } 34797 }); 34798 } 34799 }; 34800}]; 34801 34802var ngSwitchWhenDirective = ngDirective({ 34803 transclude: 'element', 34804 priority: 1200, 34805 require: '^ngSwitch', 34806 multiElement: true, 34807 link: function(scope, element, attrs, ctrl, $transclude) { 34808 34809 var cases = attrs.ngSwitchWhen.split(attrs.ngSwitchWhenSeparator).sort().filter( 34810 // Filter duplicate cases 34811 function(element, index, array) { return array[index - 1] !== element; } 34812 ); 34813 34814 forEach(cases, function(whenCase) { 34815 ctrl.cases['!' + whenCase] = (ctrl.cases['!' + whenCase] || []); 34816 ctrl.cases['!' + whenCase].push({ transclude: $transclude, element: element }); 34817 }); 34818 } 34819}); 34820 34821var ngSwitchDefaultDirective = ngDirective({ 34822 transclude: 'element', 34823 priority: 1200, 34824 require: '^ngSwitch', 34825 multiElement: true, 34826 link: function(scope, element, attr, ctrl, $transclude) { 34827 ctrl.cases['?'] = (ctrl.cases['?'] || []); 34828 ctrl.cases['?'].push({ transclude: $transclude, element: element }); 34829 } 34830}); 34831 34832/** 34833 * @ngdoc directive 34834 * @name ngTransclude 34835 * @restrict EAC 34836 * 34837 * @description 34838 * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion. 34839 * 34840 * You can specify that you want to insert a named transclusion slot, instead of the default slot, by providing the slot name 34841 * as the value of the `ng-transclude` or `ng-transclude-slot` attribute. 34842 * 34843 * If the transcluded content is not empty (i.e. contains one or more DOM nodes, including whitespace text nodes), any existing 34844 * content of this element will be removed before the transcluded content is inserted. 34845 * If the transcluded content is empty (or only whitespace), the existing content is left intact. This lets you provide fallback 34846 * content in the case that no transcluded content is provided. 34847 * 34848 * @element ANY 34849 * 34850 * @param {string} ngTransclude|ngTranscludeSlot the name of the slot to insert at this point. If this is not provided, is empty 34851 * or its value is the same as the name of the attribute then the default slot is used. 34852 * 34853 * @example 34854 * ### Basic transclusion 34855 * This example demonstrates basic transclusion of content into a component directive. 34856 * <example name="simpleTranscludeExample" module="transcludeExample"> 34857 * <file name="index.html"> 34858 * <script> 34859 * angular.module('transcludeExample', []) 34860 * .directive('pane', function(){ 34861 * return { 34862 * restrict: 'E', 34863 * transclude: true, 34864 * scope: { title:'@' }, 34865 * template: '<div style="border: 1px solid black;">' + 34866 * '<div style="background-color: gray">{{title}}</div>' + 34867 * '<ng-transclude></ng-transclude>' + 34868 * '</div>' 34869 * }; 34870 * }) 34871 * .controller('ExampleController', ['$scope', function($scope) { 34872 * $scope.title = 'Lorem Ipsum'; 34873 * $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; 34874 * }]); 34875 * </script> 34876 * <div ng-controller="ExampleController"> 34877 * <input ng-model="title" aria-label="title"> <br/> 34878 * <textarea ng-model="text" aria-label="text"></textarea> <br/> 34879 * <pane title="{{title}}"><span>{{text}}</span></pane> 34880 * </div> 34881 * </file> 34882 * <file name="protractor.js" type="protractor"> 34883 * it('should have transcluded', function() { 34884 * var titleElement = element(by.model('title')); 34885 * titleElement.clear(); 34886 * titleElement.sendKeys('TITLE'); 34887 * var textElement = element(by.model('text')); 34888 * textElement.clear(); 34889 * textElement.sendKeys('TEXT'); 34890 * expect(element(by.binding('title')).getText()).toEqual('TITLE'); 34891 * expect(element(by.binding('text')).getText()).toEqual('TEXT'); 34892 * }); 34893 * </file> 34894 * </example> 34895 * 34896 * @example 34897 * ### Transclude fallback content 34898 * This example shows how to use `NgTransclude` with fallback content, that 34899 * is displayed if no transcluded content is provided. 34900 * 34901 * <example module="transcludeFallbackContentExample" name="ng-transclude"> 34902 * <file name="index.html"> 34903 * <script> 34904 * angular.module('transcludeFallbackContentExample', []) 34905 * .directive('myButton', function(){ 34906 * return { 34907 * restrict: 'E', 34908 * transclude: true, 34909 * scope: true, 34910 * template: '<button style="cursor: pointer;">' + 34911 * '<ng-transclude>' + 34912 * '<b style="color: red;">Button1</b>' + 34913 * '</ng-transclude>' + 34914 * '</button>' 34915 * }; 34916 * }); 34917 * </script> 34918 * <!-- fallback button content --> 34919 * <my-button id="fallback"></my-button> 34920 * <!-- modified button content --> 34921 * <my-button id="modified"> 34922 * <i style="color: green;">Button2</i> 34923 * </my-button> 34924 * </file> 34925 * <file name="protractor.js" type="protractor"> 34926 * it('should have different transclude element content', function() { 34927 * expect(element(by.id('fallback')).getText()).toBe('Button1'); 34928 * expect(element(by.id('modified')).getText()).toBe('Button2'); 34929 * }); 34930 * </file> 34931 * </example> 34932 * 34933 * @example 34934 * ### Multi-slot transclusion 34935 * This example demonstrates using multi-slot transclusion in a component directive. 34936 * <example name="multiSlotTranscludeExample" module="multiSlotTranscludeExample"> 34937 * <file name="index.html"> 34938 * <style> 34939 * .title, .footer { 34940 * background-color: gray 34941 * } 34942 * </style> 34943 * <div ng-controller="ExampleController"> 34944 * <input ng-model="title" aria-label="title"> <br/> 34945 * <textarea ng-model="text" aria-label="text"></textarea> <br/> 34946 * <pane> 34947 * <pane-title><a ng-href="{{link}}">{{title}}</a></pane-title> 34948 * <pane-body><p>{{text}}</p></pane-body> 34949 * </pane> 34950 * </div> 34951 * </file> 34952 * <file name="app.js"> 34953 * angular.module('multiSlotTranscludeExample', []) 34954 * .directive('pane', function() { 34955 * return { 34956 * restrict: 'E', 34957 * transclude: { 34958 * 'title': '?paneTitle', 34959 * 'body': 'paneBody', 34960 * 'footer': '?paneFooter' 34961 * }, 34962 * template: '<div style="border: 1px solid black;">' + 34963 * '<div class="title" ng-transclude="title">Fallback Title</div>' + 34964 * '<div ng-transclude="body"></div>' + 34965 * '<div class="footer" ng-transclude="footer">Fallback Footer</div>' + 34966 * '</div>' 34967 * }; 34968 * }) 34969 * .controller('ExampleController', ['$scope', function($scope) { 34970 * $scope.title = 'Lorem Ipsum'; 34971 * $scope.link = 'https://google.com'; 34972 * $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; 34973 * }]); 34974 * </file> 34975 * <file name="protractor.js" type="protractor"> 34976 * it('should have transcluded the title and the body', function() { 34977 * var titleElement = element(by.model('title')); 34978 * titleElement.clear(); 34979 * titleElement.sendKeys('TITLE'); 34980 * var textElement = element(by.model('text')); 34981 * textElement.clear(); 34982 * textElement.sendKeys('TEXT'); 34983 * expect(element(by.css('.title')).getText()).toEqual('TITLE'); 34984 * expect(element(by.binding('text')).getText()).toEqual('TEXT'); 34985 * expect(element(by.css('.footer')).getText()).toEqual('Fallback Footer'); 34986 * }); 34987 * </file> 34988 * </example> 34989 */ 34990var ngTranscludeMinErr = minErr('ngTransclude'); 34991var ngTranscludeDirective = ['$compile', function($compile) { 34992 return { 34993 restrict: 'EAC', 34994 compile: function ngTranscludeCompile(tElement) { 34995 34996 // Remove and cache any original content to act as a fallback 34997 var fallbackLinkFn = $compile(tElement.contents()); 34998 tElement.empty(); 34999 35000 return function ngTranscludePostLink($scope, $element, $attrs, controller, $transclude) { 35001 35002 if (!$transclude) { 35003 throw ngTranscludeMinErr('orphan', 35004 'Illegal use of ngTransclude directive in the template! ' + 35005 'No parent directive that requires a transclusion found. ' + 35006 'Element: {0}', 35007 startingTag($element)); 35008 } 35009 35010 35011 // If the attribute is of the form: `ng-transclude="ng-transclude"` then treat it like the default 35012 if ($attrs.ngTransclude === $attrs.$attr.ngTransclude) { 35013 $attrs.ngTransclude = ''; 35014 } 35015 var slotName = $attrs.ngTransclude || $attrs.ngTranscludeSlot; 35016 35017 // If the slot is required and no transclusion content is provided then this call will throw an error 35018 $transclude(ngTranscludeCloneAttachFn, null, slotName); 35019 35020 // If the slot is optional and no transclusion content is provided then use the fallback content 35021 if (slotName && !$transclude.isSlotFilled(slotName)) { 35022 useFallbackContent(); 35023 } 35024 35025 function ngTranscludeCloneAttachFn(clone, transcludedScope) { 35026 if (clone.length && notWhitespace(clone)) { 35027 $element.append(clone); 35028 } else { 35029 useFallbackContent(); 35030 // There is nothing linked against the transcluded scope since no content was available, 35031 // so it should be safe to clean up the generated scope. 35032 transcludedScope.$destroy(); 35033 } 35034 } 35035 35036 function useFallbackContent() { 35037 // Since this is the fallback content rather than the transcluded content, 35038 // we link against the scope of this directive rather than the transcluded scope 35039 fallbackLinkFn($scope, function(clone) { 35040 $element.append(clone); 35041 }); 35042 } 35043 35044 function notWhitespace(nodes) { 35045 for (var i = 0, ii = nodes.length; i < ii; i++) { 35046 var node = nodes[i]; 35047 if (node.nodeType !== NODE_TYPE_TEXT || node.nodeValue.trim()) { 35048 return true; 35049 } 35050 } 35051 } 35052 }; 35053 } 35054 }; 35055}]; 35056 35057/** 35058 * @ngdoc directive 35059 * @name script 35060 * @restrict E 35061 * 35062 * @description 35063 * Load the content of a `<script>` element into {@link ng.$templateCache `$templateCache`}, so that the 35064 * template can be used by {@link ng.directive:ngInclude `ngInclude`}, 35065 * {@link ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the 35066 * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be 35067 * assigned through the element's `id`, which can then be used as a directive's `templateUrl`. 35068 * 35069 * @param {string} type Must be set to `'text/ng-template'`. 35070 * @param {string} id Cache name of the template. 35071 * 35072 * @example 35073 <example name="script-tag"> 35074 <file name="index.html"> 35075 <script type="text/ng-template" id="/tpl.html"> 35076 Content of the template. 35077 </script> 35078 35079 <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a> 35080 <div id="tpl-content" ng-include src="currentTpl"></div> 35081 </file> 35082 <file name="protractor.js" type="protractor"> 35083 it('should load template defined inside script tag', function() { 35084 element(by.css('#tpl-link')).click(); 35085 expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/); 35086 }); 35087 </file> 35088 </example> 35089 */ 35090var scriptDirective = ['$templateCache', function($templateCache) { 35091 return { 35092 restrict: 'E', 35093 terminal: true, 35094 compile: function(element, attr) { 35095 if (attr.type === 'text/ng-template') { 35096 var templateUrl = attr.id, 35097 text = element[0].text; 35098 35099 $templateCache.put(templateUrl, text); 35100 } 35101 } 35102 }; 35103}]; 35104 35105/* exported selectDirective, optionDirective */ 35106 35107var noopNgModelController = { $setViewValue: noop, $render: noop }; 35108 35109function setOptionSelectedStatus(optionEl, value) { 35110 optionEl.prop('selected', value); 35111 /** 35112 * When unselecting an option, setting the property to null / false should be enough 35113 * However, screenreaders might react to the selected attribute instead, see 35114 * https://github.com/angular/angular.js/issues/14419 35115 * Note: "selected" is a boolean attr and will be removed when the "value" arg in attr() is false 35116 * or null 35117 */ 35118 optionEl.attr('selected', value); 35119} 35120 35121/** 35122 * @ngdoc type 35123 * @name select.SelectController 35124 * 35125 * @description 35126 * The controller for the {@link ng.select select} directive. The controller exposes 35127 * a few utility methods that can be used to augment the behavior of a regular or an 35128 * {@link ng.ngOptions ngOptions} select element. 35129 * 35130 * @example 35131 * ### Set a custom error when the unknown option is selected 35132 * 35133 * This example sets a custom error "unknownValue" on the ngModelController 35134 * when the select element's unknown option is selected, i.e. when the model is set to a value 35135 * that is not matched by any option. 35136 * 35137 * <example name="select-unknown-value-error" module="staticSelect"> 35138 * <file name="index.html"> 35139 * <div ng-controller="ExampleController"> 35140 * <form name="myForm"> 35141 * <label for="testSelect"> Single select: </label><br> 35142 * <select name="testSelect" ng-model="selected" unknown-value-error> 35143 * <option value="option-1">Option 1</option> 35144 * <option value="option-2">Option 2</option> 35145 * </select><br> 35146 * <span class="error" ng-if="myForm.testSelect.$error.unknownValue"> 35147 * Error: The current model doesn't match any option</span><br> 35148 * 35149 * <button ng-click="forceUnknownOption()">Force unknown option</button><br> 35150 * </form> 35151 * </div> 35152 * </file> 35153 * <file name="app.js"> 35154 * angular.module('staticSelect', []) 35155 * .controller('ExampleController', ['$scope', function($scope) { 35156 * $scope.selected = null; 35157 * 35158 * $scope.forceUnknownOption = function() { 35159 * $scope.selected = 'nonsense'; 35160 * }; 35161 * }]) 35162 * .directive('unknownValueError', function() { 35163 * return { 35164 * require: ['ngModel', 'select'], 35165 * link: function(scope, element, attrs, ctrls) { 35166 * var ngModelCtrl = ctrls[0]; 35167 * var selectCtrl = ctrls[1]; 35168 * 35169 * ngModelCtrl.$validators.unknownValue = function(modelValue, viewValue) { 35170 * if (selectCtrl.$isUnknownOptionSelected()) { 35171 * return false; 35172 * } 35173 * 35174 * return true; 35175 * }; 35176 * } 35177 * 35178 * }; 35179 * }); 35180 * </file> 35181 *</example> 35182 * 35183 * 35184 * @example 35185 * ### Set the "required" error when the unknown option is selected. 35186 * 35187 * By default, the "required" error on the ngModelController is only set on a required select 35188 * when the empty option is selected. This example adds a custom directive that also sets the 35189 * error when the unknown option is selected. 35190 * 35191 * <example name="select-unknown-value-required" module="staticSelect"> 35192 * <file name="index.html"> 35193 * <div ng-controller="ExampleController"> 35194 * <form name="myForm"> 35195 * <label for="testSelect"> Select: </label><br> 35196 * <select name="testSelect" ng-model="selected" required unknown-value-required> 35197 * <option value="option-1">Option 1</option> 35198 * <option value="option-2">Option 2</option> 35199 * </select><br> 35200 * <span class="error" ng-if="myForm.testSelect.$error.required">Error: Please select a value</span><br> 35201 * 35202 * <button ng-click="forceUnknownOption()">Force unknown option</button><br> 35203 * </form> 35204 * </div> 35205 * </file> 35206 * <file name="app.js"> 35207 * angular.module('staticSelect', []) 35208 * .controller('ExampleController', ['$scope', function($scope) { 35209 * $scope.selected = null; 35210 * 35211 * $scope.forceUnknownOption = function() { 35212 * $scope.selected = 'nonsense'; 35213 * }; 35214 * }]) 35215 * .directive('unknownValueRequired', function() { 35216 * return { 35217 * priority: 1, // This directive must run after the required directive has added its validator 35218 * require: ['ngModel', 'select'], 35219 * link: function(scope, element, attrs, ctrls) { 35220 * var ngModelCtrl = ctrls[0]; 35221 * var selectCtrl = ctrls[1]; 35222 * 35223 * var originalRequiredValidator = ngModelCtrl.$validators.required; 35224 * 35225 * ngModelCtrl.$validators.required = function() { 35226 * if (attrs.required && selectCtrl.$isUnknownOptionSelected()) { 35227 * return false; 35228 * } 35229 * 35230 * return originalRequiredValidator.apply(this, arguments); 35231 * }; 35232 * } 35233 * }; 35234 * }); 35235 * </file> 35236 * <file name="protractor.js" type="protractor"> 35237 * it('should show the error message when the unknown option is selected', function() { 35238 35239 var error = element(by.className('error')); 35240 35241 expect(error.getText()).toBe('Error: Please select a value'); 35242 35243 element(by.cssContainingText('option', 'Option 1')).click(); 35244 35245 expect(error.isPresent()).toBe(false); 35246 35247 element(by.tagName('button')).click(); 35248 35249 expect(error.getText()).toBe('Error: Please select a value'); 35250 }); 35251 * </file> 35252 *</example> 35253 * 35254 * 35255 */ 35256var SelectController = 35257 ['$element', '$scope', /** @this */ function($element, $scope) { 35258 35259 var self = this, 35260 optionsMap = new NgMap(); 35261 35262 self.selectValueMap = {}; // Keys are the hashed values, values the original values 35263 35264 // If the ngModel doesn't get provided then provide a dummy noop version to prevent errors 35265 self.ngModelCtrl = noopNgModelController; 35266 self.multiple = false; 35267 35268 // The "unknown" option is one that is prepended to the list if the viewValue 35269 // does not match any of the options. When it is rendered the value of the unknown 35270 // option is '? XXX ?' where XXX is the hashKey of the value that is not known. 35271 // 35272 // Support: IE 9 only 35273 // We can't just jqLite('<option>') since jqLite is not smart enough 35274 // to create it in <select> and IE barfs otherwise. 35275 self.unknownOption = jqLite(window.document.createElement('option')); 35276 35277 // The empty option is an option with the value '' that the application developer can 35278 // provide inside the select. It is always selectable and indicates that a "null" selection has 35279 // been made by the user. 35280 // If the select has an empty option, and the model of the select is set to "undefined" or "null", 35281 // the empty option is selected. 35282 // If the model is set to a different unmatched value, the unknown option is rendered and 35283 // selected, i.e both are present, because a "null" selection and an unknown value are different. 35284 self.hasEmptyOption = false; 35285 self.emptyOption = undefined; 35286 35287 self.renderUnknownOption = function(val) { 35288 var unknownVal = self.generateUnknownOptionValue(val); 35289 self.unknownOption.val(unknownVal); 35290 $element.prepend(self.unknownOption); 35291 setOptionSelectedStatus(self.unknownOption, true); 35292 $element.val(unknownVal); 35293 }; 35294 35295 self.updateUnknownOption = function(val) { 35296 var unknownVal = self.generateUnknownOptionValue(val); 35297 self.unknownOption.val(unknownVal); 35298 setOptionSelectedStatus(self.unknownOption, true); 35299 $element.val(unknownVal); 35300 }; 35301 35302 self.generateUnknownOptionValue = function(val) { 35303 return '? ' + hashKey(val) + ' ?'; 35304 }; 35305 35306 self.removeUnknownOption = function() { 35307 if (self.unknownOption.parent()) self.unknownOption.remove(); 35308 }; 35309 35310 self.selectEmptyOption = function() { 35311 if (self.emptyOption) { 35312 $element.val(''); 35313 setOptionSelectedStatus(self.emptyOption, true); 35314 } 35315 }; 35316 35317 self.unselectEmptyOption = function() { 35318 if (self.hasEmptyOption) { 35319 setOptionSelectedStatus(self.emptyOption, false); 35320 } 35321 }; 35322 35323 $scope.$on('$destroy', function() { 35324 // disable unknown option so that we don't do work when the whole select is being destroyed 35325 self.renderUnknownOption = noop; 35326 }); 35327 35328 // Read the value of the select control, the implementation of this changes depending 35329 // upon whether the select can have multiple values and whether ngOptions is at work. 35330 self.readValue = function readSingleValue() { 35331 var val = $element.val(); 35332 // ngValue added option values are stored in the selectValueMap, normal interpolations are not 35333 var realVal = val in self.selectValueMap ? self.selectValueMap[val] : val; 35334 35335 if (self.hasOption(realVal)) { 35336 return realVal; 35337 } 35338 35339 return null; 35340 }; 35341 35342 35343 // Write the value to the select control, the implementation of this changes depending 35344 // upon whether the select can have multiple values and whether ngOptions is at work. 35345 self.writeValue = function writeSingleValue(value) { 35346 // Make sure to remove the selected attribute from the previously selected option 35347 // Otherwise, screen readers might get confused 35348 var currentlySelectedOption = $element[0].options[$element[0].selectedIndex]; 35349 if (currentlySelectedOption) setOptionSelectedStatus(jqLite(currentlySelectedOption), false); 35350 35351 if (self.hasOption(value)) { 35352 self.removeUnknownOption(); 35353 35354 var hashedVal = hashKey(value); 35355 $element.val(hashedVal in self.selectValueMap ? hashedVal : value); 35356 35357 // Set selected attribute and property on selected option for screen readers 35358 var selectedOption = $element[0].options[$element[0].selectedIndex]; 35359 setOptionSelectedStatus(jqLite(selectedOption), true); 35360 } else { 35361 self.selectUnknownOrEmptyOption(value); 35362 } 35363 }; 35364 35365 35366 // Tell the select control that an option, with the given value, has been added 35367 self.addOption = function(value, element) { 35368 // Skip comment nodes, as they only pollute the `optionsMap` 35369 if (element[0].nodeType === NODE_TYPE_COMMENT) return; 35370 35371 assertNotHasOwnProperty(value, '"option value"'); 35372 if (value === '') { 35373 self.hasEmptyOption = true; 35374 self.emptyOption = element; 35375 } 35376 var count = optionsMap.get(value) || 0; 35377 optionsMap.set(value, count + 1); 35378 // Only render at the end of a digest. This improves render performance when many options 35379 // are added during a digest and ensures all relevant options are correctly marked as selected 35380 scheduleRender(); 35381 }; 35382 35383 // Tell the select control that an option, with the given value, has been removed 35384 self.removeOption = function(value) { 35385 var count = optionsMap.get(value); 35386 if (count) { 35387 if (count === 1) { 35388 optionsMap.delete(value); 35389 if (value === '') { 35390 self.hasEmptyOption = false; 35391 self.emptyOption = undefined; 35392 } 35393 } else { 35394 optionsMap.set(value, count - 1); 35395 } 35396 } 35397 }; 35398 35399 // Check whether the select control has an option matching the given value 35400 self.hasOption = function(value) { 35401 return !!optionsMap.get(value); 35402 }; 35403 35404 /** 35405 * @ngdoc method 35406 * @name select.SelectController#$hasEmptyOption 35407 * 35408 * @description 35409 * 35410 * Returns `true` if the select element currently has an empty option 35411 * element, i.e. an option that signifies that the select is empty / the selection is null. 35412 * 35413 */ 35414 self.$hasEmptyOption = function() { 35415 return self.hasEmptyOption; 35416 }; 35417 35418 /** 35419 * @ngdoc method 35420 * @name select.SelectController#$isUnknownOptionSelected 35421 * 35422 * @description 35423 * 35424 * Returns `true` if the select element's unknown option is selected. The unknown option is added 35425 * and automatically selected whenever the select model doesn't match any option. 35426 * 35427 */ 35428 self.$isUnknownOptionSelected = function() { 35429 // Presence of the unknown option means it is selected 35430 return $element[0].options[0] === self.unknownOption[0]; 35431 }; 35432 35433 /** 35434 * @ngdoc method 35435 * @name select.SelectController#$isEmptyOptionSelected 35436 * 35437 * @description 35438 * 35439 * Returns `true` if the select element has an empty option and this empty option is currently 35440 * selected. Returns `false` if the select element has no empty option or it is not selected. 35441 * 35442 */ 35443 self.$isEmptyOptionSelected = function() { 35444 return self.hasEmptyOption && $element[0].options[$element[0].selectedIndex] === self.emptyOption[0]; 35445 }; 35446 35447 self.selectUnknownOrEmptyOption = function(value) { 35448 if (value == null && self.emptyOption) { 35449 self.removeUnknownOption(); 35450 self.selectEmptyOption(); 35451 } else if (self.unknownOption.parent().length) { 35452 self.updateUnknownOption(value); 35453 } else { 35454 self.renderUnknownOption(value); 35455 } 35456 }; 35457 35458 var renderScheduled = false; 35459 function scheduleRender() { 35460 if (renderScheduled) return; 35461 renderScheduled = true; 35462 $scope.$$postDigest(function() { 35463 renderScheduled = false; 35464 self.ngModelCtrl.$render(); 35465 }); 35466 } 35467 35468 var updateScheduled = false; 35469 function scheduleViewValueUpdate(renderAfter) { 35470 if (updateScheduled) return; 35471 35472 updateScheduled = true; 35473 35474 $scope.$$postDigest(function() { 35475 if ($scope.$$destroyed) return; 35476 35477 updateScheduled = false; 35478 self.ngModelCtrl.$setViewValue(self.readValue()); 35479 if (renderAfter) self.ngModelCtrl.$render(); 35480 }); 35481 } 35482 35483 35484 self.registerOption = function(optionScope, optionElement, optionAttrs, interpolateValueFn, interpolateTextFn) { 35485 35486 if (optionAttrs.$attr.ngValue) { 35487 // The value attribute is set by ngValue 35488 var oldVal, hashedVal; 35489 optionAttrs.$observe('value', function valueAttributeObserveAction(newVal) { 35490 35491 var removal; 35492 var previouslySelected = optionElement.prop('selected'); 35493 35494 if (isDefined(hashedVal)) { 35495 self.removeOption(oldVal); 35496 delete self.selectValueMap[hashedVal]; 35497 removal = true; 35498 } 35499 35500 hashedVal = hashKey(newVal); 35501 oldVal = newVal; 35502 self.selectValueMap[hashedVal] = newVal; 35503 self.addOption(newVal, optionElement); 35504 // Set the attribute directly instead of using optionAttrs.$set - this stops the observer 35505 // from firing a second time. Other $observers on value will also get the result of the 35506 // ngValue expression, not the hashed value 35507 optionElement.attr('value', hashedVal); 35508 35509 if (removal && previouslySelected) { 35510 scheduleViewValueUpdate(); 35511 } 35512 35513 }); 35514 } else if (interpolateValueFn) { 35515 // The value attribute is interpolated 35516 optionAttrs.$observe('value', function valueAttributeObserveAction(newVal) { 35517 // This method is overwritten in ngOptions and has side-effects! 35518 self.readValue(); 35519 35520 var removal; 35521 var previouslySelected = optionElement.prop('selected'); 35522 35523 if (isDefined(oldVal)) { 35524 self.removeOption(oldVal); 35525 removal = true; 35526 } 35527 oldVal = newVal; 35528 self.addOption(newVal, optionElement); 35529 35530 if (removal && previouslySelected) { 35531 scheduleViewValueUpdate(); 35532 } 35533 }); 35534 } else if (interpolateTextFn) { 35535 // The text content is interpolated 35536 optionScope.$watch(interpolateTextFn, function interpolateWatchAction(newVal, oldVal) { 35537 optionAttrs.$set('value', newVal); 35538 var previouslySelected = optionElement.prop('selected'); 35539 if (oldVal !== newVal) { 35540 self.removeOption(oldVal); 35541 } 35542 self.addOption(newVal, optionElement); 35543 35544 if (oldVal && previouslySelected) { 35545 scheduleViewValueUpdate(); 35546 } 35547 }); 35548 } else { 35549 // The value attribute is static 35550 self.addOption(optionAttrs.value, optionElement); 35551 } 35552 35553 35554 optionAttrs.$observe('disabled', function(newVal) { 35555 35556 // Since model updates will also select disabled options (like ngOptions), 35557 // we only have to handle options becoming disabled, not enabled 35558 35559 if (newVal === 'true' || newVal && optionElement.prop('selected')) { 35560 if (self.multiple) { 35561 scheduleViewValueUpdate(true); 35562 } else { 35563 self.ngModelCtrl.$setViewValue(null); 35564 self.ngModelCtrl.$render(); 35565 } 35566 } 35567 }); 35568 35569 optionElement.on('$destroy', function() { 35570 var currentValue = self.readValue(); 35571 var removeValue = optionAttrs.value; 35572 35573 self.removeOption(removeValue); 35574 scheduleRender(); 35575 35576 if (self.multiple && currentValue && currentValue.indexOf(removeValue) !== -1 || 35577 currentValue === removeValue 35578 ) { 35579 // When multiple (selected) options are destroyed at the same time, we don't want 35580 // to run a model update for each of them. Instead, run a single update in the $$postDigest 35581 scheduleViewValueUpdate(true); 35582 } 35583 }); 35584 }; 35585}]; 35586 35587/** 35588 * @ngdoc directive 35589 * @name select 35590 * @restrict E 35591 * 35592 * @description 35593 * HTML `select` element with AngularJS data-binding. 35594 * 35595 * The `select` directive is used together with {@link ngModel `ngModel`} to provide data-binding 35596 * between the scope and the `<select>` control (including setting default values). 35597 * It also handles dynamic `<option>` elements, which can be added using the {@link ngRepeat `ngRepeat}` or 35598 * {@link ngOptions `ngOptions`} directives. 35599 * 35600 * When an item in the `<select>` menu is selected, the value of the selected option will be bound 35601 * to the model identified by the `ngModel` directive. With static or repeated options, this is 35602 * the content of the `value` attribute or the textContent of the `<option>`, if the value attribute is missing. 35603 * Value and textContent can be interpolated. 35604 * 35605 * The {@link select.SelectController select controller} exposes utility functions that can be used 35606 * to manipulate the select's behavior. 35607 * 35608 * ## Matching model and option values 35609 * 35610 * In general, the match between the model and an option is evaluated by strictly comparing the model 35611 * value against the value of the available options. 35612 * 35613 * If you are setting the option value with the option's `value` attribute, or textContent, the 35614 * value will always be a `string` which means that the model value must also be a string. 35615 * Otherwise the `select` directive cannot match them correctly. 35616 * 35617 * To bind the model to a non-string value, you can use one of the following strategies: 35618 * - the {@link ng.ngOptions `ngOptions`} directive 35619 * ({@link ng.select#using-select-with-ngoptions-and-setting-a-default-value}) 35620 * - the {@link ng.ngValue `ngValue`} directive, which allows arbitrary expressions to be 35621 * option values ({@link ng.select#using-ngvalue-to-bind-the-model-to-an-array-of-objects Example}) 35622 * - model $parsers / $formatters to convert the string value 35623 * ({@link ng.select#binding-select-to-a-non-string-value-via-ngmodel-parsing-formatting Example}) 35624 * 35625 * If the viewValue of `ngModel` does not match any of the options, then the control 35626 * will automatically add an "unknown" option, which it then removes when the mismatch is resolved. 35627 * 35628 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can 35629 * be nested into the `<select>` element. This element will then represent the `null` or "not selected" 35630 * option. See example below for demonstration. 35631 * 35632 * ## Choosing between `ngRepeat` and `ngOptions` 35633 * 35634 * In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions 35635 * ngOptions} to achieve a similar result. However, `ngOptions` provides some benefits: 35636 * - more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the 35637 * comprehension expression 35638 * - reduced memory consumption by not creating a new scope for each repeated instance 35639 * - increased render speed by creating the options in a documentFragment instead of individually 35640 * 35641 * Specifically, select with repeated options slows down significantly starting at 2000 options in 35642 * Chrome and Internet Explorer / Edge. 35643 * 35644 * 35645 * @param {string} ngModel Assignable AngularJS expression to data-bind to. 35646 * @param {string=} name Property name of the form under which the control is published. 35647 * @param {string=} multiple Allows multiple options to be selected. The selected values will be 35648 * bound to the model as an array. 35649 * @param {string=} required Sets `required` validation error key if the value is not entered. 35650 * @param {string=} ngRequired Adds required attribute and required validation constraint to 35651 * the element when the ngRequired expression evaluates to true. Use ngRequired instead of required 35652 * when you want to data-bind to the required attribute. 35653 * @param {string=} ngChange AngularJS expression to be executed when selected option(s) changes due to user 35654 * interaction with the select element. 35655 * @param {string=} ngOptions sets the options that the select is populated with and defines what is 35656 * set on the model on selection. See {@link ngOptions `ngOptions`}. 35657 * @param {string=} ngAttrSize sets the size of the select element dynamically. Uses the 35658 * {@link guide/interpolation#-ngattr-for-binding-to-arbitrary-attributes ngAttr} directive. 35659 * 35660 * 35661 * @example 35662 * ### Simple `select` elements with static options 35663 * 35664 * <example name="static-select" module="staticSelect"> 35665 * <file name="index.html"> 35666 * <div ng-controller="ExampleController"> 35667 * <form name="myForm"> 35668 * <label for="singleSelect"> Single select: </label><br> 35669 * <select name="singleSelect" ng-model="data.singleSelect"> 35670 * <option value="option-1">Option 1</option> 35671 * <option value="option-2">Option 2</option> 35672 * </select><br> 35673 * 35674 * <label for="singleSelect"> Single select with "not selected" option and dynamic option values: </label><br> 35675 * <select name="singleSelect" id="singleSelect" ng-model="data.singleSelect"> 35676 * <option value="">---Please select---</option> <!-- not selected / blank option --> 35677 * <option value="{{data.option1}}">Option 1</option> <!-- interpolation --> 35678 * <option value="option-2">Option 2</option> 35679 * </select><br> 35680 * <button ng-click="forceUnknownOption()">Force unknown option</button><br> 35681 * <tt>singleSelect = {{data.singleSelect}}</tt> 35682 * 35683 * <hr> 35684 * <label for="multipleSelect"> Multiple select: </label><br> 35685 * <select name="multipleSelect" id="multipleSelect" ng-model="data.multipleSelect" multiple> 35686 * <option value="option-1">Option 1</option> 35687 * <option value="option-2">Option 2</option> 35688 * <option value="option-3">Option 3</option> 35689 * </select><br> 35690 * <tt>multipleSelect = {{data.multipleSelect}}</tt><br/> 35691 * </form> 35692 * </div> 35693 * </file> 35694 * <file name="app.js"> 35695 * angular.module('staticSelect', []) 35696 * .controller('ExampleController', ['$scope', function($scope) { 35697 * $scope.data = { 35698 * singleSelect: null, 35699 * multipleSelect: [], 35700 * option1: 'option-1' 35701 * }; 35702 * 35703 * $scope.forceUnknownOption = function() { 35704 * $scope.data.singleSelect = 'nonsense'; 35705 * }; 35706 * }]); 35707 * </file> 35708 *</example> 35709 * 35710 * @example 35711 * ### Using `ngRepeat` to generate `select` options 35712 * <example name="select-ngrepeat" module="ngrepeatSelect"> 35713 * <file name="index.html"> 35714 * <div ng-controller="ExampleController"> 35715 * <form name="myForm"> 35716 * <label for="repeatSelect"> Repeat select: </label> 35717 * <select name="repeatSelect" id="repeatSelect" ng-model="data.model"> 35718 * <option ng-repeat="option in data.availableOptions" value="{{option.id}}">{{option.name}}</option> 35719 * </select> 35720 * </form> 35721 * <hr> 35722 * <tt>model = {{data.model}}</tt><br/> 35723 * </div> 35724 * </file> 35725 * <file name="app.js"> 35726 * angular.module('ngrepeatSelect', []) 35727 * .controller('ExampleController', ['$scope', function($scope) { 35728 * $scope.data = { 35729 * model: null, 35730 * availableOptions: [ 35731 * {id: '1', name: 'Option A'}, 35732 * {id: '2', name: 'Option B'}, 35733 * {id: '3', name: 'Option C'} 35734 * ] 35735 * }; 35736 * }]); 35737 * </file> 35738 *</example> 35739 * 35740 * @example 35741 * ### Using `ngValue` to bind the model to an array of objects 35742 * <example name="select-ngvalue" module="ngvalueSelect"> 35743 * <file name="index.html"> 35744 * <div ng-controller="ExampleController"> 35745 * <form name="myForm"> 35746 * <label for="ngvalueselect"> ngvalue select: </label> 35747 * <select size="6" name="ngvalueselect" ng-model="data.model" multiple> 35748 * <option ng-repeat="option in data.availableOptions" ng-value="option.value">{{option.name}}</option> 35749 * </select> 35750 * </form> 35751 * <hr> 35752 * <pre>model = {{data.model | json}}</pre><br/> 35753 * </div> 35754 * </file> 35755 * <file name="app.js"> 35756 * angular.module('ngvalueSelect', []) 35757 * .controller('ExampleController', ['$scope', function($scope) { 35758 * $scope.data = { 35759 * model: null, 35760 * availableOptions: [ 35761 {value: 'myString', name: 'string'}, 35762 {value: 1, name: 'integer'}, 35763 {value: true, name: 'boolean'}, 35764 {value: null, name: 'null'}, 35765 {value: {prop: 'value'}, name: 'object'}, 35766 {value: ['a'], name: 'array'} 35767 * ] 35768 * }; 35769 * }]); 35770 * </file> 35771 *</example> 35772 * 35773 * @example 35774 * ### Using `select` with `ngOptions` and setting a default value 35775 * See the {@link ngOptions ngOptions documentation} for more `ngOptions` usage examples. 35776 * 35777 * <example name="select-with-default-values" module="defaultValueSelect"> 35778 * <file name="index.html"> 35779 * <div ng-controller="ExampleController"> 35780 * <form name="myForm"> 35781 * <label for="mySelect">Make a choice:</label> 35782 * <select name="mySelect" id="mySelect" 35783 * ng-options="option.name for option in data.availableOptions track by option.id" 35784 * ng-model="data.selectedOption"></select> 35785 * </form> 35786 * <hr> 35787 * <tt>option = {{data.selectedOption}}</tt><br/> 35788 * </div> 35789 * </file> 35790 * <file name="app.js"> 35791 * angular.module('defaultValueSelect', []) 35792 * .controller('ExampleController', ['$scope', function($scope) { 35793 * $scope.data = { 35794 * availableOptions: [ 35795 * {id: '1', name: 'Option A'}, 35796 * {id: '2', name: 'Option B'}, 35797 * {id: '3', name: 'Option C'} 35798 * ], 35799 * selectedOption: {id: '3', name: 'Option C'} //This sets the default value of the select in the ui 35800 * }; 35801 * }]); 35802 * </file> 35803 *</example> 35804 * 35805 * @example 35806 * ### Binding `select` to a non-string value via `ngModel` parsing / formatting 35807 * 35808 * <example name="select-with-non-string-options" module="nonStringSelect"> 35809 * <file name="index.html"> 35810 * <select ng-model="model.id" convert-to-number> 35811 * <option value="0">Zero</option> 35812 * <option value="1">One</option> 35813 * <option value="2">Two</option> 35814 * </select> 35815 * {{ model }} 35816 * </file> 35817 * <file name="app.js"> 35818 * angular.module('nonStringSelect', []) 35819 * .run(function($rootScope) { 35820 * $rootScope.model = { id: 2 }; 35821 * }) 35822 * .directive('convertToNumber', function() { 35823 * return { 35824 * require: 'ngModel', 35825 * link: function(scope, element, attrs, ngModel) { 35826 * ngModel.$parsers.push(function(val) { 35827 * return parseInt(val, 10); 35828 * }); 35829 * ngModel.$formatters.push(function(val) { 35830 * return '' + val; 35831 * }); 35832 * } 35833 * }; 35834 * }); 35835 * </file> 35836 * <file name="protractor.js" type="protractor"> 35837 * it('should initialize to model', function() { 35838 * expect(element(by.model('model.id')).$('option:checked').getText()).toEqual('Two'); 35839 * }); 35840 * </file> 35841 * </example> 35842 * 35843 */ 35844var selectDirective = function() { 35845 35846 return { 35847 restrict: 'E', 35848 require: ['select', '?ngModel'], 35849 controller: SelectController, 35850 priority: 1, 35851 link: { 35852 pre: selectPreLink, 35853 post: selectPostLink 35854 } 35855 }; 35856 35857 function selectPreLink(scope, element, attr, ctrls) { 35858 35859 var selectCtrl = ctrls[0]; 35860 var ngModelCtrl = ctrls[1]; 35861 35862 // if ngModel is not defined, we don't need to do anything but set the registerOption 35863 // function to noop, so options don't get added internally 35864 if (!ngModelCtrl) { 35865 selectCtrl.registerOption = noop; 35866 return; 35867 } 35868 35869 35870 selectCtrl.ngModelCtrl = ngModelCtrl; 35871 35872 // When the selected item(s) changes we delegate getting the value of the select control 35873 // to the `readValue` method, which can be changed if the select can have multiple 35874 // selected values or if the options are being generated by `ngOptions` 35875 element.on('change', function() { 35876 selectCtrl.removeUnknownOption(); 35877 scope.$apply(function() { 35878 ngModelCtrl.$setViewValue(selectCtrl.readValue()); 35879 }); 35880 }); 35881 35882 // If the select allows multiple values then we need to modify how we read and write 35883 // values from and to the control; also what it means for the value to be empty and 35884 // we have to add an extra watch since ngModel doesn't work well with arrays - it 35885 // doesn't trigger rendering if only an item in the array changes. 35886 if (attr.multiple) { 35887 selectCtrl.multiple = true; 35888 35889 // Read value now needs to check each option to see if it is selected 35890 selectCtrl.readValue = function readMultipleValue() { 35891 var array = []; 35892 forEach(element.find('option'), function(option) { 35893 if (option.selected && !option.disabled) { 35894 var val = option.value; 35895 array.push(val in selectCtrl.selectValueMap ? selectCtrl.selectValueMap[val] : val); 35896 } 35897 }); 35898 return array; 35899 }; 35900 35901 // Write value now needs to set the selected property of each matching option 35902 selectCtrl.writeValue = function writeMultipleValue(value) { 35903 forEach(element.find('option'), function(option) { 35904 var shouldBeSelected = !!value && (includes(value, option.value) || 35905 includes(value, selectCtrl.selectValueMap[option.value])); 35906 var currentlySelected = option.selected; 35907 35908 // Support: IE 9-11 only, Edge 12-15+ 35909 // In IE and Edge adding options to the selection via shift+click/UP/DOWN 35910 // will de-select already selected options if "selected" on those options was set 35911 // more than once (i.e. when the options were already selected) 35912 // So we only modify the selected property if necessary. 35913 // Note: this behavior cannot be replicated via unit tests because it only shows in the 35914 // actual user interface. 35915 if (shouldBeSelected !== currentlySelected) { 35916 setOptionSelectedStatus(jqLite(option), shouldBeSelected); 35917 } 35918 35919 }); 35920 }; 35921 35922 // we have to do it on each watch since ngModel watches reference, but 35923 // we need to work of an array, so we need to see if anything was inserted/removed 35924 var lastView, lastViewRef = NaN; 35925 scope.$watch(function selectMultipleWatch() { 35926 if (lastViewRef === ngModelCtrl.$viewValue && !equals(lastView, ngModelCtrl.$viewValue)) { 35927 lastView = shallowCopy(ngModelCtrl.$viewValue); 35928 ngModelCtrl.$render(); 35929 } 35930 lastViewRef = ngModelCtrl.$viewValue; 35931 }); 35932 35933 // If we are a multiple select then value is now a collection 35934 // so the meaning of $isEmpty changes 35935 ngModelCtrl.$isEmpty = function(value) { 35936 return !value || value.length === 0; 35937 }; 35938 35939 } 35940 } 35941 35942 function selectPostLink(scope, element, attrs, ctrls) { 35943 // if ngModel is not defined, we don't need to do anything 35944 var ngModelCtrl = ctrls[1]; 35945 if (!ngModelCtrl) return; 35946 35947 var selectCtrl = ctrls[0]; 35948 35949 // We delegate rendering to the `writeValue` method, which can be changed 35950 // if the select can have multiple selected values or if the options are being 35951 // generated by `ngOptions`. 35952 // This must be done in the postLink fn to prevent $render to be called before 35953 // all nodes have been linked correctly. 35954 ngModelCtrl.$render = function() { 35955 selectCtrl.writeValue(ngModelCtrl.$viewValue); 35956 }; 35957 } 35958}; 35959 35960 35961// The option directive is purely designed to communicate the existence (or lack of) 35962// of dynamically created (and destroyed) option elements to their containing select 35963// directive via its controller. 35964var optionDirective = ['$interpolate', function($interpolate) { 35965 return { 35966 restrict: 'E', 35967 priority: 100, 35968 compile: function(element, attr) { 35969 var interpolateValueFn, interpolateTextFn; 35970 35971 if (isDefined(attr.ngValue)) { 35972 // Will be handled by registerOption 35973 } else if (isDefined(attr.value)) { 35974 // If the value attribute is defined, check if it contains an interpolation 35975 interpolateValueFn = $interpolate(attr.value, true); 35976 } else { 35977 // If the value attribute is not defined then we fall back to the 35978 // text content of the option element, which may be interpolated 35979 interpolateTextFn = $interpolate(element.text(), true); 35980 if (!interpolateTextFn) { 35981 attr.$set('value', element.text()); 35982 } 35983 } 35984 35985 return function(scope, element, attr) { 35986 // This is an optimization over using ^^ since we don't want to have to search 35987 // all the way to the root of the DOM for every single option element 35988 var selectCtrlName = '$selectController', 35989 parent = element.parent(), 35990 selectCtrl = parent.data(selectCtrlName) || 35991 parent.parent().data(selectCtrlName); // in case we are in optgroup 35992 35993 if (selectCtrl) { 35994 selectCtrl.registerOption(scope, element, attr, interpolateValueFn, interpolateTextFn); 35995 } 35996 }; 35997 } 35998 }; 35999}]; 36000 36001/** 36002 * @ngdoc directive 36003 * @name ngRequired 36004 * @restrict A 36005 * 36006 * @param {expression} ngRequired AngularJS expression. If it evaluates to `true`, it sets the 36007 * `required` attribute to the element and adds the `required` 36008 * {@link ngModel.NgModelController#$validators `validator`}. 36009 * 36010 * @description 36011 * 36012 * ngRequired adds the required {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. 36013 * It is most often used for {@link input `input`} and {@link select `select`} controls, but can also be 36014 * applied to custom controls. 36015 * 36016 * The directive sets the `required` attribute on the element if the AngularJS expression inside 36017 * `ngRequired` evaluates to true. A special directive for setting `required` is necessary because we 36018 * cannot use interpolation inside `required`. See the {@link guide/interpolation interpolation guide} 36019 * for more info. 36020 * 36021 * The validator will set the `required` error key to true if the `required` attribute is set and 36022 * calling {@link ngModel.NgModelController#$isEmpty `NgModelController.$isEmpty`} with the 36023 * {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} returns `true`. For example, the 36024 * `$isEmpty()` implementation for `input[text]` checks the length of the `$viewValue`. When developing 36025 * custom controls, `$isEmpty()` can be overwritten to account for a $viewValue that is not string-based. 36026 * 36027 * @example 36028 * <example name="ngRequiredDirective" module="ngRequiredExample"> 36029 * <file name="index.html"> 36030 * <script> 36031 * angular.module('ngRequiredExample', []) 36032 * .controller('ExampleController', ['$scope', function($scope) { 36033 * $scope.required = true; 36034 * }]); 36035 * </script> 36036 * <div ng-controller="ExampleController"> 36037 * <form name="form"> 36038 * <label for="required">Toggle required: </label> 36039 * <input type="checkbox" ng-model="required" id="required" /> 36040 * <br> 36041 * <label for="input">This input must be filled if `required` is true: </label> 36042 * <input type="text" ng-model="model" id="input" name="input" ng-required="required" /><br> 36043 * <hr> 36044 * required error set? = <code>{{form.input.$error.required}}</code><br> 36045 * model = <code>{{model}}</code> 36046 * </form> 36047 * </div> 36048 * </file> 36049 * <file name="protractor.js" type="protractor"> 36050 var required = element(by.binding('form.input.$error.required')); 36051 var model = element(by.binding('model')); 36052 var input = element(by.id('input')); 36053 36054 it('should set the required error', function() { 36055 expect(required.getText()).toContain('true'); 36056 36057 input.sendKeys('123'); 36058 expect(required.getText()).not.toContain('true'); 36059 expect(model.getText()).toContain('123'); 36060 }); 36061 * </file> 36062 * </example> 36063 */ 36064var requiredDirective = ['$parse', function($parse) { 36065 return { 36066 restrict: 'A', 36067 require: '?ngModel', 36068 link: function(scope, elm, attr, ctrl) { 36069 if (!ctrl) return; 36070 // For boolean attributes like required, presence means true 36071 var value = attr.hasOwnProperty('required') || $parse(attr.ngRequired)(scope); 36072 36073 if (!attr.ngRequired) { 36074 // force truthy in case we are on non input element 36075 // (input elements do this automatically for boolean attributes like required) 36076 attr.required = true; 36077 } 36078 36079 ctrl.$validators.required = function(modelValue, viewValue) { 36080 return !value || !ctrl.$isEmpty(viewValue); 36081 }; 36082 36083 attr.$observe('required', function(newVal) { 36084 36085 if (value !== newVal) { 36086 value = newVal; 36087 ctrl.$validate(); 36088 } 36089 }); 36090 } 36091 }; 36092}]; 36093 36094/** 36095 * @ngdoc directive 36096 * @name ngPattern 36097 * @restrict A 36098 * 36099 * @param {expression|RegExp} ngPattern AngularJS expression that must evaluate to a `RegExp` or a `String` 36100 * parsable into a `RegExp`, or a `RegExp` literal. See above for 36101 * more details. 36102 * 36103 * @description 36104 * 36105 * ngPattern adds the pattern {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. 36106 * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls. 36107 * 36108 * The validator sets the `pattern` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} 36109 * does not match a RegExp which is obtained from the `ngPattern` attribute value: 36110 * - the value is an AngularJS expression: 36111 * - If the expression evaluates to a RegExp object, then this is used directly. 36112 * - If the expression evaluates to a string, then it will be converted to a RegExp after wrapping it 36113 * in `^` and `$` characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`. 36114 * - If the value is a RegExp literal, e.g. `ngPattern="/^\d+$/"`, it is used directly. 36115 * 36116 * <div class="alert alert-info"> 36117 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 36118 * start at the index of the last search's match, thus not taking the whole input value into 36119 * account. 36120 * </div> 36121 * 36122 * <div class="alert alert-info"> 36123 * **Note:** This directive is also added when the plain `pattern` attribute is used, with two 36124 * differences: 36125 * <ol> 36126 * <li> 36127 * `ngPattern` does not set the `pattern` attribute and therefore HTML5 constraint validation is 36128 * not available. 36129 * </li> 36130 * <li> 36131 * The `ngPattern` attribute must be an expression, while the `pattern` value must be 36132 * interpolated. 36133 * </li> 36134 * </ol> 36135 * </div> 36136 * 36137 * @example 36138 * <example name="ngPatternDirective" module="ngPatternExample"> 36139 * <file name="index.html"> 36140 * <script> 36141 * angular.module('ngPatternExample', []) 36142 * .controller('ExampleController', ['$scope', function($scope) { 36143 * $scope.regex = '\\d+'; 36144 * }]); 36145 * </script> 36146 * <div ng-controller="ExampleController"> 36147 * <form name="form"> 36148 * <label for="regex">Set a pattern (regex string): </label> 36149 * <input type="text" ng-model="regex" id="regex" /> 36150 * <br> 36151 * <label for="input">This input is restricted by the current pattern: </label> 36152 * <input type="text" ng-model="model" id="input" name="input" ng-pattern="regex" /><br> 36153 * <hr> 36154 * input valid? = <code>{{form.input.$valid}}</code><br> 36155 * model = <code>{{model}}</code> 36156 * </form> 36157 * </div> 36158 * </file> 36159 * <file name="protractor.js" type="protractor"> 36160 var model = element(by.binding('model')); 36161 var input = element(by.id('input')); 36162 36163 it('should validate the input with the default pattern', function() { 36164 input.sendKeys('aaa'); 36165 expect(model.getText()).not.toContain('aaa'); 36166 36167 input.clear().then(function() { 36168 input.sendKeys('123'); 36169 expect(model.getText()).toContain('123'); 36170 }); 36171 }); 36172 * </file> 36173 * </example> 36174 */ 36175var patternDirective = ['$parse', function($parse) { 36176 return { 36177 restrict: 'A', 36178 require: '?ngModel', 36179 compile: function(tElm, tAttr) { 36180 var patternExp; 36181 var parseFn; 36182 36183 if (tAttr.ngPattern) { 36184 patternExp = tAttr.ngPattern; 36185 36186 // ngPattern might be a scope expression, or an inlined regex, which is not parsable. 36187 // We get value of the attribute here, so we can compare the old and the new value 36188 // in the observer to avoid unnecessary validations 36189 if (tAttr.ngPattern.charAt(0) === '/' && REGEX_STRING_REGEXP.test(tAttr.ngPattern)) { 36190 parseFn = function() { return tAttr.ngPattern; }; 36191 } else { 36192 parseFn = $parse(tAttr.ngPattern); 36193 } 36194 } 36195 36196 return function(scope, elm, attr, ctrl) { 36197 if (!ctrl) return; 36198 36199 var attrVal = attr.pattern; 36200 36201 if (attr.ngPattern) { 36202 attrVal = parseFn(scope); 36203 } else { 36204 patternExp = attr.pattern; 36205 } 36206 36207 var regexp = parsePatternAttr(attrVal, patternExp, elm); 36208 36209 attr.$observe('pattern', function(newVal) { 36210 var oldRegexp = regexp; 36211 36212 regexp = parsePatternAttr(newVal, patternExp, elm); 36213 36214 if ((oldRegexp && oldRegexp.toString()) !== (regexp && regexp.toString())) { 36215 ctrl.$validate(); 36216 } 36217 }); 36218 36219 ctrl.$validators.pattern = function(modelValue, viewValue) { 36220 // HTML5 pattern constraint validates the input value, so we validate the viewValue 36221 return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue); 36222 }; 36223 }; 36224 } 36225 36226 }; 36227}]; 36228 36229/** 36230 * @ngdoc directive 36231 * @name ngMaxlength 36232 * @restrict A 36233 * 36234 * @param {expression} ngMaxlength AngularJS expression that must evaluate to a `Number` or `String` 36235 * parsable into a `Number`. Used as value for the `maxlength` 36236 * {@link ngModel.NgModelController#$validators validator}. 36237 * 36238 * @description 36239 * 36240 * ngMaxlength adds the maxlength {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. 36241 * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls. 36242 * 36243 * The validator sets the `maxlength` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} 36244 * is longer than the integer obtained by evaluating the AngularJS expression given in the 36245 * `ngMaxlength` attribute value. 36246 * 36247 * <div class="alert alert-info"> 36248 * **Note:** This directive is also added when the plain `maxlength` attribute is used, with two 36249 * differences: 36250 * <ol> 36251 * <li> 36252 * `ngMaxlength` does not set the `maxlength` attribute and therefore HTML5 constraint 36253 * validation is not available. 36254 * </li> 36255 * <li> 36256 * The `ngMaxlength` attribute must be an expression, while the `maxlength` value must be 36257 * interpolated. 36258 * </li> 36259 * </ol> 36260 * </div> 36261 * 36262 * @example 36263 * <example name="ngMaxlengthDirective" module="ngMaxlengthExample"> 36264 * <file name="index.html"> 36265 * <script> 36266 * angular.module('ngMaxlengthExample', []) 36267 * .controller('ExampleController', ['$scope', function($scope) { 36268 * $scope.maxlength = 5; 36269 * }]); 36270 * </script> 36271 * <div ng-controller="ExampleController"> 36272 * <form name="form"> 36273 * <label for="maxlength">Set a maxlength: </label> 36274 * <input type="number" ng-model="maxlength" id="maxlength" /> 36275 * <br> 36276 * <label for="input">This input is restricted by the current maxlength: </label> 36277 * <input type="text" ng-model="model" id="input" name="input" ng-maxlength="maxlength" /><br> 36278 * <hr> 36279 * input valid? = <code>{{form.input.$valid}}</code><br> 36280 * model = <code>{{model}}</code> 36281 * </form> 36282 * </div> 36283 * </file> 36284 * <file name="protractor.js" type="protractor"> 36285 var model = element(by.binding('model')); 36286 var input = element(by.id('input')); 36287 36288 it('should validate the input with the default maxlength', function() { 36289 input.sendKeys('abcdef'); 36290 expect(model.getText()).not.toContain('abcdef'); 36291 36292 input.clear().then(function() { 36293 input.sendKeys('abcde'); 36294 expect(model.getText()).toContain('abcde'); 36295 }); 36296 }); 36297 * </file> 36298 * </example> 36299 */ 36300var maxlengthDirective = ['$parse', function($parse) { 36301 return { 36302 restrict: 'A', 36303 require: '?ngModel', 36304 link: function(scope, elm, attr, ctrl) { 36305 if (!ctrl) return; 36306 36307 var maxlength = attr.maxlength || $parse(attr.ngMaxlength)(scope); 36308 var maxlengthParsed = parseLength(maxlength); 36309 36310 attr.$observe('maxlength', function(value) { 36311 if (maxlength !== value) { 36312 maxlengthParsed = parseLength(value); 36313 maxlength = value; 36314 ctrl.$validate(); 36315 } 36316 }); 36317 ctrl.$validators.maxlength = function(modelValue, viewValue) { 36318 return (maxlengthParsed < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlengthParsed); 36319 }; 36320 } 36321 }; 36322}]; 36323 36324/** 36325 * @ngdoc directive 36326 * @name ngMinlength 36327 * @restrict A 36328 * 36329 * @param {expression} ngMinlength AngularJS expression that must evaluate to a `Number` or `String` 36330 * parsable into a `Number`. Used as value for the `minlength` 36331 * {@link ngModel.NgModelController#$validators validator}. 36332 * 36333 * @description 36334 * 36335 * ngMinlength adds the minlength {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. 36336 * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls. 36337 * 36338 * The validator sets the `minlength` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} 36339 * is shorter than the integer obtained by evaluating the AngularJS expression given in the 36340 * `ngMinlength` attribute value. 36341 * 36342 * <div class="alert alert-info"> 36343 * **Note:** This directive is also added when the plain `minlength` attribute is used, with two 36344 * differences: 36345 * <ol> 36346 * <li> 36347 * `ngMinlength` does not set the `minlength` attribute and therefore HTML5 constraint 36348 * validation is not available. 36349 * </li> 36350 * <li> 36351 * The `ngMinlength` value must be an expression, while the `minlength` value must be 36352 * interpolated. 36353 * </li> 36354 * </ol> 36355 * </div> 36356 * 36357 * @example 36358 * <example name="ngMinlengthDirective" module="ngMinlengthExample"> 36359 * <file name="index.html"> 36360 * <script> 36361 * angular.module('ngMinlengthExample', []) 36362 * .controller('ExampleController', ['$scope', function($scope) { 36363 * $scope.minlength = 3; 36364 * }]); 36365 * </script> 36366 * <div ng-controller="ExampleController"> 36367 * <form name="form"> 36368 * <label for="minlength">Set a minlength: </label> 36369 * <input type="number" ng-model="minlength" id="minlength" /> 36370 * <br> 36371 * <label for="input">This input is restricted by the current minlength: </label> 36372 * <input type="text" ng-model="model" id="input" name="input" ng-minlength="minlength" /><br> 36373 * <hr> 36374 * input valid? = <code>{{form.input.$valid}}</code><br> 36375 * model = <code>{{model}}</code> 36376 * </form> 36377 * </div> 36378 * </file> 36379 * <file name="protractor.js" type="protractor"> 36380 var model = element(by.binding('model')); 36381 var input = element(by.id('input')); 36382 36383 it('should validate the input with the default minlength', function() { 36384 input.sendKeys('ab'); 36385 expect(model.getText()).not.toContain('ab'); 36386 36387 input.sendKeys('abc'); 36388 expect(model.getText()).toContain('abc'); 36389 }); 36390 * </file> 36391 * </example> 36392 */ 36393var minlengthDirective = ['$parse', function($parse) { 36394 return { 36395 restrict: 'A', 36396 require: '?ngModel', 36397 link: function(scope, elm, attr, ctrl) { 36398 if (!ctrl) return; 36399 36400 var minlength = attr.minlength || $parse(attr.ngMinlength)(scope); 36401 var minlengthParsed = parseLength(minlength) || -1; 36402 36403 attr.$observe('minlength', function(value) { 36404 if (minlength !== value) { 36405 minlengthParsed = parseLength(value) || -1; 36406 minlength = value; 36407 ctrl.$validate(); 36408 } 36409 36410 }); 36411 ctrl.$validators.minlength = function(modelValue, viewValue) { 36412 return ctrl.$isEmpty(viewValue) || viewValue.length >= minlengthParsed; 36413 }; 36414 } 36415 }; 36416}]; 36417 36418 36419function parsePatternAttr(regex, patternExp, elm) { 36420 if (!regex) return undefined; 36421 36422 if (isString(regex)) { 36423 regex = new RegExp('^' + regex + '$'); 36424 } 36425 36426 if (!regex.test) { 36427 throw minErr('ngPattern')('noregexp', 36428 'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp, 36429 regex, startingTag(elm)); 36430 } 36431 36432 return regex; 36433} 36434 36435function parseLength(val) { 36436 var intVal = toInt(val); 36437 return isNumberNaN(intVal) ? -1 : intVal; 36438} 36439 36440if (window.angular.bootstrap) { 36441 // AngularJS is already loaded, so we can return here... 36442 if (window.console) { 36443 console.log('WARNING: Tried to load AngularJS more than once.'); 36444 } 36445 return; 36446} 36447 36448// try to bind to jquery now so that one can write jqLite(fn) 36449// but we will rebind on bootstrap again. 36450bindJQuery(); 36451 36452publishExternalAPI(angular); 36453 36454angular.module("ngLocale", [], ["$provide", function($provide) { 36455var PLURAL_CATEGORY = {ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"}; 36456function getDecimals(n) { 36457 n = n + ''; 36458 var i = n.indexOf('.'); 36459 return (i == -1) ? 0 : n.length - i - 1; 36460} 36461 36462function getVF(n, opt_precision) { 36463 var v = opt_precision; 36464 36465 if (undefined === v) { 36466 v = Math.min(getDecimals(n), 3); 36467 } 36468 36469 var base = Math.pow(10, v); 36470 var f = ((n * base) | 0) % base; 36471 return {v: v, f: f}; 36472} 36473 36474$provide.value("$locale", { 36475 "DATETIME_FORMATS": { 36476 "AMPMS": [ 36477 "AM", 36478 "PM" 36479 ], 36480 "DAY": [ 36481 "Sunday", 36482 "Monday", 36483 "Tuesday", 36484 "Wednesday", 36485 "Thursday", 36486 "Friday", 36487 "Saturday" 36488 ], 36489 "ERANAMES": [ 36490 "Before Christ", 36491 "Anno Domini" 36492 ], 36493 "ERAS": [ 36494 "BC", 36495 "AD" 36496 ], 36497 "FIRSTDAYOFWEEK": 6, 36498 "MONTH": [ 36499 "January", 36500 "February", 36501 "March", 36502 "April", 36503 "May", 36504 "June", 36505 "July", 36506 "August", 36507 "September", 36508 "October", 36509 "November", 36510 "December" 36511 ], 36512 "SHORTDAY": [ 36513 "Sun", 36514 "Mon", 36515 "Tue", 36516 "Wed", 36517 "Thu", 36518 "Fri", 36519 "Sat" 36520 ], 36521 "SHORTMONTH": [ 36522 "Jan", 36523 "Feb", 36524 "Mar", 36525 "Apr", 36526 "May", 36527 "Jun", 36528 "Jul", 36529 "Aug", 36530 "Sep", 36531 "Oct", 36532 "Nov", 36533 "Dec" 36534 ], 36535 "STANDALONEMONTH": [ 36536 "January", 36537 "February", 36538 "March", 36539 "April", 36540 "May", 36541 "June", 36542 "July", 36543 "August", 36544 "September", 36545 "October", 36546 "November", 36547 "December" 36548 ], 36549 "WEEKENDRANGE": [ 36550 5, 36551 6 36552 ], 36553 "fullDate": "EEEE, MMMM d, y", 36554 "longDate": "MMMM d, y", 36555 "medium": "MMM d, y h:mm:ss a", 36556 "mediumDate": "MMM d, y", 36557 "mediumTime": "h:mm:ss a", 36558 "short": "M/d/yy h:mm a", 36559 "shortDate": "M/d/yy", 36560 "shortTime": "h:mm a" 36561 }, 36562 "NUMBER_FORMATS": { 36563 "CURRENCY_SYM": "$", 36564 "DECIMAL_SEP": ".", 36565 "GROUP_SEP": ",", 36566 "PATTERNS": [ 36567 { 36568 "gSize": 3, 36569 "lgSize": 3, 36570 "maxFrac": 3, 36571 "minFrac": 0, 36572 "minInt": 1, 36573 "negPre": "-", 36574 "negSuf": "", 36575 "posPre": "", 36576 "posSuf": "" 36577 }, 36578 { 36579 "gSize": 3, 36580 "lgSize": 3, 36581 "maxFrac": 2, 36582 "minFrac": 2, 36583 "minInt": 1, 36584 "negPre": "-\u00a4", 36585 "negSuf": "", 36586 "posPre": "\u00a4", 36587 "posSuf": "" 36588 } 36589 ] 36590 }, 36591 "id": "en-us", 36592 "localeID": "en_US", 36593 "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;} 36594}); 36595}]); 36596 36597 jqLite(function() { 36598 angularInit(window.document, bootstrap); 36599 }); 36600 36601})(window); 36602 36603!window.angular.$$csp().noInlineStyle && window.angular.element(document.head).prepend(window.angular.element('<style>').text('@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate){display:none !important;}ng\\:form{display:block;}.ng-animate-shim{visibility:hidden;}.ng-anchor{position:absolute;}'));
36603
vendor: 16,592 bytes, lines 36604-37029
36604/** 36605 * @license AngularJS v1.8.3 36606 * (c) 2010-2020 Google LLC. http://angularjs.org 36607 * License: MIT 36608 */ 36609(function(window, angular) {'use strict'; 36610 36611/** 36612 * @ngdoc module 36613 * @name ngAria 36614 * @description 36615 * 36616 * The `ngAria` module provides support for common 36617 * [<abbr title="Accessible Rich Internet Applications">ARIA</abbr>](http://www.w3.org/TR/wai-aria/) 36618 * attributes that convey state or semantic information about the application for users 36619 * of assistive technologies, such as screen readers. 36620 * 36621 * ## Usage 36622 * 36623 * For ngAria to do its magic, simply include the module `ngAria` as a dependency. The following 36624 * directives are supported: 36625 * `ngModel`, `ngChecked`, `ngReadonly`, `ngRequired`, `ngValue`, `ngDisabled`, `ngShow`, `ngHide`, 36626 * `ngClick`, `ngDblClick`, and `ngMessages`. 36627 * 36628 * Below is a more detailed breakdown of the attributes handled by ngAria: 36629 * 36630 * | Directive | Supported Attributes | 36631 * |---------------------------------------------|-----------------------------------------------------------------------------------------------------| 36632 * | {@link ng.directive:ngModel ngModel} | aria-checked, aria-valuemin, aria-valuemax, aria-valuenow, aria-invalid, aria-required, input roles | 36633 * | {@link ng.directive:ngDisabled ngDisabled} | aria-disabled | 36634 * | {@link ng.directive:ngRequired ngRequired} | aria-required | 36635 * | {@link ng.directive:ngChecked ngChecked} | aria-checked | 36636 * | {@link ng.directive:ngReadonly ngReadonly} | aria-readonly | 36637 * | {@link ng.directive:ngValue ngValue} | aria-checked | 36638 * | {@link ng.directive:ngShow ngShow} | aria-hidden | 36639 * | {@link ng.directive:ngHide ngHide} | aria-hidden | 36640 * | {@link ng.directive:ngDblclick ngDblclick} | tabindex | 36641 * | {@link module:ngMessages ngMessages} | aria-live | 36642 * | {@link ng.directive:ngClick ngClick} | tabindex, keydown event, button role | 36643 * 36644 * Find out more information about each directive by reading the 36645 * {@link guide/accessibility ngAria Developer Guide}. 36646 * 36647 * ## Example 36648 * Using ngDisabled with ngAria: 36649 * ```html 36650 * <md-checkbox ng-disabled="disabled"> 36651 * ``` 36652 * Becomes: 36653 * ```html 36654 * <md-checkbox ng-disabled="disabled" aria-disabled="true"> 36655 * ``` 36656 * 36657 * ## Disabling Specific Attributes 36658 * It is possible to disable individual attributes added by ngAria with the 36659 * {@link ngAria.$ariaProvider#config config} method. For more details, see the 36660 * {@link guide/accessibility Developer Guide}. 36661 * 36662 * ## Disabling `ngAria` on Specific Elements 36663 * It is possible to make `ngAria` ignore a specific element, by adding the `ng-aria-disable` 36664 * attribute on it. Note that only the element itself (and not its child elements) will be ignored. 36665 */ 36666var ARIA_DISABLE_ATTR = 'ngAriaDisable'; 36667 36668var ngAriaModule = angular.module('ngAria', ['ng']). 36669 info({ angularVersion: '1.8.3' }). 36670 provider('$aria', $AriaProvider); 36671 36672/** 36673* Internal Utilities 36674*/ 36675var nativeAriaNodeNames = ['BUTTON', 'A', 'INPUT', 'TEXTAREA', 'SELECT', 'DETAILS', 'SUMMARY']; 36676 36677var isNodeOneOf = function(elem, nodeTypeArray) { 36678 if (nodeTypeArray.indexOf(elem[0].nodeName) !== -1) { 36679 return true; 36680 } 36681}; 36682/** 36683 * @ngdoc provider 36684 * @name $ariaProvider 36685 * @this 36686 * 36687 * @description 36688 * 36689 * Used for configuring the ARIA attributes injected and managed by ngAria. 36690 * 36691 * ```js 36692 * angular.module('myApp', ['ngAria'], function config($ariaProvider) { 36693 * $ariaProvider.config({ 36694 * ariaValue: true, 36695 * tabindex: false 36696 * }); 36697 * }); 36698 *``` 36699 * 36700 * ## Dependencies 36701 * Requires the {@link ngAria} module to be installed. 36702 * 36703 */ 36704function $AriaProvider() { 36705 var config = { 36706 ariaHidden: true, 36707 ariaChecked: true, 36708 ariaReadonly: true, 36709 ariaDisabled: true, 36710 ariaRequired: true, 36711 ariaInvalid: true, 36712 ariaValue: true, 36713 tabindex: true, 36714 bindKeydown: true, 36715 bindRoleForClick: true 36716 }; 36717 36718 /** 36719 * @ngdoc method 36720 * @name $ariaProvider#config 36721 * 36722 * @param {object} config object to enable/disable specific ARIA attributes 36723 * 36724 * - **ariaHidden** â `{boolean}` â Enables/disables aria-hidden tags 36725 * - **ariaChecked** â `{boolean}` â Enables/disables aria-checked tags 36726 * - **ariaReadonly** â `{boolean}` â Enables/disables aria-readonly tags 36727 * - **ariaDisabled** â `{boolean}` â Enables/disables aria-disabled tags 36728 * - **ariaRequired** â `{boolean}` â Enables/disables aria-required tags 36729 * - **ariaInvalid** â `{boolean}` â Enables/disables aria-invalid tags 36730 * - **ariaValue** â `{boolean}` â Enables/disables aria-valuemin, aria-valuemax and 36731 * aria-valuenow tags 36732 * - **tabindex** â `{boolean}` â Enables/disables tabindex tags 36733 * - **bindKeydown** â `{boolean}` â Enables/disables keyboard event binding on non-interactive 36734 * elements (such as `div` or `li`) using ng-click, making them more accessible to users of 36735 * assistive technologies 36736 * - **bindRoleForClick** â `{boolean}` â Adds role=button to non-interactive elements (such as 36737 * `div` or `li`) using ng-click, making them more accessible to users of assistive 36738 * technologies 36739 * 36740 * @description 36741 * Enables/disables various ARIA attributes 36742 */ 36743 this.config = function(newConfig) { 36744 config = angular.extend(config, newConfig); 36745 }; 36746 36747 function watchExpr(attrName, ariaAttr, nativeAriaNodeNames, negate) { 36748 return function(scope, elem, attr) { 36749 if (attr.hasOwnProperty(ARIA_DISABLE_ATTR)) return; 36750 36751 var ariaCamelName = attr.$normalize(ariaAttr); 36752 if (config[ariaCamelName] && !isNodeOneOf(elem, nativeAriaNodeNames) && !attr[ariaCamelName]) { 36753 scope.$watch(attr[attrName], function(boolVal) { 36754 // ensure boolean value 36755 boolVal = negate ? !boolVal : !!boolVal; 36756 elem.attr(ariaAttr, boolVal); 36757 }); 36758 } 36759 }; 36760 } 36761 /** 36762 * @ngdoc service 36763 * @name $aria 36764 * 36765 * @description 36766 * 36767 * The $aria service contains helper methods for applying common 36768 * [ARIA](http://www.w3.org/TR/wai-aria/) attributes to HTML directives. 36769 * 36770 * ngAria injects common accessibility attributes that tell assistive technologies when HTML 36771 * elements are enabled, selected, hidden, and more. To see how this is performed with ngAria, 36772 * let's review a code snippet from ngAria itself: 36773 * 36774 *```js 36775 * ngAriaModule.directive('ngDisabled', ['$aria', function($aria) { 36776 * return $aria.$$watchExpr('ngDisabled', 'aria-disabled', nativeAriaNodeNames, false); 36777 * }]) 36778 *``` 36779 * Shown above, the ngAria module creates a directive with the same signature as the 36780 * traditional `ng-disabled` directive. But this ngAria version is dedicated to 36781 * solely managing accessibility attributes on custom elements. The internal `$aria` service is 36782 * used to watch the boolean attribute `ngDisabled`. If it has not been explicitly set by the 36783 * developer, `aria-disabled` is injected as an attribute with its value synchronized to the 36784 * value in `ngDisabled`. 36785 * 36786 * Because ngAria hooks into the `ng-disabled` directive, developers do not have to do 36787 * anything to enable this feature. The `aria-disabled` attribute is automatically managed 36788 * simply as a silent side-effect of using `ng-disabled` with the ngAria module. 36789 * 36790 * The full list of directives that interface with ngAria: 36791 * * **ngModel** 36792 * * **ngChecked** 36793 * * **ngReadonly** 36794 * * **ngRequired** 36795 * * **ngDisabled** 36796 * * **ngValue** 36797 * * **ngShow** 36798 * * **ngHide** 36799 * * **ngClick** 36800 * * **ngDblclick** 36801 * * **ngMessages** 36802 * 36803 * Read the {@link guide/accessibility ngAria Developer Guide} for a thorough explanation of each 36804 * directive. 36805 * 36806 * 36807 * ## Dependencies 36808 * Requires the {@link ngAria} module to be installed. 36809 */ 36810 this.$get = function() { 36811 return { 36812 config: function(key) { 36813 return config[key]; 36814 }, 36815 $$watchExpr: watchExpr 36816 }; 36817 }; 36818} 36819 36820 36821ngAriaModule.directive('ngShow', ['$aria', function($aria) { 36822 return $aria.$$watchExpr('ngShow', 'aria-hidden', [], true); 36823}]) 36824.directive('ngHide', ['$aria', function($aria) { 36825 return $aria.$$watchExpr('ngHide', 'aria-hidden', [], false); 36826}]) 36827.directive('ngValue', ['$aria', function($aria) { 36828 return $aria.$$watchExpr('ngValue', 'aria-checked', nativeAriaNodeNames, false); 36829}]) 36830.directive('ngChecked', ['$aria', function($aria) { 36831 return $aria.$$watchExpr('ngChecked', 'aria-checked', nativeAriaNodeNames, false); 36832}]) 36833.directive('ngReadonly', ['$aria', function($aria) { 36834 return $aria.$$watchExpr('ngReadonly', 'aria-readonly', nativeAriaNodeNames, false); 36835}]) 36836.directive('ngRequired', ['$aria', function($aria) { 36837 return $aria.$$watchExpr('ngRequired', 'aria-required', nativeAriaNodeNames, false); 36838}]) 36839.directive('ngModel', ['$aria', function($aria) { 36840 36841 function shouldAttachAttr(attr, normalizedAttr, elem, allowNonAriaNodes) { 36842 return $aria.config(normalizedAttr) && 36843 !elem.attr(attr) && 36844 (allowNonAriaNodes || !isNodeOneOf(elem, nativeAriaNodeNames)) && 36845 (elem.attr('type') !== 'hidden' || elem[0].nodeName !== 'INPUT'); 36846 } 36847 36848 function shouldAttachRole(role, elem) { 36849 // if element does not have role attribute 36850 // AND element type is equal to role (if custom element has a type equaling shape) <-- remove? 36851 // AND element is not in nativeAriaNodeNames 36852 return !elem.attr('role') && (elem.attr('type') === role) && !isNodeOneOf(elem, nativeAriaNodeNames); 36853 } 36854 36855 function getShape(attr, elem) { 36856 var type = attr.type, 36857 role = attr.role; 36858 36859 return ((type || role) === 'checkbox' || role === 'menuitemcheckbox') ? 'checkbox' : 36860 ((type || role) === 'radio' || role === 'menuitemradio') ? 'radio' : 36861 (type === 'range' || role === 'progressbar' || role === 'slider') ? 'range' : ''; 36862 } 36863 36864 return { 36865 restrict: 'A', 36866 require: 'ngModel', 36867 priority: 200, //Make sure watches are fired after any other directives that affect the ngModel value 36868 compile: function(elem, attr) { 36869 if (attr.hasOwnProperty(ARIA_DISABLE_ATTR)) return; 36870 36871 var shape = getShape(attr, elem); 36872 36873 return { 36874 post: function(scope, elem, attr, ngModel) { 36875 var needsTabIndex = shouldAttachAttr('tabindex', 'tabindex', elem, false); 36876 36877 function ngAriaWatchModelValue() { 36878 return ngModel.$modelValue; 36879 } 36880 36881 function getRadioReaction(newVal) { 36882 // Strict comparison would cause a BC 36883 // eslint-disable-next-line eqeqeq 36884 var boolVal = (attr.value == ngModel.$viewValue); 36885 elem.attr('aria-checked', boolVal); 36886 } 36887 36888 function getCheckboxReaction() { 36889 elem.attr('aria-checked', !ngModel.$isEmpty(ngModel.$viewValue)); 36890 } 36891 36892 switch (shape) { 36893 case 'radio': 36894 case 'checkbox': 36895 if (shouldAttachRole(shape, elem)) { 36896 elem.attr('role', shape); 36897 } 36898 if (shouldAttachAttr('aria-checked', 'ariaChecked', elem, false)) { 36899 scope.$watch(ngAriaWatchModelValue, shape === 'radio' ? 36900 getRadioReaction : getCheckboxReaction); 36901 } 36902 if (needsTabIndex) { 36903 elem.attr('tabindex', 0); 36904 } 36905 break; 36906 case 'range': 36907 if (shouldAttachRole(shape, elem)) { 36908 elem.attr('role', 'slider'); 36909 } 36910 if ($aria.config('ariaValue')) { 36911 var needsAriaValuemin = !elem.attr('aria-valuemin') && 36912 (attr.hasOwnProperty('min') || attr.hasOwnProperty('ngMin')); 36913 var needsAriaValuemax = !elem.attr('aria-valuemax') && 36914 (attr.hasOwnProperty('max') || attr.hasOwnProperty('ngMax')); 36915 var needsAriaValuenow = !elem.attr('aria-valuenow'); 36916 36917 if (needsAriaValuemin) { 36918 attr.$observe('min', function ngAriaValueMinReaction(newVal) { 36919 elem.attr('aria-valuemin', newVal); 36920 }); 36921 } 36922 if (needsAriaValuemax) { 36923 attr.$observe('max', function ngAriaValueMinReaction(newVal) { 36924 elem.attr('aria-valuemax', newVal); 36925 }); 36926 } 36927 if (needsAriaValuenow) { 36928 scope.$watch(ngAriaWatchModelValue, function ngAriaValueNowReaction(newVal) { 36929 elem.attr('aria-valuenow', newVal); 36930 }); 36931 } 36932 } 36933 if (needsTabIndex) { 36934 elem.attr('tabindex', 0); 36935 } 36936 break; 36937 } 36938 36939 if (!attr.hasOwnProperty('ngRequired') && ngModel.$validators.required 36940 && shouldAttachAttr('aria-required', 'ariaRequired', elem, false)) { 36941 // ngModel.$error.required is undefined on custom controls 36942 attr.$observe('required', function() { 36943 elem.attr('aria-required', !!attr['required']); 36944 }); 36945 } 36946 36947 if (shouldAttachAttr('aria-invalid', 'ariaInvalid', elem, true)) { 36948 scope.$watch(function ngAriaInvalidWatch() { 36949 return ngModel.$invalid; 36950 }, function ngAriaInvalidReaction(newVal) { 36951 elem.attr('aria-invalid', !!newVal); 36952 }); 36953 } 36954 } 36955 }; 36956 } 36957 }; 36958}]) 36959.directive('ngDisabled', ['$aria', function($aria) { 36960 return $aria.$$watchExpr('ngDisabled', 'aria-disabled', nativeAriaNodeNames, false); 36961}]) 36962.directive('ngMessages', function() { 36963 return { 36964 restrict: 'A', 36965 require: '?ngMessages', 36966 link: function(scope, elem, attr, ngMessages) { 36967 if (attr.hasOwnProperty(ARIA_DISABLE_ATTR)) return; 36968 36969 if (!elem.attr('aria-live')) { 36970 elem.attr('aria-live', 'assertive'); 36971 } 36972 } 36973 }; 36974}) 36975.directive('ngClick',['$aria', '$parse', function($aria, $parse) { 36976 return { 36977 restrict: 'A', 36978 compile: function(elem, attr) { 36979 if (attr.hasOwnProperty(ARIA_DISABLE_ATTR)) return; 36980 36981 var fn = $parse(attr.ngClick); 36982 return function(scope, elem, attr) { 36983 36984 if (!isNodeOneOf(elem, nativeAriaNodeNames)) { 36985 36986 if ($aria.config('bindRoleForClick') && !elem.attr('role')) { 36987 elem.attr('role', 'button'); 36988 } 36989 36990 if ($aria.config('tabindex') && !elem.attr('tabindex')) { 36991 elem.attr('tabindex', 0); 36992 } 36993 36994 if ($aria.config('bindKeydown') && !attr.ngKeydown && !attr.ngKeypress && !attr.ngKeyup) { 36995 elem.on('keydown', function(event) { 36996 var keyCode = event.which || event.keyCode; 36997 36998 if (keyCode === 13 || keyCode === 32) { 36999 // If the event is triggered on a non-interactive element ... 37000 if (nativeAriaNodeNames.indexOf(event.target.nodeName) === -1 && !event.target.isContentEditable) { 37001 // ... prevent the default browser behavior (e.g. scrolling when pressing spacebar) 37002 // See https://github.com/angular/angular.js/issues/16664 37003 event.preventDefault(); 37004 } 37005 scope.$apply(callback); 37006 } 37007 37008 function callback() { 37009 fn(scope, { $event: event }); 37010 } 37011 }); 37012 } 37013 } 37014 }; 37015 } 37016 }; 37017}]) 37018.directive('ngDblclick', ['$aria', function($aria) { 37019 return function(scope, elem, attr) { 37020 if (attr.hasOwnProperty(ARIA_DISABLE_ATTR)) return; 37021 37022 if ($aria.config('tabindex') && !elem.attr('tabindex') && !isNodeOneOf(elem, nativeAriaNodeNames)) { 37023 elem.attr('tabindex', 0); 37024 } 37025 }; 37026}]); 37027 37028 37029})(window, window.angular);
37030
vendor: 31,501 bytes, lines 37031-37859
37031/** 37032 * @license AngularJS v1.8.3 37033 * (c) 2010-2020 Google LLC. http://angularjs.org 37034 * License: MIT 37035 */ 37036(function(window, angular) {'use strict'; 37037 37038var forEach; 37039var isArray; 37040var isString; 37041var jqLite; 37042 37043/** 37044 * @ngdoc module 37045 * @name ngMessages 37046 * @description 37047 * 37048 * The `ngMessages` module provides enhanced support for displaying messages within templates 37049 * (typically within forms or when rendering message objects that return key/value data). 37050 * Instead of relying on JavaScript code and/or complex ng-if statements within your form template to 37051 * show and hide error messages specific to the state of an input field, the `ngMessages` and 37052 * `ngMessage` directives are designed to handle the complexity, inheritance and priority 37053 * sequencing based on the order of how the messages are defined in the template. 37054 * 37055 * Currently, the ngMessages module only contains the code for the `ngMessages`, `ngMessagesInclude` 37056 * `ngMessage`, `ngMessageExp` and `ngMessageDefault` directives. 37057 * 37058 * ## Usage 37059 * The `ngMessages` directive allows keys in a key/value collection to be associated with a child element 37060 * (or 'message') that will show or hide based on the truthiness of that key's value in the collection. A common use 37061 * case for `ngMessages` is to display error messages for inputs using the `$error` object exposed by the 37062 * {@link ngModel ngModel} directive. 37063 * 37064 * The child elements of the `ngMessages` directive are matched to the collection keys by a `ngMessage` or 37065 * `ngMessageExp` directive. The value of these attributes must match a key in the collection that is provided by 37066 * the `ngMessages` directive. 37067 * 37068 * Consider the following example, which illustrates a typical use case of `ngMessages`. Within the form `myForm` we 37069 * have a text input named `myField` which is bound to the scope variable `field` using the {@link ngModel ngModel} 37070 * directive. 37071 * 37072 * The `myField` field is a required input of type `email` with a maximum length of 15 characters. 37073 * 37074 * ```html 37075 * <form name="myForm"> 37076 * <label> 37077 * Enter text: 37078 * <input type="email" ng-model="field" name="myField" required maxlength="15" /> 37079 * </label> 37080 * <div ng-messages="myForm.myField.$error" role="alert"> 37081 * <div ng-message="required">Please enter a value for this field.</div> 37082 * <div ng-message="email">This field must be a valid email address.</div> 37083 * <div ng-message="maxlength">This field can be at most 15 characters long.</div> 37084 * </div> 37085 * </form> 37086 * ``` 37087 * 37088 * In order to show error messages corresponding to `myField` we first create an element with an `ngMessages` attribute 37089 * set to the `$error` object owned by the `myField` input in our `myForm` form. 37090 * 37091 * Within this element we then create separate elements for each of the possible errors that `myField` could have. 37092 * The `ngMessage` attribute is used to declare which element(s) will appear for which error - for example, 37093 * setting `ng-message="required"` specifies that this particular element should be displayed when there 37094 * is no value present for the required field `myField` (because the key `required` will be `true` in the object 37095 * `myForm.myField.$error`). 37096 * 37097 * ### Message order 37098 * 37099 * By default, `ngMessages` will only display one message for a particular key/value collection at any time. If more 37100 * than one message (or error) key is currently true, then which message is shown is determined by the order of messages 37101 * in the HTML template code (messages declared first are prioritised). This mechanism means the developer does not have 37102 * to prioritize messages using custom JavaScript code. 37103 * 37104 * Given the following error object for our example (which informs us that the field `myField` currently has both the 37105 * `required` and `email` errors): 37106 * 37107 * ```javascript 37108 * <!-- keep in mind that ngModel automatically sets these error flags --> 37109 * myField.$error = { required : true, email: true, maxlength: false }; 37110 * ``` 37111 * The `required` message will be displayed to the user since it appears before the `email` message in the DOM. 37112 * Once the user types a single character, the `required` message will disappear (since the field now has a value) 37113 * but the `email` message will be visible because it is still applicable. 37114 * 37115 * ### Displaying multiple messages at the same time 37116 * 37117 * While `ngMessages` will by default only display one error element at a time, the `ng-messages-multiple` attribute can 37118 * be applied to the `ngMessages` container element to cause it to display all applicable error messages at once: 37119 * 37120 * ```html 37121 * <!-- attribute-style usage --> 37122 * <div ng-messages="myForm.myField.$error" ng-messages-multiple>...</div> 37123 * 37124 * <!-- element-style usage --> 37125 * <ng-messages for="myForm.myField.$error" multiple>...</ng-messages> 37126 * ``` 37127 * 37128 * ## Reusing and Overriding Messages 37129 * In addition to prioritization, ngMessages also allows for including messages from a remote or an inline 37130 * template. This allows for generic collection of messages to be reused across multiple parts of an 37131 * application. 37132 * 37133 * ```html 37134 * <script type="text/ng-template" id="error-messages"> 37135 * <div ng-message="required">This field is required</div> 37136 * <div ng-message="minlength">This field is too short</div> 37137 * </script> 37138 * 37139 * <div ng-messages="myForm.myField.$error" role="alert"> 37140 * <div ng-messages-include="error-messages"></div> 37141 * </div> 37142 * ``` 37143 * 37144 * However, including generic messages may not be useful enough to match all input fields, therefore, 37145 * `ngMessages` provides the ability to override messages defined in the remote template by redefining 37146 * them within the directive container. 37147 * 37148 * ```html 37149 * <!-- a generic template of error messages known as "my-custom-messages" --> 37150 * <script type="text/ng-template" id="my-custom-messages"> 37151 * <div ng-message="required">This field is required</div> 37152 * <div ng-message="minlength">This field is too short</div> 37153 * </script> 37154 * 37155 * <form name="myForm"> 37156 * <label> 37157 * Email address 37158 * <input type="email" 37159 * id="email" 37160 * name="myEmail" 37161 * ng-model="email" 37162 * minlength="5" 37163 * required /> 37164 * </label> 37165 * <!-- any ng-message elements that appear BEFORE the ng-messages-include will 37166 * override the messages present in the ng-messages-include template --> 37167 * <div ng-messages="myForm.myEmail.$error" role="alert"> 37168 * <!-- this required message has overridden the template message --> 37169 * <div ng-message="required">You did not enter your email address</div> 37170 * 37171 * <!-- this is a brand new message and will appear last in the prioritization --> 37172 * <div ng-message="email">Your email address is invalid</div> 37173 * 37174 * <!-- and here are the generic error messages --> 37175 * <div ng-messages-include="my-custom-messages"></div> 37176 * </div> 37177 * </form> 37178 * ``` 37179 * 37180 * In the example HTML code above the message that is set on required will override the corresponding 37181 * required message defined within the remote template. Therefore, with particular input fields (such 37182 * email addresses, date fields, autocomplete inputs, etc...), specialized error messages can be applied 37183 * while more generic messages can be used to handle other, more general input errors. 37184 * 37185 * ## Dynamic Messaging 37186 * ngMessages also supports using expressions to dynamically change key values. Using arrays and 37187 * repeaters to list messages is also supported. This means that the code below will be able to 37188 * fully adapt itself and display the appropriate message when any of the expression data changes: 37189 * 37190 * ```html 37191 * <form name="myForm"> 37192 * <label> 37193 * Email address 37194 * <input type="email" 37195 * name="myEmail" 37196 * ng-model="email" 37197 * minlength="5" 37198 * required /> 37199 * </label> 37200 * <div ng-messages="myForm.myEmail.$error" role="alert"> 37201 * <div ng-message="required">You did not enter your email address</div> 37202 * <div ng-repeat="errorMessage in errorMessages"> 37203 * <!-- use ng-message-exp for a message whose key is given by an expression --> 37204 * <div ng-message-exp="errorMessage.type">{{ errorMessage.text }}</div> 37205 * </div> 37206 * </div> 37207 * </form> 37208 * ``` 37209 * 37210 * The `errorMessage.type` expression can be a string value or it can be an array so 37211 * that multiple errors can be associated with a single error message: 37212 * 37213 * ```html 37214 * <label> 37215 * Email address 37216 * <input type="email" 37217 * ng-model="data.email" 37218 * name="myEmail" 37219 * ng-minlength="5" 37220 * ng-maxlength="100" 37221 * required /> 37222 * </label> 37223 * <div ng-messages="myForm.myEmail.$error" role="alert"> 37224 * <div ng-message-exp="'required'">You did not enter your email address</div> 37225 * <div ng-message-exp="['minlength', 'maxlength']"> 37226 * Your email must be between 5 and 100 characters long 37227 * </div> 37228 * </div> 37229 * ``` 37230 * 37231 * Feel free to use other structural directives such as ng-if and ng-switch to further control 37232 * what messages are active and when. Be careful, if you place ng-message on the same element 37233 * as these structural directives, AngularJS may not be able to determine if a message is active 37234 * or not. Therefore it is best to place the ng-message on a child element of the structural 37235 * directive. 37236 * 37237 * ```html 37238 * <div ng-messages="myForm.myEmail.$error" role="alert"> 37239 * <div ng-if="showRequiredError"> 37240 * <div ng-message="required">Please enter something</div> 37241 * </div> 37242 * </div> 37243 * ``` 37244 * 37245 * ## Animations 37246 * If the `ngAnimate` module is active within the application then the `ngMessages`, `ngMessage` and 37247 * `ngMessageExp` directives will trigger animations whenever any messages are added and removed from 37248 * the DOM by the `ngMessages` directive. 37249 * 37250 * Whenever the `ngMessages` directive contains one or more visible messages then the `.ng-active` CSS 37251 * class will be added to the element. The `.ng-inactive` CSS class will be applied when there are no 37252 * messages present. Therefore, CSS transitions and keyframes as well as JavaScript animations can 37253 * hook into the animations whenever these classes are added/removed. 37254 * 37255 * Let's say that our HTML code for our messages container looks like so: 37256 * 37257 * ```html 37258 * <div ng-messages="myMessages" class="my-messages" role="alert"> 37259 * <div ng-message="alert" class="some-message">...</div> 37260 * <div ng-message="fail" class="some-message">...</div> 37261 * </div> 37262 * ``` 37263 * 37264 * Then the CSS animation code for the message container looks like so: 37265 * 37266 * ```css 37267 * .my-messages { 37268 * transition:1s linear all; 37269 * } 37270 * .my-messages.ng-active { 37271 * // messages are visible 37272 * } 37273 * .my-messages.ng-inactive { 37274 * // messages are hidden 37275 * } 37276 * ``` 37277 * 37278 * Whenever an inner message is attached (becomes visible) or removed (becomes hidden) then the enter 37279 * and leave animation is triggered for each particular element bound to the `ngMessage` directive. 37280 * 37281 * Therefore, the CSS code for the inner messages looks like so: 37282 * 37283 * ```css 37284 * .some-message { 37285 * transition:1s linear all; 37286 * } 37287 * 37288 * .some-message.ng-enter {} 37289 * .some-message.ng-enter.ng-enter-active {} 37290 * 37291 * .some-message.ng-leave {} 37292 * .some-message.ng-leave.ng-leave-active {} 37293 * ``` 37294 * 37295 * {@link ngAnimate See the ngAnimate docs} to learn how to use JavaScript animations or to learn 37296 * more about ngAnimate. 37297 * 37298 * ## Displaying a default message 37299 * If the ngMessages renders no inner ngMessage directive (i.e. when none of the truthy 37300 * keys are matched by a defined message), then it will render a default message 37301 * using the {@link ngMessageDefault} directive. 37302 * Note that matched messages will always take precedence over unmatched messages. That means 37303 * the default message will not be displayed when another message is matched. This is also 37304 * true for `ng-messages-multiple`. 37305 * 37306 * ```html 37307 * <div ng-messages="myForm.myField.$error" role="alert"> 37308 * <div ng-message="required">This field is required</div> 37309 * <div ng-message="minlength">This field is too short</div> 37310 * <div ng-message-default>This field has an input error</div> 37311 * </div> 37312 * ``` 37313 * 37314 37315 */ 37316angular.module('ngMessages', [], function initAngularHelpers() { 37317 // Access helpers from AngularJS core. 37318 // Do it inside a `config` block to ensure `window.angular` is available. 37319 forEach = angular.forEach; 37320 isArray = angular.isArray; 37321 isString = angular.isString; 37322 jqLite = angular.element; 37323}) 37324 .info({ angularVersion: '1.8.3' }) 37325 37326 /** 37327 * @ngdoc directive 37328 * @module ngMessages 37329 * @name ngMessages 37330 * @restrict AE 37331 * 37332 * @description 37333 * `ngMessages` is a directive that is designed to show and hide messages based on the state 37334 * of a key/value object that it listens on. The directive itself complements error message 37335 * reporting with the `ngModel` $error object (which stores a key/value state of validation errors). 37336 * 37337 * `ngMessages` manages the state of internal messages within its container element. The internal 37338 * messages use the `ngMessage` directive and will be inserted/removed from the page depending 37339 * on if they're present within the key/value object. By default, only one message will be displayed 37340 * at a time and this depends on the prioritization of the messages within the template. (This can 37341 * be changed by using the `ng-messages-multiple` or `multiple` attribute on the directive container.) 37342 * 37343 * A remote template can also be used (With {@link ngMessagesInclude}) to promote message 37344 * reusability and messages can also be overridden. 37345 * 37346 * A default message can also be displayed when no `ngMessage` directive is inserted, using the 37347 * {@link ngMessageDefault} directive. 37348 * 37349 * {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`. 37350 * 37351 * @usage 37352 * ```html 37353 * <!-- using attribute directives --> 37354 * <ANY ng-messages="expression" role="alert"> 37355 * <ANY ng-message="stringValue">...</ANY> 37356 * <ANY ng-message="stringValue1, stringValue2, ...">...</ANY> 37357 * <ANY ng-message-exp="expressionValue">...</ANY> 37358 * <ANY ng-message-default>...</ANY> 37359 * </ANY> 37360 * 37361 * <!-- or by using element directives --> 37362 * <ng-messages for="expression" role="alert"> 37363 * <ng-message when="stringValue">...</ng-message> 37364 * <ng-message when="stringValue1, stringValue2, ...">...</ng-message> 37365 * <ng-message when-exp="expressionValue">...</ng-message> 37366 * <ng-message-default>...</ng-message-default> 37367 * </ng-messages> 37368 * ``` 37369 * 37370 * @param {string} ngMessages an AngularJS expression evaluating to a key/value object 37371 * (this is typically the $error object on an ngModel instance). 37372 * @param {string=} ngMessagesMultiple|multiple when set, all messages will be displayed with true 37373 * 37374 * @example 37375 * <example name="ngMessages-directive" module="ngMessagesExample" 37376 * deps="angular-messages.js" 37377 * animations="true" fixBase="true"> 37378 * <file name="index.html"> 37379 * <form name="myForm"> 37380 * <label> 37381 * Enter your name: 37382 * <input type="text" 37383 * name="myName" 37384 * ng-model="name" 37385 * ng-minlength="5" 37386 * ng-maxlength="20" 37387 * required /> 37388 * </label> 37389 * <pre>myForm.myName.$error = {{ myForm.myName.$error | json }}</pre> 37390 * 37391 * <div ng-messages="myForm.myName.$error" style="color:maroon" role="alert"> 37392 * <div ng-message="required">You did not enter a field</div> 37393 * <div ng-message="minlength">Your field is too short</div> 37394 * <div ng-message="maxlength">Your field is too long</div> 37395 * <div ng-message-default>This field has an input error</div> 37396 * </div> 37397 * </form> 37398 * </file> 37399 * <file name="script.js"> 37400 * angular.module('ngMessagesExample', ['ngMessages']); 37401 * </file> 37402 * </example> 37403 */ 37404 .directive('ngMessages', ['$animate', function($animate) { 37405 var ACTIVE_CLASS = 'ng-active'; 37406 var INACTIVE_CLASS = 'ng-inactive'; 37407 37408 return { 37409 require: 'ngMessages', 37410 restrict: 'AE', 37411 controller: ['$element', '$scope', '$attrs', function NgMessagesCtrl($element, $scope, $attrs) { 37412 var ctrl = this; 37413 var latestKey = 0; 37414 var nextAttachId = 0; 37415 37416 this.getAttachId = function getAttachId() { return nextAttachId++; }; 37417 37418 var messages = this.messages = {}; 37419 var renderLater, cachedCollection; 37420 37421 this.render = function(collection) { 37422 collection = collection || {}; 37423 37424 renderLater = false; 37425 cachedCollection = collection; 37426 37427 // this is true if the attribute is empty or if the attribute value is truthy 37428 var multiple = isAttrTruthy($scope, $attrs.ngMessagesMultiple) || 37429 isAttrTruthy($scope, $attrs.multiple); 37430 37431 var unmatchedMessages = []; 37432 var matchedKeys = {}; 37433 var truthyKeys = 0; 37434 var messageItem = ctrl.head; 37435 var messageFound = false; 37436 var totalMessages = 0; 37437 37438 // we use != instead of !== to allow for both undefined and null values 37439 while (messageItem != null) { 37440 totalMessages++; 37441 var messageCtrl = messageItem.message; 37442 37443 var messageUsed = false; 37444 if (!messageFound) { 37445 forEach(collection, function(value, key) { 37446 if (truthy(value) && !messageUsed) { 37447 truthyKeys++; 37448 37449 if (messageCtrl.test(key)) { 37450 // this is to prevent the same error name from showing up twice 37451 if (matchedKeys[key]) return; 37452 matchedKeys[key] = true; 37453 37454 messageUsed = true; 37455 messageCtrl.attach(); 37456 } 37457 } 37458 }); 37459 } 37460 37461 if (messageUsed) { 37462 // unless we want to display multiple messages then we should 37463 // set a flag here to avoid displaying the next message in the list 37464 messageFound = !multiple; 37465 } else { 37466 unmatchedMessages.push(messageCtrl); 37467 } 37468 37469 messageItem = messageItem.next; 37470 } 37471 37472 forEach(unmatchedMessages, function(messageCtrl) { 37473 messageCtrl.detach(); 37474 }); 37475 37476 var messageMatched = unmatchedMessages.length !== totalMessages; 37477 var attachDefault = ctrl.default && !messageMatched && truthyKeys > 0; 37478 37479 if (attachDefault) { 37480 ctrl.default.attach(); 37481 } else if (ctrl.default) { 37482 ctrl.default.detach(); 37483 } 37484 37485 if (messageMatched || attachDefault) { 37486 $animate.setClass($element, ACTIVE_CLASS, INACTIVE_CLASS); 37487 } else { 37488 $animate.setClass($element, INACTIVE_CLASS, ACTIVE_CLASS); 37489 } 37490 }; 37491 37492 $scope.$watchCollection($attrs.ngMessages || $attrs['for'], ctrl.render); 37493 37494 this.reRender = function() { 37495 if (!renderLater) { 37496 renderLater = true; 37497 $scope.$evalAsync(function() { 37498 if (renderLater && cachedCollection) { 37499 ctrl.render(cachedCollection); 37500 } 37501 }); 37502 } 37503 }; 37504 37505 this.register = function(comment, messageCtrl, isDefault) { 37506 if (isDefault) { 37507 ctrl.default = messageCtrl; 37508 } else { 37509 var nextKey = latestKey.toString(); 37510 messages[nextKey] = { 37511 message: messageCtrl 37512 }; 37513 insertMessageNode($element[0], comment, nextKey); 37514 comment.$$ngMessageNode = nextKey; 37515 latestKey++; 37516 } 37517 37518 ctrl.reRender(); 37519 }; 37520 37521 this.deregister = function(comment, isDefault) { 37522 if (isDefault) { 37523 delete ctrl.default; 37524 } else { 37525 var key = comment.$$ngMessageNode; 37526 delete comment.$$ngMessageNode; 37527 removeMessageNode($element[0], comment, key); 37528 delete messages[key]; 37529 } 37530 ctrl.reRender(); 37531 }; 37532 37533 function findPreviousMessage(parent, comment) { 37534 var prevNode = comment; 37535 var parentLookup = []; 37536 37537 while (prevNode && prevNode !== parent) { 37538 var prevKey = prevNode.$$ngMessageNode; 37539 if (prevKey && prevKey.length) { 37540 return messages[prevKey]; 37541 } 37542 37543 // dive deeper into the DOM and examine its children for any ngMessage 37544 // comments that may be in an element that appears deeper in the list 37545 if (prevNode.childNodes.length && parentLookup.indexOf(prevNode) === -1) { 37546 parentLookup.push(prevNode); 37547 prevNode = prevNode.childNodes[prevNode.childNodes.length - 1]; 37548 } else if (prevNode.previousSibling) { 37549 prevNode = prevNode.previousSibling; 37550 } else { 37551 prevNode = prevNode.parentNode; 37552 parentLookup.push(prevNode); 37553 } 37554 } 37555 } 37556 37557 function insertMessageNode(parent, comment, key) { 37558 var messageNode = messages[key]; 37559 if (!ctrl.head) { 37560 ctrl.head = messageNode; 37561 } else { 37562 var match = findPreviousMessage(parent, comment); 37563 if (match) { 37564 messageNode.next = match.next; 37565 match.next = messageNode; 37566 } else { 37567 messageNode.next = ctrl.head; 37568 ctrl.head = messageNode; 37569 } 37570 } 37571 } 37572 37573 function removeMessageNode(parent, comment, key) { 37574 var messageNode = messages[key]; 37575 37576 // This message node may have already been removed by a call to deregister() 37577 if (!messageNode) return; 37578 37579 var match = findPreviousMessage(parent, comment); 37580 if (match) { 37581 match.next = messageNode.next; 37582 } else { 37583 ctrl.head = messageNode.next; 37584 } 37585 } 37586 }] 37587 }; 37588 37589 function isAttrTruthy(scope, attr) { 37590 return (isString(attr) && attr.length === 0) || //empty attribute 37591 truthy(scope.$eval(attr)); 37592 } 37593 37594 function truthy(val) { 37595 return isString(val) ? val.length : !!val; 37596 } 37597 }]) 37598 37599 /** 37600 * @ngdoc directive 37601 * @name ngMessagesInclude 37602 * @restrict AE 37603 * @scope 37604 * 37605 * @description 37606 * `ngMessagesInclude` is a directive with the purpose to import existing ngMessage template 37607 * code from a remote template and place the downloaded template code into the exact spot 37608 * that the ngMessagesInclude directive is placed within the ngMessages container. This allows 37609 * for a series of pre-defined messages to be reused and also allows for the developer to 37610 * determine what messages are overridden due to the placement of the ngMessagesInclude directive. 37611 * 37612 * @usage 37613 * ```html 37614 * <!-- using attribute directives --> 37615 * <ANY ng-messages="expression" role="alert"> 37616 * <ANY ng-messages-include="remoteTplString">...</ANY> 37617 * </ANY> 37618 * 37619 * <!-- or by using element directives --> 37620 * <ng-messages for="expression" role="alert"> 37621 * <ng-messages-include src="expressionValue1">...</ng-messages-include> 37622 * </ng-messages> 37623 * ``` 37624 * 37625 * {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`. 37626 * 37627 * @param {string} ngMessagesInclude|src a string value corresponding to the remote template. 37628 */ 37629 .directive('ngMessagesInclude', 37630 ['$templateRequest', '$document', '$compile', function($templateRequest, $document, $compile) { 37631 37632 return { 37633 restrict: 'AE', 37634 require: '^^ngMessages', // we only require this for validation sake 37635 link: function($scope, element, attrs) { 37636 var src = attrs.ngMessagesInclude || attrs.src; 37637 $templateRequest(src).then(function(html) { 37638 if ($scope.$$destroyed) return; 37639 37640 if (isString(html) && !html.trim()) { 37641 // Empty template - nothing to compile 37642 replaceElementWithMarker(element, src); 37643 } else { 37644 // Non-empty template - compile and link 37645 $compile(html)($scope, function(contents) { 37646 element.after(contents); 37647 replaceElementWithMarker(element, src); 37648 }); 37649 } 37650 }); 37651 } 37652 }; 37653 37654 // Helpers 37655 function replaceElementWithMarker(element, src) { 37656 // A comment marker is placed for debugging purposes 37657 var comment = $compile.$$createComment ? 37658 $compile.$$createComment('ngMessagesInclude', src) : 37659 $document[0].createComment(' ngMessagesInclude: ' + src + ' '); 37660 var marker = jqLite(comment); 37661 element.after(marker); 37662 37663 // Don't pollute the DOM anymore by keeping an empty directive element 37664 element.remove(); 37665 } 37666 }]) 37667 37668 /** 37669 * @ngdoc directive 37670 * @name ngMessage 37671 * @restrict AE 37672 * @scope 37673 * @priority 1 37674 * 37675 * @description 37676 * `ngMessage` is a directive with the purpose to show and hide a particular message. 37677 * For `ngMessage` to operate, a parent `ngMessages` directive on a parent DOM element 37678 * must be situated since it determines which messages are visible based on the state 37679 * of the provided key/value map that `ngMessages` listens on. 37680 * 37681 * More information about using `ngMessage` can be found in the 37682 * {@link module:ngMessages `ngMessages` module documentation}. 37683 * 37684 * @usage 37685 * ```html 37686 * <!-- using attribute directives --> 37687 * <ANY ng-messages="expression" role="alert"> 37688 * <ANY ng-message="stringValue">...</ANY> 37689 * <ANY ng-message="stringValue1, stringValue2, ...">...</ANY> 37690 * </ANY> 37691 * 37692 * <!-- or by using element directives --> 37693 * <ng-messages for="expression" role="alert"> 37694 * <ng-message when="stringValue">...</ng-message> 37695 * <ng-message when="stringValue1, stringValue2, ...">...</ng-message> 37696 * </ng-messages> 37697 * ``` 37698 * 37699 * @param {expression} ngMessage|when a string value corresponding to the message key. 37700 */ 37701 .directive('ngMessage', ngMessageDirectiveFactory()) 37702 37703 37704 /** 37705 * @ngdoc directive 37706 * @name ngMessageExp 37707 * @restrict AE 37708 * @priority 1 37709 * @scope 37710 * 37711 * @description 37712 * `ngMessageExp` is the same as {@link directive:ngMessage `ngMessage`}, but instead of a static 37713 * value, it accepts an expression to be evaluated for the message key. 37714 * 37715 * @usage 37716 * ```html 37717 * <!-- using attribute directives --> 37718 * <ANY ng-messages="expression"> 37719 * <ANY ng-message-exp="expressionValue">...</ANY> 37720 * </ANY> 37721 * 37722 * <!-- or by using element directives --> 37723 * <ng-messages for="expression"> 37724 * <ng-message when-exp="expressionValue">...</ng-message> 37725 * </ng-messages> 37726 * ``` 37727 * 37728 * {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`. 37729 * 37730 * @param {expression} ngMessageExp|whenExp an expression value corresponding to the message key. 37731 */ 37732 .directive('ngMessageExp', ngMessageDirectiveFactory()) 37733 37734 /** 37735 * @ngdoc directive 37736 * @name ngMessageDefault 37737 * @restrict AE 37738 * @scope 37739 * 37740 * @description 37741 * `ngMessageDefault` is a directive with the purpose to show and hide a default message for 37742 * {@link directive:ngMessages}, when none of provided messages matches. 37743 * 37744 * More information about using `ngMessageDefault` can be found in the 37745 * {@link module:ngMessages `ngMessages` module documentation}. 37746 * 37747 * @usage 37748 * ```html 37749 * <!-- using attribute directives --> 37750 * <ANY ng-messages="expression" role="alert"> 37751 * <ANY ng-message="stringValue">...</ANY> 37752 * <ANY ng-message="stringValue1, stringValue2, ...">...</ANY> 37753 * <ANY ng-message-default>...</ANY> 37754 * </ANY> 37755 * 37756 * <!-- or by using element directives --> 37757 * <ng-messages for="expression" role="alert"> 37758 * <ng-message when="stringValue">...</ng-message> 37759 * <ng-message when="stringValue1, stringValue2, ...">...</ng-message> 37760 * <ng-message-default>...</ng-message-default> 37761 * </ng-messages> 37762 * 37763 */ 37764 .directive('ngMessageDefault', ngMessageDirectiveFactory(true)); 37765 37766function ngMessageDirectiveFactory(isDefault) { 37767 return ['$animate', function($animate) { 37768 return { 37769 restrict: 'AE', 37770 transclude: 'element', 37771 priority: 1, // must run before ngBind, otherwise the text is set on the comment 37772 terminal: true, 37773 require: '^^ngMessages', 37774 link: function(scope, element, attrs, ngMessagesCtrl, $transclude) { 37775 var commentNode, records, staticExp, dynamicExp; 37776 37777 if (!isDefault) { 37778 commentNode = element[0]; 37779 staticExp = attrs.ngMessage || attrs.when; 37780 dynamicExp = attrs.ngMessageExp || attrs.whenExp; 37781 37782 var assignRecords = function(items) { 37783 records = items 37784 ? (isArray(items) 37785 ? items 37786 : items.split(/[\s,]+/)) 37787 : null; 37788 ngMessagesCtrl.reRender(); 37789 }; 37790 37791 if (dynamicExp) { 37792 assignRecords(scope.$eval(dynamicExp)); 37793 scope.$watchCollection(dynamicExp, assignRecords); 37794 } else { 37795 assignRecords(staticExp); 37796 } 37797 } 37798 37799 var currentElement, messageCtrl; 37800 ngMessagesCtrl.register(commentNode, messageCtrl = { 37801 test: function(name) { 37802 return contains(records, name); 37803 }, 37804 attach: function() { 37805 if (!currentElement) { 37806 $transclude(function(elm, newScope) { 37807 $animate.enter(elm, null, element); 37808 currentElement = elm; 37809 37810 // Each time we attach this node to a message we get a new id that we can match 37811 // when we are destroying the node later. 37812 var $$attachId = currentElement.$$attachId = ngMessagesCtrl.getAttachId(); 37813 37814 // in the event that the element or a parent element is destroyed 37815 // by another structural directive then it's time 37816 // to deregister the message from the controller 37817 currentElement.on('$destroy', function() { 37818 // If the message element was removed via a call to `detach` then `currentElement` will be null 37819 // So this handler only handles cases where something else removed the message element. 37820 if (currentElement && currentElement.$$attachId === $$attachId) { 37821 ngMessagesCtrl.deregister(commentNode, isDefault); 37822 messageCtrl.detach(); 37823 } 37824 newScope.$destroy(); 37825 }); 37826 }); 37827 } 37828 }, 37829 detach: function() { 37830 if (currentElement) { 37831 var elm = currentElement; 37832 currentElement = null; 37833 $animate.leave(elm); 37834 } 37835 } 37836 }, isDefault); 37837 37838 // We need to ensure that this directive deregisters itself when it no longer exists 37839 // Normally this is done when the attached element is destroyed; but if this directive 37840 // gets removed before we attach the message to the DOM there is nothing to watch 37841 // in which case we must deregister when the containing scope is destroyed. 37842 scope.$on('$destroy', function() { 37843 ngMessagesCtrl.deregister(commentNode, isDefault); 37844 }); 37845 } 37846 }; 37847 }]; 37848 37849 function contains(collection, key) { 37850 if (collection) { 37851 return isArray(collection) 37852 ? collection.indexOf(key) >= 0 37853 : collection.hasOwnProperty(key); 37854 } 37855 } 37856} 37857 37858 37859})(window, window.angular);
37860
vendor: 33,197 bytes, lines 37861-38751
37861/** 37862 * @license AngularJS v1.8.3 37863 * (c) 2010-2020 Google LLC. http://angularjs.org 37864 * License: MIT 37865 */ 37866(function(window, angular) {'use strict'; 37867 37868/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 37869 * Any commits to this file should be reviewed with security in mind. * 37870 * Changes to this file can potentially create security vulnerabilities. * 37871 * An approval from 2 Core members with history of modifying * 37872 * this file is required. * 37873 * * 37874 * Does the change somehow allow for arbitrary javascript to be executed? * 37875 * Or allows for someone to change the prototype of built-in objects? * 37876 * Or gives undesired access to variables likes document or window? * 37877 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 37878 37879var $sanitizeMinErr = angular.$$minErr('$sanitize'); 37880var bind; 37881var extend; 37882var forEach; 37883var isArray; 37884var isDefined; 37885var lowercase; 37886var noop; 37887var nodeContains; 37888var htmlParser; 37889var htmlSanitizeWriter; 37890 37891/** 37892 * @ngdoc module 37893 * @name ngSanitize 37894 * @description 37895 * 37896 * The `ngSanitize` module provides functionality to sanitize HTML. 37897 * 37898 * See {@link ngSanitize.$sanitize `$sanitize`} for usage. 37899 */ 37900 37901/** 37902 * @ngdoc service 37903 * @name $sanitize 37904 * @kind function 37905 * 37906 * @description 37907 * Sanitizes an html string by stripping all potentially dangerous tokens. 37908 * 37909 * The input is sanitized by parsing the HTML into tokens. All safe tokens (from a trusted URI list) are 37910 * then serialized back to a properly escaped HTML string. This means that no unsafe input can make 37911 * it into the returned string. 37912 * 37913 * The trusted URIs for URL sanitization of attribute values is configured using the functions 37914 * `aHrefSanitizationTrustedUrlList` and `imgSrcSanitizationTrustedUrlList` of {@link $compileProvider}. 37915 * 37916 * The input may also contain SVG markup if this is enabled via {@link $sanitizeProvider}. 37917 * 37918 * @param {string} html HTML input. 37919 * @returns {string} Sanitized HTML. 37920 * 37921 * @example 37922 <example module="sanitizeExample" deps="angular-sanitize.js" name="sanitize-service"> 37923 <file name="index.html"> 37924 <script> 37925 angular.module('sanitizeExample', ['ngSanitize']) 37926 .controller('ExampleController', ['$scope', '$sce', function($scope, $sce) { 37927 $scope.snippet = 37928 '<p style="color:blue">an html\n' + 37929 '<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' + 37930 'snippet</p>'; 37931 $scope.deliberatelyTrustDangerousSnippet = function() { 37932 return $sce.trustAsHtml($scope.snippet); 37933 }; 37934 }]); 37935 </script> 37936 <div ng-controller="ExampleController"> 37937 Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea> 37938 <table> 37939 <tr> 37940 <td>Directive</td> 37941 <td>How</td> 37942 <td>Source</td> 37943 <td>Rendered</td> 37944 </tr> 37945 <tr id="bind-html-with-sanitize"> 37946 <td>ng-bind-html</td> 37947 <td>Automatically uses $sanitize</td> 37948 <td><pre><div ng-bind-html="snippet"><br/></div></pre></td> 37949 <td><div ng-bind-html="snippet"></div></td> 37950 </tr> 37951 <tr id="bind-html-with-trust"> 37952 <td>ng-bind-html</td> 37953 <td>Bypass $sanitize by explicitly trusting the dangerous value</td> 37954 <td> 37955 <pre><div ng-bind-html="deliberatelyTrustDangerousSnippet()"> 37956</div></pre> 37957 </td> 37958 <td><div ng-bind-html="deliberatelyTrustDangerousSnippet()"></div></td> 37959 </tr> 37960 <tr id="bind-default"> 37961 <td>ng-bind</td> 37962 <td>Automatically escapes</td> 37963 <td><pre><div ng-bind="snippet"><br/></div></pre></td> 37964 <td><div ng-bind="snippet"></div></td> 37965 </tr> 37966 </table> 37967 </div> 37968 </file> 37969 <file name="protractor.js" type="protractor"> 37970 it('should sanitize the html snippet by default', function() { 37971 expect(element(by.css('#bind-html-with-sanitize div')).getAttribute('innerHTML')). 37972 toBe('<p>an html\n<em>click here</em>\nsnippet</p>'); 37973 }); 37974 37975 it('should inline raw snippet if bound to a trusted value', function() { 37976 expect(element(by.css('#bind-html-with-trust div')).getAttribute('innerHTML')). 37977 toBe("<p style=\"color:blue\">an html\n" + 37978 "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + 37979 "snippet</p>"); 37980 }); 37981 37982 it('should escape snippet without any filter', function() { 37983 expect(element(by.css('#bind-default div')).getAttribute('innerHTML')). 37984 toBe("<p style=\"color:blue\">an html\n" + 37985 "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + 37986 "snippet</p>"); 37987 }); 37988 37989 it('should update', function() { 37990 element(by.model('snippet')).clear(); 37991 element(by.model('snippet')).sendKeys('new <b onclick="alert(1)">text</b>'); 37992 expect(element(by.css('#bind-html-with-sanitize div')).getAttribute('innerHTML')). 37993 toBe('new <b>text</b>'); 37994 expect(element(by.css('#bind-html-with-trust div')).getAttribute('innerHTML')).toBe( 37995 'new <b onclick="alert(1)">text</b>'); 37996 expect(element(by.css('#bind-default div')).getAttribute('innerHTML')).toBe( 37997 "new <b onclick=\"alert(1)\">text</b>"); 37998 }); 37999 </file> 38000 </example> 38001 */ 38002 38003 38004/** 38005 * @ngdoc provider 38006 * @name $sanitizeProvider 38007 * @this 38008 * 38009 * @description 38010 * Creates and configures {@link $sanitize} instance. 38011 */ 38012function $SanitizeProvider() { 38013 var hasBeenInstantiated = false; 38014 var svgEnabled = false; 38015 38016 this.$get = ['$$sanitizeUri', function($$sanitizeUri) { 38017 hasBeenInstantiated = true; 38018 if (svgEnabled) { 38019 extend(validElements, svgElements); 38020 } 38021 return function(html) { 38022 var buf = []; 38023 htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) { 38024 return !/^unsafe:/.test($$sanitizeUri(uri, isImage)); 38025 })); 38026 return buf.join(''); 38027 }; 38028 }]; 38029 38030 38031 /** 38032 * @ngdoc method 38033 * @name $sanitizeProvider#enableSvg 38034 * @kind function 38035 * 38036 * @description 38037 * Enables a subset of svg to be supported by the sanitizer. 38038 * 38039 * <div class="alert alert-warning"> 38040 * <p>By enabling this setting without taking other precautions, you might expose your 38041 * application to click-hijacking attacks. In these attacks, sanitized svg elements could be positioned 38042 * outside of the containing element and be rendered over other elements on the page (e.g. a login 38043 * link). Such behavior can then result in phishing incidents.</p> 38044 * 38045 * <p>To protect against these, explicitly setup `overflow: hidden` css rule for all potential svg 38046 * tags within the sanitized content:</p> 38047 * 38048 * <br> 38049 * 38050 * <pre><code> 38051 * .rootOfTheIncludedContent svg { 38052 * overflow: hidden !important; 38053 * } 38054 * </code></pre> 38055 * </div> 38056 * 38057 * @param {boolean=} flag Enable or disable SVG support in the sanitizer. 38058 * @returns {boolean|$sanitizeProvider} Returns the currently configured value if called 38059 * without an argument or self for chaining otherwise. 38060 */ 38061 this.enableSvg = function(enableSvg) { 38062 if (isDefined(enableSvg)) { 38063 svgEnabled = enableSvg; 38064 return this; 38065 } else { 38066 return svgEnabled; 38067 } 38068 }; 38069 38070 38071 /** 38072 * @ngdoc method 38073 * @name $sanitizeProvider#addValidElements 38074 * @kind function 38075 * 38076 * @description 38077 * Extends the built-in lists of valid HTML/SVG elements, i.e. elements that are considered safe 38078 * and are not stripped off during sanitization. You can extend the following lists of elements: 38079 * 38080 * - `htmlElements`: A list of elements (tag names) to extend the current list of safe HTML 38081 * elements. HTML elements considered safe will not be removed during sanitization. All other 38082 * elements will be stripped off. 38083 * 38084 * - `htmlVoidElements`: This is similar to `htmlElements`, but marks the elements as 38085 * "void elements" (similar to HTML 38086 * [void elements](https://rawgit.com/w3c/html/html5.1-2/single-page.html#void-elements)). These 38087 * elements have no end tag and cannot have content. 38088 * 38089 * - `svgElements`: This is similar to `htmlElements`, but for SVG elements. This list is only 38090 * taken into account if SVG is {@link ngSanitize.$sanitizeProvider#enableSvg enabled} for 38091 * `$sanitize`. 38092 * 38093 * <div class="alert alert-info"> 38094 * This method must be called during the {@link angular.Module#config config} phase. Once the 38095 * `$sanitize` service has been instantiated, this method has no effect. 38096 * </div> 38097 * 38098 * <div class="alert alert-warning"> 38099 * Keep in mind that extending the built-in lists of elements may expose your app to XSS or 38100 * other vulnerabilities. Be very mindful of the elements you add. 38101 * </div> 38102 * 38103 * @param {Array<String>|Object} elements - A list of valid HTML elements or an object with one or 38104 * more of the following properties: 38105 * - **htmlElements** - `{Array<String>}` - A list of elements to extend the current list of 38106 * HTML elements. 38107 * - **htmlVoidElements** - `{Array<String>}` - A list of elements to extend the current list of 38108 * void HTML elements; i.e. elements that do not have an end tag. 38109 * - **svgElements** - `{Array<String>}` - A list of elements to extend the current list of SVG 38110 * elements. The list of SVG elements is only taken into account if SVG is 38111 * {@link ngSanitize.$sanitizeProvider#enableSvg enabled} for `$sanitize`. 38112 * 38113 * Passing an array (`[...]`) is equivalent to passing `{htmlElements: [...]}`. 38114 * 38115 * @return {$sanitizeProvider} Returns self for chaining. 38116 */ 38117 this.addValidElements = function(elements) { 38118 if (!hasBeenInstantiated) { 38119 if (isArray(elements)) { 38120 elements = {htmlElements: elements}; 38121 } 38122 38123 addElementsTo(svgElements, elements.svgElements); 38124 addElementsTo(voidElements, elements.htmlVoidElements); 38125 addElementsTo(validElements, elements.htmlVoidElements); 38126 addElementsTo(validElements, elements.htmlElements); 38127 } 38128 38129 return this; 38130 }; 38131 38132 38133 /** 38134 * @ngdoc method 38135 * @name $sanitizeProvider#addValidAttrs 38136 * @kind function 38137 * 38138 * @description 38139 * Extends the built-in list of valid attributes, i.e. attributes that are considered safe and are 38140 * not stripped off during sanitization. 38141 * 38142 * **Note**: 38143 * The new attributes will not be treated as URI attributes, which means their values will not be 38144 * sanitized as URIs using `$compileProvider`'s 38145 * {@link ng.$compileProvider#aHrefSanitizationTrustedUrlList aHrefSanitizationTrustedUrlList} and 38146 * {@link ng.$compileProvider#imgSrcSanitizationTrustedUrlList imgSrcSanitizationTrustedUrlList}. 38147 * 38148 * <div class="alert alert-info"> 38149 * This method must be called during the {@link angular.Module#config config} phase. Once the 38150 * `$sanitize` service has been instantiated, this method has no effect. 38151 * </div> 38152 * 38153 * <div class="alert alert-warning"> 38154 * Keep in mind that extending the built-in list of attributes may expose your app to XSS or 38155 * other vulnerabilities. Be very mindful of the attributes you add. 38156 * </div> 38157 * 38158 * @param {Array<String>} attrs - A list of valid attributes. 38159 * 38160 * @returns {$sanitizeProvider} Returns self for chaining. 38161 */ 38162 this.addValidAttrs = function(attrs) { 38163 if (!hasBeenInstantiated) { 38164 extend(validAttrs, arrayToMap(attrs, true)); 38165 } 38166 return this; 38167 }; 38168 38169 ////////////////////////////////////////////////////////////////////////////////////////////////// 38170 // Private stuff 38171 ////////////////////////////////////////////////////////////////////////////////////////////////// 38172 38173 bind = angular.bind; 38174 extend = angular.extend; 38175 forEach = angular.forEach; 38176 isArray = angular.isArray; 38177 isDefined = angular.isDefined; 38178 lowercase = angular.$$lowercase; 38179 noop = angular.noop; 38180 38181 htmlParser = htmlParserImpl; 38182 htmlSanitizeWriter = htmlSanitizeWriterImpl; 38183 38184 nodeContains = window.Node.prototype.contains || /** @this */ function(arg) { 38185 // eslint-disable-next-line no-bitwise 38186 return !!(this.compareDocumentPosition(arg) & 16); 38187 }; 38188 38189 // Regular Expressions for parsing tags and attributes 38190 var SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g, 38191 // Match everything outside of normal chars and " (quote character) 38192 NON_ALPHANUMERIC_REGEXP = /([^#-~ |!])/g; 38193 38194 38195 // Good source of info about elements and attributes 38196 // http://dev.w3.org/html5/spec/Overview.html#semantics 38197 // http://simon.html5.org/html-elements 38198 38199 // Safe Void Elements - HTML5 38200 // http://dev.w3.org/html5/spec/Overview.html#void-elements 38201 var voidElements = stringToMap('area,br,col,hr,img,wbr'); 38202 38203 // Elements that you can, intentionally, leave open (and which close themselves) 38204 // http://dev.w3.org/html5/spec/Overview.html#optional-tags 38205 var optionalEndTagBlockElements = stringToMap('colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr'), 38206 optionalEndTagInlineElements = stringToMap('rp,rt'), 38207 optionalEndTagElements = extend({}, 38208 optionalEndTagInlineElements, 38209 optionalEndTagBlockElements); 38210 38211 // Safe Block Elements - HTML5 38212 var blockElements = extend({}, optionalEndTagBlockElements, stringToMap('address,article,' + 38213 'aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,' + 38214 'h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,section,table,ul')); 38215 38216 // Inline Elements - HTML5 38217 var inlineElements = extend({}, optionalEndTagInlineElements, stringToMap('a,abbr,acronym,b,' + 38218 'bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,' + 38219 'samp,small,span,strike,strong,sub,sup,time,tt,u,var')); 38220 38221 // SVG Elements 38222 // https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Elements 38223 // Note: the elements animate,animateColor,animateMotion,animateTransform,set are intentionally omitted. 38224 // They can potentially allow for arbitrary javascript to be executed. See #11290 38225 var svgElements = stringToMap('circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph,' + 38226 'hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline,' + 38227 'radialGradient,rect,stop,svg,switch,text,title,tspan'); 38228 38229 // Blocked Elements (will be stripped) 38230 var blockedElements = stringToMap('script,style'); 38231 38232 var validElements = extend({}, 38233 voidElements, 38234 blockElements, 38235 inlineElements, 38236 optionalEndTagElements); 38237 38238 //Attributes that have href and hence need to be sanitized 38239 var uriAttrs = stringToMap('background,cite,href,longdesc,src,xlink:href,xml:base'); 38240 38241 var htmlAttrs = stringToMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' + 38242 'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,' + 38243 'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,' + 38244 'scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,' + 38245 'valign,value,vspace,width'); 38246 38247 // SVG attributes (without "id" and "name" attributes) 38248 // https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Attributes 38249 var svgAttrs = stringToMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,' + 38250 'baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,' + 38251 'cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,' + 38252 'font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,' + 38253 'height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,keySplines,keyTimes,lang,' + 38254 'marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mathematical,' + 38255 'max,min,offset,opacity,orient,origin,overline-position,overline-thickness,panose-1,' + 38256 'path,pathLength,points,preserveAspectRatio,r,refX,refY,repeatCount,repeatDur,' + 38257 'requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,stemv,stop-color,' + 38258 'stop-opacity,strikethrough-position,strikethrough-thickness,stroke,stroke-dasharray,' + 38259 'stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,' + 38260 'stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,underline-position,' + 38261 'underline-thickness,unicode,unicode-range,units-per-em,values,version,viewBox,visibility,' + 38262 'width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,xlink:show,xlink:title,' + 38263 'xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,zoomAndPan', true); 38264 38265 var validAttrs = extend({}, 38266 uriAttrs, 38267 svgAttrs, 38268 htmlAttrs); 38269 38270 function stringToMap(str, lowercaseKeys) { 38271 return arrayToMap(str.split(','), lowercaseKeys); 38272 } 38273 38274 function arrayToMap(items, lowercaseKeys) { 38275 var obj = {}, i; 38276 for (i = 0; i < items.length; i++) { 38277 obj[lowercaseKeys ? lowercase(items[i]) : items[i]] = true; 38278 } 38279 return obj; 38280 } 38281 38282 function addElementsTo(elementsMap, newElements) { 38283 if (newElements && newElements.length) { 38284 extend(elementsMap, arrayToMap(newElements)); 38285 } 38286 } 38287 38288 /** 38289 * Create an inert document that contains the dirty HTML that needs sanitizing. 38290 * We use the DOMParser API by default and fall back to createHTMLDocument if DOMParser is not 38291 * available. 38292 */ 38293 var getInertBodyElement /* function(html: string): HTMLBodyElement */ = (function(window, document) { 38294 if (isDOMParserAvailable()) { 38295 return getInertBodyElement_DOMParser; 38296 } 38297 38298 if (!document || !document.implementation) { 38299 throw $sanitizeMinErr('noinert', 'Can\'t create an inert html document'); 38300 } 38301 var inertDocument = document.implementation.createHTMLDocument('inert'); 38302 var inertBodyElement = (inertDocument.documentElement || inertDocument.getDocumentElement()).querySelector('body'); 38303 return getInertBodyElement_InertDocument; 38304 38305 function isDOMParserAvailable() { 38306 try { 38307 return !!getInertBodyElement_DOMParser(''); 38308 } catch (e) { 38309 return false; 38310 } 38311 } 38312 38313 function getInertBodyElement_DOMParser(html) { 38314 // We add this dummy element to ensure that the rest of the content is parsed as expected 38315 // e.g. leading whitespace is maintained and tags like `<meta>` do not get hoisted to the `<head>` tag. 38316 html = '<remove></remove>' + html; 38317 try { 38318 var body = new window.DOMParser().parseFromString(html, 'text/html').body; 38319 body.firstChild.remove(); 38320 return body; 38321 } catch (e) { 38322 return undefined; 38323 } 38324 } 38325 38326 function getInertBodyElement_InertDocument(html) { 38327 inertBodyElement.innerHTML = html; 38328 38329 // Support: IE 9-11 only 38330 // strip custom-namespaced attributes on IE<=11 38331 if (document.documentMode) { 38332 stripCustomNsAttrs(inertBodyElement); 38333 } 38334 38335 return inertBodyElement; 38336 } 38337 })(window, window.document); 38338 38339 /** 38340 * @example 38341 * htmlParser(htmlString, { 38342 * start: function(tag, attrs) {}, 38343 * end: function(tag) {}, 38344 * chars: function(text) {}, 38345 * comment: function(text) {} 38346 * }); 38347 * 38348 * @param {string} html string 38349 * @param {object} handler 38350 */ 38351 function htmlParserImpl(html, handler) { 38352 if (html === null || html === undefined) { 38353 html = ''; 38354 } else if (typeof html !== 'string') { 38355 html = '' + html; 38356 } 38357 38358 var inertBodyElement = getInertBodyElement(html); 38359 if (!inertBodyElement) return ''; 38360 38361 //mXSS protection 38362 var mXSSAttempts = 5; 38363 do { 38364 if (mXSSAttempts === 0) { 38365 throw $sanitizeMinErr('uinput', 'Failed to sanitize html because the input is unstable'); 38366 } 38367 mXSSAttempts--; 38368 38369 // trigger mXSS if it is going to happen by reading and writing the innerHTML 38370 html = inertBodyElement.innerHTML; 38371 inertBodyElement = getInertBodyElement(html); 38372 } while (html !== inertBodyElement.innerHTML); 38373 38374 var node = inertBodyElement.firstChild; 38375 while (node) { 38376 switch (node.nodeType) { 38377 case 1: // ELEMENT_NODE 38378 handler.start(node.nodeName.toLowerCase(), attrToMap(node.attributes)); 38379 break; 38380 case 3: // TEXT NODE 38381 handler.chars(node.textContent); 38382 break; 38383 } 38384 38385 var nextNode; 38386 if (!(nextNode = node.firstChild)) { 38387 if (node.nodeType === 1) { 38388 handler.end(node.nodeName.toLowerCase()); 38389 } 38390 nextNode = getNonDescendant('nextSibling', node); 38391 if (!nextNode) { 38392 while (nextNode == null) { 38393 node = getNonDescendant('parentNode', node); 38394 if (node === inertBodyElement) break; 38395 nextNode = getNonDescendant('nextSibling', node); 38396 if (node.nodeType === 1) { 38397 handler.end(node.nodeName.toLowerCase()); 38398 } 38399 } 38400 } 38401 } 38402 node = nextNode; 38403 } 38404 38405 while ((node = inertBodyElement.firstChild)) { 38406 inertBodyElement.removeChild(node); 38407 } 38408 } 38409 38410 function attrToMap(attrs) { 38411 var map = {}; 38412 for (var i = 0, ii = attrs.length; i < ii; i++) { 38413 var attr = attrs[i]; 38414 map[attr.name] = attr.value; 38415 } 38416 return map; 38417 } 38418 38419 38420 /** 38421 * Escapes all potentially dangerous characters, so that the 38422 * resulting string can be safely inserted into attribute or 38423 * element text. 38424 * @param value 38425 * @returns {string} escaped text 38426 */ 38427 function encodeEntities(value) { 38428 return value. 38429 replace(/&/g, '&'). 38430 replace(SURROGATE_PAIR_REGEXP, function(value) { 38431 var hi = value.charCodeAt(0); 38432 var low = value.charCodeAt(1); 38433 return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';'; 38434 }). 38435 replace(NON_ALPHANUMERIC_REGEXP, function(value) { 38436 return '&#' + value.charCodeAt(0) + ';'; 38437 }). 38438 replace(/</g, '<'). 38439 replace(/>/g, '>'); 38440 } 38441 38442 /** 38443 * create an HTML/XML writer which writes to buffer 38444 * @param {Array} buf use buf.join('') to get out sanitized html string 38445 * @returns {object} in the form of { 38446 * start: function(tag, attrs) {}, 38447 * end: function(tag) {}, 38448 * chars: function(text) {}, 38449 * comment: function(text) {} 38450 * } 38451 */ 38452 function htmlSanitizeWriterImpl(buf, uriValidator) { 38453 var ignoreCurrentElement = false; 38454 var out = bind(buf, buf.push); 38455 return { 38456 start: function(tag, attrs) { 38457 tag = lowercase(tag); 38458 if (!ignoreCurrentElement && blockedElements[tag]) { 38459 ignoreCurrentElement = tag; 38460 } 38461 if (!ignoreCurrentElement && validElements[tag] === true) { 38462 out('<'); 38463 out(tag); 38464 forEach(attrs, function(value, key) { 38465 var lkey = lowercase(key); 38466 var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background'); 38467 if (validAttrs[lkey] === true && 38468 (uriAttrs[lkey] !== true || uriValidator(value, isImage))) { 38469 out(' '); 38470 out(key); 38471 out('="'); 38472 out(encodeEntities(value)); 38473 out('"'); 38474 } 38475 }); 38476 out('>'); 38477 } 38478 }, 38479 end: function(tag) { 38480 tag = lowercase(tag); 38481 if (!ignoreCurrentElement && validElements[tag] === true && voidElements[tag] !== true) { 38482 out('</'); 38483 out(tag); 38484 out('>'); 38485 } 38486 // eslint-disable-next-line eqeqeq 38487 if (tag == ignoreCurrentElement) { 38488 ignoreCurrentElement = false; 38489 } 38490 }, 38491 chars: function(chars) { 38492 if (!ignoreCurrentElement) { 38493 out(encodeEntities(chars)); 38494 } 38495 } 38496 }; 38497 } 38498 38499 38500 /** 38501 * When IE9-11 comes across an unknown namespaced attribute e.g. 'xlink:foo' it adds 'xmlns:ns1' attribute to declare 38502 * ns1 namespace and prefixes the attribute with 'ns1' (e.g. 'ns1:xlink:foo'). This is undesirable since we don't want 38503 * to allow any of these custom attributes. This method strips them all. 38504 * 38505 * @param node Root element to process 38506 */ 38507 function stripCustomNsAttrs(node) { 38508 while (node) { 38509 if (node.nodeType === window.Node.ELEMENT_NODE) { 38510 var attrs = node.attributes; 38511 for (var i = 0, l = attrs.length; i < l; i++) { 38512 var attrNode = attrs[i]; 38513 var attrName = attrNode.name.toLowerCase(); 38514 if (attrName === 'xmlns:ns1' || attrName.lastIndexOf('ns1:', 0) === 0) { 38515 node.removeAttributeNode(attrNode); 38516 i--; 38517 l--; 38518 } 38519 } 38520 } 38521 38522 var nextNode = node.firstChild; 38523 if (nextNode) { 38524 stripCustomNsAttrs(nextNode); 38525 } 38526 38527 node = getNonDescendant('nextSibling', node); 38528 } 38529 } 38530 38531 function getNonDescendant(propName, node) { 38532 // An element is clobbered if its `propName` property points to one of its descendants 38533 var nextNode = node[propName]; 38534 if (nextNode && nodeContains.call(node, nextNode)) { 38535 throw $sanitizeMinErr('elclob', 'Failed to sanitize html because the element is clobbered: {0}', node.outerHTML || node.outerText); 38536 } 38537 return nextNode; 38538 } 38539} 38540 38541function sanitizeText(chars) { 38542 var buf = []; 38543 var writer = htmlSanitizeWriter(buf, noop); 38544 writer.chars(chars); 38545 return buf.join(''); 38546} 38547 38548 38549// define ngSanitize module and register $sanitize service 38550angular.module('ngSanitize', []) 38551 .provider('$sanitize', $SanitizeProvider) 38552 .info({ angularVersion: '1.8.3' }); 38553 38554/** 38555 * @ngdoc filter 38556 * @name linky 38557 * @kind function 38558 * 38559 * @description 38560 * Finds links in text input and turns them into html links. Supports `http/https/ftp/sftp/mailto` and 38561 * plain email address links. 38562 * 38563 * Requires the {@link ngSanitize `ngSanitize`} module to be installed. 38564 * 38565 * @param {string} text Input text. 38566 * @param {string} [target] Window (`_blank|_self|_parent|_top`) or named frame to open links in. 38567 * @param {object|function(url)} [attributes] Add custom attributes to the link element. 38568 * 38569 * Can be one of: 38570 * 38571 * - `object`: A map of attributes 38572 * - `function`: Takes the url as a parameter and returns a map of attributes 38573 * 38574 * If the map of attributes contains a value for `target`, it overrides the value of 38575 * the target parameter. 38576 * 38577 * 38578 * @returns {string} Html-linkified and {@link $sanitize sanitized} text. 38579 * 38580 * @usage 38581 <span ng-bind-html="linky_expression | linky"></span> 38582 * 38583 * @example 38584 <example module="linkyExample" deps="angular-sanitize.js" name="linky-filter"> 38585 <file name="index.html"> 38586 <div ng-controller="ExampleController"> 38587 Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea> 38588 <table> 38589 <tr> 38590 <th>Filter</th> 38591 <th>Source</th> 38592 <th>Rendered</th> 38593 </tr> 38594 <tr id="linky-filter"> 38595 <td>linky filter</td> 38596 <td> 38597 <pre><div ng-bind-html="snippet | linky"><br></div></pre> 38598 </td> 38599 <td> 38600 <div ng-bind-html="snippet | linky"></div> 38601 </td> 38602 </tr> 38603 <tr id="linky-target"> 38604 <td>linky target</td> 38605 <td> 38606 <pre><div ng-bind-html="snippetWithSingleURL | linky:'_blank'"><br></div></pre> 38607 </td> 38608 <td> 38609 <div ng-bind-html="snippetWithSingleURL | linky:'_blank'"></div> 38610 </td> 38611 </tr> 38612 <tr id="linky-custom-attributes"> 38613 <td>linky custom attributes</td> 38614 <td> 38615 <pre><div ng-bind-html="snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}"><br></div></pre> 38616 </td> 38617 <td> 38618 <div ng-bind-html="snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}"></div> 38619 </td> 38620 </tr> 38621 <tr id="escaped-html"> 38622 <td>no filter</td> 38623 <td><pre><div ng-bind="snippet"><br></div></pre></td> 38624 <td><div ng-bind="snippet"></div></td> 38625 </tr> 38626 </table> 38627 </file> 38628 <file name="script.js"> 38629 angular.module('linkyExample', ['ngSanitize']) 38630 .controller('ExampleController', ['$scope', function($scope) { 38631 $scope.snippet = 38632 'Pretty text with some links:\n' + 38633 'http://angularjs.org/,\n' + 38634 'mailto:[email protected],\n' + 38635 '[email protected],\n' + 38636 'and one more: ftp://127.0.0.1/.'; 38637 $scope.snippetWithSingleURL = 'http://angularjs.org/'; 38638 }]); 38639 </file> 38640 <file name="protractor.js" type="protractor"> 38641 it('should linkify the snippet with urls', function() { 38642 expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()). 38643 toBe('Pretty text with some links: http://angularjs.org/, [email protected], ' + 38644 '[email protected], and one more: ftp://127.0.0.1/.'); 38645 expect(element.all(by.css('#linky-filter a')).count()).toEqual(4); 38646 }); 38647 38648 it('should not linkify snippet without the linky filter', function() { 38649 expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()). 38650 toBe('Pretty text with some links: http://angularjs.org/, mailto:[email protected], ' + 38651 '[email protected], and one more: ftp://127.0.0.1/.'); 38652 expect(element.all(by.css('#escaped-html a')).count()).toEqual(0); 38653 }); 38654 38655 it('should update', function() { 38656 element(by.model('snippet')).clear(); 38657 element(by.model('snippet')).sendKeys('new http://link.'); 38658 expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()). 38659 toBe('new http://link.'); 38660 expect(element.all(by.css('#linky-filter a')).count()).toEqual(1); 38661 expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()) 38662 .toBe('new http://link.'); 38663 }); 38664 38665 it('should work with the target property', function() { 38666 expect(element(by.id('linky-target')). 38667 element(by.binding("snippetWithSingleURL | linky:'_blank'")).getText()). 38668 toBe('http://angularjs.org/'); 38669 expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank'); 38670 }); 38671 38672 it('should optionally add custom attributes', function() { 38673 expect(element(by.id('linky-custom-attributes')). 38674 element(by.binding("snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}")).getText()). 38675 toBe('http://angularjs.org/'); 38676 expect(element(by.css('#linky-custom-attributes a')).getAttribute('rel')).toEqual('nofollow'); 38677 }); 38678 </file> 38679 </example> 38680 */ 38681angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) { 38682 var LINKY_URL_REGEXP = 38683 /((s?ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"\u201d\u2019]/i, 38684 MAILTO_REGEXP = /^mailto:/i; 38685 38686 var linkyMinErr = angular.$$minErr('linky'); 38687 var isDefined = angular.isDefined; 38688 var isFunction = angular.isFunction; 38689 var isObject = angular.isObject; 38690 var isString = angular.isString; 38691 38692 return function(text, target, attributes) { 38693 if (text == null || text === '') return text; 38694 if (!isString(text)) throw linkyMinErr('notstring', 'Expected string but received: {0}', text); 38695 38696 var attributesFn = 38697 isFunction(attributes) ? attributes : 38698 isObject(attributes) ? function getAttributesObject() {return attributes;} : 38699 function getEmptyAttributesObject() {return {};}; 38700 38701 var match; 38702 var raw = text; 38703 var html = []; 38704 var url; 38705 var i; 38706 while ((match = raw.match(LINKY_URL_REGEXP))) { 38707 // We can not end in these as they are sometimes found at the end of the sentence 38708 url = match[0]; 38709 // if we did not match ftp/http/www/mailto then assume mailto 38710 if (!match[2] && !match[4]) { 38711 url = (match[3] ? 'http://' : 'mailto:') + url; 38712 } 38713 i = match.index; 38714 addText(raw.substr(0, i)); 38715 addLink(url, match[0].replace(MAILTO_REGEXP, '')); 38716 raw = raw.substring(i + match[0].length); 38717 } 38718 addText(raw); 38719 return $sanitize(html.join('')); 38720 38721 function addText(text) { 38722 if (!text) { 38723 return; 38724 } 38725 html.push(sanitizeText(text)); 38726 } 38727 38728 function addLink(url, text) { 38729 var key, linkAttributes = attributesFn(url); 38730 html.push('<a '); 38731 38732 for (key in linkAttributes) { 38733 html.push(key + '="' + linkAttributes[key] + '" '); 38734 } 38735 38736 if (isDefined(target) && !('target' in linkAttributes)) { 38737 html.push('target="', 38738 target, 38739 '" '); 38740 } 38741 html.push('href="', 38742 url.replace(/"/g, '"'), 38743 '">'); 38744 addText(text); 38745 html.push('</a>'); 38746 } 38747 }; 38748}]); 38749 38750 38751})(window, window.angular);
38752 38753/** 38754 * Bunch of useful filters for angularJS(with no external dependencies!) 38755 * @version v0.5.17 - 2017-09-22 * @link https://github.com/a8m/angular-filter 38756 * @author Ariel Mashraki <[email protected]> 38757 * @license MIT License, http://www.opensource.org/licenses/MIT 38758 */!function(a,b,c){"use strict";function d(a){return E(a)?a:Object.keys(a).map(function(b){return a[b]})}function e(a){return null===a}function f(a,b){var d=Object.keys(a);return d.map(function(d){return b[d]!==c&&b[d]==a[d]}).indexOf(!1)==-1}function g(a,b){function c(a,b,c){for(var d=0;b+d<=a.length;){if(a.charAt(b+d)==c)return d;d++}return-1}for(var d=0,e=0;e<=b.length;e++){var f=c(a,d,b.charAt(e));if(f==-1)return!1;d+=f+1}return!0}function h(a,b,c){var d=0;return a.filter(function(a){var e=y(c)?d<b&&c(a):d<b;return d=e?d+1:d,e})}function i(a,b){return Math.round(a*Math.pow(10,b))/Math.pow(10,b)}function j(a,b,c){b=b||[];var d=Object.keys(a);return d.forEach(function(d){if(D(a[d])&&!E(a[d])){var e=c?c+"."+d:c;j(a[d],b,e||d)}else{var f=c?c+"."+d:d;b.push(f)}}),b}function k(a){return a&&a.$evalAsync&&a.$watch}function l(){return function(a,b){return a>b}}function m(){return function(a,b){return a>=b}}function n(){return function(a,b){return a<b}}function o(){return function(a,b){return a<=b}}function p(){return function(a,b){return a==b}}function q(){return function(a,b){return a!=b}}function r(){return function(a,b){return a===b}}function s(){return function(a,b){return a!==b}}function t(a){return function(b,c){return b=D(b)?d(b):b,!(!E(b)||z(c))&&b.some(function(b){return B(c)&&D(b)||A(c)?a(c)(b):b===c})}}function u(a,b){return b=b||0,b>=a.length?a:E(a[b])?u(a.slice(0,b).concat(a[b],a.slice(b+1)),b):u(a,b+1)}function v(a){return function(b,c){function e(a,b){return!z(b)&&a.some(function(a){return I(a,b)})}if(b=D(b)?d(b):b,!E(b))return b;var f=[],g=a(c);return z(c)?b.filter(function(a,b,c){return c.indexOf(a)===b}):b.filter(function(a){var b=g(a);return!e(f,b)&&(f.push(b),!0)})}}function w(a,b,c){return b?a+c+w(a,--b,c):a}function x(){return function(a){return B(a)?a.split(" ").map(function(a){return a.charAt(0).toUpperCase()+a.substring(1)}).join(" "):a}}var y=b.isDefined,z=b.isUndefined,A=b.isFunction,B=b.isString,C=b.isNumber,D=b.isObject,E=b.isArray,F=b.forEach,G=b.extend,H=b.copy,I=b.equals;String.prototype.contains||(String.prototype.contains=function(){return String.prototype.indexOf.apply(this,arguments)!==-1}),b.module("a8m.angular",[]).filter("isUndefined",function(){return function(a){return b.isUndefined(a)}}).filter("isDefined",function(){return function(a){return b.isDefined(a)}}).filter("isFunction",function(){return function(a){return b.isFunction(a)}}).filter("isString",function(){return function(a){return b.isString(a)}}).filter("isNumber",function(){return function(a){return b.isNumber(a)}}).filter("isArray",function(){return function(a){return b.isArray(a)}}).filter("isObject",function(){return function(a){return b.isObject(a)}}).filter("isEqual",function(){return function(a,c){return b.equals(a,c)}}),b.module("a8m.conditions",[]).filter({isGreaterThan:l,">":l,isGreaterThanOrEqualTo:m,">=":m,isLessThan:n,"<":n,isLessThanOrEqualTo:o,"<=":o,isEqualTo:p,"==":p,isNotEqualTo:q,"!=":q,isIdenticalTo:r,"===":r,isNotIdenticalTo:s,"!==":s}),b.module("a8m.is-null",[]).filter("isNull",function(){return function(a){return e(a)}}),b.module("a8m.after-where",[]).filter("afterWhere",function(){return function(a,b){if(a=D(a)?d(a):a,!E(a)||z(b))return a;var c=a.map(function(a){return f(b,a)}).indexOf(!0);return a.slice(c===-1?0:c)}}),b.module("a8m.after",[]).filter("after",function(){return function(a,b){return a=D(a)?d(a):a,E(a)?a.slice(b):a}}),b.module("a8m.before-where",[]).filter("beforeWhere",function(){return function(a,b){if(a=D(a)?d(a):a,!E(a)||z(b))return a;var c=a.map(function(a){return f(b,a)}).indexOf(!0);return a.slice(0,c===-1?a.length:++c)}}),b.module("a8m.before",[]).filter("before",function(){return function(a,b){return a=D(a)?d(a):a,E(a)?a.slice(0,b?--b:b):a}}),b.module("a8m.chunk-by",["a8m.filter-watcher"]).filter("chunkBy",["filterWatcher",function(a){return function(b,c,d){function e(a,b){for(var c=[];a--;)c[a]=b;return c}function f(a,b,c){return E(a)?a.map(function(a,d,f){return d*=b,a=f.slice(d,d+b),!z
38758(c)&&a.length<b?a.concat(e(b-a.length,c)):a}).slice(0,Math.ceil(a.length/b)):a}return a.isMemoized("chunkBy",arguments)||a.memoize("chunkBy",arguments,this,f(b,c,d))}}]),b.module("a8m.concat",[]).filter("concat",[function(){return function(a,b){if(z(b))return a;if(E(a))return D(b)?a.concat(d(b)):a.concat(b);if(D(a)){var c=d(a);return D(b)?c.concat(d(b)):c.concat(b)}return a}}]),b.module("a8m.contains",[]).filter({contains:["$parse",t],some:["$parse",t]}),b.module("a8m.count-by",[]).filter("countBy",["$parse",function(a){return function(b,c){var e,f={},g=a(c);return b=D(b)?d(b):b,!E(b)||z(c)?b:(b.forEach(function(a){e=g(a),f[e]||(f[e]=0),f[e]++}),f)}}]),b.module("a8m.defaults",[]).filter("defaults",["$parse",function(a){return function(b,c){if(b=D(b)?d(b):b,!E(b)||!D(c))return b;var e=j(c);return b.forEach(function(b){e.forEach(function(d){var e=a(d),f=e.assign;z(e(b))&&f(b,e(c))})}),b}}]),b.module("a8m.every",[]).filter("every",["$parse",function(a){return function(b,c){return b=D(b)?d(b):b,!(E(b)&&!z(c))||b.every(function(b){return D(b)||A(c)?a(c)(b):b===c})}}]),b.module("a8m.filter-by",[]).filter("filterBy",["$parse",function(a){return function(b,e,f,g){var h;return f=B(f)||C(f)?String(f).toLowerCase():c,b=D(b)?d(b):b,!E(b)||z(f)?b:b.filter(function(b){return e.some(function(c){if(~c.indexOf("+")){var d=c.replace(/\s+/g,"").split("+");h=d.map(function(c){return a(c)(b)}).join(" ")}else h=a(c)(b);return!(!B(h)&&!C(h))&&(h=String(h).toLowerCase(),g?h===f:h.contains(f))})})}}]),b.module("a8m.first",[]).filter("first",["$parse",function(a){return function(b){var e,f,g;return b=D(b)?d(b):b,E(b)?(g=Array.prototype.slice.call(arguments,1),e=C(g[0])?g[0]:1,f=C(g[0])?C(g[1])?c:g[1]:g[0],g.length?h(b,e,f?a(f):f):b[0]):b}}]),b.module("a8m.flatten",[]).filter("flatten",function(){return function(a,b){return b=b||!1,a=D(a)?d(a):a,E(a)?b?[].concat.apply([],a):u(a,0):a}}),b.module("a8m.fuzzy-by",[]).filter("fuzzyBy",["$parse",function(a){return function(b,c,e,f){var h,i,j=f||!1;return b=D(b)?d(b):b,!E(b)||z(c)||z(e)?b:(i=a(c),b.filter(function(a){return h=i(a),!!B(h)&&(h=j?h:h.toLowerCase(),e=j?e:e.toLowerCase(),g(h,e)!==!1)}))}}]),b.module("a8m.fuzzy",[]).filter("fuzzy",function(){return function(a,b,c){function e(a,b){var c,d,e=Object.keys(a);return 0<e.filter(function(e){return c=a[e],!!d||!!B(c)&&(c=f?c:c.toLowerCase(),d=g(c,b)!==!1)}).length}var f=c||!1;return a=D(a)?d(a):a,!E(a)||z(b)?a:(b=f?b:b.toLowerCase(),a.filter(function(a){return B(a)?(a=f?a:a.toLowerCase(),g(a,b)!==!1):!!D(a)&&e(a,b)}))}}),b.module("a8m.group-by",["a8m.filter-watcher"]).filter("groupBy",["$parse","filterWatcher",function(a,b){return function(c,d){function e(a,b){var c,d={};
38758return F(a,function(a){c=b(a),d[c]||(d[c]=[]),d[c].push(a)}),d}return!D(c)||z(d)?c:b.isMemoized("groupBy",arguments)||b.memoize("groupBy",arguments,this,e(c,a(d)))}}]),b.module("a8m.is-empty",[]).filter("isEmpty",function(){return function(a){return D(a)?!d(a).length:!a.length}}),b.module("a8m.join",[]).filter("join",function(){return function(a,b){return z(a)||!E(a)?a:(z(b)&&(b=" "),a.join(b))}}),b.module("a8m.last",[]).filter("last",["$parse",function(a){return function(b){var e,f,g,i=H(b);return i=D(i)?d(i):i,E(i)?(g=Array.prototype.slice.call(arguments,1),e=C(g[0])?g[0]:1,f=C(g[0])?C(g[1])?c:g[1]:g[0],g.length?h(i.reverse(),e,f?a(f):f).reverse():i[i.length-1]):i}}]),b.module("a8m.map",[]).filter("map",["$parse",function(a){return function(b,c){return b=D(b)?d(b):b,!E(b)||z(c)?b:b.map(function(b){return a(c)(b)})}}]),b.module("a8m.omit",[]).filter("omit",["$parse",function(a){return function(b,c){return b=D(b)?d(b):b,!E(b)||z(c)?b:b.filter(function(b){return!a(c)(b)})}}]),b.module("a8m.pick",[]).filter("pick",["$parse",function(a){return function(b,c){return b=D(b)?d(b):b,!E(b)||z(c)?b:b.filter(function(b){return a(c)(b)})}}]),b.module("a8m.range",[]).filter("range",function(){return function(a,b,c,d,e){c=c||0,d=d||1;for(var f=0;f<parseInt(b);f++){var g=c+f*d;a.push(A(e)?e(g):g)}return a}}),b.module("a8m.remove-with",[]).filter("removeWith",function(){return function(a,b){return z(b)?a:(a=D(a)?d(a):a,a.filter(function(a){return!f(b,a)}))}}),b.module("a8m.remove",[]).filter("remove",function(){return function(a){a=D(a)?d(a):a;var b=Array.prototype.slice.call(arguments,1);return E(a)?a.filter(function(a){return!b.some(function(b){return I(b,a)})}):a}}),b.module("a8m.reverse",[]).filter("reverse",[function(){return function(a){return a=D(a)?d(a):a,B(a)?a.split("").reverse().join(""):E(a)?a.slice().reverse():a}}]),b.module("a8m.search-field",[]).filter("searchField",["$parse",function(a){return function(b){var c,e;b=D(b)?d(b):b;var f=Array.prototype.slice.call(arguments,1);return E(b)&&f.length?b.map(function(b){return e=f.map(function(d){return(c=a(d))(b)}).join(" "),G(b,{searchField:e})}):b}}]),b.module("a8m.to-array",[]).filter("toArray",function(){return function(a,b){return D(a)?b?Object.keys(a).map(function(b){return G(a[b],{$key:b})}):d(a):a}}),b.module("a8m.unique",[]).filter({unique:["$parse",v],uniq:["$parse",v]}),b.module("a8m.where",[]).filter("where",function(){return function(a,b){return z(b)?a:(a=D(a)?d(a):a,a.filter(function(a){return f(b,a)}))}}),b.module("a8m.xor",[]).filter("xor",["$parse",function(a){return function(b,c,e){function f(b,c){var d=a(e);return c.some(function(a){return e?I(d(a),d(b)):I(a,b)})}return e=e||!1,b=D(b)?d(b):b,c=D(c)?d(c):c,E(b)&&E(c)?b.concat(c).filter(function(a){return!(f(a,b)&&f(a,c))}):b}}]),b.module("a8m.math.abs",[]).filter("abs",function(){return function(a){return Math.abs(a)}}),b.module("a8m.math.byteFmt",[]).filter("byteFmt",function(){var a=[{str:"B",val:1024}];return["KB","MB","GB","TB","PB","EB","ZB","YB"].forEach(function(b,c){a.push({str:b,val:1024*a[c].val})}),function(b,c){if(C(c)&&isFinite(c)&&c%1===0&&c>=0&&C(b)&&isFinite(b)){for(var d=0;d<a.length-1&&b>=a[d].val;)d++;return b/=d>0?a[d-1].val:1,i(b,c)+" "+a[d].str}return"NaN"}}),b.module("a8m.math.degrees",[]).filter("degrees",function(){return function(a,b){if(C(b)&&isFinite(b)&&b%1===0&&b>=0&&C(a)&&isFinite(a)){var c=180*a/Math.PI;return Math.round(c*Math.pow(10,b))/Math.pow(10,b)}return"NaN"}}),b.module("a8m.math.kbFmt",[]).filter("kbFmt",function(){var a=[{str:"KB",val:1024}];return["MB","GB","TB","PB","EB","ZB","YB"].forEach(function(b,c){a.push({str:b,val:1024*a[c].val})}),function(b,c){if(C(c)&&isFinite(c)&&c%1===0&&c>=0&&C(b)&&isFinite(b)){for(var d=0;d<a.length-1&&b>=a[d].val;)d++;return b/=d>0?a[d-1].val:1,i(b,c)+" "+a[d].str}return"NaN"}}),b.module("a8m.math.max",[]).filter("max",["$parse",function(a){function b(b,c){var d=b.map(function(b){return a(c)(b)});return d.indexOf(Math.max.apply(Math,d))}return function(a,c){return E(a)?z(c)?Math.max.apply(Math,a):a[b(a,c)]:a}}]),b.module("a8m.math.min",[]).filter("min",["$parse",function(a){function b(b,c){var d=b.map(function(b){return a(c)(b)});return d.indexOf(Math.min.apply(Math,d))}return function(a,c){return E(a)?z(c)?Math.min.apply(Math,a):a[b(a,c)]:a}}]),b.module("a8m.math.percent",[]).filter("percent",function(){return function(a,b,c){var d=B(a)?Number(a):a;return b=b||100,c=c||!1,!C(d)||isNaN(d)?a:c?Math.round(d/b*100):d/b*100}}),b.module("a8m.math.radians",[]).filter("radians",function(){return function(a,b){if(C(b)&&isFinite(b)&&b%1===0&&b>=0&&C(a)&&isFinite(a)){var c=3.14159265359*a/180;return Math.round(c*Math.pow(10,b))/Math.pow(10,b)}return"NaN"}}),b.module("a8m.math.radix",[]).filter("radix",function(){return function(a,b){var c=/^[2-9]$|^[1-2]\d$|^3[0-6]$/;return C(a)&&c.test(b)?a.toString(b).toUpperCase():a}}),b.module("a8m.math.shortFmt",[]).filter("shortFmt",function(){return function(a,b){return C(b)&&isFinite(b)&&b%1===0&&b>=0&&C(a)&&isFinite(a)?a<1e3?""+a:a<1e6?i(a/1e3,b)+" K":a<1e9?i(a/1e6,b)+" M":i(a/1e9,b)+" B":"NaN"}}),b.module("a8m.math.sum",[]).filter("sum",function(){return function(a,b){return E(a)?a.reduce(function(a,b){return a+b},b||0):a}}),b.module("a8m.ends-with",[]).filter("endsWith",function(){return function(a,b,c){var d,e=c||!1;return!B(a)||z(b)?a:(a=e?a:a.toLowerCase(),d=a.length-b.length,a.indexOf(e?b:b.toLowerCase(),d)!==-1)}}),b.module("a8m.latinize",[]).filter("latinize",[function(){function a(a){return a.replace(/[^\u
387580000-\u007E]/g,function(a){return c[a]||a})}for(var b=[{base:"A",letters:"Aâ¶ï¼¡ÃÃÃẦẤẪẨÃÄÄáº°áº®áº´áº²È¦Ç ÃÇẢÃ
ǺÇÈÈẠẬẶá¸ÄȺⱯ"},{base:"AA",letters:"ê²"},{base:"AE",letters:"ÃǼǢ"},{base:"AO",letters:"ê´"},{base:"AU",letters:"ê¶"},{base:"AV",letters:"ê¸êº"},{base:"AY",letters:"ê¼"},{base:"B",letters:"Bâ·ï¼¢á¸á¸á¸ÉÆÆ"},{base:"C",letters:"Câ¸ï¼£ÄÄÄÄÃá¸ÆÈ»ê¾"},{base:"D",letters:"Dâ¹ï¼¤á¸Äá¸á¸á¸á¸ÄÆÆÆê¹"},{base:"DZ",letters:"DZÇ"},{base:"Dz",letters:"DzÇ
"},{base:"E",letters:"Eâºï¼¥ÃÃÃá»áº¾á»á»áº¼Äá¸á¸ÄÄÃẺÄÈÈẸá»È¨á¸Äá¸á¸ÆÆ"},{base:"F",letters:"Fâ»ï¼¦á¸Æê»"},{base:"G",letters:"Gâ¼ï¼§Ç´ÄḠÄÄ Ç¦Ä¢Ç¤Æê ê½ê¾"},{base:"H",letters:"Hâ½ï¼¨Ä¤á¸¢á¸¦ÈḤḨḪĦⱧⱵê"},{base:"I",letters:"Iâ¾ï¼©ÃÃÃĨĪĬİÃḮá»ÇÈÈá»Ä®á¸¬Æ"},{base:"J",letters:"Jâ¿ï¼ªÄ´É"},{base:"K",letters:"KâKḰǨḲĶḴÆâ±©êêêê¢"},{base:"L",letters:"LâLĿĹĽḶḸĻḼḺÅȽⱢⱠêêê"},{base:"LJ",letters:"Ç"},{base:"Lj",letters:"Ç"},{base:"M",letters:"Mâï¼á¸¾á¹á¹â±®Æ"},{base:"N",letters:"NâNǸÅÃá¹Åá¹Å
á¹á¹È Æêê¤"},{base:"NJ",letters:"Ç"},{base:"Nj",letters:"Ç"},{base:"O",letters:"OâOÃÃÃá»á»á»á»Ãá¹È¬á¹Åá¹á¹ÅȮȰÃȪá»ÅÇÈÈÆ á»á»á» á»á»¢á»á»ÇªÇ¬ÃÇ¾ÆÆêê"},{base:"OI",letters:"Æ¢"},{base:"OO",letters:"ê"},{base:"OU",letters:"È¢"},{base:"OE",letters:"ÂÅ"},{base:"oe",letters:"ÂÅ"},{base:"P",letters:"Pâ
ï¼°á¹á¹Æ¤â±£êêê"},{base:"Q",letters:"Qâï¼±êêÉ"},{base:"R",letters:"Râï¼²Åá¹ÅÈÈá¹á¹Åá¹ÉⱤêê¦ê"},{base:"S",letters:"Sâï¼³áºÅṤÅṠŠṦṢṨÈÅâ±¾ê¨ê"},{base:"T",letters:"TâTṪŤṬÈŢṰṮŦƬƮȾê"},{base:"TZ",letters:"ê¨"},{base:"U",letters:"Uâï¼µÃÃÃŨṸŪṺŬÃÇÇÇÇỦŮŰÇÈÈÆ¯á»ªá»¨á»®á»¬á»°á»¤á¹²Å²á¹¶á¹´É"},{base:"V",letters:"VâVṼṾƲêÉ
"},{base:"VY",letters:"ê "},{base:"W",letters:"Wâï¼·áºáºÅ´áºáºáºâ±²"},{base:"X",letters:"XâXáºáº"},{base:"Y",letters:"YâYỲÃŶỸȲáºÅ¸á»¶á»´Æ³ÉỾ"},{base:"Z",letters:"ZâZŹáºÅ»Å½áºáºÆµÈ¤â±¿â±«ê¢"},{base:"a",letters:"aâï½áºÃ áâầấẫẩãÄÄằắẵẳȧǡäÇảåǻÇÈÈạáºáº·á¸Ä
â±¥É"},{base:"aa",letters:"ê³"},{base:"ae",letters:"æǽǣ"},{base:"ao",letters:"êµ"},{base:"au",letters:"ê·"},{base:"av",letters:"ê¹ê»"},{base:"ay",letters:"ê½"},{base:"b",letters:"bâï½á¸á¸
á¸ÆÆÉ"},{base:"c",letters:"câï½ÄÄÄÄÃ§á¸ÆÈ¼ê¿â"},{base:"d",letters:"dâï½á¸Äá¸á¸á¸á¸ÄÆÉÉêº"},{base:"dz",letters:"dzÇ"},{base:"e",letters:"eâï½
èéêá»áº¿á»
á»áº½Äá¸á¸ÄÄëẻÄÈ
Èẹá»È©á¸Äá¸á¸ÉÉÇ"},{base:"f",letters:"fâï½á¸Æê¼"},{base:"g",letters:"gâï½ÇµÄḡÄÄ¡Ç§Ä£Ç¥É ê¡áµ¹ê¿"},{base:"h",letters:"hâï½Ä¥á¸£á¸§ÈḥḩḫáºÄ§â±¨â±¶É¥"},{base:"hv",letters:"Æ"},{base:"i",letters:"iâï½Ã¬ÃîĩīÄïḯá»ÇÈÈá»Ä¯á¸É¨Ä±"},{base:"j",letters:"jâï½ÄµÇ°É"},{base:"k",letters:"kâï½á¸±Ç©á¸³Ä·á¸µÆâ±ªêêê
ê£"},{base:"l",letters:"lâï½ÅÄºÄ¾á¸·á¸¹Ä¼á¸½á¸»Å¿ÅÆÉ«â±¡êêê"},{base:"lj",letters:"Ç"},{base:"m",letters:"mâï½á¸¿á¹á¹É±É¯"},{base:"n",letters:"nâï½Ç¹Åñá¹
Åá¹Åá¹á¹ÆÉ²Åêê¥"},{base:"nj",letters:"Ç"},{base:"o",letters:"oâï½Ã²Ã³Ã´á»á»á»á»Ãµá¹Èá¹Åá¹á¹Åȯȱöȫá»ÅÇÈÈÆ¡á»á»á»¡á»á»£á»á»Ç«ÇøǿÉêêɵ"},{base:"oi",letters:"Æ£"},{base:"ou",letters:"È£"},{base:"oo",letters:"ê"},{base:"p",letters:"pâï½á¹á¹Æ¥áµ½êêê"},{base:"q",letters:"qâ ï½Éêê"},{base:"r",letters:"râ¡ï½Åá¹ÅÈÈá¹á¹Åá¹Éɽêê§ê"},{base:"s",letters:"sâ¢ï½ÃÅá¹¥ÅṡšṧṣṩÈÅÈ¿ê©ê
áº"},{base:"t",letters:"tâ£ï½á¹«áºÅ¥á¹ÈţṱṯŧÆÊⱦê"},{base:"tz",letters:"ê©"},{base:"u",letters:"uâ¤ï½Ã¹ÃºÃ»Å©á¹¹Å«á¹»ÅüÇÇÇÇủůűÇÈÈÆ°á»«á»©á»¯á»á»±á»¥á¹³Å³á¹·á¹µÊ"},{base:"v",letters:"vâ¥ï½á¹½á¹¿ÊêÊ"},{base:"vy",letters:"ê¡"},{base:"w",letters:"wâ¦ï½áºáºÅµáºáº
áºáºâ±³"},{base:"x",letters:"xâ§ï½áºáº"},{base:"y",letters:"yâ¨ï½á»³Ã½Å·á»¹È³áºÃ¿á»·áºá»µÆ´Éỿ"},{base:"z",letters:"zâ©ï½ÅºáºÅ¼Å¾áºáºÆ¶È¥Éⱬê£"}],c={},d=0;d<b.length;d++)for(var e=b[d].letters.split(""),f=0;f<e.length;f++)c[e[f]]=b[d].base;return function(b){return B(b)?a(b):b}}]),b.module("a8m.ltrim",[]).filter("ltrim",function(){return function(a,b){var c=b||"\\s";return B(a)?a.replace(new RegExp("^"+c+"+"),""):a}}),b.module("a8m.match",[]).filter("match",function(){return function(a,b,c){var d=new RegExp(b,c);return B(a)?a.match(d):null}}),b.module("a8m.phoneUS",[]).filter("phoneUS",function(){return function(a){return a+="","("+a.slice(0,3)+") "+a.slice(3,6)+"-"+a.slice(6)}}),b.module("a8m.repeat",[]).filter("repeat",[function(){return function(a,b,c){var d=~~b;return B(a)&&d?w(a,--b,c||""):a}}]),b.module("a8m.rtrim",[]).filter("rtrim",function(){return function(a,b){var c=b||"\\s";return B(a)?a.replace(new RegExp(c+"+$"),""):a}}),b.module("a8m.slugify",[]).filter("slugify",[function(){return function(a,b){var c=z(b)?"-":b;return B(a)?a.toLowerCase().replace(/\s+/g,c):a}}]),b.module("a8m.split",[]).filter("split",function(){function a(a){return a.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")}return function(b,c,d){var f,g,h,i;return z(b)||!B(b)?null:(z(c)&&(c=""),isNaN(d)&&(d=0),f=new RegExp(a(c),"g"),g=b.match(f),e(g)||d>=g.length?[b]:0===d?b.split(c):(h=b.split(c),i=h.splice(0,d+1),h.unshift(i.join(c)),h))}}),b.module("a8m.starts-with",[]).filter("startsWith",function(){return function(a,b,c){var d=c||!1;return!B(a)||z(b)?a:(a=d?a:a.toLowerCase(),!a.indexOf(d?b:b.toLowerCase()))}}),b.module("a8m.stringular",[]).filter("stringular",function(){return function(a){var b=Array.prototype.slice.call(arguments,1
38758);return a.replace(/{(\d+)}/g,function(a,c){return z(b[c])?a:b[c]})}}),b.module("a8m.strip-tags",[]).filter("stripTags",function(){return function(a){return B(a)?a.replace(/<\S[^><]*>/g,""):a}}),b.module("a8m.test",[]).filter("test",function(){return function(a,b,c){var d=new RegExp(b,c);return B(a)?d.test(a):a}}),b.module("a8m.trim",[]).filter("trim",function(){return function(a,b){var c=b||"\\s";return B(a)?a.replace(new RegExp("^"+c+"+|"+c+"+$","g"),""):a}}),b.module("a8m.truncate",[]).filter("truncate",function(){return function(a,b,c,d){return b=z(b)?a.length:b,d=d||!1,c=c||"",!B(a)||a.length<=b?a:a.substring(0,d?a.indexOf(" ",b)===-1?a.length:a.indexOf(" ",b):b)+c}}),b.module("a8m.ucfirst",[]).filter({ucfirst:x,titleize:x}),b.module("a8m.uri-component-encode",[]).filter("uriComponentEncode",["$window",function(a){return function(b){return B(b)?a.encodeURIComponent(b):b}}]),b.module("a8m.uri-encode",[]).filter("uriEncode",["$window",function(a){return function(b){return B(b)?a.encodeURI(b):b}}]),b.module("a8m.wrap",[]).filter("wrap",function(){return function(a,b,c){return B(a)&&y(b)?[b,a,c||b].join(""):a}}),b.module("a8m.filter-watcher",[]).provider("filterWatcher",function(){this.$get=["$window","$rootScope",function(a,b){function c(b,c){function d(){var b=[];return function(c,d){if(D(d)&&!e(d)){if(~b.indexOf(d))return"[Circular]";b.push(d)}return a==d?"$WINDOW":a.document==d?"$DOCUMENT":k(d)?"$SCOPE":d}}return[b,JSON.stringify(c,d())].join("#").replace(/"/g,"")}function d(a){var b=a.targetScope.$id;F(l[b],function(a){delete j[a]}),delete l[b]}function f(){m(function(){b.$$phase||(j={})},2e3)}function g(a,b){var c=a.$id;return z(l[c])&&(a.$on("$destroy",d),l[c]=[]),l[c].push(b)}function h(a,b){var d=c(a,b);return j[d]}function i(a,b,d,e){var h=c(a,b);return j[h]=e,k(d)?g(d,h):f(),e}var j={},l={},m=a.setTimeout;return{isMemoized:h,memoize:i}}]}),b.module("angular.filter",["a8m.ucfirst","a8m.uri-encode","a8m.uri-component-encode","a8m.slugify","a8m.latinize","a8m.strip-tags","a8m.stringular","a8m.truncate","a8m.starts-with","a8m.ends-with","a8m.wrap","a8m.trim","a8m.ltrim","a8m.rtrim","a8m.repeat","a8m.test","a8m.match","a8m.split","a8m.phoneUS","a8m.to-array","a8m.concat","a8m.contains","a8m.unique","a8m.is-empty","a8m.after","a8m.after-where","a8m.before","a8m.before-where","a8m.defaults","a8m.where","a8m.reverse","a8m.remove","a8m.remove-with","a8m.group-by","a8m.count-by","a8m.chunk-by","a8m.search-field","a8m.fuzzy-by","a8m.fuzzy","a8m.omit","a8m.pick","a8m.every","a8m.filter-by","a8m.xor","a8m.map","a8m.first","a8m.last","a8m.flatten","a8m.join","a8m.range","a8m.math.max","a8m.math.min","a8m.math.abs","a8m.math.percent","a8m.math.radix","a8m.math.sum","a8m.math.degrees","a8m.math.radians","a8m.math.byteFmt","a8m.math.kbFmt","a8m.math.shortFmt","a8m.angular","a8m.conditions","a8m.is-null","a8m.filter-watcher"])}(window,window.angular); 38759//# sourceMappingURL=angular.js.map
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.