2016-03-28 10:46:51 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ license AngularJS v1 . 3.20
2016-04-18 12:34:29 +00:00
* ( c ) 2010 - 2014 Google , Inc . http : //angularjs.org
2016-03-28 10:46:51 +00:00
* License : MIT
2018-06-27 02:29:44 -04:00
*
* Permission is hereby granted , free of charge , to any person obtaining a copy
* of this software and associated documentation files ( the "Software" ) , to deal
* in the Software without restriction , including without limitation the rights
* to use , copy , modify , merge , publish , distribute , sublicense , and / or sell
* copies of the Software , and to permit persons to whom the Software is
* furnished to do so , subject to the following conditions :
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software .
*
* THE SOFTWARE IS PROVIDED "AS IS" , WITHOUT WARRANTY OF ANY KIND , EXPRESS OR
* IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY ,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT . IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM , DAMAGES OR OTHER
* LIABILITY , WHETHER IN AN ACTION OF CONTRACT , TORT OR OTHERWISE , ARISING FROM ,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE .
*
2016-03-28 10:46:51 +00:00
* /
( function ( window , document , undefined ) { 'use strict' ;
/ * *
* @ description
*
* This object provides a utility for producing rich Error messages within
* Angular . It can be called as follows :
*
* var exampleMinErr = minErr ( 'example' ) ;
* throw exampleMinErr ( 'one' , 'This {0} is {1}' , foo , bar ) ;
*
* The above creates an instance of minErr in the example namespace . The
* resulting error will have a namespaced error code of example . one . The
* resulting error will replace { 0 } with the value of foo , and { 1 } with the
* value of bar . The object is not restricted in the number of arguments it can
* take .
*
* If fewer arguments are specified than necessary for interpolation , the extra
* interpolation markers will be preserved in the final string .
*
* Since data will be parsed statically during a build step , some restrictions
* are applied with respect to how minErr instances are created and called .
* Instances should have names of the form namespaceMinErr for a minErr created
* using minErr ( 'namespace' ) . Error codes , namespaces and template strings
* should all be static strings , not variables or general expressions .
*
* @ param { string } module The namespace to use for the new minErr instance .
2018-05-05 12:13:16 +02:00
* @ param { function } ErrorConstructor Custom error constructor to be instantiated when returning
* error from returned function , for cases when a particular type of error is useful .
* @ returns { function ( code : string , template : string , ... templateArgs ) : Error } minErr instance
2016-03-28 10:46:51 +00:00
* /
2018-05-05 12:13:16 +02:00
function minErr ( module , ErrorConstructor ) {
ErrorConstructor = ErrorConstructor || Error ;
return function ( ) {
2016-04-18 12:34:29 +00:00
var code = arguments [ 0 ] ,
prefix = '[' + ( module ? module + ':' : '' ) + code + '] ' ,
template = arguments [ 1 ] ,
templateArgs = arguments ,
2018-05-05 12:13:16 +02:00
2016-04-18 12:34:29 +00:00
message , i ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
message = prefix + template . replace ( /\{\d+\}/g , function ( match ) {
2016-04-18 12:34:29 +00:00
var index = + match . slice ( 1 , - 1 ) , arg ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( index + 2 < templateArgs . length ) {
2018-05-05 12:13:16 +02:00
return toDebugString ( templateArgs [ index + 2 ] ) ;
2016-03-28 10:46:51 +00:00
}
return match ;
} ) ;
2018-05-05 12:13:16 +02:00
message = message + '\nhttp://errors.angularjs.org/1.3.20/' +
2016-03-28 10:46:51 +00:00
( module ? module + '/' : '' ) + code ;
2016-04-18 12:34:29 +00:00
for ( i = 2 ; i < arguments . length ; i ++ ) {
2018-05-05 12:13:16 +02:00
message = message + ( i == 2 ? '?' : '&' ) + 'p' + ( i - 2 ) + '=' +
encodeURIComponent ( toDebugString ( arguments [ i ] ) ) ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
return new ErrorConstructor ( message ) ;
2016-03-28 10:46:51 +00:00
} ;
}
/* We need to tell jshint what variables are being exported */
2018-05-05 12:13:16 +02:00
/ * g l o b a l a n g u l a r : t r u e ,
msie : true ,
jqLite : true ,
jQuery : true ,
slice : true ,
splice : true ,
push : true ,
toString : true ,
ngMinErr : true ,
angularModule : true ,
uid : true ,
REGEX _STRING _REGEXP : true ,
VALIDITY _STATE _PROPERTY : true ,
lowercase : true ,
uppercase : true ,
manualLowercase : true ,
manualUppercase : true ,
nodeName _ : true ,
isArrayLike : true ,
forEach : true ,
sortedKeys : true ,
forEachSorted : true ,
reverseParams : true ,
nextUid : true ,
setHashKey : true ,
extend : true ,
int : true ,
inherit : true ,
noop : true ,
identity : true ,
valueFn : true ,
isUndefined : true ,
isDefined : true ,
isObject : true ,
isString : true ,
isNumber : true ,
isDate : true ,
isArray : true ,
isFunction : true ,
isRegExp : true ,
isWindow : true ,
isScope : true ,
isFile : true ,
isFormData : true ,
isBlob : true ,
isBoolean : true ,
isPromiseLike : true ,
trim : true ,
escapeForRegexp : true ,
isElement : true ,
makeMap : true ,
includes : true ,
arrayRemove : true ,
copy : true ,
shallowCopy : true ,
equals : true ,
csp : true ,
concat : true ,
sliceArgs : true ,
bind : true ,
toJsonReplacer : true ,
toJson : true ,
fromJson : true ,
startingTag : true ,
tryDecodeURIComponent : true ,
parseKeyValue : true ,
toKeyValue : true ,
encodeUriSegment : true ,
encodeUriQuery : true ,
angularInit : true ,
bootstrap : true ,
getTestability : true ,
snake _case : true ,
bindJQuery : true ,
assertArg : true ,
assertArgFn : true ,
assertNotHasOwnProperty : true ,
getter : true ,
getBlockNodes : true ,
hasOwnProperty : true ,
createMap : true ,
NODE _TYPE _ELEMENT : true ,
NODE _TYPE _ATTRIBUTE : true ,
NODE _TYPE _TEXT : true ,
NODE _TYPE _COMMENT : true ,
NODE _TYPE _DOCUMENT : true ,
NODE _TYPE _DOCUMENT _FRAGMENT : true ,
2016-03-28 10:46:51 +00:00
* /
////////////////////////////////////
2018-05-05 12:13:16 +02:00
/ * *
* @ ngdoc module
* @ name ng
* @ module ng
* @ description
*
* # ng ( core module )
* The ng module is loaded by default when an AngularJS application is started . The module itself
* contains the essential components for an AngularJS application to function . The table below
* lists a high level breakdown of each of the services / factories , filters , directives and testing
* components available within this core module .
*
* < div doc - module - components = "ng" > < / d i v >
* /
var REGEX _STRING _REGEXP = /^\/(.+)\/([a-z]*)$/ ;
// The name of a form control's ValidityState property.
// This is used so that it's possible for internal tests to create mock ValidityStates.
var VALIDITY _STATE _PROPERTY = 'validity' ;
2016-03-28 10:46:51 +00:00
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name angular . lowercase
2018-05-05 12:13:16 +02:00
* @ module ng
* @ kind function
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ description Converts the specified string to lowercase .
* @ param { string } string String to be converted to lowercase .
* @ returns { string } Lowercased string .
2016-03-28 10:46:51 +00:00
* /
2018-05-05 12:13:16 +02:00
var lowercase = function ( string ) { return isString ( string ) ? string . toLowerCase ( ) : string ; } ;
var hasOwnProperty = Object . prototype . hasOwnProperty ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
/ * *
* @ ngdoc function
* @ name angular . uppercase
2018-05-05 12:13:16 +02:00
* @ module ng
* @ kind function
2016-04-18 12:34:29 +00:00
*
* @ description Converts the specified string to uppercase .
* @ param { string } string String to be converted to uppercase .
* @ returns { string } Uppercased string .
* /
2018-05-05 12:13:16 +02:00
var uppercase = function ( string ) { return isString ( string ) ? string . toUpperCase ( ) : string ; } ;
2016-03-28 10:46:51 +00:00
var manualLowercase = function ( s ) {
/* jshint bitwise: false */
return isString ( s )
? s . replace ( /[A-Z]/g , function ( ch ) { return String . fromCharCode ( ch . charCodeAt ( 0 ) | 32 ) ; } )
: s ;
} ;
var manualUppercase = function ( s ) {
/* jshint bitwise: false */
return isString ( s )
? s . replace ( /[a-z]/g , function ( ch ) { return String . fromCharCode ( ch . charCodeAt ( 0 ) & ~ 32 ) ; } )
: s ;
} ;
// String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish
// locale, for this reason we need to detect this case and redefine lowercase/uppercase methods
2016-04-18 12:34:29 +00:00
// with correct but slower alternatives.
2016-03-28 10:46:51 +00:00
if ( 'i' !== 'I' . toLowerCase ( ) ) {
lowercase = manualLowercase ;
uppercase = manualUppercase ;
}
2018-05-05 12:13:16 +02:00
var
msie , // holds major version number for IE, or NaN if UA is not IE.
2016-03-28 10:46:51 +00:00
jqLite , // delay binding since jQuery could be loaded after us.
jQuery , // delay binding
slice = [ ] . slice ,
2018-05-05 12:13:16 +02:00
splice = [ ] . splice ,
2016-03-28 10:46:51 +00:00
push = [ ] . push ,
toString = Object . prototype . toString ,
ngMinErr = minErr ( 'ng' ) ,
/** @name angular */
angular = window . angular || ( window . angular = { } ) ,
angularModule ,
2018-05-05 12:13:16 +02:00
uid = 0 ;
2016-03-28 10:46:51 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* documentMode is an IE - only property
* http : //msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx
2016-03-28 10:46:51 +00:00
* /
2018-05-05 12:13:16 +02:00
msie = document . documentMode ;
2016-03-28 10:46:51 +00:00
/ * *
* @ private
* @ param { * } obj
* @ return { boolean } Returns true if ` obj ` is an array or array - like object ( NodeList , Arguments ,
* String ... )
* /
function isArrayLike ( obj ) {
2016-04-18 12:34:29 +00:00
if ( obj == null || isWindow ( obj ) ) {
return false ;
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
// Support: iOS 8.2 (not reproducible in simulator)
// "length" in obj used to prevent JIT error (gh-11508)
var length = "length" in Object ( obj ) && obj . length ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
if ( obj . nodeType === NODE _TYPE _ELEMENT && length ) {
2016-04-18 12:34:29 +00:00
return true ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
return isString ( obj ) || isArray ( obj ) || length === 0 ||
typeof length === 'number' && length > 0 && ( length - 1 ) in obj ;
2016-03-28 10:46:51 +00:00
}
/ * *
* @ ngdoc function
* @ name angular . forEach
2018-05-05 12:13:16 +02:00
* @ module ng
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Invokes the ` iterator ` function once for each item in ` obj ` collection , which can be either an
2018-05-05 12:13:16 +02:00
* object or an array . The ` iterator ` function is invoked with ` iterator(value, key, obj) ` , where ` value `
* is the value of an object property or an array element , ` key ` is the object property key or
* array element index and obj is the ` obj ` itself . Specifying a ` context ` for the function is optional .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* It is worth noting that ` .forEach ` does not iterate over inherited properties because it filters
2016-03-28 10:46:51 +00:00
* using the ` hasOwnProperty ` method .
*
2018-05-05 12:13:16 +02:00
* Unlike ES262 ' s
* [ Array . prototype . forEach ] ( http : //www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18),
* Providing 'undefined' or 'null' values for ` obj ` will not throw a TypeError , but rather just
* return the value provided .
*
` ` ` js
2016-03-28 10:46:51 +00:00
var values = { name : 'misko' , gender : 'male' } ;
var log = [ ] ;
2018-05-05 12:13:16 +02:00
angular . forEach ( values , function ( value , key ) {
2016-03-28 10:46:51 +00:00
this . push ( key + ': ' + value ) ;
} , log ) ;
2018-05-05 12:13:16 +02:00
expect ( log ) . toEqual ( [ 'name: misko' , 'gender: male' ] ) ;
` ` `
2016-03-28 10:46:51 +00:00
*
* @ param { Object | Array } obj Object to iterate over .
* @ param { Function } iterator Iterator function .
* @ param { Object = } context Object to become context ( ` this ` ) for the iterator function .
* @ returns { Object | Array } Reference to ` obj ` .
* /
2018-05-05 12:13:16 +02:00
2016-03-28 10:46:51 +00:00
function forEach ( obj , iterator , context ) {
2018-05-05 12:13:16 +02:00
var key , length ;
2016-03-28 10:46:51 +00:00
if ( obj ) {
2018-05-05 12:13:16 +02:00
if ( isFunction ( obj ) ) {
2016-03-28 10:46:51 +00:00
for ( key in obj ) {
// Need to check if hasOwnProperty exists,
// as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function
if ( key != 'prototype' && key != 'length' && key != 'name' && ( ! obj . hasOwnProperty || obj . hasOwnProperty ( key ) ) ) {
2018-05-05 12:13:16 +02:00
iterator . call ( context , obj [ key ] , key , obj ) ;
}
}
} else if ( isArray ( obj ) || isArrayLike ( obj ) ) {
var isPrimitive = typeof obj !== 'object' ;
for ( key = 0 , length = obj . length ; key < length ; key ++ ) {
if ( isPrimitive || key in obj ) {
iterator . call ( context , obj [ key ] , key , obj ) ;
2016-03-28 10:46:51 +00:00
}
}
} else if ( obj . forEach && obj . forEach !== forEach ) {
2018-05-05 12:13:16 +02:00
obj . forEach ( iterator , context , obj ) ;
2016-03-28 10:46:51 +00:00
} else {
for ( key in obj ) {
2016-04-18 12:34:29 +00:00
if ( obj . hasOwnProperty ( key ) ) {
2018-05-05 12:13:16 +02:00
iterator . call ( context , obj [ key ] , key , obj ) ;
2016-03-28 10:46:51 +00:00
}
}
}
}
return obj ;
}
2016-04-18 12:34:29 +00:00
function sortedKeys ( obj ) {
2018-05-05 12:13:16 +02:00
return Object . keys ( obj ) . sort ( ) ;
2016-04-18 12:34:29 +00:00
}
2016-03-28 10:46:51 +00:00
function forEachSorted ( obj , iterator , context ) {
2016-04-18 12:34:29 +00:00
var keys = sortedKeys ( obj ) ;
2018-05-05 12:13:16 +02:00
for ( var i = 0 ; i < keys . length ; i ++ ) {
2016-03-28 10:46:51 +00:00
iterator . call ( context , obj [ keys [ i ] ] , keys [ i ] ) ;
}
return keys ;
}
/ * *
* when using forEach the params are value , key , but it is often useful to have key , value .
* @ param { function ( string , * ) } iteratorFn
* @ returns { function ( * , string ) }
* /
function reverseParams ( iteratorFn ) {
2016-04-18 12:34:29 +00:00
return function ( value , key ) { iteratorFn ( key , value ) ; } ;
2016-03-28 10:46:51 +00:00
}
/ * *
2018-05-05 12:13:16 +02:00
* A consistent way of creating unique IDs in angular .
*
* Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before
* we hit number precision issues in JavaScript .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* Math . pow ( 2 , 53 ) / 60 / 60 / 24 / 365 / 10 = 28.6 M
*
* @ returns { number } an unique alpha - numeric string
2016-03-28 10:46:51 +00:00
* /
function nextUid ( ) {
2018-05-05 12:13:16 +02:00
return ++ uid ;
2016-03-28 10:46:51 +00:00
}
/ * *
* Set or clear the hashkey for an object .
* @ param obj object
* @ param h the hashkey ( ! truthy to delete the hashkey )
* /
function setHashKey ( obj , h ) {
if ( h ) {
obj . $$hashKey = h ;
2018-05-05 12:13:16 +02:00
} else {
2016-04-18 12:34:29 +00:00
delete obj . $$hashKey ;
2016-03-28 10:46:51 +00:00
}
}
/ * *
* @ ngdoc function
* @ name angular . extend
2018-05-05 12:13:16 +02:00
* @ module ng
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
2018-05-05 12:13:16 +02:00
* Extends the destination object ` dst ` by copying own enumerable properties from the ` src ` object ( s )
* to ` dst ` . You can specify multiple ` src ` objects . If you want to preserve original objects , you can do so
* by passing an empty object as the target : ` var object = angular.extend({}, object1, object2) ` .
* Note : Keep in mind that ` angular.extend ` does not support recursive merge ( deep copy ) .
2016-03-28 10:46:51 +00:00
*
* @ param { Object } dst Destination object .
* @ param { ... Object } src Source object ( s ) .
* @ returns { Object } Reference to ` dst ` .
* /
function extend ( dst ) {
2016-04-18 12:34:29 +00:00
var h = dst . $$hashKey ;
2018-05-05 12:13:16 +02:00
for ( var i = 1 , ii = arguments . length ; i < ii ; i ++ ) {
var obj = arguments [ i ] ;
if ( obj ) {
var keys = Object . keys ( obj ) ;
for ( var j = 0 , jj = keys . length ; j < jj ; j ++ ) {
var key = keys [ j ] ;
dst [ key ] = obj [ key ] ;
}
2016-04-18 12:34:29 +00:00
}
2018-05-05 12:13:16 +02:00
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
setHashKey ( dst , h ) ;
2016-04-18 12:34:29 +00:00
return dst ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
function int ( str ) {
2016-03-28 10:46:51 +00:00
return parseInt ( str , 10 ) ;
}
function inherit ( parent , extra ) {
2018-05-05 12:13:16 +02:00
return extend ( Object . create ( parent ) , extra ) ;
2016-03-28 10:46:51 +00:00
}
/ * *
* @ ngdoc function
* @ name angular . noop
2018-05-05 12:13:16 +02:00
* @ module ng
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* A function that performs no operations . This function can be useful when writing code in the
* functional style .
2018-05-05 12:13:16 +02:00
` ` ` js
2016-03-28 10:46:51 +00:00
function foo ( callback ) {
var result = calculateResult ( ) ;
( callback || angular . noop ) ( result ) ;
}
2018-05-05 12:13:16 +02:00
` ` `
2016-03-28 10:46:51 +00:00
* /
function noop ( ) { }
noop . $inject = [ ] ;
/ * *
* @ ngdoc function
* @ name angular . identity
2018-05-05 12:13:16 +02:00
* @ module ng
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* A function that returns its first argument . This function is useful when writing code in the
* functional style .
*
2018-05-05 12:13:16 +02:00
` ` ` js
2016-03-28 10:46:51 +00:00
function transformer ( transformationFn , value ) {
return ( transformationFn || angular . identity ) ( value ) ;
} ;
2018-05-05 12:13:16 +02:00
` ` `
* @ param { * } value to be returned .
* @ returns { * } the value passed in .
2016-03-28 10:46:51 +00:00
* /
function identity ( $ ) { return $ ; }
identity . $inject = [ ] ;
2016-04-18 12:34:29 +00:00
function valueFn ( value ) { return function ( ) { return value ; } ; }
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc function
* @ name angular . isUndefined
2018-05-05 12:13:16 +02:00
* @ module ng
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Determines if a reference is undefined .
*
* @ param { * } value Reference to check .
* @ returns { boolean } True if ` value ` is undefined .
* /
2018-05-05 12:13:16 +02:00
function isUndefined ( value ) { return typeof value === 'undefined' ; }
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc function
* @ name angular . isDefined
2018-05-05 12:13:16 +02:00
* @ module ng
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Determines if a reference is defined .
*
* @ param { * } value Reference to check .
* @ returns { boolean } True if ` value ` is defined .
* /
2018-05-05 12:13:16 +02:00
function isDefined ( value ) { return typeof value !== 'undefined' ; }
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc function
* @ name angular . isObject
2018-05-05 12:13:16 +02:00
* @ module ng
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Determines if a reference is an ` Object ` . Unlike ` typeof ` in JavaScript , ` null ` s are not
2018-05-05 12:13:16 +02:00
* considered to be objects . Note that JavaScript arrays are objects .
2016-03-28 10:46:51 +00:00
*
* @ param { * } value Reference to check .
* @ returns { boolean } True if ` value ` is an ` Object ` but not ` null ` .
* /
2018-05-05 12:13:16 +02:00
function isObject ( value ) {
// http://jsperf.com/isobject4
return value !== null && typeof value === 'object' ;
}
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc function
* @ name angular . isString
2018-05-05 12:13:16 +02:00
* @ module ng
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Determines if a reference is a ` String ` .
*
* @ param { * } value Reference to check .
* @ returns { boolean } True if ` value ` is a ` String ` .
* /
2018-05-05 12:13:16 +02:00
function isString ( value ) { return typeof value === 'string' ; }
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc function
* @ name angular . isNumber
2018-05-05 12:13:16 +02:00
* @ module ng
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Determines if a reference is a ` Number ` .
*
2018-05-05 12:13:16 +02:00
* This includes the "special" numbers ` NaN ` , ` +Infinity ` and ` -Infinity ` .
*
* If you wish to exclude these then you can use the native
* [ ` isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite)
* method .
*
2016-03-28 10:46:51 +00:00
* @ param { * } value Reference to check .
* @ returns { boolean } True if ` value ` is a ` Number ` .
* /
2018-05-05 12:13:16 +02:00
function isNumber ( value ) { return typeof value === 'number' ; }
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc function
* @ name angular . isDate
2018-05-05 12:13:16 +02:00
* @ module ng
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Determines if a value is a date .
*
* @ param { * } value Reference to check .
* @ returns { boolean } True if ` value ` is a ` Date ` .
* /
2018-05-05 12:13:16 +02:00
function isDate ( value ) {
2016-03-28 10:46:51 +00:00
return toString . call ( value ) === '[object Date]' ;
}
/ * *
* @ ngdoc function
* @ name angular . isArray
2018-05-05 12:13:16 +02:00
* @ module ng
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Determines if a reference is an ` Array ` .
*
* @ param { * } value Reference to check .
* @ returns { boolean } True if ` value ` is an ` Array ` .
* /
2018-05-05 12:13:16 +02:00
var isArray = Array . isArray ;
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc function
* @ name angular . isFunction
2018-05-05 12:13:16 +02:00
* @ module ng
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Determines if a reference is a ` Function ` .
*
* @ param { * } value Reference to check .
* @ returns { boolean } True if ` value ` is a ` Function ` .
* /
2018-05-05 12:13:16 +02:00
function isFunction ( value ) { return typeof value === 'function' ; }
2016-03-28 10:46:51 +00:00
/ * *
* Determines if a value is a regular expression object .
*
* @ private
* @ param { * } value Reference to check .
* @ returns { boolean } True if ` value ` is a ` RegExp ` .
* /
function isRegExp ( value ) {
return toString . call ( value ) === '[object RegExp]' ;
}
/ * *
* Checks if ` obj ` is a window object .
*
* @ private
* @ param { * } obj Object to check
* @ returns { boolean } True if ` obj ` is a window obj .
* /
function isWindow ( obj ) {
2018-05-05 12:13:16 +02:00
return obj && obj . window === obj ;
2016-03-28 10:46:51 +00:00
}
function isScope ( obj ) {
return obj && obj . $evalAsync && obj . $watch ;
}
function isFile ( obj ) {
return toString . call ( obj ) === '[object File]' ;
}
2018-05-05 12:13:16 +02:00
function isFormData ( obj ) {
return toString . call ( obj ) === '[object FormData]' ;
}
function isBlob ( obj ) {
return toString . call ( obj ) === '[object Blob]' ;
}
2016-03-28 10:46:51 +00:00
function isBoolean ( value ) {
return typeof value === 'boolean' ;
}
2018-05-05 12:13:16 +02:00
function isPromiseLike ( obj ) {
return obj && isFunction ( obj . then ) ;
}
var trim = function ( value ) {
return isString ( value ) ? value . trim ( ) : value ;
} ;
// Copied from:
// http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021
// Prereq: s is a string.
var escapeForRegexp = function ( s ) {
return s . replace ( /([-()\[\]{}+?*.$\^|,:#<!\\])/g , '\\$1' ) .
replace ( /\x08/g , '\\x08' ) ;
} ;
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc function
* @ name angular . isElement
2018-05-05 12:13:16 +02:00
* @ module ng
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Determines if a reference is a DOM element ( or wrapped jQuery element ) .
*
* @ param { * } value Reference to check .
* @ returns { boolean } True if ` value ` is a DOM element ( or wrapped jQuery element ) .
* /
function isElement ( node ) {
return ! ! ( node &&
( node . nodeName // we are a direct element
2018-05-05 12:13:16 +02:00
|| ( node . prop && node . attr && node . find ) ) ) ; // we have an on and find method part of jQuery API
2016-03-28 10:46:51 +00:00
}
/ * *
* @ param str 'key1,key2,...'
* @ returns { object } in the form of { key1 : true , key2 : true , ... }
* /
2018-05-05 12:13:16 +02:00
function makeMap ( str ) {
2016-04-18 12:34:29 +00:00
var obj = { } , items = str . split ( "," ) , i ;
2018-05-05 12:13:16 +02:00
for ( i = 0 ; i < items . length ; i ++ )
obj [ items [ i ] ] = true ;
2016-03-28 10:46:51 +00:00
return obj ;
}
2018-05-05 12:13:16 +02:00
function nodeName _ ( element ) {
return lowercase ( element . nodeName || ( element [ 0 ] && element [ 0 ] . nodeName ) ) ;
2016-03-28 10:46:51 +00:00
}
function includes ( array , obj ) {
2018-05-05 12:13:16 +02:00
return Array . prototype . indexOf . call ( array , obj ) != - 1 ;
2016-03-28 10:46:51 +00:00
}
function arrayRemove ( array , value ) {
2018-05-05 12:13:16 +02:00
var index = array . indexOf ( value ) ;
if ( index >= 0 )
2016-03-28 10:46:51 +00:00
array . splice ( index , 1 ) ;
2016-04-18 12:34:29 +00:00
return value ;
}
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc function
* @ name angular . copy
2018-05-05 12:13:16 +02:00
* @ module ng
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Creates a deep copy of ` source ` , which should be an object or an array .
*
* * If no destination is supplied , a copy of the object or array is created .
2018-05-05 12:13:16 +02:00
* * If a destination is provided , all of its elements ( for arrays ) or properties ( for objects )
2016-03-28 10:46:51 +00:00
* are deleted and then all elements / properties from the source are copied to it .
* * If ` source ` is not an object or array ( inc . ` null ` and ` undefined ` ) , ` source ` is returned .
* * If ` source ` is identical to 'destination' an exception will be thrown .
*
* @ param { * } source The source that will be used to make a copy .
* Can be any type , including primitives , ` null ` , and ` undefined ` .
* @ param { ( Object | Array ) = } destination Destination into which the source is copied . If
* provided , must be of the same type as ` source ` .
* @ returns { * } The copy or updated ` destination ` , if ` destination ` was specified .
*
* @ example
2018-05-05 12:13:16 +02:00
< example module = "copyExample" >
< file name = "index.html" >
< div ng - controller = "ExampleController" >
2016-03-28 10:46:51 +00:00
< form novalidate class = "simple-form" >
Name : < input type = "text" ng - model = "user.name" / > < br / >
E - mail : < input type = "email" ng - model = "user.email" / > < br / >
Gender : < input type = "radio" ng - model = "user.gender" value = "male" / > male
< input type = "radio" ng - model = "user.gender" value = "female" / > female < br / >
< button ng - click = "reset()" > RESET < / b u t t o n >
< button ng - click = "update(user)" > SAVE < / b u t t o n >
< / f o r m >
< pre > form = { { user | json } } < / p r e >
< pre > master = { { master | json } } < / p r e >
< / d i v >
< script >
2018-05-05 12:13:16 +02:00
angular . module ( 'copyExample' , [ ] )
. controller ( 'ExampleController' , [ '$scope' , function ( $scope ) {
$scope . master = { } ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
$scope . update = function ( user ) {
// Example with 1 argument
$scope . master = angular . copy ( user ) ;
} ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
$scope . reset = function ( ) {
// Example with 2 arguments
angular . copy ( $scope . master , $scope . user ) ;
} ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
$scope . reset ( ) ;
} ] ) ;
2016-03-28 10:46:51 +00:00
< / s c r i p t >
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
2018-05-05 12:13:16 +02:00
function copy ( source , destination , stackSource , stackDest ) {
2016-04-18 12:34:29 +00:00
if ( isWindow ( source ) || isScope ( source ) ) {
throw ngMinErr ( 'cpws' ,
"Can't copy! Making copies of Window or Scope instances is not supported." ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
if ( ! destination ) {
destination = source ;
if ( source ) {
if ( isArray ( source ) ) {
2018-05-05 12:13:16 +02:00
destination = copy ( source , [ ] , stackSource , stackDest ) ;
2016-04-18 12:34:29 +00:00
} else if ( isDate ( source ) ) {
destination = new Date ( source . getTime ( ) ) ;
} else if ( isRegExp ( source ) ) {
2018-05-05 12:13:16 +02:00
destination = new RegExp ( source . source , source . toString ( ) . match ( /[^\/]*$/ ) [ 0 ] ) ;
destination . lastIndex = source . lastIndex ;
2016-04-18 12:34:29 +00:00
} else if ( isObject ( source ) ) {
2018-05-05 12:13:16 +02:00
var emptyObject = Object . create ( Object . getPrototypeOf ( source ) ) ;
destination = copy ( source , emptyObject , stackSource , stackDest ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
}
} else {
if ( source === destination ) throw ngMinErr ( 'cpi' ,
"Can't copy! Source and destination are identical." ) ;
2018-05-05 12:13:16 +02:00
stackSource = stackSource || [ ] ;
stackDest = stackDest || [ ] ;
if ( isObject ( source ) ) {
var index = stackSource . indexOf ( source ) ;
if ( index !== - 1 ) return stackDest [ index ] ;
stackSource . push ( source ) ;
stackDest . push ( destination ) ;
}
var result ;
2016-04-18 12:34:29 +00:00
if ( isArray ( source ) ) {
destination . length = 0 ;
2018-05-05 12:13:16 +02:00
for ( var i = 0 ; i < source . length ; i ++ ) {
result = copy ( source [ i ] , null , stackSource , stackDest ) ;
if ( isObject ( source [ i ] ) ) {
stackSource . push ( source [ i ] ) ;
stackDest . push ( result ) ;
}
destination . push ( result ) ;
2016-03-28 10:46:51 +00:00
}
} else {
2016-04-18 12:34:29 +00:00
var h = destination . $$hashKey ;
2018-05-05 12:13:16 +02:00
if ( isArray ( destination ) ) {
destination . length = 0 ;
} else {
forEach ( destination , function ( value , key ) {
delete destination [ key ] ;
} ) ;
}
for ( var key in source ) {
if ( source . hasOwnProperty ( key ) ) {
result = copy ( source [ key ] , null , stackSource , stackDest ) ;
if ( isObject ( source [ key ] ) ) {
stackSource . push ( source [ key ] ) ;
stackDest . push ( result ) ;
}
destination [ key ] = result ;
}
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
setHashKey ( destination , h ) ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
return destination ;
2016-03-28 10:46:51 +00:00
}
/ * *
2018-05-05 12:13:16 +02:00
* Creates a shallow copy of an object , an array or a primitive .
*
* Assumes that there are no proto properties for objects .
2016-03-28 10:46:51 +00:00
* /
function shallowCopy ( src , dst ) {
2018-05-05 12:13:16 +02:00
if ( isArray ( src ) ) {
dst = dst || [ ] ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
for ( var i = 0 , ii = src . length ; i < ii ; i ++ ) {
dst [ i ] = src [ i ] ;
}
} else if ( isObject ( src ) ) {
dst = dst || { } ;
for ( var key in src ) {
if ( ! ( key . charAt ( 0 ) === '$' && key . charAt ( 1 ) === '$' ) ) {
dst [ key ] = src [ key ] ;
}
2016-03-28 10:46:51 +00:00
}
}
2018-05-05 12:13:16 +02:00
return dst || src ;
2016-03-28 10:46:51 +00:00
}
/ * *
* @ ngdoc function
* @ name angular . equals
2018-05-05 12:13:16 +02:00
* @ module ng
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Determines if two objects or two values are equivalent . Supports value types , regular
* expressions , arrays and objects .
*
* Two objects or values are considered equivalent if at least one of the following is true :
*
* * Both objects or values pass ` === ` comparison .
* * Both objects or values are of the same type and all of their properties are equal by
* comparing them with ` angular.equals ` .
* * Both values are NaN . ( In JavaScript , NaN == NaN => false . But we consider two NaN as equal )
2018-05-05 12:13:16 +02:00
* * Both values represent the same regular expression ( In JavaScript ,
2016-03-28 10:46:51 +00:00
* /abc/ == /abc/ => false . But we consider two regular expressions as equal when their textual
* representation matches ) .
*
* During a property comparison , properties of ` function ` type and properties with names
* that begin with ` $ ` are ignored .
*
* Scope and DOMWindow objects are being compared only by identify ( ` === ` ) .
*
* @ param { * } o1 Object or value to compare .
* @ param { * } o2 Object or value to compare .
* @ returns { boolean } True if arguments are equal .
* /
function equals ( o1 , o2 ) {
if ( o1 === o2 ) return true ;
if ( o1 === null || o2 === null ) return false ;
if ( o1 !== o1 && o2 !== o2 ) return true ; // NaN === NaN
var t1 = typeof o1 , t2 = typeof o2 , length , key , keySet ;
2016-04-18 12:34:29 +00:00
if ( t1 == t2 ) {
if ( t1 == 'object' ) {
if ( isArray ( o1 ) ) {
if ( ! isArray ( o2 ) ) return false ;
if ( ( length = o1 . length ) == o2 . length ) {
2018-05-05 12:13:16 +02:00
for ( key = 0 ; key < length ; key ++ ) {
2016-04-18 12:34:29 +00:00
if ( ! equals ( o1 [ key ] , o2 [ key ] ) ) return false ;
}
return true ;
}
} else if ( isDate ( o1 ) ) {
2018-05-05 12:13:16 +02:00
if ( ! isDate ( o2 ) ) return false ;
return equals ( o1 . getTime ( ) , o2 . getTime ( ) ) ;
} else if ( isRegExp ( o1 ) ) {
return isRegExp ( o2 ) ? o1 . toString ( ) == o2 . toString ( ) : false ;
2016-04-18 12:34:29 +00:00
} else {
2018-05-05 12:13:16 +02:00
if ( isScope ( o1 ) || isScope ( o2 ) || isWindow ( o1 ) || isWindow ( o2 ) ||
isArray ( o2 ) || isDate ( o2 ) || isRegExp ( o2 ) ) return false ;
2016-04-18 12:34:29 +00:00
keySet = { } ;
2018-05-05 12:13:16 +02:00
for ( key in o1 ) {
2016-04-18 12:34:29 +00:00
if ( key . charAt ( 0 ) === '$' || isFunction ( o1 [ key ] ) ) continue ;
2016-03-28 10:46:51 +00:00
if ( ! equals ( o1 [ key ] , o2 [ key ] ) ) return false ;
2016-04-18 12:34:29 +00:00
keySet [ key ] = true ;
}
2018-05-05 12:13:16 +02:00
for ( key in o2 ) {
2016-04-18 12:34:29 +00:00
if ( ! keySet . hasOwnProperty ( key ) &&
key . charAt ( 0 ) !== '$' &&
o2 [ key ] !== undefined &&
! isFunction ( o2 [ key ] ) ) return false ;
2016-03-28 10:46:51 +00:00
}
return true ;
}
}
}
return false ;
}
2018-05-05 12:13:16 +02:00
var csp = function ( ) {
if ( isDefined ( csp . isActive _ ) ) return csp . isActive _ ;
var active = ! ! ( document . querySelector ( '[ng-csp]' ) ||
document . querySelector ( '[data-ng-csp]' ) ) ;
if ( ! active ) {
try {
/* jshint -W031, -W054 */
new Function ( '' ) ;
/* jshint +W031, +W054 */
} catch ( e ) {
active = true ;
}
}
return ( csp . isActive _ = active ) ;
} ;
2016-03-28 10:46:51 +00:00
function concat ( array1 , array2 , index ) {
return array1 . concat ( slice . call ( array2 , index ) ) ;
}
function sliceArgs ( args , startIndex ) {
return slice . call ( args , startIndex || 0 ) ;
}
/* jshint -W101 */
/ * *
* @ ngdoc function
* @ name angular . bind
2018-05-05 12:13:16 +02:00
* @ module ng
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Returns a function which calls function ` fn ` bound to ` self ` ( ` self ` becomes the ` this ` for
* ` fn ` ) . You can supply optional ` args ` that are prebound to the function . This feature is also
* known as [ partial application ] ( http : //en.wikipedia.org/wiki/Partial_application), as
* distinguished from [ function currying ] ( http : //en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application).
*
* @ param { Object } self Context which ` fn ` should be evaluated in .
* @ param { function ( ) } fn Function to be bound .
* @ param { ... * } args Optional arguments to be prebound to the ` fn ` function call .
* @ returns { function ( ) } Function that wraps the ` fn ` with all the specified bindings .
* /
/* jshint +W101 */
function bind ( self , fn ) {
var curryArgs = arguments . length > 2 ? sliceArgs ( arguments , 2 ) : [ ] ;
if ( isFunction ( fn ) && ! ( fn instanceof RegExp ) ) {
return curryArgs . length
? function ( ) {
return arguments . length
2018-05-05 12:13:16 +02:00
? fn . apply ( self , concat ( curryArgs , arguments , 0 ) )
2016-03-28 10:46:51 +00:00
: fn . apply ( self , curryArgs ) ;
}
: function ( ) {
return arguments . length
? fn . apply ( self , arguments )
: fn . call ( self ) ;
} ;
} else {
// in IE, native methods are not functions so they cannot be bound (note: they don't need to be)
return fn ;
}
}
function toJsonReplacer ( key , value ) {
var val = value ;
2018-05-05 12:13:16 +02:00
if ( typeof key === 'string' && key . charAt ( 0 ) === '$' && key . charAt ( 1 ) === '$' ) {
2016-03-28 10:46:51 +00:00
val = undefined ;
} else if ( isWindow ( value ) ) {
val = '$WINDOW' ;
} else if ( value && document === value ) {
val = '$DOCUMENT' ;
} else if ( isScope ( value ) ) {
val = '$SCOPE' ;
}
return val ;
}
/ * *
* @ ngdoc function
* @ name angular . toJson
2018-05-05 12:13:16 +02:00
* @ module ng
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
2018-05-05 12:13:16 +02:00
* Serializes input into a JSON - formatted string . Properties with leading $$ characters will be
2016-03-28 10:46:51 +00:00
* stripped since angular uses this notation internally .
*
* @ param { Object | Array | Date | string | number } obj Input to be serialized into JSON .
2018-05-05 12:13:16 +02:00
* @ param { boolean | number } [ pretty = 2 ] If set to true , the JSON output will contain newlines and whitespace .
* If set to an integer , the JSON output will contain that many spaces per indentation .
2016-03-28 10:46:51 +00:00
* @ returns { string | undefined } JSON - ified string representing ` obj ` .
* /
function toJson ( obj , pretty ) {
2016-04-18 12:34:29 +00:00
if ( typeof obj === 'undefined' ) return undefined ;
2018-05-05 12:13:16 +02:00
if ( ! isNumber ( pretty ) ) {
pretty = pretty ? 2 : null ;
}
return JSON . stringify ( obj , toJsonReplacer , pretty ) ;
2016-03-28 10:46:51 +00:00
}
/ * *
* @ ngdoc function
* @ name angular . fromJson
2018-05-05 12:13:16 +02:00
* @ module ng
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Deserializes a JSON string .
*
* @ param { string } json JSON string to deserialize .
2018-05-05 12:13:16 +02:00
* @ returns { Object | Array | string | number } Deserialized JSON string .
2016-03-28 10:46:51 +00:00
* /
function fromJson ( json ) {
return isString ( json )
? JSON . parse ( json )
: json ;
}
/ * *
* @ returns { string } Returns the string representation of the element .
* /
function startingTag ( element ) {
element = jqLite ( element ) . clone ( ) ;
try {
// turns out IE does not let you set .html() on elements which
// are not allowed to have children. So we just ignore it.
element . empty ( ) ;
2018-05-05 12:13:16 +02:00
} catch ( e ) { }
2016-03-28 10:46:51 +00:00
var elemHtml = jqLite ( '<div>' ) . append ( element ) . html ( ) ;
try {
2018-05-05 12:13:16 +02:00
return element [ 0 ] . nodeType === NODE _TYPE _TEXT ? lowercase ( elemHtml ) :
2016-03-28 10:46:51 +00:00
elemHtml .
match ( /^(<[^>]+>)/ ) [ 1 ] .
2016-04-18 12:34:29 +00:00
replace ( /^<([\w\-]+)/ , function ( match , nodeName ) { return '<' + lowercase ( nodeName ) ; } ) ;
2018-05-05 12:13:16 +02:00
} catch ( e ) {
2016-03-28 10:46:51 +00:00
return lowercase ( elemHtml ) ;
}
}
/////////////////////////////////////////////////
/ * *
* Tries to decode the URI component without throwing an exception .
*
* @ private
* @ param str value potential URI component to check .
* @ returns { boolean } True if ` value ` can be decoded
* with the decodeURIComponent function .
* /
function tryDecodeURIComponent ( value ) {
try {
return decodeURIComponent ( value ) ;
2018-05-05 12:13:16 +02:00
} catch ( e ) {
2016-03-28 10:46:51 +00:00
// Ignore any invalid uri component
}
}
/ * *
* Parses an escaped url query string into key - value pairs .
2018-05-05 12:13:16 +02:00
* @ returns { Object . < string , boolean | Array > }
2016-03-28 10:46:51 +00:00
* /
function parseKeyValue ( /**string*/ keyValue ) {
2016-04-18 12:34:29 +00:00
var obj = { } , key _value , key ;
2018-05-05 12:13:16 +02:00
forEach ( ( keyValue || "" ) . split ( '&' ) , function ( keyValue ) {
if ( keyValue ) {
key _value = keyValue . replace ( /\+/g , '%20' ) . split ( '=' ) ;
2016-04-18 12:34:29 +00:00
key = tryDecodeURIComponent ( key _value [ 0 ] ) ;
2018-05-05 12:13:16 +02:00
if ( isDefined ( key ) ) {
2016-04-18 12:34:29 +00:00
var val = isDefined ( key _value [ 1 ] ) ? tryDecodeURIComponent ( key _value [ 1 ] ) : true ;
2018-05-05 12:13:16 +02:00
if ( ! hasOwnProperty . call ( obj , key ) ) {
2016-03-28 10:46:51 +00:00
obj [ key ] = val ;
2018-05-05 12:13:16 +02:00
} else if ( isArray ( obj [ key ] ) ) {
2016-03-28 10:46:51 +00:00
obj [ key ] . push ( val ) ;
} else {
obj [ key ] = [ obj [ key ] , val ] ;
}
}
}
} ) ;
return obj ;
}
function toKeyValue ( obj ) {
var parts = [ ] ;
forEach ( obj , function ( value , key ) {
if ( isArray ( value ) ) {
forEach ( value , function ( arrayValue ) {
parts . push ( encodeUriQuery ( key , true ) +
( arrayValue === true ? '' : '=' + encodeUriQuery ( arrayValue , true ) ) ) ;
} ) ;
} else {
parts . push ( encodeUriQuery ( key , true ) +
( value === true ? '' : '=' + encodeUriQuery ( value , true ) ) ) ;
}
} ) ;
return parts . length ? parts . join ( '&' ) : '' ;
}
/ * *
* We need our custom method because encodeURIComponent is too aggressive and doesn ' t follow
* http : //www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
* segments :
* segment = * pchar
* pchar = unreserved / pct - encoded / sub - delims / ":" / "@"
* pct - encoded = "%" HEXDIG HEXDIG
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
* sub - delims = "!" / "$" / "&" / "'" / "(" / ")"
* / "*" / "+" / "," / ";" / "="
* /
function encodeUriSegment ( val ) {
return encodeUriQuery ( val , true ) .
replace ( /%26/gi , '&' ) .
replace ( /%3D/gi , '=' ) .
replace ( /%2B/gi , '+' ) ;
}
/ * *
* This method is intended for encoding * key * or * value * parts of query component . We need a custom
* method because encodeURIComponent is too aggressive and encodes stuff that doesn ' t have to be
* encoded per http : //tools.ietf.org/html/rfc3986:
* query = * ( pchar / "/" / "?" )
* pchar = unreserved / pct - encoded / sub - delims / ":" / "@"
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
* pct - encoded = "%" HEXDIG HEXDIG
* sub - delims = "!" / "$" / "&" / "'" / "(" / ")"
* / "*" / "+" / "," / ";" / "="
* /
function encodeUriQuery ( val , pctEncodeSpaces ) {
return encodeURIComponent ( val ) .
replace ( /%40/gi , '@' ) .
replace ( /%3A/gi , ':' ) .
replace ( /%24/g , '$' ) .
replace ( /%2C/gi , ',' ) .
2018-05-05 12:13:16 +02:00
replace ( /%3B/gi , ';' ) .
2016-03-28 10:46:51 +00:00
replace ( /%20/g , ( pctEncodeSpaces ? '%20' : '+' ) ) ;
}
2018-05-05 12:13:16 +02:00
var ngAttrPrefixes = [ 'ng-' , 'data-ng-' , 'ng:' , 'x-ng-' ] ;
function getNgAttribute ( element , ngAttr ) {
var attr , i , ii = ngAttrPrefixes . length ;
element = jqLite ( element ) ;
for ( i = 0 ; i < ii ; ++ i ) {
attr = ngAttrPrefixes [ i ] + ngAttr ;
if ( isString ( attr = element . attr ( attr ) ) ) {
return attr ;
}
}
return null ;
}
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngApp
* @ module ng
2016-03-28 10:46:51 +00:00
*
* @ element ANY
* @ param { angular . Module } ngApp an optional application
* { @ link angular . module module } name to load .
2018-05-05 12:13:16 +02:00
* @ param { boolean = } ngStrictDi if this attribute is present on the app element , the injector will be
* created in "strict-di" mode . This means that the application will fail to invoke functions which
* do not use explicit function annotation ( and are thus unsuitable for minification ) , as described
* in { @ link guide / di the Dependency Injection guide } , and useful debugging info will assist in
* tracking down the root of these bugs .
2016-03-28 10:46:51 +00:00
*
* @ description
*
* Use this directive to * * auto - bootstrap * * an AngularJS application . The ` ngApp ` directive
* designates the * * root element * * of the application and is typically placed near the root element
* of the page - e . g . on the ` <body> ` or ` <html> ` tags .
*
2016-04-18 12:34:29 +00:00
* Only one AngularJS application can be auto - bootstrapped per HTML document . The first ` ngApp `
* found in the document will be used to define the root element to auto - bootstrap as an
* application . To run multiple applications in an HTML document you must manually bootstrap them using
* { @ link angular . bootstrap } instead . AngularJS applications cannot be nested within each other .
2016-03-28 10:46:51 +00:00
*
* You can specify an * * AngularJS module * * to be used as the root module for the application . This
2018-05-05 12:13:16 +02:00
* module will be loaded into the { @ link auto . $injector } when the application is bootstrapped . It
2016-03-28 10:46:51 +00:00
* should contain the application code needed or have dependencies on other modules that will
* contain the code . See { @ link angular . module } for more information .
*
* In the example below if the ` ngApp ` directive were not placed on the ` html ` element then the
* document would not be compiled , the ` AppController ` would not be instantiated and the ` {{ a+b }} `
* would not be resolved to ` 3 ` .
*
2018-05-05 12:13:16 +02:00
* ` ngApp ` is the easiest , and most common way to bootstrap an application .
2016-03-28 10:46:51 +00:00
*
< example module = "ngAppDemo" >
< file name = "index.html" >
< div ng - controller = "ngAppDemoController" >
I can add : { { a } } + { { b } } = { { a + b } }
2018-05-05 12:13:16 +02:00
< / d i v >
2016-03-28 10:46:51 +00:00
< / f i l e >
< file name = "script.js" >
angular . module ( 'ngAppDemo' , [ ] ) . controller ( 'ngAppDemoController' , function ( $scope ) {
$scope . a = 1 ;
$scope . b = 2 ;
} ) ;
< / f i l e >
< / e x a m p l e >
*
2018-05-05 12:13:16 +02:00
* Using ` ngStrictDi ` , you would see something like this :
*
< example ng - app - included = "true" >
< file name = "index.html" >
< div ng - app = "ngAppStrictDemo" ng - strict - di >
< div ng - controller = "GoodController1" >
I can add : { { a } } + { { b } } = { { a + b } }
< p > This renders because the controller does not fail to
instantiate , by using explicit annotation style ( see
script . js for details )
< / p >
< / d i v >
< div ng - controller = "GoodController2" >
Name : < input ng - model = "name" > < br / >
Hello , { { name } } !
< p > This renders because the controller does not fail to
instantiate , by using explicit annotation style
( see script . js for details )
< / p >
< / d i v >
< div ng - controller = "BadController" >
I can add : { { a } } + { { b } } = { { a + b } }
< p > The controller could not be instantiated , due to relying
on automatic function annotations ( which are disabled in
strict mode ) . As such , the content of this section is not
interpolated , and there should be an error in your web console .
< / p >
< / d i v >
< / d i v >
< / f i l e >
< file name = "script.js" >
angular . module ( 'ngAppStrictDemo' , [ ] )
// BadController will fail to instantiate, due to relying on automatic function annotation,
// rather than an explicit annotation
. controller ( 'BadController' , function ( $scope ) {
$scope . a = 1 ;
$scope . b = 2 ;
} )
// Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated,
// due to using explicit annotations using the array style and $inject property, respectively.
. controller ( 'GoodController1' , [ '$scope' , function ( $scope ) {
$scope . a = 1 ;
$scope . b = 2 ;
} ] )
. controller ( 'GoodController2' , GoodController2 ) ;
function GoodController2 ( $scope ) {
$scope . name = "World" ;
}
GoodController2 . $inject = [ '$scope' ] ;
< / f i l e >
< file name = "style.css" >
div [ ng - controller ] {
margin - bottom : 1 em ;
- webkit - border - radius : 4 px ;
border - radius : 4 px ;
border : 1 px solid ;
padding : . 5 em ;
}
div [ ng - controller ^= Good ] {
border - color : # d6e9c6 ;
background - color : # dff0d8 ;
color : # 3 c763d ;
}
div [ ng - controller ^= Bad ] {
border - color : # ebccd1 ;
background - color : # f2dede ;
color : # a94442 ;
margin - bottom : 0 ;
}
< / f i l e >
< / e x a m p l e >
2016-04-18 12:34:29 +00:00
* /
function angularInit ( element , bootstrap ) {
2018-05-05 12:13:16 +02:00
var appElement ,
2016-04-18 12:34:29 +00:00
module ,
2018-05-05 12:13:16 +02:00
config = { } ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
// The element `element` has priority over any other element
forEach ( ngAttrPrefixes , function ( prefix ) {
var name = prefix + 'app' ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
if ( ! appElement && element . hasAttribute && element . hasAttribute ( name ) ) {
appElement = element ;
module = element . getAttribute ( name ) ;
2016-03-28 10:46:51 +00:00
}
} ) ;
2018-05-05 12:13:16 +02:00
forEach ( ngAttrPrefixes , function ( prefix ) {
var name = prefix + 'app' ;
var candidate ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
if ( ! appElement && ( candidate = element . querySelector ( '[' + name . replace ( ':' , '\\:' ) + ']' ) ) ) {
appElement = candidate ;
module = candidate . getAttribute ( name ) ;
2016-03-28 10:46:51 +00:00
}
} ) ;
if ( appElement ) {
2018-05-05 12:13:16 +02:00
config . strictDi = getNgAttribute ( appElement , "strict-di" ) !== null ;
bootstrap ( appElement , module ? [ module ] : [ ] , config ) ;
2016-03-28 10:46:51 +00:00
}
}
/ * *
* @ ngdoc function
* @ name angular . bootstrap
2018-05-05 12:13:16 +02:00
* @ module ng
2016-03-28 10:46:51 +00:00
* @ description
* Use this function to manually start up angular application .
*
2016-04-18 12:34:29 +00:00
* See : { @ link guide / bootstrap Bootstrap }
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* Note that Protractor based end - to - end tests cannot use this function to bootstrap manually .
* They must use { @ link ng . directive : ngApp ngApp } .
*
* Angular will detect if it has been loaded into the browser more than once and only allow the
* first loaded script to be bootstrapped and will report a warning to the browser console for
* each of the subsequent scripts . This prevents strange results in applications , where otherwise
* multiple instances of Angular try to work on the DOM .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* ` ` ` html
* < ! doctype html >
* < html >
* < body >
* < div ng - controller = "WelcomeController" >
* { { greeting } }
* < / d i v >
*
* < script src = "angular.js" > < / s c r i p t >
* < script >
* var app = angular . module ( 'demo' , [ ] )
* . controller ( 'WelcomeController' , function ( $scope ) {
* $scope . greeting = 'Welcome!' ;
* } ) ;
* angular . bootstrap ( document , [ 'demo' ] ) ;
* < / s c r i p t >
* < / b o d y >
* < / h t m l >
* ` ` `
*
* @ param { DOMElement } element DOM element which is the root of angular application .
2016-03-28 10:46:51 +00:00
* @ param { Array < String | Function | Array >= } modules an array of modules to load into the application .
* Each item in the array should be the name of a predefined module or a ( DI annotated )
2018-05-05 12:13:16 +02:00
* function that will be invoked by the injector as a ` config ` block .
2016-03-28 10:46:51 +00:00
* See : { @ link angular . module modules }
2018-05-05 12:13:16 +02:00
* @ param { Object = } config an object for defining configuration options for the application . The
* following keys are supported :
*
* * ` strictDi ` - disable automatic function annotation for the application . This is meant to
* assist in finding bugs which break minified code . Defaults to ` false ` .
*
* @ returns { auto . $injector } Returns the newly created injector for this app .
2016-03-28 10:46:51 +00:00
* /
2018-05-05 12:13:16 +02:00
function bootstrap ( element , modules , config ) {
if ( ! isObject ( config ) ) config = { } ;
var defaultConfig = {
strictDi : false
} ;
config = extend ( defaultConfig , config ) ;
2016-03-28 10:46:51 +00:00
var doBootstrap = function ( ) {
element = jqLite ( element ) ;
if ( element . injector ( ) ) {
var tag = ( element [ 0 ] === document ) ? 'document' : startingTag ( element ) ;
2018-05-05 12:13:16 +02:00
//Encode angle brackets to prevent input from being sanitized to empty string #8683
throw ngMinErr (
'btstrpd' ,
"App Already Bootstrapped with this Element '{0}'" ,
tag . replace ( /</ , '<' ) . replace ( />/ , '>' ) ) ;
2016-03-28 10:46:51 +00:00
}
modules = modules || [ ] ;
modules . unshift ( [ '$provide' , function ( $provide ) {
$provide . value ( '$rootElement' , element ) ;
} ] ) ;
2018-05-05 12:13:16 +02:00
if ( config . debugInfoEnabled ) {
// Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`.
modules . push ( [ '$compileProvider' , function ( $compileProvider ) {
$compileProvider . debugInfoEnabled ( true ) ;
} ] ) ;
}
2016-03-28 10:46:51 +00:00
modules . unshift ( 'ng' ) ;
2018-05-05 12:13:16 +02:00
var injector = createInjector ( modules , config . strictDi ) ;
injector . invoke ( [ '$rootScope' , '$rootElement' , '$compile' , '$injector' ,
function bootstrapApply ( scope , element , compile , injector ) {
2016-03-28 10:46:51 +00:00
scope . $apply ( function ( ) {
element . data ( '$injector' , injector ) ;
compile ( element ) ( scope ) ;
} ) ;
} ]
) ;
return injector ;
} ;
2018-05-05 12:13:16 +02:00
var NG _ENABLE _DEBUG _INFO = /^NG_ENABLE_DEBUG_INFO!/ ;
2016-03-28 10:46:51 +00:00
var NG _DEFER _BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/ ;
2018-05-05 12:13:16 +02:00
if ( window && NG _ENABLE _DEBUG _INFO . test ( window . name ) ) {
config . debugInfoEnabled = true ;
window . name = window . name . replace ( NG _ENABLE _DEBUG _INFO , '' ) ;
}
2016-03-28 10:46:51 +00:00
if ( window && ! NG _DEFER _BOOTSTRAP . test ( window . name ) ) {
return doBootstrap ( ) ;
}
window . name = window . name . replace ( NG _DEFER _BOOTSTRAP , '' ) ;
angular . resumeBootstrap = function ( extraModules ) {
forEach ( extraModules , function ( module ) {
modules . push ( module ) ;
} ) ;
2018-05-05 12:13:16 +02:00
return doBootstrap ( ) ;
2016-03-28 10:46:51 +00:00
} ;
2018-05-05 12:13:16 +02:00
if ( isFunction ( angular . resumeDeferredBootstrap ) ) {
angular . resumeDeferredBootstrap ( ) ;
}
}
/ * *
* @ ngdoc function
* @ name angular . reloadWithDebugInfo
* @ module ng
* @ description
* Use this function to reload the current application with debug information turned on .
* This takes precedence over a call to ` $ compileProvider.debugInfoEnabled(false) ` .
*
* See { @ link ng . $compileProvider # debugInfoEnabled } for more .
* /
function reloadWithDebugInfo ( ) {
window . name = 'NG_ENABLE_DEBUG_INFO!' + window . name ;
window . location . reload ( ) ;
}
/ * *
* @ name angular . getTestability
* @ module ng
* @ description
* Get the testability service for the instance of Angular on the given
* element .
* @ param { DOMElement } element DOM element which is the root of angular application .
* /
function getTestability ( rootElement ) {
var injector = angular . element ( rootElement ) . injector ( ) ;
if ( ! injector ) {
throw ngMinErr ( 'test' ,
'no injector found for element argument to getTestability' ) ;
}
return injector . get ( '$$testability' ) ;
2016-03-28 10:46:51 +00:00
}
var SNAKE _CASE _REGEXP = /[A-Z]/g ;
2018-05-05 12:13:16 +02:00
function snake _case ( name , separator ) {
2016-03-28 10:46:51 +00:00
separator = separator || '_' ;
return name . replace ( SNAKE _CASE _REGEXP , function ( letter , pos ) {
return ( pos ? separator : '' ) + letter . toLowerCase ( ) ;
} ) ;
}
2018-05-05 12:13:16 +02:00
var bindJQueryFired = false ;
var skipDestroyOnNextJQueryCleanData ;
2016-03-28 10:46:51 +00:00
function bindJQuery ( ) {
2018-05-05 12:13:16 +02:00
var originalCleanData ;
if ( bindJQueryFired ) {
return ;
}
2016-03-28 10:46:51 +00:00
// bind to jQuery if present;
2016-04-18 12:34:29 +00:00
jQuery = window . jQuery ;
2018-05-05 12:13:16 +02:00
// Use jQuery if it exists with proper functionality, otherwise default to us.
// Angular 1.2+ requires jQuery 1.7+ for on()/off() support.
// Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older
// versions. It will not work for sure with jQuery <1.7, though.
if ( jQuery && jQuery . fn . on ) {
2016-03-28 10:46:51 +00:00
jqLite = jQuery ;
extend ( jQuery . fn , {
scope : JQLitePrototype . scope ,
isolateScope : JQLitePrototype . isolateScope ,
controller : JQLitePrototype . controller ,
injector : JQLitePrototype . injector ,
inheritedData : JQLitePrototype . inheritedData
} ) ;
2018-05-05 12:13:16 +02:00
// All nodes removed from the DOM via various jQuery APIs like .remove()
// are passed through jQuery.cleanData. Monkey-patch this method to fire
// the $destroy event on all removed nodes.
originalCleanData = jQuery . cleanData ;
jQuery . cleanData = function ( elems ) {
var events ;
if ( ! skipDestroyOnNextJQueryCleanData ) {
for ( var i = 0 , elem ; ( elem = elems [ i ] ) != null ; i ++ ) {
events = jQuery . _data ( elem , "events" ) ;
if ( events && events . $destroy ) {
jQuery ( elem ) . triggerHandler ( '$destroy' ) ;
}
}
} else {
skipDestroyOnNextJQueryCleanData = false ;
}
originalCleanData ( elems ) ;
} ;
2016-03-28 10:46:51 +00:00
} else {
jqLite = JQLite ;
}
2018-05-05 12:13:16 +02:00
2016-03-28 10:46:51 +00:00
angular . element = jqLite ;
2018-05-05 12:13:16 +02:00
// Prevent double-proxying.
bindJQueryFired = true ;
2016-03-28 10:46:51 +00:00
}
/ * *
* throw error if the argument is falsy .
* /
function assertArg ( arg , name , reason ) {
if ( ! arg ) {
throw ngMinErr ( 'areq' , "Argument '{0}' is {1}" , ( name || '?' ) , ( reason || "required" ) ) ;
}
return arg ;
}
function assertArgFn ( arg , name , acceptArrayAnnotation ) {
if ( acceptArrayAnnotation && isArray ( arg ) ) {
arg = arg [ arg . length - 1 ] ;
}
assertArg ( isFunction ( arg ) , name , 'not a function, got ' +
2018-05-05 12:13:16 +02:00
( arg && typeof arg === 'object' ? arg . constructor . name || 'Object' : typeof arg ) ) ;
2016-03-28 10:46:51 +00:00
return arg ;
}
/ * *
* throw error if the name given is hasOwnProperty
* @ param { String } name the name to test
* @ param { String } context the context in which the name is used , such as module or directive
* /
function assertNotHasOwnProperty ( name , context ) {
if ( name === 'hasOwnProperty' ) {
throw ngMinErr ( 'badname' , "hasOwnProperty is not a valid {0} name" , context ) ;
}
}
/ * *
* Return the value accessible from the object by path . Any undefined traversals are ignored
* @ param { Object } obj starting object
2018-05-05 12:13:16 +02:00
* @ param { String } path path to traverse
* @ param { boolean } [ bindFnToScope = true ]
* @ returns { Object } value as accessible by path
2016-03-28 10:46:51 +00:00
* /
//TODO(misko): this function needs to be removed
function getter ( obj , path , bindFnToScope ) {
if ( ! path ) return obj ;
var keys = path . split ( '.' ) ;
var key ;
var lastInstance = obj ;
var len = keys . length ;
for ( var i = 0 ; i < len ; i ++ ) {
key = keys [ i ] ;
if ( obj ) {
obj = ( lastInstance = obj ) [ key ] ;
}
}
if ( ! bindFnToScope && isFunction ( obj ) ) {
return bind ( lastInstance , obj ) ;
}
return obj ;
}
/ * *
* Return the DOM siblings between the first and last node in the given array .
* @ param { Array } array like object
2018-05-05 12:13:16 +02:00
* @ returns { jqLite } jqLite collection containing the nodes
2016-03-28 10:46:51 +00:00
* /
2018-05-05 12:13:16 +02:00
function getBlockNodes ( nodes ) {
// TODO(perf): just check if all items in `nodes` are siblings and if they are return the original
// collection, otherwise update the original collection.
var node = nodes [ 0 ] ;
var endNode = nodes [ nodes . length - 1 ] ;
var blockNodes = [ node ] ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
do {
2018-05-05 12:13:16 +02:00
node = node . nextSibling ;
if ( ! node ) break ;
blockNodes . push ( node ) ;
} while ( node !== endNode ) ;
return jqLite ( blockNodes ) ;
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
/ * *
* Creates a new object without a prototype . This object is useful for lookup without having to
* guard against prototypically inherited properties via hasOwnProperty .
*
* Related micro - benchmarks :
* - http : //jsperf.com/object-create2
* - http : //jsperf.com/proto-map-lookup/2
* - http : //jsperf.com/for-in-vs-object-keys2
*
* @ returns { Object }
* /
function createMap ( ) {
return Object . create ( null ) ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
var NODE _TYPE _ELEMENT = 1 ;
var NODE _TYPE _ATTRIBUTE = 2 ;
var NODE _TYPE _TEXT = 3 ;
var NODE _TYPE _COMMENT = 8 ;
var NODE _TYPE _DOCUMENT = 9 ;
var NODE _TYPE _DOCUMENT _FRAGMENT = 11 ;
2016-03-28 10:46:51 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc type
2016-03-28 10:46:51 +00:00
* @ name angular . Module
2018-05-05 12:13:16 +02:00
* @ module ng
2016-03-28 10:46:51 +00:00
* @ description
*
* Interface for configuring angular { @ link angular . module modules } .
* /
function setupModuleLoader ( window ) {
var $injectorMinErr = minErr ( '$injector' ) ;
var ngMinErr = minErr ( 'ng' ) ;
function ensure ( obj , name , factory ) {
return obj [ name ] || ( obj [ name ] = factory ( ) ) ;
}
var angular = ensure ( window , 'angular' , Object ) ;
// We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
angular . $$minErr = angular . $$minErr || minErr ;
return ensure ( angular , 'module' , function ( ) {
/** @type {Object.<string, angular.Module>} */
var modules = { } ;
/ * *
* @ ngdoc function
* @ name angular . module
2018-05-05 12:13:16 +02:00
* @ module ng
2016-03-28 10:46:51 +00:00
* @ description
*
* The ` angular.module ` is a global place for creating , registering and retrieving Angular
* modules .
* All modules ( angular core or 3 rd party ) that should be available to an application must be
* registered using this mechanism .
*
2016-04-18 12:34:29 +00:00
* When passed two or more arguments , a new module is created . If passed only one argument , an
* existing module ( the name passed as the first argument to ` module ` ) is retrieved .
2016-03-28 10:46:51 +00:00
*
*
* # Module
*
2018-05-05 12:13:16 +02:00
* A module is a collection of services , directives , controllers , filters , and configuration information .
* ` angular.module ` is used to configure the { @ link auto . $injector $injector } .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
* // Create a new module
* var myModule = angular . module ( 'myModule' , [ ] ) ;
*
* // register a new service
* myModule . value ( 'appName' , 'MyCoolApp' ) ;
*
* // configure existing services inside initialization blocks.
2018-05-05 12:13:16 +02:00
* myModule . config ( [ '$locationProvider' , function ( $locationProvider ) {
2016-03-28 10:46:51 +00:00
* // Configure existing providers
* $locationProvider . hashPrefix ( '!' ) ;
2018-05-05 12:13:16 +02:00
* } ] ) ;
* ` ` `
2016-03-28 10:46:51 +00:00
*
* Then you can create an injector and load your modules like this :
*
2018-05-05 12:13:16 +02:00
* ` ` ` js
* var injector = angular . injector ( [ 'ng' , 'myModule' ] )
* ` ` `
2016-03-28 10:46:51 +00:00
*
* However it 's more likely that you' ll just use
* { @ link ng . directive : ngApp ngApp } or
* { @ link angular . bootstrap } to simplify this process for you .
*
* @ param { ! string } name The name of the module to create or retrieve .
2018-05-05 12:13:16 +02:00
* @ param { ! Array . < string >= } requires If specified then new module is being created . If
* unspecified then the module is being retrieved for further configuration .
* @ param { Function = } configFn Optional configuration function for the module . Same as
* { @ link angular . Module # config Module # config ( ) } .
2016-04-18 12:34:29 +00:00
* @ returns { module } new module with the { @ link angular . Module } api .
2016-03-28 10:46:51 +00:00
* /
return function module ( name , requires , configFn ) {
var assertNotHasOwnProperty = function ( name , context ) {
if ( name === 'hasOwnProperty' ) {
throw ngMinErr ( 'badname' , 'hasOwnProperty is not a valid {0} name' , context ) ;
}
} ;
assertNotHasOwnProperty ( name , 'module' ) ;
if ( requires && modules . hasOwnProperty ( name ) ) {
modules [ name ] = null ;
}
return ensure ( modules , name , function ( ) {
if ( ! requires ) {
throw $injectorMinErr ( 'nomod' , "Module '{0}' is not available! You either misspelled " +
"the module name or forgot to load it. If registering a module ensure that you " +
"specify the dependencies as the second argument." , name ) ;
}
/** @type {!Array.<Array.<*>>} */
var invokeQueue = [ ] ;
2018-05-05 12:13:16 +02:00
/** @type {!Array.<Function>} */
var configBlocks = [ ] ;
2016-03-28 10:46:51 +00:00
/** @type {!Array.<Function>} */
var runBlocks = [ ] ;
2018-05-05 12:13:16 +02:00
var config = invokeLater ( '$injector' , 'invoke' , 'push' , configBlocks ) ;
2016-03-28 10:46:51 +00:00
/** @type {angular.Module} */
var moduleInstance = {
// Private state
_invokeQueue : invokeQueue ,
2018-05-05 12:13:16 +02:00
_configBlocks : configBlocks ,
2016-03-28 10:46:51 +00:00
_runBlocks : runBlocks ,
/ * *
* @ ngdoc property
* @ name angular . Module # requires
2018-05-05 12:13:16 +02:00
* @ module ng
*
2016-03-28 10:46:51 +00:00
* @ description
* Holds the list of modules which the injector will load before the current module is
* loaded .
* /
requires : requires ,
/ * *
* @ ngdoc property
* @ name angular . Module # name
2018-05-05 12:13:16 +02:00
* @ module ng
*
2016-03-28 10:46:51 +00:00
* @ description
2018-05-05 12:13:16 +02:00
* Name of the module .
2016-03-28 10:46:51 +00:00
* /
name : name ,
/ * *
* @ ngdoc method
* @ name angular . Module # provider
2018-05-05 12:13:16 +02:00
* @ module ng
2016-03-28 10:46:51 +00:00
* @ param { string } name service name
* @ param { Function } providerType Construction function for creating new instance of the
* service .
* @ description
2018-05-05 12:13:16 +02:00
* See { @ link auto . $provide # provider $provide . provider ( ) } .
2016-03-28 10:46:51 +00:00
* /
2016-04-18 12:34:29 +00:00
provider : invokeLater ( '$provide' , 'provider' ) ,
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc method
* @ name angular . Module # factory
2018-05-05 12:13:16 +02:00
* @ module ng
2016-03-28 10:46:51 +00:00
* @ param { string } name service name
* @ param { Function } providerFunction Function for creating new instance of the service .
* @ description
2018-05-05 12:13:16 +02:00
* See { @ link auto . $provide # factory $provide . factory ( ) } .
2016-03-28 10:46:51 +00:00
* /
2016-04-18 12:34:29 +00:00
factory : invokeLater ( '$provide' , 'factory' ) ,
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc method
* @ name angular . Module # service
2018-05-05 12:13:16 +02:00
* @ module ng
2016-03-28 10:46:51 +00:00
* @ param { string } name service name
* @ param { Function } constructor A constructor function that will be instantiated .
* @ description
2018-05-05 12:13:16 +02:00
* See { @ link auto . $provide # service $provide . service ( ) } .
2016-03-28 10:46:51 +00:00
* /
2016-04-18 12:34:29 +00:00
service : invokeLater ( '$provide' , 'service' ) ,
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc method
* @ name angular . Module # value
2018-05-05 12:13:16 +02:00
* @ module ng
2016-03-28 10:46:51 +00:00
* @ param { string } name service name
* @ param { * } object Service instance object .
* @ description
2018-05-05 12:13:16 +02:00
* See { @ link auto . $provide # value $provide . value ( ) } .
2016-03-28 10:46:51 +00:00
* /
value : invokeLater ( '$provide' , 'value' ) ,
/ * *
* @ ngdoc method
* @ name angular . Module # constant
2018-05-05 12:13:16 +02:00
* @ module ng
2016-03-28 10:46:51 +00:00
* @ param { string } name constant name
* @ param { * } object Constant value .
* @ description
2016-04-18 12:34:29 +00:00
* Because the constant are fixed , they get applied before other provide methods .
2018-05-05 12:13:16 +02:00
* See { @ link auto . $provide # constant $provide . constant ( ) } .
2016-03-28 10:46:51 +00:00
* /
constant : invokeLater ( '$provide' , 'constant' , 'unshift' ) ,
/ * *
* @ ngdoc method
* @ name angular . Module # animation
2018-05-05 12:13:16 +02:00
* @ module ng
2016-03-28 10:46:51 +00:00
* @ param { string } name animation name
* @ param { Function } animationFactory Factory function for creating new instance of an
* animation .
* @ description
*
* * * NOTE * * : animations take effect only if the * * ngAnimate * * module is loaded .
*
*
* Defines an animation hook that can be later used with
2016-04-18 12:34:29 +00:00
* { @ link ngAnimate . $animate $animate } service and directives that use this service .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
* module . animation ( '.animation-name' , function ( $inject1 , $inject2 ) {
* return {
* eventName : function ( element , done ) {
* //code to run the animation
* //once complete, then run done()
* return function cancellationFunction ( element ) {
* //code to cancel the animation
* }
* }
* }
* } )
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* See { @ link ng . $animateProvider # register $animateProvider . register ( ) } and
2016-03-28 10:46:51 +00:00
* { @ link ngAnimate ngAnimate module } for more information .
* /
2016-04-18 12:34:29 +00:00
animation : invokeLater ( '$animateProvider' , 'register' ) ,
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc method
* @ name angular . Module # filter
2018-05-05 12:13:16 +02:00
* @ module ng
* @ param { string } name Filter name - this must be a valid angular expression identifier
2016-03-28 10:46:51 +00:00
* @ param { Function } filterFactory Factory function for creating new instance of filter .
* @ description
* See { @ link ng . $filterProvider # register $filterProvider . register ( ) } .
2018-05-05 12:13:16 +02:00
*
* < div class = "alert alert-warning" >
* * * Note : * * Filter names must be valid angular { @ link expression } identifiers , such as ` uppercase ` or ` orderBy ` .
* Names with special characters , such as hyphens and dots , are not allowed . If you wish to namespace
* your filters , then you can use capitalization ( ` myappSubsectionFilterx ` ) or underscores
* ( ` myapp_subsection_filterx ` ) .
* < / d i v >
2016-03-28 10:46:51 +00:00
* /
2016-04-18 12:34:29 +00:00
filter : invokeLater ( '$filterProvider' , 'register' ) ,
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc method
* @ name angular . Module # controller
2018-05-05 12:13:16 +02:00
* @ module ng
2016-03-28 10:46:51 +00:00
* @ param { string | Object } name Controller name , or an object map of controllers where the
* keys are the names and the values are the constructors .
* @ param { Function } constructor Controller constructor function .
* @ description
* See { @ link ng . $controllerProvider # register $controllerProvider . register ( ) } .
* /
2016-04-18 12:34:29 +00:00
controller : invokeLater ( '$controllerProvider' , 'register' ) ,
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc method
* @ name angular . Module # directive
2018-05-05 12:13:16 +02:00
* @ module ng
2016-03-28 10:46:51 +00:00
* @ param { string | Object } name Directive name , or an object map of directives where the
* keys are the names and the values are the factories .
* @ param { Function } directiveFactory Factory function for creating new instance of
* directives .
* @ description
2018-05-05 12:13:16 +02:00
* See { @ link ng . $compileProvider # directive $compileProvider . directive ( ) } .
2016-03-28 10:46:51 +00:00
* /
2016-04-18 12:34:29 +00:00
directive : invokeLater ( '$compileProvider' , 'directive' ) ,
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc method
* @ name angular . Module # config
2018-05-05 12:13:16 +02:00
* @ module ng
2016-03-28 10:46:51 +00:00
* @ param { Function } configFn Execute this function on module load . Useful for service
* configuration .
* @ description
* Use this method to register work which needs to be performed on module loading .
2018-05-05 12:13:16 +02:00
* For more about how to configure services , see
* { @ link providers # provider - recipe Provider Recipe } .
2016-03-28 10:46:51 +00:00
* /
config : config ,
/ * *
* @ ngdoc method
* @ name angular . Module # run
2018-05-05 12:13:16 +02:00
* @ module ng
2016-03-28 10:46:51 +00:00
* @ param { Function } initializationFn Execute this function after injector creation .
* Useful for application initialization .
* @ description
* Use this method to register work which should be performed when the injector is done
* loading all modules .
* /
run : function ( block ) {
runBlocks . push ( block ) ;
return this ;
}
} ;
if ( configFn ) {
config ( configFn ) ;
}
2018-05-05 12:13:16 +02:00
return moduleInstance ;
2016-03-28 10:46:51 +00:00
/ * *
* @ param { string } provider
* @ param { string } method
* @ param { String = } insertMethod
* @ returns { angular . Module }
* /
2018-05-05 12:13:16 +02:00
function invokeLater ( provider , method , insertMethod , queue ) {
if ( ! queue ) queue = invokeQueue ;
2016-03-28 10:46:51 +00:00
return function ( ) {
2018-05-05 12:13:16 +02:00
queue [ insertMethod || 'push' ] ( [ provider , method , arguments ] ) ;
2016-03-28 10:46:51 +00:00
return moduleInstance ;
} ;
}
} ) ;
} ;
} ) ;
}
2018-05-05 12:13:16 +02:00
/* global: toDebugString: true */
function serializeObject ( obj ) {
var seen = [ ] ;
return JSON . stringify ( obj , function ( key , val ) {
val = toJsonReplacer ( key , val ) ;
if ( isObject ( val ) ) {
if ( seen . indexOf ( val ) >= 0 ) return '<<already seen>>' ;
seen . push ( val ) ;
}
return val ;
} ) ;
}
function toDebugString ( obj ) {
if ( typeof obj === 'function' ) {
return obj . toString ( ) . replace ( / \{[\s\S]*$/ , '' ) ;
} else if ( typeof obj === 'undefined' ) {
return 'undefined' ;
} else if ( typeof obj !== 'string' ) {
return serializeObject ( obj ) ;
}
return obj ;
}
/ * g l o b a l a n g u l a r M o d u l e : t r u e ,
version : true ,
$LocaleProvider ,
$CompileProvider ,
htmlAnchorDirective ,
inputDirective ,
inputDirective ,
formDirective ,
scriptDirective ,
selectDirective ,
styleDirective ,
optionDirective ,
ngBindDirective ,
ngBindHtmlDirective ,
ngBindTemplateDirective ,
ngClassDirective ,
ngClassEvenDirective ,
ngClassOddDirective ,
ngCspDirective ,
ngCloakDirective ,
ngControllerDirective ,
ngFormDirective ,
ngHideDirective ,
ngIfDirective ,
ngIncludeDirective ,
ngIncludeFillContentDirective ,
ngInitDirective ,
ngNonBindableDirective ,
ngPluralizeDirective ,
ngRepeatDirective ,
ngShowDirective ,
ngStyleDirective ,
ngSwitchDirective ,
ngSwitchWhenDirective ,
ngSwitchDefaultDirective ,
ngOptionsDirective ,
ngTranscludeDirective ,
ngModelDirective ,
ngListDirective ,
ngChangeDirective ,
patternDirective ,
patternDirective ,
requiredDirective ,
requiredDirective ,
minlengthDirective ,
minlengthDirective ,
maxlengthDirective ,
maxlengthDirective ,
ngValueDirective ,
ngModelOptionsDirective ,
ngAttributeAliasDirectives ,
ngEventDirectives ,
$AnchorScrollProvider ,
$AnimateProvider ,
$BrowserProvider ,
$CacheFactoryProvider ,
$ControllerProvider ,
$DocumentProvider ,
$ExceptionHandlerProvider ,
$FilterProvider ,
$InterpolateProvider ,
$IntervalProvider ,
$HttpProvider ,
$HttpBackendProvider ,
$LocationProvider ,
$LogProvider ,
$ParseProvider ,
$RootScopeProvider ,
$QProvider ,
$$QProvider ,
$$SanitizeUriProvider ,
$SceProvider ,
$SceDelegateProvider ,
$SnifferProvider ,
$TemplateCacheProvider ,
$TemplateRequestProvider ,
$$TestabilityProvider ,
$TimeoutProvider ,
$$RAFProvider ,
$$AsyncCallbackProvider ,
$WindowProvider ,
$$jqLiteProvider
2016-03-28 10:46:51 +00:00
* /
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc object
2016-03-28 10:46:51 +00:00
* @ name angular . version
2018-05-05 12:13:16 +02:00
* @ module ng
2016-03-28 10:46:51 +00:00
* @ description
2016-04-18 12:34:29 +00:00
* An object that contains information about the current AngularJS version . This object has the
* following properties :
2016-03-28 10:46:51 +00:00
*
* - ` full ` – ` {string} ` – Full version string , such as "0.9.18" .
* - ` major ` – ` {number} ` – Major version number , such as "0" .
* - ` minor ` – ` {number} ` – Minor version number , such as "9" .
* - ` dot ` – ` {number} ` – Dot version number , such as "18" .
* - ` codeName ` – ` {string} ` – Code name of the release , such as "jiggling-armfat" .
* /
var version = {
2018-05-05 12:13:16 +02:00
full : '1.3.20' , // all of these placeholder strings will be replaced by grunt's
2016-03-28 10:46:51 +00:00
major : 1 , // package task
2018-05-05 12:13:16 +02:00
minor : 3 ,
dot : 20 ,
codeName : 'shallow-translucence'
2016-03-28 10:46:51 +00:00
} ;
2018-05-05 12:13:16 +02:00
function publishExternalAPI ( angular ) {
2016-03-28 10:46:51 +00:00
extend ( angular , {
'bootstrap' : bootstrap ,
'copy' : copy ,
'extend' : extend ,
'equals' : equals ,
'element' : jqLite ,
'forEach' : forEach ,
'injector' : createInjector ,
2018-05-05 12:13:16 +02:00
'noop' : noop ,
'bind' : bind ,
2016-03-28 10:46:51 +00:00
'toJson' : toJson ,
'fromJson' : fromJson ,
2018-05-05 12:13:16 +02:00
'identity' : identity ,
2016-03-28 10:46:51 +00:00
'isUndefined' : isUndefined ,
'isDefined' : isDefined ,
'isString' : isString ,
'isFunction' : isFunction ,
'isObject' : isObject ,
'isNumber' : isNumber ,
'isElement' : isElement ,
'isArray' : isArray ,
'version' : version ,
'isDate' : isDate ,
'lowercase' : lowercase ,
'uppercase' : uppercase ,
'callbacks' : { counter : 0 } ,
2018-05-05 12:13:16 +02:00
'getTestability' : getTestability ,
2016-03-28 10:46:51 +00:00
'$$minErr' : minErr ,
2018-05-05 12:13:16 +02:00
'$$csp' : csp ,
'reloadWithDebugInfo' : reloadWithDebugInfo
2016-03-28 10:46:51 +00:00
} ) ;
angularModule = setupModuleLoader ( window ) ;
2016-04-18 12:34:29 +00:00
try {
angularModule ( 'ngLocale' ) ;
} catch ( e ) {
angularModule ( 'ngLocale' , [ ] ) . provider ( '$locale' , $LocaleProvider ) ;
}
2016-03-28 10:46:51 +00:00
angularModule ( 'ng' , [ 'ngLocale' ] , [ '$provide' ,
function ngModule ( $provide ) {
// $$sanitizeUriProvider needs to be before $compileProvider as it is used by it.
$provide . provider ( {
$$sanitizeUri : $$SanitizeUriProvider
} ) ;
$provide . provider ( '$compile' , $CompileProvider ) .
directive ( {
a : htmlAnchorDirective ,
input : inputDirective ,
textarea : inputDirective ,
form : formDirective ,
script : scriptDirective ,
select : selectDirective ,
style : styleDirective ,
option : optionDirective ,
ngBind : ngBindDirective ,
ngBindHtml : ngBindHtmlDirective ,
ngBindTemplate : ngBindTemplateDirective ,
ngClass : ngClassDirective ,
ngClassEven : ngClassEvenDirective ,
ngClassOdd : ngClassOddDirective ,
ngCloak : ngCloakDirective ,
ngController : ngControllerDirective ,
ngForm : ngFormDirective ,
ngHide : ngHideDirective ,
ngIf : ngIfDirective ,
ngInclude : ngIncludeDirective ,
ngInit : ngInitDirective ,
ngNonBindable : ngNonBindableDirective ,
ngPluralize : ngPluralizeDirective ,
ngRepeat : ngRepeatDirective ,
ngShow : ngShowDirective ,
ngStyle : ngStyleDirective ,
ngSwitch : ngSwitchDirective ,
ngSwitchWhen : ngSwitchWhenDirective ,
ngSwitchDefault : ngSwitchDefaultDirective ,
ngOptions : ngOptionsDirective ,
ngTransclude : ngTranscludeDirective ,
ngModel : ngModelDirective ,
ngList : ngListDirective ,
ngChange : ngChangeDirective ,
2018-05-05 12:13:16 +02:00
pattern : patternDirective ,
ngPattern : patternDirective ,
2016-03-28 10:46:51 +00:00
required : requiredDirective ,
ngRequired : requiredDirective ,
2018-05-05 12:13:16 +02:00
minlength : minlengthDirective ,
ngMinlength : minlengthDirective ,
maxlength : maxlengthDirective ,
ngMaxlength : maxlengthDirective ,
ngValue : ngValueDirective ,
ngModelOptions : ngModelOptionsDirective
2016-03-28 10:46:51 +00:00
} ) .
directive ( {
ngInclude : ngIncludeFillContentDirective
} ) .
directive ( ngAttributeAliasDirectives ) .
directive ( ngEventDirectives ) ;
$provide . provider ( {
$anchorScroll : $AnchorScrollProvider ,
$animate : $AnimateProvider ,
$browser : $BrowserProvider ,
$cacheFactory : $CacheFactoryProvider ,
$controller : $ControllerProvider ,
$document : $DocumentProvider ,
$exceptionHandler : $ExceptionHandlerProvider ,
$filter : $FilterProvider ,
$interpolate : $InterpolateProvider ,
$interval : $IntervalProvider ,
$http : $HttpProvider ,
$httpBackend : $HttpBackendProvider ,
$location : $LocationProvider ,
$log : $LogProvider ,
$parse : $ParseProvider ,
$rootScope : $RootScopeProvider ,
$q : $QProvider ,
2018-05-05 12:13:16 +02:00
$$q : $$QProvider ,
2016-03-28 10:46:51 +00:00
$sce : $SceProvider ,
$sceDelegate : $SceDelegateProvider ,
$sniffer : $SnifferProvider ,
$templateCache : $TemplateCacheProvider ,
2018-05-05 12:13:16 +02:00
$templateRequest : $TemplateRequestProvider ,
$$testability : $$TestabilityProvider ,
2016-03-28 10:46:51 +00:00
$timeout : $TimeoutProvider ,
2018-05-05 12:13:16 +02:00
$window : $WindowProvider ,
$$rAF : $$RAFProvider ,
$$asyncCallback : $$AsyncCallbackProvider ,
$$jqLite : $$jqLiteProvider
2016-03-28 10:46:51 +00:00
} ) ;
}
] ) ;
}
2018-05-05 12:13:16 +02:00
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Any commits to this file should be reviewed with security in mind . *
* Changes to this file can potentially create security vulnerabilities . *
* An approval from 2 Core members with history of modifying *
* this file is required . *
* *
* Does the change somehow allow for arbitrary javascript to be executed ? *
* Or allows for someone to change the prototype of built - in objects ? *
* Or gives undesired access to variables likes document or window ? *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/ * g l o b a l J Q L i t e P r o t o t y p e : t r u e ,
addEventListenerFn : true ,
removeEventListenerFn : true ,
BOOLEAN _ATTR : true ,
ALIASED _ATTR : true ,
2016-03-28 10:46:51 +00:00
* /
//////////////////////////////////
//JQLite
//////////////////////////////////
/ * *
* @ ngdoc function
* @ name angular . element
2018-05-05 12:13:16 +02:00
* @ module ng
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Wraps a raw DOM element or HTML string as a [ jQuery ] ( http : //jquery.com) element.
*
* If jQuery is available , ` angular.element ` is an alias for the
* [ jQuery ] ( http : //api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element`
2016-04-18 12:34:29 +00:00
* delegates to Angular ' s built - in subset of jQuery , called "jQuery lite" or "jqLite."
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* < div class = "alert alert-success" > jqLite is a tiny , API - compatible subset of jQuery that allows
* Angular to manipulate the DOM in a cross - browser compatible way . * * jqLite * * implements only the most
* commonly needed functionality with the goal of having a very small footprint . < / d i v >
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* To use ` jQuery ` , simply ensure it is loaded before the ` angular.js ` file .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* < div class = "alert" > * * Note : * * all element references in Angular are always wrapped with jQuery or
* jqLite ; they are never raw DOM references . < / d i v >
2016-03-28 10:46:51 +00:00
*
* # # Angular ' s jqLite
* jqLite provides only the following jQuery methods :
*
* - [ ` addClass() ` ] ( http : //api.jquery.com/addClass/)
* - [ ` after() ` ] ( http : //api.jquery.com/after/)
* - [ ` append() ` ] ( http : //api.jquery.com/append/)
2018-05-05 12:13:16 +02:00
* - [ ` attr() ` ] ( http : //api.jquery.com/attr/) - Does not support functions as parameters
* - [ ` bind() ` ] ( http : //api.jquery.com/bind/) - Does not support namespaces, selectors or eventData
2016-03-28 10:46:51 +00:00
* - [ ` children() ` ] ( http : //api.jquery.com/children/) - Does not support selectors
* - [ ` clone() ` ] ( http : //api.jquery.com/clone/)
* - [ ` contents() ` ] ( http : //api.jquery.com/contents/)
2018-05-05 12:13:16 +02:00
* - [ ` css() ` ] ( http : //api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`. As a setter, does not convert numbers to strings or append 'px'.
2016-03-28 10:46:51 +00:00
* - [ ` data() ` ] ( http : //api.jquery.com/data/)
2018-05-05 12:13:16 +02:00
* - [ ` detach() ` ] ( http : //api.jquery.com/detach/)
2016-03-28 10:46:51 +00:00
* - [ ` empty() ` ] ( http : //api.jquery.com/empty/)
* - [ ` eq() ` ] ( http : //api.jquery.com/eq/)
* - [ ` find() ` ] ( http : //api.jquery.com/find/) - Limited to lookups by tag name
* - [ ` hasClass() ` ] ( http : //api.jquery.com/hasClass/)
* - [ ` html() ` ] ( http : //api.jquery.com/html/)
* - [ ` next() ` ] ( http : //api.jquery.com/next/) - Does not support selectors
* - [ ` on() ` ] ( http : //api.jquery.com/on/) - Does not support namespaces, selectors or eventData
2016-04-18 12:34:29 +00:00
* - [ ` off() ` ] ( http : //api.jquery.com/off/) - Does not support namespaces or selectors
2016-03-28 10:46:51 +00:00
* - [ ` one() ` ] ( http : //api.jquery.com/one/) - Does not support namespaces or selectors
* - [ ` parent() ` ] ( http : //api.jquery.com/parent/) - Does not support selectors
* - [ ` prepend() ` ] ( http : //api.jquery.com/prepend/)
* - [ ` prop() ` ] ( http : //api.jquery.com/prop/)
* - [ ` ready() ` ] ( http : //api.jquery.com/ready/)
* - [ ` remove() ` ] ( http : //api.jquery.com/remove/)
* - [ ` removeAttr() ` ] ( http : //api.jquery.com/removeAttr/)
* - [ ` removeClass() ` ] ( http : //api.jquery.com/removeClass/)
* - [ ` removeData() ` ] ( http : //api.jquery.com/removeData/)
* - [ ` replaceWith() ` ] ( http : //api.jquery.com/replaceWith/)
* - [ ` text() ` ] ( http : //api.jquery.com/text/)
* - [ ` toggleClass() ` ] ( http : //api.jquery.com/toggleClass/)
* - [ ` triggerHandler() ` ] ( http : //api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers.
2018-05-05 12:13:16 +02:00
* - [ ` unbind() ` ] ( http : //api.jquery.com/unbind/) - Does not support namespaces
2016-03-28 10:46:51 +00:00
* - [ ` val() ` ] ( http : //api.jquery.com/val/)
* - [ ` wrap() ` ] ( http : //api.jquery.com/wrap/)
*
* # # jQuery / jqLite Extras
* Angular also provides the following additional methods and events to both jQuery and jqLite :
*
* # # # Events
* - ` $ destroy ` - AngularJS intercepts all jqLite / jQuery ' s DOM destruction apis and fires this event
* on all DOM nodes being removed . This can be used to clean up any 3 rd party bindings to the DOM
* element before it is removed .
*
* # # # Methods
* - ` controller(name) ` - retrieves the controller of the current element or its parent . By default
* retrieves controller associated with the ` ngController ` directive . If ` name ` is provided as
* camelCase directive name , then the controller for this directive will be retrieved ( e . g .
* ` 'ngModel' ` ) .
* - ` injector() ` - retrieves the injector of the current element or its parent .
2018-05-05 12:13:16 +02:00
* - ` scope() ` - retrieves the { @ link ng . $rootScope . Scope scope } of the current
* element or its parent . Requires { @ link guide / production # disabling - debug - data Debug Data } to
* be enabled .
* - ` isolateScope() ` - retrieves an isolate { @ link ng . $rootScope . Scope scope } if one is attached directly to the
2016-03-28 10:46:51 +00:00
* current element . This getter should be used only on elements that contain a directive which starts a new isolate
* scope . Calling ` scope() ` on this element always returns the original non - isolate scope .
2018-05-05 12:13:16 +02:00
* Requires { @ link guide / production # disabling - debug - data Debug Data } to be enabled .
2016-03-28 10:46:51 +00:00
* - ` inheritedData() ` - same as ` data() ` , but walks up the DOM until a value is found or the top
* parent element is reached .
*
* @ param { string | DOMElement } element HTML string or DOMElement to be wrapped into jQuery .
* @ returns { Object } jQuery object .
* /
2018-05-05 12:13:16 +02:00
JQLite . expando = 'ng339' ;
2016-03-28 10:46:51 +00:00
var jqCache = JQLite . cache = { } ,
jqId = 1 ,
2018-05-05 12:13:16 +02:00
addEventListenerFn = function ( element , type , fn ) {
element . addEventListener ( type , fn , false ) ;
} ,
removeEventListenerFn = function ( element , type , fn ) {
element . removeEventListener ( type , fn , false ) ;
} ;
/ *
* ! ! ! This is an undocumented "private" function ! ! !
* /
JQLite . _data = function ( node ) {
//jQuery always returns an object on cache miss
return this . cache [ node [ this . expando ] ] || { } ;
} ;
2016-03-28 10:46:51 +00:00
function jqNextId ( ) { return ++ jqId ; }
var SPECIAL _CHARS _REGEXP = /([\:\-\_]+(.))/g ;
var MOZ _HACK _REGEXP = /^moz([A-Z])/ ;
2018-05-05 12:13:16 +02:00
var MOUSE _EVENT _MAP = { mouseleave : "mouseout" , mouseenter : "mouseover" } ;
2016-03-28 10:46:51 +00:00
var jqLiteMinErr = minErr ( 'jqLite' ) ;
/ * *
* Converts snake _case to camelCase .
* Also there is special case for Moz prefix starting with upper case letter .
* @ param name Name to normalize
* /
function camelCase ( name ) {
return name .
replace ( SPECIAL _CHARS _REGEXP , function ( _ , separator , letter , offset ) {
return offset ? letter . toUpperCase ( ) : letter ;
} ) .
replace ( MOZ _HACK _REGEXP , 'Moz$1' ) ;
}
2018-05-05 12:13:16 +02:00
var SINGLE _TAG _REGEXP = /^<(\w+)\s*\/?>(?:<\/\1>|)$/ ;
var HTML _REGEXP = /<|&#?\w+;/ ;
var TAG _NAME _REGEXP = /<([\w:]+)/ ;
var XHTML _TAG _REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
var wrapMap = {
'option' : [ 1 , '<select multiple="multiple">' , '</select>' ] ,
'thead' : [ 1 , '<table>' , '</table>' ] ,
'col' : [ 2 , '<table><colgroup>' , '</colgroup></table>' ] ,
'tr' : [ 2 , '<table><tbody>' , '</tbody></table>' ] ,
'td' : [ 3 , '<table><tbody><tr>' , '</tr></tbody></table>' ] ,
'_default' : [ 0 , "" , "" ]
} ;
wrapMap . optgroup = wrapMap . option ;
wrapMap . tbody = wrapMap . tfoot = wrapMap . colgroup = wrapMap . caption = wrapMap . thead ;
wrapMap . th = wrapMap . td ;
function jqLiteIsTextNode ( html ) {
return ! HTML _REGEXP . test ( html ) ;
}
function jqLiteAcceptsData ( node ) {
// The window object can accept data but has no nodeType
// Otherwise we are only interested in elements (1) and documents (9)
var nodeType = node . nodeType ;
return nodeType === NODE _TYPE _ELEMENT || ! nodeType || nodeType === NODE _TYPE _DOCUMENT ;
}
function jqLiteBuildFragment ( html , context ) {
var tmp , tag , wrap ,
fragment = context . createDocumentFragment ( ) ,
nodes = [ ] , i ;
if ( jqLiteIsTextNode ( html ) ) {
// Convert non-html into a text node
nodes . push ( context . createTextNode ( html ) ) ;
} else {
// Convert html into DOM nodes
tmp = tmp || fragment . appendChild ( context . createElement ( "div" ) ) ;
tag = ( TAG _NAME _REGEXP . exec ( html ) || [ "" , "" ] ) [ 1 ] . toLowerCase ( ) ;
wrap = wrapMap [ tag ] || wrapMap . _default ;
tmp . innerHTML = wrap [ 1 ] + html . replace ( XHTML _TAG _REGEXP , "<$1></$2>" ) + wrap [ 2 ] ;
// Descend through wrappers to the right content
i = wrap [ 0 ] ;
while ( i -- ) {
tmp = tmp . lastChild ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
nodes = concat ( nodes , tmp . childNodes ) ;
tmp = fragment . firstChild ;
tmp . textContent = "" ;
}
// Remove wrapper from fragment
fragment . textContent = "" ;
fragment . innerHTML = "" ; // Clear inner HTML
forEach ( nodes , function ( node ) {
fragment . appendChild ( node ) ;
} ) ;
return fragment ;
}
function jqLiteParseHTML ( html , context ) {
context = context || document ;
var parsed ;
if ( ( parsed = SINGLE _TAG _REGEXP . exec ( html ) ) ) {
return [ context . createElement ( parsed [ 1 ] ) ] ;
}
if ( ( parsed = jqLiteBuildFragment ( html , context ) ) ) {
return parsed . childNodes ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
return [ ] ;
2016-03-28 10:46:51 +00:00
}
/////////////////////////////////////////////
function JQLite ( element ) {
if ( element instanceof JQLite ) {
return element ;
}
2018-05-05 12:13:16 +02:00
var argIsString ;
if ( isString ( element ) ) {
element = trim ( element ) ;
argIsString = true ;
}
2016-03-28 10:46:51 +00:00
if ( ! ( this instanceof JQLite ) ) {
2018-05-05 12:13:16 +02:00
if ( argIsString && element . charAt ( 0 ) != '<' ) {
2016-03-28 10:46:51 +00:00
throw jqLiteMinErr ( 'nosel' , 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element' ) ;
}
return new JQLite ( element ) ;
}
2018-05-05 12:13:16 +02:00
if ( argIsString ) {
jqLiteAddNodes ( this , jqLiteParseHTML ( element ) ) ;
2016-03-28 10:46:51 +00:00
} else {
jqLiteAddNodes ( this , element ) ;
}
}
function jqLiteClone ( element ) {
return element . cloneNode ( true ) ;
}
2018-05-05 12:13:16 +02:00
function jqLiteDealoc ( element , onlyDescendants ) {
if ( ! onlyDescendants ) jqLiteRemoveData ( element ) ;
if ( element . querySelectorAll ) {
var descendants = element . querySelectorAll ( '*' ) ;
for ( var i = 0 , l = descendants . length ; i < l ; i ++ ) {
jqLiteRemoveData ( descendants [ i ] ) ;
}
2016-03-28 10:46:51 +00:00
}
}
function jqLiteOff ( element , type , fn , unsupported ) {
if ( isDefined ( unsupported ) ) throw jqLiteMinErr ( 'offargs' , 'jqLite#off() does not support the `selector` argument' ) ;
2018-05-05 12:13:16 +02:00
var expandoStore = jqLiteExpandoStore ( element ) ;
var events = expandoStore && expandoStore . events ;
var handle = expandoStore && expandoStore . handle ;
2016-03-28 10:46:51 +00:00
if ( ! handle ) return ; //no listeners registered
2018-05-05 12:13:16 +02:00
if ( ! type ) {
for ( type in events ) {
if ( type !== '$destroy' ) {
removeEventListenerFn ( element , type , handle ) ;
}
2016-03-28 10:46:51 +00:00
delete events [ type ] ;
2018-05-05 12:13:16 +02:00
}
2016-03-28 10:46:51 +00:00
} else {
forEach ( type . split ( ' ' ) , function ( type ) {
2018-05-05 12:13:16 +02:00
if ( isDefined ( fn ) ) {
var listenerFns = events [ type ] ;
arrayRemove ( listenerFns || [ ] , fn ) ;
if ( listenerFns && listenerFns . length > 0 ) {
return ;
}
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
removeEventListenerFn ( element , type , handle ) ;
delete events [ type ] ;
2016-03-28 10:46:51 +00:00
} ) ;
}
}
function jqLiteRemoveData ( element , name ) {
2018-05-05 12:13:16 +02:00
var expandoId = element . ng339 ;
var expandoStore = expandoId && jqCache [ expandoId ] ;
2016-03-28 10:46:51 +00:00
if ( expandoStore ) {
if ( name ) {
2018-05-05 12:13:16 +02:00
delete expandoStore . data [ name ] ;
2016-03-28 10:46:51 +00:00
return ;
}
if ( expandoStore . handle ) {
2018-05-05 12:13:16 +02:00
if ( expandoStore . events . $destroy ) {
expandoStore . handle ( { } , '$destroy' ) ;
}
2016-03-28 10:46:51 +00:00
jqLiteOff ( element ) ;
}
delete jqCache [ expandoId ] ;
2018-05-05 12:13:16 +02:00
element . ng339 = undefined ; // don't delete DOM expandos. IE and Chrome don't like it
2016-03-28 10:46:51 +00:00
}
}
2018-05-05 12:13:16 +02:00
function jqLiteExpandoStore ( element , createIfNecessary ) {
var expandoId = element . ng339 ,
expandoStore = expandoId && jqCache [ expandoId ] ;
if ( createIfNecessary && ! expandoStore ) {
element . ng339 = expandoId = jqNextId ( ) ;
expandoStore = jqCache [ expandoId ] = { events : { } , data : { } , handle : undefined } ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
return expandoStore ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
2016-03-28 10:46:51 +00:00
function jqLiteData ( element , key , value ) {
2018-05-05 12:13:16 +02:00
if ( jqLiteAcceptsData ( element ) ) {
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
var isSimpleSetter = isDefined ( value ) ;
var isSimpleGetter = ! isSimpleSetter && key && ! isObject ( key ) ;
var massGetter = ! key ;
var expandoStore = jqLiteExpandoStore ( element , ! isSimpleGetter ) ;
var data = expandoStore && expandoStore . data ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
if ( isSimpleSetter ) { // data('key', value)
data [ key ] = value ;
} else {
if ( massGetter ) { // data()
return data ;
2016-03-28 10:46:51 +00:00
} else {
2018-05-05 12:13:16 +02:00
if ( isSimpleGetter ) { // data('key')
// don't force creation of expandoStore if it doesn't exist yet
return data && data [ key ] ;
} else { // mass-setter: data({key1: val1, key2: val2})
extend ( data , key ) ;
}
2016-03-28 10:46:51 +00:00
}
}
}
}
function jqLiteHasClass ( element , selector ) {
if ( ! element . getAttribute ) return false ;
return ( ( " " + ( element . getAttribute ( 'class' ) || '' ) + " " ) . replace ( /[\n\t]/g , " " ) .
2018-05-05 12:13:16 +02:00
indexOf ( " " + selector + " " ) > - 1 ) ;
2016-03-28 10:46:51 +00:00
}
function jqLiteRemoveClass ( element , cssClasses ) {
if ( cssClasses && element . setAttribute ) {
forEach ( cssClasses . split ( ' ' ) , function ( cssClass ) {
element . setAttribute ( 'class' , trim (
( " " + ( element . getAttribute ( 'class' ) || '' ) + " " )
. replace ( /[\n\t]/g , " " )
. replace ( " " + trim ( cssClass ) + " " , " " ) )
) ;
} ) ;
}
}
function jqLiteAddClass ( element , cssClasses ) {
if ( cssClasses && element . setAttribute ) {
var existingClasses = ( ' ' + ( element . getAttribute ( 'class' ) || '' ) + ' ' )
. replace ( /[\n\t]/g , " " ) ;
forEach ( cssClasses . split ( ' ' ) , function ( cssClass ) {
cssClass = trim ( cssClass ) ;
if ( existingClasses . indexOf ( ' ' + cssClass + ' ' ) === - 1 ) {
existingClasses += cssClass + ' ' ;
}
} ) ;
element . setAttribute ( 'class' , trim ( existingClasses ) ) ;
}
}
2018-05-05 12:13:16 +02:00
2016-03-28 10:46:51 +00:00
function jqLiteAddNodes ( root , elements ) {
2018-05-05 12:13:16 +02:00
// THIS CODE IS VERY HOT. Don't make changes without benchmarking.
2016-03-28 10:46:51 +00:00
if ( elements ) {
2018-05-05 12:13:16 +02:00
// if a Node (the most common case)
if ( elements . nodeType ) {
root [ root . length ++ ] = elements ;
} else {
var length = elements . length ;
// if an Array or NodeList and not a Window
if ( typeof length === 'number' && elements . window !== elements ) {
if ( length ) {
for ( var i = 0 ; i < length ; i ++ ) {
root [ root . length ++ ] = elements [ i ] ;
}
}
} else {
root [ root . length ++ ] = elements ;
}
2016-03-28 10:46:51 +00:00
}
}
}
2018-05-05 12:13:16 +02:00
2016-03-28 10:46:51 +00:00
function jqLiteController ( element , name ) {
2018-05-05 12:13:16 +02:00
return jqLiteInheritedData ( element , '$' + ( name || 'ngController' ) + 'Controller' ) ;
2016-03-28 10:46:51 +00:00
}
function jqLiteInheritedData ( element , name , value ) {
// if element is the document object work with the html element instead
// this makes $(document).scope() possible
2018-05-05 12:13:16 +02:00
if ( element . nodeType == NODE _TYPE _DOCUMENT ) {
element = element . documentElement ;
2016-03-28 10:46:51 +00:00
}
var names = isArray ( name ) ? name : [ name ] ;
2018-05-05 12:13:16 +02:00
while ( element ) {
2016-03-28 10:46:51 +00:00
for ( var i = 0 , ii = names . length ; i < ii ; i ++ ) {
2018-05-05 12:13:16 +02:00
if ( ( value = jqLite . data ( element , names [ i ] ) ) !== undefined ) return value ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
// If dealing with a document fragment node with a host element, and no parent, use the host
// element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM
// to lookup parent controllers.
element = element . parentNode || ( element . nodeType === NODE _TYPE _DOCUMENT _FRAGMENT && element . host ) ;
2016-03-28 10:46:51 +00:00
}
}
function jqLiteEmpty ( element ) {
2018-05-05 12:13:16 +02:00
jqLiteDealoc ( element , true ) ;
2016-03-28 10:46:51 +00:00
while ( element . firstChild ) {
element . removeChild ( element . firstChild ) ;
}
}
2018-05-05 12:13:16 +02:00
function jqLiteRemove ( element , keepData ) {
if ( ! keepData ) jqLiteDealoc ( element ) ;
var parent = element . parentNode ;
if ( parent ) parent . removeChild ( element ) ;
}
function jqLiteDocumentLoaded ( action , win ) {
win = win || window ;
if ( win . document . readyState === 'complete' ) {
// Force the action to be run async for consistent behaviour
// from the action's point of view
// i.e. it will definitely not be in a $apply
win . setTimeout ( action ) ;
} else {
// No need to unbind this handler as load is only ever called once
jqLite ( win ) . on ( 'load' , action ) ;
}
}
2016-03-28 10:46:51 +00:00
//////////////////////////////////////////
// Functions which are declared directly.
//////////////////////////////////////////
var JQLitePrototype = JQLite . prototype = {
ready : function ( fn ) {
var fired = false ;
function trigger ( ) {
if ( fired ) return ;
fired = true ;
fn ( ) ;
}
2018-05-05 12:13:16 +02:00
// check if document is already loaded
if ( document . readyState === 'complete' ) {
2016-03-28 10:46:51 +00:00
setTimeout ( trigger ) ;
} else {
this . on ( 'DOMContentLoaded' , trigger ) ; // works for modern browsers and IE9
// we can not use jqLite since we are not done loading and jQuery could be loaded later.
// jshint -W064
JQLite ( window ) . on ( 'load' , trigger ) ; // fallback to window.onload for others
// jshint +W064
}
} ,
toString : function ( ) {
var value = [ ] ;
2018-05-05 12:13:16 +02:00
forEach ( this , function ( e ) { value . push ( '' + e ) ; } ) ;
2016-03-28 10:46:51 +00:00
return '[' + value . join ( ', ' ) + ']' ;
} ,
eq : function ( index ) {
return ( index >= 0 ) ? jqLite ( this [ index ] ) : jqLite ( this [ this . length + index ] ) ;
} ,
length : 0 ,
push : push ,
sort : [ ] . sort ,
splice : [ ] . splice
} ;
//////////////////////////////////////////
// Functions iterating getter/setters.
// these functions return self on setter and
// value on get.
//////////////////////////////////////////
var BOOLEAN _ATTR = { } ;
forEach ( 'multiple,selected,checked,disabled,readOnly,required,open' . split ( ',' ) , function ( value ) {
BOOLEAN _ATTR [ lowercase ( value ) ] = value ;
} ) ;
var BOOLEAN _ELEMENTS = { } ;
forEach ( 'input,select,option,textarea,button,form,details' . split ( ',' ) , function ( value ) {
2018-05-05 12:13:16 +02:00
BOOLEAN _ELEMENTS [ value ] = true ;
2016-03-28 10:46:51 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
var ALIASED _ATTR = {
'ngMinlength' : 'minlength' ,
'ngMaxlength' : 'maxlength' ,
'ngMin' : 'min' ,
'ngMax' : 'max' ,
'ngPattern' : 'pattern'
} ;
2016-03-28 10:46:51 +00:00
function getBooleanAttrName ( element , name ) {
// check dom last since we will most likely fail on name
var booleanAttr = BOOLEAN _ATTR [ name . toLowerCase ( ) ] ;
// booleanAttr is here twice to minimize DOM access
2018-05-05 12:13:16 +02:00
return booleanAttr && BOOLEAN _ELEMENTS [ nodeName _ ( element ) ] && booleanAttr ;
}
function getAliasedAttrName ( element , name ) {
var nodeName = element . nodeName ;
return ( nodeName === 'INPUT' || nodeName === 'TEXTAREA' ) && ALIASED _ATTR [ name ] ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
forEach ( {
data : jqLiteData ,
removeData : jqLiteRemoveData
} , function ( fn , name ) {
JQLite [ name ] = fn ;
} ) ;
2016-03-28 10:46:51 +00:00
forEach ( {
data : jqLiteData ,
inheritedData : jqLiteInheritedData ,
scope : function ( element ) {
// Can't use jqLiteData here directly so we stay compatible with jQuery!
2018-05-05 12:13:16 +02:00
return jqLite . data ( element , '$scope' ) || jqLiteInheritedData ( element . parentNode || element , [ '$isolateScope' , '$scope' ] ) ;
2016-03-28 10:46:51 +00:00
} ,
isolateScope : function ( element ) {
// Can't use jqLiteData here directly so we stay compatible with jQuery!
2018-05-05 12:13:16 +02:00
return jqLite . data ( element , '$isolateScope' ) || jqLite . data ( element , '$isolateScopeNoTemplate' ) ;
2016-03-28 10:46:51 +00:00
} ,
2018-05-05 12:13:16 +02:00
controller : jqLiteController ,
2016-03-28 10:46:51 +00:00
injector : function ( element ) {
return jqLiteInheritedData ( element , '$injector' ) ;
} ,
2018-05-05 12:13:16 +02:00
removeAttr : function ( element , name ) {
2016-03-28 10:46:51 +00:00
element . removeAttribute ( name ) ;
} ,
hasClass : jqLiteHasClass ,
css : function ( element , name , value ) {
name = camelCase ( name ) ;
if ( isDefined ( value ) ) {
element . style [ name ] = value ;
} else {
2018-05-05 12:13:16 +02:00
return element . style [ name ] ;
2016-03-28 10:46:51 +00:00
}
} ,
2018-05-05 12:13:16 +02:00
attr : function ( element , name , value ) {
var nodeType = element . nodeType ;
if ( nodeType === NODE _TYPE _TEXT || nodeType === NODE _TYPE _ATTRIBUTE || nodeType === NODE _TYPE _COMMENT ) {
return ;
}
2016-03-28 10:46:51 +00:00
var lowercasedName = lowercase ( name ) ;
if ( BOOLEAN _ATTR [ lowercasedName ] ) {
if ( isDefined ( value ) ) {
if ( ! ! value ) {
element [ name ] = true ;
element . setAttribute ( name , lowercasedName ) ;
} else {
element [ name ] = false ;
element . removeAttribute ( lowercasedName ) ;
}
} else {
return ( element [ name ] ||
2018-05-05 12:13:16 +02:00
( element . attributes . getNamedItem ( name ) || noop ) . specified )
2016-03-28 10:46:51 +00:00
? lowercasedName
: undefined ;
}
} else if ( isDefined ( value ) ) {
element . setAttribute ( name , value ) ;
} else if ( element . getAttribute ) {
// the extra argument "2" is to get the right thing for a.href in IE, see jQuery code
// some elements (e.g. Document) don't have get attribute, so return undefined
var ret = element . getAttribute ( name , 2 ) ;
// normalize non-existing attributes to undefined (as jQuery)
return ret === null ? undefined : ret ;
}
} ,
prop : function ( element , name , value ) {
if ( isDefined ( value ) ) {
element [ name ] = value ;
} else {
return element [ name ] ;
}
} ,
text : ( function ( ) {
getText . $dv = '' ;
return getText ;
function getText ( element , value ) {
if ( isUndefined ( value ) ) {
2018-05-05 12:13:16 +02:00
var nodeType = element . nodeType ;
return ( nodeType === NODE _TYPE _ELEMENT || nodeType === NODE _TYPE _TEXT ) ? element . textContent : '' ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
element . textContent = value ;
2016-03-28 10:46:51 +00:00
}
} ) ( ) ,
val : function ( element , value ) {
if ( isUndefined ( value ) ) {
2018-05-05 12:13:16 +02:00
if ( element . multiple && nodeName _ ( element ) === 'select' ) {
2016-03-28 10:46:51 +00:00
var result = [ ] ;
2018-05-05 12:13:16 +02:00
forEach ( element . options , function ( option ) {
2016-03-28 10:46:51 +00:00
if ( option . selected ) {
result . push ( option . value || option . text ) ;
}
} ) ;
return result . length === 0 ? null : result ;
}
return element . value ;
}
element . value = value ;
} ,
html : function ( element , value ) {
if ( isUndefined ( value ) ) {
return element . innerHTML ;
}
2018-05-05 12:13:16 +02:00
jqLiteDealoc ( element , true ) ;
2016-03-28 10:46:51 +00:00
element . innerHTML = value ;
} ,
empty : jqLiteEmpty
2018-05-05 12:13:16 +02:00
} , function ( fn , name ) {
2016-03-28 10:46:51 +00:00
/ * *
* Properties : writes return selection , reads return first value
* /
JQLite . prototype [ name ] = function ( arg1 , arg2 ) {
var i , key ;
2018-05-05 12:13:16 +02:00
var nodeCount = this . length ;
2016-03-28 10:46:51 +00:00
// jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it
// in a way that survives minification.
// jqLiteEmpty takes no arguments but is a setter.
if ( fn !== jqLiteEmpty &&
2016-04-18 12:34:29 +00:00
( ( ( fn . length == 2 && ( fn !== jqLiteHasClass && fn !== jqLiteController ) ) ? arg1 : arg2 ) === undefined ) ) {
2016-03-28 10:46:51 +00:00
if ( isObject ( arg1 ) ) {
// we are a write, but the object properties are the key/values
2018-05-05 12:13:16 +02:00
for ( i = 0 ; i < nodeCount ; i ++ ) {
2016-03-28 10:46:51 +00:00
if ( fn === jqLiteData ) {
// data() takes the whole object in jQuery
fn ( this [ i ] , arg1 ) ;
} else {
for ( key in arg1 ) {
fn ( this [ i ] , key , arg1 [ key ] ) ;
}
}
}
// return self for chaining
return this ;
} else {
// we are a read, so read the first child.
2018-05-05 12:13:16 +02:00
// TODO: do we still need this?
2016-03-28 10:46:51 +00:00
var value = fn . $dv ;
// Only if we have $dv do we iterate over all, otherwise it is just the first element.
2018-05-05 12:13:16 +02:00
var jj = ( value === undefined ) ? Math . min ( nodeCount , 1 ) : nodeCount ;
2016-03-28 10:46:51 +00:00
for ( var j = 0 ; j < jj ; j ++ ) {
var nodeValue = fn ( this [ j ] , arg1 , arg2 ) ;
value = value ? value + nodeValue : nodeValue ;
}
return value ;
}
} else {
// we are a write, so apply to all children
2018-05-05 12:13:16 +02:00
for ( i = 0 ; i < nodeCount ; i ++ ) {
2016-03-28 10:46:51 +00:00
fn ( this [ i ] , arg1 , arg2 ) ;
}
// return self for chaining
return this ;
}
} ;
} ) ;
function createEventHandler ( element , events ) {
2018-05-05 12:13:16 +02:00
var eventHandler = function ( event , type ) {
// jQuery specific api
event . isDefaultPrevented = function ( ) {
return event . defaultPrevented ;
} ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
var eventFns = events [ type || event . type ] ;
var eventFnsLength = eventFns ? eventFns . length : 0 ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
if ( ! eventFnsLength ) return ;
if ( isUndefined ( event . immediatePropagationStopped ) ) {
var originalStopImmediatePropagation = event . stopImmediatePropagation ;
event . stopImmediatePropagation = function ( ) {
event . immediatePropagationStopped = true ;
if ( event . stopPropagation ) {
event . stopPropagation ( ) ;
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
if ( originalStopImmediatePropagation ) {
originalStopImmediatePropagation . call ( event ) ;
}
2016-03-28 10:46:51 +00:00
} ;
}
2018-05-05 12:13:16 +02:00
event . isImmediatePropagationStopped = function ( ) {
return event . immediatePropagationStopped === true ;
2016-03-28 10:46:51 +00:00
} ;
// Copy event handlers in case event handlers array is modified during execution.
2018-05-05 12:13:16 +02:00
if ( ( eventFnsLength > 1 ) ) {
eventFns = shallowCopy ( eventFns ) ;
}
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
for ( var i = 0 ; i < eventFnsLength ; i ++ ) {
if ( ! event . isImmediatePropagationStopped ( ) ) {
eventFns [ i ] . call ( element , event ) ;
}
2016-03-28 10:46:51 +00:00
}
} ;
2018-05-05 12:13:16 +02:00
// TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all
// events on `element`
2016-03-28 10:46:51 +00:00
eventHandler . elem = element ;
return eventHandler ;
}
//////////////////////////////////////////
// Functions iterating traversal.
// These functions chain results into a single
// selector.
//////////////////////////////////////////
forEach ( {
removeData : jqLiteRemoveData ,
2018-05-05 12:13:16 +02:00
on : function jqLiteOn ( element , type , fn , unsupported ) {
2016-04-18 12:34:29 +00:00
if ( isDefined ( unsupported ) ) throw jqLiteMinErr ( 'onargs' , 'jqLite#on() does not support the `selector` or `eventData` parameters' ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
// Do not add event handlers to non-elements because they will not be cleaned up.
if ( ! jqLiteAcceptsData ( element ) ) {
return ;
}
var expandoStore = jqLiteExpandoStore ( element , true ) ;
var events = expandoStore . events ;
var handle = expandoStore . handle ;
if ( ! handle ) {
handle = expandoStore . handle = createEventHandler ( element , events ) ;
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
// http://jsperf.com/string-indexof-vs-split
var types = type . indexOf ( ' ' ) >= 0 ? type . split ( ' ' ) : [ type ] ;
var i = types . length ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
while ( i -- ) {
type = types [ i ] ;
2016-03-28 10:46:51 +00:00
var eventFns = events [ type ] ;
if ( ! eventFns ) {
2018-05-05 12:13:16 +02:00
events [ type ] = [ ] ;
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
if ( type === 'mouseenter' || type === 'mouseleave' ) {
2016-04-18 12:34:29 +00:00
// Refer to jQuery's implementation of mouseenter & mouseleave
// Read about mouseenter and mouseleave:
// http://www.quirksmode.org/js/events_mouse.html#link8
2018-05-05 12:13:16 +02:00
jqLiteOn ( element , MOUSE _EVENT _MAP [ type ] , function ( event ) {
2016-04-18 12:34:29 +00:00
var target = this , related = event . relatedTarget ;
// For mousenter/leave call the handler if related is outside the target.
// NB: No relatedTarget if the mouse left/entered the browser window
2018-05-05 12:13:16 +02:00
if ( ! related || ( related !== target && ! target . contains ( related ) ) ) {
2016-04-18 12:34:29 +00:00
handle ( event , type ) ;
}
} ) ;
} else {
2018-05-05 12:13:16 +02:00
if ( type !== '$destroy' ) {
addEventListenerFn ( element , type , handle ) ;
}
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
eventFns = events [ type ] ;
2016-03-28 10:46:51 +00:00
}
eventFns . push ( fn ) ;
2018-05-05 12:13:16 +02:00
}
2016-03-28 10:46:51 +00:00
} ,
off : jqLiteOff ,
one : function ( element , type , fn ) {
element = jqLite ( element ) ;
//add the listener twice so that when it is called
//you can remove the original function and still be
//able to call element.off(ev, fn) normally
element . on ( type , function onFn ( ) {
element . off ( type , fn ) ;
element . off ( type , onFn ) ;
} ) ;
element . on ( type , fn ) ;
} ,
replaceWith : function ( element , replaceNode ) {
var index , parent = element . parentNode ;
jqLiteDealoc ( element ) ;
2018-05-05 12:13:16 +02:00
forEach ( new JQLite ( replaceNode ) , function ( node ) {
2016-03-28 10:46:51 +00:00
if ( index ) {
parent . insertBefore ( node , index . nextSibling ) ;
} else {
parent . replaceChild ( node , element ) ;
}
index = node ;
} ) ;
} ,
children : function ( element ) {
var children = [ ] ;
2018-05-05 12:13:16 +02:00
forEach ( element . childNodes , function ( element ) {
if ( element . nodeType === NODE _TYPE _ELEMENT )
2016-03-28 10:46:51 +00:00
children . push ( element ) ;
} ) ;
return children ;
} ,
contents : function ( element ) {
2018-05-05 12:13:16 +02:00
return element . contentDocument || element . childNodes || [ ] ;
2016-03-28 10:46:51 +00:00
} ,
append : function ( element , node ) {
2018-05-05 12:13:16 +02:00
var nodeType = element . nodeType ;
if ( nodeType !== NODE _TYPE _ELEMENT && nodeType !== NODE _TYPE _DOCUMENT _FRAGMENT ) return ;
node = new JQLite ( node ) ;
for ( var i = 0 , ii = node . length ; i < ii ; i ++ ) {
var child = node [ i ] ;
element . appendChild ( child ) ;
}
2016-03-28 10:46:51 +00:00
} ,
prepend : function ( element , node ) {
2018-05-05 12:13:16 +02:00
if ( element . nodeType === NODE _TYPE _ELEMENT ) {
2016-03-28 10:46:51 +00:00
var index = element . firstChild ;
2018-05-05 12:13:16 +02:00
forEach ( new JQLite ( node ) , function ( child ) {
2016-03-28 10:46:51 +00:00
element . insertBefore ( child , index ) ;
} ) ;
}
} ,
wrap : function ( element , wrapNode ) {
2018-05-05 12:13:16 +02:00
wrapNode = jqLite ( wrapNode ) . eq ( 0 ) . clone ( ) [ 0 ] ;
2016-04-18 12:34:29 +00:00
var parent = element . parentNode ;
if ( parent ) {
parent . replaceChild ( wrapNode , element ) ;
}
wrapNode . appendChild ( element ) ;
2016-03-28 10:46:51 +00:00
} ,
2018-05-05 12:13:16 +02:00
remove : jqLiteRemove ,
detach : function ( element ) {
jqLiteRemove ( element , true ) ;
2016-03-28 10:46:51 +00:00
} ,
after : function ( element , newElement ) {
var index = element , parent = element . parentNode ;
2018-05-05 12:13:16 +02:00
newElement = new JQLite ( newElement ) ;
for ( var i = 0 , ii = newElement . length ; i < ii ; i ++ ) {
var node = newElement [ i ] ;
2016-03-28 10:46:51 +00:00
parent . insertBefore ( node , index . nextSibling ) ;
index = node ;
2018-05-05 12:13:16 +02:00
}
2016-03-28 10:46:51 +00:00
} ,
addClass : jqLiteAddClass ,
removeClass : jqLiteRemoveClass ,
toggleClass : function ( element , selector , condition ) {
2018-05-05 12:13:16 +02:00
if ( selector ) {
forEach ( selector . split ( ' ' ) , function ( className ) {
var classCondition = condition ;
if ( isUndefined ( classCondition ) ) {
classCondition = ! jqLiteHasClass ( element , className ) ;
}
( classCondition ? jqLiteAddClass : jqLiteRemoveClass ) ( element , className ) ;
} ) ;
2016-03-28 10:46:51 +00:00
}
} ,
parent : function ( element ) {
var parent = element . parentNode ;
2018-05-05 12:13:16 +02:00
return parent && parent . nodeType !== NODE _TYPE _DOCUMENT _FRAGMENT ? parent : null ;
2016-03-28 10:46:51 +00:00
} ,
next : function ( element ) {
2018-05-05 12:13:16 +02:00
return element . nextElementSibling ;
2016-03-28 10:46:51 +00:00
} ,
find : function ( element , selector ) {
if ( element . getElementsByTagName ) {
return element . getElementsByTagName ( selector ) ;
} else {
return [ ] ;
}
} ,
clone : jqLiteClone ,
2018-05-05 12:13:16 +02:00
triggerHandler : function ( element , event , extraParameters ) {
var dummyEvent , eventFnsCopy , handlerArgs ;
var eventName = event . type || event ;
var expandoStore = jqLiteExpandoStore ( element ) ;
var events = expandoStore && expandoStore . events ;
var eventFns = events && events [ eventName ] ;
if ( eventFns ) {
// Create a dummy event to pass to the handlers
dummyEvent = {
preventDefault : function ( ) { this . defaultPrevented = true ; } ,
isDefaultPrevented : function ( ) { return this . defaultPrevented === true ; } ,
stopImmediatePropagation : function ( ) { this . immediatePropagationStopped = true ; } ,
isImmediatePropagationStopped : function ( ) { return this . immediatePropagationStopped === true ; } ,
stopPropagation : noop ,
type : eventName ,
target : element
} ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
// If a custom event was provided then extend our dummy event with it
if ( event . type ) {
dummyEvent = extend ( dummyEvent , event ) ;
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
// Copy event handlers in case event handlers array is modified during execution.
eventFnsCopy = shallowCopy ( eventFns ) ;
handlerArgs = extraParameters ? [ dummyEvent ] . concat ( extraParameters ) : [ dummyEvent ] ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
forEach ( eventFnsCopy , function ( fn ) {
if ( ! dummyEvent . isImmediatePropagationStopped ( ) ) {
fn . apply ( element , handlerArgs ) ;
}
} ) ;
}
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
} , function ( fn , name ) {
2016-03-28 10:46:51 +00:00
/ * *
* chaining functions
* /
JQLite . prototype [ name ] = function ( arg1 , arg2 , arg3 ) {
var value ;
2018-05-05 12:13:16 +02:00
for ( var i = 0 , ii = this . length ; i < ii ; i ++ ) {
2016-03-28 10:46:51 +00:00
if ( isUndefined ( value ) ) {
value = fn ( this [ i ] , arg1 , arg2 , arg3 ) ;
if ( isDefined ( value ) ) {
// any function which returns a value needs to be wrapped
value = jqLite ( value ) ;
}
} else {
jqLiteAddNodes ( value , fn ( this [ i ] , arg1 , arg2 , arg3 ) ) ;
}
}
return isDefined ( value ) ? value : this ;
} ;
// bind legacy bind/unbind to on/off
JQLite . prototype . bind = JQLite . prototype . on ;
JQLite . prototype . unbind = JQLite . prototype . off ;
} ) ;
2018-05-05 12:13:16 +02:00
// Provider for private $$jqLite service
function $$jqLiteProvider ( ) {
this . $get = function $$jqLite ( ) {
return extend ( JQLite , {
hasClass : function ( node , classes ) {
if ( node . attr ) node = node [ 0 ] ;
return jqLiteHasClass ( node , classes ) ;
} ,
addClass : function ( node , classes ) {
if ( node . attr ) node = node [ 0 ] ;
return jqLiteAddClass ( node , classes ) ;
} ,
removeClass : function ( node , classes ) {
if ( node . attr ) node = node [ 0 ] ;
return jqLiteRemoveClass ( node , classes ) ;
}
} ) ;
} ;
}
2016-03-28 10:46:51 +00:00
/ * *
* Computes a hash of an 'obj' .
* Hash of a :
* string is string
* number is number as string
* object is either result of calling $$hashKey function on the object or uniquely generated id ,
* that is also assigned to the $$hashKey property of the object .
*
* @ param obj
* @ returns { string } hash string such that the same input will have the same hash string .
* The resulting string key is in 'type:hashKey' format .
* /
2018-05-05 12:13:16 +02:00
function hashKey ( obj , nextUidFn ) {
var key = obj && obj . $$hashKey ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
if ( key ) {
if ( typeof key === 'function' ) {
2016-03-28 10:46:51 +00:00
key = obj . $$hashKey ( ) ;
}
2018-05-05 12:13:16 +02:00
return key ;
}
var objType = typeof obj ;
if ( objType == 'function' || ( objType == 'object' && obj !== null ) ) {
key = obj . $$hashKey = objType + ':' + ( nextUidFn || nextUid ) ( ) ;
2016-03-28 10:46:51 +00:00
} else {
2018-05-05 12:13:16 +02:00
key = objType + ':' + obj ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
return key ;
2016-03-28 10:46:51 +00:00
}
/ * *
* HashMap which can use objects as keys
* /
2018-05-05 12:13:16 +02:00
function HashMap ( array , isolatedUid ) {
if ( isolatedUid ) {
var uid = 0 ;
this . nextUid = function ( ) {
return ++ uid ;
} ;
}
2016-03-28 10:46:51 +00:00
forEach ( array , this . put , this ) ;
}
HashMap . prototype = {
/ * *
* Store key value pair
* @ param key key to store can be any type
* @ param value value to store can be any type
* /
put : function ( key , value ) {
2018-05-05 12:13:16 +02:00
this [ hashKey ( key , this . nextUid ) ] = value ;
2016-03-28 10:46:51 +00:00
} ,
/ * *
* @ param key
2018-05-05 12:13:16 +02:00
* @ returns { Object } the value for the key
2016-03-28 10:46:51 +00:00
* /
get : function ( key ) {
2018-05-05 12:13:16 +02:00
return this [ hashKey ( key , this . nextUid ) ] ;
2016-03-28 10:46:51 +00:00
} ,
/ * *
* Remove the key / value pair
* @ param key
* /
remove : function ( key ) {
2018-05-05 12:13:16 +02:00
var value = this [ key = hashKey ( key , this . nextUid ) ] ;
2016-03-28 10:46:51 +00:00
delete this [ key ] ;
return value ;
}
} ;
/ * *
* @ ngdoc function
2018-05-05 12:13:16 +02:00
* @ module ng
2016-03-28 10:46:51 +00:00
* @ name angular . injector
2018-05-05 12:13:16 +02:00
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
2018-05-05 12:13:16 +02:00
* Creates an injector object that can be used for retrieving services as well as for
2016-03-28 10:46:51 +00:00
* dependency injection ( see { @ link guide / di dependency injection } ) .
*
* @ param { Array . < string | Function > } modules A list of module functions or their aliases . See
2018-05-05 12:13:16 +02:00
* { @ link angular . module } . The ` ng ` module must be explicitly added .
* @ param { boolean = } [ strictDi = false ] Whether the injector should be in strict mode , which
* disallows argument name annotation inference .
* @ returns { injector } Injector object . See { @ link auto . $injector $injector } .
2016-03-28 10:46:51 +00:00
*
* @ example
* Typical usage
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
* // create an injector
* var $injector = angular . injector ( [ 'ng' ] ) ;
*
* // use the injector to kick off your application
* // use the type inference to auto inject arguments, or use implicit injection
2018-05-05 12:13:16 +02:00
* $injector . invoke ( function ( $rootScope , $compile , $document ) {
2016-03-28 10:46:51 +00:00
* $compile ( $document ) ( $rootScope ) ;
* $rootScope . $digest ( ) ;
* } ) ;
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
* Sometimes you want to get access to the injector of a currently running Angular app
* from outside Angular . Perhaps , you want to inject and compile some markup after the
2018-05-05 12:13:16 +02:00
* application has been bootstrapped . You can do this using the extra ` injector() ` added
2016-03-28 10:46:51 +00:00
* to JQuery / jqLite elements . See { @ link angular . element } .
*
* * This is fairly rare but could be the case if a third party library is injecting the
* markup . *
*
* In the following example a new block of HTML containing a ` ng-controller `
* directive is added to the end of the document body by JQuery . We then compile and link
* it into the current AngularJS scope .
*
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
* var $div = $ ( '<div ng-controller="MyCtrl">{{content.label}}</div>' ) ;
* $ ( document . body ) . append ( $div ) ;
*
* angular . element ( document ) . injector ( ) . invoke ( function ( $compile ) {
* var scope = angular . element ( $div ) . scope ( ) ;
* $compile ( $div ) ( scope ) ;
* } ) ;
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
* /
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc module
* @ name auto
2016-03-28 10:46:51 +00:00
* @ description
*
2018-05-05 12:13:16 +02:00
* Implicit module which gets automatically added to each { @ link auto . $injector $injector } .
2016-03-28 10:46:51 +00:00
* /
2016-04-18 12:34:29 +00:00
var FN _ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m ;
2016-03-28 10:46:51 +00:00
var FN _ARG _SPLIT = /,/ ;
var FN _ARG = /^\s*(_?)(\S+?)\1\s*$/ ;
var STRIP _COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg ;
var $injectorMinErr = minErr ( '$injector' ) ;
2018-05-05 12:13:16 +02:00
function anonFn ( fn ) {
// For anonymous functions, showing at the very least the function signature can help in
// debugging.
var fnText = fn . toString ( ) . replace ( STRIP _COMMENTS , '' ) ,
args = fnText . match ( FN _ARGS ) ;
if ( args ) {
return 'function(' + ( args [ 1 ] || '' ) . replace ( /[\s\r\n]+/ , ' ' ) + ')' ;
}
return 'fn' ;
}
function annotate ( fn , strictDi , name ) {
2016-03-28 10:46:51 +00:00
var $inject ,
2016-04-18 12:34:29 +00:00
fnText ,
2016-03-28 10:46:51 +00:00
argDecl ,
last ;
2018-05-05 12:13:16 +02:00
if ( typeof fn === 'function' ) {
2016-03-28 10:46:51 +00:00
if ( ! ( $inject = fn . $inject ) ) {
$inject = [ ] ;
if ( fn . length ) {
2018-05-05 12:13:16 +02:00
if ( strictDi ) {
if ( ! isString ( name ) || ! name ) {
name = fn . name || anonFn ( fn ) ;
}
throw $injectorMinErr ( 'strictdi' ,
'{0} is not using explicit annotation and cannot be invoked in strict mode' , name ) ;
}
2016-04-18 12:34:29 +00:00
fnText = fn . toString ( ) . replace ( STRIP _COMMENTS , '' ) ;
argDecl = fnText . match ( FN _ARGS ) ;
2018-05-05 12:13:16 +02:00
forEach ( argDecl [ 1 ] . split ( FN _ARG _SPLIT ) , function ( arg ) {
arg . replace ( FN _ARG , function ( all , underscore , name ) {
2016-03-28 10:46:51 +00:00
$inject . push ( name ) ;
} ) ;
} ) ;
}
fn . $inject = $inject ;
}
} else if ( isArray ( fn ) ) {
last = fn . length - 1 ;
assertArgFn ( fn [ last ] , 'fn' ) ;
$inject = fn . slice ( 0 , last ) ;
} else {
assertArgFn ( fn , 'fn' , true ) ;
}
return $inject ;
}
///////////////////////////////////////
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc service
* @ name $injector
2016-03-28 10:46:51 +00:00
*
* @ description
*
* ` $ injector ` is used to retrieve object instances as defined by
2018-05-05 12:13:16 +02:00
* { @ link auto . $provide provider } , instantiate types , invoke methods ,
2016-03-28 10:46:51 +00:00
* and load modules .
*
* The following always holds true :
*
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
* var $injector = angular . injector ( ) ;
* expect ( $injector . get ( '$injector' ) ) . toBe ( $injector ) ;
2018-05-05 12:13:16 +02:00
* expect ( $injector . invoke ( function ( $injector ) {
2016-03-28 10:46:51 +00:00
* return $injector ;
2018-05-05 12:13:16 +02:00
* } ) ) . toBe ( $injector ) ;
* ` ` `
2016-03-28 10:46:51 +00:00
*
* # Injection Function Annotation
*
* JavaScript does not have annotations , and annotations are needed for dependency injection . The
* following are all valid ways of annotating function with injection arguments and are equivalent .
*
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
* // inferred (only works if code not minified/obfuscated)
* $injector . invoke ( function ( serviceA ) { } ) ;
*
* // annotated
* function explicit ( serviceA ) { } ;
* explicit . $inject = [ 'serviceA' ] ;
* $injector . invoke ( explicit ) ;
*
* // inline
* $injector . invoke ( [ 'serviceA' , function ( serviceA ) { } ] ) ;
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
* # # Inference
*
* In JavaScript calling ` toString() ` on a function returns the function definition . The definition
2018-05-05 12:13:16 +02:00
* can then be parsed and the function arguments can be extracted . This method of discovering
* annotations is disallowed when the injector is in strict mode .
* * NOTE : * This does not work with minification , and obfuscation tools since these tools change the
* argument names .
2016-03-28 10:46:51 +00:00
*
* # # ` $ inject ` Annotation
2018-05-05 12:13:16 +02:00
* By adding an ` $ inject ` property onto a function the injection parameters can be specified .
2016-03-28 10:46:51 +00:00
*
* # # Inline
* As an array of injection names , where the last item in the array is the function to call .
* /
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $injector # get
2016-03-28 10:46:51 +00:00
*
* @ description
* Return an instance of the service .
*
* @ param { string } name The name of the instance to retrieve .
2018-05-05 12:13:16 +02:00
* @ param { string = } caller An optional string to provide the origin of the function call for error messages .
2016-03-28 10:46:51 +00:00
* @ return { * } The instance .
* /
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $injector # invoke
2016-03-28 10:46:51 +00:00
*
* @ description
* Invoke the method and supply the method arguments from the ` $ injector ` .
*
2018-05-05 12:13:16 +02:00
* @ param { Function | Array . < string | Function > } fn The injectable function to invoke . Function parameters are
* injected according to the { @ link guide / di $inject Annotation } rules .
2016-03-28 10:46:51 +00:00
* @ param { Object = } self The ` this ` for the invoked method .
* @ param { Object = } locals Optional object . If preset then any argument names are read from this
* object first , before the ` $ injector ` is consulted .
* @ returns { * } the value returned by the invoked ` fn ` function .
* /
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $injector # has
2016-03-28 10:46:51 +00:00
*
* @ description
2018-05-05 12:13:16 +02:00
* Allows the user to query if the particular service exists .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* @ param { string } name Name of the service to query .
* @ returns { boolean } ` true ` if injector has given service .
2016-03-28 10:46:51 +00:00
* /
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $injector # instantiate
2016-03-28 10:46:51 +00:00
* @ description
2018-05-05 12:13:16 +02:00
* Create a new instance of JS type . The method takes a constructor function , invokes the new
* operator , and supplies all of the arguments to the constructor function as specified by the
2016-03-28 10:46:51 +00:00
* constructor annotation .
*
2018-05-05 12:13:16 +02:00
* @ param { Function } Type Annotated constructor function .
2016-03-28 10:46:51 +00:00
* @ param { Object = } locals Optional object . If preset then any argument names are read from this
* object first , before the ` $ injector ` is consulted .
* @ returns { Object } new instance of ` Type ` .
* /
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $injector # annotate
2016-03-28 10:46:51 +00:00
*
* @ description
* Returns an array of service names which the function is requesting for injection . This API is
* used by the injector to determine which services need to be injected into the function when the
* function is invoked . There are three ways in which the function can be annotated with the needed
* dependencies .
*
* # Argument names
*
* The simplest form is to extract the dependencies from the arguments of the function . This is done
* by converting the function into a string using ` toString() ` method and extracting the argument
* names .
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
* // Given
* function MyController ( $scope , $route ) {
* // ...
* }
*
* // Then
* expect ( injector . annotate ( MyController ) ) . toEqual ( [ '$scope' , '$route' ] ) ;
2018-05-05 12:13:16 +02:00
* ` ` `
*
* You can disallow this method by using strict injection mode .
2016-03-28 10:46:51 +00:00
*
* This method does not work with code minification / obfuscation . For this reason the following
* annotation strategies are supported .
*
* # The ` $ inject ` property
*
* If a function has an ` $ inject ` property and its value is an array of strings , then the strings
* represent names of services to be injected into the function .
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
* // Given
* var MyController = function ( obfuscatedScope , obfuscatedRoute ) {
* // ...
* }
* // Define function dependencies
* MyController [ '$inject' ] = [ '$scope' , '$route' ] ;
*
* // Then
* expect ( injector . annotate ( MyController ) ) . toEqual ( [ '$scope' , '$route' ] ) ;
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
* # The array notation
*
* It is often desirable to inline Injected functions and that ' s when setting the ` $ inject ` property
* is very inconvenient . In these situations using the array notation to specify the dependencies in
* a way that survives minification is a better choice :
*
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
* // We wish to write this (not minification / obfuscation safe)
* injector . invoke ( function ( $compile , $rootScope ) {
* // ...
* } ) ;
*
* // We are forced to write break inlining
* var tmpFn = function ( obfuscatedCompile , obfuscatedRootScope ) {
* // ...
* } ;
* tmpFn . $inject = [ '$compile' , '$rootScope' ] ;
* injector . invoke ( tmpFn ) ;
*
* // To better support inline function the inline annotation is supported
* injector . invoke ( [ '$compile' , '$rootScope' , function ( obfCompile , obfRootScope ) {
* // ...
* } ] ) ;
*
* // Therefore
* expect ( injector . annotate (
* [ '$compile' , '$rootScope' , function ( obfus _$compile , obfus _$rootScope ) { } ] )
* ) . toEqual ( [ '$compile' , '$rootScope' ] ) ;
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* @ param { Function | Array . < string | Function > } fn Function for which dependent service names need to
2016-03-28 10:46:51 +00:00
* be retrieved as described above .
*
2018-05-05 12:13:16 +02:00
* @ param { boolean = } [ strictDi = false ] Disallow argument name annotation inference .
*
2016-03-28 10:46:51 +00:00
* @ returns { Array . < string > } The names of the services which the function requires .
* /
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc service
* @ name $provide
2016-03-28 10:46:51 +00:00
*
* @ description
*
2018-05-05 12:13:16 +02:00
* The { @ link auto . $provide $provide } service has a number of methods for registering components
* with the { @ link auto . $injector $injector } . Many of these functions are also exposed on
2016-03-28 10:46:51 +00:00
* { @ link angular . Module } .
*
* An Angular * * service * * is a singleton object created by a * * service factory * * . These * * service
* factories * * are functions which , in turn , are created by a * * service provider * * .
* The * * service providers * * are constructor functions . When instantiated they must contain a
* property called ` $ get ` , which holds the * * service factory * * function .
*
2018-05-05 12:13:16 +02:00
* When you request a service , the { @ link auto . $injector $injector } is responsible for finding the
2016-03-28 10:46:51 +00:00
* correct * * service provider * * , instantiating it and then calling its ` $ get ` * * service factory * *
* function to get the instance of the * * service * * .
*
* Often services have no configuration options and there is no need to add methods to the service
* provider . The provider will be no more than a constructor function with a ` $ get ` property . For
2018-05-05 12:13:16 +02:00
* these cases the { @ link auto . $provide $provide } service has additional helper methods to register
2016-03-28 10:46:51 +00:00
* services without specifying a provider .
*
2018-05-05 12:13:16 +02:00
* * { @ link auto . $provide # provider provider ( provider ) } - registers a * * service provider * * with the
* { @ link auto . $injector $injector }
* * { @ link auto . $provide # constant constant ( obj ) } - registers a value / object that can be accessed by
2016-03-28 10:46:51 +00:00
* providers and services .
2018-05-05 12:13:16 +02:00
* * { @ link auto . $provide # value value ( obj ) } - registers a value / object that can only be accessed by
2016-03-28 10:46:51 +00:00
* services , not providers .
2018-05-05 12:13:16 +02:00
* * { @ link auto . $provide # factory factory ( fn ) } - registers a service * * factory function * * , ` fn ` ,
2016-03-28 10:46:51 +00:00
* that will be wrapped in a * * service provider * * object , whose ` $ get ` property will contain the
* given factory function .
2018-05-05 12:13:16 +02:00
* * { @ link auto . $provide # service service ( class ) } - registers a * * constructor function * * , ` class `
2016-03-28 10:46:51 +00:00
* that will be wrapped in a * * service provider * * object , whose ` $ get ` property will instantiate
* a new object using the given constructor function .
*
* See the individual methods for more information and examples .
* /
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $provide # provider
2016-03-28 10:46:51 +00:00
* @ description
*
2018-05-05 12:13:16 +02:00
* Register a * * provider function * * with the { @ link auto . $injector $injector } . Provider functions
2016-03-28 10:46:51 +00:00
* are constructor functions , whose instances are responsible for "providing" a factory for a
* service .
*
* Service provider names start with the name of the service they provide followed by ` Provider ` .
* For example , the { @ link ng . $log $log } service has a provider called
* { @ link ng . $logProvider $logProvider } .
*
* Service provider objects can have additional methods which allow configuration of the provider
* and its service . Importantly , you can configure what kind of service is created by the ` $ get `
* method , or how that service will act . For example , the { @ link ng . $logProvider $logProvider } has a
* method { @ link ng . $logProvider # debugEnabled debugEnabled }
* which lets you specify whether the { @ link ng . $log $log } service will log debug messages to the
* console or not .
*
* @ param { string } name The name of the instance . NOTE : the provider will be available under ` name +
'Provider' ` key.
* @ param { ( Object | function ( ) ) } provider If the provider is :
*
* - ` Object ` : then it should have a ` $ get ` method . The ` $ get ` method will be invoked using
2018-05-05 12:13:16 +02:00
* { @ link auto . $injector # invoke $injector . invoke ( ) } when an instance needs to be created .
2016-03-28 10:46:51 +00:00
* - ` Constructor ` : a new instance of the provider will be created using
2018-05-05 12:13:16 +02:00
* { @ link auto . $injector # instantiate $injector . instantiate ( ) } , then treated as ` object ` .
2016-03-28 10:46:51 +00:00
*
* @ returns { Object } registered provider instance
* @ example
*
* The following example shows how to create a simple event tracking service and register it using
2018-05-05 12:13:16 +02:00
* { @ link auto . $provide # provider $provide . provider ( ) } .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
* // Define the eventTracker provider
* function EventTrackerProvider ( ) {
* var trackingUrl = '/track' ;
*
* // A provider method for configuring where the tracked events should been saved
* this . setTrackingUrl = function ( url ) {
* trackingUrl = url ;
* } ;
*
* // The service factory function
* this . $get = [ '$http' , function ( $http ) {
* var trackedEvents = { } ;
* return {
* // Call this to track an event
* event : function ( event ) {
* var count = trackedEvents [ event ] || 0 ;
* count += 1 ;
* trackedEvents [ event ] = count ;
* return count ;
* } ,
* // Call this to save the tracked events to the trackingUrl
* save : function ( ) {
* $http . post ( trackingUrl , trackedEvents ) ;
* }
* } ;
* } ] ;
* }
*
* describe ( 'eventTracker' , function ( ) {
* var postSpy ;
*
* beforeEach ( module ( function ( $provide ) {
* // Register the eventTracker provider
* $provide . provider ( 'eventTracker' , EventTrackerProvider ) ;
* } ) ) ;
*
* beforeEach ( module ( function ( eventTrackerProvider ) {
* // Configure eventTracker provider
* eventTrackerProvider . setTrackingUrl ( '/custom-track' ) ;
* } ) ) ;
*
* it ( 'tracks events' , inject ( function ( eventTracker ) {
* expect ( eventTracker . event ( 'login' ) ) . toEqual ( 1 ) ;
* expect ( eventTracker . event ( 'login' ) ) . toEqual ( 2 ) ;
* } ) ) ;
*
* it ( 'saves to the tracking url' , inject ( function ( eventTracker , $http ) {
* postSpy = spyOn ( $http , 'post' ) ;
* eventTracker . event ( 'login' ) ;
* eventTracker . save ( ) ;
* expect ( postSpy ) . toHaveBeenCalled ( ) ;
* expect ( postSpy . mostRecentCall . args [ 0 ] ) . not . toEqual ( '/track' ) ;
* expect ( postSpy . mostRecentCall . args [ 0 ] ) . toEqual ( '/custom-track' ) ;
* expect ( postSpy . mostRecentCall . args [ 1 ] ) . toEqual ( { 'login' : 1 } ) ;
* } ) ) ;
* } ) ;
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
* /
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $provide # factory
2016-03-28 10:46:51 +00:00
* @ description
*
* Register a * * service factory * * , which will be called to return the service instance .
* This is short for registering a service where its provider consists of only a ` $ get ` property ,
* which is the given service factory function .
2018-05-05 12:13:16 +02:00
* You should use { @ link auto . $provide # factory $provide . factory ( getFn ) } if you do not need to
2016-03-28 10:46:51 +00:00
* configure your service in a provider .
*
* @ param { string } name The name of the instance .
2018-05-05 12:13:16 +02:00
* @ param { Function | Array . < string | Function > } $getFn The injectable $getFn for the instance creation .
* Internally this is a short hand for ` $ provide.provider(name, { $ get: $ getFn}) ` .
2016-03-28 10:46:51 +00:00
* @ returns { Object } registered provider instance
*
* @ example
* Here is an example of registering a service
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
* $provide . factory ( 'ping' , [ '$http' , function ( $http ) {
* return function ping ( ) {
* return $http . send ( '/ping' ) ;
* } ;
* } ] ) ;
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
* You would then inject and use this service like this :
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
* someModule . controller ( 'Ctrl' , [ 'ping' , function ( ping ) {
* ping ( ) ;
* } ] ) ;
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
* /
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $provide # service
2016-03-28 10:46:51 +00:00
* @ description
*
* Register a * * service constructor * * , which will be invoked with ` new ` to create the service
* instance .
2016-04-18 12:34:29 +00:00
* This is short for registering a service where its provider ' s ` $ get ` property is the service
* constructor function that will be used to instantiate the service instance .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* You should use { @ link auto . $provide # service $provide . service ( class ) } if you define your service
2016-03-28 10:46:51 +00:00
* as a type / class .
*
* @ param { string } name The name of the instance .
2018-05-05 12:13:16 +02:00
* @ param { Function | Array . < string | Function > } constructor An injectable class ( constructor function )
* that will be instantiated .
2016-03-28 10:46:51 +00:00
* @ returns { Object } registered provider instance
*
* @ example
* Here is an example of registering a service using
2018-05-05 12:13:16 +02:00
* { @ link auto . $provide # service $provide . service ( class ) } .
* ` ` ` js
* var Ping = function ( $http ) {
* this . $http = $http ;
* } ;
2016-05-18 00:10:50 +00:00
*
2018-05-05 12:13:16 +02:00
* Ping . $inject = [ '$http' ] ;
2016-05-18 00:10:50 +00:00
*
2018-05-05 12:13:16 +02:00
* Ping . prototype . send = function ( ) {
* return this . $http . get ( '/ping' ) ;
* } ;
* $provide . service ( 'ping' , Ping ) ;
* ` ` `
2016-03-28 10:46:51 +00:00
* You would then inject and use this service like this :
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
* someModule . controller ( 'Ctrl' , [ 'ping' , function ( ping ) {
* ping . send ( ) ;
* } ] ) ;
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
* /
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $provide # value
2016-03-28 10:46:51 +00:00
* @ description
*
2018-05-05 12:13:16 +02:00
* Register a * * value service * * with the { @ link auto . $injector $injector } , such as a string , a
2016-04-18 12:34:29 +00:00
* number , an array , an object or a function . This is short for registering a service where its
2016-03-28 10:46:51 +00:00
* provider ' s ` $ get ` property is a factory function that takes no arguments and returns the * * value
2016-04-18 12:34:29 +00:00
* service * * .
2016-03-28 10:46:51 +00:00
*
* Value services are similar to constant services , except that they cannot be injected into a
* module configuration function ( see { @ link angular . Module # config } ) but they can be overridden by
2016-04-18 12:34:29 +00:00
* an Angular
2018-05-05 12:13:16 +02:00
* { @ link auto . $provide # decorator decorator } .
2016-03-28 10:46:51 +00:00
*
* @ param { string } name The name of the instance .
* @ param { * } value The value .
* @ returns { Object } registered provider instance
*
* @ example
* Here are some examples of creating value services .
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
* $provide . value ( 'ADMIN_USER' , 'admin' ) ;
*
* $provide . value ( 'RoleLookup' , { admin : 0 , writer : 1 , reader : 2 } ) ;
*
* $provide . value ( 'halfOf' , function ( value ) {
* return value / 2 ;
* } ) ;
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
* /
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $provide # constant
2016-03-28 10:46:51 +00:00
* @ description
*
2016-04-18 12:34:29 +00:00
* Register a * * constant service * * , such as a string , a number , an array , an object or a function ,
2018-05-05 12:13:16 +02:00
* with the { @ link auto . $injector $injector } . Unlike { @ link auto . $provide # value value } it can be
2016-03-28 10:46:51 +00:00
* injected into a module configuration function ( see { @ link angular . Module # config } ) and it cannot
2018-05-05 12:13:16 +02:00
* be overridden by an Angular { @ link auto . $provide # decorator decorator } .
2016-03-28 10:46:51 +00:00
*
* @ param { string } name The name of the constant .
* @ param { * } value The constant value .
* @ returns { Object } registered instance
*
* @ example
* Here a some examples of creating constants :
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
* $provide . constant ( 'SHARD_HEIGHT' , 306 ) ;
*
* $provide . constant ( 'MY_COLOURS' , [ 'red' , 'blue' , 'grey' ] ) ;
*
* $provide . constant ( 'double' , function ( value ) {
* return value * 2 ;
* } ) ;
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
* /
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $provide # decorator
2016-03-28 10:46:51 +00:00
* @ description
*
2018-05-05 12:13:16 +02:00
* Register a * * service decorator * * with the { @ link auto . $injector $injector } . A service decorator
2016-04-18 12:34:29 +00:00
* intercepts the creation of a service , allowing it to override or modify the behaviour of the
2016-03-28 10:46:51 +00:00
* service . The object returned by the decorator may be the original service , or a new service
* object which replaces or wraps and delegates to the original service .
*
* @ param { string } name The name of the service to decorate .
2018-05-05 12:13:16 +02:00
* @ param { Function | Array . < string | Function > } decorator This function will be invoked when the service needs to be
2016-03-28 10:46:51 +00:00
* instantiated and should return the decorated service instance . The function is called using
2018-05-05 12:13:16 +02:00
* the { @ link auto . $injector # invoke injector . invoke } method and is therefore fully injectable .
2016-03-28 10:46:51 +00:00
* Local injection arguments :
*
* * ` $ delegate ` - The original service instance , which can be monkey patched , configured ,
* decorated or delegated to .
*
* @ example
* Here we decorate the { @ link ng . $log $log } service to convert warnings to errors by intercepting
* calls to { @ link ng . $log # error $log . warn ( ) } .
2018-05-05 12:13:16 +02:00
* ` ` ` js
* $provide . decorator ( '$log' , [ '$delegate' , function ( $delegate ) {
2016-03-28 10:46:51 +00:00
* $delegate . warn = $delegate . error ;
* return $delegate ;
* } ] ) ;
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
* /
2018-05-05 12:13:16 +02:00
function createInjector ( modulesToLoad , strictDi ) {
strictDi = ( strictDi === true ) ;
2016-03-28 10:46:51 +00:00
var INSTANTIATING = { } ,
providerSuffix = 'Provider' ,
path = [ ] ,
2018-05-05 12:13:16 +02:00
loadedModules = new HashMap ( [ ] , true ) ,
2016-03-28 10:46:51 +00:00
providerCache = {
$provide : {
provider : supportObject ( provider ) ,
factory : supportObject ( factory ) ,
service : supportObject ( service ) ,
value : supportObject ( value ) ,
constant : supportObject ( constant ) ,
decorator : decorator
}
} ,
providerInjector = ( providerCache . $injector =
2018-05-05 12:13:16 +02:00
createInternalInjector ( providerCache , function ( serviceName , caller ) {
if ( angular . isString ( caller ) ) {
path . push ( caller ) ;
}
2016-03-28 10:46:51 +00:00
throw $injectorMinErr ( 'unpr' , "Unknown provider: {0}" , path . join ( ' <- ' ) ) ;
} ) ) ,
instanceCache = { } ,
2016-04-18 12:34:29 +00:00
instanceInjector = ( instanceCache . $injector =
2018-05-05 12:13:16 +02:00
createInternalInjector ( instanceCache , function ( serviceName , caller ) {
var provider = providerInjector . get ( serviceName + providerSuffix , caller ) ;
return instanceInjector . invoke ( provider . $get , provider , undefined , serviceName ) ;
2016-04-18 12:34:29 +00:00
} ) ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
forEach ( loadModules ( modulesToLoad ) , function ( fn ) { instanceInjector . invoke ( fn || noop ) ; } ) ;
2016-03-28 10:46:51 +00:00
return instanceInjector ;
////////////////////////////////////
// $provider
////////////////////////////////////
function supportObject ( delegate ) {
return function ( key , value ) {
if ( isObject ( key ) ) {
forEach ( key , reverseParams ( delegate ) ) ;
} else {
return delegate ( key , value ) ;
}
} ;
}
function provider ( name , provider _ ) {
assertNotHasOwnProperty ( name , 'service' ) ;
if ( isFunction ( provider _ ) || isArray ( provider _ ) ) {
provider _ = providerInjector . instantiate ( provider _ ) ;
}
if ( ! provider _ . $get ) {
throw $injectorMinErr ( 'pget' , "Provider '{0}' must define $get factory method." , name ) ;
}
return providerCache [ name + providerSuffix ] = provider _ ;
}
2018-05-05 12:13:16 +02:00
function enforceReturnValue ( name , factory ) {
return function enforcedReturnValue ( ) {
var result = instanceInjector . invoke ( factory , this ) ;
if ( isUndefined ( result ) ) {
throw $injectorMinErr ( 'undef' , "Provider '{0}' must return a value from $get factory method." , name ) ;
}
return result ;
} ;
}
function factory ( name , factoryFn , enforce ) {
return provider ( name , {
$get : enforce !== false ? enforceReturnValue ( name , factoryFn ) : factoryFn
} ) ;
}
2016-03-28 10:46:51 +00:00
function service ( name , constructor ) {
return factory ( name , [ '$injector' , function ( $injector ) {
return $injector . instantiate ( constructor ) ;
} ] ) ;
}
2018-05-05 12:13:16 +02:00
function value ( name , val ) { return factory ( name , valueFn ( val ) , false ) ; }
2016-03-28 10:46:51 +00:00
function constant ( name , value ) {
assertNotHasOwnProperty ( name , 'constant' ) ;
providerCache [ name ] = value ;
instanceCache [ name ] = value ;
}
function decorator ( serviceName , decorFn ) {
var origProvider = providerInjector . get ( serviceName + providerSuffix ) ,
orig$get = origProvider . $get ;
origProvider . $get = function ( ) {
var origInstance = instanceInjector . invoke ( orig$get , origProvider ) ;
return instanceInjector . invoke ( decorFn , null , { $delegate : origInstance } ) ;
} ;
}
////////////////////////////////////
// Module Loading
////////////////////////////////////
2018-05-05 12:13:16 +02:00
function loadModules ( modulesToLoad ) {
var runBlocks = [ ] , moduleFn ;
2016-03-28 10:46:51 +00:00
forEach ( modulesToLoad , function ( module ) {
if ( loadedModules . get ( module ) ) return ;
loadedModules . put ( module , true ) ;
2018-05-05 12:13:16 +02:00
function runInvokeQueue ( queue ) {
var i , ii ;
for ( i = 0 , ii = queue . length ; i < ii ; i ++ ) {
var invokeArgs = queue [ i ] ,
provider = providerInjector . get ( invokeArgs [ 0 ] ) ;
provider [ invokeArgs [ 1 ] ] . apply ( provider , invokeArgs [ 2 ] ) ;
}
}
2016-03-28 10:46:51 +00:00
try {
if ( isString ( module ) ) {
moduleFn = angularModule ( module ) ;
runBlocks = runBlocks . concat ( loadModules ( moduleFn . requires ) ) . concat ( moduleFn . _runBlocks ) ;
2018-05-05 12:13:16 +02:00
runInvokeQueue ( moduleFn . _invokeQueue ) ;
runInvokeQueue ( moduleFn . _configBlocks ) ;
2016-03-28 10:46:51 +00:00
} else if ( isFunction ( module ) ) {
runBlocks . push ( providerInjector . invoke ( module ) ) ;
} else if ( isArray ( module ) ) {
runBlocks . push ( providerInjector . invoke ( module ) ) ;
} else {
assertArgFn ( module , 'module' ) ;
}
} catch ( e ) {
if ( isArray ( module ) ) {
module = module [ module . length - 1 ] ;
}
if ( e . message && e . stack && e . stack . indexOf ( e . message ) == - 1 ) {
// Safari & FF's stack traces don't contain error.message content
// unlike those of Chrome and IE
// So if stack doesn't contain message, we create a new string that contains both.
// Since error.stack is read-only in Safari, I'm overriding e and not e.stack here.
/* jshint -W022 */
e = e . message + '\n' + e . stack ;
}
throw $injectorMinErr ( 'modulerr' , "Failed to instantiate module {0} due to:\n{1}" ,
module , e . stack || e . message || e ) ;
}
} ) ;
return runBlocks ;
}
////////////////////////////////////
// internal Injector
////////////////////////////////////
function createInternalInjector ( cache , factory ) {
2018-05-05 12:13:16 +02:00
function getService ( serviceName , caller ) {
2016-03-28 10:46:51 +00:00
if ( cache . hasOwnProperty ( serviceName ) ) {
if ( cache [ serviceName ] === INSTANTIATING ) {
2018-05-05 12:13:16 +02:00
throw $injectorMinErr ( 'cdep' , 'Circular dependency found: {0}' ,
serviceName + ' <- ' + path . join ( ' <- ' ) ) ;
2016-03-28 10:46:51 +00:00
}
return cache [ serviceName ] ;
} else {
try {
path . unshift ( serviceName ) ;
cache [ serviceName ] = INSTANTIATING ;
2018-05-05 12:13:16 +02:00
return cache [ serviceName ] = factory ( serviceName , caller ) ;
2016-03-28 10:46:51 +00:00
} catch ( err ) {
if ( cache [ serviceName ] === INSTANTIATING ) {
delete cache [ serviceName ] ;
}
throw err ;
} finally {
path . shift ( ) ;
}
}
}
2018-05-05 12:13:16 +02:00
function invoke ( fn , self , locals , serviceName ) {
if ( typeof locals === 'string' ) {
serviceName = locals ;
locals = null ;
}
2016-03-28 10:46:51 +00:00
var args = [ ] ,
2018-05-05 12:13:16 +02:00
$inject = createInjector . $$annotate ( fn , strictDi , serviceName ) ,
2016-04-18 12:34:29 +00:00
length , i ,
key ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
for ( i = 0 , length = $inject . length ; i < length ; i ++ ) {
2016-04-18 12:34:29 +00:00
key = $inject [ i ] ;
2016-03-28 10:46:51 +00:00
if ( typeof key !== 'string' ) {
throw $injectorMinErr ( 'itkn' ,
'Incorrect injection token! Expected service name as string, got {0}' , key ) ;
}
2016-04-18 12:34:29 +00:00
args . push (
locals && locals . hasOwnProperty ( key )
? locals [ key ]
2018-05-05 12:13:16 +02:00
: getService ( key , serviceName )
2016-04-18 12:34:29 +00:00
) ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
if ( isArray ( fn ) ) {
2016-04-18 12:34:29 +00:00
fn = fn [ length ] ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
// http://jsperf.com/angularjs-invoke-apply-vs-switch
// #5388
return fn . apply ( self , args ) ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
function instantiate ( Type , locals , serviceName ) {
2016-03-28 10:46:51 +00:00
// Check if Type is annotated and use just the given function at n-1 as parameter
// e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
2018-05-05 12:13:16 +02:00
// Object creation: http://jsperf.com/create-constructor/2
var instance = Object . create ( ( isArray ( Type ) ? Type [ Type . length - 1 ] : Type ) . prototype || null ) ;
var returnedValue = invoke ( Type , instance , locals , serviceName ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
return isObject ( returnedValue ) || isFunction ( returnedValue ) ? returnedValue : instance ;
}
2016-03-28 10:46:51 +00:00
return {
invoke : invoke ,
instantiate : instantiate ,
get : getService ,
2018-05-05 12:13:16 +02:00
annotate : createInjector . $$annotate ,
2016-03-28 10:46:51 +00:00
has : function ( name ) {
return providerCache . hasOwnProperty ( name + providerSuffix ) || cache . hasOwnProperty ( name ) ;
}
} ;
}
}
2018-05-05 12:13:16 +02:00
createInjector . $$annotate = annotate ;
2016-03-28 10:46:51 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc provider
* @ name $anchorScrollProvider
2016-03-28 10:46:51 +00:00
*
* @ description
2018-05-05 12:13:16 +02:00
* Use ` $ anchorScrollProvider ` to disable automatic scrolling whenever
* { @ link ng . $location # hash $location . hash ( ) } changes .
2016-03-28 10:46:51 +00:00
* /
function $AnchorScrollProvider ( ) {
var autoScrollingEnabled = true ;
2018-05-05 12:13:16 +02:00
/ * *
* @ ngdoc method
* @ name $anchorScrollProvider # disableAutoScrolling
*
* @ description
* By default , { @ link ng . $anchorScroll $anchorScroll ( ) } will automatically detect changes to
* { @ link ng . $location # hash $location . hash ( ) } and scroll to the element matching the new hash . < br / >
* Use this method to disable automatic scrolling .
*
* If automatic scrolling is disabled , one must explicitly call
* { @ link ng . $anchorScroll $anchorScroll ( ) } in order to scroll to the element related to the
* current hash .
* /
2016-03-28 10:46:51 +00:00
this . disableAutoScrolling = function ( ) {
autoScrollingEnabled = false ;
} ;
2018-05-05 12:13:16 +02:00
/ * *
* @ ngdoc service
* @ name $anchorScroll
* @ kind function
* @ requires $window
* @ requires $location
* @ requires $rootScope
*
* @ description
* When called , it checks the current value of { @ link ng . $location # hash $location . hash ( ) } and
* scrolls to the related element , according to the rules specified in the
* [ Html5 spec ] ( http : //dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document).
*
* It also watches the { @ link ng . $location # hash $location . hash ( ) } and automatically scrolls to
* match any anchor whenever it changes . This can be disabled by calling
* { @ link ng . $anchorScrollProvider # disableAutoScrolling $anchorScrollProvider . disableAutoScrolling ( ) } .
*
* Additionally , you can use its { @ link ng . $anchorScroll # yOffset yOffset } property to specify a
* vertical scroll - offset ( either fixed or dynamic ) .
*
* @ property { ( number | function | jqLite ) } yOffset
* If set , specifies a vertical scroll - offset . This is often useful when there are fixed
* positioned elements at the top of the page , such as navbars , headers etc .
*
* ` yOffset ` can be specified in various ways :
* - * * number * * : A fixed number of pixels to be used as offset . < br / > < br / >
* - * * function * * : A getter function called everytime ` $ anchorScroll() ` is executed . Must return
* a number representing the offset ( in pixels ) . < br / > < br / >
* - * * jqLite * * : A jqLite / jQuery element to be used for specifying the offset . The distance from
* the top of the page to the element ' s bottom will be used as offset . < br / >
* * * Note * * : The element will be taken into account only as long as its ` position ` is set to
* ` fixed ` . This option is useful , when dealing with responsive navbars / headers that adjust
* their height and / or positioning according to the viewport ' s size .
*
* < br / >
* < div class = "alert alert-warning" >
* In order for ` yOffset ` to work properly , scrolling should take place on the document ' s root and
* not some child element .
* < / d i v >
*
* @ example
< example module = "anchorScrollExample" >
< file name = "index.html" >
< div id = "scrollArea" ng - controller = "ScrollController" >
< a ng - click = "gotoBottom()" > Go to bottom < / a >
< a id = "bottom" > < / a > Y o u ' r e a t t h e b o t t o m !
< / d i v >
< / f i l e >
< file name = "script.js" >
angular . module ( 'anchorScrollExample' , [ ] )
. controller ( 'ScrollController' , [ '$scope' , '$location' , '$anchorScroll' ,
function ( $scope , $location , $anchorScroll ) {
$scope . gotoBottom = function ( ) {
// set the location.hash to the id of
// the element you wish to scroll to.
$location . hash ( 'bottom' ) ;
// call $anchorScroll()
$anchorScroll ( ) ;
} ;
} ] ) ;
< / f i l e >
< file name = "style.css" >
# scrollArea {
height : 280 px ;
overflow : auto ;
}
# bottom {
display : block ;
margin - top : 2000 px ;
}
< / f i l e >
< / e x a m p l e >
*
* < hr / >
* The example below illustrates the use of a vertical scroll - offset ( specified as a fixed value ) .
* See { @ link ng . $anchorScroll # yOffset $anchorScroll . yOffset } for more details .
*
* @ example
< example module = "anchorScrollOffsetExample" >
< file name = "index.html" >
< div class = "fixed-header" ng - controller = "headerCtrl" >
< a href = "" ng - click = "gotoAnchor(x)" ng - repeat = "x in [1,2,3,4,5]" >
Go to anchor { { x } }
< / a >
< / d i v >
< div id = "anchor{{x}}" class = "anchor" ng - repeat = "x in [1,2,3,4,5]" >
Anchor { { x } } of 5
< / d i v >
< / f i l e >
< file name = "script.js" >
angular . module ( 'anchorScrollOffsetExample' , [ ] )
. run ( [ '$anchorScroll' , function ( $anchorScroll ) {
$anchorScroll . yOffset = 50 ; // always scroll by 50 extra pixels
} ] )
. controller ( 'headerCtrl' , [ '$anchorScroll' , '$location' , '$scope' ,
function ( $anchorScroll , $location , $scope ) {
$scope . gotoAnchor = function ( x ) {
var newHash = 'anchor' + x ;
if ( $location . hash ( ) !== newHash ) {
// set the $location.hash to `newHash` and
// $anchorScroll will automatically scroll to it
$location . hash ( 'anchor' + x ) ;
} else {
// call $anchorScroll() explicitly,
// since $location.hash hasn't changed
$anchorScroll ( ) ;
}
} ;
}
] ) ;
< / f i l e >
< file name = "style.css" >
body {
padding - top : 50 px ;
}
. anchor {
border : 2 px dashed DarkOrchid ;
padding : 10 px 10 px 200 px 10 px ;
}
. fixed - header {
background - color : rgba ( 0 , 0 , 0 , 0.2 ) ;
height : 50 px ;
position : fixed ;
top : 0 ; left : 0 ; right : 0 ;
}
. fixed - header > a {
display : inline - block ;
margin : 5 px 15 px ;
}
< / f i l e >
< / e x a m p l e >
* /
this . $get = [ '$window' , '$location' , '$rootScope' , function ( $window , $location , $rootScope ) {
var document = $window . document ;
// Helper function to get first anchor from a NodeList
// (using `Array#some()` instead of `angular#forEach()` since it's more performant
// and working in all supported browsers.)
2016-03-28 10:46:51 +00:00
function getFirstAnchor ( list ) {
var result = null ;
2018-05-05 12:13:16 +02:00
Array . prototype . some . call ( list , function ( element ) {
if ( nodeName _ ( element ) === 'a' ) {
result = element ;
return true ;
}
2016-03-28 10:46:51 +00:00
} ) ;
return result ;
}
2018-05-05 12:13:16 +02:00
function getYOffset ( ) {
var offset = scroll . yOffset ;
if ( isFunction ( offset ) ) {
offset = offset ( ) ;
} else if ( isElement ( offset ) ) {
var elem = offset [ 0 ] ;
var style = $window . getComputedStyle ( elem ) ;
if ( style . position !== 'fixed' ) {
offset = 0 ;
} else {
offset = elem . getBoundingClientRect ( ) . bottom ;
}
} else if ( ! isNumber ( offset ) ) {
offset = 0 ;
}
return offset ;
}
function scrollTo ( elem ) {
if ( elem ) {
elem . scrollIntoView ( ) ;
var offset = getYOffset ( ) ;
if ( offset ) {
// `offset` is the number of pixels we should scroll UP in order to align `elem` properly.
// This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the
// top of the viewport.
//
// IF the number of pixels from the top of `elem` to the end of the page's content is less
// than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some
// way down the page.
//
// This is often the case for elements near the bottom of the page.
//
// In such cases we do not need to scroll the whole `offset` up, just the difference between
// the top of the element and the offset, which is enough to align the top of `elem` at the
// desired position.
var elemTop = elem . getBoundingClientRect ( ) . top ;
$window . scrollBy ( 0 , elemTop - offset ) ;
}
} else {
$window . scrollTo ( 0 , 0 ) ;
}
}
2016-04-18 12:34:29 +00:00
function scroll ( ) {
var hash = $location . hash ( ) , elm ;
2016-03-28 10:46:51 +00:00
// empty hash, scroll to the top of the page
2018-05-05 12:13:16 +02:00
if ( ! hash ) scrollTo ( null ) ;
2016-03-28 10:46:51 +00:00
// element with given id
2018-05-05 12:13:16 +02:00
else if ( ( elm = document . getElementById ( hash ) ) ) scrollTo ( elm ) ;
2016-03-28 10:46:51 +00:00
// first anchor with given name :-D
2018-05-05 12:13:16 +02:00
else if ( ( elm = getFirstAnchor ( document . getElementsByName ( hash ) ) ) ) scrollTo ( elm ) ;
2016-03-28 10:46:51 +00:00
// no element and hash == 'top', scroll to the top of the page
2018-05-05 12:13:16 +02:00
else if ( hash === 'top' ) scrollTo ( null ) ;
2016-03-28 10:46:51 +00:00
}
// does not scroll when user clicks on anchor link that is currently on
// (no url change, no $location.hash() change), browser native does scroll
if ( autoScrollingEnabled ) {
$rootScope . $watch ( function autoScrollWatch ( ) { return $location . hash ( ) ; } ,
2018-05-05 12:13:16 +02:00
function autoScrollWatchAction ( newVal , oldVal ) {
// skip the initial scroll if $location.hash is empty
if ( newVal === oldVal && newVal === '' ) return ;
jqLiteDocumentLoaded ( function ( ) {
$rootScope . $evalAsync ( scroll ) ;
} ) ;
2016-03-28 10:46:51 +00:00
} ) ;
}
return scroll ;
} ] ;
}
var $animateMinErr = minErr ( '$animate' ) ;
2016-04-18 12:34:29 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc provider
* @ name $animateProvider
2016-04-18 12:34:29 +00:00
*
* @ description
* Default implementation of $animate that doesn ' t perform any animations , instead just
* synchronously performs DOM
* updates and calls done ( ) callbacks .
*
* In order to enable animations the ngAnimate module has to be loaded .
*
* To see the functional implementation check out src / ngAnimate / animate . js
* /
var $AnimateProvider = [ '$provide' , function ( $provide ) {
2016-03-28 10:46:51 +00:00
2016-05-18 00:10:50 +00:00
2016-04-18 12:34:29 +00:00
this . $$selectors = { } ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $animateProvider # register
2016-03-28 10:46:51 +00:00
*
* @ description
* Registers a new injectable animation factory function . The factory function produces the
* animation object which contains callback functions for each event that is expected to be
* animated .
*
2016-04-18 12:34:29 +00:00
* * ` eventFn ` : ` function(Element, doneFunction) ` The element to animate , the ` doneFunction `
* must be called once the element animation is complete . If a function is returned then the
* animation service will use this function to cancel the animation whenever a cancel event is
* triggered .
2016-03-28 10:46:51 +00:00
*
*
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
* return {
2016-04-18 12:34:29 +00:00
* eventFn : function ( element , done ) {
* //code to run the animation
* //once complete, then run done()
* return function cancellationFunction ( ) {
* //code to cancel the animation
* }
* }
* }
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ param { string } name The name of the animation .
2018-05-05 12:13:16 +02:00
* @ param { Function } factory The factory function that will be executed to return the animation
2016-03-28 10:46:51 +00:00
* object .
* /
this . register = function ( name , factory ) {
var key = name + '-animation' ;
2016-04-18 12:34:29 +00:00
if ( name && name . charAt ( 0 ) != '.' ) throw $animateMinErr ( 'notcsel' ,
"Expecting class selector starting with '.' got '{0}'." , name ) ;
this . $$selectors [ name . substr ( 1 ) ] = key ;
2016-03-28 10:46:51 +00:00
$provide . factory ( key , factory ) ;
} ;
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $animateProvider # classNameFilter
2016-03-28 10:46:51 +00:00
*
* @ description
* Sets and / or returns the CSS class regular expression that is checked when performing
* an animation . Upon bootstrap the classNameFilter value is not set at all and will
2016-04-18 12:34:29 +00:00
* therefore enable $animate to attempt to perform an animation on any element .
* When setting the classNameFilter value , animations will only be performed on elements
2016-03-28 10:46:51 +00:00
* that successfully match the filter expression . This in turn can boost performance
* for low - powered devices as well as applications containing a lot of structural operations .
* @ param { RegExp = } expression The className expression which will be checked against all animations
* @ return { RegExp } The current CSS className expression value . If null then there is no expression value
* /
this . classNameFilter = function ( expression ) {
2018-05-05 12:13:16 +02:00
if ( arguments . length === 1 ) {
2016-03-28 10:46:51 +00:00
this . $$classNameFilter = ( expression instanceof RegExp ) ? expression : null ;
}
return this . $$classNameFilter ;
} ;
2018-05-05 12:13:16 +02:00
this . $get = [ '$$q' , '$$asyncCallback' , '$rootScope' , function ( $$q , $$asyncCallback , $rootScope ) {
var currentDefer ;
function runAnimationPostDigest ( fn ) {
var cancelFn , defer = $$q . defer ( ) ;
defer . promise . $$cancelFn = function ngAnimateMaybeCancel ( ) {
cancelFn && cancelFn ( ) ;
} ;
$rootScope . $$postDigest ( function ngAnimatePostDigest ( ) {
cancelFn = fn ( function ngAnimateNotifyComplete ( ) {
defer . resolve ( ) ;
} ) ;
} ) ;
return defer . promise ;
}
function resolveElementClasses ( element , classes ) {
var toAdd = [ ] , toRemove = [ ] ;
var hasClasses = createMap ( ) ;
forEach ( ( element . attr ( 'class' ) || '' ) . split ( /\s+/ ) , function ( className ) {
hasClasses [ className ] = true ;
} ) ;
forEach ( classes , function ( status , className ) {
var hasClass = hasClasses [ className ] ;
// If the most recent class manipulation (via $animate) was to remove the class, and the
// element currently has the class, the class is scheduled for removal. Otherwise, if
// the most recent class manipulation (via $animate) was to add the class, and the
// element does not currently have the class, the class is scheduled to be added.
if ( status === false && hasClass ) {
toRemove . push ( className ) ;
} else if ( status === true && ! hasClass ) {
toAdd . push ( className ) ;
}
} ) ;
return ( toAdd . length + toRemove . length ) > 0 &&
[ toAdd . length ? toAdd : null , toRemove . length ? toRemove : null ] ;
}
function cachedClassManipulation ( cache , classes , op ) {
for ( var i = 0 , ii = classes . length ; i < ii ; ++ i ) {
var className = classes [ i ] ;
cache [ className ] = op ;
}
}
function asyncPromise ( ) {
// only serve one instance of a promise in order to save CPU cycles
if ( ! currentDefer ) {
currentDefer = $$q . defer ( ) ;
$$asyncCallback ( function ( ) {
currentDefer . resolve ( ) ;
currentDefer = null ;
} ) ;
}
return currentDefer . promise ;
}
function applyStyles ( element , options ) {
if ( angular . isObject ( options ) ) {
var styles = extend ( options . from || { } , options . to || { } ) ;
element . css ( styles ) ;
}
}
2016-03-28 10:46:51 +00:00
/ * *
*
2018-05-05 12:13:16 +02:00
* @ ngdoc service
* @ name $animate
2016-04-18 12:34:29 +00:00
* @ description The $animate service provides rudimentary DOM manipulation functions to
* insert , remove and move elements within the DOM , as well as adding and removing classes .
* This service is the core service used by the ngAnimate $animator service which provides
* high - level animation hooks for CSS and JavaScript .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* $animate is available in the AngularJS core , however , the ngAnimate module must be included
* to enable full out animation support . Otherwise , $animate will only perform simple DOM
* manipulation operations .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* To learn more about enabling animation support , click here to visit the { @ link ngAnimate
* ngAnimate module page } as well as the { @ link ngAnimate . $animate ngAnimate $animate service
* page } .
2016-03-28 10:46:51 +00:00
* /
return {
2018-05-05 12:13:16 +02:00
animate : function ( element , from , to ) {
applyStyles ( element , { from : from , to : to } ) ;
return asyncPromise ( ) ;
} ,
2016-03-28 10:46:51 +00:00
/ * *
*
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $animate # enter
* @ kind function
* @ description Inserts the element into the DOM either after the ` after ` element or
* as the first child within the ` parent ` element . When the function is called a promise
* is returned that will be resolved at a later time .
* @ param { DOMElement } element the element which will be inserted into the DOM
* @ param { DOMElement } parent the parent element which will append the element as
2016-04-18 12:34:29 +00:00
* a child ( if the after element is not present )
2018-05-05 12:13:16 +02:00
* @ param { DOMElement } after the sibling element which will append the element
2016-04-18 12:34:29 +00:00
* after itself
2018-05-05 12:13:16 +02:00
* @ param { object = } options an optional collection of styles that will be applied to the element .
* @ return { Promise } the animation callback promise
2016-03-28 10:46:51 +00:00
* /
2018-05-05 12:13:16 +02:00
enter : function ( element , parent , after , options ) {
applyStyles ( element , options ) ;
after ? after . after ( element )
: parent . prepend ( element ) ;
return asyncPromise ( ) ;
2016-03-28 10:46:51 +00:00
} ,
/ * *
*
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $animate # leave
* @ kind function
* @ description Removes the element from the DOM . When the function is called a promise
* is returned that will be resolved at a later time .
* @ param { DOMElement } element the element which will be removed from the DOM
* @ param { object = } options an optional collection of options that will be applied to the element .
* @ return { Promise } the animation callback promise
2016-03-28 10:46:51 +00:00
* /
2018-05-05 12:13:16 +02:00
leave : function ( element , options ) {
applyStyles ( element , options ) ;
2016-04-18 12:34:29 +00:00
element . remove ( ) ;
2018-05-05 12:13:16 +02:00
return asyncPromise ( ) ;
2016-03-28 10:46:51 +00:00
} ,
/ * *
*
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $animate # move
* @ kind function
2016-04-18 12:34:29 +00:00
* @ description Moves the position of the provided element within the DOM to be placed
2018-05-05 12:13:16 +02:00
* either after the ` after ` element or inside of the ` parent ` element . When the function
* is called a promise is returned that will be resolved at a later time .
2016-05-18 00:10:50 +00:00
*
2018-05-05 12:13:16 +02:00
* @ param { DOMElement } element the element which will be moved around within the
2016-04-18 12:34:29 +00:00
* DOM
2018-05-05 12:13:16 +02:00
* @ param { DOMElement } parent the parent element where the element will be
2016-04-18 12:34:29 +00:00
* inserted into ( if the after element is not present )
2018-05-05 12:13:16 +02:00
* @ param { DOMElement } after the sibling element where the element will be
2016-04-18 12:34:29 +00:00
* positioned next to
2018-05-05 12:13:16 +02:00
* @ param { object = } options an optional collection of options that will be applied to the element .
* @ return { Promise } the animation callback promise
2016-03-28 10:46:51 +00:00
* /
2018-05-05 12:13:16 +02:00
move : function ( element , parent , after , options ) {
2016-04-18 12:34:29 +00:00
// Do not remove element before insert. Removing will cause data associated with the
// element to be dropped. Insert will implicitly do the remove.
2018-05-05 12:13:16 +02:00
return this . enter ( element , parent , after , options ) ;
2016-03-28 10:46:51 +00:00
} ,
/ * *
*
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $animate # addClass
* @ kind function
* @ description Adds the provided className CSS class value to the provided element .
* When the function is called a promise is returned that will be resolved at a later time .
* @ param { DOMElement } element the element which will have the className value
2016-04-18 12:34:29 +00:00
* added to it
* @ param { string } className the CSS class which will be added to the element
2018-05-05 12:13:16 +02:00
* @ param { object = } options an optional collection of options that will be applied to the element .
* @ return { Promise } the animation callback promise
2016-03-28 10:46:51 +00:00
* /
2018-05-05 12:13:16 +02:00
addClass : function ( element , className , options ) {
return this . setClass ( element , className , [ ] , options ) ;
} ,
$$addClassImmediately : function ( element , className , options ) {
element = jqLite ( element ) ;
className = ! isString ( className )
? ( isArray ( className ) ? className . join ( ' ' ) : '' )
: className ;
forEach ( element , function ( element ) {
2016-04-18 12:34:29 +00:00
jqLiteAddClass ( element , className ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
applyStyles ( element , options ) ;
return asyncPromise ( ) ;
2016-03-28 10:46:51 +00:00
} ,
/ * *
*
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $animate # removeClass
* @ kind function
2016-04-18 12:34:29 +00:00
* @ description Removes the provided className CSS class value from the provided element .
2018-05-05 12:13:16 +02:00
* When the function is called a promise is returned that will be resolved at a later time .
* @ param { DOMElement } element the element which will have the className value
2016-04-18 12:34:29 +00:00
* removed from it
* @ param { string } className the CSS class which will be removed from the element
2018-05-05 12:13:16 +02:00
* @ param { object = } options an optional collection of options that will be applied to the element .
* @ return { Promise } the animation callback promise
2016-03-28 10:46:51 +00:00
* /
2018-05-05 12:13:16 +02:00
removeClass : function ( element , className , options ) {
return this . setClass ( element , [ ] , className , options ) ;
} ,
$$removeClassImmediately : function ( element , className , options ) {
element = jqLite ( element ) ;
className = ! isString ( className )
? ( isArray ( className ) ? className . join ( ' ' ) : '' )
: className ;
forEach ( element , function ( element ) {
2016-04-18 12:34:29 +00:00
jqLiteRemoveClass ( element , className ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
applyStyles ( element , options ) ;
return asyncPromise ( ) ;
} ,
/ * *
*
* @ ngdoc method
* @ name $animate # setClass
* @ kind function
* @ description Adds and / or removes the given CSS classes to and from the element .
* When the function is called a promise is returned that will be resolved at a later time .
* @ param { DOMElement } element the element which will have its CSS classes changed
* removed from it
* @ param { string } add the CSS classes which will be added to the element
* @ param { string } remove the CSS class which will be removed from the element
* @ param { object = } options an optional collection of options that will be applied to the element .
* @ return { Promise } the animation callback promise
* /
setClass : function ( element , add , remove , options ) {
var self = this ;
var STORAGE _KEY = '$$animateClasses' ;
var createdCache = false ;
element = jqLite ( element ) ;
var cache = element . data ( STORAGE _KEY ) ;
if ( ! cache ) {
cache = {
classes : { } ,
options : options
} ;
createdCache = true ;
} else if ( options && cache . options ) {
cache . options = angular . extend ( cache . options || { } , options ) ;
}
var classes = cache . classes ;
add = isArray ( add ) ? add : add . split ( ' ' ) ;
remove = isArray ( remove ) ? remove : remove . split ( ' ' ) ;
cachedClassManipulation ( classes , add , true ) ;
cachedClassManipulation ( classes , remove , false ) ;
if ( createdCache ) {
cache . promise = runAnimationPostDigest ( function ( done ) {
var cache = element . data ( STORAGE _KEY ) ;
element . removeData ( STORAGE _KEY ) ;
// in the event that the element is removed before postDigest
// is run then the cache will be undefined and there will be
// no need anymore to add or remove and of the element classes
if ( cache ) {
var classes = resolveElementClasses ( element , cache . classes ) ;
if ( classes ) {
self . $$setClassImmediately ( element , classes [ 0 ] , classes [ 1 ] , cache . options ) ;
}
}
done ( ) ;
} ) ;
element . data ( STORAGE _KEY , cache ) ;
}
return cache . promise ;
2016-03-28 10:46:51 +00:00
} ,
2018-05-05 12:13:16 +02:00
$$setClassImmediately : function ( element , add , remove , options ) {
add && this . $$addClassImmediately ( element , add ) ;
remove && this . $$removeClassImmediately ( element , remove ) ;
applyStyles ( element , options ) ;
return asyncPromise ( ) ;
} ,
enabled : noop ,
cancel : noop
2016-03-28 10:46:51 +00:00
} ;
} ] ;
2016-04-18 12:34:29 +00:00
} ] ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
function $$AsyncCallbackProvider ( ) {
this . $get = [ '$$rAF' , '$timeout' , function ( $$rAF , $timeout ) {
return $$rAF . supported
? function ( fn ) { return $$rAF ( fn ) ; }
: function ( fn ) {
return $timeout ( fn , 0 , false ) ;
} ;
} ] ;
}
/* global stripHash: true */
2016-03-28 10:46:51 +00:00
/ * *
* ! This is a private undocumented service !
*
2018-05-05 12:13:16 +02:00
* @ name $browser
2016-03-28 10:46:51 +00:00
* @ requires $log
* @ description
* This object has two goals :
*
* - hide all the global state in the browser caused by the window object
* - abstract away all the browser specific features and inconsistencies
*
* For tests we provide { @ link ngMock . $browser mock implementation } of the ` $ browser `
* service , which can be used for convenient testing of the application without the interaction with
* the real browser apis .
* /
/ * *
* @ param { object } window The global window object .
* @ param { object } document jQuery wrapped document .
2018-05-05 12:13:16 +02:00
* @ param { object } $log window . console or an object with the same interface .
2016-03-28 10:46:51 +00:00
* @ param { object } $sniffer $sniffer service
* /
function Browser ( window , document , $log , $sniffer ) {
var self = this ,
2016-04-18 12:34:29 +00:00
rawDocument = document [ 0 ] ,
2016-03-28 10:46:51 +00:00
location = window . location ,
history = window . history ,
setTimeout = window . setTimeout ,
clearTimeout = window . clearTimeout ,
pendingDeferIds = { } ;
self . isMock = false ;
var outstandingRequestCount = 0 ;
var outstandingRequestCallbacks = [ ] ;
// TODO(vojta): remove this temporary api
self . $$completeOutstandingRequest = completeOutstandingRequest ;
self . $$incOutstandingRequestCount = function ( ) { outstandingRequestCount ++ ; } ;
/ * *
* Executes the ` fn ` function ( supports currying ) and decrements the ` outstandingRequestCallbacks `
* counter . If the counter reaches 0 , all the ` outstandingRequestCallbacks ` are executed .
* /
function completeOutstandingRequest ( fn ) {
try {
fn . apply ( null , sliceArgs ( arguments , 1 ) ) ;
} finally {
outstandingRequestCount -- ;
if ( outstandingRequestCount === 0 ) {
2018-05-05 12:13:16 +02:00
while ( outstandingRequestCallbacks . length ) {
2016-03-28 10:46:51 +00:00
try {
outstandingRequestCallbacks . pop ( ) ( ) ;
} catch ( e ) {
$log . error ( e ) ;
}
}
}
}
}
2018-05-05 12:13:16 +02:00
function getHash ( url ) {
var index = url . indexOf ( '#' ) ;
return index === - 1 ? '' : url . substr ( index ) ;
}
2016-03-28 10:46:51 +00:00
/ * *
* @ private
* Note : this method is used only by scenario runner
* TODO ( vojta ) : prefix this method with $$ ?
* @ param { function ( ) } callback Function that will be called when no outstanding request
* /
self . notifyWhenNoOutstandingRequests = function ( callback ) {
2016-04-18 12:34:29 +00:00
// force browser to execute all pollFns - this is needed so that cookies and other pollers fire
// at some deterministic time in respect to the test runner's actions. Leaving things up to the
// regular poller would result in flaky tests.
2018-05-05 12:13:16 +02:00
forEach ( pollFns , function ( pollFn ) { pollFn ( ) ; } ) ;
2016-04-18 12:34:29 +00:00
2016-03-28 10:46:51 +00:00
if ( outstandingRequestCount === 0 ) {
callback ( ) ;
} else {
outstandingRequestCallbacks . push ( callback ) ;
}
} ;
//////////////////////////////////////////////////////////////
2016-04-18 12:34:29 +00:00
// Poll Watcher API
2016-03-28 10:46:51 +00:00
//////////////////////////////////////////////////////////////
2016-04-18 12:34:29 +00:00
var pollFns = [ ] ,
pollTimeout ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ name $browser # addPollFn
2016-04-18 12:34:29 +00:00
*
* @ param { function ( ) } fn Poll function to add
*
* @ description
* Adds a function to the list of functions that poller periodically executes ,
* and starts polling if not started yet .
*
* @ returns { function ( ) } the added function
* /
self . addPollFn = function ( fn ) {
if ( isUndefined ( pollTimeout ) ) startPoller ( 100 , setTimeout ) ;
pollFns . push ( fn ) ;
return fn ;
} ;
/ * *
* @ param { number } interval How often should browser call poll functions ( ms )
* @ param { function ( ) } setTimeout Reference to a real or fake ` setTimeout ` function .
*
* @ description
* Configures the poller to run in the specified intervals , using the specified
* setTimeout fn and kicks it off .
* /
function startPoller ( interval , setTimeout ) {
( function check ( ) {
2018-05-05 12:13:16 +02:00
forEach ( pollFns , function ( pollFn ) { pollFn ( ) ; } ) ;
2016-04-18 12:34:29 +00:00
pollTimeout = setTimeout ( check , interval ) ;
} ) ( ) ;
}
//////////////////////////////////////////////////////////////
// URL API
//////////////////////////////////////////////////////////////
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
var cachedState , lastHistoryState ,
lastBrowserUrl = location . href ,
2016-04-18 12:34:29 +00:00
baseElement = document . find ( 'base' ) ,
2018-05-05 12:13:16 +02:00
reloadLocation = null ;
cacheState ( ) ;
lastHistoryState = cachedState ;
2016-03-28 10:46:51 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ name $browser # url
2016-03-28 10:46:51 +00:00
*
* @ description
* GETTER :
* Without any argument , this method just returns current value of location . href .
*
* SETTER :
* With at least one argument , this method sets url to new value .
* If html5 history api supported , pushState / replaceState is used , otherwise
* location . href / location . replace is used .
* Returns its own instance to allow chaining
*
* NOTE : this api is intended for use only by the $location service . Please use the
* { @ link ng . $location $location service } to change url .
*
* @ param { string } url New url ( when used as setter )
2018-05-05 12:13:16 +02:00
* @ param { boolean = } replace Should new url replace current history record ?
* @ param { object = } state object to use with pushState / replaceState
2016-03-28 10:46:51 +00:00
* /
2018-05-05 12:13:16 +02:00
self . url = function ( url , replace , state ) {
// In modern browsers `history.state` is `null` by default; treating it separately
// from `undefined` would cause `$browser.url('/foo')` to change `history.state`
// to undefined via `pushState`. Instead, let's change `undefined` to `null` here.
if ( isUndefined ( state ) ) {
state = null ;
}
2016-03-28 10:46:51 +00:00
// Android Browser BFCache causes location, history reference to become stale.
if ( location !== window . location ) location = window . location ;
if ( history !== window . history ) history = window . history ;
// setter
if ( url ) {
2018-05-05 12:13:16 +02:00
var sameState = lastHistoryState === state ;
// Don't change anything if previous and current URLs and states match. This also prevents
// IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode.
// See https://github.com/angular/angular.js/commit/ffb2701
if ( lastBrowserUrl === url && ( ! $sniffer . history || sameState ) ) {
return self ;
}
var sameBase = lastBrowserUrl && stripHash ( lastBrowserUrl ) === stripHash ( url ) ;
2016-03-28 10:46:51 +00:00
lastBrowserUrl = url ;
2018-05-05 12:13:16 +02:00
lastHistoryState = state ;
// Don't use history API if only the hash changed
// due to a bug in IE10/IE11 which leads
// to not firing a `hashchange` nor `popstate` event
// in some cases (see #9143).
if ( $sniffer . history && ( ! sameBase || ! sameState ) ) {
history [ replace ? 'replaceState' : 'pushState' ] ( state , '' , url ) ;
cacheState ( ) ;
// Do the assignment again so that those two variables are referentially identical.
lastHistoryState = cachedState ;
2016-04-18 12:34:29 +00:00
} else {
2018-05-05 12:13:16 +02:00
if ( ! sameBase || reloadLocation ) {
reloadLocation = url ;
}
2016-03-28 10:46:51 +00:00
if ( replace ) {
location . replace ( url ) ;
2018-05-05 12:13:16 +02:00
} else if ( ! sameBase ) {
2016-04-18 12:34:29 +00:00
location . href = url ;
2018-05-05 12:13:16 +02:00
} else {
location . hash = getHash ( url ) ;
2016-03-28 10:46:51 +00:00
}
}
return self ;
// getter
} else {
2018-05-05 12:13:16 +02:00
// - reloadLocation is needed as browsers don't allow to read out
// the new location.href if a reload happened.
2016-03-28 10:46:51 +00:00
// - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
2018-05-05 12:13:16 +02:00
return reloadLocation || location . href . replace ( /%27/g , "'" ) ;
2016-03-28 10:46:51 +00:00
}
} ;
2018-05-05 12:13:16 +02:00
/ * *
* @ name $browser # state
*
* @ description
* This method is a getter .
*
* Return history . state or null if history . state is undefined .
*
* @ returns { object } state
* /
self . state = function ( ) {
return cachedState ;
} ;
2016-03-28 10:46:51 +00:00
var urlChangeListeners = [ ] ,
urlChangeInit = false ;
2018-05-05 12:13:16 +02:00
function cacheStateAndFireUrlChange ( ) {
cacheState ( ) ;
fireUrlChange ( ) ;
}
function getCurrentState ( ) {
try {
return history . state ;
} catch ( e ) {
// MSIE can reportedly throw when there is no state (UNCONFIRMED).
}
}
// This variable should be used *only* inside the cacheState function.
var lastCachedState = null ;
function cacheState ( ) {
// This should be the only place in $browser where `history.state` is read.
cachedState = getCurrentState ( ) ;
cachedState = isUndefined ( cachedState ) ? null : cachedState ;
// Prevent callbacks fo fire twice if both hashchange & popstate were fired.
if ( equals ( cachedState , lastCachedState ) ) {
cachedState = lastCachedState ;
}
lastCachedState = cachedState ;
}
2016-03-28 10:46:51 +00:00
function fireUrlChange ( ) {
2018-05-05 12:13:16 +02:00
if ( lastBrowserUrl === self . url ( ) && lastHistoryState === cachedState ) {
return ;
}
2016-03-28 10:46:51 +00:00
lastBrowserUrl = self . url ( ) ;
2018-05-05 12:13:16 +02:00
lastHistoryState = cachedState ;
2016-03-28 10:46:51 +00:00
forEach ( urlChangeListeners , function ( listener ) {
2018-05-05 12:13:16 +02:00
listener ( self . url ( ) , cachedState ) ;
2016-03-28 10:46:51 +00:00
} ) ;
}
/ * *
2018-05-05 12:13:16 +02:00
* @ name $browser # onUrlChange
2016-03-28 10:46:51 +00:00
*
* @ description
* Register callback function that will be called , when url changes .
*
* It ' s only called when the url is changed from outside of angular :
* - user types different url into address bar
* - user clicks on history ( forward / back ) button
* - user clicks on a link
*
* It ' s not called when url is changed by $browser . url ( ) method
*
* The listener gets called with new url as parameter .
*
* NOTE : this api is intended for use only by the $location service . Please use the
* { @ link ng . $location $location service } to monitor url changes in angular apps .
*
* @ param { function ( string ) } listener Listener function to be called when url changes .
* @ return { function ( string ) } Returns the registered listener fn - handy if the fn is anonymous .
* /
self . onUrlChange = function ( callback ) {
2018-05-05 12:13:16 +02:00
// TODO(vojta): refactor to use node's syntax for events
2016-03-28 10:46:51 +00:00
if ( ! urlChangeInit ) {
// We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera)
// don't fire popstate when user change the address bar and don't fire hashchange when url
// changed by push/replaceState
// html5 history api - popstate event
2018-05-05 12:13:16 +02:00
if ( $sniffer . history ) jqLite ( window ) . on ( 'popstate' , cacheStateAndFireUrlChange ) ;
2016-03-28 10:46:51 +00:00
// hashchange event
2018-05-05 12:13:16 +02:00
jqLite ( window ) . on ( 'hashchange' , cacheStateAndFireUrlChange ) ;
2016-03-28 10:46:51 +00:00
urlChangeInit = true ;
}
urlChangeListeners . push ( callback ) ;
return callback ;
} ;
2018-05-05 12:13:16 +02:00
/ * *
* Checks whether the url has changed outside of Angular .
* Needs to be exported to be able to check for changes that have been done in sync ,
* as hashchange / popstate events fire in async .
* /
self . $$checkUrlChange = fireUrlChange ;
2016-03-28 10:46:51 +00:00
//////////////////////////////////////////////////////////////
// Misc API
//////////////////////////////////////////////////////////////
/ * *
2018-05-05 12:13:16 +02:00
* @ name $browser # baseHref
2016-03-28 10:46:51 +00:00
*
* @ description
* Returns current < base href >
* ( always relative - without domain )
*
2018-05-05 12:13:16 +02:00
* @ returns { string } The current base href
2016-03-28 10:46:51 +00:00
* /
self . baseHref = function ( ) {
var href = baseElement . attr ( 'href' ) ;
return href ? href . replace ( /^(https?\:)?\/\/[^\/]*/ , '' ) : '' ;
} ;
2016-04-18 12:34:29 +00:00
//////////////////////////////////////////////////////////////
// Cookies API
//////////////////////////////////////////////////////////////
var lastCookies = { } ;
var lastCookieString = '' ;
var cookiePath = self . baseHref ( ) ;
2018-05-05 12:13:16 +02:00
function safeDecodeURIComponent ( str ) {
try {
return decodeURIComponent ( str ) ;
} catch ( e ) {
return str ;
}
}
2016-04-18 12:34:29 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ name $browser # cookies
2016-04-18 12:34:29 +00:00
*
* @ param { string = } name Cookie name
* @ param { string = } value Cookie value
*
* @ description
* The cookies method provides a 'private' low level access to browser cookies .
* It is not meant to be used directly , use the $cookie service instead .
*
* The return values vary depending on the arguments that the method was called with as follows :
*
* - cookies ( ) - > hash of all cookies , this is NOT a copy of the internal state , so do not modify
* it
* - cookies ( name , value ) - > set name to value , if value is undefined delete the cookie
* - cookies ( name ) - > the same as ( name , undefined ) == DELETES ( no one calls it right now that
* way )
*
* @ returns { Object } Hash of all cookies ( if called without any parameter )
* /
self . cookies = function ( name , value ) {
var cookieLength , cookieArray , cookie , i , index ;
if ( name ) {
if ( value === undefined ) {
2018-05-05 12:13:16 +02:00
rawDocument . cookie = encodeURIComponent ( name ) + "=;path=" + cookiePath +
2016-04-18 12:34:29 +00:00
";expires=Thu, 01 Jan 1970 00:00:00 GMT" ;
} else {
if ( isString ( value ) ) {
2018-05-05 12:13:16 +02:00
cookieLength = ( rawDocument . cookie = encodeURIComponent ( name ) + '=' + encodeURIComponent ( value ) +
2016-04-18 12:34:29 +00:00
';path=' + cookiePath ) . length + 1 ;
// per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum:
// - 300 cookies
// - 20 cookies per unique domain
// - 4096 bytes per cookie
if ( cookieLength > 4096 ) {
2018-05-05 12:13:16 +02:00
$log . warn ( "Cookie '" + name +
"' possibly not set or overflowed because it was too large (" +
2016-04-18 12:34:29 +00:00
cookieLength + " > 4096 bytes)!" ) ;
}
}
}
} else {
if ( rawDocument . cookie !== lastCookieString ) {
lastCookieString = rawDocument . cookie ;
cookieArray = lastCookieString . split ( "; " ) ;
lastCookies = { } ;
for ( i = 0 ; i < cookieArray . length ; i ++ ) {
cookie = cookieArray [ i ] ;
index = cookie . indexOf ( '=' ) ;
if ( index > 0 ) { //ignore nameless cookies
2018-05-05 12:13:16 +02:00
name = safeDecodeURIComponent ( cookie . substring ( 0 , index ) ) ;
2016-04-18 12:34:29 +00:00
// the first value that is seen for a cookie is the most
// specific one. values for the same cookie name that
// follow are for less specific paths.
if ( lastCookies [ name ] === undefined ) {
2018-05-05 12:13:16 +02:00
lastCookies [ name ] = safeDecodeURIComponent ( cookie . substring ( index + 1 ) ) ;
2016-04-18 12:34:29 +00:00
}
}
}
}
return lastCookies ;
}
} ;
2016-03-28 10:46:51 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ name $browser # defer
2016-03-28 10:46:51 +00:00
* @ param { function ( ) } fn A function , who ' s execution should be deferred .
* @ param { number = } [ delay = 0 ] of milliseconds to defer the function execution .
* @ returns { * } DeferId that can be used to cancel the task via ` $ browser.defer.cancel() ` .
*
* @ description
* Executes a fn asynchronously via ` setTimeout(fn, delay) ` .
*
* Unlike when calling ` setTimeout ` directly , in test this function is mocked and instead of using
* ` setTimeout ` in tests , the fns are queued in an array , which can be programmatically flushed
* via ` $ browser.defer.flush() ` .
*
* /
self . defer = function ( fn , delay ) {
var timeoutId ;
outstandingRequestCount ++ ;
timeoutId = setTimeout ( function ( ) {
delete pendingDeferIds [ timeoutId ] ;
completeOutstandingRequest ( fn ) ;
} , delay || 0 ) ;
pendingDeferIds [ timeoutId ] = true ;
return timeoutId ;
} ;
/ * *
2018-05-05 12:13:16 +02:00
* @ name $browser # defer . cancel
2016-03-28 10:46:51 +00:00
*
* @ description
* Cancels a deferred task identified with ` deferId ` .
*
* @ param { * } deferId Token returned by the ` $ browser.defer ` function .
* @ returns { boolean } Returns ` true ` if the task hasn ' t executed yet and was successfully
* canceled .
* /
self . defer . cancel = function ( deferId ) {
if ( pendingDeferIds [ deferId ] ) {
delete pendingDeferIds [ deferId ] ;
clearTimeout ( deferId ) ;
completeOutstandingRequest ( noop ) ;
return true ;
}
return false ;
} ;
}
2018-05-05 12:13:16 +02:00
function $BrowserProvider ( ) {
2016-03-28 10:46:51 +00:00
this . $get = [ '$window' , '$log' , '$sniffer' , '$document' ,
2018-05-05 12:13:16 +02:00
function ( $window , $log , $sniffer , $document ) {
2016-03-28 10:46:51 +00:00
return new Browser ( $window , $document , $log , $sniffer ) ;
} ] ;
}
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc service
* @ name $cacheFactory
2016-03-28 10:46:51 +00:00
*
* @ description
2018-05-05 12:13:16 +02:00
* Factory that constructs { @ link $cacheFactory . Cache Cache } objects and gives access to
* them .
2016-05-18 00:10:50 +00:00
*
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-05-18 00:10:50 +00:00
*
2016-03-28 10:46:51 +00:00
* var cache = $cacheFactory ( 'cacheId' ) ;
* expect ( $cacheFactory . get ( 'cacheId' ) ) . toBe ( cache ) ;
* expect ( $cacheFactory . get ( 'noSuchCacheId' ) ) . not . toBeDefined ( ) ;
*
* cache . put ( "key" , "value" ) ;
* cache . put ( "another key" , "another value" ) ;
*
* // We've specified no options on creation
2016-05-18 00:10:50 +00:00
* expect ( cache . info ( ) ) . toEqual ( { id : 'cacheId' , size : 2 } ) ;
*
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
*
* @ param { string } cacheId Name or id of the newly created cache .
* @ param { object = } options Options object that specifies the cache behavior . Properties :
*
* - ` {number=} ` ` capacity ` — turns the cache into LRU cache .
*
* @ returns { object } Newly created cache object with the following set of methods :
*
* - ` {object} ` ` info() ` — Returns id , size , and options of cache .
* - ` {{*}} ` ` put({string} key, {*} value) ` — Puts a new key - value pair into the cache and returns
* it .
* - ` {{*}} ` ` get({string} key) ` — Returns cached value for ` key ` or undefined for cache miss .
* - ` {void} ` ` remove({string} key) ` — Removes a key - value pair from the cache .
* - ` {void} ` ` removeAll() ` — Removes all cached values .
* - ` {void} ` ` destroy() ` — Removes references to this cache from $cacheFactory .
*
2018-05-05 12:13:16 +02:00
* @ example
< example module = "cacheExampleApp" >
< file name = "index.html" >
< div ng - controller = "CacheController" >
< input ng - model = "newCacheKey" placeholder = "Key" >
< input ng - model = "newCacheValue" placeholder = "Value" >
< button ng - click = "put(newCacheKey, newCacheValue)" > Cache < / b u t t o n >
< p ng - if = "keys.length" > Cached Values < / p >
< div ng - repeat = "key in keys" >
< span ng - bind = "key" > < / s p a n >
< span > : < / s p a n >
< b ng - bind = "cache.get(key)" > < / b >
< / d i v >
< p > Cache Info < / p >
< div ng - repeat = "(key, value) in cache.info()" >
< span ng - bind = "key" > < / s p a n >
< span > : < / s p a n >
< b ng - bind = "value" > < / b >
< / d i v >
< / d i v >
< / f i l e >
< file name = "script.js" >
angular . module ( 'cacheExampleApp' , [ ] ) .
controller ( 'CacheController' , [ '$scope' , '$cacheFactory' , function ( $scope , $cacheFactory ) {
$scope . keys = [ ] ;
$scope . cache = $cacheFactory ( 'cacheId' ) ;
$scope . put = function ( key , value ) {
if ( $scope . cache . get ( key ) === undefined ) {
$scope . keys . push ( key ) ;
}
$scope . cache . put ( key , value === undefined ? null : value ) ;
} ;
} ] ) ;
< / f i l e >
< file name = "style.css" >
p {
margin : 10 px 0 3 px ;
}
< / f i l e >
< / e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
function $CacheFactoryProvider ( ) {
this . $get = function ( ) {
var caches = { } ;
function cacheFactory ( cacheId , options ) {
if ( cacheId in caches ) {
throw minErr ( '$cacheFactory' ) ( 'iid' , "CacheId '{0}' is already taken!" , cacheId ) ;
}
var size = 0 ,
stats = extend ( { } , options , { id : cacheId } ) ,
2016-04-18 12:34:29 +00:00
data = { } ,
2016-03-28 10:46:51 +00:00
capacity = ( options && options . capacity ) || Number . MAX _VALUE ,
2016-04-18 12:34:29 +00:00
lruHash = { } ,
2016-03-28 10:46:51 +00:00
freshEnd = null ,
staleEnd = null ;
2018-05-05 12:13:16 +02:00
/ * *
* @ ngdoc type
* @ name $cacheFactory . Cache
*
* @ description
* A cache object used to store and retrieve data , primarily used by
* { @ link $http $http } and the { @ link ng . directive : script script } directive to cache
* templates and other data .
*
* ` ` ` js
* angular . module ( 'superCache' )
* . factory ( 'superCache' , [ '$cacheFactory' , function ( $cacheFactory ) {
* return $cacheFactory ( 'super-cache' ) ;
* } ] ) ;
* ` ` `
*
* Example test :
*
* ` ` ` js
* it ( 'should behave like a cache' , inject ( function ( superCache ) {
* superCache . put ( 'key' , 'value' ) ;
* superCache . put ( 'another key' , 'another value' ) ;
*
* expect ( superCache . info ( ) ) . toEqual ( {
* id : 'super-cache' ,
* size : 2
* } ) ;
*
* superCache . remove ( 'another key' ) ;
* expect ( superCache . get ( 'another key' ) ) . toBeUndefined ( ) ;
*
* superCache . removeAll ( ) ;
* expect ( superCache . info ( ) ) . toEqual ( {
* id : 'super-cache' ,
* size : 0
* } ) ;
* } ) ) ;
* ` ` `
* /
2016-03-28 10:46:51 +00:00
return caches [ cacheId ] = {
2018-05-05 12:13:16 +02:00
/ * *
* @ ngdoc method
* @ name $cacheFactory . Cache # put
* @ kind function
*
* @ description
* Inserts a named entry into the { @ link $cacheFactory . Cache Cache } object to be
* retrieved later , and incrementing the size of the cache if the key was not already
* present in the cache . If behaving like an LRU cache , it will also remove stale
* entries from the set .
*
* It will not insert undefined values into the cache .
*
* @ param { string } key the key under which the cached data is stored .
* @ param { * } value the value to store alongside the key . If it is undefined , the key
* will not be stored .
* @ returns { * } the value stored .
* /
2016-03-28 10:46:51 +00:00
put : function ( key , value ) {
2018-05-05 12:13:16 +02:00
if ( capacity < Number . MAX _VALUE ) {
var lruEntry = lruHash [ key ] || ( lruHash [ key ] = { key : key } ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
refresh ( lruEntry ) ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( isUndefined ( value ) ) return ;
2016-03-28 10:46:51 +00:00
if ( ! ( key in data ) ) size ++ ;
data [ key ] = value ;
if ( size > capacity ) {
this . remove ( staleEnd . key ) ;
}
return value ;
} ,
2018-05-05 12:13:16 +02:00
/ * *
* @ ngdoc method
* @ name $cacheFactory . Cache # get
* @ kind function
*
* @ description
* Retrieves named data stored in the { @ link $cacheFactory . Cache Cache } object .
*
* @ param { string } key the key of the data to be retrieved
* @ returns { * } the value stored .
* /
2016-03-28 10:46:51 +00:00
get : function ( key ) {
2018-05-05 12:13:16 +02:00
if ( capacity < Number . MAX _VALUE ) {
var lruEntry = lruHash [ key ] ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
if ( ! lruEntry ) return ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
refresh ( lruEntry ) ;
}
2016-03-28 10:46:51 +00:00
return data [ key ] ;
} ,
2018-05-05 12:13:16 +02:00
/ * *
* @ ngdoc method
* @ name $cacheFactory . Cache # remove
* @ kind function
*
* @ description
* Removes an entry from the { @ link $cacheFactory . Cache Cache } object .
*
* @ param { string } key the key of the entry to be removed
* /
2016-03-28 10:46:51 +00:00
remove : function ( key ) {
2018-05-05 12:13:16 +02:00
if ( capacity < Number . MAX _VALUE ) {
var lruEntry = lruHash [ key ] ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
if ( ! lruEntry ) return ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
if ( lruEntry == freshEnd ) freshEnd = lruEntry . p ;
if ( lruEntry == staleEnd ) staleEnd = lruEntry . n ;
link ( lruEntry . n , lruEntry . p ) ;
delete lruHash [ key ] ;
}
2016-03-28 10:46:51 +00:00
delete data [ key ] ;
size -- ;
} ,
2018-05-05 12:13:16 +02:00
/ * *
* @ ngdoc method
* @ name $cacheFactory . Cache # removeAll
* @ kind function
*
* @ description
* Clears the cache object of any entries .
* /
2016-03-28 10:46:51 +00:00
removeAll : function ( ) {
2016-04-18 12:34:29 +00:00
data = { } ;
2016-03-28 10:46:51 +00:00
size = 0 ;
2016-04-18 12:34:29 +00:00
lruHash = { } ;
2016-03-28 10:46:51 +00:00
freshEnd = staleEnd = null ;
} ,
2018-05-05 12:13:16 +02:00
/ * *
* @ ngdoc method
* @ name $cacheFactory . Cache # destroy
* @ kind function
*
* @ description
* Destroys the { @ link $cacheFactory . Cache Cache } object entirely ,
* removing it from the { @ link $cacheFactory $cacheFactory } set .
* /
2016-03-28 10:46:51 +00:00
destroy : function ( ) {
data = null ;
stats = null ;
lruHash = null ;
delete caches [ cacheId ] ;
} ,
2018-05-05 12:13:16 +02:00
/ * *
* @ ngdoc method
* @ name $cacheFactory . Cache # info
* @ kind function
*
* @ description
* Retrieve information regarding a particular { @ link $cacheFactory . Cache Cache } .
*
* @ returns { object } an object with the following properties :
* < ul >
* < li > * * id * * : the id of the cache instance < / l i >
* < li > * * size * * : the number of entries kept in the cache instance < / l i >
* < li > * * ... * * : any additional properties from the options object when creating the
* cache . < / l i >
* < / u l >
* /
2016-03-28 10:46:51 +00:00
info : function ( ) {
return extend ( { } , stats , { size : size } ) ;
}
} ;
/ * *
* makes the ` entry ` the freshEnd of the LRU linked list
* /
function refresh ( entry ) {
if ( entry != freshEnd ) {
if ( ! staleEnd ) {
staleEnd = entry ;
} else if ( staleEnd == entry ) {
staleEnd = entry . n ;
}
link ( entry . n , entry . p ) ;
link ( entry , freshEnd ) ;
freshEnd = entry ;
freshEnd . n = null ;
}
}
/ * *
* bidirectionally links two entries of the LRU linked list
* /
function link ( nextEntry , prevEntry ) {
if ( nextEntry != prevEntry ) {
if ( nextEntry ) nextEntry . p = prevEntry ; //p stands for previous, 'prev' didn't minify
if ( prevEntry ) prevEntry . n = nextEntry ; //n stands for next, 'next' didn't minify
}
}
}
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $cacheFactory # info
2016-03-28 10:46:51 +00:00
*
* @ description
2018-05-05 12:13:16 +02:00
* Get information about all the caches that have been created
2016-03-28 10:46:51 +00:00
*
* @ returns { Object } - key - value map of ` cacheId ` to the result of calling ` cache#info `
* /
cacheFactory . info = function ( ) {
var info = { } ;
forEach ( caches , function ( cache , cacheId ) {
info [ cacheId ] = cache . info ( ) ;
} ) ;
return info ;
} ;
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $cacheFactory # get
2016-03-28 10:46:51 +00:00
*
* @ description
* Get access to a cache object by the ` cacheId ` used when it was created .
*
* @ param { string } cacheId Name or id of a cache to access .
* @ returns { object } Cache object identified by the cacheId or undefined if no such cache .
* /
cacheFactory . get = function ( cacheId ) {
return caches [ cacheId ] ;
} ;
return cacheFactory ;
} ;
}
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc service
* @ name $templateCache
2016-03-28 10:46:51 +00:00
*
* @ description
* The first time a template is used , it is loaded in the template cache for quick retrieval . You
* can load templates directly into the cache in a ` script ` tag , or by consuming the
* ` $ templateCache ` service directly .
2016-05-18 00:10:50 +00:00
*
2016-03-28 10:46:51 +00:00
* Adding via the ` script ` tag :
2018-05-05 12:13:16 +02:00
*
* ` ` ` html
* < script type = "text/ng-template" id = "templateId.html" >
* < p > This is the content of the template < / p >
* < / s c r i p t >
* ` ` `
2016-05-18 00:10:50 +00:00
*
2016-03-28 10:46:51 +00:00
* * * Note : * * the ` script ` tag containing the template does not need to be included in the ` head ` of
2018-05-05 12:13:16 +02:00
* the document , but it must be a descendent of the { @ link ng . $rootElement $rootElement } ( IE ,
* element with ng - app attribute ) , otherwise the template will be ignored .
2016-05-18 00:10:50 +00:00
*
2018-05-05 12:13:16 +02:00
* Adding via the ` $ templateCache ` service :
2016-05-18 00:10:50 +00:00
*
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
* var myApp = angular . module ( 'myApp' , [ ] ) ;
* myApp . run ( function ( $templateCache ) {
* $templateCache . put ( 'templateId.html' , 'This is the content of the template' ) ;
* } ) ;
2018-05-05 12:13:16 +02:00
* ` ` `
2016-05-18 00:10:50 +00:00
*
2016-03-28 10:46:51 +00:00
* To retrieve the template later , simply use it in your HTML :
2018-05-05 12:13:16 +02:00
* ` ` ` html
2016-03-28 10:46:51 +00:00
* < div ng - include = " 'templateId.html' " > < / d i v >
2018-05-05 12:13:16 +02:00
* ` ` `
2016-05-18 00:10:50 +00:00
*
2016-03-28 10:46:51 +00:00
* or get it via Javascript :
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
* $templateCache . get ( 'templateId.html' )
2018-05-05 12:13:16 +02:00
* ` ` `
2016-05-18 00:10:50 +00:00
*
2016-03-28 10:46:51 +00:00
* See { @ link ng . $cacheFactory $cacheFactory } .
*
* /
function $TemplateCacheProvider ( ) {
this . $get = [ '$cacheFactory' , function ( $cacheFactory ) {
return $cacheFactory ( 'templates' ) ;
} ] ;
}
2018-05-05 12:13:16 +02:00
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Any commits to this file should be reviewed with security in mind . *
* Changes to this file can potentially create security vulnerabilities . *
* An approval from 2 Core members with history of modifying *
* this file is required . *
* *
* Does the change somehow allow for arbitrary javascript to be executed ? *
* Or allows for someone to change the prototype of built - in objects ? *
* Or gives undesired access to variables likes document or window ? *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
2016-03-28 10:46:51 +00:00
/ * ! V A R I A B L E / F U N C T I O N N A M I N G C O N V E N T I O N S T H A T A P P L Y T O T H I S F I L E !
*
* DOM - related variables :
*
* - "node" - DOM Node
* - "element" - DOM Element or Node
* - "$node" or "$element" - jqLite - wrapped node or element
*
*
* Compiler related stuff :
*
* - "linkFn" - linking fn of a single directive
* - "nodeLinkFn" - function that aggregates all linking fns for a particular node
* - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node
* - "compositeLinkFn" - function that aggregates all linking fns for a compilation root ( nodeList )
* /
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc service
* @ name $compile
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Compiles an HTML string or DOM into a template and produces a template function , which
* can then be used to link { @ link ng . $rootScope . Scope ` scope ` } and the template together .
*
* The compilation is a process of walking the DOM tree and matching DOM elements to
2018-05-05 12:13:16 +02:00
* { @ link ng . $compileProvider # directive directives } .
2016-03-28 10:46:51 +00:00
*
* < div class = "alert alert-warning" >
* * * Note : * * This document is an in - depth reference of all directive options .
* For a gentle introduction to directives with examples of common use cases ,
* see the { @ link guide / directive directive guide } .
* < / d i v >
*
* # # Comprehensive Directive API
*
* There are many different options for a directive .
*
* The difference resides in the return value of the factory function .
* You can either return a "Directive Definition Object" ( see below ) that defines the directive properties ,
* or just the ` postLink ` function ( all other properties will have the default values ) .
*
* < div class = "alert alert-success" >
* * * Best Practice : * * It ' s recommended to use the "directive definition object" form .
* < / d i v >
*
* Here ' s an example directive declared with a Directive Definition Object :
*
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
* var myModule = angular . module ( ... ) ;
*
* myModule . directive ( 'directiveName' , function factory ( injectables ) {
* var directiveDefinitionObject = {
* priority : 0 ,
* template : '<div></div>' , // or // function(tElement, tAttrs) { ... },
* // or
* // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... },
* transclude : false ,
* restrict : 'A' ,
2018-05-05 12:13:16 +02:00
* templateNamespace : 'html' ,
2016-03-28 10:46:51 +00:00
* scope : false ,
* controller : function ( $scope , $element , $attrs , $transclude , otherInjectables ) { ... } ,
2018-05-05 12:13:16 +02:00
* controllerAs : 'stringIdentifier' ,
* bindToController : false ,
2016-03-28 10:46:51 +00:00
* require : 'siblingDirectiveName' , // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'],
* compile : function compile ( tElement , tAttrs , transclude ) {
* return {
* pre : function preLink ( scope , iElement , iAttrs , controller ) { ... } ,
* post : function postLink ( scope , iElement , iAttrs , controller ) { ... }
* }
* // or
* // return function postLink( ... ) { ... }
* } ,
* // or
* // link: {
* // pre: function preLink(scope, iElement, iAttrs, controller) { ... },
* // post: function postLink(scope, iElement, iAttrs, controller) { ... }
* // }
* // or
* // link: function postLink( ... ) { ... }
* } ;
* return directiveDefinitionObject ;
* } ) ;
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
* < div class = "alert alert-warning" >
* * * Note : * * Any unspecified options will use the default value . You can see the default values below .
* < / d i v >
*
* Therefore the above can be simplified as :
*
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
* var myModule = angular . module ( ... ) ;
*
* myModule . directive ( 'directiveName' , function factory ( injectables ) {
* var directiveDefinitionObject = {
* link : function postLink ( scope , iElement , iAttrs ) { ... }
* } ;
* return directiveDefinitionObject ;
* // or
* // return function postLink(scope, iElement, iAttrs) { ... }
* } ) ;
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
*
*
* # # # Directive Definition Object
*
2018-05-05 12:13:16 +02:00
* The directive definition object provides instructions to the { @ link ng . $compile
2016-03-28 10:46:51 +00:00
* compiler } . The attributes are :
*
2018-05-05 12:13:16 +02:00
* # # # # ` multiElement `
* When this property is set to true , the HTML compiler will collect DOM nodes between
* nodes with the attributes ` directive-name-start ` and ` directive-name-end ` , and group them
* together as the directive elements . It is recommended that this feature be used on directives
* which are not strictly behavioural ( such as { @ link ngClick } ) , and which
* do not manipulate or replace child nodes ( such as { @ link ngInclude } ) .
*
2016-03-28 10:46:51 +00:00
* # # # # ` priority `
* When there are multiple directives defined on a single DOM element , sometimes it
* is necessary to specify the order in which the directives are applied . The ` priority ` is used
* to sort the directives before their ` compile ` functions get called . Priority is defined as a
* number . Directives with greater numerical ` priority ` are compiled first . Pre - link functions
* are also run in priority order , but post - link functions are run in reverse order . The order
* of directives with the same priority is undefined . The default priority is ` 0 ` .
*
* # # # # ` terminal `
* If set to true then the current ` priority ` will be the last set of directives
* which will execute ( any directives at the current priority will still execute
2018-05-05 12:13:16 +02:00
* as the order of execution on same ` priority ` is undefined ) . Note that expressions
* and other directives used in the directive ' s template will also be excluded from execution .
2016-03-28 10:46:51 +00:00
*
* # # # # ` scope `
2016-04-18 12:34:29 +00:00
* * * If set to ` true ` , * * then a new scope will be created for this directive . If multiple directives on the
* same element request a new scope , only one new scope is created . The new scope rule does not
* apply for the root of the template since the root of the template always gets a new scope .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* * * If set to ` {} ` ( object hash ) , * * then a new "isolate" scope is created . The 'isolate' scope differs from
* normal scope in that it does not prototypically inherit from the parent scope . This is useful
* when creating reusable components , which should not accidentally read or modify data in the
* parent scope .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* The 'isolate' scope takes an object hash which defines a set of local scope properties
* derived from the parent scope . These local properties are useful for aliasing values for
* templates . Locals definition is a hash of local scope property to its source :
2016-03-28 10:46:51 +00:00
*
* * ` @ ` or ` @attr ` - bind a local scope property to the value of DOM attribute . The result is
2016-04-18 12:34:29 +00:00
* always a string since DOM attributes are strings . If no ` attr ` name is specified then the
* attribute name is assumed to be the same as the local name .
* Given ` <widget my-attr="hello {{name}}"> ` and widget definition
* of ` scope: { localName:'@myAttr' } ` , then widget scope property ` localName ` will reflect
* the interpolated value of ` hello {{name}} ` . As the ` name ` attribute changes so will the
* ` localName ` property on the widget scope . The ` name ` is read from the parent scope ( not
* component scope ) .
*
* * ` = ` or ` =attr ` - set up bi - directional binding between a local scope property and the
* parent scope property of name defined via the value of the ` attr ` attribute . If no ` attr `
* name is specified then the attribute name is assumed to be the same as the local name .
* Given ` <widget my-attr="parentModel"> ` and widget definition of
* ` scope: { localModel:'=myAttr' } ` , then widget scope property ` localModel ` will reflect the
2016-03-28 10:46:51 +00:00
* value of ` parentModel ` on the parent scope . Any changes to ` parentModel ` will be reflected
2016-04-18 12:34:29 +00:00
* in ` localModel ` and any changes in ` localModel ` will reflect in ` parentModel ` . If the parent
* scope property doesn ' t exist , it will throw a NON _ASSIGNABLE _MODEL _EXPRESSION exception . You
2018-05-05 12:13:16 +02:00
* can avoid this behavior using ` =? ` or ` =?attr ` in order to flag the property as optional . If
* you want to shallow watch for changes ( i . e . $watchCollection instead of $watch ) you can use
* ` =* ` or ` =*attr ` ( ` =*? ` or ` =*?attr ` if the property is optional ) .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* * ` & ` or ` &attr ` - provides a way to execute an expression in the context of the parent scope .
* If no ` attr ` name is specified then the attribute name is assumed to be the same as the
* local name . Given ` <widget my-attr="count = count + value"> ` and widget definition of
* ` scope: { localFn:'&myAttr' } ` , then isolate scope property ` localFn ` will point to
* a function wrapper for the ` count = count + value ` expression . Often it ' s desirable to
2018-05-05 12:13:16 +02:00
* pass data from the isolated scope via an expression to the parent scope , this can be
2016-04-18 12:34:29 +00:00
* done by passing a map of local variable names and values into the expression wrapper fn .
* For example , if the expression is ` increment(amount) ` then we can specify the amount value
* by calling the ` localFn ` as ` localFn({amount: 22}) ` .
2016-03-28 10:46:51 +00:00
*
*
2018-05-05 12:13:16 +02:00
* # # # # ` bindToController `
* When an isolate scope is used for a component ( see above ) , and ` controllerAs ` is used , ` bindToController: true ` will
* allow a component to have its properties bound to the controller , rather than to scope . When the controller
* is instantiated , the initial values of the isolate scope bindings are already available .
2016-03-28 10:46:51 +00:00
*
* # # # # ` controller `
* Controller constructor function . The controller is instantiated before the
2016-04-18 12:34:29 +00:00
* pre - linking phase and it is shared with other directives ( see
2016-03-28 10:46:51 +00:00
* ` require ` attribute ) . This allows the directives to communicate with each other and augment
* each other ' s behavior . The controller is injectable ( and supports bracket notation ) with the following locals :
*
* * ` $ scope ` - Current scope associated with the element
* * ` $ element ` - Current element
* * ` $ attrs ` - Current attributes object for the element
2018-05-05 12:13:16 +02:00
* * ` $ transclude ` - A transclude linking function pre - bound to the correct transclusion scope :
* ` function([scope], cloneLinkingFn, futureParentElement) ` .
* * ` scope ` : optional argument to override the scope .
* * ` cloneLinkingFn ` : optional argument to create clones of the original transcluded content .
* * ` futureParentElement ` :
* * defines the parent to which the ` cloneLinkingFn ` will add the cloned elements .
* * default : ` $ element.parent() ` resp . ` $ element ` for ` transclude:'element' ` resp . ` transclude:true ` .
* * only needed for transcludes that are allowed to contain non html elements ( e . g . SVG elements )
* and when the ` cloneLinkinFn ` is passed ,
* as those elements need to created and cloned in a special way when they are defined outside their
* usual containers ( e . g . like ` <svg> ` ) .
* * See also the ` directive.templateNamespace ` property .
2016-03-28 10:46:51 +00:00
*
*
* # # # # ` require `
* Require another directive and inject its controller as the fourth argument to the linking function . The
2016-04-18 12:34:29 +00:00
* ` require ` takes a string name ( or array of strings ) of the directive ( s ) to pass in . If an array is used , the
* injected argument will be an array in corresponding order . If no such directive can be
2018-05-05 12:13:16 +02:00
* found , or if the directive does not have a controller , then an error is raised ( unless no link function
* is specified , in which case error checking is skipped ) . The name can be prefixed with :
2016-03-28 10:46:51 +00:00
*
* * ( no prefix ) - Locate the required controller on the current element . Throw an error if not found .
* * ` ? ` - Attempt to locate the required controller or pass ` null ` to the ` link ` fn if not found .
2018-05-05 12:13:16 +02:00
* * ` ^ ` - Locate the required controller by searching the element and its parents . Throw an error if not found .
* * ` ^^ ` - Locate the required controller by searching the element ' s parents . Throw an error if not found .
* * ` ?^ ` - Attempt to locate the required controller by searching the element and its parents or pass
* ` null ` to the ` link ` fn if not found .
* * ` ?^^ ` - Attempt to locate the required controller by searching the element ' s parents , or pass
* ` null ` to the ` link ` fn if not found .
2016-03-28 10:46:51 +00:00
*
*
* # # # # ` controllerAs `
2016-04-18 12:34:29 +00:00
* Controller alias at the directive scope . An alias for the controller so it
* can be referenced at the directive template . The directive needs to define a scope for this
* configuration to be used . Useful in the case when directive is used as component .
2016-03-28 10:46:51 +00:00
*
*
* # # # # ` restrict `
* String of subset of ` EACM ` which restricts the directive to a specific directive
2018-05-05 12:13:16 +02:00
* declaration style . If omitted , the defaults ( elements and attributes ) are used .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* * ` E ` - Element name ( default ) : ` <my-directive></my-directive> `
2016-03-28 10:46:51 +00:00
* * ` A ` - Attribute ( default ) : ` <div my-directive="exp"></div> `
* * ` C ` - Class : ` <div class="my-directive: exp;"></div> `
* * ` M ` - Comment : ` <!-- directive: my-directive exp --> `
*
*
2018-05-05 12:13:16 +02:00
* # # # # ` templateNamespace `
* String representing the document type used by the markup in the template .
* AngularJS needs this information as those elements need to be created and cloned
* in a special way when they are defined outside their usual containers like ` <svg> ` and ` <math> ` .
*
* * ` html ` - All root nodes in the template are HTML . Root nodes may also be
* top - level elements such as ` <svg> ` or ` <math> ` .
* * ` svg ` - The root nodes in the template are SVG elements ( excluding ` <math> ` ) .
* * ` math ` - The root nodes in the template are MathML elements ( excluding ` <svg> ` ) .
*
* If no ` templateNamespace ` is specified , then the namespace is considered to be ` html ` .
*
2016-03-28 10:46:51 +00:00
* # # # # ` template `
2018-05-05 12:13:16 +02:00
* HTML markup that may :
* * Replace the contents of the directive ' s element ( default ) .
* * Replace the directive ' s element itself ( if ` replace ` is true - DEPRECATED ) .
* * Wrap the contents of the directive ' s element ( if ` transclude ` is true ) .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* Value may be :
*
* * A string . For example ` <div red-on-hover>{{delete_str}}</div> ` .
* * A function which takes two arguments ` tElement ` and ` tAttrs ` ( described in the ` compile `
* function api below ) and returns a string value .
2016-03-28 10:46:51 +00:00
*
*
* # # # # ` templateUrl `
2018-05-05 12:13:16 +02:00
* This is similar to ` template ` but the template is loaded from the specified URL , asynchronously .
*
* Because template loading is asynchronous the compiler will suspend compilation of directives on that element
* for later when the template has been resolved . In the meantime it will continue to compile and link
* sibling and parent elements as though this element had not contained any directives .
*
* The compiler does not suspend the entire compilation to wait for templates to be loaded because this
* would result in the whole app "stalling" until all templates are loaded asynchronously - even in the
* case when only one deeply nested directive has ` templateUrl ` .
*
* Template loading is asynchronous even if the template has been preloaded into the { @ link $templateCache }
2016-03-28 10:46:51 +00:00
*
* You can specify ` templateUrl ` as a string representing the URL or as a function which takes two
* arguments ` tElement ` and ` tAttrs ` ( described in the ` compile ` function api below ) and returns
* a string value representing the url . In either case , the template URL is passed through { @ link
2018-05-05 12:13:16 +02:00
* $sce # getTrustedResourceUrl $sce . getTrustedResourceUrl } .
2016-03-28 10:46:51 +00:00
*
*
2018-05-05 12:13:16 +02:00
* # # # # ` replace ` ( [ * DEPRECATED * ! ] , will be removed in next major release - i . e . v2 . 0 )
* specify what the template should replace . Defaults to ` false ` .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* * ` true ` - the template will replace the directive ' s element .
* * ` false ` - the template will replace the contents of the directive ' s element .
*
* The replacement process migrates all of the attributes / classes from the old element to the new
* one . See the { @ link guide / directive # template - expanding - directive
* Directives Guide } for an example .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* There are very few scenarios where element replacement is required for the application function ,
* the main one being reusable custom components that are used within SVG contexts
* ( because SVG doesn ' t work with custom elements in the DOM tree ) .
2016-03-28 10:46:51 +00:00
*
* # # # # ` transclude `
2018-05-05 12:13:16 +02:00
* Extract the contents of the element where the directive appears and make it available to the directive .
* The contents are compiled and provided to the directive as a * * transclusion function * * . See the
* { @ link $compile # transclusion Transclusion } section below .
2016-04-18 12:34:29 +00:00
*
2018-05-05 12:13:16 +02:00
* There are two kinds of transclusion depending upon whether you want to transclude just the contents of the
* directive ' s element or the entire element :
*
* * ` true ` - transclude the content ( i . e . the child nodes ) of the directive ' s element .
* * ` 'element' ` - transclude the whole of the directive ' s element including any directives on this
* element that defined at a lower priority than this directive . When used , the ` template `
* property is ignored .
2016-03-28 10:46:51 +00:00
*
*
* # # # # ` compile `
*
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
* function compile ( tElement , tAttrs , transclude ) { ... }
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
* The compile function deals with transforming the template DOM . Since most directives do not do
2018-05-05 12:13:16 +02:00
* template transformation , it is not used often . The compile function takes the following arguments :
2016-03-28 10:46:51 +00:00
*
* * ` tElement ` - template element - The element where the directive has been declared . It is
* safe to do template transformation on the element and child elements only .
*
* * ` tAttrs ` - template attributes - Normalized list of attributes declared on this element shared
* between all directive compile functions .
*
* * ` transclude ` - [ * DEPRECATED * ! ] A transclude linking function : ` function(scope, cloneLinkingFn) `
*
* < div class = "alert alert-warning" >
* * * Note : * * The template instance and the link instance may be different objects if the template has
* been cloned . For this reason it is * * not * * safe to do anything other than DOM transformations that
* apply to all cloned DOM nodes within the compile function . Specifically , DOM listener registration
* should be done in a linking function rather than in a compile function .
* < / d i v >
2018-05-05 12:13:16 +02:00
* < div class = "alert alert-warning" >
* * * Note : * * The compile function cannot handle directives that recursively use themselves in their
* own templates or compile functions . Compiling these directives results in an infinite loop and a
* stack overflow errors .
*
* This can be avoided by manually using $compile in the postLink function to imperatively compile
* a directive ' s template instead of relying on automatic template compilation via ` template ` or
* ` templateUrl ` declaration or manual compilation inside the compile function .
* < / d i v >
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* < div class = "alert alert-error" >
2016-03-28 10:46:51 +00:00
* * * Note : * * The ` transclude ` function that is passed to the compile function is deprecated , as it
* e . g . does not know about the right outer scope . Please use the transclude function that is passed
* to the link function instead .
* < / d i v >
* A compile function can have a return value which can be either a function or an object .
*
* * returning a ( post - link ) function - is equivalent to registering the linking function via the
* ` link ` property of the config object when the compile function is empty .
*
* * returning an object with function ( s ) registered via ` pre ` and ` post ` properties - allows you to
* control when a linking function should be called during the linking phase . See info about
* pre - linking and post - linking functions below .
*
*
* # # # # ` link `
* This property is used only if the ` compile ` property is not defined .
*
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
* function link ( scope , iElement , iAttrs , controller , transcludeFn ) { ... }
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
* The link function is responsible for registering DOM listeners as well as updating the DOM . It is
* executed after the template has been cloned . This is where most of the directive logic will be
* put .
*
2018-05-05 12:13:16 +02:00
* * ` scope ` - { @ link ng . $rootScope . Scope Scope } - The scope to be used by the
* directive for registering { @ link ng . $rootScope . Scope # $watch watches } .
2016-03-28 10:46:51 +00:00
*
* * ` iElement ` - instance element - The element where the directive is to be used . It is safe to
* manipulate the children of the element only in ` postLink ` function since the children have
* already been linked .
*
* * ` iAttrs ` - instance attributes - Normalized list of attributes declared on this element shared
* between all directive linking functions .
*
2018-05-05 12:13:16 +02:00
* * ` controller ` - the directive ' s required controller instance ( s ) - Instances are shared
* among all directives , which allows the directives to use the controllers as a communication
* channel . The exact value depends on the directive ' s ` require ` property :
* * ` string ` : the controller instance
* * ` array ` : array of controller instances
* * no controller ( s ) required : ` undefined `
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* If a required controller cannot be found , and it is optional , the instance is ` null ` ,
* otherwise the { @ link error : $compile : ctreq Missing Required Controller } error is thrown .
2016-04-18 12:34:29 +00:00
*
2018-05-05 12:13:16 +02:00
* * ` transcludeFn ` - A transclude linking function pre - bound to the correct transclusion scope .
* This is the same as the ` $ transclude `
* parameter of directive controllers , see there for details .
* ` function([scope], cloneLinkingFn, futureParentElement) ` .
2016-03-28 10:46:51 +00:00
*
* # # # # Pre - linking function
*
* Executed before the child elements are linked . Not safe to do DOM transformation since the
* compiler linking function will fail to locate the correct elements for linking .
*
* # # # # Post - linking function
*
2018-05-05 12:13:16 +02:00
* Executed after the child elements are linked .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* Note that child elements that contain ` templateUrl ` directives will not have been compiled
* and linked since they are waiting for their template to load asynchronously and their own
* compilation and linking has been suspended until that occurs .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* It is safe to do DOM transformation in the post - linking function on elements that are not waiting
* for their async templates to be resolved .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
*
* # # # Transclusion
*
* Transclusion is the process of extracting a collection of DOM elements from one part of the DOM and
* copying them to another part of the DOM , while maintaining their connection to the original AngularJS
* scope from where they were taken .
*
* Transclusion is used ( often with { @ link ngTransclude } ) to insert the
* original contents of a directive ' s element into a specified place in the template of the directive .
* The benefit of transclusion , over simply moving the DOM elements manually , is that the transcluded
* content has access to the properties on the scope from which it was taken , even if the directive
* has isolated scope .
* See the { @ link guide / directive # creating - a - directive - that - wraps - other - elements Directives Guide } .
*
* This makes it possible for the widget to have private state for its template , while the transcluded
* content has access to its originating scope .
*
* < div class = "alert alert-warning" >
* * * Note : * * When testing an element transclude directive you must not place the directive at the root of the
* DOM fragment that is being compiled . See { @ link guide / unit - testing # testing - transclusion - directives
* Testing Transclusion Directives } .
* < / d i v >
*
* # # # # Transclusion Functions
*
* When a directive requests transclusion , the compiler extracts its contents and provides a * * transclusion
* function * * to the directive ' s ` link ` function and ` controller ` . This transclusion function is a special
* * * linking function * * that will return the compiled contents linked to a new transclusion scope .
*
* < div class = "alert alert-info" >
* If you are just using { @ link ngTransclude } then you don ' t need to worry about this function , since
* ngTransclude will deal with it for us .
* < / d i v >
*
* If you want to manually control the insertion and removal of the transcluded content in your directive
* then you must use this transclude function . When you call a transclude function it returns a a jqLite / JQuery
* object that contains the compiled DOM , which is linked to the correct transclusion scope .
*
* When you call a transclusion function you can pass in a * * clone attach function * * . This function accepts
* two parameters , ` function(clone, scope) { ... } ` , where the ` clone ` is a fresh compiled copy of your transcluded
* content and the ` scope ` is the newly created transclusion scope , to which the clone is bound .
*
* < div class = "alert alert-info" >
* * * Best Practice * * : Always provide a ` cloneFn ` ( clone attach function ) when you call a translude function
* since you then get a fresh clone of the original DOM and also have access to the new transclusion scope .
* < / d i v >
*
* It is normal practice to attach your transcluded content ( ` clone ` ) to the DOM inside your * * clone
* attach function * * :
*
* ` ` ` js
* var transcludedContent , transclusionScope ;
*
* $transclude ( function ( clone , scope ) {
* element . append ( clone ) ;
* transcludedContent = clone ;
* transclusionScope = scope ;
* } ) ;
* ` ` `
*
* Later , if you want to remove the transcluded content from your DOM then you should also destroy the
* associated transclusion scope :
*
* ` ` ` js
* transcludedContent . remove ( ) ;
* transclusionScope . $destroy ( ) ;
* ` ` `
*
* < div class = "alert alert-info" >
* * * Best Practice * * : if you intend to add and remove transcluded content manually in your directive
* ( by calling the transclude function to get the DOM and and calling ` element.remove() ` to remove it ) ,
* then you are also responsible for calling ` $ destroy ` on the transclusion scope .
* < / d i v >
*
* The built - in DOM manipulation directives , such as { @ link ngIf } , { @ link ngSwitch } and { @ link ngRepeat }
* automatically destroy their transluded clones as necessary so you do not need to worry about this if
* you are simply using { @ link ngTransclude } to inject the transclusion into your directive .
*
*
* # # # # Transclusion Scopes
*
* When you call a transclude function it returns a DOM fragment that is pre - bound to a * * transclusion
* scope * * . This scope is special , in that it is a child of the directive ' s scope ( and so gets destroyed
* when the directive ' s scope gets destroyed ) but it inherits the properties of the scope from which it
* was taken .
*
* For example consider a directive that uses transclusion and isolated scope . The DOM hierarchy might look
* like this :
*
* ` ` ` html
* < div ng - app >
* < div isolate >
* < div transclusion >
* < / d i v >
* < / d i v >
* < / d i v >
* ` ` `
*
* The ` $ parent ` scope hierarchy will look like this :
*
* ` ` `
* - $rootScope
* - isolate
* - transclusion
* ` ` `
*
* but the scopes will inherit prototypically from different scopes to their ` $ parent ` .
*
* ` ` `
* - $rootScope
* - transclusion
* - isolate
* ` ` `
*
*
* # # # Attributes
*
* The { @ link ng . $compile . directive . Attributes Attributes } object - passed as a parameter in the
* ` link() ` or ` compile() ` functions . It has a variety of uses .
*
* accessing * Normalized attribute names : *
* Directives like 'ngBind' can be expressed in many ways : 'ng:bind' , ` data-ng-bind ` , or 'x-ng-bind' .
* the attributes object allows for normalized access to
2016-04-18 12:34:29 +00:00
* the attributes .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* * * Directive inter - communication : * All directives share the same instance of the attributes
* object which allows the directives to use the attributes object as inter directive
* communication .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* * * Supports interpolation : * Interpolation attributes are assigned to the attribute object
* allowing other directives to read the interpolated value .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* * * Observing interpolated attributes : * Use ` $ observe ` to observe the value changes of attributes
* that contain interpolation ( e . g . ` src="{{bar}}" ` ) . Not only is this very efficient but it ' s also
* the only way to easily get the actual value because during the linking phase the interpolation
* hasn ' t been evaluated yet and so the value is at this time set to ` undefined ` .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-04-18 12:34:29 +00:00
* function linkingFn ( scope , elm , attrs , ctrl ) {
* // get the attribute value
* console . log ( attrs . ngModel ) ;
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* // change the attribute
* attrs . $set ( 'ngModel' , 'new value' ) ;
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* // observe changes to interpolated attribute
* attrs . $observe ( 'ngModel' , function ( value ) {
* console . log ( 'ngModel has changed value to ' + value ) ;
* } ) ;
* }
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* # # Example
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* < div class = "alert alert-warning" >
* * * Note * * : Typically directives are registered with ` module.directive ` . The example below is
* to illustrate how ` $ compile ` works .
2016-03-28 10:46:51 +00:00
* < / d i v >
*
2018-05-05 12:13:16 +02:00
< example module = "compileExample" >
< file name = "index.html" >
2016-03-28 10:46:51 +00:00
< script >
2018-05-05 12:13:16 +02:00
angular . module ( 'compileExample' , [ ] , function ( $compileProvider ) {
2016-03-28 10:46:51 +00:00
// configure new 'compile' directive by passing a directive
// factory function. The factory function injects the '$compile'
$compileProvider . directive ( 'compile' , function ( $compile ) {
// directive factory creates a link function
return function ( scope , element , attrs ) {
scope . $watch (
function ( scope ) {
// watch the 'compile' expression for changes
return scope . $eval ( attrs . compile ) ;
} ,
function ( value ) {
// when the 'compile' expression changes
// assign it into the current DOM
element . html ( value ) ;
// compile the new DOM and link it to the current
// scope.
// NOTE: we only compile .childNodes so that
// we don't get into infinite loop compiling ourselves
$compile ( element . contents ( ) ) ( scope ) ;
}
) ;
} ;
2018-05-05 12:13:16 +02:00
} ) ;
} )
. controller ( 'GreeterController' , [ '$scope' , function ( $scope ) {
2016-03-28 10:46:51 +00:00
$scope . name = 'Angular' ;
$scope . html = 'Hello {{name}}' ;
2018-05-05 12:13:16 +02:00
} ] ) ;
2016-03-28 10:46:51 +00:00
< / s c r i p t >
2018-05-05 12:13:16 +02:00
< div ng - controller = "GreeterController" >
2016-04-18 12:34:29 +00:00
< input ng - model = "name" > < br >
< textarea ng - model = "html" > < / t e x t a r e a > < b r >
2016-03-28 10:46:51 +00:00
< div compile = "html" > < / d i v >
< / d i v >
2018-05-05 12:13:16 +02:00
< / f i l e >
< file name = "protractor.js" type = "protractor" >
2016-03-28 10:46:51 +00:00
it ( 'should auto compile' , function ( ) {
2018-05-05 12:13:16 +02:00
var textarea = $ ( 'textarea' ) ;
var output = $ ( 'div[compile]' ) ;
// The initial state reads 'Hello Angular'.
expect ( output . getText ( ) ) . toBe ( 'Hello Angular' ) ;
textarea . clear ( ) ;
textarea . sendKeys ( '{{name}}!' ) ;
expect ( output . getText ( ) ) . toBe ( 'Angular!' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-03-28 10:46:51 +00:00
*
*
* @ param { string | DOMElement } element Element or HTML string to compile into a template function .
2018-05-05 12:13:16 +02:00
* @ param { function ( angular . Scope , cloneAttachFn = ) } transclude function available to directives - DEPRECATED .
*
* < div class = "alert alert-error" >
* * * Note : * * Passing a ` transclude ` function to the $compile function is deprecated , as it
* e . g . will not use the right outer scope . Please pass the transclude function as a
* ` parentBoundTranscludeFn ` to the link function instead .
* < / d i v >
*
* @ param { number } maxPriority only apply directives lower than given priority ( Only effects the
2016-03-28 10:46:51 +00:00
* root element ( s ) , not their children )
2018-05-05 12:13:16 +02:00
* @ returns { function ( scope , cloneAttachFn = , options = ) } a link function which is used to bind template
2016-03-28 10:46:51 +00:00
* ( a DOM element / tree ) to a scope . Where :
*
* * ` scope ` - A { @ link ng . $rootScope . Scope Scope } to bind to .
* * ` cloneAttachFn ` - If ` cloneAttachFn ` is provided , then the link function will clone the
* ` template ` and call the ` cloneAttachFn ` function allowing the caller to attach the
* cloned elements to the DOM document at the appropriate place . The ` cloneAttachFn ` is
2016-04-18 12:34:29 +00:00
* called as : < br > ` cloneAttachFn(clonedElement, scope) ` where :
2016-03-28 10:46:51 +00:00
*
* * ` clonedElement ` - is a clone of the original ` element ` passed into the compiler .
* * ` scope ` - is the current scope with which the linking function is working with .
*
2018-05-05 12:13:16 +02:00
* * ` options ` - An optional object hash with linking options . If ` options ` is provided , then the following
* keys may be used to control linking behavior :
*
* * ` parentBoundTranscludeFn ` - the transclude function made available to
* directives ; if given , it will be passed through to the link functions of
* directives found in ` element ` during compilation .
* * ` transcludeControllers ` - an object hash with keys that map controller names
* to controller instances ; if given , it will make the controllers
* available to directives .
* * ` futureParentElement ` - defines the parent to which the ` cloneAttachFn ` will add
* the cloned elements ; only needed for transcludes that are allowed to contain non html
* elements ( e . g . SVG elements ) . See also the directive . controller property .
*
2016-03-28 10:46:51 +00:00
* Calling the linking function returns the element of the template . It is either the original
* element passed in , or the clone of the element if the ` cloneAttachFn ` is provided .
*
* After linking the view is not updated until after a call to $digest which typically is done by
* Angular automatically .
*
* If you need access to the bound view , there are two ways to do it :
*
* - If you are not asking the linking function to clone the template , create the DOM element ( s )
* before you send them to the compiler and keep this reference around .
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
* var element = $compile ( '<p>{{total}}</p>' ) ( scope ) ;
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
* - if on the other hand , you need the element to be cloned , the view reference from the original
* example would not point to the clone , but rather to the original template that was cloned . In
* this case , you can access the clone via the cloneAttachFn :
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
* var templateElement = angular . element ( '<p>{{total}}</p>' ) ,
* scope = ... . ;
*
* var clonedElement = $compile ( templateElement ) ( scope , function ( clonedElement , scope ) {
* //attach the clone to DOM document at the right place
* } ) ;
*
* //now we have reference to the cloned DOM via `clonedElement`
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
*
* For information on how the compiler works , see the
* { @ link guide / compiler Angular HTML Compiler } section of the Developer Guide .
* /
var $compileMinErr = minErr ( '$compile' ) ;
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc provider
* @ name $compileProvider
2016-03-28 10:46:51 +00:00
*
* @ description
* /
$CompileProvider . $inject = [ '$provide' , '$$sanitizeUriProvider' ] ;
function $CompileProvider ( $provide , $$sanitizeUriProvider ) {
var hasDirectives = { } ,
Suffix = 'Directive' ,
2018-05-05 12:13:16 +02:00
COMMENT _DIRECTIVE _REGEXP = /^\s*directive\:\s*([\w\-]+)\s+(.*)$/ ,
CLASS _DIRECTIVE _REGEXP = /(([\w\-]+)(?:\:([^;]+))?;?)/ ,
ALL _OR _NOTHING _ATTRS = makeMap ( 'ngSrc,ngSrcset,src,srcset' ) ,
REQUIRE _PREFIX _REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/ ;
2016-03-28 10:46:51 +00:00
// Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
// The assumption is that future DOM event attribute names will begin with
// 'on' and be composed of only English letters.
var EVENT _HANDLER _ATTR _REGEXP = /^(on[a-z]+|formaction)$/ ;
2018-05-05 12:13:16 +02:00
function parseIsolateBindings ( scope , directiveName ) {
var LOCAL _REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/ ;
var bindings = { } ;
forEach ( scope , function ( definition , scopeName ) {
var match = definition . match ( LOCAL _REGEXP ) ;
if ( ! match ) {
throw $compileMinErr ( 'iscp' ,
"Invalid isolate scope definition for directive '{0}'." +
" Definition: {... {1}: '{2}' ...}" ,
directiveName , scopeName , definition ) ;
}
bindings [ scopeName ] = {
mode : match [ 1 ] [ 0 ] ,
collection : match [ 2 ] === '*' ,
optional : match [ 3 ] === '?' ,
attrName : match [ 4 ] || scopeName
} ;
} ) ;
return bindings ;
}
2016-03-28 10:46:51 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $compileProvider # directive
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Register a new directive with the compiler .
*
* @ param { string | Object } name Name of the directive in camel - case ( i . e . < code > ngBind < / c o d e > w h i c h
* will match as < code > ng - bind < / c o d e > ) , o r a n o b j e c t m a p o f d i r e c t i v e s w h e r e t h e k e y s a r e t h e
* names and the values are the factories .
2018-05-05 12:13:16 +02:00
* @ param { Function | Array } directiveFactory An injectable directive factory function . See
2016-04-18 12:34:29 +00:00
* { @ link guide / directive } for more info .
2016-03-28 10:46:51 +00:00
* @ returns { ng . $compileProvider } Self for chaining .
* /
2016-04-18 12:34:29 +00:00
this . directive = function registerDirective ( name , directiveFactory ) {
2016-03-28 10:46:51 +00:00
assertNotHasOwnProperty ( name , 'directive' ) ;
if ( isString ( name ) ) {
assertArg ( directiveFactory , 'directiveFactory' ) ;
if ( ! hasDirectives . hasOwnProperty ( name ) ) {
hasDirectives [ name ] = [ ] ;
$provide . factory ( name + Suffix , [ '$injector' , '$exceptionHandler' ,
function ( $injector , $exceptionHandler ) {
var directives = [ ] ;
forEach ( hasDirectives [ name ] , function ( directiveFactory , index ) {
try {
var directive = $injector . invoke ( directiveFactory ) ;
if ( isFunction ( directive ) ) {
directive = { compile : valueFn ( directive ) } ;
} else if ( ! directive . compile && directive . link ) {
directive . compile = valueFn ( directive . link ) ;
}
directive . priority = directive . priority || 0 ;
directive . index = index ;
directive . name = directive . name || name ;
directive . require = directive . require || ( directive . controller && directive . name ) ;
2018-05-05 12:13:16 +02:00
directive . restrict = directive . restrict || 'EA' ;
if ( isObject ( directive . scope ) ) {
directive . $$isolateBindings = parseIsolateBindings ( directive . scope , directive . name ) ;
}
2016-03-28 10:46:51 +00:00
directives . push ( directive ) ;
} catch ( e ) {
$exceptionHandler ( e ) ;
}
} ) ;
return directives ;
} ] ) ;
}
hasDirectives [ name ] . push ( directiveFactory ) ;
} else {
forEach ( name , reverseParams ( registerDirective ) ) ;
}
return this ;
} ;
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $compileProvider # aHrefSanitizationWhitelist
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Retrieves or overrides the default regular expression that is used for whitelisting of safe
* urls during a [ href ] sanitization .
*
2018-05-05 12:13:16 +02:00
* The sanitization is a security measure aimed at preventing XSS attacks via html links .
2016-03-28 10:46:51 +00:00
*
* Any url about to be assigned to a [ href ] via data - binding is first normalized and turned into
* an absolute url . Afterwards , the url is matched against the ` aHrefSanitizationWhitelist `
* regular expression . If a match is found , the original url is written into the dom . Otherwise ,
* the absolute url is prefixed with ` 'unsafe:' ` string and only then is it written into the DOM .
*
* @ param { RegExp = } regexp New regexp to whitelist urls with .
* @ returns { RegExp | ng . $compileProvider } Current RegExp if called without value or self for
* chaining otherwise .
* /
this . aHrefSanitizationWhitelist = function ( regexp ) {
if ( isDefined ( regexp ) ) {
$$sanitizeUriProvider . aHrefSanitizationWhitelist ( regexp ) ;
return this ;
} else {
return $$sanitizeUriProvider . aHrefSanitizationWhitelist ( ) ;
}
} ;
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $compileProvider # imgSrcSanitizationWhitelist
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Retrieves or overrides the default regular expression that is used for whitelisting of safe
* urls during img [ src ] sanitization .
*
* The sanitization is a security measure aimed at prevent XSS attacks via html links .
*
* Any url about to be assigned to img [ src ] via data - binding is first normalized and turned into
* an absolute url . Afterwards , the url is matched against the ` imgSrcSanitizationWhitelist `
* regular expression . If a match is found , the original url is written into the dom . Otherwise ,
* the absolute url is prefixed with ` 'unsafe:' ` string and only then is it written into the DOM .
*
* @ param { RegExp = } regexp New regexp to whitelist urls with .
* @ returns { RegExp | ng . $compileProvider } Current RegExp if called without value or self for
* chaining otherwise .
* /
this . imgSrcSanitizationWhitelist = function ( regexp ) {
if ( isDefined ( regexp ) ) {
$$sanitizeUriProvider . imgSrcSanitizationWhitelist ( regexp ) ;
return this ;
} else {
return $$sanitizeUriProvider . imgSrcSanitizationWhitelist ( ) ;
}
} ;
2018-05-05 12:13:16 +02:00
/ * *
* @ ngdoc method
* @ name $compileProvider # debugInfoEnabled
*
* @ param { boolean = } enabled update the debugInfoEnabled state if provided , otherwise just return the
* current debugInfoEnabled state
* @ returns { * } current value if used as getter or itself ( chaining ) if used as setter
*
* @ kind function
*
* @ description
* Call this method to enable / disable various debug runtime information in the compiler such as adding
* binding information and a reference to the current scope on to DOM elements .
* If enabled , the compiler will add the following to DOM elements that have been bound to the scope
* * ` ng-binding ` CSS class
* * ` $ binding ` data property containing an array of the binding expressions
*
* You may want to disable this in production for a significant performance boost . See
* { @ link guide / production # disabling - debug - data Disabling Debug Data } for more .
*
* The default value is true .
* /
var debugInfoEnabled = true ;
this . debugInfoEnabled = function ( enabled ) {
if ( isDefined ( enabled ) ) {
debugInfoEnabled = enabled ;
return this ;
}
return debugInfoEnabled ;
} ;
2016-03-28 10:46:51 +00:00
this . $get = [
2018-05-05 12:13:16 +02:00
'$injector' , '$interpolate' , '$exceptionHandler' , '$templateRequest' , '$parse' ,
2016-04-18 12:34:29 +00:00
'$controller' , '$rootScope' , '$document' , '$sce' , '$animate' , '$$sanitizeUri' ,
2018-05-05 12:13:16 +02:00
function ( $injector , $interpolate , $exceptionHandler , $templateRequest , $parse ,
2016-04-18 12:34:29 +00:00
$controller , $rootScope , $document , $sce , $animate , $$sanitizeUri ) {
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
var Attributes = function ( element , attributesToCopy ) {
if ( attributesToCopy ) {
var keys = Object . keys ( attributesToCopy ) ;
var i , l , key ;
for ( i = 0 , l = keys . length ; i < l ; i ++ ) {
key = keys [ i ] ;
this [ key ] = attributesToCopy [ key ] ;
}
} else {
this . $attr = { } ;
}
2016-03-28 10:46:51 +00:00
this . $$element = element ;
2016-04-18 12:34:29 +00:00
} ;
2016-03-28 10:46:51 +00:00
Attributes . prototype = {
2018-05-05 12:13:16 +02:00
/ * *
* @ ngdoc method
* @ name $compile . directive . Attributes # $normalize
* @ kind function
*
* @ description
* Converts an attribute name ( e . g . dash / colon / underscore - delimited string , optionally prefixed with ` x- ` or
* ` data- ` ) to its normalized , camelCase form .
*
* Also there is special case for Moz prefix starting with upper case letter .
*
* For further information check out the guide on { @ link guide / directive # matching - directives Matching Directives }
*
* @ param { string } name Name to normalize
* /
2016-03-28 10:46:51 +00:00
$normalize : directiveNormalize ,
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $compile . directive . Attributes # $addClass
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Adds the CSS class value specified by the classVal parameter to the element . If animations
* are enabled then an animation will be triggered for the class addition .
*
* @ param { string } classVal The className value that will be added to the element
* /
2018-05-05 12:13:16 +02:00
$addClass : function ( classVal ) {
if ( classVal && classVal . length > 0 ) {
2016-03-28 10:46:51 +00:00
$animate . addClass ( this . $$element , classVal ) ;
}
} ,
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $compile . directive . Attributes # $removeClass
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Removes the CSS class value specified by the classVal parameter from the element . If
* animations are enabled then an animation will be triggered for the class removal .
*
* @ param { string } classVal The className value that will be removed from the element
* /
2018-05-05 12:13:16 +02:00
$removeClass : function ( classVal ) {
if ( classVal && classVal . length > 0 ) {
2016-03-28 10:46:51 +00:00
$animate . removeClass ( this . $$element , classVal ) ;
}
} ,
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $compile . directive . Attributes # $updateClass
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Adds and removes the appropriate CSS class values to the element based on the difference
* between the new and old CSS class values ( specified as newClasses and oldClasses ) .
*
* @ param { string } newClasses The current CSS className value
* @ param { string } oldClasses The former CSS className value
* /
2018-05-05 12:13:16 +02:00
$updateClass : function ( newClasses , oldClasses ) {
var toAdd = tokenDifference ( newClasses , oldClasses ) ;
if ( toAdd && toAdd . length ) {
$animate . addClass ( this . $$element , toAdd ) ;
}
var toRemove = tokenDifference ( oldClasses , newClasses ) ;
if ( toRemove && toRemove . length ) {
$animate . removeClass ( this . $$element , toRemove ) ;
}
2016-03-28 10:46:51 +00:00
} ,
/ * *
* Set a normalized attribute on the element in a way such that all directives
* can share the attribute . This function properly handles boolean attributes .
* @ param { string } key Normalized key . ( ie ngAttribute )
* @ param { string | boolean } value The value to set . If ` null ` attribute will be deleted .
* @ param { boolean = } writeAttr If false , does not write the value to DOM element attribute .
* Defaults to true .
* @ param { string = } attrName Optional none normalized name . Defaults to key .
* /
$set : function ( key , value , writeAttr , attrName ) {
// TODO: decide whether or not to throw an error if "class"
//is set through this function since it may cause $updateClass to
//become unstable.
2018-05-05 12:13:16 +02:00
var node = this . $$element [ 0 ] ,
booleanKey = getBooleanAttrName ( node , key ) ,
aliasedKey = getAliasedAttrName ( node , key ) ,
observer = key ,
2016-03-28 10:46:51 +00:00
nodeName ;
if ( booleanKey ) {
this . $$element . prop ( key , value ) ;
attrName = booleanKey ;
2018-05-05 12:13:16 +02:00
} else if ( aliasedKey ) {
this [ aliasedKey ] = value ;
observer = aliasedKey ;
2016-03-28 10:46:51 +00:00
}
this [ key ] = value ;
// translate normalized key to actual key
if ( attrName ) {
this . $attr [ key ] = attrName ;
} else {
attrName = this . $attr [ key ] ;
if ( ! attrName ) {
this . $attr [ key ] = attrName = snake _case ( key , '-' ) ;
}
}
nodeName = nodeName _ ( this . $$element ) ;
2018-05-05 12:13:16 +02:00
if ( ( nodeName === 'a' && key === 'href' ) ||
( nodeName === 'img' && key === 'src' ) ) {
// sanitize a[href] and img[src] values
2016-03-28 10:46:51 +00:00
this [ key ] = value = $$sanitizeUri ( value , key === 'src' ) ;
2018-05-05 12:13:16 +02:00
} else if ( nodeName === 'img' && key === 'srcset' ) {
// sanitize img[srcset] values
var result = "" ;
// first check if there are spaces because it's not the same pattern
var trimmedSrcset = trim ( value ) ;
// ( 999x ,| 999w ,| ,|, )
var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/ ;
var pattern = /\s/ . test ( trimmedSrcset ) ? srcPattern : /(,)/ ;
// split srcset into tuple of uri and descriptor except for the last item
var rawUris = trimmedSrcset . split ( pattern ) ;
// for each tuples
var nbrUrisWith2parts = Math . floor ( rawUris . length / 2 ) ;
for ( var i = 0 ; i < nbrUrisWith2parts ; i ++ ) {
var innerIdx = i * 2 ;
// sanitize the uri
result += $$sanitizeUri ( trim ( rawUris [ innerIdx ] ) , true ) ;
// add the descriptor
result += ( " " + trim ( rawUris [ innerIdx + 1 ] ) ) ;
}
// split the last item into uri and descriptor
var lastTuple = trim ( rawUris [ i * 2 ] ) . split ( /\s/ ) ;
// sanitize the last uri
result += $$sanitizeUri ( trim ( lastTuple [ 0 ] ) , true ) ;
// and add the last descriptor if any
if ( lastTuple . length === 2 ) {
result += ( " " + trim ( lastTuple [ 1 ] ) ) ;
}
this [ key ] = value = result ;
2016-03-28 10:46:51 +00:00
}
if ( writeAttr !== false ) {
2016-04-18 12:34:29 +00:00
if ( value === null || value === undefined ) {
2016-03-28 10:46:51 +00:00
this . $$element . removeAttr ( attrName ) ;
} else {
2016-04-18 12:34:29 +00:00
this . $$element . attr ( attrName , value ) ;
2016-03-28 10:46:51 +00:00
}
}
// fire observers
var $$observers = this . $$observers ;
2018-05-05 12:13:16 +02:00
$$observers && forEach ( $$observers [ observer ] , function ( fn ) {
2016-03-28 10:46:51 +00:00
try {
fn ( value ) ;
} catch ( e ) {
$exceptionHandler ( e ) ;
}
} ) ;
} ,
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $compile . directive . Attributes # $observe
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Observes an interpolated attribute .
*
* The observer function will be invoked once during the next ` $ digest ` following
* compilation . The observer is then invoked whenever the interpolated value
* changes .
*
* @ param { string } key Normalized key . ( ie ngAttribute ) .
* @ param { function ( interpolatedValue ) } fn Function that will be called whenever
the interpolated value of the attribute changes .
2018-05-05 12:13:16 +02:00
* See the { @ link guide / directive # text - and - attribute - bindings Directives } guide for more info .
* @ returns { function ( ) } Returns a deregistration function for this observer .
2016-03-28 10:46:51 +00:00
* /
$observe : function ( key , fn ) {
var attrs = this ,
2018-05-05 12:13:16 +02:00
$$observers = ( attrs . $$observers || ( attrs . $$observers = createMap ( ) ) ) ,
2016-03-28 10:46:51 +00:00
listeners = ( $$observers [ key ] || ( $$observers [ key ] = [ ] ) ) ;
listeners . push ( fn ) ;
$rootScope . $evalAsync ( function ( ) {
2018-05-05 12:13:16 +02:00
if ( ! listeners . $$inter && attrs . hasOwnProperty ( key ) ) {
2016-03-28 10:46:51 +00:00
// no one registered attribute interpolation function, so lets call it manually
fn ( attrs [ key ] ) ;
}
} ) ;
2018-05-05 12:13:16 +02:00
return function ( ) {
arrayRemove ( listeners , fn ) ;
} ;
2016-03-28 10:46:51 +00:00
}
} ;
2018-05-05 12:13:16 +02:00
function safeAddClass ( $element , className ) {
try {
$element . addClass ( className ) ;
} catch ( e ) {
// ignore, since it means that we are trying to set class on
// SVG element, where class name is read-only.
}
}
2016-03-28 10:46:51 +00:00
var startSymbol = $interpolate . startSymbol ( ) ,
endSymbol = $interpolate . endSymbol ( ) ,
2016-04-18 12:34:29 +00:00
denormalizeTemplate = ( startSymbol == '{{' || endSymbol == '}}' )
2016-03-28 10:46:51 +00:00
? identity
: function denormalizeTemplate ( template ) {
return template . replace ( /\{\{/g , startSymbol ) . replace ( /}}/g , endSymbol ) ;
} ,
NG _ATTR _BINDING = /^ngAttr[A-Z]/ ;
2018-05-05 12:13:16 +02:00
compile . $$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo ( $element , binding ) {
var bindings = $element . data ( '$binding' ) || [ ] ;
if ( isArray ( binding ) ) {
bindings = bindings . concat ( binding ) ;
} else {
bindings . push ( binding ) ;
}
$element . data ( '$binding' , bindings ) ;
} : noop ;
compile . $$addBindingClass = debugInfoEnabled ? function $$addBindingClass ( $element ) {
safeAddClass ( $element , 'ng-binding' ) ;
} : noop ;
compile . $$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo ( $element , scope , isolated , noTemplate ) {
var dataName = isolated ? ( noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope' ) : '$scope' ;
$element . data ( dataName , scope ) ;
} : noop ;
compile . $$addScopeClass = debugInfoEnabled ? function $$addScopeClass ( $element , isolated ) {
safeAddClass ( $element , isolated ? 'ng-isolate-scope' : 'ng-scope' ) ;
} : noop ;
2016-03-28 10:46:51 +00:00
return compile ;
//================================
function compile ( $compileNodes , transcludeFn , maxPriority , ignoreDirective ,
previousCompileContext ) {
if ( ! ( $compileNodes instanceof jqLite ) ) {
// jquery always rewraps, whereas we need to preserve the original selector so that we can
// modify it.
$compileNodes = jqLite ( $compileNodes ) ;
}
// We can not compile top level text elements since text nodes can be merged and we will
// not be able to attach scope data to them, so we will wrap them in <span>
2018-05-05 12:13:16 +02:00
forEach ( $compileNodes , function ( node , index ) {
if ( node . nodeType == NODE _TYPE _TEXT && node . nodeValue . match ( /\S+/ ) /* non-empty */ ) {
$compileNodes [ index ] = jqLite ( node ) . wrap ( '<span></span>' ) . parent ( ) [ 0 ] ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
} ) ;
2016-03-28 10:46:51 +00:00
var compositeLinkFn =
compileNodes ( $compileNodes , transcludeFn , $compileNodes ,
maxPriority , ignoreDirective , previousCompileContext ) ;
2018-05-05 12:13:16 +02:00
compile . $$addScopeClass ( $compileNodes ) ;
var namespace = null ;
return function publicLinkFn ( scope , cloneConnectFn , options ) {
2016-03-28 10:46:51 +00:00
assertArg ( scope , 'scope' ) ;
2018-05-05 12:13:16 +02:00
options = options || { } ;
var parentBoundTranscludeFn = options . parentBoundTranscludeFn ,
transcludeControllers = options . transcludeControllers ,
futureParentElement = options . futureParentElement ;
// When `parentBoundTranscludeFn` is passed, it is a
// `controllersBoundTransclude` function (it was previously passed
// as `transclude` to directive.link) so we must unwrap it to get
// its `boundTranscludeFn`
if ( parentBoundTranscludeFn && parentBoundTranscludeFn . $$boundTransclude ) {
parentBoundTranscludeFn = parentBoundTranscludeFn . $$boundTransclude ;
}
if ( ! namespace ) {
namespace = detectNamespaceForChildElements ( futureParentElement ) ;
}
var $linkNode ;
if ( namespace !== 'html' ) {
// When using a directive with replace:true and templateUrl the $compileNodes
// (or a child element inside of them)
// might change, so we need to recreate the namespace adapted compileNodes
// for call to the link function.
// Note: This will already clone the nodes...
$linkNode = jqLite (
wrapTemplate ( namespace , jqLite ( '<div>' ) . append ( $compileNodes ) . html ( ) )
) ;
} else if ( cloneConnectFn ) {
// important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
// and sometimes changes the structure of the DOM.
$linkNode = JQLitePrototype . clone . call ( $compileNodes ) ;
} else {
$linkNode = $compileNodes ;
}
if ( transcludeControllers ) {
for ( var controllerName in transcludeControllers ) {
$linkNode . data ( '$' + controllerName + 'Controller' , transcludeControllers [ controllerName ] . instance ) ;
2016-03-28 10:46:51 +00:00
}
}
2018-05-05 12:13:16 +02:00
compile . $$addScopeInfo ( $linkNode , scope ) ;
2016-03-28 10:46:51 +00:00
if ( cloneConnectFn ) cloneConnectFn ( $linkNode , scope ) ;
2018-05-05 12:13:16 +02:00
if ( compositeLinkFn ) compositeLinkFn ( scope , $linkNode , $linkNode , parentBoundTranscludeFn ) ;
2016-03-28 10:46:51 +00:00
return $linkNode ;
} ;
}
2018-05-05 12:13:16 +02:00
function detectNamespaceForChildElements ( parentElement ) {
// TODO: Make this detect MathML as well...
var node = parentElement && parentElement [ 0 ] ;
if ( ! node ) {
return 'html' ;
} else {
return nodeName _ ( node ) !== 'foreignobject' && node . toString ( ) . match ( /SVG/ ) ? 'svg' : 'html' ;
2016-04-18 12:34:29 +00:00
}
}
/ * *
2016-03-28 10:46:51 +00:00
* Compile function matches each node in nodeList against the directives . Once all directives
* for a particular node are collected their compile functions are executed . The compile
* functions return values - the linking functions - are combined into a composite linking
* function , which is the a linking function for the node .
*
* @ param { NodeList } nodeList an array of nodes or NodeList to compile
2018-05-05 12:13:16 +02:00
* @ param { function ( angular . Scope , cloneAttachFn = ) } transcludeFn A linking function , where the
2016-03-28 10:46:51 +00:00
* scope argument is auto - generated to the new child of the transcluded parent scope .
* @ param { DOMElement = } $rootElement If the nodeList is the root of the compilation tree then
* the rootElement must be set the jqLite collection of the compile root . This is
* needed so that the jqLite collection items can be replaced with widgets .
* @ param { number = } maxPriority Max directive priority .
2018-05-05 12:13:16 +02:00
* @ returns { Function } A composite linking function of all of the matched directives or null .
2016-03-28 10:46:51 +00:00
* /
function compileNodes ( nodeList , transcludeFn , $rootElement , maxPriority , ignoreDirective ,
previousCompileContext ) {
var linkFns = [ ] ,
2018-05-05 12:13:16 +02:00
attrs , directives , nodeLinkFn , childNodes , childLinkFn , linkFnFound , nodeLinkFnFound ;
2016-03-28 10:46:51 +00:00
for ( var i = 0 ; i < nodeList . length ; i ++ ) {
attrs = new Attributes ( ) ;
// we must always refer to nodeList[i] since the nodes can be replaced underneath us.
directives = collectDirectives ( nodeList [ i ] , [ ] , attrs , i === 0 ? maxPriority : undefined ,
ignoreDirective ) ;
nodeLinkFn = ( directives . length )
? applyDirectivesToNode ( directives , nodeList [ i ] , attrs , transcludeFn , $rootElement ,
null , [ ] , [ ] , previousCompileContext )
: null ;
if ( nodeLinkFn && nodeLinkFn . scope ) {
2018-05-05 12:13:16 +02:00
compile . $$addScopeClass ( attrs . $$element ) ;
2016-03-28 10:46:51 +00:00
}
childLinkFn = ( nodeLinkFn && nodeLinkFn . terminal ||
! ( childNodes = nodeList [ i ] . childNodes ) ||
! childNodes . length )
? null
: compileNodes ( childNodes ,
2018-05-05 12:13:16 +02:00
nodeLinkFn ? (
( nodeLinkFn . transcludeOnThisElement || ! nodeLinkFn . templateOnThisElement )
&& nodeLinkFn . transclude ) : transcludeFn ) ;
if ( nodeLinkFn || childLinkFn ) {
linkFns . push ( i , nodeLinkFn , childLinkFn ) ;
linkFnFound = true ;
nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn ;
}
2016-03-28 10:46:51 +00:00
//use the previous context only for the first element in the virtual group
previousCompileContext = null ;
}
// return a linking function if we have found anything, null otherwise
return linkFnFound ? compositeLinkFn : null ;
2018-05-05 12:13:16 +02:00
function compositeLinkFn ( scope , nodeList , $rootElement , parentBoundTranscludeFn ) {
var nodeLinkFn , childLinkFn , node , childScope , i , ii , idx , childBoundTranscludeFn ;
var stableNodeList ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
if ( nodeLinkFnFound ) {
// copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our
// offsets don't get screwed up
var nodeListLength = nodeList . length ;
stableNodeList = new Array ( nodeListLength ) ;
// create a sparse array by only copying the elements which have a linkFn
for ( i = 0 ; i < linkFns . length ; i += 3 ) {
idx = linkFns [ i ] ;
stableNodeList [ idx ] = nodeList [ idx ] ;
}
} else {
stableNodeList = nodeList ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
for ( i = 0 , ii = linkFns . length ; i < ii ; ) {
node = stableNodeList [ linkFns [ i ++ ] ] ;
2016-03-28 10:46:51 +00:00
nodeLinkFn = linkFns [ i ++ ] ;
childLinkFn = linkFns [ i ++ ] ;
if ( nodeLinkFn ) {
if ( nodeLinkFn . scope ) {
childScope = scope . $new ( ) ;
2018-05-05 12:13:16 +02:00
compile . $$addScopeInfo ( jqLite ( node ) , childScope ) ;
2016-03-28 10:46:51 +00:00
} else {
childScope = scope ;
}
2018-05-05 12:13:16 +02:00
if ( nodeLinkFn . transcludeOnThisElement ) {
childBoundTranscludeFn = createBoundTranscludeFn (
scope , nodeLinkFn . transclude , parentBoundTranscludeFn ,
nodeLinkFn . elementTranscludeOnThisElement ) ;
} else if ( ! nodeLinkFn . templateOnThisElement && parentBoundTranscludeFn ) {
childBoundTranscludeFn = parentBoundTranscludeFn ;
} else if ( ! parentBoundTranscludeFn && transcludeFn ) {
childBoundTranscludeFn = createBoundTranscludeFn ( scope , transcludeFn ) ;
2016-03-28 10:46:51 +00:00
} else {
2018-05-05 12:13:16 +02:00
childBoundTranscludeFn = null ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
nodeLinkFn ( childLinkFn , childScope , node , $rootElement , childBoundTranscludeFn ) ;
2016-03-28 10:46:51 +00:00
} else if ( childLinkFn ) {
2018-05-05 12:13:16 +02:00
childLinkFn ( scope , node . childNodes , undefined , parentBoundTranscludeFn ) ;
2016-03-28 10:46:51 +00:00
}
}
}
}
2018-05-05 12:13:16 +02:00
function createBoundTranscludeFn ( scope , transcludeFn , previousBoundTranscludeFn , elementTransclusion ) {
var boundTranscludeFn = function ( transcludedScope , cloneFn , controllers , futureParentElement , containingScope ) {
2016-03-28 10:46:51 +00:00
if ( ! transcludedScope ) {
2018-05-05 12:13:16 +02:00
transcludedScope = scope . $new ( false , containingScope ) ;
2016-03-28 10:46:51 +00:00
transcludedScope . $$transcluded = true ;
}
2018-05-05 12:13:16 +02:00
return transcludeFn ( transcludedScope , cloneFn , {
parentBoundTranscludeFn : previousBoundTranscludeFn ,
transcludeControllers : controllers ,
futureParentElement : futureParentElement
} ) ;
2016-04-18 12:34:29 +00:00
} ;
2018-05-05 12:13:16 +02:00
return boundTranscludeFn ;
2016-03-28 10:46:51 +00:00
}
/ * *
* Looks for directives on the given node and adds them to the directive collection which is
* sorted .
*
* @ param node Node to search .
* @ param directives An array to which the directives are added to . This array is sorted before
* the function returns .
* @ param attrs The shared attrs object which is used to populate the normalized attributes .
* @ param { number = } maxPriority Max directive priority .
* /
function collectDirectives ( node , directives , attrs , maxPriority , ignoreDirective ) {
var nodeType = node . nodeType ,
attrsMap = attrs . $attr ,
match ,
className ;
2018-05-05 12:13:16 +02:00
switch ( nodeType ) {
case NODE _TYPE _ELEMENT : /* Element */
2016-03-28 10:46:51 +00:00
// use the node name: <directive>
addDirective ( directives ,
2018-05-05 12:13:16 +02:00
directiveNormalize ( nodeName _ ( node ) ) , 'E' , maxPriority , ignoreDirective ) ;
2016-03-28 10:46:51 +00:00
// iterate over the attributes
2018-05-05 12:13:16 +02:00
for ( var attr , name , nName , ngAttrName , value , isNgAttr , nAttrs = node . attributes ,
2016-03-28 10:46:51 +00:00
j = 0 , jj = nAttrs && nAttrs . length ; j < jj ; j ++ ) {
var attrStartName = false ;
var attrEndName = false ;
attr = nAttrs [ j ] ;
2018-05-05 12:13:16 +02:00
name = attr . name ;
value = trim ( attr . value ) ;
// support ngAttr attribute binding
ngAttrName = directiveNormalize ( name ) ;
if ( isNgAttr = NG _ATTR _BINDING . test ( ngAttrName ) ) {
name = name . replace ( PREFIX _REGEXP , '' )
. substr ( 8 ) . replace ( /_(.)/g , function ( match , letter ) {
return letter . toUpperCase ( ) ;
} ) ;
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
var directiveNName = ngAttrName . replace ( /(Start|End)$/ , '' ) ;
if ( directiveIsMultiElement ( directiveNName ) ) {
2016-04-18 12:34:29 +00:00
if ( ngAttrName === directiveNName + 'Start' ) {
attrStartName = name ;
attrEndName = name . substr ( 0 , name . length - 5 ) + 'end' ;
name = name . substr ( 0 , name . length - 6 ) ;
}
2018-05-05 12:13:16 +02:00
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
nName = directiveNormalize ( name . toLowerCase ( ) ) ;
attrsMap [ nName ] = name ;
if ( isNgAttr || ! attrs . hasOwnProperty ( nName ) ) {
attrs [ nName ] = value ;
if ( getBooleanAttrName ( node , nName ) ) {
attrs [ nName ] = true ; // presence means true
}
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
addAttrInterpolateDirective ( node , directives , value , nName , isNgAttr ) ;
addDirective ( directives , nName , 'A' , maxPriority , ignoreDirective , attrStartName ,
attrEndName ) ;
2016-03-28 10:46:51 +00:00
}
// use class as directive
className = node . className ;
2018-05-05 12:13:16 +02:00
if ( isObject ( className ) ) {
// Maybe SVGAnimatedString
className = className . animVal ;
}
2016-03-28 10:46:51 +00:00
if ( isString ( className ) && className !== '' ) {
while ( match = CLASS _DIRECTIVE _REGEXP . exec ( className ) ) {
nName = directiveNormalize ( match [ 2 ] ) ;
if ( addDirective ( directives , nName , 'C' , maxPriority , ignoreDirective ) ) {
attrs [ nName ] = trim ( match [ 3 ] ) ;
}
className = className . substr ( match . index + match [ 0 ] . length ) ;
}
}
break ;
2018-05-05 12:13:16 +02:00
case NODE _TYPE _TEXT : /* Text Node */
2016-03-28 10:46:51 +00:00
addTextInterpolateDirective ( directives , node . nodeValue ) ;
break ;
2018-05-05 12:13:16 +02:00
case NODE _TYPE _COMMENT : /* Comment */
2016-03-28 10:46:51 +00:00
try {
match = COMMENT _DIRECTIVE _REGEXP . exec ( node . nodeValue ) ;
if ( match ) {
nName = directiveNormalize ( match [ 1 ] ) ;
if ( addDirective ( directives , nName , 'M' , maxPriority , ignoreDirective ) ) {
attrs [ nName ] = trim ( match [ 2 ] ) ;
}
}
} catch ( e ) {
// turns out that under some circumstances IE9 throws errors when one attempts to read
// comment's node value.
// Just ignore it and continue. (Can't seem to reproduce in test case.)
}
break ;
}
directives . sort ( byPriority ) ;
return directives ;
}
/ * *
* Given a node with an directive - start it collects all of the siblings until it finds
* directive - end .
* @ param node
* @ param attrStart
* @ param attrEnd
* @ returns { * }
* /
function groupScan ( node , attrStart , attrEnd ) {
var nodes = [ ] ;
var depth = 0 ;
if ( attrStart && node . hasAttribute && node . hasAttribute ( attrStart ) ) {
do {
if ( ! node ) {
throw $compileMinErr ( 'uterdir' ,
"Unterminated attribute, found '{0}' but no matching '{1}' found." ,
attrStart , attrEnd ) ;
}
2018-05-05 12:13:16 +02:00
if ( node . nodeType == NODE _TYPE _ELEMENT ) {
2016-03-28 10:46:51 +00:00
if ( node . hasAttribute ( attrStart ) ) depth ++ ;
if ( node . hasAttribute ( attrEnd ) ) depth -- ;
}
nodes . push ( node ) ;
node = node . nextSibling ;
} while ( depth > 0 ) ;
} else {
nodes . push ( node ) ;
}
return jqLite ( nodes ) ;
}
/ * *
* Wrapper for linking function which converts normal linking function into a grouped
* linking function .
* @ param linkFn
* @ param attrStart
* @ param attrEnd
* @ returns { Function }
* /
function groupElementsLinkFnWrapper ( linkFn , attrStart , attrEnd ) {
2016-04-18 12:34:29 +00:00
return function ( scope , element , attrs , controllers , transcludeFn ) {
2016-03-28 10:46:51 +00:00
element = groupScan ( element [ 0 ] , attrStart , attrEnd ) ;
return linkFn ( scope , element , attrs , controllers , transcludeFn ) ;
} ;
}
/ * *
* Once the directives have been collected , their compile functions are executed . This method
* is responsible for inlining directive templates as well as terminating the application
* of the directives if the terminal directive has been reached .
*
* @ param { Array } directives Array of collected directives to execute their compile function .
* this needs to be pre - sorted by priority order .
* @ param { Node } compileNode The raw DOM node to apply the compile functions to
* @ param { Object } templateAttrs The shared attribute function
2018-05-05 12:13:16 +02:00
* @ param { function ( angular . Scope , cloneAttachFn = ) } transcludeFn A linking function , where the
2016-03-28 10:46:51 +00:00
* scope argument is auto - generated to the new
* child of the transcluded parent scope .
* @ param { JQLite } jqCollection If we are working on the root of the compile tree then this
* argument has the root jqLite array so that we can replace nodes
* on it .
* @ param { Object = } originalReplaceDirective An optional directive that will be ignored when
* compiling the transclusion .
* @ param { Array . < Function > } preLinkFns
* @ param { Array . < Function > } postLinkFns
* @ param { Object } previousCompileContext Context used for previous compilation of the current
* node
2018-05-05 12:13:16 +02:00
* @ returns { Function } linkFn
2016-03-28 10:46:51 +00:00
* /
function applyDirectivesToNode ( directives , compileNode , templateAttrs , transcludeFn ,
jqCollection , originalReplaceDirective , preLinkFns , postLinkFns ,
previousCompileContext ) {
previousCompileContext = previousCompileContext || { } ;
var terminalPriority = - Number . MAX _VALUE ,
2016-04-18 12:34:29 +00:00
newScopeDirective ,
2016-03-28 10:46:51 +00:00
controllerDirectives = previousCompileContext . controllerDirectives ,
2018-05-05 12:13:16 +02:00
controllers ,
2016-03-28 10:46:51 +00:00
newIsolateScopeDirective = previousCompileContext . newIsolateScopeDirective ,
templateDirective = previousCompileContext . templateDirective ,
nonTlbTranscludeDirective = previousCompileContext . nonTlbTranscludeDirective ,
hasTranscludeDirective = false ,
2018-05-05 12:13:16 +02:00
hasTemplate = false ,
hasElementTranscludeDirective = previousCompileContext . hasElementTranscludeDirective ,
2016-03-28 10:46:51 +00:00
$compileNode = templateAttrs . $$element = jqLite ( compileNode ) ,
directive ,
directiveName ,
$template ,
replaceDirective = originalReplaceDirective ,
childTranscludeFn = transcludeFn ,
linkFn ,
directiveValue ;
// executes all directives on the current element
2018-05-05 12:13:16 +02:00
for ( var i = 0 , ii = directives . length ; i < ii ; i ++ ) {
2016-03-28 10:46:51 +00:00
directive = directives [ i ] ;
var attrStart = directive . $$start ;
var attrEnd = directive . $$end ;
// collect multiblock sections
if ( attrStart ) {
$compileNode = groupScan ( compileNode , attrStart , attrEnd ) ;
}
$template = undefined ;
if ( terminalPriority > directive . priority ) {
break ; // prevent further processing of directives
}
if ( directiveValue = directive . scope ) {
// skip the check for directives with async templates, we'll check the derived sync
// directive when the template arrives
if ( ! directive . templateUrl ) {
if ( isObject ( directiveValue ) ) {
2018-05-05 12:13:16 +02:00
// This directive is trying to add an isolated scope.
// Check that there is no scope of any kind already
assertNoDuplicate ( 'new/isolated scope' , newIsolateScopeDirective || newScopeDirective ,
directive , $compileNode ) ;
2016-03-28 10:46:51 +00:00
newIsolateScopeDirective = directive ;
2018-05-05 12:13:16 +02:00
} else {
// This directive is trying to add a child scope.
// Check that there is no isolated scope already
assertNoDuplicate ( 'new/isolated scope' , newIsolateScopeDirective , directive ,
$compileNode ) ;
2016-03-28 10:46:51 +00:00
}
}
2018-05-05 12:13:16 +02:00
newScopeDirective = newScopeDirective || directive ;
2016-03-28 10:46:51 +00:00
}
directiveName = directive . name ;
if ( ! directive . templateUrl && directive . controller ) {
directiveValue = directive . controller ;
2016-04-18 12:34:29 +00:00
controllerDirectives = controllerDirectives || { } ;
2016-03-28 10:46:51 +00:00
assertNoDuplicate ( "'" + directiveName + "' controller" ,
controllerDirectives [ directiveName ] , directive , $compileNode ) ;
controllerDirectives [ directiveName ] = directive ;
}
if ( directiveValue = directive . transclude ) {
hasTranscludeDirective = true ;
// Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion.
2018-05-05 12:13:16 +02:00
// This option should only be used by directives that know how to safely handle element transclusion,
2016-03-28 10:46:51 +00:00
// where the transcluded nodes are added or replaced after linking.
if ( ! directive . $$tlb ) {
assertNoDuplicate ( 'transclusion' , nonTlbTranscludeDirective , directive , $compileNode ) ;
nonTlbTranscludeDirective = directive ;
}
if ( directiveValue == 'element' ) {
hasElementTranscludeDirective = true ;
terminalPriority = directive . priority ;
2018-05-05 12:13:16 +02:00
$template = $compileNode ;
2016-03-28 10:46:51 +00:00
$compileNode = templateAttrs . $$element =
2016-04-18 12:34:29 +00:00
jqLite ( document . createComment ( ' ' + directiveName + ': ' +
templateAttrs [ directiveName ] + ' ' ) ) ;
2016-03-28 10:46:51 +00:00
compileNode = $compileNode [ 0 ] ;
2018-05-05 12:13:16 +02:00
replaceWith ( jqCollection , sliceArgs ( $template ) , compileNode ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
childTranscludeFn = compile ( $template , transcludeFn , terminalPriority ,
2016-03-28 10:46:51 +00:00
replaceDirective && replaceDirective . name , {
// Don't pass in:
// - controllerDirectives - otherwise we'll create duplicates controllers
// - newIsolateScopeDirective or templateDirective - combining templates with
// element transclusion doesn't make sense.
//
// We need only nonTlbTranscludeDirective so that we prevent putting transclusion
// on the same element more than once.
nonTlbTranscludeDirective : nonTlbTranscludeDirective
} ) ;
} else {
$template = jqLite ( jqLiteClone ( compileNode ) ) . contents ( ) ;
$compileNode . empty ( ) ; // clear contents
2016-04-18 12:34:29 +00:00
childTranscludeFn = compile ( $template , transcludeFn ) ;
2016-03-28 10:46:51 +00:00
}
}
if ( directive . template ) {
2018-05-05 12:13:16 +02:00
hasTemplate = true ;
2016-03-28 10:46:51 +00:00
assertNoDuplicate ( 'template' , templateDirective , directive , $compileNode ) ;
templateDirective = directive ;
directiveValue = ( isFunction ( directive . template ) )
? directive . template ( $compileNode , templateAttrs )
: directive . template ;
directiveValue = denormalizeTemplate ( directiveValue ) ;
if ( directive . replace ) {
replaceDirective = directive ;
2018-05-05 12:13:16 +02:00
if ( jqLiteIsTextNode ( directiveValue ) ) {
$template = [ ] ;
} else {
$template = removeComments ( wrapTemplate ( directive . templateNamespace , trim ( directiveValue ) ) ) ;
}
2016-03-28 10:46:51 +00:00
compileNode = $template [ 0 ] ;
2018-05-05 12:13:16 +02:00
if ( $template . length != 1 || compileNode . nodeType !== NODE _TYPE _ELEMENT ) {
2016-03-28 10:46:51 +00:00
throw $compileMinErr ( 'tplrt' ,
"Template for directive '{0}' must have exactly one root element. {1}" ,
directiveName , '' ) ;
}
replaceWith ( jqCollection , $compileNode , compileNode ) ;
var newTemplateAttrs = { $attr : { } } ;
// combine directives from the original node and from the template:
// - take the array of directives for this element
// - split it into two parts, those that already applied (processed) and those that weren't (unprocessed)
// - collect directives from the template and sort them by priority
// - combine directives as: processed + template + unprocessed
var templateDirectives = collectDirectives ( compileNode , [ ] , newTemplateAttrs ) ;
var unprocessedDirectives = directives . splice ( i + 1 , directives . length - ( i + 1 ) ) ;
2016-04-18 12:34:29 +00:00
if ( newIsolateScopeDirective ) {
markDirectivesAsIsolate ( templateDirectives ) ;
2016-03-28 10:46:51 +00:00
}
directives = directives . concat ( templateDirectives ) . concat ( unprocessedDirectives ) ;
mergeTemplateAttributes ( templateAttrs , newTemplateAttrs ) ;
ii = directives . length ;
} else {
$compileNode . html ( directiveValue ) ;
}
}
if ( directive . templateUrl ) {
2018-05-05 12:13:16 +02:00
hasTemplate = true ;
2016-03-28 10:46:51 +00:00
assertNoDuplicate ( 'template' , templateDirective , directive , $compileNode ) ;
templateDirective = directive ;
if ( directive . replace ) {
replaceDirective = directive ;
}
nodeLinkFn = compileTemplateUrl ( directives . splice ( i , directives . length - i ) , $compileNode ,
2018-05-05 12:13:16 +02:00
templateAttrs , jqCollection , hasTranscludeDirective && childTranscludeFn , preLinkFns , postLinkFns , {
2016-03-28 10:46:51 +00:00
controllerDirectives : controllerDirectives ,
newIsolateScopeDirective : newIsolateScopeDirective ,
templateDirective : templateDirective ,
nonTlbTranscludeDirective : nonTlbTranscludeDirective
} ) ;
ii = directives . length ;
} else if ( directive . compile ) {
try {
linkFn = directive . compile ( $compileNode , templateAttrs , childTranscludeFn ) ;
if ( isFunction ( linkFn ) ) {
addLinkFns ( null , linkFn , attrStart , attrEnd ) ;
} else if ( linkFn ) {
addLinkFns ( linkFn . pre , linkFn . post , attrStart , attrEnd ) ;
}
} catch ( e ) {
$exceptionHandler ( e , startingTag ( $compileNode ) ) ;
}
}
if ( directive . terminal ) {
nodeLinkFn . terminal = true ;
terminalPriority = Math . max ( terminalPriority , directive . priority ) ;
}
}
nodeLinkFn . scope = newScopeDirective && newScopeDirective . scope === true ;
2018-05-05 12:13:16 +02:00
nodeLinkFn . transcludeOnThisElement = hasTranscludeDirective ;
nodeLinkFn . elementTranscludeOnThisElement = hasElementTranscludeDirective ;
nodeLinkFn . templateOnThisElement = hasTemplate ;
nodeLinkFn . transclude = childTranscludeFn ;
previousCompileContext . hasElementTranscludeDirective = hasElementTranscludeDirective ;
2016-03-28 10:46:51 +00:00
// might be normal or delayed nodeLinkFn depending on if templateUrl is present
return nodeLinkFn ;
////////////////////
function addLinkFns ( pre , post , attrStart , attrEnd ) {
if ( pre ) {
if ( attrStart ) pre = groupElementsLinkFnWrapper ( pre , attrStart , attrEnd ) ;
pre . require = directive . require ;
2018-05-05 12:13:16 +02:00
pre . directiveName = directiveName ;
2016-03-28 10:46:51 +00:00
if ( newIsolateScopeDirective === directive || directive . $$isolateScope ) {
pre = cloneAndAnnotateFn ( pre , { isolateScope : true } ) ;
}
preLinkFns . push ( pre ) ;
}
if ( post ) {
if ( attrStart ) post = groupElementsLinkFnWrapper ( post , attrStart , attrEnd ) ;
post . require = directive . require ;
2018-05-05 12:13:16 +02:00
post . directiveName = directiveName ;
2016-03-28 10:46:51 +00:00
if ( newIsolateScopeDirective === directive || directive . $$isolateScope ) {
post = cloneAndAnnotateFn ( post , { isolateScope : true } ) ;
}
postLinkFns . push ( post ) ;
}
}
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
function getControllers ( directiveName , require , $element , elementControllers ) {
2016-04-18 12:34:29 +00:00
var value , retrievalMethod = 'data' , optional = false ;
2018-05-05 12:13:16 +02:00
var $searchElement = $element ;
var match ;
2016-04-18 12:34:29 +00:00
if ( isString ( require ) ) {
2018-05-05 12:13:16 +02:00
match = require . match ( REQUIRE _PREFIX _REGEXP ) ;
require = require . substring ( match [ 0 ] . length ) ;
if ( match [ 3 ] ) {
if ( match [ 1 ] ) match [ 3 ] = null ;
else match [ 1 ] = match [ 3 ] ;
}
if ( match [ 1 ] === '^' ) {
retrievalMethod = 'inheritedData' ;
} else if ( match [ 1 ] === '^^' ) {
retrievalMethod = 'inheritedData' ;
$searchElement = $element . parent ( ) ;
2016-04-18 12:34:29 +00:00
}
2018-05-05 12:13:16 +02:00
if ( match [ 2 ] === '?' ) {
optional = true ;
}
2016-04-18 12:34:29 +00:00
value = null ;
if ( elementControllers && retrievalMethod === 'data' ) {
2018-05-05 12:13:16 +02:00
if ( value = elementControllers [ require ] ) {
value = value . instance ;
}
2016-04-18 12:34:29 +00:00
}
2018-05-05 12:13:16 +02:00
value = value || $searchElement [ retrievalMethod ] ( '$' + require + 'Controller' ) ;
2016-04-18 12:34:29 +00:00
if ( ! value && ! optional ) {
throw $compileMinErr ( 'ctreq' ,
"Controller '{0}', required by directive '{1}', can't be found!" ,
require , directiveName ) ;
}
2018-05-05 12:13:16 +02:00
return value || null ;
2016-04-18 12:34:29 +00:00
} else if ( isArray ( require ) ) {
value = [ ] ;
forEach ( require , function ( require ) {
2018-05-05 12:13:16 +02:00
value . push ( getControllers ( directiveName , require , $element , elementControllers ) ) ;
2016-04-18 12:34:29 +00:00
} ) ;
}
return value ;
}
2016-03-28 10:46:51 +00:00
function nodeLinkFn ( childLinkFn , scope , linkNode , $rootElement , boundTranscludeFn ) {
2018-05-05 12:13:16 +02:00
var i , ii , linkFn , controller , isolateScope , elementControllers , transcludeFn , $element ,
attrs ;
2016-03-28 10:46:51 +00:00
if ( compileNode === linkNode ) {
attrs = templateAttrs ;
2018-05-05 12:13:16 +02:00
$element = templateAttrs . $$element ;
2016-03-28 10:46:51 +00:00
} else {
2018-05-05 12:13:16 +02:00
$element = jqLite ( linkNode ) ;
attrs = new Attributes ( $element , templateAttrs ) ;
2016-03-28 10:46:51 +00:00
}
if ( newIsolateScopeDirective ) {
isolateScope = scope . $new ( true ) ;
2018-05-05 12:13:16 +02:00
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
if ( boundTranscludeFn ) {
// track `boundTranscludeFn` so it can be unwrapped if `transcludeFn`
// is later passed as `parentBoundTranscludeFn` to `publicLinkFn`
transcludeFn = controllersBoundTransclude ;
transcludeFn . $$boundTransclude = boundTranscludeFn ;
}
if ( controllerDirectives ) {
// TODO: merge `controllers` and `elementControllers` into single object.
controllers = { } ;
elementControllers = { } ;
forEach ( controllerDirectives , function ( directive ) {
var locals = {
$scope : directive === newIsolateScopeDirective || directive . $$isolateScope ? isolateScope : scope ,
$element : $element ,
$attrs : attrs ,
$transclude : transcludeFn
} , controllerInstance ;
controller = directive . controller ;
if ( controller == '@' ) {
controller = attrs [ directive . name ] ;
}
controllerInstance = $controller ( controller , locals , true , directive . controllerAs ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
// For directives with element transclusion the element is a comment,
// but jQuery .data doesn't support attaching data to comment nodes as it's hard to
// clean up (http://bugs.jquery.com/ticket/8335).
// Instead, we save the controllers for the element in a local hash and attach to .data
// later, once we have the actual element.
elementControllers [ directive . name ] = controllerInstance ;
if ( ! hasElementTranscludeDirective ) {
$element . data ( '$' + directive . name + 'Controller' , controllerInstance . instance ) ;
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
controllers [ directive . name ] = controllerInstance ;
} ) ;
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
if ( newIsolateScopeDirective ) {
compile . $$addScopeInfo ( $element , isolateScope , true , ! ( templateDirective && ( templateDirective === newIsolateScopeDirective ||
templateDirective === newIsolateScopeDirective . $$originalDirective ) ) ) ;
compile . $$addScopeClass ( $element , true ) ;
var isolateScopeController = controllers && controllers [ newIsolateScopeDirective . name ] ;
var isolateBindingContext = isolateScope ;
if ( isolateScopeController && isolateScopeController . identifier &&
newIsolateScopeDirective . bindToController === true ) {
isolateBindingContext = isolateScopeController . instance ;
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
forEach ( isolateScope . $$isolateBindings = newIsolateScopeDirective . $$isolateBindings , function ( definition , scopeName ) {
var attrName = definition . attrName ,
optional = definition . optional ,
mode = definition . mode , // @, =, or &
2016-04-18 12:34:29 +00:00
lastValue ,
parentGet , parentSet , compare ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
switch ( mode ) {
case '@' :
attrs . $observe ( attrName , function ( value ) {
2018-05-05 12:13:16 +02:00
isolateBindingContext [ scopeName ] = value ;
2016-04-18 12:34:29 +00:00
} ) ;
attrs . $$observers [ attrName ] . $$scope = scope ;
2018-05-05 12:13:16 +02:00
if ( attrs [ attrName ] ) {
2016-04-18 12:34:29 +00:00
// If the attribute has been provided then we trigger an interpolation to ensure
// the value is there for use in the link fn
2018-05-05 12:13:16 +02:00
isolateBindingContext [ scopeName ] = $interpolate ( attrs [ attrName ] ) ( scope ) ;
2016-04-18 12:34:29 +00:00
}
break ;
case '=' :
if ( optional && ! attrs [ attrName ] ) {
return ;
}
parentGet = $parse ( attrs [ attrName ] ) ;
if ( parentGet . literal ) {
compare = equals ;
} else {
2018-05-05 12:13:16 +02:00
compare = function ( a , b ) { return a === b || ( a !== a && b !== b ) ; } ;
2016-04-18 12:34:29 +00:00
}
parentSet = parentGet . assign || function ( ) {
// reset the change, or we will throw this exception on every $digest
2018-05-05 12:13:16 +02:00
lastValue = isolateBindingContext [ scopeName ] = parentGet ( scope ) ;
2016-04-18 12:34:29 +00:00
throw $compileMinErr ( 'nonassign' ,
"Expression '{0}' used with directive '{1}' is non-assignable!" ,
attrs [ attrName ] , newIsolateScopeDirective . name ) ;
} ;
2018-05-05 12:13:16 +02:00
lastValue = isolateBindingContext [ scopeName ] = parentGet ( scope ) ;
var parentValueWatch = function parentValueWatch ( parentValue ) {
if ( ! compare ( parentValue , isolateBindingContext [ scopeName ] ) ) {
2016-04-18 12:34:29 +00:00
// we are out of sync and need to copy
if ( ! compare ( parentValue , lastValue ) ) {
// parent changed and it has precedence
2018-05-05 12:13:16 +02:00
isolateBindingContext [ scopeName ] = parentValue ;
2016-04-18 12:34:29 +00:00
} else {
// if the parent can be assigned then do so
2018-05-05 12:13:16 +02:00
parentSet ( scope , parentValue = isolateBindingContext [ scopeName ] ) ;
2016-04-18 12:34:29 +00:00
}
}
return lastValue = parentValue ;
2018-05-05 12:13:16 +02:00
} ;
parentValueWatch . $stateful = true ;
var unwatch ;
if ( definition . collection ) {
unwatch = scope . $watchCollection ( attrs [ attrName ] , parentValueWatch ) ;
} else {
unwatch = scope . $watch ( $parse ( attrs [ attrName ] , parentValueWatch ) , null , parentGet . literal ) ;
}
isolateScope . $on ( '$destroy' , unwatch ) ;
2016-04-18 12:34:29 +00:00
break ;
case '&' :
parentGet = $parse ( attrs [ attrName ] ) ;
2018-05-05 12:13:16 +02:00
isolateBindingContext [ scopeName ] = function ( locals ) {
2016-04-18 12:34:29 +00:00
return parentGet ( scope , locals ) ;
} ;
break ;
}
} ) ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
if ( controllers ) {
forEach ( controllers , function ( controller ) {
controller ( ) ;
2016-04-18 12:34:29 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
controllers = null ;
2016-04-18 12:34:29 +00:00
}
2016-03-28 10:46:51 +00:00
// PRELINKING
2018-05-05 12:13:16 +02:00
for ( i = 0 , ii = preLinkFns . length ; i < ii ; i ++ ) {
linkFn = preLinkFns [ i ] ;
invokeLinkFn ( linkFn ,
linkFn . isolateScope ? isolateScope : scope ,
$element ,
attrs ,
linkFn . require && getControllers ( linkFn . directiveName , linkFn . require , $element , elementControllers ) ,
transcludeFn
) ;
2016-03-28 10:46:51 +00:00
}
// RECURSION
// We only pass the isolate scope, if the isolate directive has a template,
// otherwise the child elements do not belong to the isolate directive.
var scopeToChild = scope ;
if ( newIsolateScopeDirective && ( newIsolateScopeDirective . template || newIsolateScopeDirective . templateUrl === null ) ) {
scopeToChild = isolateScope ;
}
childLinkFn && childLinkFn ( scopeToChild , linkNode . childNodes , undefined , boundTranscludeFn ) ;
// POSTLINKING
2018-05-05 12:13:16 +02:00
for ( i = postLinkFns . length - 1 ; i >= 0 ; i -- ) {
linkFn = postLinkFns [ i ] ;
invokeLinkFn ( linkFn ,
linkFn . isolateScope ? isolateScope : scope ,
$element ,
attrs ,
linkFn . require && getControllers ( linkFn . directiveName , linkFn . require , $element , elementControllers ) ,
transcludeFn
) ;
2016-04-18 12:34:29 +00:00
}
2016-03-28 10:46:51 +00:00
// This is the function that is injected as `$transclude`.
2018-05-05 12:13:16 +02:00
// Note: all arguments are optional!
function controllersBoundTransclude ( scope , cloneAttachFn , futureParentElement ) {
2016-03-28 10:46:51 +00:00
var transcludeControllers ;
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
// No scope passed in:
if ( ! isScope ( scope ) ) {
futureParentElement = cloneAttachFn ;
2016-03-28 10:46:51 +00:00
cloneAttachFn = scope ;
scope = undefined ;
}
if ( hasElementTranscludeDirective ) {
transcludeControllers = elementControllers ;
}
2018-05-05 12:13:16 +02:00
if ( ! futureParentElement ) {
futureParentElement = hasElementTranscludeDirective ? $element . parent ( ) : $element ;
}
return boundTranscludeFn ( scope , cloneAttachFn , transcludeControllers , futureParentElement , scopeToChild ) ;
2016-03-28 10:46:51 +00:00
}
}
}
2016-04-18 12:34:29 +00:00
function markDirectivesAsIsolate ( directives ) {
// mark all directives as needing isolate scope.
2016-03-28 10:46:51 +00:00
for ( var j = 0 , jj = directives . length ; j < jj ; j ++ ) {
2016-04-18 12:34:29 +00:00
directives [ j ] = inherit ( directives [ j ] , { $$isolateScope : true } ) ;
2016-03-28 10:46:51 +00:00
}
}
/ * *
* looks up the directive and decorates it with exception handling and proper parameters . We
* call this the boundDirective .
*
* @ param { string } name name of the directive to look up .
* @ param { string } location The directive must be found in specific format .
* String containing any of theses characters :
*
* * ` E ` : element name
* * ` A': attribute
* * ` C ` : class
* * ` M ` : comment
2018-05-05 12:13:16 +02:00
* @ returns { boolean } true if directive was added .
2016-03-28 10:46:51 +00:00
* /
function addDirective ( tDirectives , name , location , maxPriority , ignoreDirective , startAttrName ,
endAttrName ) {
if ( name === ignoreDirective ) return null ;
var match = null ;
if ( hasDirectives . hasOwnProperty ( name ) ) {
2018-05-05 12:13:16 +02:00
for ( var directive , directives = $injector . get ( name + Suffix ) ,
i = 0 , ii = directives . length ; i < ii ; i ++ ) {
2016-03-28 10:46:51 +00:00
try {
directive = directives [ i ] ;
2018-05-05 12:13:16 +02:00
if ( ( maxPriority === undefined || maxPriority > directive . priority ) &&
2016-03-28 10:46:51 +00:00
directive . restrict . indexOf ( location ) != - 1 ) {
if ( startAttrName ) {
directive = inherit ( directive , { $$start : startAttrName , $$end : endAttrName } ) ;
}
tDirectives . push ( directive ) ;
match = directive ;
}
2018-05-05 12:13:16 +02:00
} catch ( e ) { $exceptionHandler ( e ) ; }
2016-03-28 10:46:51 +00:00
}
}
return match ;
}
2018-05-05 12:13:16 +02:00
/ * *
* looks up the directive and returns true if it is a multi - element directive ,
* and therefore requires DOM nodes between - start and - end markers to be grouped
* together .
*
* @ param { string } name name of the directive to look up .
* @ returns true if directive was registered as multi - element .
* /
function directiveIsMultiElement ( name ) {
if ( hasDirectives . hasOwnProperty ( name ) ) {
for ( var directive , directives = $injector . get ( name + Suffix ) ,
i = 0 , ii = directives . length ; i < ii ; i ++ ) {
directive = directives [ i ] ;
if ( directive . multiElement ) {
return true ;
}
}
}
return false ;
}
2016-03-28 10:46:51 +00:00
/ * *
* When the element is replaced with HTML template then the new attributes
* on the template need to be merged with the existing attributes in the DOM .
* The desired effect is to have both of the attributes present .
*
* @ param { object } dst destination attributes ( original DOM )
* @ param { object } src source attributes ( from the directive template )
* /
function mergeTemplateAttributes ( dst , src ) {
var srcAttr = src . $attr ,
dstAttr = dst . $attr ,
$element = dst . $$element ;
// reapply the old attributes to the new element
forEach ( dst , function ( value , key ) {
if ( key . charAt ( 0 ) != '$' ) {
2018-05-05 12:13:16 +02:00
if ( src [ key ] && src [ key ] !== value ) {
2016-03-28 10:46:51 +00:00
value += ( key === 'style' ? ';' : ' ' ) + src [ key ] ;
}
dst . $set ( key , value , true , srcAttr [ key ] ) ;
}
} ) ;
// copy the new attributes on the old attrs object
forEach ( src , function ( value , key ) {
if ( key == 'class' ) {
safeAddClass ( $element , value ) ;
dst [ 'class' ] = ( dst [ 'class' ] ? dst [ 'class' ] + ' ' : '' ) + value ;
} else if ( key == 'style' ) {
$element . attr ( 'style' , $element . attr ( 'style' ) + ';' + value ) ;
dst [ 'style' ] = ( dst [ 'style' ] ? dst [ 'style' ] + ';' : '' ) + value ;
// `dst` will never contain hasOwnProperty as DOM parser won't let it.
// You will get an "InvalidCharacterError: DOM Exception 5" error if you
// have an attribute like "has-own-property" or "data-has-own-property", etc.
} else if ( key . charAt ( 0 ) != '$' && ! dst . hasOwnProperty ( key ) ) {
dst [ key ] = value ;
dstAttr [ key ] = srcAttr [ key ] ;
}
} ) ;
}
function compileTemplateUrl ( directives , $compileNode , tAttrs ,
$rootElement , childTranscludeFn , preLinkFns , postLinkFns , previousCompileContext ) {
var linkQueue = [ ] ,
afterTemplateNodeLinkFn ,
afterTemplateChildLinkFn ,
beforeTemplateCompileNode = $compileNode [ 0 ] ,
origAsyncDirective = directives . shift ( ) ,
2018-05-05 12:13:16 +02:00
derivedSyncDirective = inherit ( origAsyncDirective , {
2016-03-28 10:46:51 +00:00
templateUrl : null , transclude : null , replace : null , $$originalDirective : origAsyncDirective
} ) ,
templateUrl = ( isFunction ( origAsyncDirective . templateUrl ) )
? origAsyncDirective . templateUrl ( $compileNode , tAttrs )
2018-05-05 12:13:16 +02:00
: origAsyncDirective . templateUrl ,
templateNamespace = origAsyncDirective . templateNamespace ;
2016-03-28 10:46:51 +00:00
$compileNode . empty ( ) ;
2018-05-05 12:13:16 +02:00
$templateRequest ( templateUrl )
. then ( function ( content ) {
2016-03-28 10:46:51 +00:00
var compileNode , tempTemplateAttrs , $template , childBoundTranscludeFn ;
content = denormalizeTemplate ( content ) ;
if ( origAsyncDirective . replace ) {
2018-05-05 12:13:16 +02:00
if ( jqLiteIsTextNode ( content ) ) {
$template = [ ] ;
} else {
$template = removeComments ( wrapTemplate ( templateNamespace , trim ( content ) ) ) ;
}
2016-03-28 10:46:51 +00:00
compileNode = $template [ 0 ] ;
2018-05-05 12:13:16 +02:00
if ( $template . length != 1 || compileNode . nodeType !== NODE _TYPE _ELEMENT ) {
2016-03-28 10:46:51 +00:00
throw $compileMinErr ( 'tplrt' ,
"Template for directive '{0}' must have exactly one root element. {1}" ,
origAsyncDirective . name , templateUrl ) ;
}
tempTemplateAttrs = { $attr : { } } ;
replaceWith ( $rootElement , $compileNode , compileNode ) ;
var templateDirectives = collectDirectives ( compileNode , [ ] , tempTemplateAttrs ) ;
if ( isObject ( origAsyncDirective . scope ) ) {
2016-04-18 12:34:29 +00:00
markDirectivesAsIsolate ( templateDirectives ) ;
2016-03-28 10:46:51 +00:00
}
directives = templateDirectives . concat ( directives ) ;
mergeTemplateAttributes ( tAttrs , tempTemplateAttrs ) ;
} else {
compileNode = beforeTemplateCompileNode ;
$compileNode . html ( content ) ;
}
directives . unshift ( derivedSyncDirective ) ;
afterTemplateNodeLinkFn = applyDirectivesToNode ( directives , compileNode , tAttrs ,
childTranscludeFn , $compileNode , origAsyncDirective , preLinkFns , postLinkFns ,
previousCompileContext ) ;
forEach ( $rootElement , function ( node , i ) {
if ( node == compileNode ) {
$rootElement [ i ] = $compileNode [ 0 ] ;
}
} ) ;
afterTemplateChildLinkFn = compileNodes ( $compileNode [ 0 ] . childNodes , childTranscludeFn ) ;
2018-05-05 12:13:16 +02:00
while ( linkQueue . length ) {
2016-03-28 10:46:51 +00:00
var scope = linkQueue . shift ( ) ,
beforeTemplateLinkNode = linkQueue . shift ( ) ,
linkRootElement = linkQueue . shift ( ) ,
boundTranscludeFn = linkQueue . shift ( ) ,
linkNode = $compileNode [ 0 ] ;
2018-05-05 12:13:16 +02:00
if ( scope . $$destroyed ) continue ;
2016-03-28 10:46:51 +00:00
if ( beforeTemplateLinkNode !== beforeTemplateCompileNode ) {
2018-05-05 12:13:16 +02:00
var oldClasses = beforeTemplateLinkNode . className ;
if ( ! ( previousCompileContext . hasElementTranscludeDirective &&
origAsyncDirective . replace ) ) {
// it was cloned therefore we have to clone as well.
linkNode = jqLiteClone ( compileNode ) ;
}
2016-03-28 10:46:51 +00:00
replaceWith ( linkRootElement , jqLite ( beforeTemplateLinkNode ) , linkNode ) ;
2018-05-05 12:13:16 +02:00
// Copy in CSS classes from original node
safeAddClass ( jqLite ( linkNode ) , oldClasses ) ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
if ( afterTemplateNodeLinkFn . transcludeOnThisElement ) {
childBoundTranscludeFn = createBoundTranscludeFn ( scope , afterTemplateNodeLinkFn . transclude , boundTranscludeFn ) ;
2016-03-28 10:46:51 +00:00
} else {
childBoundTranscludeFn = boundTranscludeFn ;
}
afterTemplateNodeLinkFn ( afterTemplateChildLinkFn , scope , linkNode , $rootElement ,
childBoundTranscludeFn ) ;
}
linkQueue = null ;
} ) ;
return function delayedNodeLinkFn ( ignoreChildLinkFn , scope , node , rootElement , boundTranscludeFn ) {
2018-05-05 12:13:16 +02:00
var childBoundTranscludeFn = boundTranscludeFn ;
if ( scope . $$destroyed ) return ;
2016-03-28 10:46:51 +00:00
if ( linkQueue ) {
2018-05-05 12:13:16 +02:00
linkQueue . push ( scope ,
node ,
rootElement ,
childBoundTranscludeFn ) ;
2016-03-28 10:46:51 +00:00
} else {
2018-05-05 12:13:16 +02:00
if ( afterTemplateNodeLinkFn . transcludeOnThisElement ) {
childBoundTranscludeFn = createBoundTranscludeFn ( scope , afterTemplateNodeLinkFn . transclude , boundTranscludeFn ) ;
}
afterTemplateNodeLinkFn ( afterTemplateChildLinkFn , scope , node , rootElement , childBoundTranscludeFn ) ;
2016-03-28 10:46:51 +00:00
}
} ;
}
/ * *
* Sorting function for bound directives .
* /
function byPriority ( a , b ) {
var diff = b . priority - a . priority ;
if ( diff !== 0 ) return diff ;
if ( a . name !== b . name ) return ( a . name < b . name ) ? - 1 : 1 ;
return a . index - b . index ;
}
2016-04-18 12:34:29 +00:00
function assertNoDuplicate ( what , previousDirective , directive , element ) {
2016-03-28 10:46:51 +00:00
if ( previousDirective ) {
2016-04-18 12:34:29 +00:00
throw $compileMinErr ( 'multidir' , 'Multiple directives [{0}, {1}] asking for {2} on: {3}' ,
previousDirective . name , directive . name , what , startingTag ( element ) ) ;
2016-03-28 10:46:51 +00:00
}
}
function addTextInterpolateDirective ( directives , text ) {
var interpolateFn = $interpolate ( text , true ) ;
if ( interpolateFn ) {
directives . push ( {
priority : 0 ,
2018-05-05 12:13:16 +02:00
compile : function textInterpolateCompileFn ( templateNode ) {
var templateNodeParent = templateNode . parent ( ) ,
hasCompileParent = ! ! templateNodeParent . length ;
// When transcluding a template that has bindings in the root
// we don't have a parent and thus need to add the class during linking fn.
if ( hasCompileParent ) compile . $$addBindingClass ( templateNodeParent ) ;
return function textInterpolateLinkFn ( scope , node ) {
var parent = node . parent ( ) ;
if ( ! hasCompileParent ) compile . $$addBindingClass ( parent ) ;
compile . $$addBindingInfo ( parent , interpolateFn . expressions ) ;
scope . $watch ( interpolateFn , function interpolateFnWatchAction ( value ) {
node [ 0 ] . nodeValue = value ;
} ) ;
} ;
}
2016-03-28 10:46:51 +00:00
} ) ;
}
}
2018-05-05 12:13:16 +02:00
function wrapTemplate ( type , template ) {
type = lowercase ( type || 'html' ) ;
switch ( type ) {
case 'svg' :
case 'math' :
var wrapper = document . createElement ( 'div' ) ;
wrapper . innerHTML = '<' + type + '>' + template + '</' + type + '>' ;
return wrapper . childNodes [ 0 ] . childNodes ;
default :
return template ;
}
}
2016-03-28 10:46:51 +00:00
function getTrustedContext ( node , attrNormalizedName ) {
if ( attrNormalizedName == "srcdoc" ) {
return $sce . HTML ;
}
var tag = nodeName _ ( node ) ;
// maction[xlink:href] can source SVG. It's not limited to <maction>.
if ( attrNormalizedName == "xlinkHref" ||
2018-05-05 12:13:16 +02:00
( tag == "form" && attrNormalizedName == "action" ) ||
( tag != "img" && ( attrNormalizedName == "src" ||
2016-03-28 10:46:51 +00:00
attrNormalizedName == "ngSrc" ) ) ) {
return $sce . RESOURCE _URL ;
}
}
2018-05-05 12:13:16 +02:00
function addAttrInterpolateDirective ( node , directives , value , name , allOrNothing ) {
var trustedContext = getTrustedContext ( node , name ) ;
allOrNothing = ALL _OR _NOTHING _ATTRS [ name ] || allOrNothing ;
var interpolateFn = $interpolate ( value , true , trustedContext , allOrNothing ) ;
2016-03-28 10:46:51 +00:00
// no interpolation found -> ignore
if ( ! interpolateFn ) return ;
2018-05-05 12:13:16 +02:00
if ( name === "multiple" && nodeName _ ( node ) === "select" ) {
2016-03-28 10:46:51 +00:00
throw $compileMinErr ( "selmulti" ,
"Binding to the 'multiple' attribute is not supported. Element: {0}" ,
startingTag ( node ) ) ;
}
directives . push ( {
priority : 100 ,
compile : function ( ) {
return {
pre : function attrInterpolatePreLinkFn ( scope , element , attr ) {
2016-04-18 12:34:29 +00:00
var $$observers = ( attr . $$observers || ( attr . $$observers = { } ) ) ;
2016-03-28 10:46:51 +00:00
if ( EVENT _HANDLER _ATTR _REGEXP . test ( name ) ) {
throw $compileMinErr ( 'nodomevents' ,
"Interpolations for HTML DOM event attributes are disallowed. Please use the " +
"ng- versions (such as ng-click instead of onclick) instead." ) ;
}
2018-05-05 12:13:16 +02:00
// If the attribute has changed since last $interpolate()ed
var newValue = attr [ name ] ;
if ( newValue !== value ) {
// we need to interpolate again since the attribute value has been updated
// (e.g. by another directive's compile function)
// ensure unset/empty values make interpolateFn falsy
interpolateFn = newValue && $interpolate ( newValue , true , trustedContext , allOrNothing ) ;
value = newValue ;
}
2016-03-28 10:46:51 +00:00
// if attribute was updated so that there is no interpolation going on we don't want to
// register any observers
if ( ! interpolateFn ) return ;
2018-05-05 12:13:16 +02:00
// initialize attr object so that it's ready in case we need the value for isolate
// scope initialization, otherwise the value would not be available from isolate
// directive's linking fn during linking phase
2016-03-28 10:46:51 +00:00
attr [ name ] = interpolateFn ( scope ) ;
2018-05-05 12:13:16 +02:00
2016-03-28 10:46:51 +00:00
( $$observers [ name ] || ( $$observers [ name ] = [ ] ) ) . $$inter = true ;
( attr . $$observers && attr . $$observers [ name ] . $$scope || scope ) .
$watch ( interpolateFn , function interpolateFnWatchAction ( newValue , oldValue ) {
//special case for class attribute addition + removal
//so that class changes can tap into the animation
//hooks provided by the $animate service. Be sure to
//skip animations when the first digest occurs (when
//both the new and the old values are the same) since
//the CSS classes are the non-interpolated values
2018-05-05 12:13:16 +02:00
if ( name === 'class' && newValue != oldValue ) {
2016-03-28 10:46:51 +00:00
attr . $updateClass ( newValue , oldValue ) ;
} else {
attr . $set ( name , newValue ) ;
}
} ) ;
}
} ;
}
} ) ;
}
/ * *
* This is a special jqLite . replaceWith , which can replace items which
* have no parents , provided that the containing jqLite collection is provided .
*
* @ param { JqLite = } $rootElement The root of the compile tree . Used so that we can replace nodes
* in the root of the tree .
* @ param { JqLite } elementsToRemove The jqLite element which we are going to replace . We keep
* the shell , but replace its DOM node reference .
* @ param { Node } newNode The new DOM node .
* /
function replaceWith ( $rootElement , elementsToRemove , newNode ) {
var firstElementToRemove = elementsToRemove [ 0 ] ,
removeCount = elementsToRemove . length ,
parent = firstElementToRemove . parentNode ,
i , ii ;
if ( $rootElement ) {
2018-05-05 12:13:16 +02:00
for ( i = 0 , ii = $rootElement . length ; i < ii ; i ++ ) {
2016-03-28 10:46:51 +00:00
if ( $rootElement [ i ] == firstElementToRemove ) {
$rootElement [ i ++ ] = newNode ;
for ( var j = i , j2 = j + removeCount - 1 ,
jj = $rootElement . length ;
j < jj ; j ++ , j2 ++ ) {
if ( j2 < jj ) {
$rootElement [ j ] = $rootElement [ j2 ] ;
} else {
delete $rootElement [ j ] ;
}
}
$rootElement . length -= removeCount - 1 ;
2018-05-05 12:13:16 +02:00
// If the replaced element is also the jQuery .context then replace it
// .context is a deprecated jQuery api, so we should set it only when jQuery set it
// http://api.jquery.com/context/
if ( $rootElement . context === firstElementToRemove ) {
$rootElement . context = newNode ;
}
2016-03-28 10:46:51 +00:00
break ;
}
}
}
if ( parent ) {
parent . replaceChild ( newNode , firstElementToRemove ) ;
}
2018-05-05 12:13:16 +02:00
// TODO(perf): what's this document fragment for? is it needed? can we at least reuse it?
2016-03-28 10:46:51 +00:00
var fragment = document . createDocumentFragment ( ) ;
2016-04-18 12:34:29 +00:00
fragment . appendChild ( firstElementToRemove ) ;
2018-05-05 12:13:16 +02:00
// Copy over user data (that includes Angular's $scope etc.). Don't copy private
// data here because there's no public interface in jQuery to do that and copying over
// event listeners (which is the main use of private data) wouldn't work anyway.
jqLite ( newNode ) . data ( jqLite ( firstElementToRemove ) . data ( ) ) ;
// Remove data of the replaced element. We cannot just call .remove()
// on the element it since that would deallocate scope that is needed
// for the new node. Instead, remove the data "manually".
if ( ! jQuery ) {
delete jqLite . cache [ firstElementToRemove [ jqLite . expando ] ] ;
} else {
// jQuery 2.x doesn't expose the data storage. Use jQuery.cleanData to clean up after
// the replaced element. The cleanData version monkey-patched by Angular would cause
// the scope to be trashed and we do need the very same scope to work with the new
// element. However, we cannot just cache the non-patched version and use it here as
// that would break if another library patches the method after Angular does (one
// example is jQuery UI). Instead, set a flag indicating scope destroying should be
// skipped this one time.
skipDestroyOnNextJQueryCleanData = true ;
jQuery . cleanData ( [ firstElementToRemove ] ) ;
}
2016-04-18 12:34:29 +00:00
for ( var k = 1 , kk = elementsToRemove . length ; k < kk ; k ++ ) {
var element = elementsToRemove [ k ] ;
jqLite ( element ) . remove ( ) ; // must do this way to clean up expando
fragment . appendChild ( element ) ;
delete elementsToRemove [ k ] ;
2016-03-28 10:46:51 +00:00
}
elementsToRemove [ 0 ] = newNode ;
elementsToRemove . length = 1 ;
}
function cloneAndAnnotateFn ( fn , annotation ) {
return extend ( function ( ) { return fn . apply ( null , arguments ) ; } , fn , annotation ) ;
}
2018-05-05 12:13:16 +02:00
function invokeLinkFn ( linkFn , scope , $element , attrs , controllers , transcludeFn ) {
try {
linkFn ( scope , $element , attrs , controllers , transcludeFn ) ;
} catch ( e ) {
$exceptionHandler ( e , startingTag ( $element ) ) ;
}
}
2016-04-18 12:34:29 +00:00
} ] ;
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
var PREFIX _REGEXP = /^((?:x|data)[\:\-_])/i ;
2016-04-18 12:34:29 +00:00
/ * *
* Converts all accepted directives format into proper directive name .
* @ param name Name to normalize
* /
function directiveNormalize ( name ) {
return camelCase ( name . replace ( PREFIX _REGEXP , '' ) ) ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc type
* @ name $compile . directive . Attributes
2016-04-18 12:34:29 +00:00
*
* @ description
* A shared object between directive compile / linking functions which contains normalized DOM
* element attributes . The values reflect current binding state ` {{ }} ` . The normalization is
* needed since all of these are treated as equivalent in Angular :
*
2018-05-05 12:13:16 +02:00
* ` ` `
2016-04-18 12:34:29 +00:00
* < span ng : bind = "a" ng - bind = "a" data - ng - bind = "a" x - ng - bind = "a" >
2018-05-05 12:13:16 +02:00
* ` ` `
2016-04-18 12:34:29 +00:00
* /
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
/ * *
* @ ngdoc property
2018-05-05 12:13:16 +02:00
* @ name $compile . directive . Attributes # $attr
*
* @ description
* A map of DOM element attribute names to the normalized name . This is
* needed to do reverse lookup from normalized name back to actual name .
2016-04-18 12:34:29 +00:00
* /
2016-03-28 10:46:51 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $compile . directive . Attributes # $set
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Set DOM element attribute value .
*
*
* @ param { string } name Normalized element attribute name of the property to modify . The name is
* reverse - translated using the { @ link ng . $compile . directive . Attributes # $attr $attr }
* property to the original name .
* @ param { string } value Value to set the attribute to . The value can be an interpolated string .
* /
/ * *
* Closure compiler type information
* /
function nodesetLinkingFn (
/* angular.Scope */ scope ,
/* NodeList */ nodeList ,
/* Element */ rootElement ,
/* function(Function) */ boundTranscludeFn
2018-05-05 12:13:16 +02:00
) { }
2016-03-28 10:46:51 +00:00
function directiveLinkingFn (
/* nodesetLinkingFn */ nodesetLinkingFn ,
/* angular.Scope */ scope ,
/* Node */ node ,
/* Element */ rootElement ,
/* function(Function) */ boundTranscludeFn
2018-05-05 12:13:16 +02:00
) { }
2016-03-28 10:46:51 +00:00
function tokenDifference ( str1 , str2 ) {
var values = '' ,
tokens1 = str1 . split ( /\s+/ ) ,
tokens2 = str2 . split ( /\s+/ ) ;
outer :
2018-05-05 12:13:16 +02:00
for ( var i = 0 ; i < tokens1 . length ; i ++ ) {
2016-03-28 10:46:51 +00:00
var token = tokens1 [ i ] ;
2018-05-05 12:13:16 +02:00
for ( var j = 0 ; j < tokens2 . length ; j ++ ) {
if ( token == tokens2 [ j ] ) continue outer ;
2016-03-28 10:46:51 +00:00
}
values += ( values . length > 0 ? ' ' : '' ) + token ;
}
return values ;
}
2018-05-05 12:13:16 +02:00
function removeComments ( jqNodes ) {
jqNodes = jqLite ( jqNodes ) ;
var i = jqNodes . length ;
if ( i <= 1 ) {
return jqNodes ;
}
while ( i -- ) {
var node = jqNodes [ i ] ;
if ( node . nodeType === NODE _TYPE _COMMENT ) {
splice . call ( jqNodes , i , 1 ) ;
}
}
return jqNodes ;
}
var $controllerMinErr = minErr ( '$controller' ) ;
2016-03-28 10:46:51 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc provider
* @ name $controllerProvider
2016-03-28 10:46:51 +00:00
* @ description
* The { @ link ng . $controller $controller service } is used by Angular to create new
* controllers .
*
* This provider allows controller registration via the
2018-05-05 12:13:16 +02:00
* { @ link ng . $controllerProvider # register register } method .
2016-03-28 10:46:51 +00:00
* /
function $ControllerProvider ( ) {
var controllers = { } ,
2018-05-05 12:13:16 +02:00
globals = false ,
2016-04-18 12:34:29 +00:00
CNTRL _REG = /^(\S+)(\s+as\s+(\w+))?$/ ;
2016-03-28 10:46:51 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $controllerProvider # register
2016-03-28 10:46:51 +00:00
* @ param { string | Object } name Controller name , or an object map of controllers where the keys are
* the names and the values are the constructors .
* @ param { Function | Array } constructor Controller constructor fn ( optionally decorated with DI
* annotations in the array notation ) .
* /
this . register = function ( name , constructor ) {
assertNotHasOwnProperty ( name , 'controller' ) ;
if ( isObject ( name ) ) {
extend ( controllers , name ) ;
} else {
controllers [ name ] = constructor ;
}
} ;
2018-05-05 12:13:16 +02:00
/ * *
* @ ngdoc method
* @ name $controllerProvider # allowGlobals
* @ description If called , allows ` $ controller ` to find controller constructors on ` window `
* /
this . allowGlobals = function ( ) {
globals = true ;
} ;
2016-03-28 10:46:51 +00:00
this . $get = [ '$injector' , '$window' , function ( $injector , $window ) {
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc service
* @ name $controller
2016-03-28 10:46:51 +00:00
* @ requires $injector
*
* @ param { Function | string } constructor If called with a function then it ' s considered to be the
* controller constructor function . Otherwise it ' s considered to be a string which is used
* to retrieve the controller constructor using the following steps :
*
* * check if a controller with given name is registered via ` $ controllerProvider `
* * check if evaluating the string on the current scope returns a constructor
2018-05-05 12:13:16 +02:00
* * if $controllerProvider # allowGlobals , check ` window[constructor] ` on the global
* ` window ` object ( not recommended )
*
* The string can use the ` controller as property ` syntax , where the controller instance is published
* as the specified property on the ` scope ` ; the ` scope ` must be injected into ` locals ` param for this
* to work correctly .
2016-03-28 10:46:51 +00:00
*
* @ param { Object } locals Injection locals for Controller .
* @ return { Object } Instance of given controller .
*
* @ description
* ` $ controller ` service is responsible for instantiating controllers .
*
2018-05-05 12:13:16 +02:00
* It ' s just a simple call to { @ link auto . $injector $injector } , but extracted into
* a service , so that one can override this service with [ BC version ] ( https : //gist.github.com/1649788).
2016-03-28 10:46:51 +00:00
* /
2018-05-05 12:13:16 +02:00
return function ( expression , locals , later , ident ) {
// PRIVATE API:
// param `later` --- indicates that the controller's constructor is invoked at a later time.
// If true, $controller will allocate the object with the correct
// prototype chain, but will not invoke the controller until a returned
// callback is invoked.
// param `ident` --- An optional label which overrides the label parsed from the controller
// expression, if any.
2016-03-28 10:46:51 +00:00
var instance , match , constructor , identifier ;
2018-05-05 12:13:16 +02:00
later = later === true ;
if ( ident && isString ( ident ) ) {
identifier = ident ;
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
if ( isString ( expression ) ) {
match = expression . match ( CNTRL _REG ) ;
if ( ! match ) {
throw $controllerMinErr ( 'ctrlfmt' ,
"Badly formed controller string '{0}'. " +
"Must match `__name__ as __id__` or `__name__`." , expression ) ;
}
2016-03-28 10:46:51 +00:00
constructor = match [ 1 ] ,
2018-05-05 12:13:16 +02:00
identifier = identifier || match [ 3 ] ;
2016-03-28 10:46:51 +00:00
expression = controllers . hasOwnProperty ( constructor )
? controllers [ constructor ]
2018-05-05 12:13:16 +02:00
: getter ( locals . $scope , constructor , true ) ||
( globals ? getter ( $window , constructor , true ) : undefined ) ;
2016-03-28 10:46:51 +00:00
assertArgFn ( expression , constructor , true ) ;
}
2018-05-05 12:13:16 +02:00
if ( later ) {
// Instantiate controller later:
// This machinery is used to create an instance of the object before calling the
// controller's constructor itself.
//
// This allows properties to be added to the controller before the constructor is
// invoked. Primarily, this is used for isolate scope bindings in $compile.
//
// This feature is not intended for use by applications, and is thus not documented
// publicly.
// Object creation: http://jsperf.com/create-constructor/2
var controllerPrototype = ( isArray ( expression ) ?
expression [ expression . length - 1 ] : expression ) . prototype ;
instance = Object . create ( controllerPrototype || null ) ;
if ( identifier ) {
addIdentifier ( locals , identifier , instance , constructor || expression . name ) ;
2016-04-18 12:34:29 +00:00
}
2018-05-05 12:13:16 +02:00
return extend ( function ( ) {
$injector . invoke ( expression , instance , locals , constructor ) ;
return instance ;
} , {
instance : instance ,
identifier : identifier
} ) ;
}
instance = $injector . instantiate ( expression , locals , constructor ) ;
if ( identifier ) {
addIdentifier ( locals , identifier , instance , constructor || expression . name ) ;
2016-03-28 10:46:51 +00:00
}
return instance ;
} ;
2018-05-05 12:13:16 +02:00
function addIdentifier ( locals , identifier , instance , name ) {
if ( ! ( locals && isObject ( locals . $scope ) ) ) {
throw minErr ( '$controller' ) ( 'noscp' ,
"Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`." ,
name , identifier ) ;
}
locals . $scope [ identifier ] = instance ;
}
2016-03-28 10:46:51 +00:00
} ] ;
}
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc service
* @ name $document
2016-03-28 10:46:51 +00:00
* @ requires $window
*
* @ description
* A { @ link angular . element jQuery or jqLite } wrapper for the browser ' s ` window.document ` object .
2018-05-05 12:13:16 +02:00
*
* @ example
< example module = "documentExample" >
< file name = "index.html" >
< div ng - controller = "ExampleController" >
< p > $document title : < b ng - bind = "title" > < / b > < / p >
< p > window . document title : < b ng - bind = "windowTitle" > < / b > < / p >
< / d i v >
< / f i l e >
< file name = "script.js" >
angular . module ( 'documentExample' , [ ] )
. controller ( 'ExampleController' , [ '$scope' , '$document' , function ( $scope , $document ) {
$scope . title = $document [ 0 ] . title ;
$scope . windowTitle = angular . element ( window . document ) [ 0 ] . title ;
} ] ) ;
< / f i l e >
< / e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
2018-05-05 12:13:16 +02:00
function $DocumentProvider ( ) {
this . $get = [ '$window' , function ( window ) {
2016-03-28 10:46:51 +00:00
return jqLite ( window . document ) ;
} ] ;
}
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc service
* @ name $exceptionHandler
* @ requires ng . $log
2016-03-28 10:46:51 +00:00
*
* @ description
* Any uncaught exception in angular expressions is delegated to this service .
* The default implementation simply delegates to ` $ log.error ` which logs it into
* the browser console .
2016-05-18 00:10:50 +00:00
*
2016-03-28 10:46:51 +00:00
* In unit tests , if ` angular-mocks.js ` is loaded , this service is overridden by
* { @ link ngMock . $exceptionHandler mock $exceptionHandler } which aids in testing .
*
* # # Example :
2016-05-18 00:10:50 +00:00
*
2018-05-05 12:13:16 +02:00
* ` ` ` js
* angular . module ( 'exceptionOverride' , [ ] ) . factory ( '$exceptionHandler' , function ( ) {
* return function ( exception , cause ) {
2016-03-28 10:46:51 +00:00
* exception . message += ' (caused by "' + cause + '")' ;
* throw exception ;
* } ;
* } ) ;
2018-05-05 12:13:16 +02:00
* ` ` `
2016-05-18 00:10:50 +00:00
*
2016-03-28 10:46:51 +00:00
* This example will override the normal action of ` $ exceptionHandler ` , to make angular
* exceptions fail hard when they happen , instead of just logging to the console .
*
2018-05-05 12:13:16 +02:00
* < hr / >
* Note , that code executed in event - listeners ( even those registered using jqLite ' s ` on ` / ` bind `
* methods ) does not delegate exceptions to the { @ link ng . $exceptionHandler $exceptionHandler }
* ( unless executed during a digest ) .
*
* If you wish , you can manually delegate exceptions , e . g .
* ` try { ... } catch(e) { $ exceptionHandler(e); } `
*
2016-03-28 10:46:51 +00:00
* @ param { Error } exception Exception associated with the error .
* @ param { string = } cause optional information about the context in which
* the error was thrown .
*
* /
function $ExceptionHandlerProvider ( ) {
this . $get = [ '$log' , function ( $log ) {
return function ( exception , cause ) {
$log . error . apply ( $log , arguments ) ;
} ;
} ] ;
}
2018-05-05 12:13:16 +02:00
var APPLICATION _JSON = 'application/json' ;
var CONTENT _TYPE _APPLICATION _JSON = { 'Content-Type' : APPLICATION _JSON + ';charset=utf-8' } ;
var JSON _START = /^\[|^\{(?!\{)/ ;
var JSON _ENDS = {
'[' : /]$/ ,
'{' : /}$/
} ;
var JSON _PROTECTION _PREFIX = /^\)\]\}',?\n/ ;
function defaultHttpResponseTransform ( data , headers ) {
if ( isString ( data ) ) {
// Strip json vulnerability protection prefix and trim whitespace
var tempData = data . replace ( JSON _PROTECTION _PREFIX , '' ) . trim ( ) ;
if ( tempData ) {
var contentType = headers ( 'Content-Type' ) ;
if ( ( contentType && ( contentType . indexOf ( APPLICATION _JSON ) === 0 ) ) || isJsonLike ( tempData ) ) {
data = fromJson ( tempData ) ;
}
}
}
return data ;
}
function isJsonLike ( str ) {
var jsonStart = str . match ( JSON _START ) ;
return jsonStart && JSON _ENDS [ jsonStart [ 0 ] ] . test ( str ) ;
}
2016-03-28 10:46:51 +00:00
/ * *
* Parse headers into key value object
*
* @ param { string } headers Raw headers as a string
* @ returns { Object } Parsed headers as key value object
* /
function parseHeaders ( headers ) {
2018-05-05 12:13:16 +02:00
var parsed = createMap ( ) , key , val , i ;
2016-04-18 12:34:29 +00:00
if ( ! headers ) return parsed ;
forEach ( headers . split ( '\n' ) , function ( line ) {
i = line . indexOf ( ':' ) ;
key = lowercase ( trim ( line . substr ( 0 , i ) ) ) ;
val = trim ( line . substr ( i + 1 ) ) ;
2016-03-28 10:46:51 +00:00
if ( key ) {
2018-05-05 12:13:16 +02:00
parsed [ key ] = parsed [ key ] ? parsed [ key ] + ', ' + val : val ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
} ) ;
2016-03-28 10:46:51 +00:00
return parsed ;
}
/ * *
* Returns a function that provides access to parsed headers .
*
* Headers are lazy parsed when first requested .
* @ see parseHeaders
*
* @ param { ( string | Object ) } headers Headers to provide access to .
* @ returns { function ( string = ) } Returns a getter function which if called with :
*
* - if called with single an argument returns a single header value or null
* - if called with no arguments returns an object containing all headers .
* /
function headersGetter ( headers ) {
2016-04-18 12:34:29 +00:00
var headersObj = isObject ( headers ) ? headers : undefined ;
2016-03-28 10:46:51 +00:00
return function ( name ) {
if ( ! headersObj ) headersObj = parseHeaders ( headers ) ;
if ( name ) {
2018-05-05 12:13:16 +02:00
var value = headersObj [ lowercase ( name ) ] ;
if ( value === void 0 ) {
value = null ;
}
return value ;
2016-03-28 10:46:51 +00:00
}
return headersObj ;
} ;
}
/ * *
* Chain all given functions
*
* This function is used for both request and response transforming
*
* @ param { * } data Data to transform .
2018-05-05 12:13:16 +02:00
* @ param { function ( string = ) } headers HTTP headers getter fn .
* @ param { number } status HTTP status code of the response .
* @ param { ( Function | Array . < Function > ) } fns Function or an array of functions .
2016-03-28 10:46:51 +00:00
* @ returns { * } Transformed data .
* /
2018-05-05 12:13:16 +02:00
function transformData ( data , headers , status , fns ) {
2016-04-18 12:34:29 +00:00
if ( isFunction ( fns ) )
2018-05-05 12:13:16 +02:00
return fns ( data , headers , status ) ;
2016-03-28 10:46:51 +00:00
forEach ( fns , function ( fn ) {
2018-05-05 12:13:16 +02:00
data = fn ( data , headers , status ) ;
2016-03-28 10:46:51 +00:00
} ) ;
return data ;
}
function isSuccess ( status ) {
return 200 <= status && status < 300 ;
}
2018-05-05 12:13:16 +02:00
/ * *
* @ ngdoc provider
* @ name $httpProvider
* @ description
* Use ` $ httpProvider ` to change the default behavior of the { @ link ng . $http $http } service .
* * /
2016-03-28 10:46:51 +00:00
function $HttpProvider ( ) {
2018-05-05 12:13:16 +02:00
/ * *
* @ ngdoc property
* @ name $httpProvider # defaults
* @ description
*
* Object containing default values for all { @ link ng . $http $http } requests .
*
* - * * ` defaults.cache ` * * - { Object } - an object built with { @ link ng . $cacheFactory ` $ cacheFactory ` }
* that will provide the cache for all requests who set their ` cache ` property to ` true ` .
* If you set the ` default.cache = false ` then only requests that specify their own custom
* cache object will be cached . See { @ link $http # caching $http Caching } for more information .
*
* - * * ` defaults.xsrfCookieName ` * * - { string } - Name of cookie containing the XSRF token .
* Defaults value is ` 'XSRF-TOKEN' ` .
*
* - * * ` defaults.xsrfHeaderName ` * * - { string } - Name of HTTP header to populate with the
* XSRF token . Defaults value is ` 'X-XSRF-TOKEN' ` .
*
* - * * ` defaults.headers ` * * - { Object } - Default headers for all $http requests .
* Refer to { @ link ng . $http # setting - http - headers $http } for documentation on
* setting default headers .
* - * * ` defaults.headers.common ` * *
* - * * ` defaults.headers.post ` * *
* - * * ` defaults.headers.put ` * *
* - * * ` defaults.headers.patch ` * *
*
* * /
2016-03-28 10:46:51 +00:00
var defaults = this . defaults = {
// transform incoming response data
2018-05-05 12:13:16 +02:00
transformResponse : [ defaultHttpResponseTransform ] ,
2016-03-28 10:46:51 +00:00
// transform outgoing request data
transformRequest : [ function ( d ) {
2018-05-05 12:13:16 +02:00
return isObject ( d ) && ! isFile ( d ) && ! isBlob ( d ) && ! isFormData ( d ) ? toJson ( d ) : d ;
2016-03-28 10:46:51 +00:00
} ] ,
// default headers
headers : {
common : {
'Accept' : 'application/json, text/plain, */*'
} ,
2018-05-05 12:13:16 +02:00
post : shallowCopy ( CONTENT _TYPE _APPLICATION _JSON ) ,
put : shallowCopy ( CONTENT _TYPE _APPLICATION _JSON ) ,
patch : shallowCopy ( CONTENT _TYPE _APPLICATION _JSON )
2016-03-28 10:46:51 +00:00
} ,
xsrfCookieName : 'XSRF-TOKEN' ,
2016-04-18 12:34:29 +00:00
xsrfHeaderName : 'X-XSRF-TOKEN'
2016-03-28 10:46:51 +00:00
} ;
2018-05-05 12:13:16 +02:00
var useApplyAsync = false ;
2016-03-28 10:46:51 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $httpProvider # useApplyAsync
* @ description
*
* Configure $http service to combine processing of multiple http responses received at around
* the same time via { @ link ng . $rootScope . Scope # $applyAsync $rootScope . $applyAsync } . This can result in
* significant performance improvement for bigger applications that make many HTTP requests
* concurrently ( common during application bootstrap ) .
*
* Defaults to false . If no value is specifed , returns the current configured value .
*
* @ param { boolean = } value If true , when requests are loaded , they will schedule a deferred
* "apply" on the next tick , giving time for subsequent requests in a roughly ~ 10 ms window
* to load and share the same digest cycle .
*
* @ returns { boolean | Object } If a value is specified , returns the $httpProvider for chaining .
* otherwise , returns the current configured value .
* * /
this . useApplyAsync = function ( value ) {
if ( isDefined ( value ) ) {
useApplyAsync = ! ! value ;
return this ;
}
return useApplyAsync ;
} ;
2016-03-28 10:46:51 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc property
* @ name $httpProvider # interceptors
* @ description
*
* Array containing service factories for all synchronous or asynchronous { @ link ng . $http $http }
* pre - processing of request or postprocessing of responses .
*
* These service factories are ordered by request , i . e . they are applied in the same order as the
* array , on request , but reverse order , on response .
*
* { @ link ng . $http # interceptors Interceptors detailed info }
* * /
var interceptorFactories = this . interceptors = [ ] ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
this . $get = [ '$httpBackend' , '$browser' , '$cacheFactory' , '$rootScope' , '$q' , '$injector' ,
function ( $httpBackend , $browser , $cacheFactory , $rootScope , $q , $injector ) {
2016-03-28 10:46:51 +00:00
var defaultCache = $cacheFactory ( '$http' ) ;
/ * *
* Interceptors stored in reverse order . Inner interceptors before outer interceptors .
* The reversal is needed so that we can build up the interception chain around the
* server request .
* /
var reversedInterceptors = [ ] ;
forEach ( interceptorFactories , function ( interceptorFactory ) {
reversedInterceptors . unshift ( isString ( interceptorFactory )
? $injector . get ( interceptorFactory ) : $injector . invoke ( interceptorFactory ) ) ;
} ) ;
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc service
* @ kind function
* @ name $http
* @ requires ng . $httpBackend
2016-03-28 10:46:51 +00:00
* @ requires $cacheFactory
* @ requires $rootScope
* @ requires $q
* @ requires $injector
*
* @ description
* The ` $ http ` service is a core Angular service that facilitates communication with the remote
2018-05-05 12:13:16 +02:00
* HTTP servers via the browser ' s [ XMLHttpRequest ] ( https : //developer.mozilla.org/en/xmlhttprequest)
* object or via [ JSONP ] ( http : //en.wikipedia.org/wiki/JSONP).
2016-03-28 10:46:51 +00:00
*
* For unit testing applications that use ` $ http ` service , see
* { @ link ngMock . $httpBackend $httpBackend mock } .
*
* For a higher level of abstraction , please check out the { @ link ngResource . $resource
* $resource } service .
*
* The $http API is based on the { @ link ng . $q deferred / promise APIs } exposed by
* the $q service . While for simple usage patterns this doesn ' t matter much , for advanced usage
* it is important to familiarize yourself with these APIs and the guarantees they provide .
*
*
2018-05-05 12:13:16 +02:00
* # # General usage
2016-04-18 12:34:29 +00:00
* The ` $ http ` service is a function which takes a single argument — a configuration object —
* that is used to generate an HTTP request and returns a { @ link ng . $q promise }
* with two $http specific methods : ` success ` and ` error ` .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* ` ` ` js
* // Simple GET request example :
* $http . get ( '/someUrl' ) .
* success ( function ( data , status , headers , config ) {
* // this callback will be called asynchronously
* // when the response is available
* } ) .
* error ( function ( data , status , headers , config ) {
* // called asynchronously if an error occurs
* // or server returns response with an error status.
* } ) ;
* ` ` `
*
* ` ` ` js
* // Simple POST request example (passing data) :
* $http . post ( '/someUrl' , { msg : 'hello word!' } ) .
2016-04-18 12:34:29 +00:00
* success ( function ( data , status , headers , config ) {
2016-03-28 10:46:51 +00:00
* // this callback will be called asynchronously
* // when the response is available
2016-04-18 12:34:29 +00:00
* } ) .
* error ( function ( data , status , headers , config ) {
2016-03-28 10:46:51 +00:00
* // called asynchronously if an error occurs
* // or server returns response with an error status.
* } ) ;
2018-05-05 12:13:16 +02:00
* ` ` `
*
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* Since the returned value of calling the $http function is a ` promise ` , you can also use
* the ` then ` method to register callbacks , and these callbacks will receive a single argument –
* an object representing the response . See the API signature and type info below for more
* details .
2016-03-28 10:46:51 +00:00
*
* A response status code between 200 and 299 is considered a success status and
* will result in the success callback being called . Note that if the response is a redirect ,
* XMLHttpRequest will transparently follow it , meaning that the error callback will not be
* called for such responses .
*
2018-05-05 12:13:16 +02:00
* # # Writing Unit Tests that use $http
* When unit testing ( using { @ link ngMock ngMock } ) , it is necessary to call
* { @ link ngMock . $httpBackend # flush $httpBackend . flush ( ) } to flush each pending
* request using trained responses .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* ` ` `
* $httpBackend . expectGET ( ... ) ;
2018-05-05 12:13:16 +02:00
* $http . get ( ... ) ;
2016-04-18 12:34:29 +00:00
* $httpBackend . flush ( ) ;
2016-03-28 10:46:51 +00:00
* ` ` `
*
2018-05-05 12:13:16 +02:00
* # # Shortcut methods
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* Shortcut methods are also available . All shortcut methods require passing in the URL , and
* request data must be passed in for POST / PUT requests .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-04-18 12:34:29 +00:00
* $http . get ( '/someUrl' ) . success ( successCallback ) ;
* $http . post ( '/someUrl' , data ) . success ( successCallback ) ;
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* Complete list of shortcut methods :
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* - { @ link ng . $http # get $http . get }
* - { @ link ng . $http # head $http . head }
* - { @ link ng . $http # post $http . post }
* - { @ link ng . $http # put $http . put }
* - { @ link ng . $http # delete $http . delete }
* - { @ link ng . $http # jsonp $http . jsonp }
* - { @ link ng . $http # patch $http . patch }
2016-03-28 10:46:51 +00:00
*
*
2018-05-05 12:13:16 +02:00
* # # Setting HTTP Headers
2016-03-28 10:46:51 +00:00
*
* The $http service will automatically add certain HTTP headers to all requests . These defaults
* can be fully configured by accessing the ` $ httpProvider.defaults.headers ` configuration
* object , which currently contains this default configuration :
*
* - ` $ httpProvider.defaults.headers.common ` ( headers that are common for all requests ) :
* - ` Accept: application/json, text/plain, * / * `
* - ` $ httpProvider.defaults.headers.post ` : ( header defaults for POST requests )
* - ` Content-Type: application/json `
* - ` $ httpProvider.defaults.headers.put ` ( header defaults for PUT requests )
* - ` Content-Type: application/json `
*
* To add or overwrite these defaults , simply add or remove a property from these configuration
* objects . To add headers for an HTTP method other than POST or PUT , simply add a new object
* with the lowercased HTTP method name as the key , e . g .
2016-04-18 12:34:29 +00:00
* ` $ httpProvider.defaults.headers.get = { 'My-Header' : 'value' }.
2016-03-28 10:46:51 +00:00
*
* The defaults can also be set at runtime via the ` $ http.defaults ` object in the same
* fashion . For example :
*
* ` ` `
* module . run ( function ( $http ) {
2018-05-05 12:13:16 +02:00
* $http . defaults . headers . common . Authorization = 'Basic YmVlcDpib29w'
2016-03-28 10:46:51 +00:00
* } ) ;
* ` ` `
*
* In addition , you can supply a ` headers ` property in the config object passed when
* calling ` $ http(config) ` , which overrides the defaults without changing them globally .
*
2018-05-05 12:13:16 +02:00
* To explicitly remove a header automatically added via $httpProvider . defaults . headers on a per request basis ,
* Use the ` headers ` property , setting the desired header to ` undefined ` . For example :
*
* ` ` ` js
* var req = {
* method : 'POST' ,
* url : 'http://example.com' ,
* headers : {
* 'Content-Type' : undefined
* } ,
* data : { test : 'test' }
* }
*
* $http ( req ) . success ( function ( ) { ... } ) . error ( function ( ) { ... } ) ;
* ` ` `
*
* # # Transforming Requests and Responses
*
* Both requests and responses can be transformed using transformation functions : ` transformRequest `
* and ` transformResponse ` . These properties can be a single function that returns
* the transformed value ( ` function(data, headersGetter, status) ` ) or an array of such transformation functions ,
* which allows you to ` push ` or ` unshift ` a new transformation function into the transformation chain .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* # # # Default Transformations
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* The ` $ httpProvider ` provider and ` $ http ` service expose ` defaults.transformRequest ` and
* ` defaults.transformResponse ` properties . If a request does not provide its own transformations
* then these will be applied .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* You can augment or replace the default transformations by modifying these properties by adding to or
* replacing the array .
*
* Angular provides the following default transformations :
*
* Request transformations ( ` $ httpProvider.defaults.transformRequest ` and ` $ http.defaults.transformRequest ` ) :
2016-03-28 10:46:51 +00:00
*
* - If the ` data ` property of the request configuration object contains an object , serialize it
* into JSON format .
*
2018-05-05 12:13:16 +02:00
* Response transformations ( ` $ httpProvider.defaults.transformResponse ` and ` $ http.defaults.transformResponse ` ) :
2016-03-28 10:46:51 +00:00
*
* - If XSRF prefix is detected , strip it ( see Security Considerations section below ) .
* - If JSON response is detected , deserialize it using a JSON parser .
*
*
2018-05-05 12:13:16 +02:00
* # # # Overriding the Default Transformations Per Request
*
* If you wish override the request / response transformations only for a single request then provide
* ` transformRequest ` and / or ` transformResponse ` properties on the configuration object passed
2016-03-28 10:46:51 +00:00
* into ` $ http ` .
*
2018-05-05 12:13:16 +02:00
* Note that if you provide these properties on the config object the default transformations will be
* overwritten . If you wish to augment the default transformations then you must include them in your
* local transformation array .
*
* The following code demonstrates adding a new response transformation to be run after the default response
* transformations have been run .
*
* ` ` ` js
* function appendTransform ( defaults , transform ) {
*
* // We can't guarantee that the default transformation is an array
* defaults = angular . isArray ( defaults ) ? defaults : [ defaults ] ;
*
* // Append the new transformation to the defaults
* return defaults . concat ( transform ) ;
* }
*
* $http ( {
* url : '...' ,
* method : 'GET' ,
* transformResponse : appendTransform ( $http . defaults . transformResponse , function ( value ) {
* return doTransform ( value ) ;
* } )
* } ) ;
* ` ` `
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
*
* # # Caching
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* To enable caching , set the request configuration ` cache ` property to ` true ` ( to use default
* cache ) or to a custom cache object ( built with { @ link ng . $cacheFactory ` $ cacheFactory ` } ) .
* When the cache is enabled , ` $ http ` stores the response from the server in the specified
* cache . The next time the same request is made , the response is served from the cache without
* sending a request to the server .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* Note that even if the response is served from cache , delivery of the data is asynchronous in
* the same way that real requests are .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* If there are multiple GET requests for the same URL that should be cached using the same
* cache , but the cache is not populated yet , only one request to the server will be made and
* the remaining requests will be fulfilled using the response from the first request .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* You can change the default cache to a new object ( built with
* { @ link ng . $cacheFactory ` $ cacheFactory ` } ) by updating the
2018-05-05 12:13:16 +02:00
* { @ link ng . $http # defaults ` $ http.defaults.cache ` } property . All requests who set
2016-04-18 12:34:29 +00:00
* their ` cache ` property to ` true ` will now use this cache object .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* If you set the default cache to ` false ` then only requests that specify their own custom
* cache object will be cached .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* # # Interceptors
2016-03-28 10:46:51 +00:00
*
* Before you start creating interceptors , be sure to understand the
* { @ link ng . $q $q and deferred / promise APIs } .
*
* For purposes of global error handling , authentication , or any kind of synchronous or
* asynchronous pre - processing of request or postprocessing of responses , it is desirable to be
* able to intercept requests before they are handed to the server and
* responses before they are handed over to the application code that
* initiated these requests . The interceptors leverage the { @ link ng . $q
* promise APIs } to fulfill this need for both synchronous and asynchronous pre - processing .
*
* The interceptors are service factories that are registered with the ` $ httpProvider ` by
* adding them to the ` $ httpProvider.interceptors ` array . The factory is called and
* injected with dependencies ( if specified ) and returns the interceptor .
*
* There are two kinds of interceptors ( and two kinds of rejection interceptors ) :
*
2018-05-05 12:13:16 +02:00
* * ` request ` : interceptors get called with a http ` config ` object . The function is free to
* modify the ` config ` object or create a new one . The function needs to return the ` config `
* object directly , or a promise containing the ` config ` or a new ` config ` object .
2016-03-28 10:46:51 +00:00
* * ` requestError ` : interceptor gets called when a previous interceptor threw an error or
* resolved with a rejection .
* * ` response ` : interceptors get called with http ` response ` object . The function is free to
2018-05-05 12:13:16 +02:00
* modify the ` response ` object or create a new one . The function needs to return the ` response `
* object directly , or as a promise containing the ` response ` or a new ` response ` object .
2016-03-28 10:46:51 +00:00
* * ` responseError ` : interceptor gets called when a previous interceptor threw an error or
* resolved with a rejection .
*
*
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
* // register the interceptor as a service
* $provide . factory ( 'myHttpInterceptor' , function ( $q , dependency1 , dependency2 ) {
* return {
* // optional method
* 'request' : function ( config ) {
* // do something on success
2018-05-05 12:13:16 +02:00
* return config ;
2016-03-28 10:46:51 +00:00
* } ,
*
* // optional method
* 'requestError' : function ( rejection ) {
* // do something on error
* if ( canRecover ( rejection ) ) {
* return responseOrNewPromise
* }
* return $q . reject ( rejection ) ;
* } ,
*
*
*
* // optional method
* 'response' : function ( response ) {
* // do something on success
2018-05-05 12:13:16 +02:00
* return response ;
2016-03-28 10:46:51 +00:00
* } ,
*
* // optional method
* 'responseError' : function ( rejection ) {
* // do something on error
* if ( canRecover ( rejection ) ) {
* return responseOrNewPromise
* }
* return $q . reject ( rejection ) ;
* }
* } ;
* } ) ;
*
* $httpProvider . interceptors . push ( 'myHttpInterceptor' ) ;
*
*
* // alternatively, register the interceptor via an anonymous factory
* $httpProvider . interceptors . push ( function ( $q , dependency1 , dependency2 ) {
* return {
* 'request' : function ( config ) {
* // same as above
* } ,
*
* 'response' : function ( response ) {
* // same as above
* }
* } ;
* } ) ;
2018-05-05 12:13:16 +02:00
* ` ` `
2016-04-18 12:34:29 +00:00
*
2018-05-05 12:13:16 +02:00
* # # Security Considerations
2016-03-28 10:46:51 +00:00
*
* When designing web applications , consider security threats from :
*
2018-05-05 12:13:16 +02:00
* - [ JSON vulnerability ] ( http : //haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx)
* - [ XSRF ] ( http : //en.wikipedia.org/wiki/Cross-site_request_forgery)
2016-03-28 10:46:51 +00:00
*
* Both server and the client must cooperate in order to eliminate these threats . Angular comes
* pre - configured with strategies that address these issues , but for this to work backend server
* cooperation is required .
*
2018-05-05 12:13:16 +02:00
* # # # JSON Vulnerability Protection
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* A [ JSON vulnerability ] ( http : //haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx)
* allows third party website to turn your JSON resource URL into
* [ JSONP ] ( http : //en.wikipedia.org/wiki/JSONP) request under some conditions. To
2016-03-28 10:46:51 +00:00
* counter this your server can prefix all JSON requests with following string ` ")]}', \n " ` .
* Angular will automatically strip the prefix before processing it as JSON .
*
* For example if your server needs to return :
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
* [ 'one' , 'two' ]
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
* which is vulnerable to attack , your server can return :
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
* ) ] } ' ,
* [ 'one' , 'two' ]
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
* Angular will strip the prefix , before processing the JSON .
*
*
2018-05-05 12:13:16 +02:00
* # # # Cross Site Request Forgery ( XSRF ) Protection
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* [ XSRF ] ( http : //en.wikipedia.org/wiki/Cross-site_request_forgery) is a technique by which
2016-04-18 12:34:29 +00:00
* an unauthorized site can gain your user ' s private data . Angular provides a mechanism
* to counter XSRF . When performing XHR requests , the $http service reads a token from a cookie
* ( by default , ` XSRF-TOKEN ` ) and sets it as an HTTP header ( ` X-XSRF-TOKEN ` ) . Since only
* JavaScript that runs on your domain could read the cookie , your server can be assured that
* the XHR came from JavaScript running on your domain . The header will not be set for
* cross - domain requests .
2016-03-28 10:46:51 +00:00
*
* To take advantage of this , your server needs to set a token in a JavaScript readable session
* cookie called ` XSRF-TOKEN ` on the first HTTP GET request . On subsequent XHR requests the
* server can verify that the cookie matches ` X-XSRF-TOKEN ` HTTP header , and therefore be sure
* that only JavaScript running on your domain could have sent the request . The token must be
* unique for each user and must be verifiable by the server ( to prevent the JavaScript from
* making up its own tokens ) . We recommend that the token is a digest of your site ' s
2018-05-05 12:13:16 +02:00
* authentication cookie with a [ salt ] ( https : //en.wikipedia.org/wiki/Salt_(cryptography))
2016-03-28 10:46:51 +00:00
* for added security .
*
* The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName
* properties of either $httpProvider . defaults at config - time , $http . defaults at run - time ,
* or the per - request config object .
*
*
* @ param { object } config Object describing the request to be made and how it should be
* processed . The object has following properties :
*
* - * * method * * – ` {string} ` – HTTP method ( e . g . 'GET' , 'POST' , etc )
* - * * url * * – ` {string} ` – Absolute or relative URL of the resource that is being requested .
2016-04-18 12:34:29 +00:00
* - * * params * * – ` {Object.<string|Object>} ` – Map of strings or objects which will be turned
* to ` ?key1=value1&key2=value2 ` after the url . If the value is not a string , it will be
* JSONified .
2016-03-28 10:46:51 +00:00
* - * * data * * – ` {string|Object} ` – Data to be sent as the request message data .
* - * * headers * * – ` {Object} ` – Map of strings or functions which return strings representing
* HTTP headers to send to the server . If the return value of a function is null , the
2016-04-18 12:34:29 +00:00
* header will not be sent .
2016-03-28 10:46:51 +00:00
* - * * xsrfHeaderName * * – ` {string} ` – Name of HTTP header to populate with the XSRF token .
* - * * xsrfCookieName * * – ` {string} ` – Name of cookie containing the XSRF token .
* - * * transformRequest * * –
* ` {function(data, headersGetter)|Array.<function(data, headersGetter)>} ` –
* transform function or an array of such functions . The transform function takes the http
* request body and headers and returns its transformed ( typically serialized ) version .
2018-05-05 12:13:16 +02:00
* See { @ link ng . $http # overriding - the - default - transformations - per - request
* Overriding the Default Transformations }
2016-03-28 10:46:51 +00:00
* - * * transformResponse * * –
2018-05-05 12:13:16 +02:00
* ` {function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>} ` –
2016-03-28 10:46:51 +00:00
* transform function or an array of such functions . The transform function takes the http
2018-05-05 12:13:16 +02:00
* response body , headers and status and returns its transformed ( typically deserialized ) version .
* See { @ link ng . $http # overriding - the - default - transformations - per - request
* Overriding the Default Transformations }
2016-04-18 12:34:29 +00:00
* - * * cache * * – ` {boolean|Cache} ` – If true , a default $http cache will be used to cache the
* GET request , otherwise if a cache instance built with
* { @ link ng . $cacheFactory $cacheFactory } , this cache will be used for
* caching .
2016-03-28 10:46:51 +00:00
* - * * timeout * * – ` {number|Promise} ` – timeout in milliseconds , or { @ link ng . $q promise }
* that should abort the request when resolved .
2018-05-05 12:13:16 +02:00
* - * * withCredentials * * - ` {boolean} ` - whether to set the ` withCredentials ` flag on the
* XHR object . See [ requests with credentials ] ( https : //developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials)
* for more information .
* - * * responseType * * - ` {string} ` - see
* [ requestType ] ( https : //developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType).
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ returns { HttpPromise } Returns a { @ link ng . $q promise } object with the
* standard ` then ` method and two http specific methods : ` success ` and ` error ` . The ` then `
* method takes two arguments a success and an error callback which will be called with a
* response object . The ` success ` and ` error ` methods take a single argument - a function that
* will be called when the request succeeds or fails respectively . The arguments passed into
* these functions are destructured representation of the response object passed into the
* ` then ` method . The response object has these properties :
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* - * * data * * – ` {string|Object} ` – The response body transformed with the transform
* functions .
* - * * status * * – ` {number} ` – HTTP status code of the response .
* - * * headers * * – ` {function([headerName])} ` – Header getter function .
* - * * config * * – ` {Object} ` – The configuration object that was used to generate the request .
2018-05-05 12:13:16 +02:00
* - * * statusText * * – ` {string} ` – HTTP status text of the response .
2016-03-28 10:46:51 +00:00
*
* @ property { Array . < Object > } pendingRequests Array of config objects for currently pending
* requests . This is primarily meant to be used for debugging purposes .
*
*
* @ example
2018-05-05 12:13:16 +02:00
< example module = "httpExample" >
2016-03-28 10:46:51 +00:00
< file name = "index.html" >
2018-05-05 12:13:16 +02:00
< div ng - controller = "FetchController" >
2016-04-18 12:34:29 +00:00
< select ng - model = "method" >
2016-03-28 10:46:51 +00:00
< option > GET < / o p t i o n >
< option > JSONP < / o p t i o n >
< / s e l e c t >
2016-04-18 12:34:29 +00:00
< input type = "text" ng - model = "url" size = "80" / >
2018-05-05 12:13:16 +02:00
< button id = "fetchbtn" ng - click = "fetch()" > fetch < / b u t t o n > < b r >
< button id = "samplegetbtn" ng - click = "updateModel('GET', 'http-hello.html')" > Sample GET < / b u t t o n >
< button id = "samplejsonpbtn"
2016-03-28 10:46:51 +00:00
ng - click = " updateModel ( 'JSONP' ,
2018-05-05 12:13:16 +02:00
'https://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero' ) " >
2016-03-28 10:46:51 +00:00
Sample JSONP
< / b u t t o n >
2018-05-05 12:13:16 +02:00
< button id = "invalidjsonpbtn"
ng - click = "updateModel('JSONP', 'https://angularjs.org/doesntexist&callback=JSON_CALLBACK')" >
2016-03-28 10:46:51 +00:00
Invalid JSONP
< / b u t t o n >
< pre > http status code : { { status } } < / p r e >
< pre > http response data : { { data } } < / p r e >
< / d i v >
< / f i l e >
< file name = "script.js" >
2018-05-05 12:13:16 +02:00
angular . module ( 'httpExample' , [ ] )
. controller ( 'FetchController' , [ '$scope' , '$http' , '$templateCache' ,
function ( $scope , $http , $templateCache ) {
$scope . method = 'GET' ;
$scope . url = 'http-hello.html' ;
$scope . fetch = function ( ) {
$scope . code = null ;
$scope . response = null ;
$http ( { method : $scope . method , url : $scope . url , cache : $templateCache } ) .
success ( function ( data , status ) {
$scope . status = status ;
$scope . data = data ;
} ) .
error ( function ( data , status ) {
$scope . data = data || "Request failed" ;
$scope . status = status ;
} ) ;
} ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
$scope . updateModel = function ( method , url ) {
$scope . method = method ;
$scope . url = url ;
} ;
} ] ) ;
2016-03-28 10:46:51 +00:00
< / f i l e >
< file name = "http-hello.html" >
Hello , $http !
< / f i l e >
2018-05-05 12:13:16 +02:00
< file name = "protractor.js" type = "protractor" >
var status = element ( by . binding ( 'status' ) ) ;
var data = element ( by . binding ( 'data' ) ) ;
var fetchBtn = element ( by . id ( 'fetchbtn' ) ) ;
var sampleGetBtn = element ( by . id ( 'samplegetbtn' ) ) ;
var sampleJsonpBtn = element ( by . id ( 'samplejsonpbtn' ) ) ;
var invalidJsonpBtn = element ( by . id ( 'invalidjsonpbtn' ) ) ;
2016-03-28 10:46:51 +00:00
it ( 'should make an xhr GET request' , function ( ) {
2018-05-05 12:13:16 +02:00
sampleGetBtn . click ( ) ;
fetchBtn . click ( ) ;
expect ( status . getText ( ) ) . toMatch ( '200' ) ;
expect ( data . getText ( ) ) . toMatch ( /Hello, \$http!/ ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
// Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185
// it('should make a JSONP request to angularjs.org', function() {
// sampleJsonpBtn.click();
// fetchBtn.click();
// expect(status.getText()).toMatch('200');
// expect(data.getText()).toMatch(/Super Hero!/);
// });
2016-03-28 10:46:51 +00:00
it ( 'should make JSONP request to invalid URL and invoke the error handler' ,
function ( ) {
2018-05-05 12:13:16 +02:00
invalidJsonpBtn . click ( ) ;
fetchBtn . click ( ) ;
expect ( status . getText ( ) ) . toMatch ( '0' ) ;
expect ( data . getText ( ) ) . toMatch ( 'Request failed' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
< / f i l e >
< / e x a m p l e >
* /
function $http ( requestConfig ) {
2018-05-05 12:13:16 +02:00
if ( ! angular . isObject ( requestConfig ) ) {
throw minErr ( '$http' ) ( 'badreq' , 'Http request configuration must be an object. Received: {0}' , requestConfig ) ;
}
var config = extend ( {
method : 'get' ,
2016-04-18 12:34:29 +00:00
transformRequest : defaults . transformRequest ,
transformResponse : defaults . transformResponse
2018-05-05 12:13:16 +02:00
} , requestConfig ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
config . headers = mergeHeaders ( requestConfig ) ;
2016-04-18 12:34:29 +00:00
config . method = uppercase ( config . method ) ;
2016-03-28 10:46:51 +00:00
var serverRequest = function ( config ) {
2018-05-05 12:13:16 +02:00
var headers = config . headers ;
var reqData = transformData ( config . data , headersGetter ( headers ) , undefined , config . transformRequest ) ;
2016-03-28 10:46:51 +00:00
// strip content-type if data is undefined
2018-05-05 12:13:16 +02:00
if ( isUndefined ( reqData ) ) {
2016-03-28 10:46:51 +00:00
forEach ( headers , function ( value , header ) {
if ( lowercase ( header ) === 'content-type' ) {
delete headers [ header ] ;
}
} ) ;
}
if ( isUndefined ( config . withCredentials ) && ! isUndefined ( defaults . withCredentials ) ) {
config . withCredentials = defaults . withCredentials ;
}
// send request
2018-05-05 12:13:16 +02:00
return sendReq ( config , reqData ) . then ( transformResponse , transformResponse ) ;
2016-03-28 10:46:51 +00:00
} ;
var chain = [ serverRequest , undefined ] ;
var promise = $q . when ( config ) ;
// apply interceptors
forEach ( reversedInterceptors , function ( interceptor ) {
if ( interceptor . request || interceptor . requestError ) {
chain . unshift ( interceptor . request , interceptor . requestError ) ;
}
if ( interceptor . response || interceptor . responseError ) {
chain . push ( interceptor . response , interceptor . responseError ) ;
}
} ) ;
2018-05-05 12:13:16 +02:00
while ( chain . length ) {
2016-03-28 10:46:51 +00:00
var thenFn = chain . shift ( ) ;
var rejectFn = chain . shift ( ) ;
promise = promise . then ( thenFn , rejectFn ) ;
}
2016-04-18 12:34:29 +00:00
promise . success = function ( fn ) {
2018-05-05 12:13:16 +02:00
assertArgFn ( fn , 'fn' ) ;
2016-04-18 12:34:29 +00:00
promise . then ( function ( response ) {
fn ( response . data , response . status , response . headers , config ) ;
} ) ;
return promise ;
} ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
promise . error = function ( fn ) {
2018-05-05 12:13:16 +02:00
assertArgFn ( fn , 'fn' ) ;
2016-04-18 12:34:29 +00:00
promise . then ( null , function ( response ) {
fn ( response . data , response . status , response . headers , config ) ;
} ) ;
return promise ;
} ;
2016-03-28 10:46:51 +00:00
return promise ;
function transformResponse ( response ) {
// make a copy since the response must be cacheable
2018-05-05 12:13:16 +02:00
var resp = extend ( { } , response ) ;
if ( ! response . data ) {
resp . data = response . data ;
} else {
resp . data = transformData ( response . data , response . headers , response . status , config . transformResponse ) ;
}
2016-03-28 10:46:51 +00:00
return ( isSuccess ( response . status ) )
? resp
: $q . reject ( resp ) ;
}
2018-05-05 12:13:16 +02:00
function executeHeaderFns ( headers ) {
var headerContent , processedHeaders = { } ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
forEach ( headers , function ( headerFn , header ) {
if ( isFunction ( headerFn ) ) {
headerContent = headerFn ( ) ;
if ( headerContent != null ) {
processedHeaders [ header ] = headerContent ;
}
} else {
processedHeaders [ header ] = headerFn ;
}
} ) ;
return processedHeaders ;
}
function mergeHeaders ( config ) {
var defHeaders = defaults . headers ,
reqHeaders = extend ( { } , config . headers ) ,
defHeaderName , lowercaseDefHeaderName , reqHeaderName ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
defHeaders = extend ( { } , defHeaders . common , defHeaders [ lowercase ( config . method ) ] ) ;
2016-04-18 12:34:29 +00:00
// using for-in instead of forEach to avoid unecessary iteration after header has been found
2016-03-28 10:46:51 +00:00
defaultHeadersIteration :
for ( defHeaderName in defHeaders ) {
lowercaseDefHeaderName = lowercase ( defHeaderName ) ;
for ( reqHeaderName in reqHeaders ) {
if ( lowercase ( reqHeaderName ) === lowercaseDefHeaderName ) {
continue defaultHeadersIteration ;
}
}
reqHeaders [ defHeaderName ] = defHeaders [ defHeaderName ] ;
}
2018-05-05 12:13:16 +02:00
// execute if header value is a function for merged headers
return executeHeaderFns ( reqHeaders ) ;
2016-03-28 10:46:51 +00:00
}
}
$http . pendingRequests = [ ] ;
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $http # get
2016-03-28 10:46:51 +00:00
*
* @ description
* Shortcut method to perform ` GET ` request .
*
* @ param { string } url Relative or absolute URL specifying the destination of the request
* @ param { Object = } config Optional configuration object
* @ returns { HttpPromise } Future object
* /
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $http # delete
2016-03-28 10:46:51 +00:00
*
* @ description
* Shortcut method to perform ` DELETE ` request .
*
* @ param { string } url Relative or absolute URL specifying the destination of the request
* @ param { Object = } config Optional configuration object
* @ returns { HttpPromise } Future object
* /
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $http # head
2016-03-28 10:46:51 +00:00
*
* @ description
* Shortcut method to perform ` HEAD ` request .
*
* @ param { string } url Relative or absolute URL specifying the destination of the request
* @ param { Object = } config Optional configuration object
* @ returns { HttpPromise } Future object
* /
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $http # jsonp
2016-03-28 10:46:51 +00:00
*
* @ description
* Shortcut method to perform ` JSONP ` request .
*
* @ param { string } url Relative or absolute URL specifying the destination of the request .
2018-05-05 12:13:16 +02:00
* The name of the callback should be the string ` JSON_CALLBACK ` .
2016-03-28 10:46:51 +00:00
* @ param { Object = } config Optional configuration object
* @ returns { HttpPromise } Future object
* /
createShortMethods ( 'get' , 'delete' , 'head' , 'jsonp' ) ;
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $http # post
2016-03-28 10:46:51 +00:00
*
* @ description
* Shortcut method to perform ` POST ` request .
*
* @ param { string } url Relative or absolute URL specifying the destination of the request
* @ param { * } data Request content
* @ param { Object = } config Optional configuration object
* @ returns { HttpPromise } Future object
* /
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $http # put
2016-03-28 10:46:51 +00:00
*
* @ description
* Shortcut method to perform ` PUT ` request .
*
* @ param { string } url Relative or absolute URL specifying the destination of the request
* @ param { * } data Request content
* @ param { Object = } config Optional configuration object
* @ returns { HttpPromise } Future object
* /
2018-05-05 12:13:16 +02:00
/ * *
* @ ngdoc method
* @ name $http # patch
*
* @ description
* Shortcut method to perform ` PATCH ` request .
*
* @ param { string } url Relative or absolute URL specifying the destination of the request
* @ param { * } data Request content
* @ param { Object = } config Optional configuration object
* @ returns { HttpPromise } Future object
* /
createShortMethodsWithData ( 'post' , 'put' , 'patch' ) ;
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc property
2018-05-05 12:13:16 +02:00
* @ name $http # defaults
2016-03-28 10:46:51 +00:00
*
* @ description
* Runtime equivalent of the ` $ httpProvider.defaults ` property . Allows configuration of
* default headers , withCredentials as well as request and response transformations .
*
* See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above .
* /
$http . defaults = defaults ;
return $http ;
function createShortMethods ( names ) {
forEach ( arguments , function ( name ) {
$http [ name ] = function ( url , config ) {
2016-04-18 12:34:29 +00:00
return $http ( extend ( config || { } , {
2016-03-28 10:46:51 +00:00
method : name ,
url : url
} ) ) ;
} ;
} ) ;
}
function createShortMethodsWithData ( name ) {
forEach ( arguments , function ( name ) {
$http [ name ] = function ( url , data , config ) {
2016-04-18 12:34:29 +00:00
return $http ( extend ( config || { } , {
2016-03-28 10:46:51 +00:00
method : name ,
url : url ,
data : data
} ) ) ;
} ;
} ) ;
}
/ * *
* Makes the request .
*
* ! ! ! ACCESSES CLOSURE VARS :
* $httpBackend , defaults , $log , $rootScope , defaultCache , $http . pendingRequests
* /
2018-05-05 12:13:16 +02:00
function sendReq ( config , reqData ) {
2016-03-28 10:46:51 +00:00
var deferred = $q . defer ( ) ,
promise = deferred . promise ,
cache ,
cachedResp ,
2018-05-05 12:13:16 +02:00
reqHeaders = config . headers ,
2016-04-18 12:34:29 +00:00
url = buildUrl ( config . url , config . params ) ;
2016-03-28 10:46:51 +00:00
$http . pendingRequests . push ( config ) ;
promise . then ( removePendingReq , removePendingReq ) ;
2018-05-05 12:13:16 +02:00
if ( ( config . cache || defaults . cache ) && config . cache !== false &&
( config . method === 'GET' || config . method === 'JSONP' ) ) {
2016-03-28 10:46:51 +00:00
cache = isObject ( config . cache ) ? config . cache
: isObject ( defaults . cache ) ? defaults . cache
: defaultCache ;
}
if ( cache ) {
cachedResp = cache . get ( url ) ;
if ( isDefined ( cachedResp ) ) {
2018-05-05 12:13:16 +02:00
if ( isPromiseLike ( cachedResp ) ) {
2016-03-28 10:46:51 +00:00
// cached request has already been sent, but there is no response yet
2018-05-05 12:13:16 +02:00
cachedResp . then ( resolvePromiseWithResult , resolvePromiseWithResult ) ;
2016-03-28 10:46:51 +00:00
} else {
// serving from cache
if ( isArray ( cachedResp ) ) {
2018-05-05 12:13:16 +02:00
resolvePromise ( cachedResp [ 1 ] , cachedResp [ 0 ] , shallowCopy ( cachedResp [ 2 ] ) , cachedResp [ 3 ] ) ;
2016-03-28 10:46:51 +00:00
} else {
2018-05-05 12:13:16 +02:00
resolvePromise ( cachedResp , 200 , { } , 'OK' ) ;
2016-03-28 10:46:51 +00:00
}
}
} else {
// put the promise for the non-transformed response into cache as a placeholder
cache . put ( url , promise ) ;
}
}
2018-05-05 12:13:16 +02:00
// if we won't have the response in cache, set the xsrf headers and
// send the request to the backend
2016-03-28 10:46:51 +00:00
if ( isUndefined ( cachedResp ) ) {
2018-05-05 12:13:16 +02:00
var xsrfValue = urlIsSameOrigin ( config . url )
? $browser . cookies ( ) [ config . xsrfCookieName || defaults . xsrfCookieName ]
: undefined ;
if ( xsrfValue ) {
reqHeaders [ ( config . xsrfHeaderName || defaults . xsrfHeaderName ) ] = xsrfValue ;
}
2016-03-28 10:46:51 +00:00
$httpBackend ( config . method , url , reqData , done , reqHeaders , config . timeout ,
config . withCredentials , config . responseType ) ;
}
return promise ;
/ * *
* Callback registered to $httpBackend ( ) :
* - caches the response if desired
* - resolves the raw $http promise
* - calls $apply
* /
2018-05-05 12:13:16 +02:00
function done ( status , response , headersString , statusText ) {
2016-03-28 10:46:51 +00:00
if ( cache ) {
if ( isSuccess ( status ) ) {
2018-05-05 12:13:16 +02:00
cache . put ( url , [ status , response , parseHeaders ( headersString ) , statusText ] ) ;
2016-03-28 10:46:51 +00:00
} else {
// remove promise from the cache
cache . remove ( url ) ;
}
}
2018-05-05 12:13:16 +02:00
function resolveHttpPromise ( ) {
resolvePromise ( response , status , headersString , statusText ) ;
}
if ( useApplyAsync ) {
$rootScope . $applyAsync ( resolveHttpPromise ) ;
} else {
resolveHttpPromise ( ) ;
if ( ! $rootScope . $$phase ) $rootScope . $apply ( ) ;
}
2016-03-28 10:46:51 +00:00
}
/ * *
* Resolves the raw $http promise .
* /
2018-05-05 12:13:16 +02:00
function resolvePromise ( response , status , headers , statusText ) {
//status: HTTP response status code, 0, -1 (aborted by timeout / promise)
status = status >= - 1 ? status : 0 ;
2016-03-28 10:46:51 +00:00
( isSuccess ( status ) ? deferred . resolve : deferred . reject ) ( {
data : response ,
status : status ,
headers : headersGetter ( headers ) ,
2018-05-05 12:13:16 +02:00
config : config ,
statusText : statusText
2016-03-28 10:46:51 +00:00
} ) ;
}
2018-05-05 12:13:16 +02:00
function resolvePromiseWithResult ( result ) {
resolvePromise ( result . data , result . status , shallowCopy ( result . headers ( ) ) , result . statusText ) ;
}
2016-03-28 10:46:51 +00:00
function removePendingReq ( ) {
2018-05-05 12:13:16 +02:00
var idx = $http . pendingRequests . indexOf ( config ) ;
2016-03-28 10:46:51 +00:00
if ( idx !== - 1 ) $http . pendingRequests . splice ( idx , 1 ) ;
}
}
2016-04-18 12:34:29 +00:00
function buildUrl ( url , params ) {
2018-05-05 12:13:16 +02:00
if ( ! params ) return url ;
var parts = [ ] ;
forEachSorted ( params , function ( value , key ) {
if ( value === null || isUndefined ( value ) ) return ;
if ( ! isArray ( value ) ) value = [ value ] ;
forEach ( value , function ( v ) {
if ( isObject ( v ) ) {
if ( isDate ( v ) ) {
v = v . toISOString ( ) ;
} else {
v = toJson ( v ) ;
}
}
parts . push ( encodeUriQuery ( key ) + '=' +
encodeUriQuery ( v ) ) ;
} ) ;
} ) ;
if ( parts . length > 0 ) {
url += ( ( url . indexOf ( '?' ) == - 1 ) ? '?' : '&' ) + parts . join ( '&' ) ;
}
return url ;
}
2016-03-28 10:46:51 +00:00
} ] ;
}
2018-05-05 12:13:16 +02:00
function createXhr ( ) {
return new window . XMLHttpRequest ( ) ;
2016-03-28 10:46:51 +00:00
}
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc service
* @ name $httpBackend
2016-03-28 10:46:51 +00:00
* @ requires $window
* @ requires $document
*
* @ description
* HTTP backend used by the { @ link ng . $http service } that delegates to
* XMLHttpRequest object or JSONP and deals with browser incompatibilities .
*
* You should never need to use this service directly , instead use the higher - level abstractions :
* { @ link ng . $http $http } or { @ link ngResource . $resource $resource } .
*
* During testing this implementation is swapped with { @ link ngMock . $httpBackend mock
* $httpBackend } which can be trained with responses .
* /
function $HttpBackendProvider ( ) {
2016-04-18 12:34:29 +00:00
this . $get = [ '$browser' , '$window' , '$document' , function ( $browser , $window , $document ) {
return createHttpBackend ( $browser , createXhr , $browser . defer , $window . angular . callbacks , $document [ 0 ] ) ;
2016-03-28 10:46:51 +00:00
} ] ;
}
function createHttpBackend ( $browser , createXhr , $browserDefer , callbacks , rawDocument ) {
// TODO(vojta): fix the signature
return function ( method , url , post , callback , headers , timeout , withCredentials , responseType ) {
$browser . $$incOutstandingRequestCount ( ) ;
url = url || $browser . url ( ) ;
if ( lowercase ( method ) == 'jsonp' ) {
var callbackId = '_' + ( callbacks . counter ++ ) . toString ( 36 ) ;
callbacks [ callbackId ] = function ( data ) {
callbacks [ callbackId ] . data = data ;
2018-05-05 12:13:16 +02:00
callbacks [ callbackId ] . called = true ;
2016-03-28 10:46:51 +00:00
} ;
var jsonpDone = jsonpReq ( url . replace ( 'JSON_CALLBACK' , 'angular.callbacks.' + callbackId ) ,
2018-05-05 12:13:16 +02:00
callbackId , function ( status , text ) {
completeRequest ( callback , status , callbacks [ callbackId ] . data , "" , text ) ;
callbacks [ callbackId ] = noop ;
2016-03-28 10:46:51 +00:00
} ) ;
} else {
2018-05-05 12:13:16 +02:00
var xhr = createXhr ( ) ;
2016-03-28 10:46:51 +00:00
xhr . open ( method , url , true ) ;
forEach ( headers , function ( value , key ) {
if ( isDefined ( value ) ) {
xhr . setRequestHeader ( key , value ) ;
}
} ) ;
2018-05-05 12:13:16 +02:00
xhr . onload = function requestLoaded ( ) {
var statusText = xhr . statusText || '' ;
// responseText is the old-school way of retrieving response (supported by IE9)
// response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
var response = ( 'response' in xhr ) ? xhr . response : xhr . responseText ;
// normalize IE9 bug (http://bugs.jquery.com/ticket/1450)
var status = xhr . status === 1223 ? 204 : xhr . status ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
// fix status code when it is 0 (0 status is undocumented).
// Occurs when accessing file resources or on Android 4.1 stock browser
// while retrieving files from application cache.
if ( status === 0 ) {
status = response ? 200 : urlResolve ( url ) . protocol == 'file' ? 404 : 0 ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
completeRequest ( callback ,
status ,
response ,
xhr . getAllResponseHeaders ( ) ,
statusText ) ;
} ;
var requestError = function ( ) {
// The response is always empty
// See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error
completeRequest ( callback , - 1 , null , null , '' ) ;
2016-03-28 10:46:51 +00:00
} ;
2018-05-05 12:13:16 +02:00
xhr . onerror = requestError ;
xhr . onabort = requestError ;
2016-03-28 10:46:51 +00:00
if ( withCredentials ) {
xhr . withCredentials = true ;
}
if ( responseType ) {
2018-05-05 12:13:16 +02:00
try {
xhr . responseType = responseType ;
} catch ( e ) {
// WebKit added support for the json responseType value on 09/03/2013
// https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are
// known to throw when setting the value "json" as the response type. Other older
// browsers implementing the responseType
//
// The json response type can be ignored if not supported, because JSON payloads are
// parsed on the client-side regardless.
if ( responseType !== 'json' ) {
throw e ;
}
}
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
xhr . send ( post || null ) ;
2016-03-28 10:46:51 +00:00
}
if ( timeout > 0 ) {
var timeoutId = $browserDefer ( timeoutRequest , timeout ) ;
2018-05-05 12:13:16 +02:00
} else if ( isPromiseLike ( timeout ) ) {
2016-03-28 10:46:51 +00:00
timeout . then ( timeoutRequest ) ;
}
function timeoutRequest ( ) {
jsonpDone && jsonpDone ( ) ;
xhr && xhr . abort ( ) ;
}
2018-05-05 12:13:16 +02:00
function completeRequest ( callback , status , response , headersString , statusText ) {
2016-03-28 10:46:51 +00:00
// cancel timeout and subsequent timeout promise resolution
2018-05-05 12:13:16 +02:00
if ( timeoutId !== undefined ) {
$browserDefer . cancel ( timeoutId ) ;
}
2016-03-28 10:46:51 +00:00
jsonpDone = xhr = null ;
2018-05-05 12:13:16 +02:00
callback ( status , response , headersString , statusText ) ;
2016-03-28 10:46:51 +00:00
$browser . $$completeOutstandingRequest ( noop ) ;
}
} ;
2018-05-05 12:13:16 +02:00
function jsonpReq ( url , callbackId , done ) {
// we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.:
2016-03-28 10:46:51 +00:00
// - fetches local scripts via XHR and evals them
// - adds and immediately removes script elements from the document
2018-05-05 12:13:16 +02:00
var script = rawDocument . createElement ( 'script' ) , callback = null ;
script . type = "text/javascript" ;
2016-03-28 10:46:51 +00:00
script . src = url ;
2018-05-05 12:13:16 +02:00
script . async = true ;
callback = function ( event ) {
removeEventListenerFn ( script , "load" , callback ) ;
removeEventListenerFn ( script , "error" , callback ) ;
rawDocument . body . removeChild ( script ) ;
script = null ;
var status = - 1 ;
var text = "unknown" ;
if ( event ) {
if ( event . type === "load" && ! callbacks [ callbackId ] . called ) {
event = { type : "error" } ;
2016-04-18 12:34:29 +00:00
}
2018-05-05 12:13:16 +02:00
text = event . type ;
status = event . type === "error" ? 404 : 200 ;
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
if ( done ) {
done ( status , text ) ;
}
} ;
addEventListenerFn ( script , "load" , callback ) ;
addEventListenerFn ( script , "error" , callback ) ;
2016-03-28 10:46:51 +00:00
rawDocument . body . appendChild ( script ) ;
2018-05-05 12:13:16 +02:00
return callback ;
2016-03-28 10:46:51 +00:00
}
}
2016-04-18 12:34:29 +00:00
var $interpolateMinErr = minErr ( '$interpolate' ) ;
2016-03-28 10:46:51 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc provider
* @ name $interpolateProvider
2016-03-28 10:46:51 +00:00
*
* @ description
*
* Used for configuring the interpolation markup . Defaults to ` {{ ` and ` }} ` .
*
* @ example
2018-05-05 12:13:16 +02:00
< example module = "customInterpolationApp" >
< file name = "index.html" >
2016-03-28 10:46:51 +00:00
< script >
var customInterpolationApp = angular . module ( 'customInterpolationApp' , [ ] ) ;
customInterpolationApp . config ( function ( $interpolateProvider ) {
$interpolateProvider . startSymbol ( '//' ) ;
$interpolateProvider . endSymbol ( '//' ) ;
} ) ;
2018-05-05 12:13:16 +02:00
customInterpolationApp . controller ( 'DemoController' , function ( ) {
2016-03-28 10:46:51 +00:00
this . label = "This binding is brought you by // interpolation symbols." ;
} ) ;
< / s c r i p t >
2016-04-18 12:34:29 +00:00
< div ng - app = "App" ng - controller = "DemoController as demo" >
2016-03-28 10:46:51 +00:00
//demo.label//
< / d i v >
2018-05-05 12:13:16 +02:00
< / f i l e >
< file name = "protractor.js" type = "protractor" >
it ( 'should interpolate binding with custom symbols' , function ( ) {
expect ( element ( by . binding ( 'demo.label' ) ) . getText ( ) ) . toBe ( 'This binding is brought you by // interpolation symbols.' ) ;
} ) ;
< / f i l e >
< / e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
function $InterpolateProvider ( ) {
var startSymbol = '{{' ;
var endSymbol = '}}' ;
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $interpolateProvider # startSymbol
2016-03-28 10:46:51 +00:00
* @ description
* Symbol to denote start of expression in the interpolated string . Defaults to ` {{ ` .
*
* @ param { string = } value new value to set the starting symbol to .
* @ returns { string | self } Returns the symbol when used as getter and self if used as setter .
* /
2018-05-05 12:13:16 +02:00
this . startSymbol = function ( value ) {
2016-03-28 10:46:51 +00:00
if ( value ) {
startSymbol = value ;
return this ;
} else {
return startSymbol ;
}
} ;
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $interpolateProvider # endSymbol
2016-03-28 10:46:51 +00:00
* @ description
* Symbol to denote the end of expression in the interpolated string . Defaults to ` }} ` .
*
* @ param { string = } value new value to set the ending symbol to .
* @ returns { string | self } Returns the symbol when used as getter and self if used as setter .
* /
2018-05-05 12:13:16 +02:00
this . endSymbol = function ( value ) {
2016-03-28 10:46:51 +00:00
if ( value ) {
endSymbol = value ;
return this ;
} else {
return endSymbol ;
}
} ;
this . $get = [ '$parse' , '$exceptionHandler' , '$sce' , function ( $parse , $exceptionHandler , $sce ) {
var startSymbolLength = startSymbol . length ,
2018-05-05 12:13:16 +02:00
endSymbolLength = endSymbol . length ,
escapedStartRegexp = new RegExp ( startSymbol . replace ( /./g , escape ) , 'g' ) ,
escapedEndRegexp = new RegExp ( endSymbol . replace ( /./g , escape ) , 'g' ) ;
function escape ( ch ) {
return '\\\\\\' + ch ;
}
2016-03-28 10:46:51 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc service
* @ name $interpolate
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ requires $parse
* @ requires $sce
*
* @ description
*
* Compiles a string with markup into an interpolation function . This service is used by the
* HTML { @ link ng . $compile $compile } service for data binding . See
* { @ link ng . $interpolateProvider $interpolateProvider } for configuring the
* interpolation markup .
*
*
2018-05-05 12:13:16 +02:00
* ` ` ` js
* var $interpolate = ... ; // injected
* var exp = $interpolate ( 'Hello {{name | uppercase}}!' ) ;
* expect ( exp ( { name : 'Angular' } ) . toEqual ( 'Hello ANGULAR!' ) ;
* ` ` `
*
* ` $ interpolate ` takes an optional fourth argument , ` allOrNothing ` . If ` allOrNothing ` is
* ` true ` , the interpolation function will return ` undefined ` unless all embedded expressions
* evaluate to a value other than ` undefined ` .
*
* ` ` ` js
* var $interpolate = ... ; // injected
* var context = { greeting : 'Hello' , name : undefined } ;
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* // default "forgiving" mode
* var exp = $interpolate ( '{{greeting}} {{name}}!' ) ;
* expect ( exp ( context ) ) . toEqual ( 'Hello !' ) ;
*
* // "allOrNothing" mode
* exp = $interpolate ( '{{greeting}} {{name}}!' , false , null , true ) ;
* expect ( exp ( context ) ) . toBeUndefined ( ) ;
* context . name = 'Angular' ;
* expect ( exp ( context ) ) . toEqual ( 'Hello Angular!' ) ;
* ` ` `
*
* ` allOrNothing ` is useful for interpolating URLs . ` ngSrc ` and ` ngSrcset ` use this behavior .
*
* # # # # Escaped Interpolation
* $interpolate provides a mechanism for escaping interpolation markers . Start and end markers
* can be escaped by preceding each of their characters with a REVERSE SOLIDUS U + 005 C ( backslash ) .
* It will be rendered as a regular start / end marker , and will not be interpreted as an expression
* or binding .
*
* This enables web - servers to prevent script injection attacks and defacing attacks , to some
* degree , while also enabling code examples to work without relying on the
* { @ link ng . directive : ngNonBindable ngNonBindable } directive .
*
* * * For security purposes , it is strongly encouraged that web servers escape user - supplied data ,
* replacing angle brackets ( & lt ; , & gt ; ) with & amp ; lt ; and & amp ; gt ; respectively , and replacing all
* interpolation start / end markers with their escaped counterparts . * *
*
* Escaped interpolation markers are only replaced with the actual interpolation markers in rendered
* output when the $interpolate service processes the text . So , for HTML elements interpolated
* by { @ link ng . $compile $compile } , or otherwise interpolated with the ` mustHaveExpression ` parameter
* set to ` true ` , the interpolated text must contain an unescaped interpolation expression . As such ,
* this is typically useful only when user - data is used in rendering a template from the server , or
* when otherwise untrusted data is used by a directive .
*
* < example >
* < file name = "index.html" >
* < div ng - init = "username='A user'" >
* < p ng - init = "apptitle='Escaping demo'" > { { apptitle } } : \ { \ { username = "defaced value" ; \ } \ }
* < / p >
* < p > < strong > { { username } } < / s t r o n g > a t t e m p t s t o i n j e c t c o d e w h i c h w i l l d e f a c e t h e
* application , but fails to accomplish their task , because the server has correctly
* escaped the interpolation start / end markers with REVERSE SOLIDUS U + 005 C ( backslash )
* characters . < / p >
* < p > Instead , the result of the attempted script injection is visible , and can be removed
* from the database by an administrator . < / p >
* < / d i v >
* < / f i l e >
* < / e x a m p l e >
2016-03-28 10:46:51 +00:00
*
* @ param { string } text The text with markup to interpolate .
* @ param { boolean = } mustHaveExpression if set to true then the interpolation string must have
* embedded expression in order to return an interpolation function . Strings with no
* embedded expression will return null for the interpolation function .
* @ param { string = } trustedContext when provided , the returned function passes the interpolated
2018-05-05 12:13:16 +02:00
* result through { @ link ng . $sce # getTrusted $sce . getTrusted ( interpolatedResult ,
2016-03-28 10:46:51 +00:00
* trustedContext ) } before returning it . Refer to the { @ link ng . $sce $sce } service that
* provides Strict Contextual Escaping for details .
2018-05-05 12:13:16 +02:00
* @ param { boolean = } allOrNothing if ` true ` , then the returned function returns undefined
* unless all embedded expressions evaluate to a value other than ` undefined ` .
2016-03-28 10:46:51 +00:00
* @ returns { function ( context ) } an interpolation function which is used to compute the
* interpolated string . The function has these parameters :
*
2018-05-05 12:13:16 +02:00
* - ` context ` : evaluation context for all expressions embedded in the interpolated text
2016-03-28 10:46:51 +00:00
* /
2018-05-05 12:13:16 +02:00
function $interpolate ( text , mustHaveExpression , trustedContext , allOrNothing ) {
allOrNothing = ! ! allOrNothing ;
2016-03-28 10:46:51 +00:00
var startIndex ,
endIndex ,
index = 0 ,
2018-05-05 12:13:16 +02:00
expressions = [ ] ,
parseFns = [ ] ,
textLength = text . length ,
2016-03-28 10:46:51 +00:00
exp ,
2018-05-05 12:13:16 +02:00
concat = [ ] ,
expressionPositions = [ ] ;
while ( index < textLength ) {
if ( ( ( startIndex = text . indexOf ( startSymbol , index ) ) != - 1 ) &&
( ( endIndex = text . indexOf ( endSymbol , startIndex + startSymbolLength ) ) != - 1 ) ) {
if ( index !== startIndex ) {
concat . push ( unescapeText ( text . substring ( index , startIndex ) ) ) ;
}
exp = text . substring ( startIndex + startSymbolLength , endIndex ) ;
expressions . push ( exp ) ;
parseFns . push ( $parse ( exp , parseStringifyInterceptor ) ) ;
2016-03-28 10:46:51 +00:00
index = endIndex + endSymbolLength ;
2018-05-05 12:13:16 +02:00
expressionPositions . push ( concat . length ) ;
concat . push ( '' ) ;
2016-03-28 10:46:51 +00:00
} else {
2018-05-05 12:13:16 +02:00
// we did not find an interpolation, so we have to add the remainder to the separators array
if ( index !== textLength ) {
concat . push ( unescapeText ( text . substring ( index ) ) ) ;
}
break ;
2016-03-28 10:46:51 +00:00
}
}
// Concatenating expressions makes it hard to reason about whether some combination of
// concatenated values are unsafe to use and could easily lead to XSS. By requiring that a
// single expression be used for iframe[src], object[src], etc., we ensure that the value
// that's used is assigned or constructed by some JS code somewhere that is more testable or
// make it obvious that you bound the value to some user controlled value. This helps reduce
// the load when auditing for XSS issues.
2018-05-05 12:13:16 +02:00
if ( trustedContext && concat . length > 1 ) {
2016-04-18 12:34:29 +00:00
throw $interpolateMinErr ( 'noconcat' ,
"Error while interpolating: {0}\nStrict Contextual Escaping disallows " +
"interpolations that concatenate multiple expressions when a trusted value is " +
"required. See http://docs.angularjs.org/api/ng.$sce" , text ) ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
if ( ! mustHaveExpression || expressions . length ) {
var compute = function ( values ) {
for ( var i = 0 , ii = expressions . length ; i < ii ; i ++ ) {
if ( allOrNothing && isUndefined ( values [ i ] ) ) return ;
concat [ expressionPositions [ i ] ] = values [ i ] ;
}
return concat . join ( '' ) ;
} ;
var getValue = function ( value ) {
return trustedContext ?
$sce . getTrusted ( trustedContext , value ) :
$sce . valueOf ( value ) ;
} ;
var stringify = function ( value ) {
if ( value == null ) { // null || undefined
return '' ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
switch ( typeof value ) {
case 'string' :
break ;
case 'number' :
value = '' + value ;
break ;
default :
value = toJson ( value ) ;
2016-04-18 12:34:29 +00:00
}
2018-05-05 12:13:16 +02:00
return value ;
2016-04-18 12:34:29 +00:00
} ;
2018-05-05 12:13:16 +02:00
return extend ( function interpolationFn ( context ) {
var i = 0 ;
var ii = expressions . length ;
var values = new Array ( ii ) ;
try {
for ( ; i < ii ; i ++ ) {
values [ i ] = parseFns [ i ] ( context ) ;
}
return compute ( values ) ;
} catch ( err ) {
var newErr = $interpolateMinErr ( 'interr' , "Can't interpolate: {0}\n{1}" , text ,
err . toString ( ) ) ;
$exceptionHandler ( newErr ) ;
}
} , {
// all of these properties are undocumented for now
exp : text , //just for compatibility with regular watchers created via $watch
expressions : expressions ,
$$watchDelegate : function ( scope , listener , objectEquality ) {
var lastValue ;
return scope . $watchGroup ( parseFns , function interpolateFnWatcher ( values , oldValues ) {
var currValue = compute ( values ) ;
if ( isFunction ( listener ) ) {
listener . call ( this , currValue , values !== oldValues ? lastValue : currValue , scope ) ;
}
lastValue = currValue ;
} , objectEquality ) ;
}
} ) ;
}
function unescapeText ( text ) {
return text . replace ( escapedStartRegexp , startSymbol ) .
replace ( escapedEndRegexp , endSymbol ) ;
}
function parseStringifyInterceptor ( value ) {
try {
value = getValue ( value ) ;
return allOrNothing && ! isDefined ( value ) ? value : stringify ( value ) ;
} catch ( err ) {
var newErr = $interpolateMinErr ( 'interr' , "Can't interpolate: {0}\n{1}" , text ,
err . toString ( ) ) ;
$exceptionHandler ( newErr ) ;
}
2016-03-28 10:46:51 +00:00
}
}
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $interpolate # startSymbol
2016-03-28 10:46:51 +00:00
* @ description
* Symbol to denote the start of expression in the interpolated string . Defaults to ` {{ ` .
*
2018-05-05 12:13:16 +02:00
* Use { @ link ng . $interpolateProvider # startSymbol ` $ interpolateProvider.startSymbol ` } to change
2016-03-28 10:46:51 +00:00
* the symbol .
*
* @ returns { string } start symbol .
* /
$interpolate . startSymbol = function ( ) {
return startSymbol ;
} ;
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $interpolate # endSymbol
2016-03-28 10:46:51 +00:00
* @ description
* Symbol to denote the end of expression in the interpolated string . Defaults to ` }} ` .
*
2018-05-05 12:13:16 +02:00
* Use { @ link ng . $interpolateProvider # endSymbol ` $ interpolateProvider.endSymbol ` } to change
2016-03-28 10:46:51 +00:00
* the symbol .
*
2018-05-05 12:13:16 +02:00
* @ returns { string } end symbol .
2016-03-28 10:46:51 +00:00
* /
$interpolate . endSymbol = function ( ) {
return endSymbol ;
} ;
return $interpolate ;
} ] ;
}
function $IntervalProvider ( ) {
2018-05-05 12:13:16 +02:00
this . $get = [ '$rootScope' , '$window' , '$q' , '$$q' ,
function ( $rootScope , $window , $q , $$q ) {
2016-03-28 10:46:51 +00:00
var intervals = { } ;
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc service
* @ name $interval
2016-03-28 10:46:51 +00:00
*
* @ description
* Angular ' s wrapper for ` window.setInterval ` . The ` fn ` function is executed every ` delay `
* milliseconds .
*
* The return value of registering an interval function is a promise . This promise will be
* notified upon each tick of the interval , and will be resolved after ` count ` iterations , or
* run indefinitely if ` count ` is not defined . The value of the notification will be the
* number of iterations that have run .
* To cancel an interval , call ` $ interval.cancel(promise) ` .
*
2018-05-05 12:13:16 +02:00
* In tests you can use { @ link ngMock . $interval # flush ` $ interval.flush(millis) ` } to
2016-03-28 10:46:51 +00:00
* move forward by ` millis ` milliseconds and trigger any functions scheduled to run in that
* time .
2016-05-18 00:10:50 +00:00
*
2016-03-28 10:46:51 +00:00
* < div class = "alert alert-warning" >
* * * Note * * : Intervals created by this service must be explicitly destroyed when you are finished
* with them . In particular they are not automatically destroyed when a controller ' s scope or a
* directive ' s element are destroyed .
* You should take this into consideration and make sure to always cancel the interval at the
* appropriate moment . See the example below for more details on how and when to do this .
* < / d i v >
*
* @ param { function ( ) } fn A function that should be called repeatedly .
* @ param { number } delay Number of milliseconds between each function call .
* @ param { number = } [ count = 0 ] Number of times to repeat . If not set , or 0 , will repeat
* indefinitely .
* @ param { boolean = } [ invokeApply = true ] If set to ` false ` skips model dirty checking , otherwise
2018-05-05 12:13:16 +02:00
* will invoke ` fn ` within the { @ link ng . $rootScope . Scope # $apply $apply } block .
2016-03-28 10:46:51 +00:00
* @ returns { promise } A promise which will be notified on each iteration .
*
* @ example
2018-05-05 12:13:16 +02:00
* < example module = "intervalExample" >
* < file name = "index.html" >
* < script >
* angular . module ( 'intervalExample' , [ ] )
* . controller ( 'ExampleController' , [ '$scope' , '$interval' ,
* function ( $scope , $interval ) {
* $scope . format = 'M/d/yy h:mm:ss a' ;
* $scope . blood _1 = 100 ;
* $scope . blood _2 = 120 ;
*
* var stop ;
* $scope . fight = function ( ) {
* // Don't start a new fight if we are already fighting
* if ( angular . isDefined ( stop ) ) return ;
*
* stop = $interval ( function ( ) {
* if ( $scope . blood _1 > 0 && $scope . blood _2 > 0 ) {
* $scope . blood _1 = $scope . blood _1 - 3 ;
* $scope . blood _2 = $scope . blood _2 - 4 ;
* } else {
* $scope . stopFight ( ) ;
* }
* } , 100 ) ;
* } ;
*
* $scope . stopFight = function ( ) {
* if ( angular . isDefined ( stop ) ) {
* $interval . cancel ( stop ) ;
* stop = undefined ;
* }
* } ;
*
* $scope . resetFight = function ( ) {
* $scope . blood _1 = 100 ;
* $scope . blood _2 = 120 ;
* } ;
*
* $scope . $on ( '$destroy' , function ( ) {
* // Make sure that the interval is destroyed too
* $scope . stopFight ( ) ;
* } ) ;
* } ] )
* // Register the 'myCurrentTime' directive factory method.
* // We inject $interval and dateFilter service since the factory method is DI.
* . directive ( 'myCurrentTime' , [ '$interval' , 'dateFilter' ,
* function ( $interval , dateFilter ) {
* // return the directive link function. (compile function not needed)
* return function ( scope , element , attrs ) {
* var format , // date format
* stopTime ; // so that we can cancel the time updates
*
* // used to update the UI
* function updateTime ( ) {
* element . text ( dateFilter ( new Date ( ) , format ) ) ;
* }
*
* // watch the expression, and update the UI on change.
* scope . $watch ( attrs . myCurrentTime , function ( value ) {
* format = value ;
* updateTime ( ) ;
* } ) ;
*
* stopTime = $interval ( updateTime , 1000 ) ;
*
* // listen on DOM destroy (removal) event, and cancel the next UI update
* // to prevent updating time after the DOM element was removed.
* element . on ( '$destroy' , function ( ) {
* $interval . cancel ( stopTime ) ;
* } ) ;
* }
* } ] ) ;
* < / s c r i p t >
*
* < div >
* < div ng - controller = "ExampleController" >
* Date format : < input ng - model = "format" > < hr / >
* Current time is : < span my - current - time = "format" > < / s p a n >
* < hr / >
* Blood 1 : < font color = 'red' > { { blood _1 } } < / f o n t >
* Blood 2 : < font color = 'red' > { { blood _2 } } < / f o n t >
* < button type = "button" data - ng - click = "fight()" > Fight < / b u t t o n >
* < button type = "button" data - ng - click = "stopFight()" > StopFight < / b u t t o n >
* < button type = "button" data - ng - click = "resetFight()" > resetFight < / b u t t o n >
* < / d i v >
* < / d i v >
*
* < / f i l e >
* < / e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
function interval ( fn , delay , count , invokeApply ) {
2016-04-18 12:34:29 +00:00
var setInterval = $window . setInterval ,
2016-03-28 10:46:51 +00:00
clearInterval = $window . clearInterval ,
iteration = 0 ,
2018-05-05 12:13:16 +02:00
skipApply = ( isDefined ( invokeApply ) && ! invokeApply ) ,
deferred = ( skipApply ? $$q : $q ) . defer ( ) ,
promise = deferred . promise ;
2016-05-18 00:10:50 +00:00
2018-05-05 12:13:16 +02:00
count = isDefined ( count ) ? count : 0 ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
promise . then ( null , null , fn ) ;
2016-03-28 10:46:51 +00:00
promise . $$intervalId = setInterval ( function tick ( ) {
deferred . notify ( iteration ++ ) ;
if ( count > 0 && iteration >= count ) {
deferred . resolve ( iteration ) ;
clearInterval ( promise . $$intervalId ) ;
delete intervals [ promise . $$intervalId ] ;
}
if ( ! skipApply ) $rootScope . $apply ( ) ;
} , delay ) ;
intervals [ promise . $$intervalId ] = deferred ;
return promise ;
}
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $interval # cancel
2016-03-28 10:46:51 +00:00
*
* @ description
* Cancels a task associated with the ` promise ` .
*
2018-05-05 12:13:16 +02:00
* @ param { promise } promise returned by the ` $ interval ` function .
2016-03-28 10:46:51 +00:00
* @ returns { boolean } Returns ` true ` if the task was successfully canceled .
* /
interval . cancel = function ( promise ) {
if ( promise && promise . $$intervalId in intervals ) {
intervals [ promise . $$intervalId ] . reject ( 'canceled' ) ;
2018-05-05 12:13:16 +02:00
$window . clearInterval ( promise . $$intervalId ) ;
2016-03-28 10:46:51 +00:00
delete intervals [ promise . $$intervalId ] ;
return true ;
}
return false ;
} ;
return interval ;
} ] ;
}
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc service
* @ name $locale
2016-03-28 10:46:51 +00:00
*
* @ description
* $locale service provides localization rules for various Angular components . As of right now the
* only public api is :
*
* * ` id ` – ` {string} ` – locale id formatted as ` languageId-countryId ` ( e . g . ` en-us ` )
* /
2018-05-05 12:13:16 +02:00
function $LocaleProvider ( ) {
2016-04-18 12:34:29 +00:00
this . $get = function ( ) {
return {
id : 'en-us' ,
NUMBER _FORMATS : {
DECIMAL _SEP : '.' ,
GROUP _SEP : ',' ,
PATTERNS : [
{ // Decimal Pattern
minInt : 1 ,
minFrac : 0 ,
maxFrac : 3 ,
posPre : '' ,
posSuf : '' ,
negPre : '-' ,
negSuf : '' ,
gSize : 3 ,
lgSize : 3
} , { //Currency Pattern
minInt : 1 ,
minFrac : 2 ,
maxFrac : 2 ,
posPre : '\u00A4' ,
posSuf : '' ,
negPre : '(\u00A4' ,
negSuf : ')' ,
gSize : 3 ,
lgSize : 3
}
] ,
CURRENCY _SYM : '$'
} ,
DATETIME _FORMATS : {
MONTH :
'January,February,March,April,May,June,July,August,September,October,November,December'
. split ( ',' ) ,
SHORTMONTH : 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec' . split ( ',' ) ,
DAY : 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday' . split ( ',' ) ,
SHORTDAY : 'Sun,Mon,Tue,Wed,Thu,Fri,Sat' . split ( ',' ) ,
AMPMS : [ 'AM' , 'PM' ] ,
medium : 'MMM d, y h:mm:ss a' ,
2018-05-05 12:13:16 +02:00
'short' : 'M/d/yy h:mm a' ,
2016-04-18 12:34:29 +00:00
fullDate : 'EEEE, MMMM d, y' ,
longDate : 'MMMM d, y' ,
mediumDate : 'MMM d, y' ,
shortDate : 'M/d/yy' ,
mediumTime : 'h:mm:ss a' ,
2018-05-05 12:13:16 +02:00
shortTime : 'h:mm a' ,
ERANAMES : [
"Before Christ" ,
"Anno Domini"
] ,
ERAS : [
"BC" ,
"AD"
]
2016-04-18 12:34:29 +00:00
} ,
pluralCat : function ( num ) {
if ( num === 1 ) {
return 'one' ;
}
return 'other' ;
}
} ;
} ;
}
2016-03-28 10:46:51 +00:00
var PATH _MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/ ,
DEFAULT _PORTS = { 'http' : 80 , 'https' : 443 , 'ftp' : 21 } ;
var $locationMinErr = minErr ( '$location' ) ;
/ * *
* Encode path using encodeUriSegment , ignoring forward slashes
*
* @ param { string } path Path to encode
* @ returns { string }
* /
function encodePath ( path ) {
var segments = path . split ( '/' ) ,
i = segments . length ;
while ( i -- ) {
segments [ i ] = encodeUriSegment ( segments [ i ] ) ;
}
return segments . join ( '/' ) ;
}
2018-05-05 12:13:16 +02:00
function parseAbsoluteUrl ( absoluteUrl , locationObj ) {
var parsedUrl = urlResolve ( absoluteUrl ) ;
2016-03-28 10:46:51 +00:00
locationObj . $$protocol = parsedUrl . protocol ;
locationObj . $$host = parsedUrl . hostname ;
2016-04-18 12:34:29 +00:00
locationObj . $$port = int ( parsedUrl . port ) || DEFAULT _PORTS [ parsedUrl . protocol ] || null ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
function parseAppUrl ( relativeUrl , locationObj ) {
2016-03-28 10:46:51 +00:00
var prefixed = ( relativeUrl . charAt ( 0 ) !== '/' ) ;
if ( prefixed ) {
relativeUrl = '/' + relativeUrl ;
}
2018-05-05 12:13:16 +02:00
var match = urlResolve ( relativeUrl ) ;
2016-03-28 10:46:51 +00:00
locationObj . $$path = decodeURIComponent ( prefixed && match . pathname . charAt ( 0 ) === '/' ?
match . pathname . substring ( 1 ) : match . pathname ) ;
locationObj . $$search = parseKeyValue ( match . search ) ;
locationObj . $$hash = decodeURIComponent ( match . hash ) ;
// make sure path starts with '/';
if ( locationObj . $$path && locationObj . $$path . charAt ( 0 ) != '/' ) {
locationObj . $$path = '/' + locationObj . $$path ;
}
}
/ * *
*
* @ param { string } begin
* @ param { string } whole
* @ returns { string } returns text from whole after begin or undefined if it does not begin with
* expected string .
* /
function beginsWith ( begin , whole ) {
if ( whole . indexOf ( begin ) === 0 ) {
return whole . substr ( begin . length ) ;
}
}
function stripHash ( url ) {
var index = url . indexOf ( '#' ) ;
return index == - 1 ? url : url . substr ( 0 , index ) ;
}
2018-05-05 12:13:16 +02:00
function trimEmptyHash ( url ) {
return url . replace ( /(#.+)|#$/ , '$1' ) ;
}
2016-03-28 10:46:51 +00:00
function stripFile ( url ) {
return url . substr ( 0 , stripHash ( url ) . lastIndexOf ( '/' ) + 1 ) ;
}
/* return the server only (scheme://host:port) */
function serverBase ( url ) {
return url . substring ( 0 , url . indexOf ( '/' , url . indexOf ( '//' ) + 2 ) ) ;
}
/ * *
* LocationHtml5Url represents an url
* This object is exposed as $location service when HTML5 mode is enabled and supported
*
* @ constructor
* @ param { string } appBase application base URL
2018-05-05 12:13:16 +02:00
* @ param { string } appBaseNoFile application base URL stripped of any filename
2016-03-28 10:46:51 +00:00
* @ param { string } basePrefix url path prefix
* /
2018-05-05 12:13:16 +02:00
function LocationHtml5Url ( appBase , appBaseNoFile , basePrefix ) {
2016-03-28 10:46:51 +00:00
this . $$html5 = true ;
basePrefix = basePrefix || '' ;
2018-05-05 12:13:16 +02:00
parseAbsoluteUrl ( appBase , this ) ;
2016-03-28 10:46:51 +00:00
/ * *
* Parse given html5 ( regular ) url string into properties
2018-05-05 12:13:16 +02:00
* @ param { string } url HTML5 url
2016-03-28 10:46:51 +00:00
* @ private
* /
this . $$parse = function ( url ) {
var pathUrl = beginsWith ( appBaseNoFile , url ) ;
if ( ! isString ( pathUrl ) ) {
throw $locationMinErr ( 'ipthprfx' , 'Invalid url "{0}", missing path prefix "{1}".' , url ,
appBaseNoFile ) ;
}
2018-05-05 12:13:16 +02:00
parseAppUrl ( pathUrl , this ) ;
2016-03-28 10:46:51 +00:00
if ( ! this . $$path ) {
this . $$path = '/' ;
}
this . $$compose ( ) ;
} ;
/ * *
* Compose url and update ` absUrl ` property
* @ private
* /
this . $$compose = function ( ) {
var search = toKeyValue ( this . $$search ) ,
hash = this . $$hash ? '#' + encodeUriSegment ( this . $$hash ) : '' ;
this . $$url = encodePath ( this . $$path ) + ( search ? '?' + search : '' ) + hash ;
this . $$absUrl = appBaseNoFile + this . $$url . substr ( 1 ) ; // first char is always '/'
} ;
2018-05-05 12:13:16 +02:00
this . $$parseLinkUrl = function ( url , relHref ) {
if ( relHref && relHref [ 0 ] === '#' ) {
// special case for links to hash fragments:
// keep the old url and only replace the hash fragment
this . hash ( relHref . slice ( 1 ) ) ;
return true ;
}
2016-03-28 10:46:51 +00:00
var appUrl , prevAppUrl ;
2018-05-05 12:13:16 +02:00
var rewrittenUrl ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
if ( ( appUrl = beginsWith ( appBase , url ) ) !== undefined ) {
2016-03-28 10:46:51 +00:00
prevAppUrl = appUrl ;
2018-05-05 12:13:16 +02:00
if ( ( appUrl = beginsWith ( basePrefix , appUrl ) ) !== undefined ) {
rewrittenUrl = appBaseNoFile + ( beginsWith ( '/' , appUrl ) || appUrl ) ;
2016-03-28 10:46:51 +00:00
} else {
2018-05-05 12:13:16 +02:00
rewrittenUrl = appBase + prevAppUrl ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
} else if ( ( appUrl = beginsWith ( appBaseNoFile , url ) ) !== undefined ) {
rewrittenUrl = appBaseNoFile + appUrl ;
2016-03-28 10:46:51 +00:00
} else if ( appBaseNoFile == url + '/' ) {
2018-05-05 12:13:16 +02:00
rewrittenUrl = appBaseNoFile ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
if ( rewrittenUrl ) {
this . $$parse ( rewrittenUrl ) ;
}
return ! ! rewrittenUrl ;
2016-03-28 10:46:51 +00:00
} ;
}
/ * *
* LocationHashbangUrl represents url
* This object is exposed as $location service when developer doesn ' t opt into html5 mode .
* It also serves as the base class for html5 mode fallback on legacy browsers .
*
* @ constructor
* @ param { string } appBase application base URL
2018-05-05 12:13:16 +02:00
* @ param { string } appBaseNoFile application base URL stripped of any filename
2016-03-28 10:46:51 +00:00
* @ param { string } hashPrefix hashbang prefix
* /
2018-05-05 12:13:16 +02:00
function LocationHashbangUrl ( appBase , appBaseNoFile , hashPrefix ) {
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
parseAbsoluteUrl ( appBase , this ) ;
2016-03-28 10:46:51 +00:00
/ * *
* Parse given hashbang url into properties
* @ param { string } url Hashbang url
* @ private
* /
this . $$parse = function ( url ) {
var withoutBaseUrl = beginsWith ( appBase , url ) || beginsWith ( appBaseNoFile , url ) ;
2018-05-05 12:13:16 +02:00
var withoutHashUrl ;
if ( ! isUndefined ( withoutBaseUrl ) && withoutBaseUrl . charAt ( 0 ) === '#' ) {
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
// The rest of the url starts with a hash so we have
// got either a hashbang path or a plain hash fragment
withoutHashUrl = beginsWith ( hashPrefix , withoutBaseUrl ) ;
if ( isUndefined ( withoutHashUrl ) ) {
// There was no hashbang prefix so we just have a hash fragment
withoutHashUrl = withoutBaseUrl ;
}
} else {
// There was no hashbang path nor hash fragment:
// If we are in HTML5 mode we use what is left as the path;
// Otherwise we ignore what is left
if ( this . $$html5 ) {
withoutHashUrl = withoutBaseUrl ;
} else {
withoutHashUrl = '' ;
if ( isUndefined ( withoutBaseUrl ) ) {
appBase = url ;
this . replace ( ) ;
}
}
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
parseAppUrl ( withoutHashUrl , this ) ;
2016-03-28 10:46:51 +00:00
this . $$path = removeWindowsDriveName ( this . $$path , withoutHashUrl , appBase ) ;
this . $$compose ( ) ;
/ *
* In Windows , on an anchor node on documents loaded from
* the filesystem , the browser will return a pathname
* prefixed with the drive name ( '/C:/path' ) when a
* pathname without a drive is set :
* * a . setAttribute ( 'href' , '/foo' )
* * a . pathname === '/C:/foo' //true
*
* Inside of Angular , we ' re always using pathnames that
* do not include drive names for routing .
* /
2018-05-05 12:13:16 +02:00
function removeWindowsDriveName ( path , url , base ) {
2016-03-28 10:46:51 +00:00
/ *
Matches paths for file protocol on windows ,
such as / C : / f o o / b a r , a n d c a p t u r e s o n l y / f o o / b a r .
* /
2018-05-05 12:13:16 +02:00
var windowsFilePathExp = /^\/[A-Z]:(\/.*)/ ;
2016-03-28 10:46:51 +00:00
var firstPathSegmentMatch ;
//Get the relative path from the input URL.
if ( url . indexOf ( base ) === 0 ) {
url = url . replace ( base , '' ) ;
}
2018-05-05 12:13:16 +02:00
// The input URL intentionally contains a first path segment that ends with a colon.
2016-03-28 10:46:51 +00:00
if ( windowsFilePathExp . exec ( url ) ) {
return path ;
}
firstPathSegmentMatch = windowsFilePathExp . exec ( path ) ;
return firstPathSegmentMatch ? firstPathSegmentMatch [ 1 ] : path ;
}
} ;
/ * *
* Compose hashbang url and update ` absUrl ` property
* @ private
* /
this . $$compose = function ( ) {
var search = toKeyValue ( this . $$search ) ,
hash = this . $$hash ? '#' + encodeUriSegment ( this . $$hash ) : '' ;
this . $$url = encodePath ( this . $$path ) + ( search ? '?' + search : '' ) + hash ;
this . $$absUrl = appBase + ( this . $$url ? hashPrefix + this . $$url : '' ) ;
} ;
2018-05-05 12:13:16 +02:00
this . $$parseLinkUrl = function ( url , relHref ) {
if ( stripHash ( appBase ) == stripHash ( url ) ) {
this . $$parse ( url ) ;
return true ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
return false ;
2016-03-28 10:46:51 +00:00
} ;
}
/ * *
* LocationHashbangUrl represents url
* This object is exposed as $location service when html5 history api is enabled but the browser
* does not support it .
*
* @ constructor
* @ param { string } appBase application base URL
2018-05-05 12:13:16 +02:00
* @ param { string } appBaseNoFile application base URL stripped of any filename
2016-03-28 10:46:51 +00:00
* @ param { string } hashPrefix hashbang prefix
* /
2018-05-05 12:13:16 +02:00
function LocationHashbangInHtml5Url ( appBase , appBaseNoFile , hashPrefix ) {
2016-03-28 10:46:51 +00:00
this . $$html5 = true ;
LocationHashbangUrl . apply ( this , arguments ) ;
2018-05-05 12:13:16 +02:00
this . $$parseLinkUrl = function ( url , relHref ) {
if ( relHref && relHref [ 0 ] === '#' ) {
// special case for links to hash fragments:
// keep the old url and only replace the hash fragment
this . hash ( relHref . slice ( 1 ) ) ;
return true ;
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
var rewrittenUrl ;
2016-03-28 10:46:51 +00:00
var appUrl ;
2018-05-05 12:13:16 +02:00
if ( appBase == stripHash ( url ) ) {
rewrittenUrl = url ;
} else if ( ( appUrl = beginsWith ( appBaseNoFile , url ) ) ) {
rewrittenUrl = appBase + hashPrefix + appUrl ;
} else if ( appBaseNoFile === url + '/' ) {
rewrittenUrl = appBaseNoFile ;
}
if ( rewrittenUrl ) {
this . $$parse ( rewrittenUrl ) ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
return ! ! rewrittenUrl ;
} ;
this . $$compose = function ( ) {
var search = toKeyValue ( this . $$search ) ,
hash = this . $$hash ? '#' + encodeUriSegment ( this . $$hash ) : '' ;
this . $$url = encodePath ( this . $$path ) + ( search ? '?' + search : '' ) + hash ;
// include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#'
this . $$absUrl = appBase + hashPrefix + this . $$url ;
2016-03-28 10:46:51 +00:00
} ;
2018-05-05 12:13:16 +02:00
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
var locationPrototype = {
2016-03-28 10:46:51 +00:00
/ * *
* Are we in html5 mode ?
* @ private
* /
$$html5 : false ,
/ * *
2018-05-05 12:13:16 +02:00
* Has any change been replacing ?
2016-03-28 10:46:51 +00:00
* @ private
* /
$$replace : false ,
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $location # absUrl
2016-03-28 10:46:51 +00:00
*
* @ description
* This method is getter only .
*
* Return full url representation with all segments encoded according to rules specified in
2018-05-05 12:13:16 +02:00
* [ RFC 3986 ] ( http : //www.ietf.org/rfc/rfc3986.txt).
*
*
* ` ` ` js
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo
* var absUrl = $location . absUrl ( ) ;
* // => "http://example.com/#/some/path?foo=bar&baz=xoxo"
* ` ` `
2016-03-28 10:46:51 +00:00
*
* @ return { string } full url
* /
absUrl : locationGetter ( '$$absUrl' ) ,
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $location # url
2016-03-28 10:46:51 +00:00
*
* @ description
* This method is getter / setter .
*
* Return url ( e . g . ` /path?a=b#hash ` ) when called without any parameter .
*
* Change path , search and hash , when called with parameter and return ` $ location ` .
*
2018-05-05 12:13:16 +02:00
*
* ` ` ` js
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo
* var url = $location . url ( ) ;
* // => "/some/path?foo=bar&baz=xoxo"
* ` ` `
*
2016-03-28 10:46:51 +00:00
* @ param { string = } url New url without base prefix ( e . g . ` /path?a=b#hash ` )
* @ return { string } url
* /
2018-05-05 12:13:16 +02:00
url : function ( url ) {
2016-04-18 12:34:29 +00:00
if ( isUndefined ( url ) )
2016-03-28 10:46:51 +00:00
return this . $$url ;
var match = PATH _MATCH . exec ( url ) ;
2018-05-05 12:13:16 +02:00
if ( match [ 1 ] || url === '' ) this . path ( decodeURIComponent ( match [ 1 ] ) ) ;
if ( match [ 2 ] || match [ 1 ] || url === '' ) this . search ( match [ 3 ] || '' ) ;
this . hash ( match [ 5 ] || '' ) ;
2016-03-28 10:46:51 +00:00
return this ;
} ,
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $location # protocol
2016-03-28 10:46:51 +00:00
*
* @ description
* This method is getter only .
*
* Return protocol of current url .
*
2018-05-05 12:13:16 +02:00
*
* ` ` ` js
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo
* var protocol = $location . protocol ( ) ;
* // => "http"
* ` ` `
*
2016-03-28 10:46:51 +00:00
* @ return { string } protocol of current url
* /
protocol : locationGetter ( '$$protocol' ) ,
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $location # host
2016-03-28 10:46:51 +00:00
*
* @ description
* This method is getter only .
*
* Return host of current url .
*
2018-05-05 12:13:16 +02:00
* Note : compared to the non - angular version ` location.host ` which returns ` hostname:port ` , this returns the ` hostname ` portion only .
*
*
* ` ` ` js
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo
* var host = $location . host ( ) ;
* // => "example.com"
*
* // given url http://user:password@example.com:8080/#/some/path?foo=bar&baz=xoxo
* host = $location . host ( ) ;
* // => "example.com"
* host = location . host ;
* // => "example.com:8080"
* ` ` `
*
2016-03-28 10:46:51 +00:00
* @ return { string } host of current url .
* /
host : locationGetter ( '$$host' ) ,
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $location # port
2016-03-28 10:46:51 +00:00
*
* @ description
* This method is getter only .
*
* Return port of current url .
*
2018-05-05 12:13:16 +02:00
*
* ` ` ` js
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo
* var port = $location . port ( ) ;
* // => 80
* ` ` `
*
2016-03-28 10:46:51 +00:00
* @ return { Number } port
* /
port : locationGetter ( '$$port' ) ,
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $location # path
2016-03-28 10:46:51 +00:00
*
* @ description
* This method is getter / setter .
*
* Return path of current url when called without any parameter .
*
* Change path when called with parameter and return ` $ location ` .
*
* Note : Path should always begin with forward slash ( / ) , t h i s m e t h o d w i l l a d d t h e f o r w a r d s l a s h
* if it is missing .
*
2018-05-05 12:13:16 +02:00
*
* ` ` ` js
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo
* var path = $location . path ( ) ;
* // => "/some/path"
* ` ` `
*
* @ param { ( string | number ) = } path New path
2016-03-28 10:46:51 +00:00
* @ return { string } path
* /
path : locationGetterSetter ( '$$path' , function ( path ) {
2018-05-05 12:13:16 +02:00
path = path !== null ? path . toString ( ) : '' ;
2016-03-28 10:46:51 +00:00
return path . charAt ( 0 ) == '/' ? path : '/' + path ;
} ) ,
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $location # search
2016-03-28 10:46:51 +00:00
*
* @ description
* This method is getter / setter .
*
* Return search part ( as object ) of current url when called without any parameter .
*
* Change search part when called with parameter and return ` $ location ` .
*
2018-05-05 12:13:16 +02:00
*
* ` ` ` js
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo
* var searchObject = $location . search ( ) ;
* // => {foo: 'bar', baz: 'xoxo'}
*
* // set foo to 'yipee'
* $location . search ( 'foo' , 'yipee' ) ;
* // $location.search() => {foo: 'yipee', baz: 'xoxo'}
* ` ` `
*
2016-03-28 10:46:51 +00:00
* @ param { string | Object . < string > | Object . < Array . < string >> } search New search params - string or
2018-05-05 12:13:16 +02:00
* hash object .
*
* When called with a single argument the method acts as a setter , setting the ` search ` component
* of ` $ location ` to the specified value .
*
* If the argument is a hash object containing an array of values , these values will be encoded
* as duplicate search parameters in the url .
*
* @ param { ( string | Number | Array < string > | boolean ) = } paramValue If ` search ` is a string or number , then ` paramValue `
* will override only a single search property .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* If ` paramValue ` is an array , it will override the property of the ` search ` component of
* ` $ location ` specified via the first argument .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* If ` paramValue ` is ` null ` , the property specified via the first argument will be deleted .
*
* If ` paramValue ` is ` true ` , the property specified via the first argument will be added with no
* value nor trailing equal sign .
*
* @ return { Object } If called with no arguments returns the parsed ` search ` object . If called with
* one or more arguments returns ` $ location ` object itself .
2016-03-28 10:46:51 +00:00
* /
search : function ( search , paramValue ) {
switch ( arguments . length ) {
case 0 :
return this . $$search ;
case 1 :
2018-05-05 12:13:16 +02:00
if ( isString ( search ) || isNumber ( search ) ) {
search = search . toString ( ) ;
2016-03-28 10:46:51 +00:00
this . $$search = parseKeyValue ( search ) ;
} else if ( isObject ( search ) ) {
2018-05-05 12:13:16 +02:00
search = copy ( search , { } ) ;
// remove object undefined or null properties
forEach ( search , function ( value , key ) {
if ( value == null ) delete search [ key ] ;
} ) ;
2016-03-28 10:46:51 +00:00
this . $$search = search ;
} else {
throw $locationMinErr ( 'isrcharg' ,
'The first argument of the `$location#search()` call must be a string or an object.' ) ;
}
break ;
default :
if ( isUndefined ( paramValue ) || paramValue === null ) {
delete this . $$search [ search ] ;
} else {
this . $$search [ search ] = paramValue ;
}
}
this . $$compose ( ) ;
return this ;
} ,
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $location # hash
2016-03-28 10:46:51 +00:00
*
* @ description
* This method is getter / setter .
*
2016-04-18 12:34:29 +00:00
* Return hash fragment when called without any parameter .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* Change hash fragment when called with parameter and return ` $ location ` .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
*
* ` ` ` js
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue
* var hash = $location . hash ( ) ;
* // => "hashValue"
* ` ` `
*
* @ param { ( string | number ) = } hash New hash fragment
2016-03-28 10:46:51 +00:00
* @ return { string } hash
* /
2018-05-05 12:13:16 +02:00
hash : locationGetterSetter ( '$$hash' , function ( hash ) {
return hash !== null ? hash . toString ( ) : '' ;
} ) ,
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $location # replace
2016-03-28 10:46:51 +00:00
*
* @ description
2016-04-18 12:34:29 +00:00
* If called , all changes to $location during current ` $ digest ` will be replacing current history
* record , instead of adding new one .
2016-03-28 10:46:51 +00:00
* /
replace : function ( ) {
this . $$replace = true ;
return this ;
}
} ;
2018-05-05 12:13:16 +02:00
forEach ( [ LocationHashbangInHtml5Url , LocationHashbangUrl , LocationHtml5Url ] , function ( Location ) {
Location . prototype = Object . create ( locationPrototype ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
/ * *
* @ ngdoc method
* @ name $location # state
*
* @ description
* This method is getter / setter .
*
* Return the history state object when called without any parameter .
*
* Change the history state object when called with one parameter and return ` $ location ` .
* The state object is later passed to ` pushState ` or ` replaceState ` .
*
* NOTE : This method is supported only in HTML5 mode and only in browsers supporting
* the HTML5 History API ( i . e . methods ` pushState ` and ` replaceState ` ) . If you need to support
* older browsers ( like IE9 or Android < 4.0 ) , don ' t use this method .
*
* @ param { object = } state State object for pushState or replaceState
* @ return { object } state
* /
Location . prototype . state = function ( state ) {
if ( ! arguments . length )
return this . $$state ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
if ( Location !== LocationHtml5Url || ! this . $$html5 ) {
throw $locationMinErr ( 'nostate' , 'History API state support is available only ' +
'in HTML5 mode and only in browsers supporting HTML5 History API' ) ;
}
// The user might modify `stateObject` after invoking `$location.state(stateObject)`
// but we're changing the $$state reference to $browser.state() during the $digest
// so the modification window is narrow.
this . $$state = isUndefined ( state ) ? null : state ;
return this ;
} ;
} ) ;
function locationGetter ( property ) {
return function ( ) {
return this [ property ] ;
} ;
}
function locationGetterSetter ( property , preprocess ) {
return function ( value ) {
2016-04-18 12:34:29 +00:00
if ( isUndefined ( value ) )
2016-03-28 10:46:51 +00:00
return this [ property ] ;
this [ property ] = preprocess ( value ) ;
this . $$compose ( ) ;
return this ;
} ;
}
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc service
* @ name $location
2016-03-28 10:46:51 +00:00
*
* @ requires $rootElement
*
* @ description
* The $location service parses the URL in the browser address bar ( based on the
2018-05-05 12:13:16 +02:00
* [ window . location ] ( https : //developer.mozilla.org/en/window.location)) and makes the URL
2016-03-28 10:46:51 +00:00
* available to your application . Changes to the URL in the address bar are reflected into
* $location service and changes to $location are reflected into the browser address bar .
*
* * * The $location service : * *
*
* - Exposes the current URL in the browser address bar , so you can
* - Watch and observe the URL .
* - Change the URL .
* - Synchronizes the URL with the browser when the user
* - Changes the address bar .
* - Clicks the back or forward button ( or clicks a History link ) .
* - Clicks on a link .
* - Represents the URL object as a set of methods ( protocol , host , port , path , search , hash ) .
*
2018-05-05 12:13:16 +02:00
* For more information see { @ link guide / $location Developer Guide : Using $location }
2016-03-28 10:46:51 +00:00
* /
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc provider
* @ name $locationProvider
2016-03-28 10:46:51 +00:00
* @ description
* Use the ` $ locationProvider ` to configure how the application deep linking paths are stored .
* /
2018-05-05 12:13:16 +02:00
function $LocationProvider ( ) {
2016-03-28 10:46:51 +00:00
var hashPrefix = '' ,
2018-05-05 12:13:16 +02:00
html5Mode = {
enabled : false ,
requireBase : true ,
rewriteLinks : true
} ;
2016-03-28 10:46:51 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $locationProvider # hashPrefix
2016-03-28 10:46:51 +00:00
* @ description
* @ param { string = } prefix Prefix for hash part ( containing path and search )
* @ returns { * } current value if used as getter or itself ( chaining ) if used as setter
* /
this . hashPrefix = function ( prefix ) {
if ( isDefined ( prefix ) ) {
hashPrefix = prefix ;
return this ;
} else {
return hashPrefix ;
}
} ;
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $locationProvider # html5Mode
2016-03-28 10:46:51 +00:00
* @ description
2018-05-05 12:13:16 +02:00
* @ param { ( boolean | Object ) = } mode If boolean , sets ` html5Mode.enabled ` to value .
* If object , sets ` enabled ` , ` requireBase ` and ` rewriteLinks ` to respective values . Supported
* properties :
* - * * enabled * * – ` {boolean} ` – ( default : false ) If true , will rely on ` history.pushState ` to
* change urls where supported . Will fall back to hash - prefixed paths in browsers that do not
* support ` pushState ` .
* - * * requireBase * * - ` {boolean} ` - ( default : ` true ` ) When html5Mode is enabled , specifies
* whether or not a < base > tag is required to be present . If ` enabled ` and ` requireBase ` are
* true , and a base tag is not present , an error will be thrown when ` $ location ` is injected .
* See the { @ link guide / $location $location guide for more information }
* - * * rewriteLinks * * - ` {boolean} ` - ( default : ` true ` ) When html5Mode is enabled ,
* enables / disables url rewriting for relative links .
*
* @ returns { Object } html5Mode object if used as getter or itself ( chaining ) if used as setter
2016-03-28 10:46:51 +00:00
* /
this . html5Mode = function ( mode ) {
2018-05-05 12:13:16 +02:00
if ( isBoolean ( mode ) ) {
html5Mode . enabled = mode ;
return this ;
} else if ( isObject ( mode ) ) {
if ( isBoolean ( mode . enabled ) ) {
html5Mode . enabled = mode . enabled ;
}
if ( isBoolean ( mode . requireBase ) ) {
html5Mode . requireBase = mode . requireBase ;
}
if ( isBoolean ( mode . rewriteLinks ) ) {
html5Mode . rewriteLinks = mode . rewriteLinks ;
}
2016-03-28 10:46:51 +00:00
return this ;
} else {
return html5Mode ;
}
} ;
/ * *
* @ ngdoc event
2018-05-05 12:13:16 +02:00
* @ name $location # $locationChangeStart
2016-03-28 10:46:51 +00:00
* @ eventType broadcast on root scope
* @ description
2018-05-05 12:13:16 +02:00
* Broadcasted before a URL will change .
*
* This change can be prevented by calling
2016-03-28 10:46:51 +00:00
* ` preventDefault ` method of the event . See { @ link ng . $rootScope . Scope # $on } for more
* details about event object . Upon successful change
2018-05-05 12:13:16 +02:00
* { @ link ng . $location # $locationChangeSuccess $locationChangeSuccess } is fired .
*
* The ` newState ` and ` oldState ` parameters may be defined only in HTML5 mode and when
* the browser supports the HTML5 History API .
2016-03-28 10:46:51 +00:00
*
* @ param { Object } angularEvent Synthetic event object .
* @ param { string } newUrl New URL
* @ param { string = } oldUrl URL that was before it was changed .
2018-05-05 12:13:16 +02:00
* @ param { string = } newState New history state object
* @ param { string = } oldState History state object that was before it was changed .
2016-03-28 10:46:51 +00:00
* /
/ * *
* @ ngdoc event
2018-05-05 12:13:16 +02:00
* @ name $location # $locationChangeSuccess
2016-03-28 10:46:51 +00:00
* @ eventType broadcast on root scope
* @ description
* Broadcasted after a URL was changed .
*
2018-05-05 12:13:16 +02:00
* The ` newState ` and ` oldState ` parameters may be defined only in HTML5 mode and when
* the browser supports the HTML5 History API .
*
2016-03-28 10:46:51 +00:00
* @ param { Object } angularEvent Synthetic event object .
* @ param { string } newUrl New URL
* @ param { string = } oldUrl URL that was before it was changed .
2018-05-05 12:13:16 +02:00
* @ param { string = } newState New history state object
* @ param { string = } oldState History state object that was before it was changed .
2016-03-28 10:46:51 +00:00
* /
2018-05-05 12:13:16 +02:00
this . $get = [ '$rootScope' , '$browser' , '$sniffer' , '$rootElement' , '$window' ,
function ( $rootScope , $browser , $sniffer , $rootElement , $window ) {
2016-03-28 10:46:51 +00:00
var $location ,
LocationMode ,
baseHref = $browser . baseHref ( ) , // if base[href] is undefined, it defaults to ''
initialUrl = $browser . url ( ) ,
appBase ;
2018-05-05 12:13:16 +02:00
if ( html5Mode . enabled ) {
if ( ! baseHref && html5Mode . requireBase ) {
throw $locationMinErr ( 'nobase' ,
"$location in HTML5 mode requires a <base> tag to be present!" ) ;
}
2016-03-28 10:46:51 +00:00
appBase = serverBase ( initialUrl ) + ( baseHref || '/' ) ;
LocationMode = $sniffer . history ? LocationHtml5Url : LocationHashbangInHtml5Url ;
} else {
appBase = stripHash ( initialUrl ) ;
LocationMode = LocationHashbangUrl ;
}
2018-05-05 12:13:16 +02:00
var appBaseNoFile = stripFile ( appBase ) ;
$location = new LocationMode ( appBase , appBaseNoFile , '#' + hashPrefix ) ;
$location . $$parseLinkUrl ( initialUrl , initialUrl ) ;
$location . $$state = $browser . state ( ) ;
var IGNORE _URI _REGEXP = /^\s*(javascript|mailto):/i ;
function setBrowserUrlWithFallback ( url , replace , state ) {
var oldUrl = $location . url ( ) ;
var oldState = $location . $$state ;
try {
$browser . url ( url , replace , state ) ;
// Make sure $location.state() returns referentially identical (not just deeply equal)
// state object; this makes possible quick checking if the state changed in the digest
// loop. Checking deep equality would be too expensive.
$location . $$state = $browser . state ( ) ;
} catch ( e ) {
// Restore old values if pushState fails
$location . url ( oldUrl ) ;
$location . $$state = oldState ;
throw e ;
}
}
2016-03-28 10:46:51 +00:00
$rootElement . on ( 'click' , function ( event ) {
// TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
// currently we open nice url link and redirect then
2018-05-05 12:13:16 +02:00
if ( ! html5Mode . rewriteLinks || event . ctrlKey || event . metaKey || event . shiftKey || event . which == 2 || event . button == 2 ) return ;
2016-03-28 10:46:51 +00:00
var elm = jqLite ( event . target ) ;
// traverse the DOM up to find first A tag
2018-05-05 12:13:16 +02:00
while ( nodeName _ ( elm [ 0 ] ) !== 'a' ) {
2016-03-28 10:46:51 +00:00
// ignore rewriting if no A tag (reached root element, or no parent - removed from document)
if ( elm [ 0 ] === $rootElement [ 0 ] || ! ( elm = elm . parent ( ) ) [ 0 ] ) return ;
}
var absHref = elm . prop ( 'href' ) ;
2018-05-05 12:13:16 +02:00
// get the actual href attribute - see
// http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx
var relHref = elm . attr ( 'href' ) || elm . attr ( 'xlink:href' ) ;
2016-03-28 10:46:51 +00:00
if ( isObject ( absHref ) && absHref . toString ( ) === '[object SVGAnimatedString]' ) {
// SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during
// an animation.
absHref = urlResolve ( absHref . animVal ) . href ;
}
2018-05-05 12:13:16 +02:00
// Ignore when url is started with javascript: or mailto:
if ( IGNORE _URI _REGEXP . test ( absHref ) ) return ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
if ( absHref && ! elm . attr ( 'target' ) && ! event . isDefaultPrevented ( ) ) {
if ( $location . $$parseLinkUrl ( absHref , relHref ) ) {
// We do a preventDefault for all urls that are part of the angular application,
// in html5mode and also without, so that we are able to abort navigation without
// getting double entries in the location history.
event . preventDefault ( ) ;
2016-03-28 10:46:51 +00:00
// update location manually
2018-05-05 12:13:16 +02:00
if ( $location . absUrl ( ) != $browser . url ( ) ) {
$rootScope . $apply ( ) ;
// hack to work around FF6 bug 684208 when scenario runner clicks on links
$window . angular [ 'ff-684208-preventDefault' ] = true ;
}
2016-03-28 10:46:51 +00:00
}
}
} ) ;
// rewrite hashbang url <> html5 url
2018-05-05 12:13:16 +02:00
if ( trimEmptyHash ( $location . absUrl ( ) ) != trimEmptyHash ( initialUrl ) ) {
2016-03-28 10:46:51 +00:00
$browser . url ( $location . absUrl ( ) , true ) ;
}
2018-05-05 12:13:16 +02:00
var initializing = true ;
2016-03-28 10:46:51 +00:00
// update $location when $browser url changes
2018-05-05 12:13:16 +02:00
$browser . onUrlChange ( function ( newUrl , newState ) {
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
if ( isUndefined ( beginsWith ( appBaseNoFile , newUrl ) ) ) {
// If we are navigating outside of the app then force a reload
$window . location . href = newUrl ;
return ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
$rootScope . $evalAsync ( function ( ) {
var oldUrl = $location . absUrl ( ) ;
var oldState = $location . $$state ;
var defaultPrevented ;
$location . $$parse ( newUrl ) ;
$location . $$state = newState ;
defaultPrevented = $rootScope . $broadcast ( '$locationChangeStart' , newUrl , oldUrl ,
newState , oldState ) . defaultPrevented ;
// if the location was changed by a `$locationChangeStart` handler then stop
// processing this location change
if ( $location . absUrl ( ) !== newUrl ) return ;
if ( defaultPrevented ) {
$location . $$parse ( oldUrl ) ;
$location . $$state = oldState ;
setBrowserUrlWithFallback ( oldUrl , false , oldState ) ;
} else {
initializing = false ;
afterLocationChange ( oldUrl , oldState ) ;
}
} ) ;
if ( ! $rootScope . $$phase ) $rootScope . $digest ( ) ;
2016-03-28 10:46:51 +00:00
} ) ;
// update browser
$rootScope . $watch ( function $locationWatch ( ) {
2018-05-05 12:13:16 +02:00
var oldUrl = trimEmptyHash ( $browser . url ( ) ) ;
var newUrl = trimEmptyHash ( $location . absUrl ( ) ) ;
var oldState = $browser . state ( ) ;
2016-03-28 10:46:51 +00:00
var currentReplace = $location . $$replace ;
2018-05-05 12:13:16 +02:00
var urlOrStateChanged = oldUrl !== newUrl ||
( $location . $$html5 && $sniffer . history && oldState !== $location . $$state ) ;
if ( initializing || urlOrStateChanged ) {
initializing = false ;
2016-03-28 10:46:51 +00:00
$rootScope . $evalAsync ( function ( ) {
2018-05-05 12:13:16 +02:00
var newUrl = $location . absUrl ( ) ;
var defaultPrevented = $rootScope . $broadcast ( '$locationChangeStart' , newUrl , oldUrl ,
$location . $$state , oldState ) . defaultPrevented ;
// if the location was changed by a `$locationChangeStart` handler then stop
// processing this location change
if ( $location . absUrl ( ) !== newUrl ) return ;
if ( defaultPrevented ) {
2016-03-28 10:46:51 +00:00
$location . $$parse ( oldUrl ) ;
2018-05-05 12:13:16 +02:00
$location . $$state = oldState ;
2016-03-28 10:46:51 +00:00
} else {
2018-05-05 12:13:16 +02:00
if ( urlOrStateChanged ) {
setBrowserUrlWithFallback ( newUrl , currentReplace ,
oldState === $location . $$state ? null : $location . $$state ) ;
}
afterLocationChange ( oldUrl , oldState ) ;
2016-03-28 10:46:51 +00:00
}
} ) ;
}
2018-05-05 12:13:16 +02:00
2016-03-28 10:46:51 +00:00
$location . $$replace = false ;
2018-05-05 12:13:16 +02:00
// we don't need to return anything because $evalAsync will make the digest loop dirty when
// there is a change
2016-03-28 10:46:51 +00:00
} ) ;
return $location ;
2018-05-05 12:13:16 +02:00
function afterLocationChange ( oldUrl , oldState ) {
$rootScope . $broadcast ( '$locationChangeSuccess' , $location . absUrl ( ) , oldUrl ,
$location . $$state , oldState ) ;
2016-03-28 10:46:51 +00:00
}
} ] ;
}
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc service
* @ name $log
2016-03-28 10:46:51 +00:00
* @ requires $window
*
* @ description
* Simple service for logging . Default implementation safely writes the message
* into the browser ' s console ( if present ) .
2016-05-18 00:10:50 +00:00
*
2016-03-28 10:46:51 +00:00
* The main purpose of this service is to simplify debugging and troubleshooting .
*
* The default is to log ` debug ` messages . You can use
* { @ link ng . $logProvider ng . $logProvider # debugEnabled } to change this .
*
* @ example
2018-05-05 12:13:16 +02:00
< example module = "logExample" >
2016-03-28 10:46:51 +00:00
< file name = "script.js" >
2018-05-05 12:13:16 +02:00
angular . module ( 'logExample' , [ ] )
. controller ( 'LogController' , [ '$scope' , '$log' , function ( $scope , $log ) {
$scope . $log = $log ;
$scope . message = 'Hello World!' ;
} ] ) ;
2016-03-28 10:46:51 +00:00
< / f i l e >
< file name = "index.html" >
2018-05-05 12:13:16 +02:00
< div ng - controller = "LogController" >
2016-03-28 10:46:51 +00:00
< p > Reload this page with open console , enter text and hit the log button ... < / p >
2016-04-18 12:34:29 +00:00
Message :
< input type = "text" ng - model = "message" / >
2016-03-28 10:46:51 +00:00
< button ng - click = "$log.log(message)" > log < / b u t t o n >
< button ng - click = "$log.warn(message)" > warn < / b u t t o n >
< button ng - click = "$log.info(message)" > info < / b u t t o n >
< button ng - click = "$log.error(message)" > error < / b u t t o n >
2018-05-05 12:13:16 +02:00
< button ng - click = "$log.debug(message)" > debug < / b u t t o n >
2016-03-28 10:46:51 +00:00
< / d i v >
< / f i l e >
< / e x a m p l e >
* /
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc provider
* @ name $logProvider
2016-03-28 10:46:51 +00:00
* @ description
* Use the ` $ logProvider ` to configure how the application logs messages
* /
2018-05-05 12:13:16 +02:00
function $LogProvider ( ) {
2016-03-28 10:46:51 +00:00
var debug = true ,
self = this ;
2016-05-18 00:10:50 +00:00
2016-03-28 10:46:51 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $logProvider # debugEnabled
2016-03-28 10:46:51 +00:00
* @ description
2018-05-05 12:13:16 +02:00
* @ param { boolean = } flag enable or disable debug level messages
2016-03-28 10:46:51 +00:00
* @ returns { * } current value if used as getter or itself ( chaining ) if used as setter
* /
this . debugEnabled = function ( flag ) {
if ( isDefined ( flag ) ) {
debug = flag ;
return this ;
} else {
return debug ;
}
} ;
2016-05-18 00:10:50 +00:00
2018-05-05 12:13:16 +02:00
this . $get = [ '$window' , function ( $window ) {
2016-03-28 10:46:51 +00:00
return {
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $log # log
2016-03-28 10:46:51 +00:00
*
* @ description
* Write a log message
* /
log : consoleLog ( 'log' ) ,
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $log # info
2016-03-28 10:46:51 +00:00
*
* @ description
* Write an information message
* /
info : consoleLog ( 'info' ) ,
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $log # warn
2016-03-28 10:46:51 +00:00
*
* @ description
* Write a warning message
* /
warn : consoleLog ( 'warn' ) ,
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $log # error
2016-03-28 10:46:51 +00:00
*
* @ description
* Write an error message
* /
error : consoleLog ( 'error' ) ,
2016-05-18 00:10:50 +00:00
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $log # debug
2016-05-18 00:10:50 +00:00
*
2016-03-28 10:46:51 +00:00
* @ description
* Write a debug message
* /
2018-05-05 12:13:16 +02:00
debug : ( function ( ) {
2016-03-28 10:46:51 +00:00
var fn = consoleLog ( 'debug' ) ;
return function ( ) {
if ( debug ) {
fn . apply ( self , arguments ) ;
}
} ;
} ( ) )
} ;
function formatError ( arg ) {
if ( arg instanceof Error ) {
if ( arg . stack ) {
arg = ( arg . message && arg . stack . indexOf ( arg . message ) === - 1 )
? 'Error: ' + arg . message + '\n' + arg . stack
: arg . stack ;
} else if ( arg . sourceURL ) {
arg = arg . message + '\n' + arg . sourceURL + ':' + arg . line ;
}
}
return arg ;
}
function consoleLog ( type ) {
var console = $window . console || { } ,
logFn = console [ type ] || console . log || noop ,
hasApply = false ;
// Note: reading logFn.apply throws an error in IE11 in IE8 document mode.
// The reason behind this is that console.log has type "object" in IE8...
try {
2018-05-05 12:13:16 +02:00
hasApply = ! ! logFn . apply ;
2016-03-28 10:46:51 +00:00
} catch ( e ) { }
if ( hasApply ) {
return function ( ) {
var args = [ ] ;
forEach ( arguments , function ( arg ) {
args . push ( formatError ( arg ) ) ;
} ) ;
return logFn . apply ( console , args ) ;
} ;
}
// we are IE which either doesn't have window.console => this is noop and we do nothing,
// or we are IE where console.log doesn't have apply so we log at least first 2 args
return function ( arg1 , arg2 ) {
logFn ( arg1 , arg2 == null ? '' : arg2 ) ;
} ;
}
} ] ;
}
2018-05-05 12:13:16 +02:00
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Any commits to this file should be reviewed with security in mind . *
* Changes to this file can potentially create security vulnerabilities . *
* An approval from 2 Core members with history of modifying *
* this file is required . *
* *
* Does the change somehow allow for arbitrary javascript to be executed ? *
* Or allows for someone to change the prototype of built - in objects ? *
* Or gives undesired access to variables likes document or window ? *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
2016-03-28 10:46:51 +00:00
var $parseMinErr = minErr ( '$parse' ) ;
// Sandboxing Angular Expressions
// ------------------------------
// Angular expressions are generally considered safe because these expressions only have direct
2018-05-05 12:13:16 +02:00
// access to `$scope` and locals. However, one can obtain the ability to execute arbitrary JS code by
2016-03-28 10:46:51 +00:00
// obtaining a reference to native JS functions such as the Function constructor.
//
// As an example, consider the following Angular expression:
//
2018-05-05 12:13:16 +02:00
// {}.toString.constructor('alert("evil JS code")')
2016-03-28 10:46:51 +00:00
//
// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits
// against the expression language, but not to prevent exploits that were enabled by exposing
2018-05-05 12:13:16 +02:00
// sensitive JavaScript or browser APIs on Scope. Exposing such objects on a Scope is never a good
2016-03-28 10:46:51 +00:00
// practice and therefore we are not even trying to protect against interaction with an object
// explicitly exposed in this way.
//
// In general, it is not possible to access a Window object from an angular expression unless a
// window or some DOM object that has a reference to window is published onto a Scope.
2018-05-05 12:13:16 +02:00
// Similarly we prevent invocations of function known to be dangerous, as well as assignments to
// native objects.
//
// See https://docs.angularjs.org/guide/security
2016-03-28 10:46:51 +00:00
function ensureSafeMemberName ( name , fullExpression ) {
2018-05-05 12:13:16 +02:00
if ( name === "__defineGetter__" || name === "__defineSetter__"
|| name === "__lookupGetter__" || name === "__lookupSetter__"
|| name === "__proto__" ) {
2016-03-28 10:46:51 +00:00
throw $parseMinErr ( 'isecfld' ,
2018-05-05 12:13:16 +02:00
'Attempting to access a disallowed field in Angular expressions! '
+ 'Expression: {0}' , fullExpression ) ;
}
return name ;
}
function getStringValue ( name , fullExpression ) {
// From the JavaScript docs:
// Property names must be strings. This means that non-string objects cannot be used
// as keys in an object. Any non-string object, including a number, is typecasted
// into a string via the toString method.
//
// So, to ensure that we are checking the same `name` that JavaScript would use,
// we cast it to a string, if possible.
// Doing `name + ''` can cause a repl error if the result to `toString` is not a string,
// this is, this will handle objects that misbehave.
name = name + '' ;
if ( ! isString ( name ) ) {
throw $parseMinErr ( 'iseccst' ,
'Cannot convert object to primitive value! '
+ 'Expression: {0}' , fullExpression ) ;
2016-03-28 10:46:51 +00:00
}
return name ;
}
function ensureSafeObject ( obj , fullExpression ) {
// nifty check if obj is Function that is fast and works across iframes and other contexts
if ( obj ) {
if ( obj . constructor === obj ) {
throw $parseMinErr ( 'isecfn' ,
'Referencing Function in Angular expressions is disallowed! Expression: {0}' ,
fullExpression ) ;
} else if ( // isWindow(obj)
2018-05-05 12:13:16 +02:00
obj . window === obj ) {
2016-03-28 10:46:51 +00:00
throw $parseMinErr ( 'isecwindow' ,
'Referencing the Window in Angular expressions is disallowed! Expression: {0}' ,
fullExpression ) ;
} else if ( // isElement(obj)
2018-05-05 12:13:16 +02:00
obj . children && ( obj . nodeName || ( obj . prop && obj . attr && obj . find ) ) ) {
2016-03-28 10:46:51 +00:00
throw $parseMinErr ( 'isecdom' ,
'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}' ,
fullExpression ) ;
2018-05-05 12:13:16 +02:00
} else if ( // block Object so that we can't get hold of dangerous Object.* methods
obj === Object ) {
throw $parseMinErr ( 'isecobj' ,
'Referencing Object in Angular expressions is disallowed! Expression: {0}' ,
fullExpression ) ;
2016-03-28 10:46:51 +00:00
}
}
return obj ;
}
2018-05-05 12:13:16 +02:00
var CALL = Function . prototype . call ;
var APPLY = Function . prototype . apply ;
var BIND = Function . prototype . bind ;
function ensureSafeFunction ( obj , fullExpression ) {
if ( obj ) {
if ( obj . constructor === obj ) {
throw $parseMinErr ( 'isecfn' ,
'Referencing Function in Angular expressions is disallowed! Expression: {0}' ,
fullExpression ) ;
} else if ( obj === CALL || obj === APPLY || obj === BIND ) {
throw $parseMinErr ( 'isecff' ,
'Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}' ,
fullExpression ) ;
}
}
}
//Keyword constants
var CONSTANTS = createMap ( ) ;
forEach ( {
'null' : function ( ) { return null ; } ,
'true' : function ( ) { return true ; } ,
'false' : function ( ) { return false ; } ,
'undefined' : function ( ) { }
} , function ( constantGetter , name ) {
constantGetter . constant = constantGetter . literal = constantGetter . sharedGetter = true ;
CONSTANTS [ name ] = constantGetter ;
} ) ;
//Not quite a constant, but can be lex/parsed the same
CONSTANTS [ 'this' ] = function ( self ) { return self ; } ;
CONSTANTS [ 'this' ] . sharedGetter = true ;
//Operators - will be wrapped by binaryFn/unaryFn/assignment/filter
var OPERATORS = extend ( createMap ( ) , {
'+' : function ( self , locals , a , b ) {
2016-04-18 12:34:29 +00:00
a = a ( self , locals ) ; b = b ( self , locals ) ;
if ( isDefined ( a ) ) {
if ( isDefined ( b ) ) {
return a + b ;
}
return a ;
}
2018-05-05 12:13:16 +02:00
return isDefined ( b ) ? b : undefined ; } ,
'-' : function ( self , locals , a , b ) {
2016-04-18 12:34:29 +00:00
a = a ( self , locals ) ; b = b ( self , locals ) ;
2018-05-05 12:13:16 +02:00
return ( isDefined ( a ) ? a : 0 ) - ( isDefined ( b ) ? b : 0 ) ;
2016-04-18 12:34:29 +00:00
} ,
2018-05-05 12:13:16 +02:00
'*' : function ( self , locals , a , b ) { return a ( self , locals ) * b ( self , locals ) ; } ,
'/' : function ( self , locals , a , b ) { return a ( self , locals ) / b ( self , locals ) ; } ,
'%' : function ( self , locals , a , b ) { return a ( self , locals ) % b ( self , locals ) ; } ,
'===' : function ( self , locals , a , b ) { return a ( self , locals ) === b ( self , locals ) ; } ,
'!==' : function ( self , locals , a , b ) { return a ( self , locals ) !== b ( self , locals ) ; } ,
'==' : function ( self , locals , a , b ) { return a ( self , locals ) == b ( self , locals ) ; } ,
'!=' : function ( self , locals , a , b ) { return a ( self , locals ) != b ( self , locals ) ; } ,
'<' : function ( self , locals , a , b ) { return a ( self , locals ) < b ( self , locals ) ; } ,
'>' : function ( self , locals , a , b ) { return a ( self , locals ) > b ( self , locals ) ; } ,
'<=' : function ( self , locals , a , b ) { return a ( self , locals ) <= b ( self , locals ) ; } ,
'>=' : function ( self , locals , a , b ) { return a ( self , locals ) >= b ( self , locals ) ; } ,
'&&' : function ( self , locals , a , b ) { return a ( self , locals ) && b ( self , locals ) ; } ,
'||' : function ( self , locals , a , b ) { return a ( self , locals ) || b ( self , locals ) ; } ,
'!' : function ( self , locals , a ) { return ! a ( self , locals ) ; } ,
//Tokenized as operators but parsed as assignment/filters
'=' : true ,
'|' : true
} ) ;
2016-03-28 10:46:51 +00:00
var ESCAPE = { "n" : "\n" , "f" : "\f" , "r" : "\r" , "t" : "\t" , "v" : "\v" , "'" : "'" , '"' : '"' } ;
/////////////////////////////////////////
/ * *
* @ constructor
* /
2018-05-05 12:13:16 +02:00
var Lexer = function ( options ) {
2016-03-28 10:46:51 +00:00
this . options = options ;
} ;
Lexer . prototype = {
constructor : Lexer ,
2018-05-05 12:13:16 +02:00
lex : function ( text ) {
2016-03-28 10:46:51 +00:00
this . text = text ;
this . index = 0 ;
this . tokens = [ ] ;
while ( this . index < this . text . length ) {
2018-05-05 12:13:16 +02:00
var ch = this . text . charAt ( this . index ) ;
if ( ch === '"' || ch === "'" ) {
this . readString ( ch ) ;
} else if ( this . isNumber ( ch ) || ch === '.' && this . isNumber ( this . peek ( ) ) ) {
2016-03-28 10:46:51 +00:00
this . readNumber ( ) ;
2018-05-05 12:13:16 +02:00
} else if ( this . isIdent ( ch ) ) {
2016-03-28 10:46:51 +00:00
this . readIdent ( ) ;
2018-05-05 12:13:16 +02:00
} else if ( this . is ( ch , '(){}[].,;:?' ) ) {
this . tokens . push ( { index : this . index , text : ch } ) ;
2016-03-28 10:46:51 +00:00
this . index ++ ;
2018-05-05 12:13:16 +02:00
} else if ( this . isWhitespace ( ch ) ) {
2016-03-28 10:46:51 +00:00
this . index ++ ;
} else {
2018-05-05 12:13:16 +02:00
var ch2 = ch + this . peek ( ) ;
2016-03-28 10:46:51 +00:00
var ch3 = ch2 + this . peek ( 2 ) ;
2018-05-05 12:13:16 +02:00
var op1 = OPERATORS [ ch ] ;
var op2 = OPERATORS [ ch2 ] ;
var op3 = OPERATORS [ ch3 ] ;
if ( op1 || op2 || op3 ) {
var token = op3 ? ch3 : ( op2 ? ch2 : ch ) ;
this . tokens . push ( { index : this . index , text : token , operator : true } ) ;
this . index += token . length ;
2016-03-28 10:46:51 +00:00
} else {
this . throwError ( 'Unexpected next character ' , this . index , this . index + 1 ) ;
}
}
}
return this . tokens ;
} ,
2018-05-05 12:13:16 +02:00
is : function ( ch , chars ) {
return chars . indexOf ( ch ) !== - 1 ;
2016-04-18 12:34:29 +00:00
} ,
peek : function ( i ) {
2016-03-28 10:46:51 +00:00
var num = i || 1 ;
return ( this . index + num < this . text . length ) ? this . text . charAt ( this . index + num ) : false ;
} ,
isNumber : function ( ch ) {
2018-05-05 12:13:16 +02:00
return ( '0' <= ch && ch <= '9' ) && typeof ch === "string" ;
2016-03-28 10:46:51 +00:00
} ,
isWhitespace : function ( ch ) {
// IE treats non-breaking space as \u00A0
return ( ch === ' ' || ch === '\r' || ch === '\t' ||
ch === '\n' || ch === '\v' || ch === '\u00A0' ) ;
} ,
isIdent : function ( ch ) {
return ( 'a' <= ch && ch <= 'z' ||
'A' <= ch && ch <= 'Z' ||
'_' === ch || ch === '$' ) ;
} ,
isExpOperator : function ( ch ) {
return ( ch === '-' || ch === '+' || this . isNumber ( ch ) ) ;
} ,
throwError : function ( error , start , end ) {
end = end || this . index ;
var colStr = ( isDefined ( start )
? 's ' + start + '-' + this . index + ' [' + this . text . substring ( start , end ) + ']'
: ' ' + end ) ;
throw $parseMinErr ( 'lexerr' , 'Lexer Error: {0} at column{1} in expression [{2}].' ,
error , colStr , this . text ) ;
} ,
readNumber : function ( ) {
var number = '' ;
var start = this . index ;
while ( this . index < this . text . length ) {
var ch = lowercase ( this . text . charAt ( this . index ) ) ;
if ( ch == '.' || this . isNumber ( ch ) ) {
number += ch ;
} else {
var peekCh = this . peek ( ) ;
if ( ch == 'e' && this . isExpOperator ( peekCh ) ) {
number += ch ;
} else if ( this . isExpOperator ( ch ) &&
peekCh && this . isNumber ( peekCh ) &&
number . charAt ( number . length - 1 ) == 'e' ) {
number += ch ;
} else if ( this . isExpOperator ( ch ) &&
( ! peekCh || ! this . isNumber ( peekCh ) ) &&
number . charAt ( number . length - 1 ) == 'e' ) {
this . throwError ( 'Invalid exponent' ) ;
} else {
break ;
}
}
this . index ++ ;
}
this . tokens . push ( {
index : start ,
text : number ,
2018-05-05 12:13:16 +02:00
constant : true ,
value : Number ( number )
2016-03-28 10:46:51 +00:00
} ) ;
} ,
readIdent : function ( ) {
var start = this . index ;
while ( this . index < this . text . length ) {
2018-05-05 12:13:16 +02:00
var ch = this . text . charAt ( this . index ) ;
if ( ! ( this . isIdent ( ch ) || this . isNumber ( ch ) ) ) {
2016-03-28 10:46:51 +00:00
break ;
}
this . index ++ ;
}
2018-05-05 12:13:16 +02:00
this . tokens . push ( {
2016-03-28 10:46:51 +00:00
index : start ,
2018-05-05 12:13:16 +02:00
text : this . text . slice ( start , this . index ) ,
identifier : true
} ) ;
2016-03-28 10:46:51 +00:00
} ,
readString : function ( quote ) {
var start = this . index ;
this . index ++ ;
var string = '' ;
var rawString = quote ;
var escape = false ;
while ( this . index < this . text . length ) {
var ch = this . text . charAt ( this . index ) ;
rawString += ch ;
if ( escape ) {
if ( ch === 'u' ) {
var hex = this . text . substring ( this . index + 1 , this . index + 5 ) ;
2016-04-18 12:34:29 +00:00
if ( ! hex . match ( /[\da-f]{4}/i ) )
2016-03-28 10:46:51 +00:00
this . throwError ( 'Invalid unicode escape [\\u' + hex + ']' ) ;
this . index += 4 ;
string += String . fromCharCode ( parseInt ( hex , 16 ) ) ;
} else {
var rep = ESCAPE [ ch ] ;
2018-05-05 12:13:16 +02:00
string = string + ( rep || ch ) ;
2016-03-28 10:46:51 +00:00
}
escape = false ;
} else if ( ch === '\\' ) {
escape = true ;
} else if ( ch === quote ) {
this . index ++ ;
this . tokens . push ( {
index : start ,
text : rawString ,
2018-05-05 12:13:16 +02:00
constant : true ,
value : string
2016-03-28 10:46:51 +00:00
} ) ;
return ;
} else {
string += ch ;
}
this . index ++ ;
}
this . throwError ( 'Unterminated quote' , start ) ;
}
} ;
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
function isConstant ( exp ) {
return exp . constant ;
}
2016-04-18 12:34:29 +00:00
/ * *
* @ constructor
* /
2018-05-05 12:13:16 +02:00
var Parser = function ( lexer , $filter , options ) {
2016-03-28 10:46:51 +00:00
this . lexer = lexer ;
2016-04-18 12:34:29 +00:00
this . $filter = $filter ;
2016-03-28 10:46:51 +00:00
this . options = options ;
} ;
2018-05-05 12:13:16 +02:00
Parser . ZERO = extend ( function ( ) {
return 0 ;
} , {
sharedGetter : true ,
constant : true
} ) ;
2016-04-18 12:34:29 +00:00
Parser . prototype = {
constructor : Parser ,
2018-05-05 12:13:16 +02:00
parse : function ( text ) {
2016-03-28 10:46:51 +00:00
this . text = text ;
this . tokens = this . lexer . lex ( text ) ;
2018-05-05 12:13:16 +02:00
var value = this . statements ( ) ;
2016-03-28 10:46:51 +00:00
if ( this . tokens . length !== 0 ) {
this . throwError ( 'is an unexpected token' , this . tokens [ 0 ] ) ;
}
2016-04-18 12:34:29 +00:00
value . literal = ! ! value . literal ;
value . constant = ! ! value . constant ;
2016-03-28 10:46:51 +00:00
return value ;
} ,
2018-05-05 12:13:16 +02:00
primary : function ( ) {
2016-04-18 12:34:29 +00:00
var primary ;
if ( this . expect ( '(' ) ) {
primary = this . filterChain ( ) ;
this . consume ( ')' ) ;
} else if ( this . expect ( '[' ) ) {
primary = this . arrayDeclaration ( ) ;
} else if ( this . expect ( '{' ) ) {
primary = this . object ( ) ;
2018-05-05 12:13:16 +02:00
} else if ( this . peek ( ) . identifier && this . peek ( ) . text in CONSTANTS ) {
primary = CONSTANTS [ this . consume ( ) . text ] ;
} else if ( this . peek ( ) . identifier ) {
primary = this . identifier ( ) ;
} else if ( this . peek ( ) . constant ) {
primary = this . constant ( ) ;
2016-04-18 12:34:29 +00:00
} else {
2018-05-05 12:13:16 +02:00
this . throwError ( 'not a primary expression' , this . peek ( ) ) ;
2016-04-18 12:34:29 +00:00
}
var next , context ;
while ( ( next = this . expect ( '(' , '[' , '.' ) ) ) {
if ( next . text === '(' ) {
primary = this . functionCall ( primary , context ) ;
context = null ;
} else if ( next . text === '[' ) {
context = primary ;
primary = this . objectIndex ( primary ) ;
} else if ( next . text === '.' ) {
context = primary ;
primary = this . fieldAccess ( primary ) ;
} else {
this . throwError ( 'IMPOSSIBLE' ) ;
}
}
return primary ;
} ,
throwError : function ( msg , token ) {
throw $parseMinErr ( 'syntax' ,
'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].' ,
token . text , msg , ( token . index + 1 ) , this . text , this . text . substring ( token . index ) ) ;
} ,
peekToken : function ( ) {
if ( this . tokens . length === 0 )
throw $parseMinErr ( 'ueoe' , 'Unexpected end of expression: {0}' , this . text ) ;
return this . tokens [ 0 ] ;
} ,
peek : function ( e1 , e2 , e3 , e4 ) {
2018-05-05 12:13:16 +02:00
return this . peekAhead ( 0 , e1 , e2 , e3 , e4 ) ;
} ,
peekAhead : function ( i , e1 , e2 , e3 , e4 ) {
if ( this . tokens . length > i ) {
var token = this . tokens [ i ] ;
2016-04-18 12:34:29 +00:00
var t = token . text ;
if ( t === e1 || t === e2 || t === e3 || t === e4 ||
( ! e1 && ! e2 && ! e3 && ! e4 ) ) {
return token ;
}
}
return false ;
} ,
2018-05-05 12:13:16 +02:00
expect : function ( e1 , e2 , e3 , e4 ) {
2016-04-18 12:34:29 +00:00
var token = this . peek ( e1 , e2 , e3 , e4 ) ;
if ( token ) {
this . tokens . shift ( ) ;
return token ;
}
return false ;
} ,
2018-05-05 12:13:16 +02:00
consume : function ( e1 ) {
if ( this . tokens . length === 0 ) {
throw $parseMinErr ( 'ueoe' , 'Unexpected end of expression: {0}' , this . text ) ;
}
var token = this . expect ( e1 ) ;
if ( ! token ) {
2016-04-18 12:34:29 +00:00
this . throwError ( 'is unexpected, expecting [' + e1 + ']' , this . peek ( ) ) ;
}
2018-05-05 12:13:16 +02:00
return token ;
2016-04-18 12:34:29 +00:00
} ,
2018-05-05 12:13:16 +02:00
unaryFn : function ( op , right ) {
var fn = OPERATORS [ op ] ;
return extend ( function $parseUnaryFn ( self , locals ) {
2016-04-18 12:34:29 +00:00
return fn ( self , locals , right ) ;
} , {
2018-05-05 12:13:16 +02:00
constant : right . constant ,
inputs : [ right ]
2016-04-18 12:34:29 +00:00
} ) ;
} ,
2018-05-05 12:13:16 +02:00
binaryFn : function ( left , op , right , isBranching ) {
var fn = OPERATORS [ op ] ;
return extend ( function $parseBinaryFn ( self , locals ) {
return fn ( self , locals , left , right ) ;
2016-04-18 12:34:29 +00:00
} , {
2018-05-05 12:13:16 +02:00
constant : left . constant && right . constant ,
inputs : ! isBranching && [ left , right ]
2016-04-18 12:34:29 +00:00
} ) ;
} ,
2018-05-05 12:13:16 +02:00
identifier : function ( ) {
var id = this . consume ( ) . text ;
//Continue reading each `.identifier` unless it is a method invocation
while ( this . peek ( '.' ) && this . peekAhead ( 1 ) . identifier && ! this . peekAhead ( 2 , '(' ) ) {
id += this . consume ( ) . text + this . consume ( ) . text ;
}
return getterFn ( id , this . options , this . text ) ;
} ,
constant : function ( ) {
var value = this . consume ( ) . value ;
return extend ( function $parseConstant ( ) {
return value ;
2016-04-18 12:34:29 +00:00
} , {
2018-05-05 12:13:16 +02:00
constant : true ,
literal : true
2016-04-18 12:34:29 +00:00
} ) ;
} ,
statements : function ( ) {
var statements = [ ] ;
2016-03-28 10:46:51 +00:00
while ( true ) {
if ( this . tokens . length > 0 && ! this . peek ( '}' , ')' , ';' , ']' ) )
2016-04-18 12:34:29 +00:00
statements . push ( this . filterChain ( ) ) ;
2016-03-28 10:46:51 +00:00
if ( ! this . expect ( ';' ) ) {
2016-04-18 12:34:29 +00:00
// optimize for the common case where there is only one statement.
// TODO(size): maybe we should not support multiple statements?
return ( statements . length === 1 )
? statements [ 0 ]
2018-05-05 12:13:16 +02:00
: function $parseStatements ( self , locals ) {
2016-04-18 12:34:29 +00:00
var value ;
2018-05-05 12:13:16 +02:00
for ( var i = 0 , ii = statements . length ; i < ii ; i ++ ) {
value = statements [ i ] ( self , locals ) ;
2016-04-18 12:34:29 +00:00
}
return value ;
} ;
2016-03-28 10:46:51 +00:00
}
}
} ,
filterChain : function ( ) {
var left = this . expression ( ) ;
var token ;
2018-05-05 12:13:16 +02:00
while ( ( token = this . expect ( '|' ) ) ) {
left = this . filter ( left ) ;
2016-04-18 12:34:29 +00:00
}
2018-05-05 12:13:16 +02:00
return left ;
2016-04-18 12:34:29 +00:00
} ,
2018-05-05 12:13:16 +02:00
filter : function ( inputFn ) {
var fn = this . $filter ( this . consume ( ) . text ) ;
var argsFn ;
var args ;
if ( this . peek ( ':' ) ) {
argsFn = [ ] ;
args = [ ] ; // we can safely reuse the array
while ( this . expect ( ':' ) ) {
2016-04-18 12:34:29 +00:00
argsFn . push ( this . expression ( ) ) ;
}
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
var inputs = [ inputFn ] . concat ( argsFn || [ ] ) ;
return extend ( function $parseFilter ( self , locals ) {
var input = inputFn ( self , locals ) ;
if ( args ) {
args [ 0 ] = input ;
var i = argsFn . length ;
while ( i -- ) {
args [ i + 1 ] = argsFn [ i ] ( self , locals ) ;
}
return fn . apply ( undefined , args ) ;
}
return fn ( input ) ;
} , {
constant : ! fn . $stateful && inputs . every ( isConstant ) ,
inputs : ! fn . $stateful && inputs
} ) ;
2016-03-28 10:46:51 +00:00
} ,
expression : function ( ) {
return this . assignment ( ) ;
} ,
assignment : function ( ) {
2016-04-18 12:34:29 +00:00
var left = this . ternary ( ) ;
var right ;
var token ;
if ( ( token = this . expect ( '=' ) ) ) {
if ( ! left . assign ) {
this . throwError ( 'implies assignment but [' +
this . text . substring ( 0 , token . index ) + '] can not be assigned to' , token ) ;
}
right = this . ternary ( ) ;
2018-05-05 12:13:16 +02:00
return extend ( function $parseAssignment ( scope , locals ) {
2016-04-18 12:34:29 +00:00
return left . assign ( scope , right ( scope , locals ) , locals ) ;
2018-05-05 12:13:16 +02:00
} , {
inputs : [ left , right ]
} ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
return left ;
2016-03-28 10:46:51 +00:00
} ,
ternary : function ( ) {
2016-04-18 12:34:29 +00:00
var left = this . logicalOR ( ) ;
var middle ;
var token ;
if ( ( token = this . expect ( '?' ) ) ) {
2018-05-05 12:13:16 +02:00
middle = this . assignment ( ) ;
if ( this . consume ( ':' ) ) {
var right = this . assignment ( ) ;
return extend ( function $parseTernary ( self , locals ) {
return left ( self , locals ) ? middle ( self , locals ) : right ( self , locals ) ;
} , {
constant : left . constant && middle . constant && right . constant
} ) ;
2016-03-28 10:46:51 +00:00
}
}
2018-05-05 12:13:16 +02:00
return left ;
2016-03-28 10:46:51 +00:00
} ,
logicalOR : function ( ) {
var left = this . logicalAND ( ) ;
2016-04-18 12:34:29 +00:00
var token ;
2018-05-05 12:13:16 +02:00
while ( ( token = this . expect ( '||' ) ) ) {
left = this . binaryFn ( left , token . text , this . logicalAND ( ) , true ) ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
return left ;
2016-03-28 10:46:51 +00:00
} ,
logicalAND : function ( ) {
var left = this . equality ( ) ;
2016-04-18 12:34:29 +00:00
var token ;
2018-05-05 12:13:16 +02:00
while ( ( token = this . expect ( '&&' ) ) ) {
left = this . binaryFn ( left , token . text , this . equality ( ) , true ) ;
2016-03-28 10:46:51 +00:00
}
return left ;
} ,
equality : function ( ) {
var left = this . relational ( ) ;
var token ;
2018-05-05 12:13:16 +02:00
while ( ( token = this . expect ( '==' , '!=' , '===' , '!==' ) ) ) {
left = this . binaryFn ( left , token . text , this . relational ( ) ) ;
2016-03-28 10:46:51 +00:00
}
return left ;
} ,
relational : function ( ) {
var left = this . additive ( ) ;
var token ;
2018-05-05 12:13:16 +02:00
while ( ( token = this . expect ( '<' , '>' , '<=' , '>=' ) ) ) {
left = this . binaryFn ( left , token . text , this . additive ( ) ) ;
2016-03-28 10:46:51 +00:00
}
return left ;
} ,
additive : function ( ) {
var left = this . multiplicative ( ) ;
var token ;
while ( ( token = this . expect ( '+' , '-' ) ) ) {
2018-05-05 12:13:16 +02:00
left = this . binaryFn ( left , token . text , this . multiplicative ( ) ) ;
2016-03-28 10:46:51 +00:00
}
return left ;
} ,
multiplicative : function ( ) {
var left = this . unary ( ) ;
var token ;
while ( ( token = this . expect ( '*' , '/' , '%' ) ) ) {
2018-05-05 12:13:16 +02:00
left = this . binaryFn ( left , token . text , this . unary ( ) ) ;
2016-03-28 10:46:51 +00:00
}
return left ;
} ,
unary : function ( ) {
var token ;
2016-04-18 12:34:29 +00:00
if ( this . expect ( '+' ) ) {
return this . primary ( ) ;
} else if ( ( token = this . expect ( '-' ) ) ) {
2018-05-05 12:13:16 +02:00
return this . binaryFn ( Parser . ZERO , token . text , this . unary ( ) ) ;
2016-04-18 12:34:29 +00:00
} else if ( ( token = this . expect ( '!' ) ) ) {
2018-05-05 12:13:16 +02:00
return this . unaryFn ( token . text , this . unary ( ) ) ;
2016-03-28 10:46:51 +00:00
} else {
return this . primary ( ) ;
}
} ,
2016-04-18 12:34:29 +00:00
fieldAccess : function ( object ) {
2018-05-05 12:13:16 +02:00
var getter = this . identifier ( ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
return extend ( function $parseFieldAccess ( scope , locals , self ) {
var o = self || object ( scope , locals ) ;
return ( o == null ) ? undefined : getter ( o ) ;
2016-04-18 12:34:29 +00:00
} , {
assign : function ( scope , value , locals ) {
2018-05-05 12:13:16 +02:00
var o = object ( scope , locals ) ;
if ( ! o ) object . assign ( scope , o = { } , locals ) ;
return getter . assign ( o , value ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
} ) ;
2016-03-28 10:46:51 +00:00
} ,
2016-04-18 12:34:29 +00:00
objectIndex : function ( obj ) {
2018-05-05 12:13:16 +02:00
var expression = this . text ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
var indexFn = this . expression ( ) ;
this . consume ( ']' ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
return extend ( function $parseObjectIndex ( self , locals ) {
2016-04-18 12:34:29 +00:00
var o = obj ( self , locals ) ,
2018-05-05 12:13:16 +02:00
i = getStringValue ( indexFn ( self , locals ) , expression ) ,
v ;
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
ensureSafeMemberName ( i , expression ) ;
2016-04-18 12:34:29 +00:00
if ( ! o ) return undefined ;
2018-05-05 12:13:16 +02:00
v = ensureSafeObject ( o [ i ] , expression ) ;
2016-04-18 12:34:29 +00:00
return v ;
} , {
assign : function ( self , value , locals ) {
2018-05-05 12:13:16 +02:00
var key = ensureSafeMemberName ( getStringValue ( indexFn ( self , locals ) , expression ) , expression ) ;
2016-04-18 12:34:29 +00:00
// prevent overwriting of Function.constructor which would break ensureSafeObject check
2018-05-05 12:13:16 +02:00
var o = ensureSafeObject ( obj ( self , locals ) , expression ) ;
if ( ! o ) obj . assign ( self , o = { } , locals ) ;
return o [ key ] = value ;
2016-04-18 12:34:29 +00:00
}
} ) ;
2016-03-28 10:46:51 +00:00
} ,
2018-05-05 12:13:16 +02:00
functionCall : function ( fnGetter , contextGetter ) {
2016-04-18 12:34:29 +00:00
var argsFn = [ ] ;
2016-03-28 10:46:51 +00:00
if ( this . peekToken ( ) . text !== ')' ) {
do {
2016-04-18 12:34:29 +00:00
argsFn . push ( this . expression ( ) ) ;
2016-03-28 10:46:51 +00:00
} while ( this . expect ( ',' ) ) ;
}
2016-04-18 12:34:29 +00:00
this . consume ( ')' ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
var expressionText = this . text ;
// we can safely reuse the array across invocations
var args = argsFn . length ? [ ] : null ;
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
return function $parseFunctionCall ( scope , locals ) {
var context = contextGetter ? contextGetter ( scope , locals ) : isDefined ( contextGetter ) ? undefined : scope ;
var fn = fnGetter ( scope , locals , context ) || noop ;
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
if ( args ) {
var i = argsFn . length ;
while ( i -- ) {
args [ i ] = ensureSafeObject ( argsFn [ i ] ( scope , locals ) , expressionText ) ;
}
2016-04-18 12:34:29 +00:00
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
ensureSafeObject ( context , expressionText ) ;
ensureSafeFunction ( fn , expressionText ) ;
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
// IE doesn't have apply for some native functions
var v = fn . apply
? fn . apply ( context , args )
: fn ( args [ 0 ] , args [ 1 ] , args [ 2 ] , args [ 3 ] , args [ 4 ] ) ;
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
if ( args ) {
// Free-up the memory (arguments of the last function call).
args . length = 0 ;
}
return ensureSafeObject ( v , expressionText ) ;
} ;
2016-03-28 10:46:51 +00:00
} ,
2016-04-18 12:34:29 +00:00
// This is used with json array declaration
2018-05-05 12:13:16 +02:00
arrayDeclaration : function ( ) {
2016-04-18 12:34:29 +00:00
var elementFns = [ ] ;
2016-03-28 10:46:51 +00:00
if ( this . peekToken ( ) . text !== ']' ) {
do {
2018-05-05 12:13:16 +02:00
if ( this . peek ( ']' ) ) {
// Support trailing commas per ES5.1.
break ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
elementFns . push ( this . expression ( ) ) ;
2016-03-28 10:46:51 +00:00
} while ( this . expect ( ',' ) ) ;
}
this . consume ( ']' ) ;
2018-05-05 12:13:16 +02:00
return extend ( function $parseArrayLiteral ( self , locals ) {
2016-04-18 12:34:29 +00:00
var array = [ ] ;
2018-05-05 12:13:16 +02:00
for ( var i = 0 , ii = elementFns . length ; i < ii ; i ++ ) {
2016-04-18 12:34:29 +00:00
array . push ( elementFns [ i ] ( self , locals ) ) ;
}
return array ;
} , {
literal : true ,
2018-05-05 12:13:16 +02:00
constant : elementFns . every ( isConstant ) ,
inputs : elementFns
2016-04-18 12:34:29 +00:00
} ) ;
2016-03-28 10:46:51 +00:00
} ,
2018-05-05 12:13:16 +02:00
object : function ( ) {
var keys = [ ] , valueFns = [ ] ;
2016-03-28 10:46:51 +00:00
if ( this . peekToken ( ) . text !== '}' ) {
do {
2018-05-05 12:13:16 +02:00
if ( this . peek ( '}' ) ) {
// Support trailing commas per ES5.1.
break ;
}
var token = this . consume ( ) ;
if ( token . constant ) {
keys . push ( token . value ) ;
} else if ( token . identifier ) {
keys . push ( token . text ) ;
} else {
this . throwError ( "invalid key" , token ) ;
2016-04-18 12:34:29 +00:00
}
2018-05-05 12:13:16 +02:00
this . consume ( ':' ) ;
valueFns . push ( this . expression ( ) ) ;
2016-03-28 10:46:51 +00:00
} while ( this . expect ( ',' ) ) ;
}
this . consume ( '}' ) ;
2018-05-05 12:13:16 +02:00
return extend ( function $parseObjectLiteral ( self , locals ) {
2016-04-18 12:34:29 +00:00
var object = { } ;
2018-05-05 12:13:16 +02:00
for ( var i = 0 , ii = valueFns . length ; i < ii ; i ++ ) {
object [ keys [ i ] ] = valueFns [ i ] ( self , locals ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
return object ;
} , {
literal : true ,
2018-05-05 12:13:16 +02:00
constant : valueFns . every ( isConstant ) ,
inputs : valueFns
2016-04-18 12:34:29 +00:00
} ) ;
2016-03-28 10:46:51 +00:00
}
} ;
2016-04-18 12:34:29 +00:00
//////////////////////////////////////////////////
// Parser helper functions
//////////////////////////////////////////////////
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
function setter ( obj , locals , path , setValue , fullExp ) {
ensureSafeObject ( obj , fullExp ) ;
ensureSafeObject ( locals , fullExp ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
var element = path . split ( '.' ) , key ;
for ( var i = 0 ; element . length > 1 ; i ++ ) {
key = ensureSafeMemberName ( element . shift ( ) , fullExp ) ;
2018-05-05 12:13:16 +02:00
var propertyObj = ( i === 0 && locals && locals [ key ] ) || obj [ key ] ;
2016-04-18 12:34:29 +00:00
if ( ! propertyObj ) {
propertyObj = { } ;
obj [ key ] = propertyObj ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
obj = ensureSafeObject ( propertyObj , fullExp ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
key = ensureSafeMemberName ( element . shift ( ) , fullExp ) ;
2018-05-05 12:13:16 +02:00
ensureSafeObject ( obj [ key ] , fullExp ) ;
2016-04-18 12:34:29 +00:00
obj [ key ] = setValue ;
return setValue ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
var getterFnCacheDefault = createMap ( ) ;
var getterFnCacheExpensive = createMap ( ) ;
function isPossiblyDangerousMemberName ( name ) {
return name == 'constructor' ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
/ * *
* Implementation of the "Black Hole" variant from :
* - http : //jsperf.com/angularjs-parse-getter/4
* - http : //jsperf.com/path-evaluation-simplified/7
* /
2018-05-05 12:13:16 +02:00
function cspSafeGetterFn ( key0 , key1 , key2 , key3 , key4 , fullExp , expensiveChecks ) {
2016-04-18 12:34:29 +00:00
ensureSafeMemberName ( key0 , fullExp ) ;
ensureSafeMemberName ( key1 , fullExp ) ;
ensureSafeMemberName ( key2 , fullExp ) ;
ensureSafeMemberName ( key3 , fullExp ) ;
ensureSafeMemberName ( key4 , fullExp ) ;
2018-05-05 12:13:16 +02:00
var eso = function ( o ) {
return ensureSafeObject ( o , fullExp ) ;
} ;
var eso0 = ( expensiveChecks || isPossiblyDangerousMemberName ( key0 ) ) ? eso : identity ;
var eso1 = ( expensiveChecks || isPossiblyDangerousMemberName ( key1 ) ) ? eso : identity ;
var eso2 = ( expensiveChecks || isPossiblyDangerousMemberName ( key2 ) ) ? eso : identity ;
var eso3 = ( expensiveChecks || isPossiblyDangerousMemberName ( key3 ) ) ? eso : identity ;
var eso4 = ( expensiveChecks || isPossiblyDangerousMemberName ( key4 ) ) ? eso : identity ;
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
return function cspSafeGetter ( scope , locals ) {
var pathVal = ( locals && locals . hasOwnProperty ( key0 ) ) ? locals : scope ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
if ( pathVal == null ) return pathVal ;
pathVal = eso0 ( pathVal [ key0 ] ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
if ( ! key1 ) return pathVal ;
if ( pathVal == null ) return undefined ;
pathVal = eso1 ( pathVal [ key1 ] ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
if ( ! key2 ) return pathVal ;
if ( pathVal == null ) return undefined ;
pathVal = eso2 ( pathVal [ key2 ] ) ;
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
if ( ! key3 ) return pathVal ;
if ( pathVal == null ) return undefined ;
pathVal = eso3 ( pathVal [ key3 ] ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
if ( ! key4 ) return pathVal ;
if ( pathVal == null ) return undefined ;
pathVal = eso4 ( pathVal [ key4 ] ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
return pathVal ;
2016-04-18 12:34:29 +00:00
} ;
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
function getterFnWithEnsureSafeObject ( fn , fullExpression ) {
return function ( s , l ) {
return fn ( s , l , ensureSafeObject , fullExpression ) ;
2016-04-18 12:34:29 +00:00
} ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
function getterFn ( path , options , fullExp ) {
2018-05-05 12:13:16 +02:00
var expensiveChecks = options . expensiveChecks ;
var getterFnCache = ( expensiveChecks ? getterFnCacheExpensive : getterFnCacheDefault ) ;
var fn = getterFnCache [ path ] ;
if ( fn ) return fn ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
var pathKeys = path . split ( '.' ) ,
2018-05-05 12:13:16 +02:00
pathKeysLength = pathKeys . length ;
2016-04-18 12:34:29 +00:00
// http://jsperf.com/angularjs-parse-getter/6
2018-05-05 12:13:16 +02:00
if ( options . csp ) {
2016-04-18 12:34:29 +00:00
if ( pathKeysLength < 6 ) {
2018-05-05 12:13:16 +02:00
fn = cspSafeGetterFn ( pathKeys [ 0 ] , pathKeys [ 1 ] , pathKeys [ 2 ] , pathKeys [ 3 ] , pathKeys [ 4 ] , fullExp , expensiveChecks ) ;
2016-03-28 10:46:51 +00:00
} else {
2018-05-05 12:13:16 +02:00
fn = function cspSafeGetter ( scope , locals ) {
2016-04-18 12:34:29 +00:00
var i = 0 , val ;
do {
val = cspSafeGetterFn ( pathKeys [ i ++ ] , pathKeys [ i ++ ] , pathKeys [ i ++ ] , pathKeys [ i ++ ] ,
2018-05-05 12:13:16 +02:00
pathKeys [ i ++ ] , fullExp , expensiveChecks ) ( scope , locals ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
locals = undefined ; // clear after first iteration
scope = val ;
} while ( i < pathKeysLength ) ;
return val ;
} ;
}
} else {
2018-05-05 12:13:16 +02:00
var code = '' ;
if ( expensiveChecks ) {
code += 's = eso(s, fe);\nl = eso(l, fe);\n' ;
}
var needsEnsureSafeObject = expensiveChecks ;
2016-04-18 12:34:29 +00:00
forEach ( pathKeys , function ( key , index ) {
ensureSafeMemberName ( key , fullExp ) ;
2018-05-05 12:13:16 +02:00
var lookupJs = ( index
2016-04-18 12:34:29 +00:00
// we simply dereference 's' on any .dot notation
? 's'
// but if we are first then we check locals first, and if so read it first
2018-05-05 12:13:16 +02:00
: '((l&&l.hasOwnProperty("' + key + '"))?l:s)' ) + '.' + key ;
if ( expensiveChecks || isPossiblyDangerousMemberName ( key ) ) {
lookupJs = 'eso(' + lookupJs + ', fe)' ;
needsEnsureSafeObject = true ;
}
code += 'if(s == null) return undefined;\n' +
's=' + lookupJs + ';\n' ;
2016-04-18 12:34:29 +00:00
} ) ;
code += 'return s;' ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
/* jshint -W054 */
2018-05-05 12:13:16 +02:00
var evaledFnGetter = new Function ( 's' , 'l' , 'eso' , 'fe' , code ) ; // s=scope, l=locals, eso=ensureSafeObject
2016-04-18 12:34:29 +00:00
/* jshint +W054 */
evaledFnGetter . toString = valueFn ( code ) ;
2018-05-05 12:13:16 +02:00
if ( needsEnsureSafeObject ) {
evaledFnGetter = getterFnWithEnsureSafeObject ( evaledFnGetter , fullExp ) ;
}
fn = evaledFnGetter ;
2016-04-18 12:34:29 +00:00
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
fn . sharedGetter = true ;
fn . assign = function ( self , value , locals ) {
return setter ( self , locals , path , value , path ) ;
} ;
getterFnCache [ path ] = fn ;
2016-04-18 12:34:29 +00:00
return fn ;
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
var objectValueOf = Object . prototype . valueOf ;
function getValueOf ( value ) {
return isFunction ( value . valueOf ) ? value . valueOf ( ) : objectValueOf . call ( value ) ;
}
2016-04-18 12:34:29 +00:00
///////////////////////////////////
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc service
* @ name $parse
* @ kind function
2016-04-18 12:34:29 +00:00
*
* @ description
*
* Converts Angular { @ link guide / expression expression } into a function .
*
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-04-18 12:34:29 +00:00
* var getter = $parse ( 'user.name' ) ;
* var setter = getter . assign ;
* var context = { user : { name : 'angular' } } ;
* var locals = { user : { name : 'local' } } ;
*
* expect ( getter ( context ) ) . toEqual ( 'angular' ) ;
* setter ( context , 'newValue' ) ;
* expect ( context . user . name ) . toEqual ( 'newValue' ) ;
* expect ( getter ( context , locals ) ) . toEqual ( 'local' ) ;
2018-05-05 12:13:16 +02:00
* ` ` `
2016-04-18 12:34:29 +00:00
*
*
* @ param { string } expression String expression to compile .
* @ returns { function ( context , locals ) } a function which represents the compiled expression :
*
* * ` context ` – ` {object} ` – an object against which any expressions embedded in the strings
* are evaluated against ( typically a scope object ) .
* * ` locals ` – ` {object=} ` – local variables context object , useful for overriding values in
* ` context ` .
*
* The returned function also has the following properties :
* * ` literal ` – ` {boolean} ` – whether the expression ' s top - level node is a JavaScript
* literal .
* * ` constant ` – ` {boolean} ` – whether the expression is made entirely of JavaScript
* constant literals .
* * ` assign ` – ` {?function(context, value)} ` – if the expression is assignable , this will be
* set to a function to change its value on the given context .
*
* /
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc provider
* @ name $parseProvider
2016-04-18 12:34:29 +00:00
*
* @ description
* ` $ parseProvider ` can be used for configuring the default behavior of the { @ link ng . $parse $parse }
* service .
* /
function $ParseProvider ( ) {
2018-05-05 12:13:16 +02:00
var cacheDefault = createMap ( ) ;
var cacheExpensive = createMap ( ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
this . $get = [ '$filter' , '$sniffer' , function ( $filter , $sniffer ) {
var $parseOptions = {
csp : $sniffer . csp ,
expensiveChecks : false
} ,
$parseOptionsExpensive = {
csp : $sniffer . csp ,
expensiveChecks : true
} ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
function wrapSharedExpression ( exp ) {
var wrapped = exp ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
if ( exp . sharedGetter ) {
wrapped = function $parseWrapper ( self , locals ) {
return exp ( self , locals ) ;
} ;
wrapped . literal = exp . literal ;
wrapped . constant = exp . constant ;
wrapped . assign = exp . assign ;
}
return wrapped ;
2016-04-18 12:34:29 +00:00
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
return function $parse ( exp , interceptorFn , expensiveChecks ) {
var parsedExpression , oneTime , cacheKey ;
switch ( typeof exp ) {
case 'string' :
cacheKey = exp = exp . trim ( ) ;
var cache = ( expensiveChecks ? cacheExpensive : cacheDefault ) ;
parsedExpression = cache [ cacheKey ] ;
if ( ! parsedExpression ) {
if ( exp . charAt ( 0 ) === ':' && exp . charAt ( 1 ) === ':' ) {
oneTime = true ;
exp = exp . substring ( 2 ) ;
}
var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions ;
var lexer = new Lexer ( parseOptions ) ;
var parser = new Parser ( lexer , $filter , parseOptions ) ;
parsedExpression = parser . parse ( exp ) ;
if ( parsedExpression . constant ) {
parsedExpression . $$watchDelegate = constantWatchDelegate ;
} else if ( oneTime ) {
//oneTime is not part of the exp passed to the Parser so we may have to
//wrap the parsedExpression before adding a $$watchDelegate
parsedExpression = wrapSharedExpression ( parsedExpression ) ;
parsedExpression . $$watchDelegate = parsedExpression . literal ?
oneTimeLiteralWatchDelegate : oneTimeWatchDelegate ;
} else if ( parsedExpression . inputs ) {
parsedExpression . $$watchDelegate = inputsWatchDelegate ;
}
cache [ cacheKey ] = parsedExpression ;
}
return addInterceptor ( parsedExpression , interceptorFn ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
case 'function' :
return addInterceptor ( exp , interceptorFn ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
default :
return addInterceptor ( noop , interceptorFn ) ;
}
2016-03-28 10:46:51 +00:00
} ;
2018-05-05 12:13:16 +02:00
function collectExpressionInputs ( inputs , list ) {
for ( var i = 0 , ii = inputs . length ; i < ii ; i ++ ) {
var input = inputs [ i ] ;
if ( ! input . constant ) {
if ( input . inputs ) {
collectExpressionInputs ( input . inputs , list ) ;
} else if ( list . indexOf ( input ) === - 1 ) { // TODO(perf) can we do better?
list . push ( input ) ;
}
}
}
return list ;
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
function expressionInputDirtyCheck ( newValue , oldValueOfValue ) {
if ( newValue == null || oldValueOfValue == null ) { // null/undefined
return newValue === oldValueOfValue ;
}
if ( typeof newValue === 'object' ) {
// attempt to convert the value to a primitive type
// TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can
// be cheaply dirty-checked
newValue = getValueOf ( newValue ) ;
if ( typeof newValue === 'object' ) {
// objects/arrays are not supported - deep-watching them would be too expensive
return false ;
}
// fall-through to the primitive equality check
}
//Primitive or NaN
return newValue === oldValueOfValue || ( newValue !== newValue && oldValueOfValue !== oldValueOfValue ) ;
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
function inputsWatchDelegate ( scope , listener , objectEquality , parsedExpression ) {
var inputExpressions = parsedExpression . $$inputs ||
( parsedExpression . $$inputs = collectExpressionInputs ( parsedExpression . inputs , [ ] ) ) ;
var lastResult ;
if ( inputExpressions . length === 1 ) {
var oldInputValue = expressionInputDirtyCheck ; // init to something unique so that equals check fails
inputExpressions = inputExpressions [ 0 ] ;
return scope . $watch ( function expressionInputWatch ( scope ) {
var newInputValue = inputExpressions ( scope ) ;
if ( ! expressionInputDirtyCheck ( newInputValue , oldInputValue ) ) {
lastResult = parsedExpression ( scope ) ;
oldInputValue = newInputValue && getValueOf ( newInputValue ) ;
2016-04-18 12:34:29 +00:00
}
2018-05-05 12:13:16 +02:00
return lastResult ;
} , listener , objectEquality ) ;
}
var oldInputValueOfValues = [ ] ;
for ( var i = 0 , ii = inputExpressions . length ; i < ii ; i ++ ) {
oldInputValueOfValues [ i ] = expressionInputDirtyCheck ; // init to something unique so that equals check fails
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
return scope . $watch ( function expressionInputsWatch ( scope ) {
var changed = false ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
for ( var i = 0 , ii = inputExpressions . length ; i < ii ; i ++ ) {
var newInputValue = inputExpressions [ i ] ( scope ) ;
if ( changed || ( changed = ! expressionInputDirtyCheck ( newInputValue , oldInputValueOfValues [ i ] ) ) ) {
oldInputValueOfValues [ i ] = newInputValue && getValueOf ( newInputValue ) ;
2016-04-18 12:34:29 +00:00
}
2018-05-05 12:13:16 +02:00
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
if ( changed ) {
lastResult = parsedExpression ( scope ) ;
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
return lastResult ;
} , listener , objectEquality ) ;
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
function oneTimeWatchDelegate ( scope , listener , objectEquality , parsedExpression ) {
var unwatch , lastValue ;
return unwatch = scope . $watch ( function oneTimeWatch ( scope ) {
return parsedExpression ( scope ) ;
} , function oneTimeListener ( value , old , scope ) {
lastValue = value ;
if ( isFunction ( listener ) ) {
listener . apply ( this , arguments ) ;
}
if ( isDefined ( value ) ) {
scope . $$postDigest ( function ( ) {
if ( isDefined ( lastValue ) ) {
unwatch ( ) ;
}
} ) ;
}
} , objectEquality ) ;
}
function oneTimeLiteralWatchDelegate ( scope , listener , objectEquality , parsedExpression ) {
var unwatch , lastValue ;
return unwatch = scope . $watch ( function oneTimeWatch ( scope ) {
return parsedExpression ( scope ) ;
} , function oneTimeListener ( value , old , scope ) {
lastValue = value ;
if ( isFunction ( listener ) ) {
listener . call ( this , value , old , scope ) ;
}
if ( isAllDefined ( value ) ) {
scope . $$postDigest ( function ( ) {
if ( isAllDefined ( lastValue ) ) unwatch ( ) ;
} ) ;
}
} , objectEquality ) ;
function isAllDefined ( value ) {
var allDefined = true ;
forEach ( value , function ( val ) {
if ( ! isDefined ( val ) ) allDefined = false ;
} ) ;
return allDefined ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
}
function constantWatchDelegate ( scope , listener , objectEquality , parsedExpression ) {
var unwatch ;
return unwatch = scope . $watch ( function constantWatch ( scope ) {
return parsedExpression ( scope ) ;
} , function constantListener ( value , old , scope ) {
if ( isFunction ( listener ) ) {
listener . apply ( this , arguments ) ;
}
unwatch ( ) ;
} , objectEquality ) ;
}
function addInterceptor ( parsedExpression , interceptorFn ) {
if ( ! interceptorFn ) return parsedExpression ;
var watchDelegate = parsedExpression . $$watchDelegate ;
var regularWatch =
watchDelegate !== oneTimeLiteralWatchDelegate &&
watchDelegate !== oneTimeWatchDelegate ;
var fn = regularWatch ? function regularInterceptedExpression ( scope , locals ) {
var value = parsedExpression ( scope , locals ) ;
return interceptorFn ( value , scope , locals ) ;
} : function oneTimeInterceptedExpression ( scope , locals ) {
var value = parsedExpression ( scope , locals ) ;
var result = interceptorFn ( value , scope , locals ) ;
// we only return the interceptor's result if the
// initial value is defined (for bind-once)
return isDefined ( value ) ? result : value ;
} ;
// Propagate $$watchDelegates other then inputsWatchDelegate
if ( parsedExpression . $$watchDelegate &&
parsedExpression . $$watchDelegate !== inputsWatchDelegate ) {
fn . $$watchDelegate = parsedExpression . $$watchDelegate ;
} else if ( ! interceptorFn . $stateful ) {
// If there is an interceptor, but no watchDelegate then treat the interceptor like
// we treat filters - it is assumed to be a pure function unless flagged with $stateful
fn . $$watchDelegate = inputsWatchDelegate ;
fn . inputs = [ parsedExpression ] ;
}
return fn ;
}
2016-03-28 10:46:51 +00:00
} ] ;
}
/ * *
* @ ngdoc service
2018-05-05 12:13:16 +02:00
* @ name $q
2016-03-28 10:46:51 +00:00
* @ requires $rootScope
*
* @ description
2018-05-05 12:13:16 +02:00
* A service that helps you run functions asynchronously , and use their return values ( or exceptions )
* when they are done processing .
*
* This is an implementation of promises / deferred objects inspired by
* [ Kris Kowal ' s Q ] ( https : //github.com/kriskowal/q).
*
* $q can be used in two fashions -- - one which is more similar to Kris Kowal 's Q or jQuery' s Deferred
* implementations , and the other which resembles ES6 promises to some degree .
*
* # $q constructor
*
* The streamlined ES6 style promise is essentially just using $q as a constructor which takes a ` resolver `
* function as the first argument . This is similar to the native Promise implementation from ES6 Harmony ,
* see [ MDN ] ( https : //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
*
* While the constructor - style use is supported , not all of the supporting methods from ES6 Harmony promises are
* available yet .
*
* It can be used like so :
*
* ` ` ` js
* // for the purpose of this example let's assume that variables `$q` and `okToGreet`
* // are available in the current lexical scope (they could have been injected or passed in).
*
* function asyncGreet ( name ) {
* // perform some asynchronous operation, resolve or reject the promise when appropriate.
* return $q ( function ( resolve , reject ) {
* setTimeout ( function ( ) {
* if ( okToGreet ( name ) ) {
* resolve ( 'Hello, ' + name + '!' ) ;
* } else {
* reject ( 'Greeting ' + name + ' is not allowed.' ) ;
* }
* } , 1000 ) ;
* } ) ;
* }
*
* var promise = asyncGreet ( 'Robin Hood' ) ;
* promise . then ( function ( greeting ) {
* alert ( 'Success: ' + greeting ) ;
* } , function ( reason ) {
* alert ( 'Failed: ' + reason ) ;
* } ) ;
* ` ` `
*
* Note : progress / notify callbacks are not currently supported via the ES6 - style interface .
*
* However , the more traditional CommonJS - style usage is still available , and documented below .
2016-03-28 10:46:51 +00:00
*
* [ The CommonJS Promise proposal ] ( http : //wiki.commonjs.org/wiki/Promises) describes a promise as an
* interface for interacting with an object that represents the result of an action that is
* performed asynchronously , and may or may not be finished at any given point in time .
*
* From the perspective of dealing with error handling , deferred and promise APIs are to
* asynchronous programming what ` try ` , ` catch ` and ` throw ` keywords are to synchronous programming .
*
2018-05-05 12:13:16 +02:00
* ` ` ` js
* // for the purpose of this example let's assume that variables `$q` and `okToGreet`
* // are available in the current lexical scope (they could have been injected or passed in).
2016-03-28 10:46:51 +00:00
*
* function asyncGreet ( name ) {
* var deferred = $q . defer ( ) ;
*
* setTimeout ( function ( ) {
2018-05-05 12:13:16 +02:00
* deferred . notify ( 'About to greet ' + name + '.' ) ;
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* if ( okToGreet ( name ) ) {
* deferred . resolve ( 'Hello, ' + name + '!' ) ;
* } else {
* deferred . reject ( 'Greeting ' + name + ' is not allowed.' ) ;
* }
2016-03-28 10:46:51 +00:00
* } , 1000 ) ;
*
* return deferred . promise ;
* }
*
* var promise = asyncGreet ( 'Robin Hood' ) ;
* promise . then ( function ( greeting ) {
* alert ( 'Success: ' + greeting ) ;
* } , function ( reason ) {
* alert ( 'Failed: ' + reason ) ;
* } , function ( update ) {
* alert ( 'Got notification: ' + update ) ;
* } ) ;
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
* At first it might not be obvious why this extra complexity is worth the trouble . The payoff
* comes in the way of guarantees that promise and deferred APIs make , see
* https : //github.com/kriskowal/uncommonjs/blob/master/promises/specification.md.
*
* Additionally the promise api allows for composition that is very hard to do with the
* traditional callback ( [ CPS ] ( http : //en.wikipedia.org/wiki/Continuation-passing_style)) approach.
* For more on this please see the [ Q documentation ] ( https : //github.com/kriskowal/q) especially the
* section on serial or parallel joining of promises .
*
* # The Deferred API
*
* A new instance of deferred is constructed by calling ` $ q.defer() ` .
*
* The purpose of the deferred object is to expose the associated Promise instance as well as APIs
* that can be used for signaling the successful or unsuccessful completion , as well as the status
* of the task .
*
* * * Methods * *
*
* - ` resolve(value) ` – resolves the derived promise with the ` value ` . If the value is a rejection
* constructed via ` $ q.reject ` , the promise will be rejected instead .
* - ` reject(reason) ` – rejects the derived promise with the ` reason ` . This is equivalent to
* resolving it with a rejection constructed via ` $ q.reject ` .
2018-05-05 12:13:16 +02:00
* - ` notify(value) ` - provides updates on the status of the promise ' s execution . This may be called
2016-03-28 10:46:51 +00:00
* multiple times before the promise is either resolved or rejected .
*
* * * Properties * *
*
* - promise – ` {Promise} ` – promise object associated with this deferred .
*
*
* # The Promise API
*
* A new promise instance is created when a deferred instance is created and can be retrieved by
* calling ` deferred.promise ` .
*
* The purpose of the promise object is to allow for interested parties to get access to the result
* of the deferred task when it completes .
*
* * * Methods * *
*
* - ` then(successCallback, errorCallback, notifyCallback) ` – regardless of when the promise was or
* will be resolved or rejected , ` then ` calls one of the success or error callbacks asynchronously
* as soon as the result is available . The callbacks are called with a single argument : the result
* or rejection reason . Additionally , the notify callback may be called zero or more times to
* provide a progress indication , before the promise is resolved or rejected .
*
* This method * returns a new promise * which is resolved or rejected via the return value of the
2016-04-18 12:34:29 +00:00
* ` successCallback ` , ` errorCallback ` . It also notifies via the return value of the
2018-05-05 12:13:16 +02:00
* ` notifyCallback ` method . The promise cannot be resolved or rejected from the notifyCallback
2016-04-18 12:34:29 +00:00
* method .
2016-03-28 10:46:51 +00:00
*
* - ` catch(errorCallback) ` – shorthand for ` promise.then(null, errorCallback) `
*
2018-05-05 12:13:16 +02:00
* - ` finally(callback, notifyCallback) ` – allows you to observe either the fulfillment or rejection of a promise ,
2016-03-28 10:46:51 +00:00
* but to do so without modifying the final value . This is useful to release resources or do some
* clean - up that needs to be done whether the promise was rejected or resolved . See the [ full
* specification ] ( https : //github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for
* more information .
*
* # Chaining promises
*
* Because calling the ` then ` method of a promise returns a new derived promise , it is easily
* possible to create a chain of promises :
*
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
* promiseB = promiseA . then ( function ( result ) {
* return result + 1 ;
* } ) ;
*
* // promiseB will be resolved immediately after promiseA is resolved and its value
* // will be the result of promiseA incremented by 1
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
* It is possible to create chains of any length and since a promise can be resolved with another
* promise ( which will defer its resolution further ) , it is possible to pause / defer resolution of
* the promises at any point in the chain . This makes it possible to implement powerful APIs like
* $http ' s response interceptors .
*
*
* # Differences between Kris Kowal ' s Q and $q
*
* There are two main differences :
*
* - $q is integrated with the { @ link ng . $rootScope . Scope } Scope model observation
* mechanism in angular , which means faster propagation of resolution or rejection into your
* models and avoiding unnecessary browser repaints , which would result in flickering UI .
* - Q has many more features than $q , but that comes at a cost of bytes . $q is tiny , but contains
* all the important functionality needed for common async tasks .
*
2016-04-18 12:34:29 +00:00
* # Testing
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
* it ( 'should simulate promise' , inject ( function ( $q , $rootScope ) {
* var deferred = $q . defer ( ) ;
* var promise = deferred . promise ;
* var resolvedValue ;
*
* promise . then ( function ( value ) { resolvedValue = value ; } ) ;
* expect ( resolvedValue ) . toBeUndefined ( ) ;
*
* // Simulate resolving of promise
* deferred . resolve ( 123 ) ;
* // Note that the 'then' function does not get called synchronously.
* // This is because we want the promise API to always be async, whether or not
* // it got called synchronously or asynchronously.
* expect ( resolvedValue ) . toBeUndefined ( ) ;
*
* // Propagate promise resolution to 'then' functions using $apply().
* $rootScope . $apply ( ) ;
* expect ( resolvedValue ) . toEqual ( 123 ) ;
* } ) ) ;
2018-05-05 12:13:16 +02:00
* ` ` `
*
* @ param { function ( function , function ) } resolver Function which is responsible for resolving or
* rejecting the newly created promise . The first parameter is a function which resolves the
* promise , the second parameter is a function which rejects the promise .
*
* @ returns { Promise } The newly created promise .
2016-03-28 10:46:51 +00:00
* /
function $QProvider ( ) {
this . $get = [ '$rootScope' , '$exceptionHandler' , function ( $rootScope , $exceptionHandler ) {
return qFactory ( function ( callback ) {
$rootScope . $evalAsync ( callback ) ;
} , $exceptionHandler ) ;
} ] ;
}
2018-05-05 12:13:16 +02:00
function $$QProvider ( ) {
this . $get = [ '$browser' , '$exceptionHandler' , function ( $browser , $exceptionHandler ) {
return qFactory ( function ( callback ) {
$browser . defer ( callback ) ;
} , $exceptionHandler ) ;
} ] ;
}
2016-03-28 10:46:51 +00:00
/ * *
* Constructs a promise manager .
*
* @ param { function ( function ) } nextTick Function for executing functions in the next turn .
* @ param { function ( ... * ) } exceptionHandler Function into which unexpected exceptions are passed for
* debugging purposes .
* @ returns { object } Promise manager .
* /
function qFactory ( nextTick , exceptionHandler ) {
2018-05-05 12:13:16 +02:00
var $qMinErr = minErr ( '$q' , TypeError ) ;
function callOnce ( self , resolveFn , rejectFn ) {
var called = false ;
function wrap ( fn ) {
return function ( value ) {
if ( called ) return ;
called = true ;
fn . call ( self , value ) ;
} ;
}
return [ wrap ( resolveFn ) , wrap ( rejectFn ) ] ;
}
2016-03-28 10:46:51 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
2016-03-28 10:46:51 +00:00
* @ name ng . $q # defer
2018-05-05 12:13:16 +02:00
* @ kind function
*
2016-03-28 10:46:51 +00:00
* @ description
* Creates a ` Deferred ` object which represents a task which will finish in the future .
*
* @ returns { Deferred } Returns a new instance of deferred .
* /
var defer = function ( ) {
2018-05-05 12:13:16 +02:00
return new Deferred ( ) ;
} ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
function Promise ( ) {
this . $$state = { status : 0 } ;
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
Promise . prototype = {
then : function ( onFulfilled , onRejected , progressBack ) {
var result = new Deferred ( ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
this . $$state . pending = this . $$state . pending || [ ] ;
this . $$state . pending . push ( [ result , onFulfilled , onRejected , progressBack ] ) ;
if ( this . $$state . status > 0 ) scheduleProcessQueue ( this . $$state ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
return result . promise ;
} ,
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
"catch" : function ( callback ) {
return this . then ( null , callback ) ;
} ,
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
"finally" : function ( callback , progressBack ) {
return this . then ( function ( value ) {
return handleCallback ( value , true , callback ) ;
} , function ( error ) {
return handleCallback ( error , false , callback ) ;
} , progressBack ) ;
}
} ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
//Faster, more basic than angular.bind http://jsperf.com/angular-bind-vs-custom-vs-native
function simpleBind ( context , fn ) {
return function ( value ) {
fn . call ( context , value ) ;
} ;
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
function processQueue ( state ) {
var fn , promise , pending ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
pending = state . pending ;
state . processScheduled = false ;
state . pending = undefined ;
for ( var i = 0 , ii = pending . length ; i < ii ; ++ i ) {
promise = pending [ i ] [ 0 ] ;
fn = pending [ i ] [ state . status ] ;
try {
if ( isFunction ( fn ) ) {
promise . resolve ( fn ( state . value ) ) ;
} else if ( state . status === 1 ) {
promise . resolve ( state . value ) ;
} else {
promise . reject ( state . value ) ;
}
} catch ( e ) {
promise . reject ( e ) ;
exceptionHandler ( e ) ;
}
}
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
function scheduleProcessQueue ( state ) {
if ( state . processScheduled || ! state . pending ) return ;
state . processScheduled = true ;
nextTick ( function ( ) { processQueue ( state ) ; } ) ;
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
function Deferred ( ) {
this . promise = new Promise ( ) ;
//Necessary to support unbound execution :/
this . resolve = simpleBind ( this , this . resolve ) ;
this . reject = simpleBind ( this , this . reject ) ;
this . notify = simpleBind ( this , this . notify ) ;
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
Deferred . prototype = {
resolve : function ( val ) {
if ( this . promise . $$state . status ) return ;
if ( val === this . promise ) {
this . $$reject ( $qMinErr (
'qcycle' ,
"Expected promise to be resolved with value other than itself '{0}'" ,
val ) ) ;
} else {
this . $$resolve ( val ) ;
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
} ,
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
$$resolve : function ( val ) {
var then , fns ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
fns = callOnce ( this , this . $$resolve , this . $$reject ) ;
try {
if ( ( isObject ( val ) || isFunction ( val ) ) ) then = val && val . then ;
if ( isFunction ( then ) ) {
this . promise . $$state . status = - 1 ;
then . call ( val , fns [ 0 ] , fns [ 1 ] , this . notify ) ;
} else {
this . promise . $$state . value = val ;
this . promise . $$state . status = 1 ;
scheduleProcessQueue ( this . promise . $$state ) ;
2016-04-18 12:34:29 +00:00
}
2018-05-05 12:13:16 +02:00
} catch ( e ) {
fns [ 1 ] ( e ) ;
exceptionHandler ( e ) ;
2016-04-18 12:34:29 +00:00
}
2018-05-05 12:13:16 +02:00
} ,
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
reject : function ( reason ) {
if ( this . promise . $$state . status ) return ;
this . $$reject ( reason ) ;
} ,
$$reject : function ( reason ) {
this . promise . $$state . value = reason ;
this . promise . $$state . status = 2 ;
scheduleProcessQueue ( this . promise . $$state ) ;
} ,
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
notify : function ( progress ) {
var callbacks = this . promise . $$state . pending ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
if ( ( this . promise . $$state . status <= 0 ) && callbacks && callbacks . length ) {
2016-03-28 10:46:51 +00:00
nextTick ( function ( ) {
2018-05-05 12:13:16 +02:00
var callback , result ;
for ( var i = 0 , ii = callbacks . length ; i < ii ; i ++ ) {
result = callbacks [ i ] [ 0 ] ;
callback = callbacks [ i ] [ 3 ] ;
try {
result . notify ( isFunction ( callback ) ? callback ( progress ) : progress ) ;
} catch ( e ) {
exceptionHandler ( e ) ;
}
}
2016-03-28 10:46:51 +00:00
} ) ;
}
2018-05-05 12:13:16 +02:00
}
2016-04-18 12:34:29 +00:00
} ;
2016-03-28 10:46:51 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $q # reject
* @ kind function
*
2016-03-28 10:46:51 +00:00
* @ description
* Creates a promise that is resolved as rejected with the specified ` reason ` . This api should be
* used to forward rejection in a chain of promises . If you are dealing with the last promise in
* a promise chain , you don ' t need to worry about it .
*
* When comparing deferreds / promises to the familiar behavior of try / c a t c h / t h r o w , t h i n k o f
* ` reject ` as the ` throw ` keyword in JavaScript . This also means that if you "catch" an error via
* a promise error callback and you want to forward the error to the promise derived from the
* current promise , you have to "rethrow" the error by returning a rejection constructed via
* ` reject ` .
*
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
* promiseB = promiseA . then ( function ( result ) {
* // success: do something and resolve promiseB
* // with the old or a new result
* return result ;
* } , function ( reason ) {
* // error: handle the error if possible and
* // resolve promiseB with newPromiseOrValue,
* // otherwise forward the rejection to promiseB
* if ( canHandle ( reason ) ) {
* // handle the error and recover
* return newPromiseOrValue ;
* }
* return $q . reject ( reason ) ;
* } ) ;
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
* @ param { * } reason Constant , message , exception or an object representing the rejection reason .
* @ returns { Promise } Returns a promise that was already resolved as rejected with the ` reason ` .
* /
var reject = function ( reason ) {
2018-05-05 12:13:16 +02:00
var result = new Deferred ( ) ;
result . reject ( reason ) ;
return result . promise ;
2016-03-28 10:46:51 +00:00
} ;
2018-05-05 12:13:16 +02:00
var makePromise = function makePromise ( value , resolved ) {
var result = new Deferred ( ) ;
if ( resolved ) {
result . resolve ( value ) ;
} else {
result . reject ( value ) ;
}
return result . promise ;
} ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
var handleCallback = function handleCallback ( value , isResolved , callback ) {
var callbackOutput = null ;
try {
if ( isFunction ( callback ) ) callbackOutput = callback ( ) ;
} catch ( e ) {
return makePromise ( e , false ) ;
}
if ( isPromiseLike ( callbackOutput ) ) {
return callbackOutput . then ( function ( ) {
return makePromise ( value , isResolved ) ;
} , function ( error ) {
return makePromise ( error , false ) ;
} ) ;
} else {
return makePromise ( value , isResolved ) ;
}
} ;
/ * *
* @ ngdoc method
* @ name $q # when
* @ kind function
*
* @ description
2016-03-28 10:46:51 +00:00
* Wraps an object that might be a value or a ( 3 rd party ) then - able promise into a $q promise .
* This is useful when you are dealing with an object that might or might not be a promise , or if
* the promise comes from a source that can ' t be trusted .
*
* @ param { * } value Value or a promise
* @ returns { Promise } Returns a promise of the passed value or promise
* /
2018-05-05 12:13:16 +02:00
var when = function ( value , callback , errback , progressBack ) {
var result = new Deferred ( ) ;
result . resolve ( value ) ;
return result . promise . then ( callback , errback , progressBack ) ;
2016-03-28 10:46:51 +00:00
} ;
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $q # all
* @ kind function
*
2016-03-28 10:46:51 +00:00
* @ description
* Combines multiple promises into a single promise that is resolved when all of the input
* promises are resolved .
*
* @ param { Array . < Promise > | Object . < Promise > } promises An array or hash of promises .
* @ returns { Promise } Returns a single promise that will be resolved with an array / hash of values ,
* each value corresponding to the promise at the same index / key in the ` promises ` array / hash .
* If any of the promises is resolved with a rejection , this resulting promise will be rejected
* with the same rejection value .
* /
2018-05-05 12:13:16 +02:00
2016-03-28 10:46:51 +00:00
function all ( promises ) {
2018-05-05 12:13:16 +02:00
var deferred = new Deferred ( ) ,
2016-03-28 10:46:51 +00:00
counter = 0 ,
results = isArray ( promises ) ? [ ] : { } ;
forEach ( promises , function ( promise , key ) {
counter ++ ;
2018-05-05 12:13:16 +02:00
when ( promise ) . then ( function ( value ) {
2016-03-28 10:46:51 +00:00
if ( results . hasOwnProperty ( key ) ) return ;
results [ key ] = value ;
if ( ! ( -- counter ) ) deferred . resolve ( results ) ;
} , function ( reason ) {
if ( results . hasOwnProperty ( key ) ) return ;
deferred . reject ( reason ) ;
} ) ;
} ) ;
if ( counter === 0 ) {
deferred . resolve ( results ) ;
}
return deferred . promise ;
}
2018-05-05 12:13:16 +02:00
var $Q = function Q ( resolver ) {
if ( ! isFunction ( resolver ) ) {
throw $qMinErr ( 'norslvr' , "Expected resolverFn, got '{0}'" , resolver ) ;
}
if ( ! ( this instanceof Q ) ) {
// More useful when $Q is the Promise itself.
return new Q ( resolver ) ;
}
var deferred = new Deferred ( ) ;
function resolveFn ( value ) {
deferred . resolve ( value ) ;
}
function rejectFn ( reason ) {
deferred . reject ( reason ) ;
}
resolver ( resolveFn , rejectFn ) ;
return deferred . promise ;
2016-03-28 10:46:51 +00:00
} ;
2018-05-05 12:13:16 +02:00
$Q . defer = defer ;
$Q . reject = reject ;
$Q . when = when ;
$Q . all = all ;
return $Q ;
}
function $$RAFProvider ( ) { //rAF
this . $get = [ '$window' , '$timeout' , function ( $window , $timeout ) {
var requestAnimationFrame = $window . requestAnimationFrame ||
$window . webkitRequestAnimationFrame ;
var cancelAnimationFrame = $window . cancelAnimationFrame ||
$window . webkitCancelAnimationFrame ||
$window . webkitCancelRequestAnimationFrame ;
var rafSupported = ! ! requestAnimationFrame ;
var rafFn = rafSupported
? function ( fn ) {
var id = requestAnimationFrame ( fn ) ;
return function ( ) {
cancelAnimationFrame ( id ) ;
} ;
}
: function ( fn ) {
var timer = $timeout ( fn , 16.66 , false ) ; // 1000 / 60 = 16.666
return function ( ) {
$timeout . cancel ( timer ) ;
} ;
} ;
queueFn . supported = rafSupported ;
var cancelLastRAF ;
var taskCount = 0 ;
var taskQueue = [ ] ;
return queueFn ;
function flush ( ) {
for ( var i = 0 ; i < taskQueue . length ; i ++ ) {
var task = taskQueue [ i ] ;
if ( task ) {
taskQueue [ i ] = null ;
task ( ) ;
}
}
taskCount = taskQueue . length = 0 ;
}
function queueFn ( asyncFn ) {
var index = taskQueue . length ;
taskCount ++ ;
taskQueue . push ( asyncFn ) ;
if ( index === 0 ) {
cancelLastRAF = rafFn ( flush ) ;
}
return function cancelQueueFn ( ) {
if ( index >= 0 ) {
taskQueue [ index ] = null ;
index = null ;
if ( -- taskCount === 0 && cancelLastRAF ) {
cancelLastRAF ( ) ;
cancelLastRAF = null ;
taskQueue . length = 0 ;
}
}
} ;
}
} ] ;
2016-03-28 10:46:51 +00:00
}
/ * *
* DESIGN NOTES
*
* The design decisions behind the scope are heavily favored for speed and memory consumption .
*
* The typical use of scope is to watch the expressions , which most of the time return the same
* value as last time so we optimize the operation .
*
* Closures construction is expensive in terms of speed as well as memory :
* - No closures , instead use prototypical inheritance for API
* - Internal state needs to be stored on scope directly , which means that private state is
* exposed as $$ _ _ _ _ properties
*
* Loop operations are optimized by using while ( count -- ) { ... }
2016-04-18 12:34:29 +00:00
* - this means that in order to keep the same order of execution as addition we have to add
2018-05-05 12:13:16 +02:00
* items to the array at the beginning ( unshift ) instead of at the end ( push )
2016-03-28 10:46:51 +00:00
*
* Child scopes are created and removed often
2016-04-18 12:34:29 +00:00
* - Using an array would be slow since inserts in middle are expensive so we use linked list
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* There are few watches then a lot of observers . This is why you don ' t want the observer to be
* implemented in the same way as watch . Watch requires return of initialization function which
* are expensive to construct .
2016-03-28 10:46:51 +00:00
* /
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc provider
* @ name $rootScopeProvider
2016-03-28 10:46:51 +00:00
* @ description
*
* Provider for the $rootScope service .
* /
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $rootScopeProvider # digestTtl
2016-03-28 10:46:51 +00:00
* @ description
*
* Sets the number of ` $ digest ` iterations the scope should attempt to execute before giving up and
* assuming that the model is unstable .
*
* The current default is 10 iterations .
*
* In complex applications it ' s possible that the dependencies between ` $ watch ` s will result in
* several digest iterations . However if an application needs more than the default 10 digest
* iterations for its model to stabilize then you should investigate what is causing the model to
* continuously change during the digest .
*
* Increasing the TTL could have performance implications , so you should not change it without
* proper justification .
*
* @ param { number } limit The number of digest iterations .
* /
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc service
* @ name $rootScope
2016-03-28 10:46:51 +00:00
* @ description
*
* Every application has a single root { @ link ng . $rootScope . Scope scope } .
* All other scopes are descendant scopes of the root scope . Scopes provide separation
* between the model and the view , via a mechanism for watching the model for changes .
2016-04-18 12:34:29 +00:00
* They also provide an event emission / broadcast and subscription facility . See the
2016-03-28 10:46:51 +00:00
* { @ link guide / scope developer guide on scopes } .
* /
2018-05-05 12:13:16 +02:00
function $RootScopeProvider ( ) {
2016-03-28 10:46:51 +00:00
var TTL = 10 ;
var $rootScopeMinErr = minErr ( '$rootScope' ) ;
var lastDirtyWatch = null ;
2018-05-05 12:13:16 +02:00
var applyAsyncId = null ;
2016-03-28 10:46:51 +00:00
this . digestTtl = function ( value ) {
if ( arguments . length ) {
TTL = value ;
}
return TTL ;
} ;
2018-05-05 12:13:16 +02:00
function createChildScopeClass ( parent ) {
function ChildScope ( ) {
this . $$watchers = this . $$nextSibling =
this . $$childHead = this . $$childTail = null ;
this . $$listeners = { } ;
this . $$listenerCount = { } ;
this . $id = nextUid ( ) ;
this . $$ChildScope = null ;
}
ChildScope . prototype = parent ;
return ChildScope ;
}
2016-04-18 12:34:29 +00:00
this . $get = [ '$injector' , '$exceptionHandler' , '$parse' , '$browser' ,
2018-05-05 12:13:16 +02:00
function ( $injector , $exceptionHandler , $parse , $browser ) {
function destroyChildScope ( $event ) {
$event . currentScope . $$destroyed = true ;
}
2016-03-28 10:46:51 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc type
* @ name $rootScope . Scope
2016-03-28 10:46:51 +00:00
*
* @ description
* A root scope can be retrieved using the { @ link ng . $rootScope $rootScope } key from the
2018-05-05 12:13:16 +02:00
* { @ link auto . $injector $injector } . Child scopes are created using the
* { @ link ng . $rootScope . Scope # $new $new ( ) } method . ( Most scopes are created automatically when
* compiled HTML template is executed . ) See also the { @ link guide / scope Scopes guide } for
* an in - depth introduction and usage examples .
2016-03-28 10:46:51 +00:00
*
*
* # Inheritance
* A scope can inherit from a parent scope , as in this example :
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
var parent = $rootScope ;
var child = parent . $new ( ) ;
parent . salutation = "Hello" ;
expect ( child . salutation ) . toEqual ( 'Hello' ) ;
child . salutation = "Welcome" ;
expect ( child . salutation ) . toEqual ( 'Welcome' ) ;
expect ( parent . salutation ) . toEqual ( 'Hello' ) ;
2018-05-05 12:13:16 +02:00
* ` ` `
*
* When interacting with ` Scope ` in tests , additional helper methods are available on the
* instances of ` Scope ` type . See { @ link ngMock . $rootScope . Scope ngMock Scope } for additional
* details .
2016-03-28 10:46:51 +00:00
*
*
* @ param { Object . < string , function ( ) >= } providers Map of service factory which need to be
* provided for the current scope . Defaults to { @ link ng } .
* @ param { Object . < string , * >= } instanceCache Provides pre - instantiated services which should
* append / override services provided by ` providers ` . This is handy
* when unit - testing and having the need to override a default
* service .
* @ returns { Object } Newly created scope .
*
* /
function Scope ( ) {
this . $id = nextUid ( ) ;
this . $$phase = this . $parent = this . $$watchers =
this . $$nextSibling = this . $$prevSibling =
this . $$childHead = this . $$childTail = null ;
2018-05-05 12:13:16 +02:00
this . $root = this ;
2016-03-28 10:46:51 +00:00
this . $$destroyed = false ;
this . $$listeners = { } ;
this . $$listenerCount = { } ;
2018-05-05 12:13:16 +02:00
this . $$isolateBindings = null ;
2016-03-28 10:46:51 +00:00
}
/ * *
* @ ngdoc property
2018-05-05 12:13:16 +02:00
* @ name $rootScope . Scope # $id
*
* @ description
* Unique scope ID ( monotonically increasing ) useful for debugging .
2016-03-28 10:46:51 +00:00
* /
2018-05-05 12:13:16 +02:00
/ * *
* @ ngdoc property
* @ name $rootScope . Scope # $parent
*
* @ description
* Reference to the parent scope .
* /
/ * *
* @ ngdoc property
* @ name $rootScope . Scope # $root
*
* @ description
* Reference to the root scope .
* /
2016-03-28 10:46:51 +00:00
Scope . prototype = {
constructor : Scope ,
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $rootScope . Scope # $new
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Creates a new child { @ link ng . $rootScope . Scope scope } .
*
2018-05-05 12:13:16 +02:00
* The parent scope will propagate the { @ link ng . $rootScope . Scope # $digest $digest ( ) } event .
* The scope can be removed from the scope hierarchy using { @ link ng . $rootScope . Scope # $destroy $destroy ( ) } .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* { @ link ng . $rootScope . Scope # $destroy $destroy ( ) } must be called on a scope when it is
2016-03-28 10:46:51 +00:00
* desired for the scope and its child scopes to be permanently detached from the parent and
* thus stop participating in model change detection and listener notification by invoking .
*
* @ param { boolean } isolate If true , then the scope does not prototypically inherit from the
* parent scope . The scope is isolated , as it can not see parent scope properties .
* When creating widgets , it is useful for the widget to not accidentally read parent
* state .
*
2018-05-05 12:13:16 +02:00
* @ param { Scope } [ parent = this ] The { @ link ng . $rootScope . Scope ` Scope ` } that will be the ` $ parent `
* of the newly created scope . Defaults to ` this ` scope if not provided .
* This is used when creating a transclude scope to correctly place it
* in the scope hierarchy while maintaining the correct prototypical
* inheritance .
*
2016-03-28 10:46:51 +00:00
* @ returns { Object } The newly created child scope .
*
* /
2018-05-05 12:13:16 +02:00
$new : function ( isolate , parent ) {
var child ;
parent = parent || this ;
2016-03-28 10:46:51 +00:00
if ( isolate ) {
child = new Scope ( ) ;
child . $root = this . $root ;
} else {
2018-05-05 12:13:16 +02:00
// Only create a child scope class if somebody asks for one,
// but cache it to allow the VM to optimize lookups.
if ( ! this . $$ChildScope ) {
this . $$ChildScope = createChildScopeClass ( this ) ;
}
child = new this . $$ChildScope ( ) ;
2016-04-18 12:34:29 +00:00
}
2018-05-05 12:13:16 +02:00
child . $parent = parent ;
child . $$prevSibling = parent . $$childTail ;
if ( parent . $$childHead ) {
parent . $$childTail . $$nextSibling = child ;
parent . $$childTail = child ;
2016-03-28 10:46:51 +00:00
} else {
2018-05-05 12:13:16 +02:00
parent . $$childHead = parent . $$childTail = child ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
// When the new scope is not isolated or we inherit from `this`, and
// the parent scope is destroyed, the property `$$destroyed` is inherited
// prototypically. In all other cases, this property needs to be set
// when the parent scope is destroyed.
// The listener needs to be added after the parent is set
if ( isolate || parent != this ) child . $on ( '$destroy' , destroyChildScope ) ;
2016-03-28 10:46:51 +00:00
return child ;
} ,
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $rootScope . Scope # $watch
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Registers a ` listener ` callback to be executed whenever the ` watchExpression ` changes .
*
2018-05-05 12:13:16 +02:00
* - The ` watchExpression ` is called on every call to { @ link ng . $rootScope . Scope # $digest
2016-04-18 12:34:29 +00:00
* $digest ( ) } and should return the value that will be watched . ( Since
2018-05-05 12:13:16 +02:00
* { @ link ng . $rootScope . Scope # $digest $digest ( ) } reruns when it detects changes the
2016-04-18 12:34:29 +00:00
* ` watchExpression ` can execute multiple times per
2018-05-05 12:13:16 +02:00
* { @ link ng . $rootScope . Scope # $digest $digest ( ) } and should be idempotent . )
2016-03-28 10:46:51 +00:00
* - The ` listener ` is called only when the value from the current ` watchExpression ` and the
* previous call to ` watchExpression ` are not equal ( with the exception of the initial run ,
2018-05-05 12:13:16 +02:00
* see below ) . Inequality is determined according to reference inequality ,
* [ strict comparison ] ( https : //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators)
* via the ` !== ` Javascript operator , unless ` objectEquality == true `
* ( see next point )
* - When ` objectEquality == true ` , inequality of the ` watchExpression ` is determined
* according to the { @ link angular . equals } function . To save the value of the object for
* later comparison , the { @ link angular . copy } function is used . This therefore means that
* watching complex objects will have adverse memory and performance implications .
2016-03-28 10:46:51 +00:00
* - The watch ` listener ` may change the model , which may trigger other ` listener ` s to fire .
* This is achieved by rerunning the watchers until no changes are detected . The rerun
* iteration limit is 10 to prevent an infinite loop deadlock .
*
*
2018-05-05 12:13:16 +02:00
* If you want to be notified whenever { @ link ng . $rootScope . Scope # $digest $digest } is called ,
* you can register a ` watchExpression ` function with no ` listener ` . ( Be prepared for
* multiple calls to your ` watchExpression ` because it will execute multiple times in a
* single { @ link ng . $rootScope . Scope # $digest $digest } cycle if a change is detected . )
2016-03-28 10:46:51 +00:00
*
* After a watcher is registered with the scope , the ` listener ` fn is called asynchronously
2018-05-05 12:13:16 +02:00
* ( via { @ link ng . $rootScope . Scope # $evalAsync $evalAsync } ) to initialize the
2016-03-28 10:46:51 +00:00
* watcher . In rare cases , this is undesirable because the listener is called when the result
* of ` watchExpression ` didn ' t change . To detect this scenario within the ` listener ` fn , you
* can compare the ` newVal ` and ` oldVal ` . If these two values are identical ( ` === ` ) then the
* listener was called due to initialization .
*
*
*
* # Example
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
// let's assume that scope was dependency injected as the $rootScope
var scope = $rootScope ;
scope . name = 'misko' ;
scope . counter = 0 ;
expect ( scope . counter ) . toEqual ( 0 ) ;
scope . $watch ( 'name' , function ( newValue , oldValue ) {
scope . counter = scope . counter + 1 ;
} ) ;
expect ( scope . counter ) . toEqual ( 0 ) ;
scope . $digest ( ) ;
2018-05-05 12:13:16 +02:00
// the listener is always called during the first $digest loop after it was registered
expect ( scope . counter ) . toEqual ( 1 ) ;
2016-03-28 10:46:51 +00:00
scope . $digest ( ) ;
2018-05-05 12:13:16 +02:00
// but now it will not be called unless the value changes
2016-04-18 12:34:29 +00:00
expect ( scope . counter ) . toEqual ( 1 ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
scope . name = 'adam' ;
scope . $digest ( ) ;
expect ( scope . counter ) . toEqual ( 2 ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
// Using a function as a watchExpression
2016-03-28 10:46:51 +00:00
var food ;
scope . foodCounter = 0 ;
expect ( scope . foodCounter ) . toEqual ( 0 ) ;
scope . $watch (
2018-05-05 12:13:16 +02:00
// This function returns the value being watched. It is called for each turn of the $digest loop
2016-03-28 10:46:51 +00:00
function ( ) { return food ; } ,
2018-05-05 12:13:16 +02:00
// This is the change listener, called when the value returned from the above function changes
2016-03-28 10:46:51 +00:00
function ( newValue , oldValue ) {
if ( newValue !== oldValue ) {
// Only increment the counter if the value changed
scope . foodCounter = scope . foodCounter + 1 ;
}
}
) ;
// No digest has been run so the counter will be zero
expect ( scope . foodCounter ) . toEqual ( 0 ) ;
// Run the digest but since food has not changed count will still be zero
scope . $digest ( ) ;
expect ( scope . foodCounter ) . toEqual ( 0 ) ;
// Update food and run digest. Now the counter will increment
food = 'cheeseburger' ;
scope . $digest ( ) ;
expect ( scope . foodCounter ) . toEqual ( 1 ) ;
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
*
*
* @ param { ( function ( ) | string ) } watchExpression Expression that is evaluated on each
2018-05-05 12:13:16 +02:00
* { @ link ng . $rootScope . Scope # $digest $digest } cycle . A change in the return value triggers
2016-03-28 10:46:51 +00:00
* a call to the ` listener ` .
*
* - ` string ` : Evaluated as { @ link guide / expression expression }
* - ` function(scope) ` : called with current ` scope ` as a parameter .
2018-05-05 12:13:16 +02:00
* @ param { function ( newVal , oldVal , scope ) } listener Callback called whenever the value
* of ` watchExpression ` changes .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* - ` newVal ` contains the current value of the ` watchExpression `
* - ` oldVal ` contains the previous value of the ` watchExpression `
* - ` scope ` refers to the current scope
* @ param { boolean = } objectEquality Compare for object equality using { @ link angular . equals } instead of
* comparing for reference equality .
2016-03-28 10:46:51 +00:00
* @ returns { function ( ) } Returns a deregistration function for this listener .
* /
2016-04-18 12:34:29 +00:00
$watch : function ( watchExp , listener , objectEquality ) {
2018-05-05 12:13:16 +02:00
var get = $parse ( watchExp ) ;
if ( get . $$watchDelegate ) {
return get . $$watchDelegate ( this , listener , objectEquality , get ) ;
}
2016-03-28 10:46:51 +00:00
var scope = this ,
array = scope . $$watchers ,
watcher = {
fn : listener ,
last : initWatchVal ,
get : get ,
2016-04-18 12:34:29 +00:00
exp : watchExp ,
2016-03-28 10:46:51 +00:00
eq : ! ! objectEquality
} ;
lastDirtyWatch = null ;
if ( ! isFunction ( listener ) ) {
2018-05-05 12:13:16 +02:00
watcher . fn = noop ;
2016-03-28 10:46:51 +00:00
}
if ( ! array ) {
array = scope . $$watchers = [ ] ;
}
// we use unshift since we use a while loop in $digest for speed.
// the while loop reads in reverse order.
array . unshift ( watcher ) ;
2018-05-05 12:13:16 +02:00
return function deregisterWatch ( ) {
2016-04-18 12:34:29 +00:00
arrayRemove ( array , watcher ) ;
2016-03-28 10:46:51 +00:00
lastDirtyWatch = null ;
} ;
} ,
2018-05-05 12:13:16 +02:00
/ * *
* @ ngdoc method
* @ name $rootScope . Scope # $watchGroup
* @ kind function
*
* @ description
* A variant of { @ link ng . $rootScope . Scope # $watch $watch ( ) } where it watches an array of ` watchExpressions ` .
* If any one expression in the collection changes the ` listener ` is executed .
*
* - The items in the ` watchExpressions ` array are observed via standard $watch operation and are examined on every
* call to $digest ( ) to see if any items changes .
* - The ` listener ` is called whenever any expression in the ` watchExpressions ` array changes .
*
* @ param { Array . < string | Function ( scope ) > } watchExpressions Array of expressions that will be individually
* watched using { @ link ng . $rootScope . Scope # $watch $watch ( ) }
*
* @ param { function ( newValues , oldValues , scope ) } listener Callback called whenever the return value of any
* expression in ` watchExpressions ` changes
* The ` newValues ` array contains the current values of the ` watchExpressions ` , with the indexes matching
* those of ` watchExpression `
* and the ` oldValues ` array contains the previous values of the ` watchExpressions ` , with the indexes matching
* those of ` watchExpression `
* The ` scope ` refers to the current scope .
* @ returns { function ( ) } Returns a de - registration function for all listeners .
* /
$watchGroup : function ( watchExpressions , listener ) {
var oldValues = new Array ( watchExpressions . length ) ;
var newValues = new Array ( watchExpressions . length ) ;
var deregisterFns = [ ] ;
var self = this ;
var changeReactionScheduled = false ;
var firstRun = true ;
if ( ! watchExpressions . length ) {
// No expressions means we call the listener ASAP
var shouldCall = true ;
self . $evalAsync ( function ( ) {
if ( shouldCall ) listener ( newValues , newValues , self ) ;
} ) ;
return function deregisterWatchGroup ( ) {
shouldCall = false ;
} ;
}
if ( watchExpressions . length === 1 ) {
// Special case size of one
return this . $watch ( watchExpressions [ 0 ] , function watchGroupAction ( value , oldValue , scope ) {
newValues [ 0 ] = value ;
oldValues [ 0 ] = oldValue ;
listener ( newValues , ( value === oldValue ) ? newValues : oldValues , scope ) ;
} ) ;
}
forEach ( watchExpressions , function ( expr , i ) {
var unwatchFn = self . $watch ( expr , function watchGroupSubAction ( value , oldValue ) {
newValues [ i ] = value ;
oldValues [ i ] = oldValue ;
if ( ! changeReactionScheduled ) {
changeReactionScheduled = true ;
self . $evalAsync ( watchGroupAction ) ;
}
} ) ;
deregisterFns . push ( unwatchFn ) ;
} ) ;
function watchGroupAction ( ) {
changeReactionScheduled = false ;
if ( firstRun ) {
firstRun = false ;
listener ( newValues , newValues , self ) ;
} else {
listener ( newValues , oldValues , self ) ;
}
}
return function deregisterWatchGroup ( ) {
while ( deregisterFns . length ) {
deregisterFns . shift ( ) ( ) ;
}
} ;
} ,
2016-04-18 12:34:29 +00:00
2016-03-28 10:46:51 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $rootScope . Scope # $watchCollection
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
2016-04-18 12:34:29 +00:00
* Shallow watches the properties of an object and fires whenever any of the properties change
* ( for arrays , this implies watching the array items ; for object maps , this implies watching
* the properties ) . If a change is detected , the ` listener ` callback is fired .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* - The ` obj ` collection is observed via standard $watch operation and is examined on every
* call to $digest ( ) to see if any items have been added , removed , or moved .
* - The ` listener ` is called whenever anything within the ` obj ` has changed . Examples include
* adding , removing , and moving items belonging to an object or array .
2016-03-28 10:46:51 +00:00
*
*
* # Example
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
$scope . names = [ 'igor' , 'matias' , 'misko' , 'james' ] ;
$scope . dataCount = 4 ;
$scope . $watchCollection ( 'names' , function ( newNames , oldNames ) {
$scope . dataCount = newNames . length ;
} ) ;
expect ( $scope . dataCount ) . toEqual ( 4 ) ;
$scope . $digest ( ) ;
//still at 4 ... no changes
expect ( $scope . dataCount ) . toEqual ( 4 ) ;
$scope . names . pop ( ) ;
$scope . $digest ( ) ;
//now there's been a change
expect ( $scope . dataCount ) . toEqual ( 3 ) ;
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
*
2018-05-05 12:13:16 +02:00
* @ param { string | function ( scope ) } obj Evaluated as { @ link guide / expression expression } . The
2016-03-28 10:46:51 +00:00
* expression value should evaluate to an object or an array which is observed on each
2018-05-05 12:13:16 +02:00
* { @ link ng . $rootScope . Scope # $digest $digest } cycle . Any shallow change within the
2016-03-28 10:46:51 +00:00
* collection will trigger a call to the ` listener ` .
*
2018-05-05 12:13:16 +02:00
* @ param { function ( newCollection , oldCollection , scope ) } listener a callback function called
* when a change is detected .
* - The ` newCollection ` object is the newly modified data obtained from the ` obj ` expression
* - The ` oldCollection ` object is a copy of the former collection data .
* Due to performance considerations , the ` oldCollection ` value is computed only if the
* ` listener ` function declares two or more arguments .
* - The ` scope ` argument refers to the current scope .
2016-03-28 10:46:51 +00:00
*
* @ returns { function ( ) } Returns a de - registration function for this listener . When the
* de - registration function is executed , the internal watch operation is terminated .
* /
$watchCollection : function ( obj , listener ) {
2018-05-05 12:13:16 +02:00
$watchCollectionInterceptor . $stateful = true ;
2016-03-28 10:46:51 +00:00
var self = this ;
2018-05-05 12:13:16 +02:00
// the current value, updated on each dirty-check run
2016-04-18 12:34:29 +00:00
var newValue ;
2018-05-05 12:13:16 +02:00
// a shallow copy of the newValue from the last dirty-check run,
// updated to match newValue during dirty-check run
var oldValue ;
// a shallow copy of the newValue from when the last change happened
var veryOldValue ;
// only track veryOldValue if the listener is asking for it
var trackVeryOldValue = ( listener . length > 1 ) ;
2016-03-28 10:46:51 +00:00
var changeDetected = 0 ;
2018-05-05 12:13:16 +02:00
var changeDetector = $parse ( obj , $watchCollectionInterceptor ) ;
2016-03-28 10:46:51 +00:00
var internalArray = [ ] ;
var internalObject = { } ;
2018-05-05 12:13:16 +02:00
var initRun = true ;
2016-03-28 10:46:51 +00:00
var oldLength = 0 ;
2018-05-05 12:13:16 +02:00
function $watchCollectionInterceptor ( _value ) {
newValue = _value ;
var newLength , key , bothNaN , newItem , oldItem ;
// If the new value is undefined, then return undefined as the watch may be a one-time watch
if ( isUndefined ( newValue ) ) return ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
if ( ! isObject ( newValue ) ) { // if primitive
2016-03-28 10:46:51 +00:00
if ( oldValue !== newValue ) {
oldValue = newValue ;
changeDetected ++ ;
}
} else if ( isArrayLike ( newValue ) ) {
if ( oldValue !== internalArray ) {
// we are transitioning from something which was not an array into array.
oldValue = internalArray ;
oldLength = oldValue . length = 0 ;
changeDetected ++ ;
}
newLength = newValue . length ;
if ( oldLength !== newLength ) {
// if lengths do not match we need to trigger change notification
changeDetected ++ ;
oldValue . length = oldLength = newLength ;
}
// copy the items to oldValue and look for changes.
for ( var i = 0 ; i < newLength ; i ++ ) {
2018-05-05 12:13:16 +02:00
oldItem = oldValue [ i ] ;
newItem = newValue [ i ] ;
bothNaN = ( oldItem !== oldItem ) && ( newItem !== newItem ) ;
if ( ! bothNaN && ( oldItem !== newItem ) ) {
2016-03-28 10:46:51 +00:00
changeDetected ++ ;
2018-05-05 12:13:16 +02:00
oldValue [ i ] = newItem ;
2016-03-28 10:46:51 +00:00
}
}
} else {
if ( oldValue !== internalObject ) {
// we are transitioning from something which was not an object into object.
oldValue = internalObject = { } ;
oldLength = 0 ;
changeDetected ++ ;
}
// copy the items to oldValue and look for changes.
newLength = 0 ;
for ( key in newValue ) {
2016-04-18 12:34:29 +00:00
if ( newValue . hasOwnProperty ( key ) ) {
2016-03-28 10:46:51 +00:00
newLength ++ ;
2018-05-05 12:13:16 +02:00
newItem = newValue [ key ] ;
oldItem = oldValue [ key ] ;
if ( key in oldValue ) {
bothNaN = ( oldItem !== oldItem ) && ( newItem !== newItem ) ;
if ( ! bothNaN && ( oldItem !== newItem ) ) {
2016-03-28 10:46:51 +00:00
changeDetected ++ ;
2018-05-05 12:13:16 +02:00
oldValue [ key ] = newItem ;
2016-03-28 10:46:51 +00:00
}
} else {
oldLength ++ ;
2018-05-05 12:13:16 +02:00
oldValue [ key ] = newItem ;
2016-03-28 10:46:51 +00:00
changeDetected ++ ;
}
}
}
if ( oldLength > newLength ) {
// we used to have more keys, need to find them and destroy them.
changeDetected ++ ;
2018-05-05 12:13:16 +02:00
for ( key in oldValue ) {
if ( ! newValue . hasOwnProperty ( key ) ) {
2016-03-28 10:46:51 +00:00
oldLength -- ;
delete oldValue [ key ] ;
}
}
}
}
return changeDetected ;
}
function $watchCollectionAction ( ) {
2018-05-05 12:13:16 +02:00
if ( initRun ) {
initRun = false ;
listener ( newValue , newValue , self ) ;
} else {
listener ( newValue , veryOldValue , self ) ;
}
// make a copy for the next time a collection is changed
if ( trackVeryOldValue ) {
if ( ! isObject ( newValue ) ) {
//primitive
veryOldValue = newValue ;
} else if ( isArrayLike ( newValue ) ) {
veryOldValue = new Array ( newValue . length ) ;
for ( var i = 0 ; i < newValue . length ; i ++ ) {
veryOldValue [ i ] = newValue [ i ] ;
}
} else { // if object
veryOldValue = { } ;
for ( var key in newValue ) {
if ( hasOwnProperty . call ( newValue , key ) ) {
veryOldValue [ key ] = newValue [ key ] ;
}
}
}
}
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
return this . $watch ( changeDetector , $watchCollectionAction ) ;
2016-03-28 10:46:51 +00:00
} ,
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $rootScope . Scope # $digest
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
2018-05-05 12:13:16 +02:00
* Processes all of the { @ link ng . $rootScope . Scope # $watch watchers } of the current scope and
* its children . Because a { @ link ng . $rootScope . Scope # $watch watcher } ' s listener can change
* the model , the ` $ digest() ` keeps calling the { @ link ng . $rootScope . Scope # $watch watchers }
2016-03-28 10:46:51 +00:00
* until no more listeners are firing . This means that it is possible to get into an infinite
* loop . This function will throw ` 'Maximum iteration limit exceeded.' ` if the number of
* iterations exceeds 10.
*
* Usually , you don ' t call ` $ digest() ` directly in
* { @ link ng . directive : ngController controllers } or in
2018-05-05 12:13:16 +02:00
* { @ link ng . $compileProvider # directive directives } .
* Instead , you should call { @ link ng . $rootScope . Scope # $apply $apply ( ) } ( typically from within
* a { @ link ng . $compileProvider # directive directive } ) , which will force a ` $ digest() ` .
2016-03-28 10:46:51 +00:00
*
* If you want to be notified whenever ` $ digest() ` is called ,
* you can register a ` watchExpression ` function with
2018-05-05 12:13:16 +02:00
* { @ link ng . $rootScope . Scope # $watch $watch ( ) } with no ` listener ` .
2016-03-28 10:46:51 +00:00
*
* In unit tests , you may need to call ` $ digest() ` to simulate the scope life cycle .
*
* # Example
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
var scope = ... ;
scope . name = 'misko' ;
scope . counter = 0 ;
expect ( scope . counter ) . toEqual ( 0 ) ;
scope . $watch ( 'name' , function ( newValue , oldValue ) {
scope . counter = scope . counter + 1 ;
} ) ;
expect ( scope . counter ) . toEqual ( 0 ) ;
scope . $digest ( ) ;
2018-05-05 12:13:16 +02:00
// the listener is always called during the first $digest loop after it was registered
expect ( scope . counter ) . toEqual ( 1 ) ;
2016-03-28 10:46:51 +00:00
scope . $digest ( ) ;
2018-05-05 12:13:16 +02:00
// but now it will not be called unless the value changes
2016-04-18 12:34:29 +00:00
expect ( scope . counter ) . toEqual ( 1 ) ;
2018-05-05 12:13:16 +02:00
scope . name = 'adam' ;
scope . $digest ( ) ;
expect ( scope . counter ) . toEqual ( 2 ) ;
* ` ` `
2016-03-28 10:46:51 +00:00
*
* /
$digest : function ( ) {
2016-04-18 12:34:29 +00:00
var watch , value , last ,
2016-03-28 10:46:51 +00:00
watchers ,
length ,
dirty , ttl = TTL ,
next , current , target = this ,
watchLog = [ ] ,
2016-04-18 12:34:29 +00:00
logIdx , logMsg , asyncTask ;
2016-03-28 10:46:51 +00:00
beginPhase ( '$digest' ) ;
2018-05-05 12:13:16 +02:00
// Check for changes to browser url that happened in sync before the call to $digest
$browser . $$checkUrlChange ( ) ;
if ( this === $rootScope && applyAsyncId !== null ) {
// If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then
// cancel the scheduled $apply and flush the queue of expressions to be evaluated.
$browser . defer . cancel ( applyAsyncId ) ;
flushApplyAsync ( ) ;
}
2016-03-28 10:46:51 +00:00
lastDirtyWatch = null ;
do { // "while dirty" loop
dirty = false ;
current = target ;
2018-05-05 12:13:16 +02:00
while ( asyncQueue . length ) {
2016-03-28 10:46:51 +00:00
try {
asyncTask = asyncQueue . shift ( ) ;
2018-05-05 12:13:16 +02:00
asyncTask . scope . $eval ( asyncTask . expression , asyncTask . locals ) ;
2016-03-28 10:46:51 +00:00
} catch ( e ) {
$exceptionHandler ( e ) ;
}
lastDirtyWatch = null ;
}
traverseScopesLoop :
do { // "traverse the scopes" loop
if ( ( watchers = current . $$watchers ) ) {
// process our watches
length = watchers . length ;
while ( length -- ) {
try {
watch = watchers [ length ] ;
// Most common watches are on primitives, in which case we can short
// circuit it with === operator, only when === fails do we use .equals
if ( watch ) {
2016-04-18 12:34:29 +00:00
if ( ( value = watch . get ( current ) ) !== ( last = watch . last ) &&
2016-03-28 10:46:51 +00:00
! ( watch . eq
? equals ( value , last )
2018-05-05 12:13:16 +02:00
: ( typeof value === 'number' && typeof last === 'number'
2016-03-28 10:46:51 +00:00
&& isNaN ( value ) && isNaN ( last ) ) ) ) {
dirty = true ;
lastDirtyWatch = watch ;
2018-05-05 12:13:16 +02:00
watch . last = watch . eq ? copy ( value , null ) : value ;
2016-04-18 12:34:29 +00:00
watch . fn ( value , ( ( last === initWatchVal ) ? value : last ) , current ) ;
2016-03-28 10:46:51 +00:00
if ( ttl < 5 ) {
logIdx = 4 - ttl ;
if ( ! watchLog [ logIdx ] ) watchLog [ logIdx ] = [ ] ;
2018-05-05 12:13:16 +02:00
watchLog [ logIdx ] . push ( {
msg : isFunction ( watch . exp ) ? 'fn: ' + ( watch . exp . name || watch . exp . toString ( ) ) : watch . exp ,
newVal : value ,
oldVal : last
} ) ;
2016-03-28 10:46:51 +00:00
}
} else if ( watch === lastDirtyWatch ) {
// If the most recently dirty watcher is now clean, short circuit since the remaining watchers
// have already been tested.
dirty = false ;
break traverseScopesLoop ;
}
}
} catch ( e ) {
$exceptionHandler ( e ) ;
}
}
}
// Insanity Warning: scope depth-first traversal
// yes, this code is a bit crazy, but it works and we have tests to prove it!
// this piece should be kept in sync with the traversal in $broadcast
2016-04-18 12:34:29 +00:00
if ( ! ( next = ( current . $$childHead ||
2016-03-28 10:46:51 +00:00
( current !== target && current . $$nextSibling ) ) ) ) {
2018-05-05 12:13:16 +02:00
while ( current !== target && ! ( next = current . $$nextSibling ) ) {
2016-03-28 10:46:51 +00:00
current = current . $parent ;
}
}
} while ( ( current = next ) ) ;
// `break traverseScopesLoop;` takes us to here
2018-05-05 12:13:16 +02:00
if ( ( dirty || asyncQueue . length ) && ! ( ttl -- ) ) {
2016-03-28 10:46:51 +00:00
clearPhase ( ) ;
throw $rootScopeMinErr ( 'infdig' ,
'{0} $digest() iterations reached. Aborting!\n' +
'Watchers fired in the last 5 iterations: {1}' ,
2018-05-05 12:13:16 +02:00
TTL , watchLog ) ;
2016-03-28 10:46:51 +00:00
}
} while ( dirty || asyncQueue . length ) ;
clearPhase ( ) ;
2018-05-05 12:13:16 +02:00
while ( postDigestQueue . length ) {
2016-03-28 10:46:51 +00:00
try {
postDigestQueue . shift ( ) ( ) ;
} catch ( e ) {
$exceptionHandler ( e ) ;
}
}
} ,
/ * *
* @ ngdoc event
2018-05-05 12:13:16 +02:00
* @ name $rootScope . Scope # $destroy
2016-03-28 10:46:51 +00:00
* @ eventType broadcast on scope being destroyed
*
* @ description
* Broadcasted when a scope and its children are being destroyed .
*
* Note that , in AngularJS , there is also a ` $ destroy ` jQuery event , which can be used to
* clean up DOM bindings before an element is removed from the DOM .
* /
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $rootScope . Scope # $destroy
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Removes the current scope ( and all of its children ) from the parent scope . Removal implies
2018-05-05 12:13:16 +02:00
* that calls to { @ link ng . $rootScope . Scope # $digest $digest ( ) } will no longer
2016-03-28 10:46:51 +00:00
* propagate to the current scope and its children . Removal also implies that the current
* scope is eligible for garbage collection .
*
* The ` $ destroy() ` is usually used by directives such as
* { @ link ng . directive : ngRepeat ngRepeat } for managing the
* unrolling of the loop .
*
* Just before a scope is destroyed , a ` $ destroy ` event is broadcasted on this scope .
* Application code can register a ` $ destroy ` event handler that will give it a chance to
* perform any necessary cleanup .
*
* Note that , in AngularJS , there is also a ` $ destroy ` jQuery event , which can be used to
* clean up DOM bindings before an element is removed from the DOM .
* /
$destroy : function ( ) {
2016-04-18 12:34:29 +00:00
// we can't destroy the root scope or a scope that has been already destroyed
2016-03-28 10:46:51 +00:00
if ( this . $$destroyed ) return ;
var parent = this . $parent ;
this . $broadcast ( '$destroy' ) ;
this . $$destroyed = true ;
2016-04-18 12:34:29 +00:00
if ( this === $rootScope ) return ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
for ( var eventName in this . $$listenerCount ) {
decrementListenerCount ( this , this . $$listenerCount [ eventName ] , eventName ) ;
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
// sever all the references to parent scopes (after this cleanup, the current scope should
// not be retained by any of our references and should be eligible for garbage collection)
2016-04-18 12:34:29 +00:00
if ( parent . $$childHead == this ) parent . $$childHead = this . $$nextSibling ;
if ( parent . $$childTail == this ) parent . $$childTail = this . $$prevSibling ;
2016-03-28 10:46:51 +00:00
if ( this . $$prevSibling ) this . $$prevSibling . $$nextSibling = this . $$nextSibling ;
if ( this . $$nextSibling ) this . $$nextSibling . $$prevSibling = this . $$prevSibling ;
2018-05-05 12:13:16 +02:00
// Disable listeners, watchers and apply/digest methods
this . $destroy = this . $digest = this . $apply = this . $evalAsync = this . $applyAsync = noop ;
this . $on = this . $watch = this . $watchGroup = function ( ) { return noop ; } ;
this . $$listeners = { } ;
// All of the code below is bogus code that works around V8's memory leak via optimized code
// and inline caches.
//
// see:
// - https://code.google.com/p/v8/issues/detail?id=2073#c26
// - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909
// - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451
2016-04-18 12:34:29 +00:00
this . $parent = this . $$nextSibling = this . $$prevSibling = this . $$childHead =
2018-05-05 12:13:16 +02:00
this . $$childTail = this . $root = this . $$watchers = null ;
2016-03-28 10:46:51 +00:00
} ,
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $rootScope . Scope # $eval
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Executes the ` expression ` on the current scope and returns the result . Any exceptions in
* the expression are propagated ( uncaught ) . This is useful when evaluating Angular
* expressions .
*
* # Example
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
var scope = ng . $rootScope . Scope ( ) ;
scope . a = 1 ;
scope . b = 2 ;
expect ( scope . $eval ( 'a+b' ) ) . toEqual ( 3 ) ;
expect ( scope . $eval ( function ( scope ) { return scope . a + scope . b ; } ) ) . toEqual ( 3 ) ;
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
* @ param { ( string | function ( ) ) = } expression An angular expression to be executed .
*
* - ` string ` : execute using the rules as defined in { @ link guide / expression expression } .
* - ` function(scope) ` : execute the function with the current ` scope ` parameter .
*
* @ param { ( object ) = } locals Local variables object , useful for overriding values in scope .
* @ returns { * } The result of evaluating the expression .
* /
$eval : function ( expr , locals ) {
return $parse ( expr ) ( this , locals ) ;
} ,
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $rootScope . Scope # $evalAsync
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Executes the expression on the current scope at a later point in time .
*
* The ` $ evalAsync ` makes no guarantees as to when the ` expression ` will be executed , only
* that :
*
* - it will execute after the function that scheduled the evaluation ( preferably before DOM
* rendering ) .
2018-05-05 12:13:16 +02:00
* - at least one { @ link ng . $rootScope . Scope # $digest $digest cycle } will be performed after
2016-03-28 10:46:51 +00:00
* ` expression ` execution .
*
* Any exceptions from the execution of the expression are forwarded to the
* { @ link ng . $exceptionHandler $exceptionHandler } service .
*
* _ _Note : _ _ if this function is called outside of a ` $ digest ` cycle , a new ` $ digest ` cycle
* will be scheduled . However , it is encouraged to always call code that changes the model
* from within an ` $ apply ` call . That includes code evaluated via ` $ evalAsync ` .
*
* @ param { ( string | function ( ) ) = } expression An angular expression to be executed .
*
* - ` string ` : execute using the rules as defined in { @ link guide / expression expression } .
* - ` function(scope) ` : execute the function with the current ` scope ` parameter .
*
2018-05-05 12:13:16 +02:00
* @ param { ( object ) = } locals Local variables object , useful for overriding values in scope .
2016-03-28 10:46:51 +00:00
* /
2018-05-05 12:13:16 +02:00
$evalAsync : function ( expr , locals ) {
2016-03-28 10:46:51 +00:00
// if we are outside of an $digest loop and this is the first time we are scheduling async
// task also schedule async auto-flush
2018-05-05 12:13:16 +02:00
if ( ! $rootScope . $$phase && ! asyncQueue . length ) {
2016-03-28 10:46:51 +00:00
$browser . defer ( function ( ) {
2018-05-05 12:13:16 +02:00
if ( asyncQueue . length ) {
2016-03-28 10:46:51 +00:00
$rootScope . $digest ( ) ;
}
} ) ;
}
2018-05-05 12:13:16 +02:00
asyncQueue . push ( { scope : this , expression : expr , locals : locals } ) ;
2016-03-28 10:46:51 +00:00
} ,
2018-05-05 12:13:16 +02:00
$$postDigest : function ( fn ) {
postDigestQueue . push ( fn ) ;
2016-03-28 10:46:51 +00:00
} ,
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $rootScope . Scope # $apply
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* ` $ apply() ` is used to execute an expression in angular from outside of the angular
* framework . ( For example from browser DOM events , setTimeout , XHR or third party libraries ) .
* Because we are calling into the angular framework we need to perform proper scope life
* cycle of { @ link ng . $exceptionHandler exception handling } ,
2018-05-05 12:13:16 +02:00
* { @ link ng . $rootScope . Scope # $digest executing watches } .
2016-03-28 10:46:51 +00:00
*
* # # Life cycle
*
* # Pseudo - Code of ` $ apply() `
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
function $apply ( expr ) {
try {
return $eval ( expr ) ;
} catch ( e ) {
$exceptionHandler ( e ) ;
} finally {
$root . $digest ( ) ;
}
}
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
*
* Scope ' s ` $ apply() ` method transitions through the following stages :
*
* 1. The { @ link guide / expression expression } is executed using the
2018-05-05 12:13:16 +02:00
* { @ link ng . $rootScope . Scope # $eval $eval ( ) } method .
2016-03-28 10:46:51 +00:00
* 2. Any exceptions from the execution of the expression are forwarded to the
* { @ link ng . $exceptionHandler $exceptionHandler } service .
2018-05-05 12:13:16 +02:00
* 3. The { @ link ng . $rootScope . Scope # $watch watch } listeners are fired immediately after the
* expression was executed using the { @ link ng . $rootScope . Scope # $digest $digest ( ) } method .
2016-03-28 10:46:51 +00:00
*
*
* @ param { ( string | function ( ) ) = } exp An angular expression to be executed .
*
* - ` string ` : execute using the rules as defined in { @ link guide / expression expression } .
* - ` function(scope) ` : execute the function with current ` scope ` parameter .
*
* @ returns { * } The result of evaluating the expression .
* /
$apply : function ( expr ) {
try {
beginPhase ( '$apply' ) ;
2016-04-18 12:34:29 +00:00
return this . $eval ( expr ) ;
2016-03-28 10:46:51 +00:00
} catch ( e ) {
$exceptionHandler ( e ) ;
} finally {
2016-04-18 12:34:29 +00:00
clearPhase ( ) ;
2016-03-28 10:46:51 +00:00
try {
$rootScope . $digest ( ) ;
} catch ( e ) {
$exceptionHandler ( e ) ;
throw e ;
}
}
} ,
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $rootScope . Scope # $applyAsync
* @ kind function
*
* @ description
* Schedule the invocation of $apply to occur at a later time . The actual time difference
* varies across browsers , but is typically around ~ 10 milliseconds .
*
* This can be used to queue up multiple expressions which need to be evaluated in the same
* digest .
*
* @ param { ( string | function ( ) ) = } exp An angular expression to be executed .
*
* - ` string ` : execute using the rules as defined in { @ link guide / expression expression } .
* - ` function(scope) ` : execute the function with current ` scope ` parameter .
* /
$applyAsync : function ( expr ) {
var scope = this ;
expr && applyAsyncQueue . push ( $applyAsyncExpression ) ;
scheduleApplyAsync ( ) ;
function $applyAsyncExpression ( ) {
scope . $eval ( expr ) ;
}
} ,
/ * *
* @ ngdoc method
* @ name $rootScope . Scope # $on
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
2018-05-05 12:13:16 +02:00
* Listens on events of a given type . See { @ link ng . $rootScope . Scope # $emit $emit } for
2016-03-28 10:46:51 +00:00
* discussion of event life cycle .
*
* The event listener function format is : ` function(event, args...) ` . The ` event ` object
* passed into the listener has the following attributes :
*
* - ` targetScope ` - ` {Scope} ` : the scope on which the event was ` $ emit ` - ed or
* ` $ broadcast ` - ed .
2018-05-05 12:13:16 +02:00
* - ` currentScope ` - ` {Scope} ` : the scope that is currently handling the event . Once the
* event propagates through the scope hierarchy , this property is set to null .
2016-03-28 10:46:51 +00:00
* - ` name ` - ` {string} ` : name of the event .
* - ` stopPropagation ` - ` {function=} ` : calling ` stopPropagation ` function will cancel
* further event propagation ( available only for events that were ` $ emit ` - ed ) .
* - ` preventDefault ` - ` {function} ` : calling ` preventDefault ` sets ` defaultPrevented ` flag
* to true .
* - ` defaultPrevented ` - ` {boolean} ` : true if ` preventDefault ` was called .
*
* @ param { string } name Event name to listen on .
2018-05-05 12:13:16 +02:00
* @ param { function ( event , ... args ) } listener Function to call when the event is emitted .
2016-03-28 10:46:51 +00:00
* @ returns { function ( ) } Returns a deregistration function for this listener .
* /
$on : function ( name , listener ) {
var namedListeners = this . $$listeners [ name ] ;
if ( ! namedListeners ) {
this . $$listeners [ name ] = namedListeners = [ ] ;
}
namedListeners . push ( listener ) ;
var current = this ;
do {
if ( ! current . $$listenerCount [ name ] ) {
current . $$listenerCount [ name ] = 0 ;
}
current . $$listenerCount [ name ] ++ ;
} while ( ( current = current . $parent ) ) ;
var self = this ;
return function ( ) {
2018-05-05 12:13:16 +02:00
var indexOfListener = namedListeners . indexOf ( listener ) ;
if ( indexOfListener !== - 1 ) {
namedListeners [ indexOfListener ] = null ;
decrementListenerCount ( self , 1 , name ) ;
}
2016-03-28 10:46:51 +00:00
} ;
} ,
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $rootScope . Scope # $emit
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Dispatches an event ` name ` upwards through the scope hierarchy notifying the
2018-05-05 12:13:16 +02:00
* registered { @ link ng . $rootScope . Scope # $on } listeners .
2016-03-28 10:46:51 +00:00
*
* The event life cycle starts at the scope on which ` $ emit ` was called . All
2018-05-05 12:13:16 +02:00
* { @ link ng . $rootScope . Scope # $on listeners } listening for ` name ` event on this scope get
2016-03-28 10:46:51 +00:00
* notified . Afterwards , the event traverses upwards toward the root scope and calls all
* registered listeners along the way . The event will stop propagating if one of the listeners
* cancels it .
*
2018-05-05 12:13:16 +02:00
* Any exception emitted from the { @ link ng . $rootScope . Scope # $on listeners } will be passed
2016-03-28 10:46:51 +00:00
* onto the { @ link ng . $exceptionHandler $exceptionHandler } service .
*
* @ param { string } name Event name to emit .
2018-05-05 12:13:16 +02:00
* @ param { ... * } args Optional one or more arguments which will be passed onto the event listeners .
* @ return { Object } Event object ( see { @ link ng . $rootScope . Scope # $on } ) .
2016-03-28 10:46:51 +00:00
* /
$emit : function ( name , args ) {
var empty = [ ] ,
namedListeners ,
scope = this ,
stopPropagation = false ,
event = {
name : name ,
targetScope : scope ,
stopPropagation : function ( ) { stopPropagation = true ; } ,
preventDefault : function ( ) {
event . defaultPrevented = true ;
} ,
defaultPrevented : false
} ,
listenerArgs = concat ( [ event ] , arguments , 1 ) ,
i , length ;
do {
namedListeners = scope . $$listeners [ name ] || empty ;
event . currentScope = scope ;
2018-05-05 12:13:16 +02:00
for ( i = 0 , length = namedListeners . length ; i < length ; i ++ ) {
2016-03-28 10:46:51 +00:00
// if listeners were deregistered, defragment the array
if ( ! namedListeners [ i ] ) {
namedListeners . splice ( i , 1 ) ;
i -- ;
length -- ;
continue ;
}
try {
//allow all listeners attached to the current scope to run
namedListeners [ i ] . apply ( null , listenerArgs ) ;
} catch ( e ) {
$exceptionHandler ( e ) ;
}
}
//if any listener on the current scope stops propagation, prevent bubbling
2018-05-05 12:13:16 +02:00
if ( stopPropagation ) {
event . currentScope = null ;
return event ;
}
2016-03-28 10:46:51 +00:00
//traverse upwards
scope = scope . $parent ;
} while ( scope ) ;
2018-05-05 12:13:16 +02:00
event . currentScope = null ;
2016-03-28 10:46:51 +00:00
return event ;
} ,
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $rootScope . Scope # $broadcast
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Dispatches an event ` name ` downwards to all child scopes ( and their children ) notifying the
2018-05-05 12:13:16 +02:00
* registered { @ link ng . $rootScope . Scope # $on } listeners .
2016-03-28 10:46:51 +00:00
*
* The event life cycle starts at the scope on which ` $ broadcast ` was called . All
2018-05-05 12:13:16 +02:00
* { @ link ng . $rootScope . Scope # $on listeners } listening for ` name ` event on this scope get
2016-03-28 10:46:51 +00:00
* notified . Afterwards , the event propagates to all direct and indirect scopes of the current
* scope and calls all registered listeners along the way . The event cannot be canceled .
*
2018-05-05 12:13:16 +02:00
* Any exception emitted from the { @ link ng . $rootScope . Scope # $on listeners } will be passed
2016-03-28 10:46:51 +00:00
* onto the { @ link ng . $exceptionHandler $exceptionHandler } service .
*
* @ param { string } name Event name to broadcast .
2018-05-05 12:13:16 +02:00
* @ param { ... * } args Optional one or more arguments which will be passed onto the event listeners .
* @ return { Object } Event object , see { @ link ng . $rootScope . Scope # $on }
2016-03-28 10:46:51 +00:00
* /
$broadcast : function ( name , args ) {
var target = this ,
current = target ,
next = target ,
event = {
name : name ,
targetScope : target ,
preventDefault : function ( ) {
event . defaultPrevented = true ;
} ,
defaultPrevented : false
2018-05-05 12:13:16 +02:00
} ;
if ( ! target . $$listenerCount [ name ] ) return event ;
var listenerArgs = concat ( [ event ] , arguments , 1 ) ,
2016-03-28 10:46:51 +00:00
listeners , i , length ;
//down while you can, then up and next sibling or up and next sibling until back at root
while ( ( current = next ) ) {
event . currentScope = current ;
listeners = current . $$listeners [ name ] || [ ] ;
2018-05-05 12:13:16 +02:00
for ( i = 0 , length = listeners . length ; i < length ; i ++ ) {
2016-03-28 10:46:51 +00:00
// if listeners were deregistered, defragment the array
if ( ! listeners [ i ] ) {
listeners . splice ( i , 1 ) ;
i -- ;
length -- ;
continue ;
}
try {
listeners [ i ] . apply ( null , listenerArgs ) ;
2018-05-05 12:13:16 +02:00
} catch ( e ) {
2016-03-28 10:46:51 +00:00
$exceptionHandler ( e ) ;
}
}
// Insanity Warning: scope depth-first traversal
// yes, this code is a bit crazy, but it works and we have tests to prove it!
// this piece should be kept in sync with the traversal in $digest
// (though it differs due to having the extra check for $$listenerCount)
if ( ! ( next = ( ( current . $$listenerCount [ name ] && current . $$childHead ) ||
( current !== target && current . $$nextSibling ) ) ) ) {
2018-05-05 12:13:16 +02:00
while ( current !== target && ! ( next = current . $$nextSibling ) ) {
2016-03-28 10:46:51 +00:00
current = current . $parent ;
}
}
}
2018-05-05 12:13:16 +02:00
event . currentScope = null ;
2016-03-28 10:46:51 +00:00
return event ;
}
} ;
var $rootScope = new Scope ( ) ;
2018-05-05 12:13:16 +02:00
//The internal queues. Expose them on the $rootScope for debugging/testing purposes.
var asyncQueue = $rootScope . $$asyncQueue = [ ] ;
var postDigestQueue = $rootScope . $$postDigestQueue = [ ] ;
var applyAsyncQueue = $rootScope . $$applyAsyncQueue = [ ] ;
2016-03-28 10:46:51 +00:00
return $rootScope ;
function beginPhase ( phase ) {
if ( $rootScope . $$phase ) {
throw $rootScopeMinErr ( 'inprog' , '{0} already in progress' , $rootScope . $$phase ) ;
}
$rootScope . $$phase = phase ;
}
function clearPhase ( ) {
$rootScope . $$phase = null ;
}
function decrementListenerCount ( current , count , name ) {
do {
current . $$listenerCount [ name ] -= count ;
if ( current . $$listenerCount [ name ] === 0 ) {
delete current . $$listenerCount [ name ] ;
}
} while ( ( current = current . $parent ) ) ;
}
/ * *
* function used as an initial value for watchers .
* because it ' s unique we can easily tell it apart from other values
* /
function initWatchVal ( ) { }
2018-05-05 12:13:16 +02:00
function flushApplyAsync ( ) {
while ( applyAsyncQueue . length ) {
try {
applyAsyncQueue . shift ( ) ( ) ;
} catch ( e ) {
$exceptionHandler ( e ) ;
}
}
applyAsyncId = null ;
}
function scheduleApplyAsync ( ) {
if ( applyAsyncId === null ) {
applyAsyncId = $browser . defer ( function ( ) {
$rootScope . $apply ( flushApplyAsync ) ;
} ) ;
}
}
2016-03-28 10:46:51 +00:00
} ] ;
}
/ * *
* @ description
* Private service to sanitize uris for links and images . Used by $compile and $sanitize .
* /
function $$SanitizeUriProvider ( ) {
var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/ ,
2018-05-05 12:13:16 +02:00
imgSrcSanitizationWhitelist = /^\s*((https?|ftp|file|blob):|data:image\/)/ ;
2016-03-28 10:46:51 +00:00
/ * *
* @ description
* Retrieves or overrides the default regular expression that is used for whitelisting of safe
* urls during a [ href ] sanitization .
*
* The sanitization is a security measure aimed at prevent XSS attacks via html links .
*
* Any url about to be assigned to a [ href ] via data - binding is first normalized and turned into
* an absolute url . Afterwards , the url is matched against the ` aHrefSanitizationWhitelist `
* regular expression . If a match is found , the original url is written into the dom . Otherwise ,
* the absolute url is prefixed with ` 'unsafe:' ` string and only then is it written into the DOM .
*
* @ param { RegExp = } regexp New regexp to whitelist urls with .
* @ returns { RegExp | ng . $compileProvider } Current RegExp if called without value or self for
* chaining otherwise .
* /
this . aHrefSanitizationWhitelist = function ( regexp ) {
if ( isDefined ( regexp ) ) {
aHrefSanitizationWhitelist = regexp ;
return this ;
}
return aHrefSanitizationWhitelist ;
} ;
/ * *
* @ description
* Retrieves or overrides the default regular expression that is used for whitelisting of safe
* urls during img [ src ] sanitization .
*
* The sanitization is a security measure aimed at prevent XSS attacks via html links .
*
* Any url about to be assigned to img [ src ] via data - binding is first normalized and turned into
* an absolute url . Afterwards , the url is matched against the ` imgSrcSanitizationWhitelist `
* regular expression . If a match is found , the original url is written into the dom . Otherwise ,
* the absolute url is prefixed with ` 'unsafe:' ` string and only then is it written into the DOM .
*
* @ param { RegExp = } regexp New regexp to whitelist urls with .
* @ returns { RegExp | ng . $compileProvider } Current RegExp if called without value or self for
* chaining otherwise .
* /
this . imgSrcSanitizationWhitelist = function ( regexp ) {
if ( isDefined ( regexp ) ) {
imgSrcSanitizationWhitelist = regexp ;
return this ;
}
return imgSrcSanitizationWhitelist ;
} ;
this . $get = function ( ) {
return function sanitizeUri ( uri , isImage ) {
var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist ;
var normalizedVal ;
2018-05-05 12:13:16 +02:00
normalizedVal = urlResolve ( uri ) . href ;
if ( normalizedVal !== '' && ! normalizedVal . match ( regex ) ) {
return 'unsafe:' + normalizedVal ;
2016-03-28 10:46:51 +00:00
}
return uri ;
} ;
} ;
}
2018-05-05 12:13:16 +02:00
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Any commits to this file should be reviewed with security in mind . *
* Changes to this file can potentially create security vulnerabilities . *
* An approval from 2 Core members with history of modifying *
* this file is required . *
* *
* Does the change somehow allow for arbitrary javascript to be executed ? *
* Or allows for someone to change the prototype of built - in objects ? *
* Or gives undesired access to variables likes document or window ? *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
2016-03-28 10:46:51 +00:00
var $sceMinErr = minErr ( '$sce' ) ;
var SCE _CONTEXTS = {
HTML : 'html' ,
CSS : 'css' ,
URL : 'url' ,
// RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a
// url. (e.g. ng-include, script src, templateUrl)
RESOURCE _URL : 'resourceUrl' ,
JS : 'js'
} ;
// Helper functions follow.
function adjustMatcher ( matcher ) {
if ( matcher === 'self' ) {
return matcher ;
} else if ( isString ( matcher ) ) {
// Strings match exactly except for 2 wildcards - '*' and '**'.
// '*' matches any character except those from the set ':/.?&'.
// '**' matches any character (like .* in a RegExp).
// More than 2 *'s raises an error as it's ill defined.
if ( matcher . indexOf ( '***' ) > - 1 ) {
throw $sceMinErr ( 'iwcard' ,
'Illegal sequence *** in string matcher. String: {0}' , matcher ) ;
}
matcher = escapeForRegexp ( matcher ) .
replace ( '\\*\\*' , '.*' ) .
replace ( '\\*' , '[^:/.?&;]*' ) ;
return new RegExp ( '^' + matcher + '$' ) ;
} else if ( isRegExp ( matcher ) ) {
// The only other type of matcher allowed is a Regexp.
// Match entire URL / disallow partial matches.
// Flags are reset (i.e. no global, ignoreCase or multiline)
return new RegExp ( '^' + matcher . source + '$' ) ;
} else {
throw $sceMinErr ( 'imatcher' ,
'Matchers may only be "self", string patterns or RegExp objects' ) ;
}
}
function adjustMatchers ( matchers ) {
var adjustedMatchers = [ ] ;
if ( isDefined ( matchers ) ) {
forEach ( matchers , function ( matcher ) {
adjustedMatchers . push ( adjustMatcher ( matcher ) ) ;
} ) ;
}
return adjustedMatchers ;
}
/ * *
* @ ngdoc service
2018-05-05 12:13:16 +02:00
* @ name $sceDelegate
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
*
* ` $ sceDelegate ` is a service that is used by the ` $ sce ` service to provide { @ link ng . $sce Strict
* Contextual Escaping ( SCE ) } services to AngularJS .
*
* Typically , you would configure or override the { @ link ng . $sceDelegate $sceDelegate } instead of
* the ` $ sce ` service to customize the way Strict Contextual Escaping works in AngularJS . This is
* because , while the ` $ sce ` provides numerous shorthand methods , etc . , you really only need to
* override 3 core functions ( ` trustAs ` , ` getTrusted ` and ` valueOf ` ) to replace the way things
* work because ` $ sce ` delegates to ` $ sceDelegate ` for these operations .
*
* Refer { @ link ng . $sceDelegateProvider $sceDelegateProvider } to configure this service .
*
* The default instance of ` $ sceDelegate ` should work out of the box with little pain . While you
* can override it completely to change the behavior of ` $ sce ` , the common case would
* involve configuring the { @ link ng . $sceDelegateProvider $sceDelegateProvider } instead by setting
* your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as
2018-05-05 12:13:16 +02:00
* templates . Refer { @ link ng . $sceDelegateProvider # resourceUrlWhitelist
2016-03-28 10:46:51 +00:00
* $sceDelegateProvider . resourceUrlWhitelist } and { @ link
2018-05-05 12:13:16 +02:00
* ng . $sceDelegateProvider # resourceUrlBlacklist $sceDelegateProvider . resourceUrlBlacklist }
2016-03-28 10:46:51 +00:00
* /
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc provider
* @ name $sceDelegateProvider
2016-03-28 10:46:51 +00:00
* @ description
*
* The ` $ sceDelegateProvider ` provider allows developers to configure the { @ link ng . $sceDelegate
* $sceDelegate } service . This allows one to get / set the whitelists and blacklists used to ensure
* that the URLs used for sourcing Angular templates are safe . Refer { @ link
2018-05-05 12:13:16 +02:00
* ng . $sceDelegateProvider # resourceUrlWhitelist $sceDelegateProvider . resourceUrlWhitelist } and
* { @ link ng . $sceDelegateProvider # resourceUrlBlacklist $sceDelegateProvider . resourceUrlBlacklist }
2016-03-28 10:46:51 +00:00
*
* For the general details about this service in Angular , read the main page for { @ link ng . $sce
* Strict Contextual Escaping ( SCE ) } .
*
* * * Example * * : Consider the following case . < a name = "example" > < / a >
*
* - your app is hosted at url ` http://myapp.example.com/ `
* - but some of your templates are hosted on other domains you control such as
* ` http://srv01.assets.example.com/ ` , ` http://srv02.assets.example.com/ ` , etc .
* - and you have an open redirect at ` http://myapp.example.com/clickThru?... ` .
*
* Here is what a secure configuration for this scenario might look like :
*
2018-05-05 12:13:16 +02:00
* ` ` `
* angular . module ( 'myApp' , [ ] ) . config ( function ( $sceDelegateProvider ) {
* $sceDelegateProvider . resourceUrlWhitelist ( [
* // Allow same origin resource loads.
* 'self' ,
* // Allow loading from our assets domain. Notice the difference between * and **.
* 'http://srv*.assets.example.com/**'
* ] ) ;
*
* // The blacklist overrides the whitelist so the open redirect here is blocked.
* $sceDelegateProvider . resourceUrlBlacklist ( [
* 'http://myapp.example.com/clickThru**'
* ] ) ;
* } ) ;
* ` ` `
2016-03-28 10:46:51 +00:00
* /
function $SceDelegateProvider ( ) {
this . SCE _CONTEXTS = SCE _CONTEXTS ;
// Resource URLs can also be trusted by policy.
var resourceUrlWhitelist = [ 'self' ] ,
resourceUrlBlacklist = [ ] ;
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $sceDelegateProvider # resourceUrlWhitelist
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ param { Array = } whitelist When provided , replaces the resourceUrlWhitelist with the value
2016-04-18 12:34:29 +00:00
* provided . This must be an array or null . A snapshot of this array is used so further
* changes to the array are ignored .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* Follow { @ link ng . $sce # resourceUrlPatternItem this link } for a description of the items
* allowed in this array .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* Note : * * an empty whitelist array will block all URLs * * !
2016-03-28 10:46:51 +00:00
*
* @ return { Array } the currently set whitelist array .
*
* The * * default value * * when no whitelist has been explicitly set is ` ['self'] ` allowing only
* same origin resource requests .
*
* @ description
* Sets / Gets the whitelist of trusted resource URLs .
* /
2018-05-05 12:13:16 +02:00
this . resourceUrlWhitelist = function ( value ) {
2016-03-28 10:46:51 +00:00
if ( arguments . length ) {
resourceUrlWhitelist = adjustMatchers ( value ) ;
}
return resourceUrlWhitelist ;
} ;
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $sceDelegateProvider # resourceUrlBlacklist
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ param { Array = } blacklist When provided , replaces the resourceUrlBlacklist with the value
2016-04-18 12:34:29 +00:00
* provided . This must be an array or null . A snapshot of this array is used so further
* changes to the array are ignored .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* Follow { @ link ng . $sce # resourceUrlPatternItem this link } for a description of the items
* allowed in this array .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* The typical usage for the blacklist is to * * block
* [ open redirects ] ( http : //cwe.mitre.org/data/definitions/601.html)** served by your domain as
* these would otherwise be trusted but actually return content from the redirected domain .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* Finally , * * the blacklist overrides the whitelist * * and has the final say .
2016-03-28 10:46:51 +00:00
*
* @ return { Array } the currently set blacklist array .
*
* The * * default value * * when no whitelist has been explicitly set is the empty array ( i . e . there
* is no blacklist . )
*
* @ description
* Sets / Gets the blacklist of trusted resource URLs .
* /
2018-05-05 12:13:16 +02:00
this . resourceUrlBlacklist = function ( value ) {
2016-03-28 10:46:51 +00:00
if ( arguments . length ) {
resourceUrlBlacklist = adjustMatchers ( value ) ;
}
return resourceUrlBlacklist ;
} ;
this . $get = [ '$injector' , function ( $injector ) {
var htmlSanitizer = function htmlSanitizer ( html ) {
throw $sceMinErr ( 'unsafe' , 'Attempting to use an unsafe value in a safe context.' ) ;
} ;
if ( $injector . has ( '$sanitize' ) ) {
htmlSanitizer = $injector . get ( '$sanitize' ) ;
}
function matchUrl ( matcher , parsedUrl ) {
if ( matcher === 'self' ) {
return urlIsSameOrigin ( parsedUrl ) ;
} else {
// definitely a regex. See adjustMatchers()
return ! ! matcher . exec ( parsedUrl . href ) ;
}
}
function isResourceUrlAllowedByPolicy ( url ) {
var parsedUrl = urlResolve ( url . toString ( ) ) ;
var i , n , allowed = false ;
// Ensure that at least one item from the whitelist allows this url.
for ( i = 0 , n = resourceUrlWhitelist . length ; i < n ; i ++ ) {
if ( matchUrl ( resourceUrlWhitelist [ i ] , parsedUrl ) ) {
allowed = true ;
break ;
}
}
if ( allowed ) {
// Ensure that no item from the blacklist blocked this url.
for ( i = 0 , n = resourceUrlBlacklist . length ; i < n ; i ++ ) {
if ( matchUrl ( resourceUrlBlacklist [ i ] , parsedUrl ) ) {
allowed = false ;
break ;
}
}
}
return allowed ;
}
function generateHolderType ( Base ) {
var holderType = function TrustedValueHolderType ( trustedValue ) {
this . $$unwrapTrustedValue = function ( ) {
return trustedValue ;
} ;
} ;
if ( Base ) {
holderType . prototype = new Base ( ) ;
}
holderType . prototype . valueOf = function sceValueOf ( ) {
return this . $$unwrapTrustedValue ( ) ;
} ;
holderType . prototype . toString = function sceToString ( ) {
return this . $$unwrapTrustedValue ( ) . toString ( ) ;
} ;
return holderType ;
}
var trustedValueHolderBase = generateHolderType ( ) ,
byType = { } ;
byType [ SCE _CONTEXTS . HTML ] = generateHolderType ( trustedValueHolderBase ) ;
byType [ SCE _CONTEXTS . CSS ] = generateHolderType ( trustedValueHolderBase ) ;
byType [ SCE _CONTEXTS . URL ] = generateHolderType ( trustedValueHolderBase ) ;
byType [ SCE _CONTEXTS . JS ] = generateHolderType ( trustedValueHolderBase ) ;
byType [ SCE _CONTEXTS . RESOURCE _URL ] = generateHolderType ( byType [ SCE _CONTEXTS . URL ] ) ;
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $sceDelegate # trustAs
2016-03-28 10:46:51 +00:00
*
* @ description
* Returns an object that is trusted by angular for use in specified strict
2018-05-05 12:13:16 +02:00
* contextual escaping contexts ( such as ng - bind - html , ng - include , any src
2016-03-28 10:46:51 +00:00
* attribute interpolation , any dom event binding attribute interpolation
* such as for onclick , etc . ) that uses the provided value .
* See { @ link ng . $sce $sce } for enabling strict contextual escaping .
*
* @ param { string } type The kind of context in which this value is safe for use . e . g . url ,
* resourceUrl , html , js and css .
* @ param { * } value The value that that should be considered trusted / safe .
* @ returns { * } A value that can be used to stand in for the provided ` value ` in places
* where Angular expects a $sce . trustAs ( ) return value .
* /
function trustAs ( type , trustedValue ) {
var Constructor = ( byType . hasOwnProperty ( type ) ? byType [ type ] : null ) ;
if ( ! Constructor ) {
throw $sceMinErr ( 'icontext' ,
'Attempted to trust a value in invalid context. Context: {0}; Value: {1}' ,
type , trustedValue ) ;
}
2016-04-18 12:34:29 +00:00
if ( trustedValue === null || trustedValue === undefined || trustedValue === '' ) {
2016-03-28 10:46:51 +00:00
return trustedValue ;
}
// All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting
// mutable objects, we ensure here that the value passed in is actually a string.
if ( typeof trustedValue !== 'string' ) {
throw $sceMinErr ( 'itype' ,
'Attempted to trust a non-string value in a content requiring a string: Context: {0}' ,
type ) ;
}
return new Constructor ( trustedValue ) ;
}
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $sceDelegate # valueOf
2016-03-28 10:46:51 +00:00
*
* @ description
2018-05-05 12:13:16 +02:00
* If the passed parameter had been returned by a prior call to { @ link ng . $sceDelegate # trustAs
2016-03-28 10:46:51 +00:00
* ` $ sceDelegate.trustAs ` } , returns the value that had been passed to { @ link
2018-05-05 12:13:16 +02:00
* ng . $sceDelegate # trustAs ` $ sceDelegate.trustAs ` } .
2016-03-28 10:46:51 +00:00
*
* If the passed parameter is not a value that had been returned by { @ link
2018-05-05 12:13:16 +02:00
* ng . $sceDelegate # trustAs ` $ sceDelegate.trustAs ` } , returns it as - is .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* @ param { * } value The result of a prior { @ link ng . $sceDelegate # trustAs ` $ sceDelegate.trustAs ` }
2016-03-28 10:46:51 +00:00
* call or anything else .
2018-05-05 12:13:16 +02:00
* @ returns { * } The ` value ` that was originally provided to { @ link ng . $sceDelegate # trustAs
2016-03-28 10:46:51 +00:00
* ` $ sceDelegate.trustAs ` } if ` value ` is the result of such a call . Otherwise , returns
* ` value ` unchanged .
* /
function valueOf ( maybeTrusted ) {
if ( maybeTrusted instanceof trustedValueHolderBase ) {
return maybeTrusted . $$unwrapTrustedValue ( ) ;
} else {
return maybeTrusted ;
}
}
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $sceDelegate # getTrusted
2016-03-28 10:46:51 +00:00
*
* @ description
2018-05-05 12:13:16 +02:00
* Takes the result of a { @ link ng . $sceDelegate # trustAs ` $ sceDelegate.trustAs ` } call and
2016-03-28 10:46:51 +00:00
* returns the originally supplied value if the queried context type is a supertype of the
* created type . If this condition isn ' t satisfied , throws an exception .
*
* @ param { string } type The kind of context in which this value is to be used .
2018-05-05 12:13:16 +02:00
* @ param { * } maybeTrusted The result of a prior { @ link ng . $sceDelegate # trustAs
2016-03-28 10:46:51 +00:00
* ` $ sceDelegate.trustAs ` } call .
2018-05-05 12:13:16 +02:00
* @ returns { * } The value the was originally provided to { @ link ng . $sceDelegate # trustAs
2016-03-28 10:46:51 +00:00
* ` $ sceDelegate.trustAs ` } if valid in this context . Otherwise , throws an exception .
* /
function getTrusted ( type , maybeTrusted ) {
2016-04-18 12:34:29 +00:00
if ( maybeTrusted === null || maybeTrusted === undefined || maybeTrusted === '' ) {
2016-03-28 10:46:51 +00:00
return maybeTrusted ;
}
var constructor = ( byType . hasOwnProperty ( type ) ? byType [ type ] : null ) ;
if ( constructor && maybeTrusted instanceof constructor ) {
return maybeTrusted . $$unwrapTrustedValue ( ) ;
}
// If we get here, then we may only take one of two actions.
// 1. sanitize the value for the requested type, or
// 2. throw an exception.
if ( type === SCE _CONTEXTS . RESOURCE _URL ) {
if ( isResourceUrlAllowedByPolicy ( maybeTrusted ) ) {
return maybeTrusted ;
} else {
throw $sceMinErr ( 'insecurl' ,
'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}' ,
maybeTrusted . toString ( ) ) ;
}
} else if ( type === SCE _CONTEXTS . HTML ) {
return htmlSanitizer ( maybeTrusted ) ;
}
throw $sceMinErr ( 'unsafe' , 'Attempting to use an unsafe value in a safe context.' ) ;
}
return { trustAs : trustAs ,
getTrusted : getTrusted ,
valueOf : valueOf } ;
} ] ;
}
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc provider
* @ name $sceProvider
2016-03-28 10:46:51 +00:00
* @ description
*
* The $sceProvider provider allows developers to configure the { @ link ng . $sce $sce } service .
* - enable / disable Strict Contextual Escaping ( SCE ) in a module
* - override the default implementation with a custom delegate
*
* Read more about { @ link ng . $sce Strict Contextual Escaping ( SCE ) } .
* /
/* jshint maxlen: false*/
/ * *
* @ ngdoc service
2018-05-05 12:13:16 +02:00
* @ name $sce
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
*
* ` $ sce ` is a service that provides Strict Contextual Escaping services to AngularJS .
*
* # Strict Contextual Escaping
*
* Strict Contextual Escaping ( SCE ) is a mode in which AngularJS requires bindings in certain
* contexts to result in a value that is marked as safe to use for that context . One example of
* such a context is binding arbitrary html controlled by the user via ` ng-bind-html ` . We refer
* to these contexts as privileged or SCE contexts .
*
* As of version 1.2 , Angular ships with SCE enabled by default .
*
2018-05-05 12:13:16 +02:00
* Note : When enabled ( the default ) , IE < 11 in quirks mode is not supported . In this mode , IE < 11 allow
2016-03-28 10:46:51 +00:00
* one to execute arbitrary javascript by the use of the expression ( ) syntax . Refer
* < http : //blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx> to learn more about them.
* You can ensure your document is in standards mode and not quirks mode by adding ` <!doctype html> `
* to the top of your HTML document .
*
* SCE assists in writing code in way that ( a ) is secure by default and ( b ) makes auditing for
* security vulnerabilities such as XSS , clickjacking , etc . a lot easier .
*
* Here ' s an example of a binding in a privileged context :
*
2018-05-05 12:13:16 +02:00
* ` ` `
* < input ng - model = "userHtml" >
* < div ng - bind - html = "userHtml" > < / d i v >
* ` ` `
2016-03-28 10:46:51 +00:00
*
* Notice that ` ng-bind-html ` is bound to ` userHtml ` controlled by the user . With SCE
* disabled , this application allows the user to render arbitrary HTML into the DIV .
* In a more realistic example , one may be rendering user comments , blog articles , etc . via
* bindings . ( HTML is just one example of a context where rendering user controlled input creates
* security vulnerabilities . )
*
* For the case of HTML , you might use a library , either on the client side , or on the server side ,
* to sanitize unsafe HTML before binding to the value and rendering it in the document .
*
* How would you ensure that every place that used these types of bindings was bound to a value that
* was sanitized by your library ( or returned as safe for rendering by your server ? ) How can you
* ensure that you didn ' t accidentally delete the line that sanitized the value , or renamed some
* properties / fields and forgot to update the binding to the sanitized value ?
*
* To be secure by default , you want to ensure that any such bindings are disallowed unless you can
* determine that something explicitly says it ' s safe to use a value for binding in that
* context . You can then audit your code ( a simple grep would do ) to ensure that this is only done
* for those values that you can easily tell are safe - because they were received from your server ,
* sanitized by your library , etc . You can organize your codebase to help with this - perhaps
* allowing only the files in a specific directory to do this . Ensuring that the internal API
* exposed by that code doesn ' t markup arbitrary values as safe then becomes a more manageable task .
*
2018-05-05 12:13:16 +02:00
* In the case of AngularJS ' SCE service , one uses { @ link ng . $sce # trustAs $sce . trustAs }
* ( and shorthand methods such as { @ link ng . $sce # trustAsHtml $sce . trustAsHtml } , etc . ) to
2016-03-28 10:46:51 +00:00
* obtain values that will be accepted by SCE / privileged contexts .
*
*
* # # How does it work ?
*
2018-05-05 12:13:16 +02:00
* In privileged contexts , directives and code will bind to the result of { @ link ng . $sce # getTrusted
2016-03-28 10:46:51 +00:00
* $sce . getTrusted ( context , value ) } rather than to the value directly . Directives use { @ link
2018-05-05 12:13:16 +02:00
* ng . $sce # parseAs $sce . parseAs } rather than ` $ parse ` to watch attribute bindings , which performs the
* { @ link ng . $sce # getTrusted $sce . getTrusted } behind the scenes on non - constant literals .
2016-03-28 10:46:51 +00:00
*
* As an example , { @ link ng . directive : ngBindHtml ngBindHtml } uses { @ link
2018-05-05 12:13:16 +02:00
* ng . $sce # parseAsHtml $sce . parseAsHtml ( binding expression ) } . Here ' s the actual code ( slightly
2016-03-28 10:46:51 +00:00
* simplified ) :
*
2018-05-05 12:13:16 +02:00
* ` ` `
* var ngBindHtmlDirective = [ '$sce' , function ( $sce ) {
* return function ( scope , element , attr ) {
* scope . $watch ( $sce . parseAsHtml ( attr . ngBindHtml ) , function ( value ) {
* element . html ( value || '' ) ;
* } ) ;
* } ;
* } ] ;
* ` ` `
2016-03-28 10:46:51 +00:00
*
* # # Impact on loading templates
*
* This applies both to the { @ link ng . directive : ngInclude ` ng-include ` } directive as well as
* ` templateUrl ` ' s specified by { @ link guide / directive directives } .
*
* By default , Angular only loads templates from the same domain and protocol as the application
2018-05-05 12:13:16 +02:00
* document . This is done by calling { @ link ng . $sce # getTrustedResourceUrl
2016-03-28 10:46:51 +00:00
* $sce . getTrustedResourceUrl } on the template URL . To load templates from other domains and / or
2018-05-05 12:13:16 +02:00
* protocols , you may either either { @ link ng . $sceDelegateProvider # resourceUrlWhitelist whitelist
* them } or { @ link ng . $sce # trustAsResourceUrl wrap it } into a trusted value .
2016-03-28 10:46:51 +00:00
*
* * Please note * :
* The browser ' s
2018-05-05 12:13:16 +02:00
* [ Same Origin Policy ] ( https : //code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest)
* and [ Cross - Origin Resource Sharing ( CORS ) ] ( http : //www.w3.org/TR/cors/)
2016-03-28 10:46:51 +00:00
* policy apply in addition to this and may further restrict whether the template is successfully
* loaded . This means that without the right CORS policy , loading templates from a different domain
* won ' t work on all browsers . Also , loading templates from ` file:// ` URL does not work on some
* browsers .
*
2018-05-05 12:13:16 +02:00
* # # This feels like too much overhead
2016-03-28 10:46:51 +00:00
*
* It ' s important to remember that SCE only applies to interpolation expressions .
*
* If your expressions are constant literals , they 're automatically trusted and you don' t need to
2018-05-05 12:13:16 +02:00
* call ` $ sce.trustAs ` on them ( remember to include the ` ngSanitize ` module ) ( e . g .
* ` <div ng-bind-html="'<b>implicitly trusted</b>'"></div> ` ) just works .
2016-03-28 10:46:51 +00:00
*
* Additionally , ` a[href] ` and ` img[src] ` automatically sanitize their URLs and do not pass them
2018-05-05 12:13:16 +02:00
* through { @ link ng . $sce # getTrusted $sce . getTrusted } . SCE doesn ' t play a role here .
2016-03-28 10:46:51 +00:00
*
* The included { @ link ng . $sceDelegate $sceDelegate } comes with sane defaults to allow you to load
* templates in ` ng-include ` from your application ' s domain without having to even know about SCE .
* It blocks loading templates from other domains or loading templates over http from an https
* served document . You can change these by setting your own custom { @ link
2018-05-05 12:13:16 +02:00
* ng . $sceDelegateProvider # resourceUrlWhitelist whitelists } and { @ link
* ng . $sceDelegateProvider # resourceUrlBlacklist blacklists } for matching such URLs .
2016-03-28 10:46:51 +00:00
*
* This significantly reduces the overhead . It is far easier to pay the small overhead and have an
* application that ' s secure and can be audited to verify that with much more ease than bolting
* security onto an application later .
*
* < a name = "contexts" > < / a >
* # # What trusted context types are supported ?
*
* | Context | Notes |
* | -- -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- |
2018-05-05 12:13:16 +02:00
* | ` $ 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 . |
2016-03-28 10:46:51 +00:00
* | ` $ sce.CSS ` | For CSS that ' s safe to source into the application . Currently unused . Feel free to use it in your own directives . |
2018-05-05 12:13:16 +02:00
* | ` $ sce.URL ` | For URLs that are safe to follow as links . Currently unused ( ` <a href= ` and ` <img src= ` sanitize their urls and don ' t constitute an SCE context . |
* | ` $ 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 ` does and therefore contexts requiring values trusted for ` $ sce.RESOURCE_URL ` can be used anywhere that values trusted for ` $ sce.URL ` are required . |
2016-03-28 10:46:51 +00:00
* | ` $ 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 . |
*
2018-05-05 12:13:16 +02:00
* # # Format of items in { @ link ng . $sceDelegateProvider # resourceUrlWhitelist resourceUrlWhitelist } / { @ link ng . $sceDelegateProvider # resourceUrlBlacklist Blacklist } < a name = "resourceUrlPatternItem" > < / a >
2016-03-28 10:46:51 +00:00
*
* Each element in these arrays must be one of the following :
*
* - * * 'self' * *
* - The special * * string * * , ` 'self' ` , can be used to match against all URLs of the * * same
* domain * * as the application document using the * * same protocol * * .
* - * * String * * ( except the special value ` 'self' ` )
* - The string is matched against the full * normalized / absolute URL * of the resource
* being tested ( substring matches are not good enough . )
* - There are exactly * * two wildcard sequences * * - ` * ` and ` ** ` . All other characters
* match themselves .
2018-05-05 12:13:16 +02:00
* - ` * ` : matches zero or more occurrences of any character other than one of the following 6
2016-04-18 12:34:29 +00:00
* characters : '`:`' , '`/`' , '`.`' , '`?`' , '`&`' and ';' . It ' s a useful wildcard for use
2016-03-28 10:46:51 +00:00
* in a whitelist .
2018-05-05 12:13:16 +02:00
* - ` ** ` : matches zero or more occurrences of * any * character . As such , it ' s not
* appropriate for use in a scheme , domain , etc . as it would match too much . ( e . g .
2016-03-28 10:46:51 +00:00
* http : //**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might
2018-05-05 12:13:16 +02:00
* not have been the intention . ) Its usage at the very end of the path is ok . ( e . g .
2016-03-28 10:46:51 +00:00
* http : //foo.example.com/templates/**).
* - * * RegExp * * ( * see caveat below * )
* - * Caveat * : While regular expressions are powerful and offer great flexibility , their syntax
* ( and all the inevitable escaping ) makes them * harder to maintain * . It ' s easy to
* accidentally introduce a bug when one updates a complex expression ( imho , all regexes should
2018-05-05 12:13:16 +02:00
* have good test coverage ) . For instance , the use of ` . ` in the regex is correct only in a
2016-03-28 10:46:51 +00:00
* small number of cases . A ` . ` character in the regex used when matching the scheme or a
* subdomain could be matched against a ` : ` or literal ` . ` that was likely not intended . It
* is highly recommended to use the string patterns and only fall back to regular expressions
2018-05-05 12:13:16 +02:00
* as a last resort .
2016-03-28 10:46:51 +00:00
* - The regular expression must be an instance of RegExp ( i . e . not a string . ) It is
* matched against the * * entire * * * normalized / absolute URL * of the resource being tested
* ( even when the RegExp did not have the ` ^ ` and ` $ ` codes . ) In addition , any flags
* present on the RegExp ( such as multiline , global , ignoreCase ) are ignored .
2018-05-05 12:13:16 +02:00
* - If you are generating your JavaScript from some other templating engine ( not
2016-03-28 10:46:51 +00:00
* recommended , e . g . in issue [ # 4006 ] ( https : //github.com/angular/angular.js/issues/4006)),
* remember to escape your regular expression ( and be aware that you might need more than
* one level of escaping depending on your templating engine and the way you interpolated
* the value . ) Do make use of your platform ' s escaping mechanism as it might be good
2018-05-05 12:13:16 +02:00
* enough before coding your own . E . g . Ruby has
2016-03-28 10:46:51 +00:00
* [ Regexp . escape ( str ) ] ( http : //www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape)
* and Python has [ re . escape ] ( http : //docs.python.org/library/re.html#re.escape).
* Javascript lacks a similar built in function for escaping . Take a look at Google
* Closure library ' s [ goog . string . regExpEscape ( s ) ] (
* http : //docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962).
*
* Refer { @ link ng . $sceDelegateProvider $sceDelegateProvider } for an example .
*
* # # Show me an example using SCE .
*
2018-05-05 12:13:16 +02:00
* < example module = "mySceApp" deps = "angular-sanitize.js" >
* < file name = "index.html" >
* < div ng - controller = "AppController as myCtrl" >
* < i ng - bind - html = "myCtrl.explicitlyTrustedHtml" id = "explicitlyTrustedHtml" > < / i > < b r > < b r >
* < b > User comments < / b > < b r >
* By default , HTML that isn 't explicitly trusted (e.g. Alice' s comment ) is sanitized when
* $sanitize is available . If $sanitize isn ' t available , this results in an error instead of an
* exploit .
* < div class = "well" >
* < div ng - repeat = "userComment in myCtrl.userComments" >
* < b > { { userComment . name } } < / b > :
* < span ng - bind - html = "userComment.htmlComment" class = "htmlComment" > < / s p a n >
* < br >
* < / d i v >
* < / d i v >
* < / d i v >
* < / f i l e >
*
* < file name = "script.js" >
* angular . module ( 'mySceApp' , [ 'ngSanitize' ] )
* . controller ( 'AppController' , [ '$http' , '$templateCache' , '$sce' ,
* function ( $http , $templateCache , $sce ) {
* var self = this ;
* $http . get ( "test_data.json" , { cache : $templateCache } ) . success ( function ( userComments ) {
* self . userComments = userComments ;
* } ) ;
* self . explicitlyTrustedHtml = $sce . trustAsHtml (
* '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' +
* 'sanitization."">Hover over this text.</span>' ) ;
* } ] ) ;
* < / f i l e >
*
* < file name = "test_data.json" >
* [
* { "name" : "Alice" ,
* "htmlComment" :
* "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>"
* } ,
* { "name" : "Bob" ,
* "htmlComment" : "<i>Yes!</i> Am I the only other one?"
* }
* ]
* < / f i l e >
*
* < file name = "protractor.js" type = "protractor" >
* describe ( 'SCE doc demo' , function ( ) {
* it ( 'should sanitize untrusted values' , function ( ) {
* expect ( element . all ( by . css ( '.htmlComment' ) ) . first ( ) . getInnerHtml ( ) )
* . toBe ( '<span>Is <i>anyone</i> reading this?</span>' ) ;
* } ) ;
*
* it ( 'should NOT sanitize explicitly trusted values' , function ( ) {
* expect ( element ( by . id ( 'explicitlyTrustedHtml' ) ) . getInnerHtml ( ) ) . toBe (
* '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' +
* 'sanitization."">Hover over this text.</span>' ) ;
* } ) ;
* } ) ;
* < / f i l e >
* < / e x a m p l e >
2016-03-28 10:46:51 +00:00
*
*
*
* # # Can I disable SCE completely ?
*
* Yes , you can . However , this is strongly discouraged . SCE gives you a lot of security benefits
* for little coding overhead . It will be much harder to take an SCE disabled application and
* either secure it on your own or enable SCE at a later stage . It might make sense to disable SCE
* for cases where you have a lot of existing code that was written before SCE was introduced and
* you ' re migrating them a module at a time .
*
* That said , here ' s how you can completely disable SCE :
*
2018-05-05 12:13:16 +02:00
* ` ` `
* angular . module ( 'myAppWithSceDisabledmyApp' , [ ] ) . config ( function ( $sceProvider ) {
* // Completely disable SCE. For demonstration purposes only!
* // Do not use in new projects.
* $sceProvider . enabled ( false ) ;
* } ) ;
* ` ` `
2016-03-28 10:46:51 +00:00
*
* /
/* jshint maxlen: 100 */
function $SceProvider ( ) {
var enabled = true ;
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $sceProvider # enabled
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ param { boolean = } value If provided , then enables / disables SCE .
* @ return { boolean } true if SCE is enabled , false otherwise .
*
* @ description
* Enables / disables SCE and returns the current value .
* /
2018-05-05 12:13:16 +02:00
this . enabled = function ( value ) {
2016-03-28 10:46:51 +00:00
if ( arguments . length ) {
enabled = ! ! value ;
}
return enabled ;
} ;
/ * D e s i g n n o t e s o n t h e d e f a u l t i m p l e m e n t a t i o n f o r S C E .
*
* The API contract for the SCE delegate
* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
* The SCE delegate object must provide the following 3 methods :
*
* - trustAs ( contextEnum , value )
* This method is used to tell the SCE service that the provided value is OK to use in the
* contexts specified by contextEnum . It must return an object that will be accepted by
* getTrusted ( ) for a compatible contextEnum and return this value .
*
* - valueOf ( value )
* For values that were not produced by trustAs ( ) , return them as is . For values that were
* produced by trustAs ( ) , return the corresponding input value to trustAs . Basically , if
* trustAs is wrapping the given values into some type , this operation unwraps it when given
* such a value .
*
* - getTrusted ( contextEnum , value )
* This function should return the a value that is safe to use in the context specified by
* contextEnum or throw and exception otherwise .
*
* NOTE : This contract deliberately does NOT state that values returned by trustAs ( ) must be
* opaque or wrapped in some holder object . That happens to be an implementation detail . For
* instance , an implementation could maintain a registry of all trusted objects by context . In
* such a case , trustAs ( ) would return the same object that was passed in . getTrusted ( ) would
* return the same object passed in if it was found in the registry under a compatible context or
* throw an exception otherwise . An implementation might only wrap values some of the time based
* on some criteria . getTrusted ( ) might return a value and not throw an exception for special
* constants or objects even if not wrapped . All such implementations fulfill this contract .
*
*
* A note on the inheritance model for SCE contexts
* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
* I ' ve used inheritance and made RESOURCE _URL wrapped types a subtype of URL wrapped types . This
* is purely an implementation details .
*
* The contract is simply this :
*
* getTrusted ( $sce . RESOURCE _URL , value ) succeeding implies that getTrusted ( $sce . URL , value )
* will also succeed .
*
* Inheritance happens to capture this in a natural way . In some future , we
* may not use inheritance anymore . That is OK because no code outside of
* sce . js and sceSpecs . js would need to be aware of this detail .
* /
2018-05-05 12:13:16 +02:00
this . $get = [ '$parse' , '$sceDelegate' , function (
$parse , $sceDelegate ) {
// Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow
2016-03-28 10:46:51 +00:00
// the "expression(javascript expression)" syntax which is insecure.
2018-05-05 12:13:16 +02:00
if ( enabled && msie < 8 ) {
2016-03-28 10:46:51 +00:00
throw $sceMinErr ( 'iequirks' ,
2018-05-05 12:13:16 +02:00
'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' +
2016-03-28 10:46:51 +00:00
'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' +
'document. See http://docs.angularjs.org/api/ng.$sce for more information.' ) ;
}
2018-05-05 12:13:16 +02:00
var sce = shallowCopy ( SCE _CONTEXTS ) ;
2016-03-28 10:46:51 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $sce # isEnabled
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ return { Boolean } true if SCE is enabled , false otherwise . If you want to set the value , you
* have to do it at module config time on { @ link ng . $sceProvider $sceProvider } .
*
* @ description
* Returns a boolean indicating if SCE is enabled .
* /
2018-05-05 12:13:16 +02:00
sce . isEnabled = function ( ) {
2016-03-28 10:46:51 +00:00
return enabled ;
} ;
sce . trustAs = $sceDelegate . trustAs ;
sce . getTrusted = $sceDelegate . getTrusted ;
sce . valueOf = $sceDelegate . valueOf ;
if ( ! enabled ) {
sce . trustAs = sce . getTrusted = function ( type , value ) { return value ; } ;
sce . valueOf = identity ;
}
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $sce # parseAs
2016-03-28 10:46:51 +00:00
*
* @ description
* Converts Angular { @ link guide / expression expression } into a function . This is like { @ link
* ng . $parse $parse } and is identical when the expression is a literal constant . Otherwise , it
2018-05-05 12:13:16 +02:00
* wraps the expression in a call to { @ link ng . $sce # getTrusted $sce . getTrusted ( * type * ,
2016-03-28 10:46:51 +00:00
* * result * ) }
*
* @ param { string } type The kind of SCE context in which this result will be used .
* @ param { string } expression String expression to compile .
* @ returns { function ( context , locals ) } a function which represents the compiled expression :
*
* * ` context ` – ` {object} ` – an object against which any expressions embedded in the strings
* are evaluated against ( typically a scope object ) .
* * ` locals ` – ` {object=} ` – local variables context object , useful for overriding values in
* ` context ` .
* /
sce . parseAs = function sceParseAs ( type , expr ) {
var parsed = $parse ( expr ) ;
if ( parsed . literal && parsed . constant ) {
return parsed ;
} else {
2018-05-05 12:13:16 +02:00
return $parse ( expr , function ( value ) {
return sce . getTrusted ( type , value ) ;
} ) ;
2016-03-28 10:46:51 +00:00
}
} ;
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $sce # trustAs
2016-03-28 10:46:51 +00:00
*
* @ description
2018-05-05 12:13:16 +02:00
* Delegates to { @ link ng . $sceDelegate # trustAs ` $ sceDelegate.trustAs ` } . As such ,
* returns an object that is trusted by angular for use in specified strict contextual
* escaping contexts ( such as ng - bind - html , ng - include , any src attribute
2016-03-28 10:46:51 +00:00
* interpolation , any dom event binding attribute interpolation such as for onclick , etc . )
* that uses the provided value . See * { @ link ng . $sce $sce } for enabling strict contextual
* escaping .
*
* @ param { string } type The kind of context in which this value is safe for use . e . g . url ,
2016-04-18 12:34:29 +00:00
* resource _url , html , js and css .
2016-03-28 10:46:51 +00:00
* @ param { * } value The value that that should be considered trusted / safe .
* @ returns { * } A value that can be used to stand in for the provided ` value ` in places
* where Angular expects a $sce . trustAs ( ) return value .
* /
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $sce # trustAsHtml
2016-03-28 10:46:51 +00:00
*
* @ description
* Shorthand method . ` $ sce.trustAsHtml(value) ` →
2018-05-05 12:13:16 +02:00
* { @ link ng . $sceDelegate # trustAs ` $ sceDelegate.trustAs( $ sce.HTML, value) ` }
2016-03-28 10:46:51 +00:00
*
* @ param { * } value The value to trustAs .
2018-05-05 12:13:16 +02:00
* @ returns { * } An object that can be passed to { @ link ng . $sce # getTrustedHtml
2016-03-28 10:46:51 +00:00
* $sce . getTrustedHtml ( value ) } to obtain the original value . ( privileged directives
* only accept expressions that are either literal constants or are the
2018-05-05 12:13:16 +02:00
* return value of { @ link ng . $sce # trustAs $sce . trustAs } . )
2016-03-28 10:46:51 +00:00
* /
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $sce # trustAsUrl
2016-03-28 10:46:51 +00:00
*
* @ description
* Shorthand method . ` $ sce.trustAsUrl(value) ` →
2018-05-05 12:13:16 +02:00
* { @ link ng . $sceDelegate # trustAs ` $ sceDelegate.trustAs( $ sce.URL, value) ` }
2016-03-28 10:46:51 +00:00
*
* @ param { * } value The value to trustAs .
2018-05-05 12:13:16 +02:00
* @ returns { * } An object that can be passed to { @ link ng . $sce # getTrustedUrl
2016-03-28 10:46:51 +00:00
* $sce . getTrustedUrl ( value ) } to obtain the original value . ( privileged directives
* only accept expressions that are either literal constants or are the
2018-05-05 12:13:16 +02:00
* return value of { @ link ng . $sce # trustAs $sce . trustAs } . )
2016-03-28 10:46:51 +00:00
* /
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $sce # trustAsResourceUrl
2016-03-28 10:46:51 +00:00
*
* @ description
* Shorthand method . ` $ sce.trustAsResourceUrl(value) ` →
2018-05-05 12:13:16 +02:00
* { @ link ng . $sceDelegate # trustAs ` $ sceDelegate.trustAs( $ sce.RESOURCE_URL, value) ` }
2016-03-28 10:46:51 +00:00
*
* @ param { * } value The value to trustAs .
2018-05-05 12:13:16 +02:00
* @ returns { * } An object that can be passed to { @ link ng . $sce # getTrustedResourceUrl
2016-03-28 10:46:51 +00:00
* $sce . getTrustedResourceUrl ( value ) } to obtain the original value . ( privileged directives
* only accept expressions that are either literal constants or are the return
2018-05-05 12:13:16 +02:00
* value of { @ link ng . $sce # trustAs $sce . trustAs } . )
2016-03-28 10:46:51 +00:00
* /
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $sce # trustAsJs
2016-03-28 10:46:51 +00:00
*
* @ description
* Shorthand method . ` $ sce.trustAsJs(value) ` →
2018-05-05 12:13:16 +02:00
* { @ link ng . $sceDelegate # trustAs ` $ sceDelegate.trustAs( $ sce.JS, value) ` }
2016-03-28 10:46:51 +00:00
*
* @ param { * } value The value to trustAs .
2018-05-05 12:13:16 +02:00
* @ returns { * } An object that can be passed to { @ link ng . $sce # getTrustedJs
2016-03-28 10:46:51 +00:00
* $sce . getTrustedJs ( value ) } to obtain the original value . ( privileged directives
* only accept expressions that are either literal constants or are the
2018-05-05 12:13:16 +02:00
* return value of { @ link ng . $sce # trustAs $sce . trustAs } . )
2016-03-28 10:46:51 +00:00
* /
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $sce # getTrusted
2016-03-28 10:46:51 +00:00
*
* @ description
2018-05-05 12:13:16 +02:00
* Delegates to { @ link ng . $sceDelegate # getTrusted ` $ sceDelegate.getTrusted ` } . As such ,
* takes the result of a { @ link ng . $sce # trustAs ` $ sce.trustAs ` } ( ) call and returns the
2016-03-28 10:46:51 +00:00
* originally supplied value if the queried context type is a supertype of the created type .
* If this condition isn ' t satisfied , throws an exception .
*
* @ param { string } type The kind of context in which this value is to be used .
2018-05-05 12:13:16 +02:00
* @ param { * } maybeTrusted The result of a prior { @ link ng . $sce # trustAs ` $ sce.trustAs ` }
2016-03-28 10:46:51 +00:00
* call .
* @ returns { * } The value the was originally provided to
2018-05-05 12:13:16 +02:00
* { @ link ng . $sce # trustAs ` $ sce.trustAs ` } if valid in this context .
2016-03-28 10:46:51 +00:00
* Otherwise , throws an exception .
* /
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $sce # getTrustedHtml
2016-03-28 10:46:51 +00:00
*
* @ description
* Shorthand method . ` $ sce.getTrustedHtml(value) ` →
2018-05-05 12:13:16 +02:00
* { @ link ng . $sceDelegate # getTrusted ` $ sceDelegate.getTrusted( $ sce.HTML, value) ` }
2016-03-28 10:46:51 +00:00
*
* @ param { * } value The value to pass to ` $ sce.getTrusted ` .
* @ returns { * } The return value of ` $ sce.getTrusted( $ sce.HTML, value) `
* /
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $sce # getTrustedCss
2016-03-28 10:46:51 +00:00
*
* @ description
* Shorthand method . ` $ sce.getTrustedCss(value) ` →
2018-05-05 12:13:16 +02:00
* { @ link ng . $sceDelegate # getTrusted ` $ sceDelegate.getTrusted( $ sce.CSS, value) ` }
2016-03-28 10:46:51 +00:00
*
* @ param { * } value The value to pass to ` $ sce.getTrusted ` .
* @ returns { * } The return value of ` $ sce.getTrusted( $ sce.CSS, value) `
* /
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $sce # getTrustedUrl
2016-03-28 10:46:51 +00:00
*
* @ description
* Shorthand method . ` $ sce.getTrustedUrl(value) ` →
2018-05-05 12:13:16 +02:00
* { @ link ng . $sceDelegate # getTrusted ` $ sceDelegate.getTrusted( $ sce.URL, value) ` }
2016-03-28 10:46:51 +00:00
*
* @ param { * } value The value to pass to ` $ sce.getTrusted ` .
* @ returns { * } The return value of ` $ sce.getTrusted( $ sce.URL, value) `
* /
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $sce # getTrustedResourceUrl
2016-03-28 10:46:51 +00:00
*
* @ description
* Shorthand method . ` $ sce.getTrustedResourceUrl(value) ` →
2018-05-05 12:13:16 +02:00
* { @ link ng . $sceDelegate # getTrusted ` $ sceDelegate.getTrusted( $ sce.RESOURCE_URL, value) ` }
2016-03-28 10:46:51 +00:00
*
* @ param { * } value The value to pass to ` $ sceDelegate.getTrusted ` .
* @ returns { * } The return value of ` $ sce.getTrusted( $ sce.RESOURCE_URL, value) `
* /
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $sce # getTrustedJs
2016-03-28 10:46:51 +00:00
*
* @ description
* Shorthand method . ` $ sce.getTrustedJs(value) ` →
2018-05-05 12:13:16 +02:00
* { @ link ng . $sceDelegate # getTrusted ` $ sceDelegate.getTrusted( $ sce.JS, value) ` }
2016-03-28 10:46:51 +00:00
*
* @ param { * } value The value to pass to ` $ sce.getTrusted ` .
* @ returns { * } The return value of ` $ sce.getTrusted( $ sce.JS, value) `
* /
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $sce # parseAsHtml
2016-03-28 10:46:51 +00:00
*
* @ description
* Shorthand method . ` $ sce.parseAsHtml(expression string) ` →
2018-05-05 12:13:16 +02:00
* { @ link ng . $sce # parseAs ` $ sce.parseAs( $ sce.HTML, value) ` }
2016-03-28 10:46:51 +00:00
*
* @ param { string } expression String expression to compile .
* @ returns { function ( context , locals ) } a function which represents the compiled expression :
*
* * ` context ` – ` {object} ` – an object against which any expressions embedded in the strings
* are evaluated against ( typically a scope object ) .
* * ` locals ` – ` {object=} ` – local variables context object , useful for overriding values in
* ` context ` .
* /
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $sce # parseAsCss
2016-03-28 10:46:51 +00:00
*
* @ description
* Shorthand method . ` $ sce.parseAsCss(value) ` →
2018-05-05 12:13:16 +02:00
* { @ link ng . $sce # parseAs ` $ sce.parseAs( $ sce.CSS, value) ` }
2016-03-28 10:46:51 +00:00
*
* @ param { string } expression String expression to compile .
* @ returns { function ( context , locals ) } a function which represents the compiled expression :
*
* * ` context ` – ` {object} ` – an object against which any expressions embedded in the strings
* are evaluated against ( typically a scope object ) .
* * ` locals ` – ` {object=} ` – local variables context object , useful for overriding values in
* ` context ` .
* /
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $sce # parseAsUrl
2016-03-28 10:46:51 +00:00
*
* @ description
* Shorthand method . ` $ sce.parseAsUrl(value) ` →
2018-05-05 12:13:16 +02:00
* { @ link ng . $sce # parseAs ` $ sce.parseAs( $ sce.URL, value) ` }
2016-03-28 10:46:51 +00:00
*
* @ param { string } expression String expression to compile .
* @ returns { function ( context , locals ) } a function which represents the compiled expression :
*
* * ` context ` – ` {object} ` – an object against which any expressions embedded in the strings
* are evaluated against ( typically a scope object ) .
* * ` locals ` – ` {object=} ` – local variables context object , useful for overriding values in
* ` context ` .
* /
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $sce # parseAsResourceUrl
2016-03-28 10:46:51 +00:00
*
* @ description
* Shorthand method . ` $ sce.parseAsResourceUrl(value) ` →
2018-05-05 12:13:16 +02:00
* { @ link ng . $sce # parseAs ` $ sce.parseAs( $ sce.RESOURCE_URL, value) ` }
2016-03-28 10:46:51 +00:00
*
* @ param { string } expression String expression to compile .
* @ returns { function ( context , locals ) } a function which represents the compiled expression :
*
* * ` context ` – ` {object} ` – an object against which any expressions embedded in the strings
* are evaluated against ( typically a scope object ) .
* * ` locals ` – ` {object=} ` – local variables context object , useful for overriding values in
* ` context ` .
* /
/ * *
* @ ngdoc method
2018-05-05 12:13:16 +02:00
* @ name $sce # parseAsJs
2016-03-28 10:46:51 +00:00
*
* @ description
* Shorthand method . ` $ sce.parseAsJs(value) ` →
2018-05-05 12:13:16 +02:00
* { @ link ng . $sce # parseAs ` $ sce.parseAs( $ sce.JS, value) ` }
2016-03-28 10:46:51 +00:00
*
* @ param { string } expression String expression to compile .
* @ returns { function ( context , locals ) } a function which represents the compiled expression :
*
* * ` context ` – ` {object} ` – an object against which any expressions embedded in the strings
* are evaluated against ( typically a scope object ) .
* * ` locals ` – ` {object=} ` – local variables context object , useful for overriding values in
* ` context ` .
* /
// Shorthand delegations.
var parse = sce . parseAs ,
getTrusted = sce . getTrusted ,
trustAs = sce . trustAs ;
2018-05-05 12:13:16 +02:00
forEach ( SCE _CONTEXTS , function ( enumValue , name ) {
2016-03-28 10:46:51 +00:00
var lName = lowercase ( name ) ;
2018-05-05 12:13:16 +02:00
sce [ camelCase ( "parse_as_" + lName ) ] = function ( expr ) {
2016-03-28 10:46:51 +00:00
return parse ( enumValue , expr ) ;
} ;
2018-05-05 12:13:16 +02:00
sce [ camelCase ( "get_trusted_" + lName ) ] = function ( value ) {
2016-03-28 10:46:51 +00:00
return getTrusted ( enumValue , value ) ;
} ;
2018-05-05 12:13:16 +02:00
sce [ camelCase ( "trust_as_" + lName ) ] = function ( value ) {
2016-03-28 10:46:51 +00:00
return trustAs ( enumValue , value ) ;
} ;
} ) ;
return sce ;
} ] ;
}
/ * *
* ! ! ! This is an undocumented "private" service ! ! !
*
2018-05-05 12:13:16 +02:00
* @ name $sniffer
2016-03-28 10:46:51 +00:00
* @ requires $window
* @ requires $document
*
* @ property { boolean } history Does the browser support html5 history api ?
* @ property { boolean } transitions Does the browser support CSS transition events ?
* @ property { boolean } animations Does the browser support CSS animation events ?
*
* @ description
* This is very simple implementation of testing browser ' s features .
* /
function $SnifferProvider ( ) {
this . $get = [ '$window' , '$document' , function ( $window , $document ) {
var eventSupport = { } ,
android =
2016-04-18 12:34:29 +00:00
int ( ( /android (\d+)/ . exec ( lowercase ( ( $window . navigator || { } ) . userAgent ) ) || [ ] ) [ 1 ] ) ,
2016-03-28 10:46:51 +00:00
boxee = /Boxee/i . test ( ( $window . navigator || { } ) . userAgent ) ,
document = $document [ 0 ] || { } ,
vendorPrefix ,
2018-05-05 12:13:16 +02:00
vendorRegex = /^(Moz|webkit|ms)(?=[A-Z])/ ,
2016-03-28 10:46:51 +00:00
bodyStyle = document . body && document . body . style ,
transitions = false ,
animations = false ,
match ;
if ( bodyStyle ) {
2018-05-05 12:13:16 +02:00
for ( var prop in bodyStyle ) {
if ( match = vendorRegex . exec ( prop ) ) {
2016-03-28 10:46:51 +00:00
vendorPrefix = match [ 0 ] ;
vendorPrefix = vendorPrefix . substr ( 0 , 1 ) . toUpperCase ( ) + vendorPrefix . substr ( 1 ) ;
break ;
}
}
2018-05-05 12:13:16 +02:00
if ( ! vendorPrefix ) {
2016-03-28 10:46:51 +00:00
vendorPrefix = ( 'WebkitOpacity' in bodyStyle ) && 'webkit' ;
}
transitions = ! ! ( ( 'transition' in bodyStyle ) || ( vendorPrefix + 'Transition' in bodyStyle ) ) ;
animations = ! ! ( ( 'animation' in bodyStyle ) || ( vendorPrefix + 'Animation' in bodyStyle ) ) ;
2018-05-05 12:13:16 +02:00
if ( android && ( ! transitions || ! animations ) ) {
2016-04-18 12:34:29 +00:00
transitions = isString ( document . body . style . webkitTransition ) ;
animations = isString ( document . body . style . webkitAnimation ) ;
2016-03-28 10:46:51 +00:00
}
}
return {
// Android has history.pushState, but it does not update location correctly
// so let's not use the history API at all.
// http://code.google.com/p/android/issues/detail?id=17471
// https://github.com/angular/angular.js/issues/904
// older webkit browser (533.9) on Boxee box has exactly the same problem as Android has
// so let's not use the history API also
// We are purposefully using `!(android < 4)` to cover the case when `android` is undefined
// jshint -W018
2016-04-18 12:34:29 +00:00
history : ! ! ( $window . history && $window . history . pushState && ! ( android < 4 ) && ! boxee ) ,
2016-03-28 10:46:51 +00:00
// jshint +W018
hasEvent : function ( event ) {
// IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
// it. In particular the event is not fired when backspace or delete key are pressed or
// when cut operation is performed.
2018-05-05 12:13:16 +02:00
// IE10+ implements 'input' event but it erroneously fires under various situations,
// e.g. when placeholder changes, or a form is focused.
if ( event === 'input' && msie <= 11 ) return false ;
2016-03-28 10:46:51 +00:00
if ( isUndefined ( eventSupport [ event ] ) ) {
var divElm = document . createElement ( 'div' ) ;
eventSupport [ event ] = 'on' + event in divElm ;
}
return eventSupport [ event ] ;
} ,
csp : csp ( ) ,
vendorPrefix : vendorPrefix ,
2018-05-05 12:13:16 +02:00
transitions : transitions ,
animations : animations ,
android : android
} ;
} ] ;
}
var $compileMinErr = minErr ( '$compile' ) ;
/ * *
* @ ngdoc service
* @ name $templateRequest
*
* @ description
* The ` $ templateRequest ` service runs security checks then downloads the provided template using
* ` $ http ` and , upon success , stores the contents inside of ` $ templateCache ` . If the HTTP request
* fails or the response data of the HTTP request is empty , a ` $ compile ` error will be thrown ( the
* exception can be thwarted by setting the 2 nd parameter of the function to true ) . Note that the
* contents of ` $ templateCache ` are trusted , so the call to ` $ sce.getTrustedUrl(tpl) ` is omitted
* when ` tpl ` is of type string and ` $ templateCache ` has the matching entry .
*
* @ param { string | TrustedResourceUrl } tpl The HTTP request template URL
* @ param { boolean = } ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty
*
* @ return { Promise } the HTTP Promise for the given .
*
* @ property { number } totalPendingRequests total amount of pending template requests being downloaded .
* /
function $TemplateRequestProvider ( ) {
this . $get = [ '$templateCache' , '$http' , '$q' , '$sce' , function ( $templateCache , $http , $q , $sce ) {
function handleRequestFn ( tpl , ignoreRequestError ) {
handleRequestFn . totalPendingRequests ++ ;
// We consider the template cache holds only trusted templates, so
// there's no need to go through whitelisting again for keys that already
// are included in there. This also makes Angular accept any script
// directive, no matter its name. However, we still need to unwrap trusted
// types.
if ( ! isString ( tpl ) || ! $templateCache . get ( tpl ) ) {
tpl = $sce . getTrustedResourceUrl ( tpl ) ;
}
var transformResponse = $http . defaults && $http . defaults . transformResponse ;
if ( isArray ( transformResponse ) ) {
transformResponse = transformResponse . filter ( function ( transformer ) {
return transformer !== defaultHttpResponseTransform ;
} ) ;
} else if ( transformResponse === defaultHttpResponseTransform ) {
transformResponse = null ;
}
var httpOptions = {
cache : $templateCache ,
transformResponse : transformResponse
} ;
return $http . get ( tpl , httpOptions )
[ 'finally' ] ( function ( ) {
handleRequestFn . totalPendingRequests -- ;
} )
. then ( function ( response ) {
return response . data ;
} , handleError ) ;
function handleError ( resp ) {
if ( ! ignoreRequestError ) {
throw $compileMinErr ( 'tpload' , 'Failed to load template: {0}' , tpl ) ;
}
return $q . reject ( resp ) ;
}
}
handleRequestFn . totalPendingRequests = 0 ;
return handleRequestFn ;
} ] ;
}
function $$TestabilityProvider ( ) {
this . $get = [ '$rootScope' , '$browser' , '$location' ,
function ( $rootScope , $browser , $location ) {
/ * *
* @ name $testability
*
* @ description
* The private $$testability service provides a collection of methods for use when debugging
* or by automated test and debugging tools .
* /
var testability = { } ;
/ * *
* @ name $$testability # findBindings
*
* @ description
* Returns an array of elements that are bound ( via ng - bind or { { } } )
* to expressions matching the input .
*
* @ param { Element } element The element root to search from .
* @ param { string } expression The binding expression to match .
* @ param { boolean } opt _exactMatch If true , only returns exact matches
* for the expression . Filters and whitespace are ignored .
* /
testability . findBindings = function ( element , expression , opt _exactMatch ) {
var bindings = element . getElementsByClassName ( 'ng-binding' ) ;
var matches = [ ] ;
forEach ( bindings , function ( binding ) {
var dataBinding = angular . element ( binding ) . data ( '$binding' ) ;
if ( dataBinding ) {
forEach ( dataBinding , function ( bindingName ) {
if ( opt _exactMatch ) {
var matcher = new RegExp ( '(^|\\s)' + escapeForRegexp ( expression ) + '(\\s|\\||$)' ) ;
if ( matcher . test ( bindingName ) ) {
matches . push ( binding ) ;
}
} else {
if ( bindingName . indexOf ( expression ) != - 1 ) {
matches . push ( binding ) ;
}
}
} ) ;
}
} ) ;
return matches ;
2016-03-28 10:46:51 +00:00
} ;
2018-05-05 12:13:16 +02:00
/ * *
* @ name $$testability # findModels
*
* @ description
* Returns an array of elements that are two - way found via ng - model to
* expressions matching the input .
*
* @ param { Element } element The element root to search from .
* @ param { string } expression The model expression to match .
* @ param { boolean } opt _exactMatch If true , only returns exact matches
* for the expression .
* /
testability . findModels = function ( element , expression , opt _exactMatch ) {
var prefixes = [ 'ng-' , 'data-ng-' , 'ng\\:' ] ;
for ( var p = 0 ; p < prefixes . length ; ++ p ) {
var attributeEquals = opt _exactMatch ? '=' : '*=' ;
var selector = '[' + prefixes [ p ] + 'model' + attributeEquals + '"' + expression + '"]' ;
var elements = element . querySelectorAll ( selector ) ;
if ( elements . length ) {
return elements ;
}
}
} ;
/ * *
* @ name $$testability # getLocation
*
* @ description
* Shortcut for getting the location in a browser agnostic way . Returns
* the path , search , and hash . ( e . g . / path ? a = b # hash )
* /
testability . getLocation = function ( ) {
return $location . url ( ) ;
} ;
/ * *
* @ name $$testability # setLocation
*
* @ description
* Shortcut for navigating to a location without doing a full page reload .
*
* @ param { string } url The location url ( path , search and hash ,
* e . g . / path ? a = b # hash ) to go to .
* /
testability . setLocation = function ( url ) {
if ( url !== $location . url ( ) ) {
$location . url ( url ) ;
$rootScope . $digest ( ) ;
}
} ;
/ * *
* @ name $$testability # whenStable
*
* @ description
* Calls the callback when $timeout and $http requests are completed .
*
* @ param { function } callback
* /
testability . whenStable = function ( callback ) {
$browser . notifyWhenNoOutstandingRequests ( callback ) ;
} ;
return testability ;
2016-03-28 10:46:51 +00:00
} ] ;
}
2016-04-18 12:34:29 +00:00
function $TimeoutProvider ( ) {
2018-05-05 12:13:16 +02:00
this . $get = [ '$rootScope' , '$browser' , '$q' , '$$q' , '$exceptionHandler' ,
function ( $rootScope , $browser , $q , $$q , $exceptionHandler ) {
2016-04-18 12:34:29 +00:00
var deferreds = { } ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc service
* @ name $timeout
2016-04-18 12:34:29 +00:00
*
* @ description
* Angular ' s wrapper for ` window.setTimeout ` . The ` fn ` function is wrapped into a try / c a t c h
* block and delegates any exceptions to
* { @ link ng . $exceptionHandler $exceptionHandler } service .
*
* The return value of registering a timeout function is a promise , which will be resolved when
* the timeout is reached and the timeout function is executed .
*
* To cancel a timeout request , call ` $ timeout.cancel(promise) ` .
*
* In tests you can use { @ link ngMock . $timeout ` $ timeout.flush() ` } to
* synchronously flush the queue of deferred functions .
*
* @ param { function ( ) } fn A function , whose execution should be delayed .
* @ param { number = } [ delay = 0 ] Delay in milliseconds .
* @ param { boolean = } [ invokeApply = true ] If set to ` false ` skips model dirty checking , otherwise
2018-05-05 12:13:16 +02:00
* will invoke ` fn ` within the { @ link ng . $rootScope . Scope # $apply $apply } block .
2016-04-18 12:34:29 +00:00
* @ returns { Promise } Promise that will be resolved when the timeout is reached . The value this
* promise will be resolved with is the return value of the ` fn ` function .
2016-05-18 00:10:50 +00:00
*
2016-04-18 12:34:29 +00:00
* /
function timeout ( fn , delay , invokeApply ) {
2018-05-05 12:13:16 +02:00
var skipApply = ( isDefined ( invokeApply ) && ! invokeApply ) ,
deferred = ( skipApply ? $$q : $q ) . defer ( ) ,
2016-04-18 12:34:29 +00:00
promise = deferred . promise ,
timeoutId ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
timeoutId = $browser . defer ( function ( ) {
try {
deferred . resolve ( fn ( ) ) ;
2018-05-05 12:13:16 +02:00
} catch ( e ) {
2016-04-18 12:34:29 +00:00
deferred . reject ( e ) ;
$exceptionHandler ( e ) ;
}
finally {
delete deferreds [ promise . $$timeoutId ] ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( ! skipApply ) $rootScope . $apply ( ) ;
} , delay ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
promise . $$timeoutId = timeoutId ;
deferreds [ timeoutId ] = deferred ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
return promise ;
2016-03-28 10:46:51 +00:00
}
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $timeout # cancel
2016-03-28 10:46:51 +00:00
*
* @ description
* Cancels a task associated with the ` promise ` . As a result of this , the promise will be
* resolved with a rejection .
*
* @ param { Promise = } promise Promise returned by the ` $ timeout ` function .
* @ returns { boolean } Returns ` true ` if the task hasn ' t executed yet and was successfully
* canceled .
* /
timeout . cancel = function ( promise ) {
if ( promise && promise . $$timeoutId in deferreds ) {
deferreds [ promise . $$timeoutId ] . reject ( 'canceled' ) ;
delete deferreds [ promise . $$timeoutId ] ;
return $browser . defer . cancel ( promise . $$timeoutId ) ;
}
return false ;
} ;
return timeout ;
} ] ;
}
// NOTE: The usage of window and document instead of $window and $document here is
// deliberate. This service depends on the specific behavior of anchor nodes created by the
// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and
// cause us to break tests. In addition, when the browser resolves a URL for XHR, it
// doesn't know about mocked locations and resolves URLs to the real document - which is
// exactly the behavior needed here. There is little value is mocking these out for this
// service.
var urlParsingNode = document . createElement ( "a" ) ;
2018-05-05 12:13:16 +02:00
var originUrl = urlResolve ( window . location . href ) ;
2016-03-28 10:46:51 +00:00
/ * *
*
* Implementation Notes for non - IE browsers
* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
* Assigning a URL to the href property of an anchor DOM node , even one attached to the DOM ,
* results both in the normalizing and parsing of the URL . Normalizing means that a relative
* URL will be resolved into an absolute URL in the context of the application document .
* Parsing means that the anchor node ' s host , hostname , protocol , port , pathname and related
* properties are all populated to reflect the normalized URL . This approach has wide
* compatibility - Safari 1 + , Mozilla 1 + , Opera 7 + , e etc . See
* http : //www.aptana.com/reference/html/api/HTMLAnchorElement.html
*
* Implementation Notes for IE
* -- -- -- -- -- -- -- -- -- -- -- -- -- -
2018-05-05 12:13:16 +02:00
* IE <= 10 normalizes the URL when assigned to the anchor node similar to the other
2016-03-28 10:46:51 +00:00
* browsers . However , the parsed components will not be set if the URL assigned did not specify
* them . ( e . g . if you assign a . href = "foo" , then a . protocol , a . host , etc . will be empty . ) We
* work around that by performing the parsing in a 2 nd step by taking a previously normalized
* URL ( e . g . by assigning to a . href ) and assigning it a . href again . This correctly populates the
* properties such as protocol , hostname , port , etc .
*
* References :
* http : //developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement
* http : //www.aptana.com/reference/html/api/HTMLAnchorElement.html
* http : //url.spec.whatwg.org/#urlutils
* https : //github.com/angular/angular.js/pull/2902
* http : //james.padolsey.com/javascript/parsing-urls-with-the-dom/
*
2018-05-05 12:13:16 +02:00
* @ kind function
2016-03-28 10:46:51 +00:00
* @ param { string } url The URL to be parsed .
* @ description Normalizes and parses a URL .
* @ returns { object } Returns the normalized URL as a dictionary .
*
* | member name | Description |
* | -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- |
* | href | A normalized version of the provided URL if it was not an absolute URL |
* | protocol | The protocol including the trailing colon |
* | host | The host and port ( if the port is non - default ) of the normalizedUrl |
* | search | The search params , minus the question mark |
* | hash | The hash string , minus the hash symbol
* | hostname | The hostname
* | port | The port , without ":"
* | pathname | The pathname , beginning with "/"
*
* /
2018-05-05 12:13:16 +02:00
function urlResolve ( url ) {
2016-03-28 10:46:51 +00:00
var href = url ;
if ( msie ) {
// Normalize before parse. Refer Implementation Notes on why this is
// done in two steps on IE.
urlParsingNode . setAttribute ( "href" , href ) ;
href = urlParsingNode . href ;
}
urlParsingNode . setAttribute ( 'href' , href ) ;
// urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils
return {
href : urlParsingNode . href ,
protocol : urlParsingNode . protocol ? urlParsingNode . protocol . replace ( /:$/ , '' ) : '' ,
host : urlParsingNode . host ,
search : urlParsingNode . search ? urlParsingNode . search . replace ( /^\?/ , '' ) : '' ,
hash : urlParsingNode . hash ? urlParsingNode . hash . replace ( /^#/ , '' ) : '' ,
hostname : urlParsingNode . hostname ,
port : urlParsingNode . port ,
pathname : ( urlParsingNode . pathname . charAt ( 0 ) === '/' )
? urlParsingNode . pathname
: '/' + urlParsingNode . pathname
} ;
}
/ * *
* Parse a request URL and determine whether this is a same - origin request as the application document .
*
* @ param { string | object } requestUrl The url of the request as a string that will be resolved
* or a parsed URL object .
* @ returns { boolean } Whether the request is for the same origin as the application document .
* /
function urlIsSameOrigin ( requestUrl ) {
var parsed = ( isString ( requestUrl ) ) ? urlResolve ( requestUrl ) : requestUrl ;
return ( parsed . protocol === originUrl . protocol &&
parsed . host === originUrl . host ) ;
}
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc service
* @ name $window
2016-03-28 10:46:51 +00:00
*
* @ description
* A reference to the browser ' s ` window ` object . While ` window `
* is globally available in JavaScript , it causes testability problems , because
* it is a global variable . In angular we always refer to it through the
* ` $ window ` service , so it may be overridden , removed or mocked for testing .
*
* Expressions , like the one defined for the ` ngClick ` directive in the example
* below , are evaluated with respect to the current scope . Therefore , there is
* no risk of inadvertently coding in a dependency on a global value in such an
* expression .
*
* @ example
2018-05-05 12:13:16 +02:00
< example module = "windowExample" >
< file name = "index.html" >
2016-03-28 10:46:51 +00:00
< script >
2018-05-05 12:13:16 +02:00
angular . module ( 'windowExample' , [ ] )
. controller ( 'ExampleController' , [ '$scope' , '$window' , function ( $scope , $window ) {
$scope . greeting = 'Hello, World!' ;
$scope . doGreeting = function ( greeting ) {
2016-03-28 10:46:51 +00:00
$window . alert ( greeting ) ;
2018-05-05 12:13:16 +02:00
} ;
} ] ) ;
2016-03-28 10:46:51 +00:00
< / s c r i p t >
2018-05-05 12:13:16 +02:00
< div ng - controller = "ExampleController" >
2016-04-18 12:34:29 +00:00
< input type = "text" ng - model = "greeting" / >
2016-03-28 10:46:51 +00:00
< button ng - click = "doGreeting(greeting)" > ALERT < / b u t t o n >
< / d i v >
2018-05-05 12:13:16 +02:00
< / f i l e >
< file name = "protractor.js" type = "protractor" >
2016-03-28 10:46:51 +00:00
it ( 'should display the greeting in the input box' , function ( ) {
2018-05-05 12:13:16 +02:00
element ( by . model ( 'greeting' ) ) . sendKeys ( 'Hello, E2E Tests' ) ;
2016-03-28 10:46:51 +00:00
// If we click the button it will block the test runner
// element(':button').click();
} ) ;
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
2018-05-05 12:13:16 +02:00
function $WindowProvider ( ) {
2016-03-28 10:46:51 +00:00
this . $get = valueFn ( window ) ;
}
2018-05-05 12:13:16 +02:00
/ * g l o b a l c u r r e n c y F i l t e r : t r u e ,
dateFilter : true ,
filterFilter : true ,
jsonFilter : true ,
limitToFilter : true ,
lowercaseFilter : true ,
numberFilter : true ,
orderByFilter : true ,
uppercaseFilter : true ,
* /
2016-03-28 10:46:51 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc provider
* @ name $filterProvider
2016-03-28 10:46:51 +00:00
* @ description
*
* Filters are just functions which transform input to an output . However filters need to be
* Dependency Injected . To achieve this a filter definition consists of a factory function which is
* annotated with dependencies and is responsible for creating a filter function .
*
2018-05-05 12:13:16 +02:00
* < div class = "alert alert-warning" >
* * * Note : * * Filter names must be valid angular { @ link expression } identifiers , such as ` uppercase ` or ` orderBy ` .
* Names with special characters , such as hyphens and dots , are not allowed . If you wish to namespace
* your filters , then you can use capitalization ( ` myappSubsectionFilterx ` ) or underscores
* ( ` myapp_subsection_filterx ` ) .
* < / d i v >
*
* ` ` ` js
2016-03-28 10:46:51 +00:00
* // Filter registration
* function MyModule ( $provide , $filterProvider ) {
* // create a service to demonstrate injection (not always needed)
* $provide . value ( 'greet' , function ( name ) {
* return 'Hello ' + name + '!' ;
* } ) ;
*
* // register a filter factory which uses the
* // greet service to demonstrate DI.
* $filterProvider . register ( 'greet' , function ( greet ) {
* // return the filter function which uses the greet service
* // to generate salutation
* return function ( text ) {
* // filters need to be forgiving so check input validity
* return text && greet ( text ) || text ;
* } ;
* } ) ;
* }
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
* The filter function is registered with the ` $ injector ` under the filter name suffix with
* ` Filter ` .
2016-05-18 00:10:50 +00:00
*
2018-05-05 12:13:16 +02:00
* ` ` ` js
2016-03-28 10:46:51 +00:00
* it ( 'should be the same instance' , inject (
* function ( $filterProvider ) {
* $filterProvider . register ( 'reverse' , function ( ) {
* return ... ;
* } ) ;
* } ,
* function ( $filter , reverseFilter ) {
* expect ( $filter ( 'reverse' ) ) . toBe ( reverseFilter ) ;
* } ) ;
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
*
* For more information about how angular filters work , and how to create your own filters , see
* { @ link guide / filter Filters } in the Angular Developer Guide .
* /
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc service
* @ name $filter
* @ kind function
2016-03-28 10:46:51 +00:00
* @ description
* Filters are used for formatting data displayed to the user .
*
* The general syntax in templates is as follows :
*
* { { expression [ | filter _name [ : parameter _value ] ... ] } }
*
* @ param { String } name Name of the filter function to retrieve
* @ return { Function } the filter function
2018-05-05 12:13:16 +02:00
* @ example
< example name = "$filter" module = "filterExample" >
< file name = "index.html" >
< div ng - controller = "MainCtrl" >
< h3 > { { originalText } } < / h 3 >
< h3 > { { filteredText } } < / h 3 >
< / d i v >
< / f i l e >
< file name = "script.js" >
angular . module ( 'filterExample' , [ ] )
. controller ( 'MainCtrl' , function ( $scope , $filter ) {
$scope . originalText = 'hello' ;
$scope . filteredText = $filter ( 'uppercase' ) ( $scope . originalText ) ;
} ) ;
< / f i l e >
< / e x a m p l e >
* /
2016-03-28 10:46:51 +00:00
$FilterProvider . $inject = [ '$provide' ] ;
function $FilterProvider ( $provide ) {
var suffix = 'Filter' ;
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name $filterProvider # register
2016-03-28 10:46:51 +00:00
* @ param { string | Object } name Name of the filter function , or an object map of filters where
* the keys are the filter names and the values are the filter factories .
2018-05-05 12:13:16 +02:00
*
* < div class = "alert alert-warning" >
* * * Note : * * Filter names must be valid angular { @ link expression } identifiers , such as ` uppercase ` or ` orderBy ` .
* Names with special characters , such as hyphens and dots , are not allowed . If you wish to namespace
* your filters , then you can use capitalization ( ` myappSubsectionFilterx ` ) or underscores
* ( ` myapp_subsection_filterx ` ) .
* < / d i v >
2016-03-28 10:46:51 +00:00
* @ returns { Object } Registered filter instance , or if a map of filters was provided then a map
* of the registered filter instances .
* /
function register ( name , factory ) {
2018-05-05 12:13:16 +02:00
if ( isObject ( name ) ) {
2016-03-28 10:46:51 +00:00
var filters = { } ;
forEach ( name , function ( filter , key ) {
filters [ key ] = register ( key , filter ) ;
} ) ;
return filters ;
} else {
return $provide . factory ( name + suffix , factory ) ;
}
}
this . register = register ;
this . $get = [ '$injector' , function ( $injector ) {
return function ( name ) {
return $injector . get ( name + suffix ) ;
} ;
} ] ;
////////////////////////////////////////
2016-05-18 00:10:50 +00:00
2016-03-28 10:46:51 +00:00
/ * g l o b a l
currencyFilter : false ,
dateFilter : false ,
filterFilter : false ,
jsonFilter : false ,
limitToFilter : false ,
lowercaseFilter : false ,
numberFilter : false ,
orderByFilter : false ,
uppercaseFilter : false ,
* /
register ( 'currency' , currencyFilter ) ;
register ( 'date' , dateFilter ) ;
register ( 'filter' , filterFilter ) ;
register ( 'json' , jsonFilter ) ;
register ( 'limitTo' , limitToFilter ) ;
register ( 'lowercase' , lowercaseFilter ) ;
register ( 'number' , numberFilter ) ;
register ( 'orderBy' , orderByFilter ) ;
register ( 'uppercase' , uppercaseFilter ) ;
}
/ * *
* @ ngdoc filter
2018-05-05 12:13:16 +02:00
* @ name filter
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Selects a subset of items from ` array ` and returns it as a new array .
*
* @ param { Array } array The source array .
* @ param { string | Object | function ( ) } expression The predicate to be used for selecting items from
* ` array ` .
*
* Can be one of :
*
2018-05-05 12:13:16 +02:00
* - ` string ` : The string is used for matching against the contents of the ` array ` . All strings or
* objects with string properties in ` array ` that match this string will be returned . This also
* applies to nested object properties .
* The predicate can be negated by prefixing the string with ` ! ` .
2016-03-28 10:46:51 +00:00
*
* - ` Object ` : A pattern object can be used to filter specific properties on objects contained
* by ` array ` . For example ` {name:"M", phone:"1"} ` predicate will return an array of items
* which have property ` name ` containing "M" and property ` phone ` containing "1" . A special
* property name ` $ ` can be used ( as in ` { $ :"text"} ` ) to accept a match against any
2018-05-05 12:13:16 +02:00
* property of the object or its nested object properties . That ' s equivalent to the simple
* substring match with a ` string ` as described above . The predicate can be negated by prefixing
* the string with ` ! ` .
* For example ` {name: "!M"} ` predicate will return an array of items which have property ` name `
* not containing "M" .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* Note that a named property will match properties on the same level only , while the special
* ` $ ` property will match properties on the same level or deeper . E . g . an array item like
* ` {name: {first: 'John', last: 'Doe'}} ` will * * not * * be matched by ` {name: 'John'} ` , but
* * * will * * be matched by ` { $ : 'John'} ` .
*
* - ` function(value, index) ` : A predicate function can be used to write arbitrary filters . The
* function is called for each element of ` array ` . The final result is an array of those
* elements that the predicate returned true for .
2016-03-28 10:46:51 +00:00
*
* @ param { function ( actual , expected ) | true | undefined } comparator Comparator which is used in
* determining if the expected value ( from the filter expression ) and actual value ( from
* the object in the array ) should be considered a match .
*
* Can be one of :
*
2018-05-05 12:13:16 +02:00
* - ` function(actual, expected) ` :
* The function will be given the object value and the predicate value to compare and
* should return true if both values should be considered equal .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* - ` true ` : A shorthand for ` function(actual, expected) { return angular.equals(actual, expected)} ` .
* This is essentially strict comparison of expected and actual .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* - ` false|undefined ` : A short hand for a function which will look for a substring match in case
* insensitive way .
2016-03-28 10:46:51 +00:00
*
* @ example
2018-05-05 12:13:16 +02:00
< example >
< file name = "index.html" >
2016-03-28 10:46:51 +00:00
< div ng - init = " friends = [ { name : 'John' , phone : '555-1276' } ,
{ name : 'Mary' , phone : '800-BIG-MARY' } ,
{ name : 'Mike' , phone : '555-4321' } ,
{ name : 'Adam' , phone : '555-5678' } ,
{ name : 'Julie' , phone : '555-8765' } ,
{ name : 'Juliette' , phone : '555-5678' } ] " > < / d i v >
2016-04-18 12:34:29 +00:00
Search : < input ng - model = "searchText" >
2016-03-28 10:46:51 +00:00
< table id = "searchTextResults" >
< tr > < th > Name < / t h > < t h > P h o n e < / t h > < / t r >
< tr ng - repeat = "friend in friends | filter:searchText" >
< td > { { friend . name } } < / t d >
< td > { { friend . phone } } < / t d >
< / t r >
< / t a b l e >
< hr >
2016-04-18 12:34:29 +00:00
Any : < input ng - model = "search.$" > < br >
Name only < input ng - model = "search.name" > < br >
Phone only < input ng - model = "search.phone" > < br >
Equality < input type = "checkbox" ng - model = "strict" > < br >
2016-03-28 10:46:51 +00:00
< table id = "searchObjResults" >
< tr > < th > Name < / t h > < t h > P h o n e < / t h > < / t r >
2018-05-05 12:13:16 +02:00
< tr ng - repeat = "friendObj in friends | filter:search:strict" >
< td > { { friendObj . name } } < / t d >
< td > { { friendObj . phone } } < / t d >
2016-03-28 10:46:51 +00:00
< / t r >
< / t a b l e >
2018-05-05 12:13:16 +02:00
< / f i l e >
< file name = "protractor.js" type = "protractor" >
var expectFriendNames = function ( expectedNames , key ) {
element . all ( by . repeater ( key + ' in friends' ) . column ( key + '.name' ) ) . then ( function ( arr ) {
arr . forEach ( function ( wd , i ) {
expect ( wd . getText ( ) ) . toMatch ( expectedNames [ i ] ) ;
} ) ;
} ) ;
} ;
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
it ( 'should search across all fields when filtering with a string' , function ( ) {
var searchText = element ( by . model ( 'searchText' ) ) ;
searchText . clear ( ) ;
searchText . sendKeys ( 'm' ) ;
expectFriendNames ( [ 'Mary' , 'Mike' , 'Adam' ] , 'friend' ) ;
searchText . clear ( ) ;
searchText . sendKeys ( '76' ) ;
expectFriendNames ( [ 'John' , 'Julie' ] , 'friend' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should search in specific fields when filtering with a predicate object' , function ( ) {
2018-05-05 12:13:16 +02:00
var searchAny = element ( by . model ( 'search.$' ) ) ;
searchAny . clear ( ) ;
searchAny . sendKeys ( 'i' ) ;
expectFriendNames ( [ 'Mary' , 'Mike' , 'Julie' , 'Juliette' ] , 'friendObj' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should use a equal comparison when comparator is true' , function ( ) {
2018-05-05 12:13:16 +02:00
var searchName = element ( by . model ( 'search.name' ) ) ;
var strict = element ( by . model ( 'strict' ) ) ;
searchName . clear ( ) ;
searchName . sendKeys ( 'Julie' ) ;
strict . click ( ) ;
expectFriendNames ( [ 'Julie' ] , 'friendObj' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
function filterFilter ( ) {
return function ( array , expression , comparator ) {
2016-04-18 12:34:29 +00:00
if ( ! isArray ( array ) ) return array ;
2018-05-05 12:13:16 +02:00
var expressionType = ( expression !== null ) ? typeof expression : 'null' ;
var predicateFn ;
var matchAgainstAnyProp ;
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
switch ( expressionType ) {
case 'function' :
predicateFn = expression ;
break ;
case 'boolean' :
case 'null' :
case 'number' :
case 'string' :
matchAgainstAnyProp = true ;
//jshint -W086
case 'object' :
//jshint +W086
predicateFn = createPredicateFn ( expression , comparator , matchAgainstAnyProp ) ;
break ;
default :
return array ;
}
return array . filter ( predicateFn ) ;
} ;
}
// Helper functions for `filterFilter`
function createPredicateFn ( expression , comparator , matchAgainstAnyProp ) {
var shouldMatchPrimitives = isObject ( expression ) && ( '$' in expression ) ;
var predicateFn ;
if ( comparator === true ) {
comparator = equals ;
} else if ( ! isFunction ( comparator ) ) {
comparator = function ( actual , expected ) {
if ( isUndefined ( actual ) ) {
// No substring matching against `undefined`
return false ;
2016-04-18 12:34:29 +00:00
}
2018-05-05 12:13:16 +02:00
if ( ( actual === null ) || ( expected === null ) ) {
// No substring matching against `null`; only match against `null`
return actual === expected ;
}
if ( isObject ( actual ) || isObject ( expected ) ) {
// Prevent an object to be considered equal to a string like `'[object'`
return false ;
}
actual = lowercase ( '' + actual ) ;
expected = lowercase ( '' + expected ) ;
return actual . indexOf ( expected ) !== - 1 ;
2016-04-18 12:34:29 +00:00
} ;
2018-05-05 12:13:16 +02:00
}
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
predicateFn = function ( item ) {
if ( shouldMatchPrimitives && ! isObject ( item ) ) {
return deepCompare ( item , expression . $ , comparator , false ) ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
return deepCompare ( item , expression , comparator , matchAgainstAnyProp ) ;
} ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
return predicateFn ;
}
function deepCompare ( actual , expected , comparator , matchAgainstAnyProp , dontMatchWholeObject ) {
var actualType = ( actual !== null ) ? typeof actual : 'null' ;
var expectedType = ( expected !== null ) ? typeof expected : 'null' ;
if ( ( expectedType === 'string' ) && ( expected . charAt ( 0 ) === '!' ) ) {
return ! deepCompare ( actual , expected . substring ( 1 ) , comparator , matchAgainstAnyProp ) ;
} else if ( isArray ( actual ) ) {
// In case `actual` is an array, consider it a match
// if ANY of it's items matches `expected`
return actual . some ( function ( item ) {
return deepCompare ( item , expected , comparator , matchAgainstAnyProp ) ;
} ) ;
}
switch ( actualType ) {
case 'object' :
var key ;
if ( matchAgainstAnyProp ) {
for ( key in actual ) {
if ( ( key . charAt ( 0 ) !== '$' ) && deepCompare ( actual [ key ] , expected , comparator , true ) ) {
return true ;
2016-04-18 12:34:29 +00:00
}
2018-05-05 12:13:16 +02:00
}
return dontMatchWholeObject ? false : deepCompare ( actual , expected , comparator , false ) ;
} else if ( expectedType === 'object' ) {
for ( key in expected ) {
var expectedVal = expected [ key ] ;
if ( isFunction ( expectedVal ) || isUndefined ( expectedVal ) ) {
continue ;
}
var matchAnyProperty = key === '$' ;
var actualVal = matchAnyProperty ? actual : actual [ key ] ;
if ( ! deepCompare ( actualVal , expectedVal , comparator , matchAnyProperty , matchAnyProperty ) ) {
return false ;
2016-04-18 12:34:29 +00:00
}
}
2018-05-05 12:13:16 +02:00
return true ;
} else {
return comparator ( actual , expected ) ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
break ;
case 'function' :
return false ;
default :
return comparator ( actual , expected ) ;
}
2016-03-28 10:46:51 +00:00
}
/ * *
* @ ngdoc filter
2018-05-05 12:13:16 +02:00
* @ name currency
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Formats a number as a currency ( ie $1 , 234.56 ) . When no currency symbol is provided , default
* symbol for current locale is used .
*
* @ param { number } amount Input to filter .
* @ param { string = } symbol Currency symbol or identifier to be displayed .
2018-05-05 12:13:16 +02:00
* @ param { number = } fractionSize Number of decimal places to round the amount to , defaults to default max fraction size for current locale
2016-03-28 10:46:51 +00:00
* @ returns { string } Formatted number .
*
*
* @ example
2018-05-05 12:13:16 +02:00
< example module = "currencyExample" >
< file name = "index.html" >
2016-03-28 10:46:51 +00:00
< script >
2018-05-05 12:13:16 +02:00
angular . module ( 'currencyExample' , [ ] )
. controller ( 'ExampleController' , [ '$scope' , function ( $scope ) {
$scope . amount = 1234.56 ;
} ] ) ;
2016-03-28 10:46:51 +00:00
< / s c r i p t >
2018-05-05 12:13:16 +02:00
< div ng - controller = "ExampleController" >
2016-04-18 12:34:29 +00:00
< input type = "number" ng - model = "amount" > < br >
2018-05-05 12:13:16 +02:00
default currency symbol ( $ ) : < span id = "currency-default" > { { amount | currency } } < / s p a n > < b r >
custom currency identifier ( USD$ ) : < span id = "currency-custom" > { { amount | currency : "USD$" } } < / s p a n >
no fractions ( 0 ) : < span id = "currency-no-fractions" > { { amount | currency : "USD$" : 0 } } < / s p a n >
2016-03-28 10:46:51 +00:00
< / d i v >
2018-05-05 12:13:16 +02:00
< / f i l e >
< file name = "protractor.js" type = "protractor" >
2016-03-28 10:46:51 +00:00
it ( 'should init with 1234.56' , function ( ) {
2018-05-05 12:13:16 +02:00
expect ( element ( by . id ( 'currency-default' ) ) . getText ( ) ) . toBe ( '$1,234.56' ) ;
expect ( element ( by . id ( 'currency-custom' ) ) . getText ( ) ) . toBe ( 'USD$1,234.56' ) ;
expect ( element ( by . id ( 'currency-no-fractions' ) ) . getText ( ) ) . toBe ( 'USD$1,235' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should update' , function ( ) {
2018-05-05 12:13:16 +02:00
if ( browser . params . browser == 'safari' ) {
// Safari does not understand the minus key. See
// https://github.com/angular/protractor/issues/481
return ;
}
element ( by . model ( 'amount' ) ) . clear ( ) ;
element ( by . model ( 'amount' ) ) . sendKeys ( '-1234' ) ;
expect ( element ( by . id ( 'currency-default' ) ) . getText ( ) ) . toBe ( '($1,234.00)' ) ;
expect ( element ( by . id ( 'currency-custom' ) ) . getText ( ) ) . toBe ( '(USD$1,234.00)' ) ;
expect ( element ( by . id ( 'currency-no-fractions' ) ) . getText ( ) ) . toBe ( '(USD$1,234)' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
currencyFilter . $inject = [ '$locale' ] ;
function currencyFilter ( $locale ) {
var formats = $locale . NUMBER _FORMATS ;
2018-05-05 12:13:16 +02:00
return function ( amount , currencySymbol , fractionSize ) {
if ( isUndefined ( currencySymbol ) ) {
currencySymbol = formats . CURRENCY _SYM ;
}
if ( isUndefined ( fractionSize ) ) {
fractionSize = formats . PATTERNS [ 1 ] . maxFrac ;
}
// if null or undefined pass it through
return ( amount == null )
? amount
: formatNumber ( amount , formats . PATTERNS [ 1 ] , formats . GROUP _SEP , formats . DECIMAL _SEP , fractionSize ) .
replace ( /\u00A4/g , currencySymbol ) ;
2016-03-28 10:46:51 +00:00
} ;
}
/ * *
* @ ngdoc filter
2018-05-05 12:13:16 +02:00
* @ name number
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Formats a number as text .
*
2018-05-05 12:13:16 +02:00
* If the input is null or undefined , it will just be returned .
* If the input is infinite ( Infinity / - Infinity ) the Infinity symbol '∞' is returned .
2016-03-28 10:46:51 +00:00
* If the input is not a number an empty string is returned .
*
* @ param { number | string } number Number to format .
* @ param { ( number | string ) = } fractionSize Number of decimal places to round the number to .
* If this is not provided then the fraction size is computed from the current locale ' s number
* formatting pattern . In the case of the default locale , it will be 3.
2016-04-18 12:34:29 +00:00
* @ returns { string } Number rounded to decimalPlaces and places a “ , ” after each third digit .
2016-03-28 10:46:51 +00:00
*
* @ example
2018-05-05 12:13:16 +02:00
< example module = "numberFilterExample" >
< file name = "index.html" >
2016-03-28 10:46:51 +00:00
< script >
2018-05-05 12:13:16 +02:00
angular . module ( 'numberFilterExample' , [ ] )
. controller ( 'ExampleController' , [ '$scope' , function ( $scope ) {
$scope . val = 1234.56789 ;
} ] ) ;
2016-03-28 10:46:51 +00:00
< / s c r i p t >
2018-05-05 12:13:16 +02:00
< div ng - controller = "ExampleController" >
2016-04-18 12:34:29 +00:00
Enter number : < input ng - model = 'val' > < br >
2018-05-05 12:13:16 +02:00
Default formatting : < span id = 'number-default' > { { val | number } } < / s p a n > < b r >
No fractions : < span > { { val | number : 0 } } < / s p a n > < b r >
Negative number : < span > { { - val | number : 4 } } < / s p a n >
2016-03-28 10:46:51 +00:00
< / d i v >
2018-05-05 12:13:16 +02:00
< / f i l e >
< file name = "protractor.js" type = "protractor" >
2016-03-28 10:46:51 +00:00
it ( 'should format numbers' , function ( ) {
2018-05-05 12:13:16 +02:00
expect ( element ( by . id ( 'number-default' ) ) . getText ( ) ) . toBe ( '1,234.568' ) ;
expect ( element ( by . binding ( 'val | number:0' ) ) . getText ( ) ) . toBe ( '1,235' ) ;
expect ( element ( by . binding ( '-val | number:4' ) ) . getText ( ) ) . toBe ( '-1,234.5679' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should update' , function ( ) {
2018-05-05 12:13:16 +02:00
element ( by . model ( 'val' ) ) . clear ( ) ;
element ( by . model ( 'val' ) ) . sendKeys ( '3374.333' ) ;
expect ( element ( by . id ( 'number-default' ) ) . getText ( ) ) . toBe ( '3,374.333' ) ;
expect ( element ( by . binding ( 'val | number:0' ) ) . getText ( ) ) . toBe ( '3,374' ) ;
expect ( element ( by . binding ( '-val | number:4' ) ) . getText ( ) ) . toBe ( '-3,374.3330' ) ;
} ) ;
< / f i l e >
< / e x a m p l e >
* /
2016-04-18 12:34:29 +00:00
2016-03-28 10:46:51 +00:00
numberFilter . $inject = [ '$locale' ] ;
function numberFilter ( $locale ) {
var formats = $locale . NUMBER _FORMATS ;
return function ( number , fractionSize ) {
2018-05-05 12:13:16 +02:00
// if null or undefined pass it through
return ( number == null )
? number
: formatNumber ( number , formats . PATTERNS [ 0 ] , formats . GROUP _SEP , formats . DECIMAL _SEP ,
fractionSize ) ;
2016-03-28 10:46:51 +00:00
} ;
}
2016-04-18 12:34:29 +00:00
var DECIMAL _SEP = '.' ;
function formatNumber ( number , pattern , groupSep , decimalSep , fractionSize ) {
2018-05-05 12:13:16 +02:00
if ( ! isFinite ( number ) || isObject ( number ) ) return '' ;
2016-04-18 12:34:29 +00:00
var isNegative = number < 0 ;
number = Math . abs ( number ) ;
var numStr = number + '' ,
formatedText = '' ,
parts = [ ] ;
var hasExponent = false ;
if ( numStr . indexOf ( 'e' ) !== - 1 ) {
var match = numStr . match ( /([\d\.]+)e(-?)(\d+)/ ) ;
if ( match && match [ 2 ] == '-' && match [ 3 ] > fractionSize + 1 ) {
2018-05-05 12:13:16 +02:00
number = 0 ;
2016-04-18 12:34:29 +00:00
} else {
formatedText = numStr ;
hasExponent = true ;
2016-03-28 10:46:51 +00:00
}
}
2016-04-18 12:34:29 +00:00
if ( ! hasExponent ) {
var fractionLen = ( numStr . split ( DECIMAL _SEP ) [ 1 ] || '' ) . length ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
// determine fractionSize if it is not specified
if ( isUndefined ( fractionSize ) ) {
fractionSize = Math . min ( Math . max ( pattern . minFrac , fractionLen ) , pattern . maxFrac ) ;
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
// safely round numbers in JS without hitting imprecisions of floating-point arithmetics
// inspired by:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
number = + ( Math . round ( + ( number . toString ( ) + 'e' + fractionSize ) ) . toString ( ) + 'e' + - fractionSize ) ;
2016-04-18 12:34:29 +00:00
var fraction = ( '' + number ) . split ( DECIMAL _SEP ) ;
var whole = fraction [ 0 ] ;
fraction = fraction [ 1 ] || '' ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
var i , pos = 0 ,
lgroup = pattern . lgSize ,
group = pattern . gSize ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( whole . length >= ( lgroup + group ) ) {
pos = whole . length - lgroup ;
for ( i = 0 ; i < pos ; i ++ ) {
2018-05-05 12:13:16 +02:00
if ( ( pos - i ) % group === 0 && i !== 0 ) {
2016-04-18 12:34:29 +00:00
formatedText += groupSep ;
}
formatedText += whole . charAt ( i ) ;
2016-03-28 10:46:51 +00:00
}
}
2016-04-18 12:34:29 +00:00
for ( i = pos ; i < whole . length ; i ++ ) {
2018-05-05 12:13:16 +02:00
if ( ( whole . length - i ) % lgroup === 0 && i !== 0 ) {
2016-04-18 12:34:29 +00:00
formatedText += groupSep ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
formatedText += whole . charAt ( i ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
// format fraction part.
2018-05-05 12:13:16 +02:00
while ( fraction . length < fractionSize ) {
2016-04-18 12:34:29 +00:00
fraction += '0' ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
if ( fractionSize && fractionSize !== "0" ) formatedText += decimalSep + fraction . substr ( 0 , fractionSize ) ;
2016-03-28 10:46:51 +00:00
} else {
2018-05-05 12:13:16 +02:00
if ( fractionSize > 0 && number < 1 ) {
2016-04-18 12:34:29 +00:00
formatedText = number . toFixed ( fractionSize ) ;
2018-05-05 12:13:16 +02:00
number = parseFloat ( formatedText ) ;
2016-03-28 10:46:51 +00:00
}
}
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
if ( number === 0 ) {
isNegative = false ;
}
parts . push ( isNegative ? pattern . negPre : pattern . posPre ,
formatedText ,
isNegative ? pattern . negSuf : pattern . posSuf ) ;
2016-04-18 12:34:29 +00:00
return parts . join ( '' ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
function padNumber ( num , digits , trim ) {
2016-03-28 10:46:51 +00:00
var neg = '' ;
2016-04-18 12:34:29 +00:00
if ( num < 0 ) {
neg = '-' ;
num = - num ;
2016-03-28 10:46:51 +00:00
}
num = '' + num ;
2018-05-05 12:13:16 +02:00
while ( num . length < digits ) num = '0' + num ;
2016-04-18 12:34:29 +00:00
if ( trim )
2016-03-28 10:46:51 +00:00
num = num . substr ( num . length - digits ) ;
return neg + num ;
}
2016-04-18 12:34:29 +00:00
function dateGetter ( name , size , offset , trim ) {
2016-03-28 10:46:51 +00:00
offset = offset || 0 ;
return function ( date ) {
var value = date [ 'get' + name ] ( ) ;
2016-04-18 12:34:29 +00:00
if ( offset > 0 || value > - offset )
2016-03-28 10:46:51 +00:00
value += offset ;
2018-05-05 12:13:16 +02:00
if ( value === 0 && offset == - 12 ) value = 12 ;
2016-04-18 12:34:29 +00:00
return padNumber ( value , size , trim ) ;
2016-03-28 10:46:51 +00:00
} ;
}
2016-04-18 12:34:29 +00:00
function dateStrGetter ( name , shortForm ) {
2016-03-28 10:46:51 +00:00
return function ( date , formats ) {
var value = date [ 'get' + name ] ( ) ;
2016-04-18 12:34:29 +00:00
var get = uppercase ( shortForm ? ( 'SHORT' + name ) : name ) ;
2016-03-28 10:46:51 +00:00
return formats [ get ] [ value ] ;
} ;
}
2016-04-18 12:34:29 +00:00
function timeZoneGetter ( date ) {
var zone = - 1 * date . getTimezoneOffset ( ) ;
2016-03-28 10:46:51 +00:00
var paddedZone = ( zone >= 0 ) ? "+" : "" ;
paddedZone += padNumber ( Math [ zone > 0 ? 'floor' : 'ceil' ] ( zone / 60 ) , 2 ) +
padNumber ( Math . abs ( zone % 60 ) , 2 ) ;
return paddedZone ;
}
2018-05-05 12:13:16 +02:00
function getFirstThursdayOfYear ( year ) {
// 0 = index of January
var dayOfWeekOnFirst = ( new Date ( year , 0 , 1 ) ) . getDay ( ) ;
// 4 = index of Thursday (+1 to account for 1st = 5)
// 11 = index of *next* Thursday (+1 account for 1st = 12)
return new Date ( year , 0 , ( ( dayOfWeekOnFirst <= 4 ) ? 5 : 12 ) - dayOfWeekOnFirst ) ;
}
function getThursdayThisWeek ( datetime ) {
return new Date ( datetime . getFullYear ( ) , datetime . getMonth ( ) ,
// 4 = index of Thursday
datetime . getDate ( ) + ( 4 - datetime . getDay ( ) ) ) ;
}
function weekGetter ( size ) {
return function ( date ) {
var firstThurs = getFirstThursdayOfYear ( date . getFullYear ( ) ) ,
thisThurs = getThursdayThisWeek ( date ) ;
var diff = + thisThurs - + firstThurs ,
result = 1 + Math . round ( diff / 6.048 e8 ) ; // 6.048e8 ms per week
return padNumber ( result , size ) ;
} ;
}
2016-03-28 10:46:51 +00:00
function ampmGetter ( date , formats ) {
return date . getHours ( ) < 12 ? formats . AMPMS [ 0 ] : formats . AMPMS [ 1 ] ;
}
2018-05-05 12:13:16 +02:00
function eraGetter ( date , formats ) {
return date . getFullYear ( ) <= 0 ? formats . ERAS [ 0 ] : formats . ERAS [ 1 ] ;
}
function longEraGetter ( date , formats ) {
return date . getFullYear ( ) <= 0 ? formats . ERANAMES [ 0 ] : formats . ERANAMES [ 1 ] ;
}
2016-03-28 10:46:51 +00:00
var DATE _FORMATS = {
2016-04-18 12:34:29 +00:00
yyyy : dateGetter ( 'FullYear' , 4 ) ,
yy : dateGetter ( 'FullYear' , 2 , 0 , true ) ,
y : dateGetter ( 'FullYear' , 1 ) ,
2016-03-28 10:46:51 +00:00
MMMM : dateStrGetter ( 'Month' ) ,
MMM : dateStrGetter ( 'Month' , true ) ,
MM : dateGetter ( 'Month' , 2 , 1 ) ,
M : dateGetter ( 'Month' , 1 , 1 ) ,
dd : dateGetter ( 'Date' , 2 ) ,
d : dateGetter ( 'Date' , 1 ) ,
HH : dateGetter ( 'Hours' , 2 ) ,
H : dateGetter ( 'Hours' , 1 ) ,
hh : dateGetter ( 'Hours' , 2 , - 12 ) ,
h : dateGetter ( 'Hours' , 1 , - 12 ) ,
mm : dateGetter ( 'Minutes' , 2 ) ,
m : dateGetter ( 'Minutes' , 1 ) ,
ss : dateGetter ( 'Seconds' , 2 ) ,
s : dateGetter ( 'Seconds' , 1 ) ,
// while ISO 8601 requires fractions to be prefixed with `.` or `,`
// we can be just safely rely on using `sss` since we currently don't support single or two digit fractions
sss : dateGetter ( 'Milliseconds' , 3 ) ,
EEEE : dateStrGetter ( 'Day' ) ,
EEE : dateStrGetter ( 'Day' , true ) ,
a : ampmGetter ,
2018-05-05 12:13:16 +02:00
Z : timeZoneGetter ,
ww : weekGetter ( 2 ) ,
w : weekGetter ( 1 ) ,
G : eraGetter ,
GG : eraGetter ,
GGG : eraGetter ,
GGGG : longEraGetter
2016-03-28 10:46:51 +00:00
} ;
2018-05-05 12:13:16 +02:00
var DATE _FORMATS _SPLIT = /((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/ ,
2016-03-28 10:46:51 +00:00
NUMBER _STRING = /^\-?\d+$/ ;
/ * *
* @ ngdoc filter
2018-05-05 12:13:16 +02:00
* @ name date
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Formats ` date ` to a string based on the requested ` format ` .
*
* ` format ` string can be composed of the following elements :
*
* * ` 'yyyy' ` : 4 digit representation of year ( e . g . AD 1 => 0001 , AD 2010 => 2010 )
* * ` 'yy' ` : 2 digit representation of year , padded ( 00 - 99 ) . ( e . g . AD 2001 => 01 , AD 2010 => 10 )
* * ` 'y' ` : 1 digit representation of year , e . g . ( AD 1 => 1 , AD 199 => 199 )
* * ` 'MMMM' ` : Month in year ( January - December )
* * ` 'MMM' ` : Month in year ( Jan - Dec )
* * ` 'MM' ` : Month in year , padded ( 01 - 12 )
* * ` 'M' ` : Month in year ( 1 - 12 )
* * ` 'dd' ` : Day in month , padded ( 01 - 31 )
* * ` 'd' ` : Day in month ( 1 - 31 )
* * ` 'EEEE' ` : Day in Week , ( Sunday - Saturday )
* * ` 'EEE' ` : Day in Week , ( Sun - Sat )
* * ` 'HH' ` : Hour in day , padded ( 00 - 23 )
* * ` 'H' ` : Hour in day ( 0 - 23 )
2018-05-05 12:13:16 +02:00
* * ` 'hh' ` : Hour in AM / PM , padded ( 01 - 12 )
* * ` 'h' ` : Hour in AM / PM , ( 1 - 12 )
2016-03-28 10:46:51 +00:00
* * ` 'mm' ` : Minute in hour , padded ( 00 - 59 )
* * ` 'm' ` : Minute in hour ( 0 - 59 )
* * ` 'ss' ` : Second in minute , padded ( 00 - 59 )
* * ` 's' ` : Second in minute ( 0 - 59 )
2018-05-05 12:13:16 +02:00
* * ` 'sss' ` : Millisecond in second , padded ( 000 - 999 )
* * ` 'a' ` : AM / PM marker
2016-03-28 10:46:51 +00:00
* * ` 'Z' ` : 4 digit ( + sign ) representation of the timezone offset ( - 1200 - + 1200 )
2018-05-05 12:13:16 +02:00
* * ` 'ww' ` : Week of year , padded ( 00 - 53 ) . Week 01 is the week with the first Thursday of the year
* * ` 'w' ` : Week of year ( 0 - 53 ) . Week 1 is the week with the first Thursday of the year
* * ` 'G' ` , ` 'GG' ` , ` 'GGG' ` : The abbreviated form of the era string ( e . g . 'AD' )
* * ` 'GGGG' ` : The long form of the era string ( e . g . 'Anno Domini' )
2016-03-28 10:46:51 +00:00
*
* ` format ` string can also be one of the following predefined
* { @ link guide / i18n localizable formats } :
*
* * ` 'medium' ` : equivalent to ` 'MMM d, y h:mm:ss a' ` for en _US locale
2018-05-05 12:13:16 +02:00
* ( e . g . Sep 3 , 2010 12 : 05 : 08 PM )
* * ` 'short' ` : equivalent to ` 'M/d/yy h:mm a' ` for en _US locale ( e . g . 9 / 3 / 10 12 : 05 PM )
* * ` 'fullDate' ` : equivalent to ` 'EEEE, MMMM d, y' ` for en _US locale
2016-03-28 10:46:51 +00:00
* ( e . g . Friday , September 3 , 2010 )
* * ` 'longDate' ` : equivalent to ` 'MMMM d, y' ` for en _US locale ( e . g . September 3 , 2010 )
* * ` 'mediumDate' ` : equivalent to ` 'MMM d, y' ` for en _US locale ( e . g . Sep 3 , 2010 )
* * ` 'shortDate' ` : equivalent to ` 'M/d/yy' ` for en _US locale ( e . g . 9 / 3 / 10 )
2018-05-05 12:13:16 +02:00
* * ` 'mediumTime' ` : equivalent to ` 'h:mm:ss a' ` for en _US locale ( e . g . 12 : 05 : 08 PM )
* * ` 'shortTime' ` : equivalent to ` 'h:mm a' ` for en _US locale ( e . g . 12 : 05 PM )
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* ` format ` string can contain literal values . These need to be escaped by surrounding with single quotes ( e . g .
* ` "h 'in the morning'" ` ) . In order to output a single quote , escape it - i . e . , two single quotes in a sequence
2016-03-28 10:46:51 +00:00
* ( e . g . ` "h 'o''clock'" ` ) .
*
* @ param { ( Date | number | string ) } date Date to format either as Date object , milliseconds ( string or
2018-05-05 12:13:16 +02:00
* number ) or various ISO 8601 datetime string formats ( e . g . yyyy - MM - ddTHH : mm : ss . sssZ and its
2016-03-28 10:46:51 +00:00
* shorter versions like yyyy - MM - ddTHH : mmZ , yyyy - MM - dd or yyyyMMddTHHmmssZ ) . If no timezone is
* specified in the string input , the time is considered to be in the local timezone .
* @ param { string = } format Formatting rules ( see Description ) . If not specified ,
* ` mediumDate ` is used .
2018-05-05 12:13:16 +02:00
* @ param { string = } timezone Timezone to be used for formatting . Right now , only ` 'UTC' ` is supported .
* If not specified , the timezone of the browser will be used .
2016-03-28 10:46:51 +00:00
* @ returns { string } Formatted string or the input if input is not recognized as date / millis .
*
* @ example
2018-05-05 12:13:16 +02:00
< example >
< file name = "index.html" >
2016-03-28 10:46:51 +00:00
< span ng - non - bindable > { { 1288323623006 | date : 'medium' } } < / s p a n > :
2018-05-05 12:13:16 +02:00
< span > { { 1288323623006 | date : 'medium' } } < / s p a n > < b r >
2016-03-28 10:46:51 +00:00
< span ng - non - bindable > { { 1288323623006 | date : 'yyyy-MM-dd HH:mm:ss Z' } } < / s p a n > :
2018-05-05 12:13:16 +02:00
< span > { { 1288323623006 | date : 'yyyy-MM-dd HH:mm:ss Z' } } < / s p a n > < b r >
2016-03-28 10:46:51 +00:00
< span ng - non - bindable > { { 1288323623006 | date : 'MM/dd/yyyy @ h:mma' } } < / s p a n > :
2018-05-05 12:13:16 +02:00
< span > { { '1288323623006' | date : 'MM/dd/yyyy @ h:mma' } } < / s p a n > < b r >
< span ng - non - bindable > { { 1288323623006 | date : "MM/dd/yyyy 'at' h:mma" } } < / s p a n > :
< span > { { '1288323623006' | date : "MM/dd/yyyy 'at' h:mma" } } < / s p a n > < b r >
< / f i l e >
< file name = "protractor.js" type = "protractor" >
2016-03-28 10:46:51 +00:00
it ( 'should format date' , function ( ) {
2018-05-05 12:13:16 +02:00
expect ( element ( by . binding ( "1288323623006 | date:'medium'" ) ) . getText ( ) ) .
2016-03-28 10:46:51 +00:00
toMatch ( /Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/ ) ;
2018-05-05 12:13:16 +02:00
expect ( element ( by . binding ( "1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'" ) ) . getText ( ) ) .
2016-03-28 10:46:51 +00:00
toMatch ( /2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/ ) ;
2018-05-05 12:13:16 +02:00
expect ( element ( by . binding ( "'1288323623006' | date:'MM/dd/yyyy @ h:mma'" ) ) . getText ( ) ) .
2016-03-28 10:46:51 +00:00
toMatch ( /10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/ ) ;
2018-05-05 12:13:16 +02:00
expect ( element ( by . binding ( "'1288323623006' | date:\"MM/dd/yyyy 'at' h:mma\"" ) ) . getText ( ) ) .
toMatch ( /10\/2\d\/2010 at \d{1,2}:\d{2}(AM|PM)/ ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
dateFilter . $inject = [ '$locale' ] ;
function dateFilter ( $locale ) {
var R _ISO8601 _STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/ ;
// 1 2 3 4 5 6 7 8 9 10 11
function jsonStringToDate ( string ) {
var match ;
if ( match = string . match ( R _ISO8601 _STR ) ) {
var date = new Date ( 0 ) ,
tzHour = 0 ,
tzMin = 0 ,
dateSetter = match [ 8 ] ? date . setUTCFullYear : date . setFullYear ,
timeSetter = match [ 8 ] ? date . setUTCHours : date . setHours ;
if ( match [ 9 ] ) {
2016-04-18 12:34:29 +00:00
tzHour = int ( match [ 9 ] + match [ 10 ] ) ;
tzMin = int ( match [ 9 ] + match [ 11 ] ) ;
}
dateSetter . call ( date , int ( match [ 1 ] ) , int ( match [ 2 ] ) - 1 , int ( match [ 3 ] ) ) ;
2018-05-05 12:13:16 +02:00
var h = int ( match [ 4 ] || 0 ) - tzHour ;
var m = int ( match [ 5 ] || 0 ) - tzMin ;
var s = int ( match [ 6 ] || 0 ) ;
var ms = Math . round ( parseFloat ( '0.' + ( match [ 7 ] || 0 ) ) * 1000 ) ;
2016-03-28 10:46:51 +00:00
timeSetter . call ( date , h , m , s , ms ) ;
return date ;
}
return string ;
}
2018-05-05 12:13:16 +02:00
return function ( date , format , timezone ) {
2016-03-28 10:46:51 +00:00
var text = '' ,
parts = [ ] ,
fn , match ;
format = format || 'mediumDate' ;
format = $locale . DATETIME _FORMATS [ format ] || format ;
if ( isString ( date ) ) {
2018-05-05 12:13:16 +02:00
date = NUMBER _STRING . test ( date ) ? int ( date ) : jsonStringToDate ( date ) ;
2016-03-28 10:46:51 +00:00
}
if ( isNumber ( date ) ) {
date = new Date ( date ) ;
}
2016-04-18 12:34:29 +00:00
if ( ! isDate ( date ) ) {
2016-03-28 10:46:51 +00:00
return date ;
}
2018-05-05 12:13:16 +02:00
while ( format ) {
2016-03-28 10:46:51 +00:00
match = DATE _FORMATS _SPLIT . exec ( format ) ;
if ( match ) {
parts = concat ( parts , match , 1 ) ;
format = parts . pop ( ) ;
} else {
parts . push ( format ) ;
format = null ;
}
}
2018-05-05 12:13:16 +02:00
if ( timezone && timezone === 'UTC' ) {
date = new Date ( date . getTime ( ) ) ;
date . setMinutes ( date . getMinutes ( ) + date . getTimezoneOffset ( ) ) ;
}
forEach ( parts , function ( value ) {
2016-03-28 10:46:51 +00:00
fn = DATE _FORMATS [ value ] ;
2016-04-18 12:34:29 +00:00
text += fn ? fn ( date , $locale . DATETIME _FORMATS )
: value . replace ( /(^'|'$)/g , '' ) . replace ( /''/g , "'" ) ;
2016-03-28 10:46:51 +00:00
} ) ;
return text ;
} ;
}
/ * *
* @ ngdoc filter
2018-05-05 12:13:16 +02:00
* @ name json
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Allows you to convert a JavaScript object into JSON string .
*
* This filter is mostly useful for debugging . When using the double curly { { value } } notation
* the binding is automatically converted to JSON .
*
* @ param { * } object Any JavaScript object ( including arrays and primitive types ) to filter .
2018-05-05 12:13:16 +02:00
* @ param { number = } spacing The number of spaces to use per indentation , defaults to 2.
2016-03-28 10:46:51 +00:00
* @ returns { string } JSON string .
*
*
2018-05-05 12:13:16 +02:00
* @ example
< example >
< file name = "index.html" >
< pre id = "default-spacing" > { { { 'name' : 'value' } | json } } < / p r e >
< pre id = "custom-spacing" > { { { 'name' : 'value' } | json : 4 } } < / p r e >
< / f i l e >
< file name = "protractor.js" type = "protractor" >
2016-03-28 10:46:51 +00:00
it ( 'should jsonify filtered objects' , function ( ) {
2018-05-05 12:13:16 +02:00
expect ( element ( by . id ( 'default-spacing' ) ) . getText ( ) ) . toMatch ( /\{\n "name": ?"value"\n}/ ) ;
expect ( element ( by . id ( 'custom-spacing' ) ) . getText ( ) ) . toMatch ( /\{\n "name": ?"value"\n}/ ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-03-28 10:46:51 +00:00
*
* /
function jsonFilter ( ) {
2018-05-05 12:13:16 +02:00
return function ( object , spacing ) {
if ( isUndefined ( spacing ) ) {
spacing = 2 ;
}
return toJson ( object , spacing ) ;
2016-03-28 10:46:51 +00:00
} ;
}
/ * *
* @ ngdoc filter
2018-05-05 12:13:16 +02:00
* @ name lowercase
* @ kind function
2016-03-28 10:46:51 +00:00
* @ description
* Converts string to lowercase .
* @ see angular . lowercase
* /
var lowercaseFilter = valueFn ( lowercase ) ;
/ * *
* @ ngdoc filter
2018-05-05 12:13:16 +02:00
* @ name uppercase
* @ kind function
2016-03-28 10:46:51 +00:00
* @ description
* Converts string to uppercase .
* @ see angular . uppercase
* /
var uppercaseFilter = valueFn ( uppercase ) ;
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc filter
* @ name limitTo
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
* Creates a new array or string containing only a specified number of elements . The elements
2018-05-05 12:13:16 +02:00
* are taken from either the beginning or the end of the source array , string or number , as specified by
* the value and sign ( positive or negative ) of ` limit ` . If a number is used as input , it is
* converted to a string .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* @ param { Array | string | number } input Source array , string or number to be limited .
2016-05-18 00:10:50 +00:00
* @ param { string | number } limit The length of the returned array or string . If the ` limit ` number
2016-03-28 10:46:51 +00:00
* is positive , ` limit ` number of items from the beginning of the source array / string are copied .
2016-05-18 00:10:50 +00:00
* If the number is negative , ` limit ` number of items from the end of the source array / string
2016-04-18 12:34:29 +00:00
* are copied . The ` limit ` will be trimmed if it exceeds ` array.length `
2016-03-28 10:46:51 +00:00
* @ returns { Array | string } A new sub - array or substring of length ` limit ` or less if input array
* had less than ` limit ` elements .
*
* @ example
2018-05-05 12:13:16 +02:00
< example module = "limitToExample" >
< file name = "index.html" >
2016-03-28 10:46:51 +00:00
< script >
2018-05-05 12:13:16 +02:00
angular . module ( 'limitToExample' , [ ] )
. controller ( 'ExampleController' , [ '$scope' , function ( $scope ) {
$scope . numbers = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ] ;
$scope . letters = "abcdefghi" ;
$scope . longNumber = 2345432342 ;
$scope . numLimit = 3 ;
$scope . letterLimit = 3 ;
$scope . longNumberLimit = 3 ;
} ] ) ;
2016-03-28 10:46:51 +00:00
< / s c r i p t >
2018-05-05 12:13:16 +02:00
< div ng - controller = "ExampleController" >
Limit { { numbers } } to : < input type = "number" step = "1" ng - model = "numLimit" >
2016-03-28 10:46:51 +00:00
< p > Output numbers : { { numbers | limitTo : numLimit } } < / p >
2018-05-05 12:13:16 +02:00
Limit { { letters } } to : < input type = "number" step = "1" ng - model = "letterLimit" >
2016-03-28 10:46:51 +00:00
< p > Output letters : { { letters | limitTo : letterLimit } } < / p >
2018-05-05 12:13:16 +02:00
Limit { { longNumber } } to : < input type = "number" step = "1" ng - model = "longNumberLimit" >
< p > Output long number : { { longNumber | limitTo : longNumberLimit } } < / p >
2016-03-28 10:46:51 +00:00
< / d i v >
2018-05-05 12:13:16 +02:00
< / f i l e >
< file name = "protractor.js" type = "protractor" >
var numLimitInput = element ( by . model ( 'numLimit' ) ) ;
var letterLimitInput = element ( by . model ( 'letterLimit' ) ) ;
var longNumberLimitInput = element ( by . model ( 'longNumberLimit' ) ) ;
var limitedNumbers = element ( by . binding ( 'numbers | limitTo:numLimit' ) ) ;
var limitedLetters = element ( by . binding ( 'letters | limitTo:letterLimit' ) ) ;
var limitedLongNumber = element ( by . binding ( 'longNumber | limitTo:longNumberLimit' ) ) ;
2016-03-28 10:46:51 +00:00
it ( 'should limit the number array to first three items' , function ( ) {
2018-05-05 12:13:16 +02:00
expect ( numLimitInput . getAttribute ( 'value' ) ) . toBe ( '3' ) ;
expect ( letterLimitInput . getAttribute ( 'value' ) ) . toBe ( '3' ) ;
expect ( longNumberLimitInput . getAttribute ( 'value' ) ) . toBe ( '3' ) ;
expect ( limitedNumbers . getText ( ) ) . toEqual ( 'Output numbers: [1,2,3]' ) ;
expect ( limitedLetters . getText ( ) ) . toEqual ( 'Output letters: abc' ) ;
expect ( limitedLongNumber . getText ( ) ) . toEqual ( 'Output long number: 234' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
// There is a bug in safari and protractor that doesn't like the minus key
// it('should update the output when -3 is entered', function() {
// numLimitInput.clear();
// numLimitInput.sendKeys('-3');
// letterLimitInput.clear();
// letterLimitInput.sendKeys('-3');
// longNumberLimitInput.clear();
// longNumberLimitInput.sendKeys('-3');
// expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]');
// expect(limitedLetters.getText()).toEqual('Output letters: ghi');
// expect(limitedLongNumber.getText()).toEqual('Output long number: 342');
// });
2016-03-28 10:46:51 +00:00
it ( 'should not exceed the maximum size of input array' , function ( ) {
2018-05-05 12:13:16 +02:00
numLimitInput . clear ( ) ;
numLimitInput . sendKeys ( '100' ) ;
letterLimitInput . clear ( ) ;
letterLimitInput . sendKeys ( '100' ) ;
longNumberLimitInput . clear ( ) ;
longNumberLimitInput . sendKeys ( '100' ) ;
expect ( limitedNumbers . getText ( ) ) . toEqual ( 'Output numbers: [1,2,3,4,5,6,7,8,9]' ) ;
expect ( limitedLetters . getText ( ) ) . toEqual ( 'Output letters: abcdefghi' ) ;
expect ( limitedLongNumber . getText ( ) ) . toEqual ( 'Output long number: 2345432342' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
* /
function limitToFilter ( ) {
2016-04-18 12:34:29 +00:00
return function ( input , limit ) {
2018-05-05 12:13:16 +02:00
if ( isNumber ( input ) ) input = input . toString ( ) ;
2016-04-18 12:34:29 +00:00
if ( ! isArray ( input ) && ! isString ( input ) ) return input ;
2016-05-18 00:10:50 +00:00
2018-05-05 12:13:16 +02:00
if ( Math . abs ( Number ( limit ) ) === Infinity ) {
limit = Number ( limit ) ;
2016-03-28 10:46:51 +00:00
} else {
2018-05-05 12:13:16 +02:00
limit = int ( limit ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
//NaN check on limit
if ( limit ) {
return limit > 0 ? input . slice ( 0 , limit ) : input . slice ( limit ) ;
} else {
return isString ( input ) ? "" : [ ] ;
2016-04-18 12:34:29 +00:00
}
2016-03-28 10:46:51 +00:00
} ;
}
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc filter
* @ name orderBy
* @ kind function
2016-03-28 10:46:51 +00:00
*
* @ description
2018-05-05 12:13:16 +02:00
* Orders a specified ` array ` by the ` expression ` predicate . It is ordered alphabetically
* for strings and numerically for numbers . Note : if you notice numbers are not being sorted
* correctly , make sure they are actually being saved as numbers and not strings .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ param { Array } array The array to sort .
2018-05-05 12:13:16 +02:00
* @ param { function ( * ) | string | Array . < ( function ( * ) | string ) >= } expression A predicate to be
2016-03-28 10:46:51 +00:00
* used by the comparator to determine the order of elements .
*
* Can be one of :
*
* - ` function ` : Getter function . The result of this function will be sorted using the
2018-05-05 12:13:16 +02:00
* ` < ` , ` === ` , ` > ` operator .
* - ` string ` : An Angular expression . The result of this expression is used to compare elements
* ( for example ` name ` to sort by a property called ` name ` or ` name.substr(0, 3) ` to sort by
* 3 first characters of a property called ` name ` ) . The result of a constant expression
* is interpreted as a property name to be used in comparisons ( for example ` "special name" `
* to sort object by the value of their ` special name ` property ) . An expression can be
* optionally prefixed with ` + ` or ` - ` to control ascending or descending sort order
* ( for example , ` +name ` or ` -name ` ) . If no property is provided , ( e . g . ` '+' ` ) then the array
* element itself is used to compare where sorting .
2016-03-28 10:46:51 +00:00
* - ` Array ` : An array of function or string predicates . The first predicate in the array
* is used for sorting , but when two items are equivalent , the next predicate is used .
*
2018-05-05 12:13:16 +02:00
* If the predicate is missing or empty then it defaults to ` '+' ` .
*
* @ param { boolean = } reverse Reverse the order of the array .
2016-03-28 10:46:51 +00:00
* @ returns { Array } Sorted copy of the source array .
*
2018-05-05 12:13:16 +02:00
*
2016-03-28 10:46:51 +00:00
* @ example
2018-05-05 12:13:16 +02:00
* The example below demonstrates a simple ngRepeat , where the data is sorted
* by age in descending order ( predicate is set to ` '-age' ` ) .
* ` reverse ` is not set , which means it defaults to ` false ` .
< example module = "orderByExample" >
< file name = "index.html" >
2016-04-18 12:34:29 +00:00
< script >
2018-05-05 12:13:16 +02:00
angular . module ( 'orderByExample' , [ ] )
. controller ( 'ExampleController' , [ '$scope' , function ( $scope ) {
$scope . friends =
[ { name : 'John' , phone : '555-1212' , age : 10 } ,
{ name : 'Mary' , phone : '555-9876' , age : 19 } ,
{ name : 'Mike' , phone : '555-4321' , age : 21 } ,
{ name : 'Adam' , phone : '555-5678' , age : 35 } ,
{ name : 'Julie' , phone : '555-8765' , age : 29 } ] ;
} ] ) ;
< / s c r i p t >
< div ng - controller = "ExampleController" >
< table class = "friend" >
< tr >
< th > Name < / t h >
< th > Phone Number < / t h >
< th > Age < / t h >
< / t r >
< tr ng - repeat = "friend in friends | orderBy:'-age'" >
< td > { { friend . name } } < / t d >
< td > { { friend . phone } } < / t d >
< td > { { friend . age } } < / t d >
< / t r >
< / t a b l e >
< / d i v >
< / f i l e >
< / e x a m p l e >
*
* The predicate and reverse parameters can be controlled dynamically through scope properties ,
* as shown in the next example .
* @ example
< example module = "orderByExample" >
< file name = "index.html" >
< script >
angular . module ( 'orderByExample' , [ ] )
. controller ( 'ExampleController' , [ '$scope' , function ( $scope ) {
$scope . friends =
[ { name : 'John' , phone : '555-1212' , age : 10 } ,
{ name : 'Mary' , phone : '555-9876' , age : 19 } ,
{ name : 'Mike' , phone : '555-4321' , age : 21 } ,
{ name : 'Adam' , phone : '555-5678' , age : 35 } ,
{ name : 'Julie' , phone : '555-8765' , age : 29 } ] ;
$scope . predicate = '-age' ;
} ] ) ;
2016-04-18 12:34:29 +00:00
< / s c r i p t >
2018-05-05 12:13:16 +02:00
< div ng - controller = "ExampleController" >
2016-03-28 10:46:51 +00:00
< pre > Sorting predicate = { { predicate } } ; reverse = { { reverse } } < / p r e >
< hr / >
2016-04-18 12:34:29 +00:00
[ < a href = "" ng - click = "predicate=''" > unsorted < / a > ]
2016-03-28 10:46:51 +00:00
< table class = "friend" >
< tr >
2016-04-18 12:34:29 +00:00
< th > < a href = "" ng - click = "predicate = 'name'; reverse=false" > Name < / a >
( < a href = "" ng - click = "predicate = '-name'; reverse=false" > ^ < / a > ) < / t h >
< th > < a href = "" ng - click = "predicate = 'phone'; reverse=!reverse" > Phone Number < / a > < / t h >
< th > < a href = "" ng - click = "predicate = 'age'; reverse=!reverse" > Age < / a > < / t h >
2016-03-28 10:46:51 +00:00
< / t r >
< tr ng - repeat = "friend in friends | orderBy:predicate:reverse" >
< td > { { friend . name } } < / t d >
< td > { { friend . phone } } < / t d >
< td > { { friend . age } } < / t d >
< / t r >
< / t a b l e >
< / d i v >
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
*
* It ' s also possible to call the orderBy filter manually , by injecting ` $ filter ` , retrieving the
* filter routine with ` $ filter('orderBy') ` , and calling the returned filter routine with the
* desired parameters .
*
* Example :
*
* @ example
< example module = "orderByExample" >
< file name = "index.html" >
< div ng - controller = "ExampleController" >
< table class = "friend" >
< tr >
< th > < a href = "" ng - click = "reverse=false;order('name', false)" > Name < / a >
( < a href = "" ng - click = "order('-name',false)" > ^ < / a > ) < / t h >
< th > < a href = "" ng - click = "reverse=!reverse;order('phone', reverse)" > Phone Number < / a > < / t h >
< th > < a href = "" ng - click = "reverse=!reverse;order('age',reverse)" > Age < / a > < / t h >
< / t r >
< tr ng - repeat = "friend in friends" >
< td > { { friend . name } } < / t d >
< td > { { friend . phone } } < / t d >
< td > { { friend . age } } < / t d >
< / t r >
< / t a b l e >
< / d i v >
< / f i l e >
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
< file name = "script.js" >
angular . module ( 'orderByExample' , [ ] )
. controller ( 'ExampleController' , [ '$scope' , '$filter' , function ( $scope , $filter ) {
var orderBy = $filter ( 'orderBy' ) ;
$scope . friends = [
{ name : 'John' , phone : '555-1212' , age : 10 } ,
{ name : 'Mary' , phone : '555-9876' , age : 19 } ,
{ name : 'Mike' , phone : '555-4321' , age : 21 } ,
{ name : 'Adam' , phone : '555-5678' , age : 35 } ,
{ name : 'Julie' , phone : '555-8765' , age : 29 }
] ;
$scope . order = function ( predicate , reverse ) {
$scope . friends = orderBy ( $scope . friends , predicate , reverse ) ;
} ;
$scope . order ( '-age' , false ) ;
} ] ) ;
< / f i l e >
< / e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
orderByFilter . $inject = [ '$parse' ] ;
2018-05-05 12:13:16 +02:00
function orderByFilter ( $parse ) {
2016-03-28 10:46:51 +00:00
return function ( array , sortPredicate , reverseOrder ) {
2018-05-05 12:13:16 +02:00
if ( ! ( isArrayLike ( array ) ) ) return array ;
sortPredicate = isArray ( sortPredicate ) ? sortPredicate : [ sortPredicate ] ;
if ( sortPredicate . length === 0 ) { sortPredicate = [ '+' ] ; }
sortPredicate = sortPredicate . map ( function ( predicate ) {
2016-04-18 12:34:29 +00:00
var descending = false , get = predicate || identity ;
if ( isString ( predicate ) ) {
2016-03-28 10:46:51 +00:00
if ( ( predicate . charAt ( 0 ) == '+' || predicate . charAt ( 0 ) == '-' ) ) {
2016-04-18 12:34:29 +00:00
descending = predicate . charAt ( 0 ) == '-' ;
2016-03-28 10:46:51 +00:00
predicate = predicate . substring ( 1 ) ;
}
2018-05-05 12:13:16 +02:00
if ( predicate === '' ) {
// Effectively no predicate was passed so we compare identity
return reverseComparator ( compare , descending ) ;
}
2016-04-18 12:34:29 +00:00
get = $parse ( predicate ) ;
2018-05-05 12:13:16 +02:00
if ( get . constant ) {
var key = get ( ) ;
return reverseComparator ( function ( a , b ) {
return compare ( a [ key ] , b [ key ] ) ;
} , descending ) ;
}
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
return reverseComparator ( function ( a , b ) {
2016-04-18 12:34:29 +00:00
return compare ( get ( a ) , get ( b ) ) ;
} , descending ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
return slice . call ( array ) . sort ( reverseComparator ( comparator , reverseOrder ) ) ;
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
function comparator ( o1 , o2 ) {
for ( var i = 0 ; i < sortPredicate . length ; i ++ ) {
2016-04-18 12:34:29 +00:00
var comp = sortPredicate [ i ] ( o1 , o2 ) ;
if ( comp !== 0 ) return comp ;
}
return 0 ;
}
function reverseComparator ( comp , descending ) {
2018-05-05 12:13:16 +02:00
return descending
? function ( a , b ) { return comp ( b , a ) ; }
2016-04-18 12:34:29 +00:00
: comp ;
}
2018-05-05 12:13:16 +02:00
function isPrimitive ( value ) {
switch ( typeof value ) {
case 'number' : /* falls through */
case 'boolean' : /* falls through */
case 'string' :
return true ;
default :
return false ;
}
}
function objectToString ( value ) {
if ( value === null ) return 'null' ;
if ( typeof value . valueOf === 'function' ) {
value = value . valueOf ( ) ;
if ( isPrimitive ( value ) ) return value ;
}
if ( typeof value . toString === 'function' ) {
value = value . toString ( ) ;
if ( isPrimitive ( value ) ) return value ;
}
return '' ;
}
function compare ( v1 , v2 ) {
2016-04-18 12:34:29 +00:00
var t1 = typeof v1 ;
var t2 = typeof v2 ;
2018-05-05 12:13:16 +02:00
if ( t1 === t2 && t1 === "object" ) {
v1 = objectToString ( v1 ) ;
v2 = objectToString ( v2 ) ;
}
if ( t1 === t2 ) {
if ( t1 === "string" ) {
2016-04-18 12:34:29 +00:00
v1 = v1 . toLowerCase ( ) ;
v2 = v2 . toLowerCase ( ) ;
}
if ( v1 === v2 ) return 0 ;
return v1 < v2 ? - 1 : 1 ;
} else {
return t1 < t2 ? - 1 : 1 ;
2016-03-28 10:46:51 +00:00
}
}
2016-04-18 12:34:29 +00:00
} ;
2016-03-28 10:46:51 +00:00
}
function ngDirective ( directive ) {
if ( isFunction ( directive ) ) {
directive = {
link : directive
} ;
}
directive . restrict = directive . restrict || 'AC' ;
return valueFn ( directive ) ;
}
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name a
2016-03-28 10:46:51 +00:00
* @ restrict E
*
* @ description
* Modifies the default behavior of the html A tag so that the default action is prevented when
* the href attribute is empty .
*
* This change permits the easy creation of action links with the ` ngClick ` directive
* without changing the location or causing page reloads , e . g . :
* ` <a href="" ng-click="list.addItem()">Add Item</a> `
* /
var htmlAnchorDirective = valueFn ( {
restrict : 'E' ,
compile : function ( element , attr ) {
2018-05-05 12:13:16 +02:00
if ( ! attr . href && ! attr . xlinkHref && ! attr . name ) {
2016-04-18 12:34:29 +00:00
return function ( scope , element ) {
2018-05-05 12:13:16 +02:00
// If the linked element is not an anchor tag anymore, do nothing
if ( element [ 0 ] . nodeName . toLowerCase ( ) !== 'a' ) return ;
// SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
var href = toString . call ( element . prop ( 'href' ) ) === '[object SVGAnimatedString]' ?
'xlink:href' : 'href' ;
element . on ( 'click' , function ( event ) {
2016-03-28 10:46:51 +00:00
// if we have no href url, then don't navigate anywhere.
2018-05-05 12:13:16 +02:00
if ( ! element . attr ( href ) ) {
2016-03-28 10:46:51 +00:00
event . preventDefault ( ) ;
}
} ) ;
} ;
}
}
} ) ;
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngHref
2016-03-28 10:46:51 +00:00
* @ restrict A
* @ priority 99
*
* @ description
* Using Angular markup like ` {{hash}} ` in an href attribute will
* make the link go to the wrong URL if the user clicks it before
* Angular has a chance to replace the ` {{hash}} ` markup with its
* value . Until Angular replaces the markup the link will be broken
2018-05-05 12:13:16 +02:00
* and will most likely return a 404 error . The ` ngHref ` directive
* solves this problem .
2016-03-28 10:46:51 +00:00
*
* The wrong way to write it :
2018-05-05 12:13:16 +02:00
* ` ` ` html
* < a href = "http://www.gravatar.com/avatar/{{hash}}" > link1 < / a >
* ` ` `
2016-03-28 10:46:51 +00:00
*
* The correct way to write it :
2018-05-05 12:13:16 +02:00
* ` ` ` html
* < a ng - href = "http://www.gravatar.com/avatar/{{hash}}" > link1 < / a >
* ` ` `
2016-03-28 10:46:51 +00:00
*
* @ element A
* @ param { template } ngHref any string which can contain ` {{}} ` markup .
*
* @ example
* This example shows various combinations of ` href ` , ` ng-href ` and ` ng-click ` attributes
* in links and their different behaviors :
2018-05-05 12:13:16 +02:00
< example >
< file name = "index.html" >
2016-03-28 10:46:51 +00:00
< input ng - model = "value" / > < br / >
< a id = "link-1" href ng - click = "value = 1" > link 1 < /a> (link, don't reload)<br / >
< a id = "link-2" href = "" ng - click = "value = 2" > link 2 < /a> (link, don't reload)<br / >
< a id = "link-3" ng - href = "/{{'123'}}" > link 3 < /a> (link, reload!)<br / >
< a id = "link-4" href = "" name = "xx" ng - click = "value = 4" > anchor < /a> (link, don't reload)<br / >
< a id = "link-5" name = "xxx" ng - click = "value = 5" > anchor < /a> (no link)<br / >
< a id = "link-6" ng - href = "{{value}}" > link < / a > ( l i n k , c h a n g e l o c a t i o n )
2018-05-05 12:13:16 +02:00
< / f i l e >
< file name = "protractor.js" type = "protractor" >
2016-03-28 10:46:51 +00:00
it ( 'should execute ng-click but not reload when href without value' , function ( ) {
2018-05-05 12:13:16 +02:00
element ( by . id ( 'link-1' ) ) . click ( ) ;
expect ( element ( by . model ( 'value' ) ) . getAttribute ( 'value' ) ) . toEqual ( '1' ) ;
expect ( element ( by . id ( 'link-1' ) ) . getAttribute ( 'href' ) ) . toBe ( '' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should execute ng-click but not reload when href empty string' , function ( ) {
2018-05-05 12:13:16 +02:00
element ( by . id ( 'link-2' ) ) . click ( ) ;
expect ( element ( by . model ( 'value' ) ) . getAttribute ( 'value' ) ) . toEqual ( '2' ) ;
expect ( element ( by . id ( 'link-2' ) ) . getAttribute ( 'href' ) ) . toBe ( '' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should execute ng-click and change url when ng-href specified' , function ( ) {
2018-05-05 12:13:16 +02:00
expect ( element ( by . id ( 'link-3' ) ) . getAttribute ( 'href' ) ) . toMatch ( /\/123$/ ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
element ( by . id ( 'link-3' ) ) . click ( ) ;
// At this point, we navigate away from an Angular page, so we need
// to use browser.driver to get the base webdriver.
browser . wait ( function ( ) {
return browser . driver . getCurrentUrl ( ) . then ( function ( url ) {
return url . match ( /\/123$/ ) ;
} ) ;
} , 5000 , 'page should navigate to /123' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
xit ( 'should execute ng-click but not reload when href empty string and name specified' , function ( ) {
element ( by . id ( 'link-4' ) ) . click ( ) ;
expect ( element ( by . model ( 'value' ) ) . getAttribute ( 'value' ) ) . toEqual ( '4' ) ;
expect ( element ( by . id ( 'link-4' ) ) . getAttribute ( 'href' ) ) . toBe ( '' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should execute ng-click but not reload when no href but name specified' , function ( ) {
2018-05-05 12:13:16 +02:00
element ( by . id ( 'link-5' ) ) . click ( ) ;
expect ( element ( by . model ( 'value' ) ) . getAttribute ( 'value' ) ) . toEqual ( '5' ) ;
expect ( element ( by . id ( 'link-5' ) ) . getAttribute ( 'href' ) ) . toBe ( null ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should only change url when only ng-href' , function ( ) {
2018-05-05 12:13:16 +02:00
element ( by . model ( 'value' ) ) . clear ( ) ;
element ( by . model ( 'value' ) ) . sendKeys ( '6' ) ;
expect ( element ( by . id ( 'link-6' ) ) . getAttribute ( 'href' ) ) . toMatch ( /\/6$/ ) ;
element ( by . id ( 'link-6' ) ) . click ( ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
// At this point, we navigate away from an Angular page, so we need
// to use browser.driver to get the base webdriver.
browser . wait ( function ( ) {
return browser . driver . getCurrentUrl ( ) . then ( function ( url ) {
return url . match ( /\/6$/ ) ;
} ) ;
} , 5000 , 'page should navigate to /6' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngSrc
2016-03-28 10:46:51 +00:00
* @ restrict A
* @ priority 99
*
* @ description
* Using Angular markup like ` {{hash}} ` in a ` src ` attribute doesn ' t
* work right : The browser will fetch from the URL with the literal
* text ` {{hash}} ` until Angular replaces the expression inside
* ` {{hash}} ` . The ` ngSrc ` directive solves this problem .
*
* The buggy way to write it :
2018-05-05 12:13:16 +02:00
* ` ` ` html
2016-04-18 12:34:29 +00:00
* < img src = "http://www.gravatar.com/avatar/{{hash}}" / >
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
* The correct way to write it :
2018-05-05 12:13:16 +02:00
* ` ` ` html
2016-04-18 12:34:29 +00:00
* < img ng - src = "http://www.gravatar.com/avatar/{{hash}}" / >
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
* @ element IMG
* @ param { template } ngSrc any string which can contain ` {{}} ` markup .
* /
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngSrcset
2016-03-28 10:46:51 +00:00
* @ restrict A
* @ priority 99
*
* @ description
* Using Angular markup like ` {{hash}} ` in a ` srcset ` attribute doesn ' t
* work right : The browser will fetch from the URL with the literal
* text ` {{hash}} ` until Angular replaces the expression inside
* ` {{hash}} ` . The ` ngSrcset ` directive solves this problem .
*
* The buggy way to write it :
2018-05-05 12:13:16 +02:00
* ` ` ` html
2016-04-18 12:34:29 +00:00
* < img srcset = "http://www.gravatar.com/avatar/{{hash}} 2x" / >
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
* The correct way to write it :
2018-05-05 12:13:16 +02:00
* ` ` ` html
2016-04-18 12:34:29 +00:00
* < img ng - srcset = "http://www.gravatar.com/avatar/{{hash}} 2x" / >
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
* @ element IMG
* @ param { template } ngSrcset any string which can contain ` {{}} ` markup .
* /
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngDisabled
2016-03-28 10:46:51 +00:00
* @ restrict A
* @ priority 100
*
* @ description
*
2018-05-05 12:13:16 +02:00
* This directive sets the ` disabled ` attribute on the element if the
* { @ link guide / expression expression } inside ` ngDisabled ` evaluates to truthy .
*
* A special directive is necessary because we cannot use interpolation inside the ` disabled `
* attribute . The following example would make the button enabled on Chrome / Firefox
* but not on older IEs :
*
* ` ` ` html
* <!-- See below for an example of ng - disabled being used correctly -- >
* < div ng - init = "isDisabled = false" >
* < button disabled = "{{isDisabled}}" > Disabled < / b u t t o n >
2016-04-18 12:34:29 +00:00
* < / d i v >
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* This is because the HTML specification does not require browsers to preserve the values of
* boolean attributes such as ` disabled ` ( Their presence means true and their absence means false . )
2016-04-18 12:34:29 +00:00
* If we put an Angular interpolation expression into such an attribute then the
* binding information would be lost when the browser removes the attribute .
2016-03-28 10:46:51 +00:00
*
* @ example
2018-05-05 12:13:16 +02:00
< example >
< file name = "index.html" >
2016-04-18 12:34:29 +00:00
Click me to toggle : < input type = "checkbox" ng - model = "checked" > < br / >
2016-03-28 10:46:51 +00:00
< button ng - model = "button" ng - disabled = "checked" > Button < / b u t t o n >
2018-05-05 12:13:16 +02:00
< / f i l e >
< file name = "protractor.js" type = "protractor" >
2016-03-28 10:46:51 +00:00
it ( 'should toggle button' , function ( ) {
2018-05-05 12:13:16 +02:00
expect ( element ( by . css ( 'button' ) ) . getAttribute ( 'disabled' ) ) . toBeFalsy ( ) ;
element ( by . model ( 'checked' ) ) . click ( ) ;
expect ( element ( by . css ( 'button' ) ) . getAttribute ( 'disabled' ) ) . toBeTruthy ( ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-03-28 10:46:51 +00:00
*
* @ element INPUT
2016-05-18 00:10:50 +00:00
* @ param { expression } ngDisabled If the { @ link guide / expression expression } is truthy ,
2018-05-05 12:13:16 +02:00
* then the ` disabled ` attribute will be set on the element
2016-03-28 10:46:51 +00:00
* /
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngChecked
2016-03-28 10:46:51 +00:00
* @ restrict A
* @ priority 100
*
* @ description
2016-04-18 12:34:29 +00:00
* The HTML specification does not require browsers to preserve the values of boolean attributes
* such as checked . ( Their presence means true and their absence means false . )
* If we put an Angular interpolation expression into such an attribute then the
* binding information would be lost when the browser removes the attribute .
* The ` ngChecked ` directive solves this problem for the ` checked ` attribute .
* This complementary directive is not removed by the browser and so provides
* a permanent reliable place to store the binding information .
2016-03-28 10:46:51 +00:00
* @ example
2018-05-05 12:13:16 +02:00
< example >
< file name = "index.html" >
2016-04-18 12:34:29 +00:00
Check me to check both : < input type = "checkbox" ng - model = "master" > < br / >
< input id = "checkSlave" type = "checkbox" ng - checked = "master" >
2018-05-05 12:13:16 +02:00
< / f i l e >
< file name = "protractor.js" type = "protractor" >
2016-03-28 10:46:51 +00:00
it ( 'should check both checkBoxes' , function ( ) {
2018-05-05 12:13:16 +02:00
expect ( element ( by . id ( 'checkSlave' ) ) . getAttribute ( 'checked' ) ) . toBeFalsy ( ) ;
element ( by . model ( 'master' ) ) . click ( ) ;
expect ( element ( by . id ( 'checkSlave' ) ) . getAttribute ( 'checked' ) ) . toBeTruthy ( ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-03-28 10:46:51 +00:00
*
* @ element INPUT
2016-05-18 00:10:50 +00:00
* @ param { expression } ngChecked If the { @ link guide / expression expression } is truthy ,
2016-04-18 12:34:29 +00:00
* then special attribute "checked" will be set on the element
2016-03-28 10:46:51 +00:00
* /
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngReadonly
2016-03-28 10:46:51 +00:00
* @ restrict A
* @ priority 100
*
* @ description
2016-04-18 12:34:29 +00:00
* The HTML specification does not require browsers to preserve the values of boolean attributes
* such as readonly . ( Their presence means true and their absence means false . )
* If we put an Angular interpolation expression into such an attribute then the
* binding information would be lost when the browser removes the attribute .
* The ` ngReadonly ` directive solves this problem for the ` readonly ` attribute .
* This complementary directive is not removed by the browser and so provides
* a permanent reliable place to store the binding information .
2016-03-28 10:46:51 +00:00
* @ example
2018-05-05 12:13:16 +02:00
< example >
< file name = "index.html" >
2016-04-18 12:34:29 +00:00
Check me to make text readonly : < input type = "checkbox" ng - model = "checked" > < br / >
< input type = "text" ng - readonly = "checked" value = "I'm Angular" / >
2018-05-05 12:13:16 +02:00
< / f i l e >
< file name = "protractor.js" type = "protractor" >
2016-03-28 10:46:51 +00:00
it ( 'should toggle readonly attr' , function ( ) {
2018-05-05 12:13:16 +02:00
expect ( element ( by . css ( '[type="text"]' ) ) . getAttribute ( 'readonly' ) ) . toBeFalsy ( ) ;
element ( by . model ( 'checked' ) ) . click ( ) ;
expect ( element ( by . css ( '[type="text"]' ) ) . getAttribute ( 'readonly' ) ) . toBeTruthy ( ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-03-28 10:46:51 +00:00
*
* @ element INPUT
2016-05-18 00:10:50 +00:00
* @ param { expression } ngReadonly If the { @ link guide / expression expression } is truthy ,
2016-03-28 10:46:51 +00:00
* then special attribute "readonly" will be set on the element
* /
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngSelected
2016-03-28 10:46:51 +00:00
* @ restrict A
* @ priority 100
*
* @ description
2016-04-18 12:34:29 +00:00
* The HTML specification does not require browsers to preserve the values of boolean attributes
* such as selected . ( Their presence means true and their absence means false . )
* If we put an Angular interpolation expression into such an attribute then the
* binding information would be lost when the browser removes the attribute .
2018-05-05 12:13:16 +02:00
* The ` ngSelected ` directive solves this problem for the ` selected ` attribute .
2016-04-18 12:34:29 +00:00
* This complementary directive is not removed by the browser and so provides
* a permanent reliable place to store the binding information .
2016-05-18 00:10:50 +00:00
*
2016-03-28 10:46:51 +00:00
* @ example
2018-05-05 12:13:16 +02:00
< example >
< file name = "index.html" >
2016-04-18 12:34:29 +00:00
Check me to select : < input type = "checkbox" ng - model = "selected" > < br / >
< select >
2016-03-28 10:46:51 +00:00
< option > Hello ! < / o p t i o n >
< option id = "greet" ng - selected = "selected" > Greetings ! < / o p t i o n >
< / s e l e c t >
2018-05-05 12:13:16 +02:00
< / f i l e >
< file name = "protractor.js" type = "protractor" >
2016-03-28 10:46:51 +00:00
it ( 'should select Greetings!' , function ( ) {
2018-05-05 12:13:16 +02:00
expect ( element ( by . id ( 'greet' ) ) . getAttribute ( 'selected' ) ) . toBeFalsy ( ) ;
element ( by . model ( 'selected' ) ) . click ( ) ;
expect ( element ( by . id ( 'greet' ) ) . getAttribute ( 'selected' ) ) . toBeTruthy ( ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-03-28 10:46:51 +00:00
*
* @ element OPTION
2016-05-18 00:10:50 +00:00
* @ param { expression } ngSelected If the { @ link guide / expression expression } is truthy ,
2016-03-28 10:46:51 +00:00
* then special attribute "selected" will be set on the element
* /
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngOpen
2016-03-28 10:46:51 +00:00
* @ restrict A
* @ priority 100
*
* @ description
2016-04-18 12:34:29 +00:00
* The HTML specification does not require browsers to preserve the values of boolean attributes
* such as open . ( Their presence means true and their absence means false . )
* If we put an Angular interpolation expression into such an attribute then the
* binding information would be lost when the browser removes the attribute .
* The ` ngOpen ` directive solves this problem for the ` open ` attribute .
* This complementary directive is not removed by the browser and so provides
* a permanent reliable place to store the binding information .
2016-03-28 10:46:51 +00:00
* @ example
2018-05-05 12:13:16 +02:00
< example >
< file name = "index.html" >
2016-04-18 12:34:29 +00:00
Check me check multiple : < input type = "checkbox" ng - model = "open" > < br / >
2016-03-28 10:46:51 +00:00
< details id = "details" ng - open = "open" >
< summary > Show / Hide me < / s u m m a r y >
< / d e t a i l s >
2018-05-05 12:13:16 +02:00
< / f i l e >
< file name = "protractor.js" type = "protractor" >
2016-03-28 10:46:51 +00:00
it ( 'should toggle open' , function ( ) {
2018-05-05 12:13:16 +02:00
expect ( element ( by . id ( 'details' ) ) . getAttribute ( 'open' ) ) . toBeFalsy ( ) ;
element ( by . model ( 'open' ) ) . click ( ) ;
expect ( element ( by . id ( 'details' ) ) . getAttribute ( 'open' ) ) . toBeTruthy ( ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-03-28 10:46:51 +00:00
*
* @ element DETAILS
2016-05-18 00:10:50 +00:00
* @ param { expression } ngOpen If the { @ link guide / expression expression } is truthy ,
2016-03-28 10:46:51 +00:00
* then special attribute "open" will be set on the element
* /
var ngAttributeAliasDirectives = { } ;
2016-04-18 12:34:29 +00:00
2016-03-28 10:46:51 +00:00
// boolean attrs are evaluated
forEach ( BOOLEAN _ATTR , function ( propName , attrName ) {
// binding to multiple is not supported
if ( propName == "multiple" ) return ;
var normalized = directiveNormalize ( 'ng-' + attrName ) ;
ngAttributeAliasDirectives [ normalized ] = function ( ) {
return {
2018-05-05 12:13:16 +02:00
restrict : 'A' ,
2016-03-28 10:46:51 +00:00
priority : 100 ,
link : function ( scope , element , attr ) {
2016-04-18 12:34:29 +00:00
scope . $watch ( attr [ normalized ] , function ngBooleanAttrWatchAction ( value ) {
attr . $set ( attrName , ! ! value ) ;
2016-03-28 10:46:51 +00:00
} ) ;
}
} ;
} ;
} ) ;
2018-05-05 12:13:16 +02:00
// aliased input attrs are evaluated
forEach ( ALIASED _ATTR , function ( htmlAttr , ngAttr ) {
ngAttributeAliasDirectives [ ngAttr ] = function ( ) {
return {
priority : 100 ,
link : function ( scope , element , attr ) {
//special case ngPattern when a literal regular expression value
//is used as the expression (this way we don't have to watch anything).
if ( ngAttr === "ngPattern" && attr . ngPattern . charAt ( 0 ) == "/" ) {
var match = attr . ngPattern . match ( REGEX _STRING _REGEXP ) ;
if ( match ) {
attr . $set ( "ngPattern" , new RegExp ( match [ 1 ] , match [ 2 ] ) ) ;
return ;
}
}
scope . $watch ( attr [ ngAttr ] , function ngAttrAliasWatchAction ( value ) {
attr . $set ( ngAttr , value ) ;
} ) ;
}
} ;
} ;
} ) ;
2016-04-18 12:34:29 +00:00
2016-03-28 10:46:51 +00:00
// ng-src, ng-srcset, ng-href are interpolated
forEach ( [ 'src' , 'srcset' , 'href' ] , function ( attrName ) {
var normalized = directiveNormalize ( 'ng-' + attrName ) ;
ngAttributeAliasDirectives [ normalized ] = function ( ) {
return {
priority : 99 , // it needs to run after the attributes are interpolated
link : function ( scope , element , attr ) {
2018-05-05 12:13:16 +02:00
var propName = attrName ,
name = attrName ;
if ( attrName === 'href' &&
toString . call ( element . prop ( 'href' ) ) === '[object SVGAnimatedString]' ) {
name = 'xlinkHref' ;
attr . $attr [ name ] = 'xlink:href' ;
propName = null ;
}
2016-03-28 10:46:51 +00:00
attr . $observe ( normalized , function ( value ) {
2018-05-05 12:13:16 +02:00
if ( ! value ) {
if ( attrName === 'href' ) {
attr . $set ( name , null ) ;
}
return ;
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
attr . $set ( name , value ) ;
2016-03-28 10:46:51 +00:00
// on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
// then calling element.setAttribute('src', 'foo') doesn't do anything, so we need
// to set the property as well to achieve the desired effect.
// we use attr[attrName] value since $set can sanitize the url.
2018-05-05 12:13:16 +02:00
if ( msie && propName ) element . prop ( propName , attr [ name ] ) ;
2016-03-28 10:46:51 +00:00
} ) ;
}
} ;
} ;
} ) ;
2018-05-05 12:13:16 +02:00
/ * g l o b a l - n u l l F o r m C t r l , - S U B M I T T E D _ C L A S S , a d d S e t V a l i d i t y M e t h o d : t r u e
* /
2016-03-28 10:46:51 +00:00
var nullFormCtrl = {
$addControl : noop ,
2018-05-05 12:13:16 +02:00
$$renameControl : nullFormRenameControl ,
2016-03-28 10:46:51 +00:00
$removeControl : noop ,
$setValidity : noop ,
$setDirty : noop ,
2018-05-05 12:13:16 +02:00
$setPristine : noop ,
$setSubmitted : noop
} ,
SUBMITTED _CLASS = 'ng-submitted' ;
function nullFormRenameControl ( control , name ) {
control . $name = name ;
}
2016-03-28 10:46:51 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc type
* @ name form . FormController
2016-03-28 10:46:51 +00:00
*
* @ property { boolean } $pristine True if user has not interacted with the form yet .
* @ property { boolean } $dirty True if user has already interacted with the form .
* @ property { boolean } $valid True if all of the containing forms and controls are valid .
* @ property { boolean } $invalid True if at least one containing control or form is invalid .
2018-05-05 12:13:16 +02:00
* @ property { boolean } $submitted True if user has submitted the form even if its invalid .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* @ property { Object } $error Is an object hash , containing references to controls or
* forms with failing validators , where :
2016-03-28 10:46:51 +00:00
*
* - keys are validation tokens ( error names ) ,
2018-05-05 12:13:16 +02:00
* - values are arrays of controls or forms that have a failing validator for given error name .
2016-03-28 10:46:51 +00:00
*
* Built - in validation tokens :
*
* - ` email `
* - ` max `
* - ` maxlength `
* - ` min `
* - ` minlength `
* - ` number `
* - ` pattern `
* - ` required `
* - ` url `
2018-05-05 12:13:16 +02:00
* - ` date `
* - ` datetimelocal `
* - ` time `
* - ` week `
* - ` month `
2016-05-18 00:10:50 +00:00
*
2016-03-28 10:46:51 +00:00
* @ description
2018-05-05 12:13:16 +02:00
* ` FormController ` keeps track of all its controls and nested forms as well as the state of them ,
2016-03-28 10:46:51 +00:00
* such as being valid / invalid or dirty / pristine .
*
* Each { @ link ng . directive : form form } directive creates an instance
* of ` FormController ` .
*
* /
//asks for $scope to fool the BC controller module
2018-05-05 12:13:16 +02:00
FormController . $inject = [ '$element' , '$attrs' , '$scope' , '$animate' , '$interpolate' ] ;
function FormController ( element , attrs , $scope , $animate , $interpolate ) {
2016-03-28 10:46:51 +00:00
var form = this ,
controls = [ ] ;
2018-05-05 12:13:16 +02:00
var parentForm = form . $$parentForm = element . parent ( ) . controller ( 'form' ) || nullFormCtrl ;
2016-03-28 10:46:51 +00:00
// init state
2018-05-05 12:13:16 +02:00
form . $error = { } ;
form . $$success = { } ;
form . $pending = undefined ;
form . $name = $interpolate ( attrs . name || attrs . ngForm || '' ) ( $scope ) ;
2016-03-28 10:46:51 +00:00
form . $dirty = false ;
form . $pristine = true ;
form . $valid = true ;
form . $invalid = false ;
2018-05-05 12:13:16 +02:00
form . $submitted = false ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
parentForm . $addControl ( form ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
/ * *
* @ ngdoc method
* @ name form . FormController # $rollbackViewValue
*
* @ description
* Rollback all form controls pending updates to the ` $ modelValue ` .
*
* Updates may be pending by a debounced event or because the input is waiting for a some future
* event defined in ` ng-model-options ` . This method is typically needed by the reset button of
* a form that uses ` ng-model-options ` to pend updates .
* /
form . $rollbackViewValue = function ( ) {
forEach ( controls , function ( control ) {
control . $rollbackViewValue ( ) ;
} ) ;
} ;
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
/ * *
* @ ngdoc method
* @ name form . FormController # $commitViewValue
*
* @ description
* Commit all form controls pending updates to the ` $ modelValue ` .
*
* Updates may be pending by a debounced event or because the input is waiting for a some future
* event defined in ` ng-model-options ` . This method is rarely needed as ` NgModelController `
* usually handles calling this in response to input events .
* /
form . $commitViewValue = function ( ) {
forEach ( controls , function ( control ) {
control . $commitViewValue ( ) ;
} ) ;
} ;
2016-03-28 10:46:51 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name form . FormController # $addControl
2016-03-28 10:46:51 +00:00
*
* @ description
2016-04-18 12:34:29 +00:00
* Register a control with the form .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* Input elements using ngModelController do this automatically when they are linked .
2016-03-28 10:46:51 +00:00
* /
form . $addControl = function ( control ) {
// Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored
// and not added to the scope. Now we throw an error.
assertNotHasOwnProperty ( control . $name , 'input' ) ;
controls . push ( control ) ;
if ( control . $name ) {
form [ control . $name ] = control ;
}
} ;
2018-05-05 12:13:16 +02:00
// Private API: rename a form control
form . $$renameControl = function ( control , newName ) {
var oldName = control . $name ;
if ( form [ oldName ] === control ) {
delete form [ oldName ] ;
}
form [ newName ] = control ;
control . $name = newName ;
} ;
2016-03-28 10:46:51 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name form . FormController # $removeControl
2016-03-28 10:46:51 +00:00
*
* @ description
* Deregister a control from the form .
*
* Input elements using ngModelController do this automatically when they are destroyed .
* /
form . $removeControl = function ( control ) {
if ( control . $name && form [ control . $name ] === control ) {
delete form [ control . $name ] ;
}
2018-05-05 12:13:16 +02:00
forEach ( form . $pending , function ( value , name ) {
form . $setValidity ( name , null , control ) ;
} ) ;
forEach ( form . $error , function ( value , name ) {
form . $setValidity ( name , null , control ) ;
} ) ;
forEach ( form . $$success , function ( value , name ) {
form . $setValidity ( name , null , control ) ;
2016-03-28 10:46:51 +00:00
} ) ;
arrayRemove ( controls , control ) ;
} ;
2018-05-05 12:13:16 +02:00
2016-03-28 10:46:51 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name form . FormController # $setValidity
2016-03-28 10:46:51 +00:00
*
* @ description
* Sets the validity of a form control .
*
* This method will also propagate to parent forms .
* /
2018-05-05 12:13:16 +02:00
addSetValidityMethod ( {
ctrl : this ,
$element : element ,
set : function ( object , property , controller ) {
var list = object [ property ] ;
if ( ! list ) {
object [ property ] = [ controller ] ;
} else {
var index = list . indexOf ( controller ) ;
if ( index === - 1 ) {
list . push ( controller ) ;
2016-03-28 10:46:51 +00:00
}
}
2018-05-05 12:13:16 +02:00
} ,
unset : function ( object , property , controller ) {
var list = object [ property ] ;
if ( ! list ) {
return ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
arrayRemove ( list , controller ) ;
if ( list . length === 0 ) {
delete object [ property ] ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
} ,
parentForm : parentForm ,
$animate : $animate
} ) ;
2016-03-28 10:46:51 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name form . FormController # $setDirty
2016-03-28 10:46:51 +00:00
*
* @ description
* Sets the form to a dirty state .
*
* This method can be called to add the 'ng-dirty' class and set the form to a dirty
* state ( ng - dirty class ) . This method will also propagate to parent forms .
* /
form . $setDirty = function ( ) {
2018-05-05 12:13:16 +02:00
$animate . removeClass ( element , PRISTINE _CLASS ) ;
$animate . addClass ( element , DIRTY _CLASS ) ;
2016-03-28 10:46:51 +00:00
form . $dirty = true ;
form . $pristine = false ;
2016-04-18 12:34:29 +00:00
parentForm . $setDirty ( ) ;
2016-03-28 10:46:51 +00:00
} ;
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc method
* @ name form . FormController # $setPristine
2016-03-28 10:46:51 +00:00
*
* @ description
* Sets the form to its pristine state .
*
* This method can be called to remove the 'ng-dirty' class and set the form to its pristine
* state ( ng - pristine class ) . This method will also propagate to all the controls contained
* in this form .
*
* Setting a form back to a pristine state is often useful when we want to 'reuse' a form after
* saving or resetting it .
* /
2018-05-05 12:13:16 +02:00
form . $setPristine = function ( ) {
$animate . setClass ( element , PRISTINE _CLASS , DIRTY _CLASS + ' ' + SUBMITTED _CLASS ) ;
2016-03-28 10:46:51 +00:00
form . $dirty = false ;
form . $pristine = true ;
2018-05-05 12:13:16 +02:00
form . $submitted = false ;
2016-03-28 10:46:51 +00:00
forEach ( controls , function ( control ) {
control . $setPristine ( ) ;
} ) ;
} ;
2018-05-05 12:13:16 +02:00
/ * *
* @ ngdoc method
* @ name form . FormController # $setUntouched
*
* @ description
* Sets the form to its untouched state .
*
* This method can be called to remove the 'ng-touched' class and set the form controls to their
* untouched state ( ng - untouched class ) .
*
* Setting a form controls back to their untouched state is often useful when setting the form
* back to its pristine state .
* /
form . $setUntouched = function ( ) {
forEach ( controls , function ( control ) {
control . $setUntouched ( ) ;
} ) ;
} ;
/ * *
* @ ngdoc method
* @ name form . FormController # $setSubmitted
*
* @ description
* Sets the form to its submitted state .
* /
form . $setSubmitted = function ( ) {
$animate . addClass ( element , SUBMITTED _CLASS ) ;
form . $submitted = true ;
parentForm . $setSubmitted ( ) ;
} ;
}
2016-04-18 12:34:29 +00:00
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngForm
2016-03-28 10:46:51 +00:00
* @ restrict EAC
*
* @ description
* Nestable alias of { @ link ng . directive : form ` form ` } directive . HTML
* does not allow nesting of form elements . It is useful to nest forms , for example if the validity of a
* sub - group of controls needs to be determined .
*
2018-05-05 12:13:16 +02:00
* Note : the purpose of ` ngForm ` is to group controls ,
* but not to be a replacement for the ` <form> ` tag with all of its capabilities
* ( e . g . posting to the server , ... ) .
*
2016-03-28 10:46:51 +00:00
* @ param { string = } ngForm | name Name of the form . If specified , the form controller will be published into
* related scope , under this name .
*
* /
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name form
2016-03-28 10:46:51 +00:00
* @ restrict E
*
* @ description
* Directive that instantiates
2018-05-05 12:13:16 +02:00
* { @ link form . FormController FormController } .
2016-03-28 10:46:51 +00:00
*
* If the ` name ` attribute is specified , the form controller is published onto the current scope under
* this name .
*
* # Alias : { @ link ng . directive : ngForm ` ngForm ` }
*
2018-05-05 12:13:16 +02:00
* In Angular , forms can be nested . This means that the outer form is valid when all of the child
2016-03-28 10:46:51 +00:00
* forms are valid as well . However , browsers do not allow nesting of ` <form> ` elements , so
2016-04-18 12:34:29 +00:00
* Angular provides the { @ link ng . directive : ngForm ` ngForm ` } directive which behaves identically to
* ` <form> ` but can be nested . This allows you to have nested forms , which is very useful when
* using Angular validation directives in forms that are dynamically generated using the
* { @ link ng . directive : ngRepeat ` ngRepeat ` } directive . Since you cannot dynamically generate the ` name `
* attribute of input elements using interpolation , you have to wrap each set of repeated inputs in an
* ` ngForm ` directive and nest these in an outer ` form ` element .
*
2016-03-28 10:46:51 +00:00
*
* # CSS classes
* - ` ng-valid ` is set if the form is valid .
* - ` ng-invalid ` is set if the form is invalid .
* - ` ng-pristine ` is set if the form is pristine .
* - ` ng-dirty ` is set if the form is dirty .
2018-05-05 12:13:16 +02:00
* - ` ng-submitted ` is set if the form was submitted .
*
* Keep in mind that ngAnimate can detect each of these classes when added and removed .
2016-03-28 10:46:51 +00:00
*
*
* # Submitting a form and preventing the default action
*
* Since the role of forms in client - side Angular applications is different than in classical
* roundtrip apps , it is desirable for the browser not to translate the form submission into a full
* page reload that sends the data to the server . Instead some javascript logic should be triggered
* to handle the form submission in an application - specific way .
*
* For this reason , Angular prevents the default action ( form submission to the server ) unless the
* ` <form> ` element has an ` action ` attribute specified .
*
* You can use one of the following two ways to specify what javascript method should be called when
* a form is submitted :
*
* - { @ link ng . directive : ngSubmit ngSubmit } directive on the form element
* - { @ link ng . directive : ngClick ngClick } directive on the first
* button or input field of type submit ( input [ type = submit ] )
*
* To prevent double execution of the handler , use only one of the { @ link ng . directive : ngSubmit ngSubmit }
* or { @ link ng . directive : ngClick ngClick } directives .
* This is because of the following form submission rules in the HTML specification :
*
* - If a form has only one input field then hitting enter in this field triggers form submit
* ( ` ngSubmit ` )
* - if a form has 2 + input fields and no buttons or input [ type = submit ] then hitting enter
* doesn ' t trigger submit
* - if a form has one or more input fields and one or more buttons or input [ type = submit ] then
* hitting enter in any of the input fields will trigger the click handler on the * first * button or
* input [ type = submit ] ( ` ngClick ` ) * and * a submit handler on the enclosing form ( ` ngSubmit ` )
*
2018-05-05 12:13:16 +02:00
* Any pending ` ngModelOptions ` changes will take place immediately when an enclosing form is
* submitted . Note that ` ngClick ` events will occur before the model is updated . Use ` ngSubmit `
* to have access to the updated model .
*
* # # Animation Hooks
*
* Animations in ngForm are triggered when any of the associated CSS classes are added and removed .
* These classes are : ` .ng-pristine ` , ` .ng-dirty ` , ` .ng-invalid ` and ` .ng-valid ` as well as any
* other validations that are performed within the form . Animations in ngForm are similar to how
* they work in ngClass and animations can be hooked into using CSS transitions , keyframes as well
* as JS animations .
*
* The following example shows a simple way to utilize CSS transitions to style a form element
* that has been rendered as invalid after it has been validated :
*
* < pre >
* //be sure to include ngAnimate as a module to hook into more
* //advanced animations
* . my - form {
* transition : 0.5 s linear all ;
* background : white ;
* }
* . my - form . ng - invalid {
* background : red ;
* color : white ;
* }
* < / p r e >
2016-03-28 10:46:51 +00:00
*
* @ example
2018-05-05 12:13:16 +02:00
< example deps = "angular-animate.js" animations = "true" fixBase = "true" module = "formExample" >
< file name = "index.html" >
2016-03-28 10:46:51 +00:00
< script >
2018-05-05 12:13:16 +02:00
angular . module ( 'formExample' , [ ] )
. controller ( 'FormController' , [ '$scope' , function ( $scope ) {
$scope . userType = 'guest' ;
} ] ) ;
2016-03-28 10:46:51 +00:00
< / s c r i p t >
2018-05-05 12:13:16 +02:00
< style >
. my - form {
- webkit - transition : all linear 0.5 s ;
transition : all linear 0.5 s ;
background : transparent ;
}
. my - form . ng - invalid {
background : red ;
}
< / s t y l e >
< form name = "myForm" ng - controller = "FormController" class = "my-form" >
2016-03-28 10:46:51 +00:00
userType : < input name = "input" ng - model = "userType" required >
< span class = "error" ng - show = "myForm.input.$error.required" > Required ! < / s p a n > < b r >
2018-05-05 12:13:16 +02:00
< code > userType = { { userType } } < / c o d e > < b r >
< code > myForm . input . $valid = { { myForm . input . $valid } } < / c o d e > < b r >
< code > myForm . input . $error = { { myForm . input . $error } } < / c o d e > < b r >
< code > myForm . $valid = { { myForm . $valid } } < / c o d e > < b r >
< code > myForm . $error . required = { { ! ! myForm . $error . required } } < / c o d e > < b r >
2016-03-28 10:46:51 +00:00
< / f o r m >
2018-05-05 12:13:16 +02:00
< / f i l e >
< file name = "protractor.js" type = "protractor" >
2016-03-28 10:46:51 +00:00
it ( 'should initialize to model' , function ( ) {
2018-05-05 12:13:16 +02:00
var userType = element ( by . binding ( 'userType' ) ) ;
var valid = element ( by . binding ( 'myForm.input.$valid' ) ) ;
expect ( userType . getText ( ) ) . toContain ( 'guest' ) ;
expect ( valid . getText ( ) ) . toContain ( 'true' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should be invalid if empty' , function ( ) {
2018-05-05 12:13:16 +02:00
var userType = element ( by . binding ( 'userType' ) ) ;
var valid = element ( by . binding ( 'myForm.input.$valid' ) ) ;
var userInput = element ( by . model ( 'userType' ) ) ;
userInput . clear ( ) ;
userInput . sendKeys ( '' ) ;
expect ( userType . getText ( ) ) . toEqual ( 'userType =' ) ;
expect ( valid . getText ( ) ) . toContain ( 'false' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
*
* @ param { string = } name Name of the form . If specified , the form controller will be published into
* related scope , under this name .
2016-03-28 10:46:51 +00:00
* /
var formDirectiveFactory = function ( isNgForm ) {
2016-04-18 12:34:29 +00:00
return [ '$timeout' , function ( $timeout ) {
2016-03-28 10:46:51 +00:00
var formDirective = {
name : 'form' ,
restrict : isNgForm ? 'EAC' : 'E' ,
controller : FormController ,
2018-05-05 12:13:16 +02:00
compile : function ngFormCompile ( formElement , attr ) {
// Setup initial state of the control
formElement . addClass ( PRISTINE _CLASS ) . addClass ( VALID _CLASS ) ;
var nameAttr = attr . name ? 'name' : ( isNgForm && attr . ngForm ? 'ngForm' : false ) ;
2016-03-28 10:46:51 +00:00
return {
2018-05-05 12:13:16 +02:00
pre : function ngFormPreLink ( scope , formElement , attr , controller ) {
// if `action` attr is not present on the form, prevent the default action (submission)
if ( ! ( 'action' in attr ) ) {
2016-03-28 10:46:51 +00:00
// we can't use jq events because if a form is destroyed during submission the default
// action is not prevented. see #1238
//
// IE 9 is not affected because it doesn't fire a submit event and try to do a full
// page reload if the form was destroyed by submission of the form via a click handler
// on a button in the form. Looks like an IE9 specific bug.
2018-05-05 12:13:16 +02:00
var handleFormSubmission = function ( event ) {
scope . $apply ( function ( ) {
controller . $commitViewValue ( ) ;
controller . $setSubmitted ( ) ;
} ) ;
event . preventDefault ( ) ;
2016-03-28 10:46:51 +00:00
} ;
2018-05-05 12:13:16 +02:00
addEventListenerFn ( formElement [ 0 ] , 'submit' , handleFormSubmission ) ;
2016-03-28 10:46:51 +00:00
// unregister the preventDefault listener so that we don't not leak memory but in a
// way that will achieve the prevention of the default action.
formElement . on ( '$destroy' , function ( ) {
$timeout ( function ( ) {
2018-05-05 12:13:16 +02:00
removeEventListenerFn ( formElement [ 0 ] , 'submit' , handleFormSubmission ) ;
2016-03-28 10:46:51 +00:00
} , 0 , false ) ;
} ) ;
}
2018-05-05 12:13:16 +02:00
var parentFormCtrl = controller . $$parentForm ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
if ( nameAttr ) {
setter ( scope , null , controller . $name , controller , controller . $name ) ;
attr . $observe ( nameAttr , function ( newValue ) {
if ( controller . $name === newValue ) return ;
setter ( scope , null , controller . $name , undefined , controller . $name ) ;
parentFormCtrl . $$renameControl ( controller , newValue ) ;
setter ( scope , null , controller . $name , controller , controller . $name ) ;
2016-03-28 10:46:51 +00:00
} ) ;
}
2018-05-05 12:13:16 +02:00
formElement . on ( '$destroy' , function ( ) {
parentFormCtrl . $removeControl ( controller ) ;
if ( nameAttr ) {
setter ( scope , null , attr [ nameAttr ] , undefined , controller . $name ) ;
}
extend ( controller , nullFormCtrl ) ; //stop propagating child destruction handlers upwards
} ) ;
2016-03-28 10:46:51 +00:00
}
} ;
}
} ;
return formDirective ;
} ] ;
} ;
var formDirective = formDirectiveFactory ( ) ;
var ngFormDirective = formDirectiveFactory ( true ) ;
2018-05-05 12:13:16 +02:00
/ * g l o b a l V A L I D _ C L A S S : f a l s e ,
INVALID _CLASS : false ,
PRISTINE _CLASS : false ,
DIRTY _CLASS : false ,
UNTOUCHED _CLASS : false ,
TOUCHED _CLASS : false ,
ngModelMinErr : false ,
2016-03-28 10:46:51 +00:00
* /
2018-05-05 12:13:16 +02:00
// Regex code is obtained from SO: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231
var 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)/ ;
2016-04-18 12:34:29 +00:00
var URL _REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/ ;
2018-05-05 12:13:16 +02:00
var EMAIL _REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i ;
2016-04-18 12:34:29 +00:00
var NUMBER _REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/ ;
2018-05-05 12:13:16 +02:00
var DATE _REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/ ;
var DATETIMELOCAL _REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/ ;
var WEEK _REGEXP = /^(\d{4})-W(\d\d)$/ ;
var MONTH _REGEXP = /^(\d{4})-(\d\d)$/ ;
var TIME _REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/ ;
2016-03-28 10:46:51 +00:00
var inputType = {
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc input
* @ name input [ text ]
2016-03-28 10:46:51 +00:00
*
* @ description
2018-05-05 12:13:16 +02:00
* Standard HTML text input with angular data binding , inherited by most of the ` input ` elements .
*
2016-03-28 10:46:51 +00:00
*
* @ param { string } ngModel Assignable angular expression to data - bind to .
* @ param { string = } name Property name of the form under which the control is published .
* @ param { string = } required Adds ` required ` validation error key if the value is not entered .
* @ param { string = } ngRequired Adds ` required ` attribute and ` required ` validation constraint to
* the element when the ngRequired expression evaluates to true . Use ` ngRequired ` instead of
* ` required ` when you want to data - bind to the ` required ` attribute .
* @ param { number = } ngMinlength Sets ` minlength ` validation error key if the value is shorter than
* minlength .
* @ param { number = } ngMaxlength Sets ` maxlength ` validation error key if the value is longer than
2018-05-05 12:13:16 +02:00
* maxlength . Setting the attribute to a negative or non - numeric value , allows view values of
* any length .
* @ param { string = } pattern Similar to ` ngPattern ` except that the attribute value is the actual string
* that contains the regular expression body that will be converted to a regular expression
* as in the ngPattern directive .
* @ param { string = } ngPattern Sets ` pattern ` validation error key if the ngModel value does not match
* a RegExp found by evaluating the Angular expression given in the attribute value .
* If the expression evaluates to a RegExp object then this is used directly .
* If the expression is a string then it will be converted to a RegExp after wrapping it in ` ^ ` and ` $ `
* characters . For instance , ` "abc" ` will be converted to ` new RegExp('^abc $ ') ` .
2016-03-28 10:46:51 +00:00
* @ param { string = } ngChange Angular expression to be executed when input changes due to user
* interaction with the input element .
* @ param { boolean = } [ ngTrim = true ] If set to false Angular will not automatically trim the input .
2018-05-05 12:13:16 +02:00
* This parameter is ignored for input [ type = password ] controls , which will never trim the
* input .
2016-03-28 10:46:51 +00:00
*
* @ example
2018-05-05 12:13:16 +02:00
< example name = "text-input-directive" module = "textInputExample" >
< file name = "index.html" >
2016-03-28 10:46:51 +00:00
< script >
2018-05-05 12:13:16 +02:00
angular . module ( 'textInputExample' , [ ] )
. controller ( 'ExampleController' , [ '$scope' , function ( $scope ) {
$scope . example = {
text : 'guest' ,
word : /^\s*\w*\s*$/
} ;
} ] ) ;
2016-03-28 10:46:51 +00:00
< / s c r i p t >
2018-05-05 12:13:16 +02:00
< form name = "myForm" ng - controller = "ExampleController" >
Single word : < input type = "text" name = "input" ng - model = "example.text"
ng - pattern = "example.word" required ng - trim = "false" >
2016-04-18 12:34:29 +00:00
< span class = "error" ng - show = "myForm.input.$error.required" >
Required ! < / s p a n >
< span class = "error" ng - show = "myForm.input.$error.pattern" >
Single word only ! < / s p a n >
2018-05-05 12:13:16 +02:00
< tt > text = { { example . text } } < /tt><br/ >
2016-03-28 10:46:51 +00:00
< tt > myForm . input . $valid = { { myForm . input . $valid } } < /tt><br/ >
< tt > myForm . input . $error = { { myForm . input . $error } } < /tt><br/ >
< tt > myForm . $valid = { { myForm . $valid } } < /tt><br/ >
< tt > myForm . $error . required = { { ! ! myForm . $error . required } } < /tt><br/ >
< / f o r m >
2018-05-05 12:13:16 +02:00
< / f i l e >
< file name = "protractor.js" type = "protractor" >
var text = element ( by . binding ( 'example.text' ) ) ;
var valid = element ( by . binding ( 'myForm.input.$valid' ) ) ;
var input = element ( by . model ( 'example.text' ) ) ;
2016-03-28 10:46:51 +00:00
it ( 'should initialize to model' , function ( ) {
2018-05-05 12:13:16 +02:00
expect ( text . getText ( ) ) . toContain ( 'guest' ) ;
expect ( valid . getText ( ) ) . toContain ( 'true' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should be invalid if empty' , function ( ) {
2018-05-05 12:13:16 +02:00
input . clear ( ) ;
input . sendKeys ( '' ) ;
expect ( text . getText ( ) ) . toEqual ( 'text =' ) ;
expect ( valid . getText ( ) ) . toContain ( 'false' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should be invalid if multi word' , function ( ) {
2018-05-05 12:13:16 +02:00
input . clear ( ) ;
input . sendKeys ( 'hello world' ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
expect ( valid . getText ( ) ) . toContain ( 'false' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
'text' : textInputType ,
2018-05-05 12:13:16 +02:00
/ * *
* @ ngdoc input
* @ name input [ date ]
*
* @ description
* Input with date validation and transformation . In browsers that do not yet support
* the HTML5 date input , a text element will be used . In that case , text must be entered in a valid ISO - 8601
* date format ( yyyy - MM - dd ) , for example : ` 2009-01-06 ` . Since many
* modern browsers do not yet support this input type , it is important to provide cues to users on the
* expected input format via a placeholder or label .
*
* The model must always be a Date object , otherwise Angular will throw an error .
* Invalid ` Date ` objects ( dates whose ` getTime() ` is ` NaN ` ) will be rendered as an empty string .
*
* The timezone to be used to read / write the ` Date ` instance in the model can be defined using
* { @ link ng . directive : ngModelOptions ngModelOptions } . By default , this is the timezone of the browser .
*
* @ param { string } ngModel Assignable angular expression to data - bind to .
* @ param { string = } name Property name of the form under which the control is published .
* @ param { string = } min Sets the ` min ` validation error key if the value entered is less than ` min ` . This must be a
* valid ISO date string ( yyyy - MM - dd ) .
* @ param { string = } max Sets the ` max ` validation error key if the value entered is greater than ` max ` . This must be
* a valid ISO date string ( yyyy - MM - dd ) .
* @ param { string = } required Sets ` required ` validation error key if the value is not entered .
* @ param { string = } ngRequired Adds ` required ` attribute and ` required ` validation constraint to
* the element when the ngRequired expression evaluates to true . Use ` ngRequired ` instead of
* ` required ` when you want to data - bind to the ` required ` attribute .
* @ param { string = } ngChange Angular expression to be executed when input changes due to user
* interaction with the input element .
*
* @ example
< example name = "date-input-directive" module = "dateInputExample" >
< file name = "index.html" >
< script >
angular . module ( 'dateInputExample' , [ ] )
. controller ( 'DateController' , [ '$scope' , function ( $scope ) {
$scope . example = {
value : new Date ( 2013 , 9 , 22 )
} ;
} ] ) ;
< / s c r i p t >
< form name = "myForm" ng - controller = "DateController as dateCtrl" >
Pick a date in 2013 :
< input type = "date" id = "exampleInput" name = "input" ng - model = "example.value"
placeholder = "yyyy-MM-dd" min = "2013-01-01" max = "2013-12-31" required / >
< span class = "error" ng - show = "myForm.input.$error.required" >
Required ! < / s p a n >
< span class = "error" ng - show = "myForm.input.$error.date" >
Not a valid date ! < / s p a n >
< tt > value = { { example . value | date : "yyyy-MM-dd" } } < /tt><br/ >
< tt > myForm . input . $valid = { { myForm . input . $valid } } < /tt><br/ >
< tt > myForm . input . $error = { { myForm . input . $error } } < /tt><br/ >
< tt > myForm . $valid = { { myForm . $valid } } < /tt><br/ >
< tt > myForm . $error . required = { { ! ! myForm . $error . required } } < /tt><br/ >
< / f o r m >
< / f i l e >
< file name = "protractor.js" type = "protractor" >
var value = element ( by . binding ( 'example.value | date: "yyyy-MM-dd"' ) ) ;
var valid = element ( by . binding ( 'myForm.input.$valid' ) ) ;
var input = element ( by . model ( 'example.value' ) ) ;
// currently protractor/webdriver does not support
// sending keys to all known HTML5 input controls
// for various browsers (see https://github.com/angular/protractor/issues/562).
function setInput ( val ) {
// set the value of the element and force validation.
var scr = "var ipt = document.getElementById('exampleInput'); " +
"ipt.value = '" + val + "';" +
"angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });" ;
browser . executeScript ( scr ) ;
}
it ( 'should initialize to model' , function ( ) {
expect ( value . getText ( ) ) . toContain ( '2013-10-22' ) ;
expect ( valid . getText ( ) ) . toContain ( 'myForm.input.$valid = true' ) ;
} ) ;
it ( 'should be invalid if empty' , function ( ) {
setInput ( '' ) ;
expect ( value . getText ( ) ) . toEqual ( 'value =' ) ;
expect ( valid . getText ( ) ) . toContain ( 'myForm.input.$valid = false' ) ;
} ) ;
it ( 'should be invalid if over max' , function ( ) {
setInput ( '2015-01-01' ) ;
expect ( value . getText ( ) ) . toContain ( '' ) ;
expect ( valid . getText ( ) ) . toContain ( 'myForm.input.$valid = false' ) ;
} ) ;
< / f i l e >
< / e x a m p l e >
* /
'date' : createDateInputType ( 'date' , DATE _REGEXP ,
createDateParser ( DATE _REGEXP , [ 'yyyy' , 'MM' , 'dd' ] ) ,
'yyyy-MM-dd' ) ,
/ * *
* @ ngdoc input
* @ name input [ datetime - local ]
*
* @ description
* Input with datetime validation and transformation . In browsers that do not yet support
* the HTML5 date input , a text element will be used . In that case , the text must be entered in a valid ISO - 8601
* local datetime format ( yyyy - MM - ddTHH : mm : ss ) , for example : ` 2010-12-28T14:57:00 ` .
*
* The model must always be a Date object , otherwise Angular will throw an error .
* Invalid ` Date ` objects ( dates whose ` getTime() ` is ` NaN ` ) will be rendered as an empty string .
*
* The timezone to be used to read / write the ` Date ` instance in the model can be defined using
* { @ link ng . directive : ngModelOptions ngModelOptions } . By default , this is the timezone of the browser .
*
* @ param { string } ngModel Assignable angular expression to data - bind to .
* @ param { string = } name Property name of the form under which the control is published .
* @ param { string = } min Sets the ` min ` validation error key if the value entered is less than ` min ` . This must be a
* valid ISO datetime format ( yyyy - MM - ddTHH : mm : ss ) .
* @ param { string = } max Sets the ` max ` validation error key if the value entered is greater than ` max ` . This must be
* a valid ISO datetime format ( yyyy - MM - ddTHH : mm : ss ) .
* @ param { string = } required Sets ` required ` validation error key if the value is not entered .
* @ param { string = } ngRequired Adds ` required ` attribute and ` required ` validation constraint to
* the element when the ngRequired expression evaluates to true . Use ` ngRequired ` instead of
* ` required ` when you want to data - bind to the ` required ` attribute .
* @ param { string = } ngChange Angular expression to be executed when input changes due to user
* interaction with the input element .
*
* @ example
< example name = "datetimelocal-input-directive" module = "dateExample" >
< file name = "index.html" >
< script >
angular . module ( 'dateExample' , [ ] )
. controller ( 'DateController' , [ '$scope' , function ( $scope ) {
$scope . example = {
value : new Date ( 2010 , 11 , 28 , 14 , 57 )
} ;
} ] ) ;
< / s c r i p t >
< form name = "myForm" ng - controller = "DateController as dateCtrl" >
Pick a date between in 2013 :
< input type = "datetime-local" id = "exampleInput" name = "input" ng - model = "example.value"
placeholder = "yyyy-MM-ddTHH:mm:ss" min = "2001-01-01T00:00:00" max = "2013-12-31T00:00:00" required / >
< span class = "error" ng - show = "myForm.input.$error.required" >
Required ! < / s p a n >
< span class = "error" ng - show = "myForm.input.$error.datetimelocal" >
Not a valid date ! < / s p a n >
< tt > value = { { example . value | date : "yyyy-MM-ddTHH:mm:ss" } } < /tt><br/ >
< tt > myForm . input . $valid = { { myForm . input . $valid } } < /tt><br/ >
< tt > myForm . input . $error = { { myForm . input . $error } } < /tt><br/ >
< tt > myForm . $valid = { { myForm . $valid } } < /tt><br/ >
< tt > myForm . $error . required = { { ! ! myForm . $error . required } } < /tt><br/ >
< / f o r m >
< / f i l e >
< file name = "protractor.js" type = "protractor" >
var value = element ( by . binding ( 'example.value | date: "yyyy-MM-ddTHH:mm:ss"' ) ) ;
var valid = element ( by . binding ( 'myForm.input.$valid' ) ) ;
var input = element ( by . model ( 'example.value' ) ) ;
// currently protractor/webdriver does not support
// sending keys to all known HTML5 input controls
// for various browsers (https://github.com/angular/protractor/issues/562).
function setInput ( val ) {
// set the value of the element and force validation.
var scr = "var ipt = document.getElementById('exampleInput'); " +
"ipt.value = '" + val + "';" +
"angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });" ;
browser . executeScript ( scr ) ;
}
it ( 'should initialize to model' , function ( ) {
expect ( value . getText ( ) ) . toContain ( '2010-12-28T14:57:00' ) ;
expect ( valid . getText ( ) ) . toContain ( 'myForm.input.$valid = true' ) ;
} ) ;
it ( 'should be invalid if empty' , function ( ) {
setInput ( '' ) ;
expect ( value . getText ( ) ) . toEqual ( 'value =' ) ;
expect ( valid . getText ( ) ) . toContain ( 'myForm.input.$valid = false' ) ;
} ) ;
it ( 'should be invalid if over max' , function ( ) {
setInput ( '2015-01-01T23:59:00' ) ;
expect ( value . getText ( ) ) . toContain ( '' ) ;
expect ( valid . getText ( ) ) . toContain ( 'myForm.input.$valid = false' ) ;
} ) ;
< / f i l e >
< / e x a m p l e >
* /
'datetime-local' : createDateInputType ( 'datetimelocal' , DATETIMELOCAL _REGEXP ,
createDateParser ( DATETIMELOCAL _REGEXP , [ 'yyyy' , 'MM' , 'dd' , 'HH' , 'mm' , 'ss' , 'sss' ] ) ,
'yyyy-MM-ddTHH:mm:ss.sss' ) ,
/ * *
* @ ngdoc input
* @ name input [ time ]
*
* @ description
* Input with time validation and transformation . In browsers that do not yet support
* the HTML5 date input , a text element will be used . In that case , the text must be entered in a valid ISO - 8601
* local time format ( HH : mm : ss ) , for example : ` 14:57:00 ` . Model must be a Date object . This binding will always output a
* Date object to the model of January 1 , 1970 , or local date ` new Date(1970, 0, 1, HH, mm, ss) ` .
*
* The model must always be a Date object , otherwise Angular will throw an error .
* Invalid ` Date ` objects ( dates whose ` getTime() ` is ` NaN ` ) will be rendered as an empty string .
*
* The timezone to be used to read / write the ` Date ` instance in the model can be defined using
* { @ link ng . directive : ngModelOptions ngModelOptions } . By default , this is the timezone of the browser .
*
* @ param { string } ngModel Assignable angular expression to data - bind to .
* @ param { string = } name Property name of the form under which the control is published .
* @ param { string = } min Sets the ` min ` validation error key if the value entered is less than ` min ` . This must be a
* valid ISO time format ( HH : mm : ss ) .
* @ param { string = } max Sets the ` max ` validation error key if the value entered is greater than ` max ` . This must be a
* valid ISO time format ( HH : mm : ss ) .
* @ param { string = } required Sets ` required ` validation error key if the value is not entered .
* @ param { string = } ngRequired Adds ` required ` attribute and ` required ` validation constraint to
* the element when the ngRequired expression evaluates to true . Use ` ngRequired ` instead of
* ` required ` when you want to data - bind to the ` required ` attribute .
* @ param { string = } ngChange Angular expression to be executed when input changes due to user
* interaction with the input element .
*
* @ example
< example name = "time-input-directive" module = "timeExample" >
< file name = "index.html" >
< script >
angular . module ( 'timeExample' , [ ] )
. controller ( 'DateController' , [ '$scope' , function ( $scope ) {
$scope . example = {
value : new Date ( 1970 , 0 , 1 , 14 , 57 , 0 )
} ;
} ] ) ;
< / s c r i p t >
< form name = "myForm" ng - controller = "DateController as dateCtrl" >
Pick a between 8 am and 5 pm :
< input type = "time" id = "exampleInput" name = "input" ng - model = "example.value"
placeholder = "HH:mm:ss" min = "08:00:00" max = "17:00:00" required / >
< span class = "error" ng - show = "myForm.input.$error.required" >
Required ! < / s p a n >
< span class = "error" ng - show = "myForm.input.$error.time" >
Not a valid date ! < / s p a n >
< tt > value = { { example . value | date : "HH:mm:ss" } } < /tt><br/ >
< tt > myForm . input . $valid = { { myForm . input . $valid } } < /tt><br/ >
< tt > myForm . input . $error = { { myForm . input . $error } } < /tt><br/ >
< tt > myForm . $valid = { { myForm . $valid } } < /tt><br/ >
< tt > myForm . $error . required = { { ! ! myForm . $error . required } } < /tt><br/ >
< / f o r m >
< / f i l e >
< file name = "protractor.js" type = "protractor" >
var value = element ( by . binding ( 'example.value | date: "HH:mm:ss"' ) ) ;
var valid = element ( by . binding ( 'myForm.input.$valid' ) ) ;
var input = element ( by . model ( 'example.value' ) ) ;
// currently protractor/webdriver does not support
// sending keys to all known HTML5 input controls
// for various browsers (https://github.com/angular/protractor/issues/562).
function setInput ( val ) {
// set the value of the element and force validation.
var scr = "var ipt = document.getElementById('exampleInput'); " +
"ipt.value = '" + val + "';" +
"angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });" ;
browser . executeScript ( scr ) ;
}
it ( 'should initialize to model' , function ( ) {
expect ( value . getText ( ) ) . toContain ( '14:57:00' ) ;
expect ( valid . getText ( ) ) . toContain ( 'myForm.input.$valid = true' ) ;
} ) ;
it ( 'should be invalid if empty' , function ( ) {
setInput ( '' ) ;
expect ( value . getText ( ) ) . toEqual ( 'value =' ) ;
expect ( valid . getText ( ) ) . toContain ( 'myForm.input.$valid = false' ) ;
} ) ;
it ( 'should be invalid if over max' , function ( ) {
setInput ( '23:59:00' ) ;
expect ( value . getText ( ) ) . toContain ( '' ) ;
expect ( valid . getText ( ) ) . toContain ( 'myForm.input.$valid = false' ) ;
} ) ;
< / f i l e >
< / e x a m p l e >
* /
'time' : createDateInputType ( 'time' , TIME _REGEXP ,
createDateParser ( TIME _REGEXP , [ 'HH' , 'mm' , 'ss' , 'sss' ] ) ,
'HH:mm:ss.sss' ) ,
/ * *
* @ ngdoc input
* @ name input [ week ]
*
* @ description
* Input with week - of - the - year validation and transformation to Date . In browsers that do not yet support
* the HTML5 week input , a text element will be used . In that case , the text must be entered in a valid ISO - 8601
* week format ( yyyy - W # # ) , for example : ` 2013-W02 ` .
*
* The model must always be a Date object , otherwise Angular will throw an error .
* Invalid ` Date ` objects ( dates whose ` getTime() ` is ` NaN ` ) will be rendered as an empty string .
*
* The timezone to be used to read / write the ` Date ` instance in the model can be defined using
* { @ link ng . directive : ngModelOptions ngModelOptions } . By default , this is the timezone of the browser .
*
* @ param { string } ngModel Assignable angular expression to data - bind to .
* @ param { string = } name Property name of the form under which the control is published .
* @ param { string = } min Sets the ` min ` validation error key if the value entered is less than ` min ` . This must be a
* valid ISO week format ( yyyy - W # # ) .
* @ param { string = } max Sets the ` max ` validation error key if the value entered is greater than ` max ` . This must be
* a valid ISO week format ( yyyy - W # # ) .
* @ param { string = } required Sets ` required ` validation error key if the value is not entered .
* @ param { string = } ngRequired Adds ` required ` attribute and ` required ` validation constraint to
* the element when the ngRequired expression evaluates to true . Use ` ngRequired ` instead of
* ` required ` when you want to data - bind to the ` required ` attribute .
* @ param { string = } ngChange Angular expression to be executed when input changes due to user
* interaction with the input element .
*
* @ example
< example name = "week-input-directive" module = "weekExample" >
< file name = "index.html" >
< script >
angular . module ( 'weekExample' , [ ] )
. controller ( 'DateController' , [ '$scope' , function ( $scope ) {
$scope . example = {
value : new Date ( 2013 , 0 , 3 )
} ;
} ] ) ;
< / s c r i p t >
< form name = "myForm" ng - controller = "DateController as dateCtrl" >
Pick a date between in 2013 :
< input id = "exampleInput" type = "week" name = "input" ng - model = "example.value"
placeholder = "YYYY-W##" min = "2012-W32" max = "2013-W52" required / >
< span class = "error" ng - show = "myForm.input.$error.required" >
Required ! < / s p a n >
< span class = "error" ng - show = "myForm.input.$error.week" >
Not a valid date ! < / s p a n >
< tt > value = { { example . value | date : "yyyy-Www" } } < /tt><br/ >
< tt > myForm . input . $valid = { { myForm . input . $valid } } < /tt><br/ >
< tt > myForm . input . $error = { { myForm . input . $error } } < /tt><br/ >
< tt > myForm . $valid = { { myForm . $valid } } < /tt><br/ >
< tt > myForm . $error . required = { { ! ! myForm . $error . required } } < /tt><br/ >
< / f o r m >
< / f i l e >
< file name = "protractor.js" type = "protractor" >
var value = element ( by . binding ( 'example.value | date: "yyyy-Www"' ) ) ;
var valid = element ( by . binding ( 'myForm.input.$valid' ) ) ;
var input = element ( by . model ( 'example.value' ) ) ;
// currently protractor/webdriver does not support
// sending keys to all known HTML5 input controls
// for various browsers (https://github.com/angular/protractor/issues/562).
function setInput ( val ) {
// set the value of the element and force validation.
var scr = "var ipt = document.getElementById('exampleInput'); " +
"ipt.value = '" + val + "';" +
"angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });" ;
browser . executeScript ( scr ) ;
}
it ( 'should initialize to model' , function ( ) {
expect ( value . getText ( ) ) . toContain ( '2013-W01' ) ;
expect ( valid . getText ( ) ) . toContain ( 'myForm.input.$valid = true' ) ;
} ) ;
it ( 'should be invalid if empty' , function ( ) {
setInput ( '' ) ;
expect ( value . getText ( ) ) . toEqual ( 'value =' ) ;
expect ( valid . getText ( ) ) . toContain ( 'myForm.input.$valid = false' ) ;
} ) ;
it ( 'should be invalid if over max' , function ( ) {
setInput ( '2015-W01' ) ;
expect ( value . getText ( ) ) . toContain ( '' ) ;
expect ( valid . getText ( ) ) . toContain ( 'myForm.input.$valid = false' ) ;
} ) ;
< / f i l e >
< / e x a m p l e >
* /
'week' : createDateInputType ( 'week' , WEEK _REGEXP , weekParser , 'yyyy-Www' ) ,
/ * *
* @ ngdoc input
* @ name input [ month ]
*
* @ description
* Input with month validation and transformation . In browsers that do not yet support
* the HTML5 month input , a text element will be used . In that case , the text must be entered in a valid ISO - 8601
* month format ( yyyy - MM ) , for example : ` 2009-01 ` .
*
* The model must always be a Date object , otherwise Angular will throw an error .
* Invalid ` Date ` objects ( dates whose ` getTime() ` is ` NaN ` ) will be rendered as an empty string .
* If the model is not set to the first of the month , the next view to model update will set it
* to the first of the month .
*
* The timezone to be used to read / write the ` Date ` instance in the model can be defined using
* { @ link ng . directive : ngModelOptions ngModelOptions } . By default , this is the timezone of the browser .
*
* @ param { string } ngModel Assignable angular expression to data - bind to .
* @ param { string = } name Property name of the form under which the control is published .
* @ param { string = } min Sets the ` min ` validation error key if the value entered is less than ` min ` . This must be
* a valid ISO month format ( yyyy - MM ) .
* @ param { string = } max Sets the ` max ` validation error key if the value entered is greater than ` max ` . This must
* be a valid ISO month format ( yyyy - MM ) .
* @ param { string = } required Sets ` required ` validation error key if the value is not entered .
* @ param { string = } ngRequired Adds ` required ` attribute and ` required ` validation constraint to
* the element when the ngRequired expression evaluates to true . Use ` ngRequired ` instead of
* ` required ` when you want to data - bind to the ` required ` attribute .
* @ param { string = } ngChange Angular expression to be executed when input changes due to user
* interaction with the input element .
*
* @ example
< example name = "month-input-directive" module = "monthExample" >
< file name = "index.html" >
< script >
angular . module ( 'monthExample' , [ ] )
. controller ( 'DateController' , [ '$scope' , function ( $scope ) {
$scope . example = {
value : new Date ( 2013 , 9 , 1 )
} ;
} ] ) ;
< / s c r i p t >
< form name = "myForm" ng - controller = "DateController as dateCtrl" >
Pick a month in 2013 :
< input id = "exampleInput" type = "month" name = "input" ng - model = "example.value"
placeholder = "yyyy-MM" min = "2013-01" max = "2013-12" required / >
< span class = "error" ng - show = "myForm.input.$error.required" >
Required ! < / s p a n >
< span class = "error" ng - show = "myForm.input.$error.month" >
Not a valid month ! < / s p a n >
< tt > value = { { example . value | date : "yyyy-MM" } } < /tt><br/ >
< tt > myForm . input . $valid = { { myForm . input . $valid } } < /tt><br/ >
< tt > myForm . input . $error = { { myForm . input . $error } } < /tt><br/ >
< tt > myForm . $valid = { { myForm . $valid } } < /tt><br/ >
< tt > myForm . $error . required = { { ! ! myForm . $error . required } } < /tt><br/ >
< / f o r m >
< / f i l e >
< file name = "protractor.js" type = "protractor" >
var value = element ( by . binding ( 'example.value | date: "yyyy-MM"' ) ) ;
var valid = element ( by . binding ( 'myForm.input.$valid' ) ) ;
var input = element ( by . model ( 'example.value' ) ) ;
// currently protractor/webdriver does not support
// sending keys to all known HTML5 input controls
// for various browsers (https://github.com/angular/protractor/issues/562).
function setInput ( val ) {
// set the value of the element and force validation.
var scr = "var ipt = document.getElementById('exampleInput'); " +
"ipt.value = '" + val + "';" +
"angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });" ;
browser . executeScript ( scr ) ;
}
it ( 'should initialize to model' , function ( ) {
expect ( value . getText ( ) ) . toContain ( '2013-10' ) ;
expect ( valid . getText ( ) ) . toContain ( 'myForm.input.$valid = true' ) ;
} ) ;
it ( 'should be invalid if empty' , function ( ) {
setInput ( '' ) ;
expect ( value . getText ( ) ) . toEqual ( 'value =' ) ;
expect ( valid . getText ( ) ) . toContain ( 'myForm.input.$valid = false' ) ;
} ) ;
it ( 'should be invalid if over max' , function ( ) {
setInput ( '2015-01' ) ;
expect ( value . getText ( ) ) . toContain ( '' ) ;
expect ( valid . getText ( ) ) . toContain ( 'myForm.input.$valid = false' ) ;
} ) ;
< / f i l e >
< / e x a m p l e >
* /
'month' : createDateInputType ( 'month' , MONTH _REGEXP ,
createDateParser ( MONTH _REGEXP , [ 'yyyy' , 'MM' ] ) ,
'yyyy-MM' ) ,
2016-03-28 10:46:51 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc input
* @ name input [ number ]
2016-03-28 10:46:51 +00:00
*
* @ description
2016-04-18 12:34:29 +00:00
* Text input with number validation and transformation . Sets the ` number ` validation
* error if not a valid number .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* < div class = "alert alert-warning" >
* The model must always be of type ` number ` otherwise Angular will throw an error .
* Be aware that a string containing a number is not enough . See the { @ link ngModel : numfmt }
* error docs for more information and an example of how to convert your model if necessary .
* < / d i v >
*
2016-03-28 10:46:51 +00:00
* @ param { string } ngModel Assignable angular expression to data - bind to .
* @ param { string = } name Property name of the form under which the control is published .
* @ param { string = } min Sets the ` min ` validation error key if the value entered is less than ` min ` .
* @ param { string = } max Sets the ` max ` validation error key if the value entered is greater than ` max ` .
* @ param { string = } required Sets ` required ` validation error key if the value is not entered .
* @ param { string = } ngRequired Adds ` required ` attribute and ` required ` validation constraint to
* the element when the ngRequired expression evaluates to true . Use ` ngRequired ` instead of
* ` required ` when you want to data - bind to the ` required ` attribute .
2016-04-18 12:34:29 +00:00
* @ param { number = } ngMinlength Sets ` minlength ` validation error key if the value is shorter than
* minlength .
* @ param { number = } ngMaxlength Sets ` maxlength ` validation error key if the value is longer than
2018-05-05 12:13:16 +02:00
* maxlength . Setting the attribute to a negative or non - numeric value , allows view values of
* any length .
* @ param { string = } pattern Similar to ` ngPattern ` except that the attribute value is the actual string
* that contains the regular expression body that will be converted to a regular expression
* as in the ngPattern directive .
* @ param { string = } ngPattern Sets ` pattern ` validation error key if the ngModel value does not match
* a RegExp found by evaluating the Angular expression given in the attribute value .
* If the expression evaluates to a RegExp object then this is used directly .
* If the expression is a string then it will be converted to a RegExp after wrapping it in ` ^ ` and ` $ `
* characters . For instance , ` "abc" ` will be converted to ` new RegExp('^abc $ ') ` .
2016-03-28 10:46:51 +00:00
* @ param { string = } ngChange Angular expression to be executed when input changes due to user
* interaction with the input element .
*
* @ example
2018-05-05 12:13:16 +02:00
< example name = "number-input-directive" module = "numberExample" >
< file name = "index.html" >
2016-04-18 12:34:29 +00:00
< script >
2018-05-05 12:13:16 +02:00
angular . module ( 'numberExample' , [ ] )
. controller ( 'ExampleController' , [ '$scope' , function ( $scope ) {
$scope . example = {
value : 12
} ;
} ] ) ;
2016-04-18 12:34:29 +00:00
< / s c r i p t >
2018-05-05 12:13:16 +02:00
< form name = "myForm" ng - controller = "ExampleController" >
Number : < input type = "number" name = "input" ng - model = "example.value"
2016-04-18 12:34:29 +00:00
min = "0" max = "99" required >
< span class = "error" ng - show = "myForm.input.$error.required" >
Required ! < / s p a n >
< span class = "error" ng - show = "myForm.input.$error.number" >
Not valid number ! < / s p a n >
2018-05-05 12:13:16 +02:00
< tt > value = { { example . value } } < /tt><br/ >
2016-04-18 12:34:29 +00:00
< tt > myForm . input . $valid = { { myForm . input . $valid } } < /tt><br/ >
< tt > myForm . input . $error = { { myForm . input . $error } } < /tt><br/ >
< tt > myForm . $valid = { { myForm . $valid } } < /tt><br/ >
< tt > myForm . $error . required = { { ! ! myForm . $error . required } } < /tt><br/ >
< / f o r m >
2018-05-05 12:13:16 +02:00
< / f i l e >
< file name = "protractor.js" type = "protractor" >
var value = element ( by . binding ( 'example.value' ) ) ;
var valid = element ( by . binding ( 'myForm.input.$valid' ) ) ;
var input = element ( by . model ( 'example.value' ) ) ;
2016-04-18 12:34:29 +00:00
it ( 'should initialize to model' , function ( ) {
2018-05-05 12:13:16 +02:00
expect ( value . getText ( ) ) . toContain ( '12' ) ;
expect ( valid . getText ( ) ) . toContain ( 'true' ) ;
2016-04-18 12:34:29 +00:00
} ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
it ( 'should be invalid if empty' , function ( ) {
2018-05-05 12:13:16 +02:00
input . clear ( ) ;
input . sendKeys ( '' ) ;
expect ( value . getText ( ) ) . toEqual ( 'value =' ) ;
expect ( valid . getText ( ) ) . toContain ( 'false' ) ;
2016-04-18 12:34:29 +00:00
} ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
it ( 'should be invalid if over max' , function ( ) {
2018-05-05 12:13:16 +02:00
input . clear ( ) ;
input . sendKeys ( '123' ) ;
expect ( value . getText ( ) ) . toEqual ( 'value =' ) ;
expect ( valid . getText ( ) ) . toContain ( 'false' ) ;
2016-04-18 12:34:29 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
2016-04-18 12:34:29 +00:00
'number' : numberInputType ,
2016-03-28 10:46:51 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc input
* @ name input [ url ]
2016-03-28 10:46:51 +00:00
*
* @ description
2016-04-18 12:34:29 +00:00
* Text input with URL validation . Sets the ` url ` validation error key if the content is not a
* valid URL .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* < div class = "alert alert-warning" >
* * * Note : * * ` input[url] ` uses a regex to validate urls that is derived from the regex
* used in Chromium . If you need stricter validation , you can use ` ng-pattern ` or modify
* the built - in validators ( see the { @ link guide / forms Forms guide } )
* < / d i v >
*
2016-03-28 10:46:51 +00:00
* @ param { string } ngModel Assignable angular expression to data - bind to .
* @ param { string = } name Property name of the form under which the control is published .
* @ param { string = } required Sets ` required ` validation error key if the value is not entered .
* @ param { string = } ngRequired Adds ` required ` attribute and ` required ` validation constraint to
* the element when the ngRequired expression evaluates to true . Use ` ngRequired ` instead of
* ` required ` when you want to data - bind to the ` required ` attribute .
* @ param { number = } ngMinlength Sets ` minlength ` validation error key if the value is shorter than
* minlength .
* @ param { number = } ngMaxlength Sets ` maxlength ` validation error key if the value is longer than
2018-05-05 12:13:16 +02:00
* maxlength . Setting the attribute to a negative or non - numeric value , allows view values of
* any length .
* @ param { string = } pattern Similar to ` ngPattern ` except that the attribute value is the actual string
* that contains the regular expression body that will be converted to a regular expression
* as in the ngPattern directive .
* @ param { string = } ngPattern Sets ` pattern ` validation error key if the ngModel value does not match
* a RegExp found by evaluating the Angular expression given in the attribute value .
* If the expression evaluates to a RegExp object then this is used directly .
* If the expression is a string then it will be converted to a RegExp after wrapping it in ` ^ ` and ` $ `
* characters . For instance , ` "abc" ` will be converted to ` new RegExp('^abc $ ') ` .
2016-03-28 10:46:51 +00:00
* @ param { string = } ngChange Angular expression to be executed when input changes due to user
* interaction with the input element .
*
* @ example
2018-05-05 12:13:16 +02:00
< example name = "url-input-directive" module = "urlExample" >
< file name = "index.html" >
2016-03-28 10:46:51 +00:00
< script >
2018-05-05 12:13:16 +02:00
angular . module ( 'urlExample' , [ ] )
. controller ( 'ExampleController' , [ '$scope' , function ( $scope ) {
$scope . url = {
text : 'http://google.com'
} ;
} ] ) ;
2016-03-28 10:46:51 +00:00
< / s c r i p t >
2018-05-05 12:13:16 +02:00
< form name = "myForm" ng - controller = "ExampleController" >
URL : < input type = "url" name = "input" ng - model = "url.text" required >
2016-04-18 12:34:29 +00:00
< span class = "error" ng - show = "myForm.input.$error.required" >
Required ! < / s p a n >
< span class = "error" ng - show = "myForm.input.$error.url" >
Not valid url ! < / s p a n >
2018-05-05 12:13:16 +02:00
< tt > text = { { url . text } } < /tt><br/ >
2016-03-28 10:46:51 +00:00
< tt > myForm . input . $valid = { { myForm . input . $valid } } < /tt><br/ >
< tt > myForm . input . $error = { { myForm . input . $error } } < /tt><br/ >
< tt > myForm . $valid = { { myForm . $valid } } < /tt><br/ >
< tt > myForm . $error . required = { { ! ! myForm . $error . required } } < /tt><br/ >
< tt > myForm . $error . url = { { ! ! myForm . $error . url } } < /tt><br/ >
< / f o r m >
2018-05-05 12:13:16 +02:00
< / f i l e >
< file name = "protractor.js" type = "protractor" >
var text = element ( by . binding ( 'url.text' ) ) ;
var valid = element ( by . binding ( 'myForm.input.$valid' ) ) ;
var input = element ( by . model ( 'url.text' ) ) ;
2016-03-28 10:46:51 +00:00
it ( 'should initialize to model' , function ( ) {
2018-05-05 12:13:16 +02:00
expect ( text . getText ( ) ) . toContain ( 'http://google.com' ) ;
expect ( valid . getText ( ) ) . toContain ( 'true' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should be invalid if empty' , function ( ) {
2018-05-05 12:13:16 +02:00
input . clear ( ) ;
input . sendKeys ( '' ) ;
expect ( text . getText ( ) ) . toEqual ( 'text =' ) ;
expect ( valid . getText ( ) ) . toContain ( 'false' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should be invalid if not url' , function ( ) {
2018-05-05 12:13:16 +02:00
input . clear ( ) ;
input . sendKeys ( 'box' ) ;
expect ( valid . getText ( ) ) . toContain ( 'false' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
'url' : urlInputType ,
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc input
* @ name input [ email ]
2016-03-28 10:46:51 +00:00
*
* @ description
* Text input with email validation . Sets the ` email ` validation error key if not a valid email
* address .
*
2018-05-05 12:13:16 +02:00
* < div class = "alert alert-warning" >
* * * Note : * * ` input[email] ` uses a regex to validate email addresses that is derived from the regex
* used in Chromium . If you need stricter validation ( e . g . requiring a top - level domain ) , you can
* use ` ng-pattern ` or modify the built - in validators ( see the { @ link guide / forms Forms guide } )
* < / d i v >
*
2016-03-28 10:46:51 +00:00
* @ param { string } ngModel Assignable angular expression to data - bind to .
* @ param { string = } name Property name of the form under which the control is published .
* @ param { string = } required Sets ` required ` validation error key if the value is not entered .
* @ param { string = } ngRequired Adds ` required ` attribute and ` required ` validation constraint to
* the element when the ngRequired expression evaluates to true . Use ` ngRequired ` instead of
* ` required ` when you want to data - bind to the ` required ` attribute .
* @ param { number = } ngMinlength Sets ` minlength ` validation error key if the value is shorter than
* minlength .
* @ param { number = } ngMaxlength Sets ` maxlength ` validation error key if the value is longer than
2018-05-05 12:13:16 +02:00
* maxlength . Setting the attribute to a negative or non - numeric value , allows view values of
* any length .
* @ param { string = } pattern Similar to ` ngPattern ` except that the attribute value is the actual string
* that contains the regular expression body that will be converted to a regular expression
* as in the ngPattern directive .
* @ param { string = } ngPattern Sets ` pattern ` validation error key if the ngModel value does not match
* a RegExp found by evaluating the Angular expression given in the attribute value .
* If the expression evaluates to a RegExp object then this is used directly .
* If the expression is a string then it will be converted to a RegExp after wrapping it in ` ^ ` and ` $ `
* characters . For instance , ` "abc" ` will be converted to ` new RegExp('^abc $ ') ` .
2016-03-28 10:46:51 +00:00
* @ param { string = } ngChange Angular expression to be executed when input changes due to user
* interaction with the input element .
*
* @ example
2018-05-05 12:13:16 +02:00
< example name = "email-input-directive" module = "emailExample" >
< file name = "index.html" >
2016-03-28 10:46:51 +00:00
< script >
2018-05-05 12:13:16 +02:00
angular . module ( 'emailExample' , [ ] )
. controller ( 'ExampleController' , [ '$scope' , function ( $scope ) {
$scope . email = {
text : 'me@example.com'
} ;
} ] ) ;
2016-03-28 10:46:51 +00:00
< / s c r i p t >
2018-05-05 12:13:16 +02:00
< form name = "myForm" ng - controller = "ExampleController" >
Email : < input type = "email" name = "input" ng - model = "email.text" required >
2016-04-18 12:34:29 +00:00
< span class = "error" ng - show = "myForm.input.$error.required" >
Required ! < / s p a n >
< span class = "error" ng - show = "myForm.input.$error.email" >
Not valid email ! < / s p a n >
2018-05-05 12:13:16 +02:00
< tt > text = { { email . text } } < /tt><br/ >
2016-03-28 10:46:51 +00:00
< tt > myForm . input . $valid = { { myForm . input . $valid } } < /tt><br/ >
< tt > myForm . input . $error = { { myForm . input . $error } } < /tt><br/ >
< tt > myForm . $valid = { { myForm . $valid } } < /tt><br/ >
< tt > myForm . $error . required = { { ! ! myForm . $error . required } } < /tt><br/ >
< tt > myForm . $error . email = { { ! ! myForm . $error . email } } < /tt><br/ >
< / f o r m >
2018-05-05 12:13:16 +02:00
< / f i l e >
< file name = "protractor.js" type = "protractor" >
var text = element ( by . binding ( 'email.text' ) ) ;
var valid = element ( by . binding ( 'myForm.input.$valid' ) ) ;
var input = element ( by . model ( 'email.text' ) ) ;
2016-03-28 10:46:51 +00:00
it ( 'should initialize to model' , function ( ) {
2018-05-05 12:13:16 +02:00
expect ( text . getText ( ) ) . toContain ( 'me@example.com' ) ;
expect ( valid . getText ( ) ) . toContain ( 'true' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should be invalid if empty' , function ( ) {
2018-05-05 12:13:16 +02:00
input . clear ( ) ;
input . sendKeys ( '' ) ;
expect ( text . getText ( ) ) . toEqual ( 'text =' ) ;
expect ( valid . getText ( ) ) . toContain ( 'false' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should be invalid if not email' , function ( ) {
2018-05-05 12:13:16 +02:00
input . clear ( ) ;
input . sendKeys ( 'xxx' ) ;
expect ( valid . getText ( ) ) . toContain ( 'false' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
'email' : emailInputType ,
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc input
* @ name input [ radio ]
2016-03-28 10:46:51 +00:00
*
* @ description
* HTML radio button .
*
* @ param { string } ngModel Assignable angular expression to data - bind to .
2016-04-18 12:34:29 +00:00
* @ param { string } value The value to which the expression should be set when selected .
2016-03-28 10:46:51 +00:00
* @ param { string = } name Property name of the form under which the control is published .
* @ param { string = } ngChange Angular expression to be executed when input changes due to user
* interaction with the input element .
2018-05-05 12:13:16 +02:00
* @ param { string } ngValue Angular expression which sets the value to which the expression should
* be set when selected .
2016-03-28 10:46:51 +00:00
*
* @ example
2018-05-05 12:13:16 +02:00
< example name = "radio-input-directive" module = "radioExample" >
< file name = "index.html" >
2016-03-28 10:46:51 +00:00
< script >
2018-05-05 12:13:16 +02:00
angular . module ( 'radioExample' , [ ] )
. controller ( 'ExampleController' , [ '$scope' , function ( $scope ) {
$scope . color = {
name : 'blue'
} ;
$scope . specialValue = {
"id" : "12345" ,
"value" : "green"
} ;
} ] ) ;
2016-03-28 10:46:51 +00:00
< / s c r i p t >
2018-05-05 12:13:16 +02:00
< form name = "myForm" ng - controller = "ExampleController" >
< input type = "radio" ng - model = "color.name" value = "red" > Red < br / >
< input type = "radio" ng - model = "color.name" ng - value = "specialValue" > Green < br / >
< input type = "radio" ng - model = "color.name" value = "blue" > Blue < br / >
< tt > color = { { color . name | json } } < /tt><br/ >
2016-03-28 10:46:51 +00:00
< / f o r m >
2018-05-05 12:13:16 +02:00
Note that ` ng-value="specialValue" ` sets radio item ' s value to be the value of ` $ scope.specialValue ` .
< / f i l e >
< file name = "protractor.js" type = "protractor" >
2016-03-28 10:46:51 +00:00
it ( 'should change state' , function ( ) {
2018-05-05 12:13:16 +02:00
var color = element ( by . binding ( 'color.name' ) ) ;
expect ( color . getText ( ) ) . toContain ( 'blue' ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
element . all ( by . model ( 'color.name' ) ) . get ( 0 ) . click ( ) ;
expect ( color . getText ( ) ) . toContain ( 'red' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
'radio' : radioInputType ,
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc input
* @ name input [ checkbox ]
2016-03-28 10:46:51 +00:00
*
* @ description
* HTML checkbox .
*
* @ param { string } ngModel Assignable angular expression to data - bind to .
* @ param { string = } name Property name of the form under which the control is published .
2018-05-05 12:13:16 +02:00
* @ param { expression = } ngTrueValue The value to which the expression should be set when selected .
* @ param { expression = } ngFalseValue The value to which the expression should be set when not selected .
2016-03-28 10:46:51 +00:00
* @ param { string = } ngChange Angular expression to be executed when input changes due to user
* interaction with the input element .
*
* @ example
2018-05-05 12:13:16 +02:00
< example name = "checkbox-input-directive" module = "checkboxExample" >
< file name = "index.html" >
2016-03-28 10:46:51 +00:00
< script >
2018-05-05 12:13:16 +02:00
angular . module ( 'checkboxExample' , [ ] )
. controller ( 'ExampleController' , [ '$scope' , function ( $scope ) {
$scope . checkboxModel = {
value1 : true ,
value2 : 'YES'
} ;
} ] ) ;
2016-03-28 10:46:51 +00:00
< / s c r i p t >
2018-05-05 12:13:16 +02:00
< form name = "myForm" ng - controller = "ExampleController" >
Value1 : < input type = "checkbox" ng - model = "checkboxModel.value1" > < br / >
Value2 : < input type = "checkbox" ng - model = "checkboxModel.value2"
ng - true - value = "'YES'" ng - false - value = "'NO'" > < br / >
< tt > value1 = { { checkboxModel . value1 } } < /tt><br/ >
< tt > value2 = { { checkboxModel . value2 } } < /tt><br/ >
2016-03-28 10:46:51 +00:00
< / f o r m >
2018-05-05 12:13:16 +02:00
< / f i l e >
< file name = "protractor.js" type = "protractor" >
2016-03-28 10:46:51 +00:00
it ( 'should change state' , function ( ) {
2018-05-05 12:13:16 +02:00
var value1 = element ( by . binding ( 'checkboxModel.value1' ) ) ;
var value2 = element ( by . binding ( 'checkboxModel.value2' ) ) ;
expect ( value1 . getText ( ) ) . toContain ( 'true' ) ;
expect ( value2 . getText ( ) ) . toContain ( 'YES' ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
element ( by . model ( 'checkboxModel.value1' ) ) . click ( ) ;
element ( by . model ( 'checkboxModel.value2' ) ) . click ( ) ;
expect ( value1 . getText ( ) ) . toContain ( 'false' ) ;
expect ( value2 . getText ( ) ) . toContain ( 'NO' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
'checkbox' : checkboxInputType ,
'hidden' : noop ,
'button' : noop ,
'submit' : noop ,
2018-05-05 12:13:16 +02:00
'reset' : noop ,
'file' : noop
2016-03-28 10:46:51 +00:00
} ;
2018-05-05 12:13:16 +02:00
function stringBasedInputType ( ctrl ) {
ctrl . $formatters . push ( function ( value ) {
return ctrl . $isEmpty ( value ) ? value : value . toString ( ) ;
} ) ;
2016-03-28 10:46:51 +00:00
}
function textInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
2018-05-05 12:13:16 +02:00
baseInputType ( scope , element , attr , ctrl , $sniffer , $browser ) ;
stringBasedInputType ( ctrl ) ;
}
function baseInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
var type = lowercase ( element [ 0 ] . type ) ;
2016-03-28 10:46:51 +00:00
// In composition mode, users are still inputing intermediate text buffer,
// hold the listener until composition is done.
// More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent
if ( ! $sniffer . android ) {
var composing = false ;
2016-04-18 12:34:29 +00:00
element . on ( 'compositionstart' , function ( data ) {
2016-03-28 10:46:51 +00:00
composing = true ;
} ) ;
element . on ( 'compositionend' , function ( ) {
composing = false ;
2018-05-05 12:13:16 +02:00
listener ( ) ;
2016-03-28 10:46:51 +00:00
} ) ;
}
2018-05-05 12:13:16 +02:00
var listener = function ( ev ) {
if ( timeout ) {
$browser . defer . cancel ( timeout ) ;
timeout = null ;
}
2016-03-28 10:46:51 +00:00
if ( composing ) return ;
2018-05-05 12:13:16 +02:00
var value = element . val ( ) ,
event = ev && ev . type ;
2016-03-28 10:46:51 +00:00
// By default we will trim the value
// If the attribute ng-trim exists we will avoid trimming
2018-05-05 12:13:16 +02:00
// If input type is 'password', the value is never trimmed
if ( type !== 'password' && ( ! attr . ngTrim || attr . ngTrim !== 'false' ) ) {
2016-03-28 10:46:51 +00:00
value = trim ( value ) ;
}
2018-05-05 12:13:16 +02:00
// If a control is suffering from bad input (due to native validators), browsers discard its
// value, so it may be necessary to revalidate (by calling $setViewValue again) even if the
// control's value is the same empty value twice in a row.
if ( ctrl . $viewValue !== value || ( value === '' && ctrl . $$hasNativeValidators ) ) {
ctrl . $setViewValue ( value , event ) ;
2016-03-28 10:46:51 +00:00
}
} ;
// if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the
// input event on backspace, delete or cut
if ( $sniffer . hasEvent ( 'input' ) ) {
element . on ( 'input' , listener ) ;
} else {
2016-04-18 12:34:29 +00:00
var timeout ;
2018-05-05 12:13:16 +02:00
var deferListener = function ( ev , input , origValue ) {
2016-03-28 10:46:51 +00:00
if ( ! timeout ) {
timeout = $browser . defer ( function ( ) {
timeout = null ;
2018-05-05 12:13:16 +02:00
if ( ! input || input . value !== origValue ) {
listener ( ev ) ;
}
2016-03-28 10:46:51 +00:00
} ) ;
}
} ;
element . on ( 'keydown' , function ( event ) {
var key = event . keyCode ;
// ignore
// command modifiers arrows
if ( key === 91 || ( 15 < key && key < 19 ) || ( 37 <= key && key <= 40 ) ) return ;
2018-05-05 12:13:16 +02:00
deferListener ( event , this , this . value ) ;
2016-03-28 10:46:51 +00:00
} ) ;
// if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it
if ( $sniffer . hasEvent ( 'paste' ) ) {
element . on ( 'paste cut' , deferListener ) ;
}
}
// if user paste into input using mouse on older browser
// or form autocomplete on newer browser, we need "change" event to catch it
element . on ( 'change' , listener ) ;
ctrl . $render = function ( ) {
2016-04-18 12:34:29 +00:00
element . val ( ctrl . $isEmpty ( ctrl . $viewValue ) ? '' : ctrl . $viewValue ) ;
2016-03-28 10:46:51 +00:00
} ;
2018-05-05 12:13:16 +02:00
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
function weekParser ( isoWeek , existingDate ) {
if ( isDate ( isoWeek ) ) {
return isoWeek ;
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
if ( isString ( isoWeek ) ) {
WEEK _REGEXP . lastIndex = 0 ;
var parts = WEEK _REGEXP . exec ( isoWeek ) ;
if ( parts ) {
var year = + parts [ 1 ] ,
week = + parts [ 2 ] ,
hours = 0 ,
minutes = 0 ,
seconds = 0 ,
milliseconds = 0 ,
firstThurs = getFirstThursdayOfYear ( year ) ,
addDays = ( week - 1 ) * 7 ;
if ( existingDate ) {
hours = existingDate . getHours ( ) ;
minutes = existingDate . getMinutes ( ) ;
seconds = existingDate . getSeconds ( ) ;
milliseconds = existingDate . getMilliseconds ( ) ;
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
return new Date ( year , 0 , firstThurs . getDate ( ) + addDays , hours , minutes , seconds , milliseconds ) ;
}
}
return NaN ;
}
function createDateParser ( regexp , mapping ) {
return function ( iso , date ) {
var parts , map ;
if ( isDate ( iso ) ) {
return iso ;
}
if ( isString ( iso ) ) {
// When a date is JSON'ified to wraps itself inside of an extra
// set of double quotes. This makes the date parsing code unable
// to match the date string and parse it as a date.
if ( iso . charAt ( 0 ) == '"' && iso . charAt ( iso . length - 1 ) == '"' ) {
iso = iso . substring ( 1 , iso . length - 1 ) ;
}
if ( ISO _DATE _REGEXP . test ( iso ) ) {
return new Date ( iso ) ;
}
regexp . lastIndex = 0 ;
parts = regexp . exec ( iso ) ;
if ( parts ) {
parts . shift ( ) ;
if ( date ) {
map = {
yyyy : date . getFullYear ( ) ,
MM : date . getMonth ( ) + 1 ,
dd : date . getDate ( ) ,
HH : date . getHours ( ) ,
mm : date . getMinutes ( ) ,
ss : date . getSeconds ( ) ,
sss : date . getMilliseconds ( ) / 1000
} ;
} else {
map = { yyyy : 1970 , MM : 1 , dd : 1 , HH : 0 , mm : 0 , ss : 0 , sss : 0 } ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
forEach ( parts , function ( part , index ) {
if ( index < mapping . length ) {
map [ mapping [ index ] ] = + part ;
}
} ) ;
return new Date ( map . yyyy , map . MM - 1 , map . dd , map . HH , map . mm , map . ss || 0 , map . sss * 1000 || 0 ) ;
}
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
return NaN ;
} ;
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
function createDateInputType ( type , regexp , parseDate , format ) {
return function dynamicDateInputType ( scope , element , attr , ctrl , $sniffer , $browser , $filter ) {
badInputChecker ( scope , element , attr , ctrl ) ;
baseInputType ( scope , element , attr , ctrl , $sniffer , $browser ) ;
var timezone = ctrl && ctrl . $options && ctrl . $options . timezone ;
var previousDate ;
ctrl . $$parserName = type ;
ctrl . $parsers . push ( function ( value ) {
if ( ctrl . $isEmpty ( value ) ) return null ;
if ( regexp . test ( value ) ) {
// Note: We cannot read ctrl.$modelValue, as there might be a different
// parser/formatter in the processing chain so that the model
// contains some different data format!
var parsedDate = parseDate ( value , previousDate ) ;
if ( timezone === 'UTC' ) {
parsedDate . setMinutes ( parsedDate . getMinutes ( ) - parsedDate . getTimezoneOffset ( ) ) ;
}
return parsedDate ;
}
return undefined ;
} ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
ctrl . $formatters . push ( function ( value ) {
if ( value && ! isDate ( value ) ) {
throw ngModelMinErr ( 'datefmt' , 'Expected `{0}` to be a date' , value ) ;
}
if ( isValidDate ( value ) ) {
previousDate = value ;
if ( previousDate && timezone === 'UTC' ) {
var timezoneOffset = 60000 * previousDate . getTimezoneOffset ( ) ;
previousDate = new Date ( previousDate . getTime ( ) + timezoneOffset ) ;
}
return $filter ( 'date' ) ( value , format , timezone ) ;
} else {
previousDate = null ;
return '' ;
}
} ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
if ( isDefined ( attr . min ) || attr . ngMin ) {
var minVal ;
ctrl . $validators . min = function ( value ) {
return ! isValidDate ( value ) || isUndefined ( minVal ) || parseDate ( value ) >= minVal ;
} ;
attr . $observe ( 'min' , function ( val ) {
minVal = parseObservedDateValue ( val ) ;
ctrl . $validate ( ) ;
} ) ;
}
if ( isDefined ( attr . max ) || attr . ngMax ) {
var maxVal ;
ctrl . $validators . max = function ( value ) {
return ! isValidDate ( value ) || isUndefined ( maxVal ) || parseDate ( value ) <= maxVal ;
} ;
attr . $observe ( 'max' , function ( val ) {
maxVal = parseObservedDateValue ( val ) ;
ctrl . $validate ( ) ;
} ) ;
}
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
function isValidDate ( value ) {
// Invalid Date: getTime() returns NaN
return value && ! ( value . getTime && value . getTime ( ) !== value . getTime ( ) ) ;
}
function parseObservedDateValue ( val ) {
return isDefined ( val ) ? ( isDate ( val ) ? val : parseDate ( val ) ) : undefined ;
}
} ;
}
function badInputChecker ( scope , element , attr , ctrl ) {
var node = element [ 0 ] ;
var nativeValidation = ctrl . $$hasNativeValidators = isObject ( node . validity ) ;
if ( nativeValidation ) {
ctrl . $parsers . push ( function ( value ) {
var validity = element . prop ( VALIDITY _STATE _PROPERTY ) || { } ;
// Detect bug in FF35 for input[email] (https://bugzilla.mozilla.org/show_bug.cgi?id=1064430):
// - also sets validity.badInput (should only be validity.typeMismatch).
// - see http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#e-mail-state-(type=email)
// - can ignore this case as we can still read out the erroneous email...
return validity . badInput && ! validity . typeMismatch ? undefined : value ;
} ) ;
2016-03-28 10:46:51 +00:00
}
}
function numberInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
2018-05-05 12:13:16 +02:00
badInputChecker ( scope , element , attr , ctrl ) ;
baseInputType ( scope , element , attr , ctrl , $sniffer , $browser ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
ctrl . $$parserName = 'number' ;
2016-03-28 10:46:51 +00:00
ctrl . $parsers . push ( function ( value ) {
2018-05-05 12:13:16 +02:00
if ( ctrl . $isEmpty ( value ) ) return null ;
if ( NUMBER _REGEXP . test ( value ) ) return parseFloat ( value ) ;
return undefined ;
2016-03-28 10:46:51 +00:00
} ) ;
ctrl . $formatters . push ( function ( value ) {
2018-05-05 12:13:16 +02:00
if ( ! ctrl . $isEmpty ( value ) ) {
if ( ! isNumber ( value ) ) {
throw ngModelMinErr ( 'numfmt' , 'Expected `{0}` to be a number' , value ) ;
}
value = value . toString ( ) ;
}
return value ;
2016-03-28 10:46:51 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
if ( isDefined ( attr . min ) || attr . ngMin ) {
var minVal ;
ctrl . $validators . min = function ( value ) {
return ctrl . $isEmpty ( value ) || isUndefined ( minVal ) || value >= minVal ;
2016-03-28 10:46:51 +00:00
} ;
2018-05-05 12:13:16 +02:00
attr . $observe ( 'min' , function ( val ) {
if ( isDefined ( val ) && ! isNumber ( val ) ) {
val = parseFloat ( val , 10 ) ;
}
minVal = isNumber ( val ) && ! isNaN ( val ) ? val : undefined ;
// TODO(matsko): implement validateLater to reduce number of validations
ctrl . $validate ( ) ;
} ) ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
if ( isDefined ( attr . max ) || attr . ngMax ) {
var maxVal ;
ctrl . $validators . max = function ( value ) {
return ctrl . $isEmpty ( value ) || isUndefined ( maxVal ) || value <= maxVal ;
2016-03-28 10:46:51 +00:00
} ;
2018-05-05 12:13:16 +02:00
attr . $observe ( 'max' , function ( val ) {
if ( isDefined ( val ) && ! isNumber ( val ) ) {
val = parseFloat ( val , 10 ) ;
}
maxVal = isNumber ( val ) && ! isNaN ( val ) ? val : undefined ;
// TODO(matsko): implement validateLater to reduce number of validations
ctrl . $validate ( ) ;
} ) ;
2016-03-28 10:46:51 +00:00
}
}
function urlInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
2018-05-05 12:13:16 +02:00
// Note: no badInputChecker here by purpose as `url` is only a validation
// in browsers, i.e. we can always read out input.value even if it is not valid!
baseInputType ( scope , element , attr , ctrl , $sniffer , $browser ) ;
stringBasedInputType ( ctrl ) ;
ctrl . $$parserName = 'url' ;
ctrl . $validators . url = function ( modelValue , viewValue ) {
var value = modelValue || viewValue ;
return ctrl . $isEmpty ( value ) || URL _REGEXP . test ( value ) ;
2016-03-28 10:46:51 +00:00
} ;
}
function emailInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
2018-05-05 12:13:16 +02:00
// Note: no badInputChecker here by purpose as `url` is only a validation
// in browsers, i.e. we can always read out input.value even if it is not valid!
baseInputType ( scope , element , attr , ctrl , $sniffer , $browser ) ;
stringBasedInputType ( ctrl ) ;
ctrl . $$parserName = 'email' ;
ctrl . $validators . email = function ( modelValue , viewValue ) {
var value = modelValue || viewValue ;
return ctrl . $isEmpty ( value ) || EMAIL _REGEXP . test ( value ) ;
2016-03-28 10:46:51 +00:00
} ;
}
function radioInputType ( scope , element , attr , ctrl ) {
// make the name unique, if not defined
if ( isUndefined ( attr . name ) ) {
element . attr ( 'name' , nextUid ( ) ) ;
}
2018-05-05 12:13:16 +02:00
var listener = function ( ev ) {
2016-03-28 10:46:51 +00:00
if ( element [ 0 ] . checked ) {
2018-05-05 12:13:16 +02:00
ctrl . $setViewValue ( attr . value , ev && ev . type ) ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
} ;
element . on ( 'click' , listener ) ;
2016-03-28 10:46:51 +00:00
ctrl . $render = function ( ) {
var value = attr . value ;
element [ 0 ] . checked = ( value == ctrl . $viewValue ) ;
} ;
attr . $observe ( 'value' , ctrl . $render ) ;
}
2018-05-05 12:13:16 +02:00
function parseConstantExpr ( $parse , context , name , expression , fallback ) {
var parseFn ;
if ( isDefined ( expression ) ) {
parseFn = $parse ( expression ) ;
if ( ! parseFn . constant ) {
throw ngModelMinErr ( 'constexpr' , 'Expected constant expression for `{0}`, but saw ' +
'`{1}`.' , name , expression ) ;
}
return parseFn ( context ) ;
}
return fallback ;
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
function checkboxInputType ( scope , element , attr , ctrl , $sniffer , $browser , $filter , $parse ) {
var trueValue = parseConstantExpr ( $parse , scope , 'ngTrueValue' , attr . ngTrueValue , true ) ;
var falseValue = parseConstantExpr ( $parse , scope , 'ngFalseValue' , attr . ngFalseValue , false ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
var listener = function ( ev ) {
ctrl . $setViewValue ( element [ 0 ] . checked , ev && ev . type ) ;
} ;
element . on ( 'click' , listener ) ;
2016-03-28 10:46:51 +00:00
ctrl . $render = function ( ) {
element [ 0 ] . checked = ctrl . $viewValue ;
} ;
2018-05-05 12:13:16 +02:00
// Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false`
// This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert
// it to a boolean.
2016-03-28 10:46:51 +00:00
ctrl . $isEmpty = function ( value ) {
2018-05-05 12:13:16 +02:00
return value === false ;
2016-03-28 10:46:51 +00:00
} ;
ctrl . $formatters . push ( function ( value ) {
2018-05-05 12:13:16 +02:00
return equals ( value , trueValue ) ;
2016-03-28 10:46:51 +00:00
} ) ;
ctrl . $parsers . push ( function ( value ) {
return value ? trueValue : falseValue ;
} ) ;
}
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name textarea
2016-03-28 10:46:51 +00:00
* @ restrict E
*
* @ description
* HTML textarea element control with angular data - binding . The data - binding and validation
* properties of this element are exactly the same as those of the
* { @ link ng . directive : input input element } .
*
* @ param { string } ngModel Assignable angular expression to data - bind to .
* @ param { string = } name Property name of the form under which the control is published .
* @ param { string = } required Sets ` required ` validation error key if the value is not entered .
* @ param { string = } ngRequired Adds ` required ` attribute and ` required ` validation constraint to
* the element when the ngRequired expression evaluates to true . Use ` ngRequired ` instead of
* ` required ` when you want to data - bind to the ` required ` attribute .
* @ param { number = } ngMinlength Sets ` minlength ` validation error key if the value is shorter than
* minlength .
* @ param { number = } ngMaxlength Sets ` maxlength ` validation error key if the value is longer than
2018-05-05 12:13:16 +02:00
* maxlength . Setting the attribute to a negative or non - numeric value , allows view values of any
* length .
2016-04-18 12:34:29 +00:00
* @ param { string = } ngPattern Sets ` pattern ` validation error key if the value does not match the
* RegExp pattern expression . Expected value is ` /regexp/ ` for inline patterns or ` regexp ` for
* patterns defined as scope expressions .
2016-03-28 10:46:51 +00:00
* @ param { string = } ngChange Angular expression to be executed when input changes due to user
* interaction with the input element .
2018-05-05 12:13:16 +02:00
* @ param { boolean = } [ ngTrim = true ] If set to false Angular will not automatically trim the input .
2016-03-28 10:46:51 +00:00
* /
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name input
2016-03-28 10:46:51 +00:00
* @ restrict E
*
* @ description
2018-05-05 12:13:16 +02:00
* HTML input element control . When used together with { @ link ngModel ` ngModel ` } , it provides data - binding ,
* input state control , and validation .
* Input control follows HTML5 input types and polyfills the HTML5 validation behavior for older browsers .
*
* < div class = "alert alert-warning" >
* * * Note : * * Not every feature offered is available for all input types .
* Specifically , data binding and event handling via ` ng-model ` is unsupported for ` input[file] ` .
* < / d i v >
2016-03-28 10:46:51 +00:00
*
* @ param { string } ngModel Assignable angular expression to data - bind to .
* @ param { string = } name Property name of the form under which the control is published .
* @ param { string = } required Sets ` required ` validation error key if the value is not entered .
* @ param { boolean = } ngRequired Sets ` required ` attribute if set to true
* @ param { number = } ngMinlength Sets ` minlength ` validation error key if the value is shorter than
* minlength .
* @ param { number = } ngMaxlength Sets ` maxlength ` validation error key if the value is longer than
2018-05-05 12:13:16 +02:00
* maxlength . Setting the attribute to a negative or non - numeric value , allows view values of any
* length .
2016-04-18 12:34:29 +00:00
* @ param { string = } ngPattern Sets ` pattern ` validation error key if the value does not match the
* RegExp pattern expression . Expected value is ` /regexp/ ` for inline patterns or ` regexp ` for
* patterns defined as scope expressions .
2016-03-28 10:46:51 +00:00
* @ param { string = } ngChange Angular expression to be executed when input changes due to user
* interaction with the input element .
2018-05-05 12:13:16 +02:00
* @ param { boolean = } [ ngTrim = true ] If set to false Angular will not automatically trim the input .
* This parameter is ignored for input [ type = password ] controls , which will never trim the
* input .
2016-03-28 10:46:51 +00:00
*
* @ example
2018-05-05 12:13:16 +02:00
< example name = "input-directive" module = "inputExample" >
< file name = "index.html" >
2016-03-28 10:46:51 +00:00
< script >
2018-05-05 12:13:16 +02:00
angular . module ( 'inputExample' , [ ] )
. controller ( 'ExampleController' , [ '$scope' , function ( $scope ) {
$scope . user = { name : 'guest' , last : 'visitor' } ;
} ] ) ;
2016-03-28 10:46:51 +00:00
< / s c r i p t >
2018-05-05 12:13:16 +02:00
< div ng - controller = "ExampleController" >
2016-03-28 10:46:51 +00:00
< form name = "myForm" >
2016-04-18 12:34:29 +00:00
User name : < input type = "text" name = "userName" ng - model = "user.name" required >
< span class = "error" ng - show = "myForm.userName.$error.required" >
Required ! < / s p a n > < b r >
Last name : < input type = "text" name = "lastName" ng - model = "user.last"
ng - minlength = "3" ng - maxlength = "10" >
< span class = "error" ng - show = "myForm.lastName.$error.minlength" >
Too short ! < / s p a n >
< span class = "error" ng - show = "myForm.lastName.$error.maxlength" >
Too long ! < / s p a n > < b r >
2016-03-28 10:46:51 +00:00
< / f o r m >
< hr >
< tt > user = { { user } } < /tt><br/ >
2016-04-18 12:34:29 +00:00
< tt > myForm . userName . $valid = { { myForm . userName . $valid } } < / t t > < b r >
< tt > myForm . userName . $error = { { myForm . userName . $error } } < / t t > < b r >
< tt > myForm . lastName . $valid = { { myForm . lastName . $valid } } < / t t > < b r >
< tt > myForm . lastName . $error = { { myForm . lastName . $error } } < / t t > < b r >
< tt > myForm . $valid = { { myForm . $valid } } < / t t > < b r >
< tt > myForm . $error . required = { { ! ! myForm . $error . required } } < / t t > < b r >
< tt > myForm . $error . minlength = { { ! ! myForm . $error . minlength } } < / t t > < b r >
< tt > myForm . $error . maxlength = { { ! ! myForm . $error . maxlength } } < / t t > < b r >
2016-03-28 10:46:51 +00:00
< / d i v >
2018-05-05 12:13:16 +02:00
< / f i l e >
< file name = "protractor.js" type = "protractor" >
var user = element ( by . exactBinding ( 'user' ) ) ;
var userNameValid = element ( by . binding ( 'myForm.userName.$valid' ) ) ;
var lastNameValid = element ( by . binding ( 'myForm.lastName.$valid' ) ) ;
var lastNameError = element ( by . binding ( 'myForm.lastName.$error' ) ) ;
var formValid = element ( by . binding ( 'myForm.$valid' ) ) ;
var userNameInput = element ( by . model ( 'user.name' ) ) ;
var userLastInput = element ( by . model ( 'user.last' ) ) ;
2016-03-28 10:46:51 +00:00
it ( 'should initialize to model' , function ( ) {
2018-05-05 12:13:16 +02:00
expect ( user . getText ( ) ) . toContain ( '{"name":"guest","last":"visitor"}' ) ;
expect ( userNameValid . getText ( ) ) . toContain ( 'true' ) ;
expect ( formValid . getText ( ) ) . toContain ( 'true' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should be invalid if empty when required' , function ( ) {
2018-05-05 12:13:16 +02:00
userNameInput . clear ( ) ;
userNameInput . sendKeys ( '' ) ;
expect ( user . getText ( ) ) . toContain ( '{"last":"visitor"}' ) ;
expect ( userNameValid . getText ( ) ) . toContain ( 'false' ) ;
expect ( formValid . getText ( ) ) . toContain ( 'false' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should be valid if empty when min length is set' , function ( ) {
2018-05-05 12:13:16 +02:00
userLastInput . clear ( ) ;
userLastInput . sendKeys ( '' ) ;
expect ( user . getText ( ) ) . toContain ( '{"name":"guest","last":""}' ) ;
expect ( lastNameValid . getText ( ) ) . toContain ( 'true' ) ;
expect ( formValid . getText ( ) ) . toContain ( 'true' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should be invalid if less than required min length' , function ( ) {
2018-05-05 12:13:16 +02:00
userLastInput . clear ( ) ;
userLastInput . sendKeys ( 'xx' ) ;
expect ( user . getText ( ) ) . toContain ( '{"name":"guest"}' ) ;
expect ( lastNameValid . getText ( ) ) . toContain ( 'false' ) ;
expect ( lastNameError . getText ( ) ) . toContain ( 'minlength' ) ;
expect ( formValid . getText ( ) ) . toContain ( 'false' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should be invalid if longer than max length' , function ( ) {
2018-05-05 12:13:16 +02:00
userLastInput . clear ( ) ;
userLastInput . sendKeys ( 'some ridiculously long name' ) ;
expect ( user . getText ( ) ) . toContain ( '{"name":"guest"}' ) ;
expect ( lastNameValid . getText ( ) ) . toContain ( 'false' ) ;
expect ( lastNameError . getText ( ) ) . toContain ( 'maxlength' ) ;
expect ( formValid . getText ( ) ) . toContain ( 'false' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
2018-05-05 12:13:16 +02:00
var inputDirective = [ '$browser' , '$sniffer' , '$filter' , '$parse' ,
function ( $browser , $sniffer , $filter , $parse ) {
2016-03-28 10:46:51 +00:00
return {
restrict : 'E' ,
2018-05-05 12:13:16 +02:00
require : [ '?ngModel' ] ,
link : {
pre : function ( scope , element , attr , ctrls ) {
if ( ctrls [ 0 ] ) {
( inputType [ lowercase ( attr . type ) ] || inputType . text ) ( scope , element , attr , ctrls [ 0 ] , $sniffer ,
$browser , $filter , $parse ) ;
}
2016-03-28 10:46:51 +00:00
}
}
} ;
} ] ;
2018-05-05 12:13:16 +02:00
var CONSTANT _VALUE _REGEXP = /^(true|false|\d+)$/ ;
2016-03-28 10:46:51 +00:00
/ * *
2018-05-05 12:13:16 +02:00
* @ ngdoc directive
* @ name ngValue
2016-04-18 12:34:29 +00:00
*
2018-05-05 12:13:16 +02:00
* @ description
* Binds the given expression to the value of ` <option> ` or { @ link input [ radio ] ` input[radio] ` } ,
* so that when the element is selected , the { @ link ngModel ` ngModel ` } of that element is set to
* the bound value .
2016-04-18 12:34:29 +00:00
*
2018-05-05 12:13:16 +02:00
* ` ngValue ` is useful when dynamically generating lists of radio buttons using
* { @ link ngRepeat ` ngRepeat ` } , as shown below .
2016-04-18 12:34:29 +00:00
*
2018-05-05 12:13:16 +02:00
* Likewise , ` ngValue ` can be used to generate ` <option> ` elements for
* the { @ link select ` select ` } element . In that case however , only strings are supported
* for the ` value ` attribute , so the resulting ` ngModel ` will always be a string .
* Support for ` select ` models with non - string values is available via ` ngOptions ` .
2016-04-18 12:34:29 +00:00
*
2018-05-05 12:13:16 +02:00
* @ element input
* @ param { string = } ngValue angular expression , whose value will be bound to the ` value ` attribute
* of the ` input ` element
2016-04-18 12:34:29 +00:00
*
2018-05-05 12:13:16 +02:00
* @ example
< example name = "ngValue-directive" module = "valueExample" >
< file name = "index.html" >
< script >
angular . module ( 'valueExample' , [ ] )
. controller ( 'ExampleController' , [ '$scope' , function ( $scope ) {
$scope . names = [ 'pizza' , 'unicorns' , 'robots' ] ;
$scope . my = { favorite : 'unicorns' } ;
} ] ) ;
< / s c r i p t >
< form ng - controller = "ExampleController" >
< h2 > Which is your favorite ? < / h 2 >
< label ng - repeat = "name in names" for = "{{name}}" >
{ { name } }
< input type = "radio"
ng - model = "my.favorite"
ng - value = "name"
id = "{{name}}"
name = "favorite" >
< / l a b e l >
< div > You chose { { my . favorite } } < / d i v >
< / f o r m >
< / f i l e >
< file name = "protractor.js" type = "protractor" >
var favorite = element ( by . binding ( 'my.favorite' ) ) ;
it ( 'should initialize to model' , function ( ) {
expect ( favorite . getText ( ) ) . toContain ( 'unicorns' ) ;
} ) ;
it ( 'should bind the values to the inputs' , function ( ) {
element . all ( by . model ( 'my.favorite' ) ) . get ( 0 ) . click ( ) ;
expect ( favorite . getText ( ) ) . toContain ( 'pizza' ) ;
} ) ;
< / f i l e >
< / e x a m p l e >
* /
var ngValueDirective = function ( ) {
return {
restrict : 'A' ,
priority : 100 ,
compile : function ( tpl , tplAttr ) {
if ( CONSTANT _VALUE _REGEXP . test ( tplAttr . ngValue ) ) {
return function ngValueConstantLink ( scope , elm , attr ) {
attr . $set ( 'value' , scope . $eval ( attr . ngValue ) ) ;
} ;
} else {
return function ngValueLink ( scope , elm , attr ) {
scope . $watch ( attr . ngValue , function valueWatchAction ( value ) {
attr . $set ( 'value' , value ) ;
} ) ;
} ;
}
}
} ;
} ;
/ * *
* @ ngdoc directive
* @ name ngBind
* @ restrict AC
2016-03-28 10:46:51 +00:00
*
* @ description
2018-05-05 12:13:16 +02:00
* The ` ngBind ` attribute tells Angular to replace the text content of the specified HTML element
* with the value of a given expression , and to update the text content when the value of that
* expression changes .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* Typically , you don ' t use ` ngBind ` directly , but instead you use the double curly markup like
* ` {{ expression }} ` which is similar but less verbose .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* It is preferable to use ` ngBind ` instead of ` {{ expression }} ` if a template is momentarily
* displayed by the browser in its raw state before Angular compiles it . Since ` ngBind ` is an
* element attribute , it makes the bindings invisible to the user while the page is loading .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* An alternative solution to this problem would be using the
* { @ link ng . directive : ngCloak ngCloak } directive .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
*
* @ element ANY
* @ param { expression } ngBind { @ link guide / expression Expression } to evaluate .
*
* @ example
* Enter a name in the Live Preview text box ; the greeting below the text box changes instantly .
< example module = "bindExample" >
< file name = "index.html" >
< script >
angular . module ( 'bindExample' , [ ] )
. controller ( 'ExampleController' , [ '$scope' , function ( $scope ) {
$scope . name = 'Whirled' ;
} ] ) ;
< / s c r i p t >
< div ng - controller = "ExampleController" >
Enter name : < input type = "text" ng - model = "name" > < br >
Hello < span ng - bind = "name" > < / s p a n > !
< / d i v >
< / f i l e >
< file name = "protractor.js" type = "protractor" >
it ( 'should check ng-bind' , function ( ) {
var nameInput = element ( by . model ( 'name' ) ) ;
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
expect ( element ( by . binding ( 'name' ) ) . getText ( ) ) . toBe ( 'Whirled' ) ;
nameInput . clear ( ) ;
nameInput . sendKeys ( 'world' ) ;
expect ( element ( by . binding ( 'name' ) ) . getText ( ) ) . toBe ( 'world' ) ;
} ) ;
< / f i l e >
< / e x a m p l e >
* /
var ngBindDirective = [ '$compile' , function ( $compile ) {
return {
restrict : 'AC' ,
compile : function ngBindCompile ( templateElement ) {
$compile . $$addBindingClass ( templateElement ) ;
return function ngBindLink ( scope , element , attr ) {
$compile . $$addBindingInfo ( element , attr . ngBind ) ;
element = element [ 0 ] ;
scope . $watch ( attr . ngBind , function ngBindWatchAction ( value ) {
element . textContent = value === undefined ? '' : value ;
} ) ;
} ;
}
} ;
} ] ;
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
/ * *
* @ ngdoc directive
* @ name ngBindTemplate
2016-04-18 12:34:29 +00:00
*
2018-05-05 12:13:16 +02:00
* @ description
* The ` ngBindTemplate ` directive specifies that the element
* text content should be replaced with the interpolation of the template
* in the ` ngBindTemplate ` attribute .
* Unlike ` ngBind ` , the ` ngBindTemplate ` can contain multiple ` {{ ` ` }} `
* expressions . This directive is needed since some HTML elements
* ( such as TITLE and OPTION ) cannot contain SPAN elements .
2016-04-18 12:34:29 +00:00
*
2018-05-05 12:13:16 +02:00
* @ element ANY
* @ param { string } ngBindTemplate template of form
* < tt > { { < / t t > < t t > e x p r e s s i o n < / t t > < t t > } } < / t t > t o e v a l .
*
* @ example
* Try it here : enter text in text box and watch the greeting change .
< example module = "bindExample" >
< file name = "index.html" >
< script >
angular . module ( 'bindExample' , [ ] )
. controller ( 'ExampleController' , [ '$scope' , function ( $scope ) {
$scope . salutation = 'Hello' ;
$scope . name = 'World' ;
} ] ) ;
< / s c r i p t >
< div ng - controller = "ExampleController" >
Salutation : < input type = "text" ng - model = "salutation" > < br >
Name : < input type = "text" ng - model = "name" > < br >
< pre ng - bind - template = "{{salutation}} {{name}}!" > < / p r e >
< / d i v >
< / f i l e >
< file name = "protractor.js" type = "protractor" >
it ( 'should check ng-bind' , function ( ) {
var salutationElem = element ( by . binding ( 'salutation' ) ) ;
var salutationInput = element ( by . model ( 'salutation' ) ) ;
var nameInput = element ( by . model ( 'name' ) ) ;
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
expect ( salutationElem . getText ( ) ) . toBe ( 'Hello World!' ) ;
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
salutationInput . clear ( ) ;
salutationInput . sendKeys ( 'Greetings' ) ;
nameInput . clear ( ) ;
nameInput . sendKeys ( 'user' ) ;
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
expect ( salutationElem . getText ( ) ) . toBe ( 'Greetings user!' ) ;
} ) ;
< / f i l e >
< / e x a m p l e >
* /
var ngBindTemplateDirective = [ '$interpolate' , '$compile' , function ( $interpolate , $compile ) {
return {
compile : function ngBindTemplateCompile ( templateElement ) {
$compile . $$addBindingClass ( templateElement ) ;
return function ngBindTemplateLink ( scope , element , attr ) {
var interpolateFn = $interpolate ( element . attr ( attr . $attr . ngBindTemplate ) ) ;
$compile . $$addBindingInfo ( element , interpolateFn . expressions ) ;
element = element [ 0 ] ;
attr . $observe ( 'ngBindTemplate' , function ( value ) {
element . textContent = value === undefined ? '' : value ;
} ) ;
} ;
}
2016-04-18 12:34:29 +00:00
} ;
2018-05-05 12:13:16 +02:00
} ] ;
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
/ * *
* @ ngdoc directive
* @ name ngBindHtml
*
* @ description
* Evaluates the expression and inserts the resulting HTML into the element in a secure way . By default ,
* the resulting HTML content will be sanitized using the { @ link ngSanitize . $sanitize $sanitize } service .
* To utilize this functionality , ensure that ` $ sanitize ` is available , for example , by including { @ link
* ngSanitize } in your module ' s dependencies ( not in core Angular ) . In order to use { @ link ngSanitize }
* in your module ' s dependencies , you need to include "angular-sanitize.js" in your application .
*
* You may also bypass sanitization for values you know are safe . To do so , bind to
* an explicitly trusted value via { @ link ng . $sce # trustAsHtml $sce . trustAsHtml } . See the example
* under { @ link ng . $sce # show - me - an - example - using - sce - Strict Contextual Escaping ( SCE ) } .
*
* Note : If a ` $ sanitize ` service is unavailable and the bound value isn ' t explicitly trusted , you
* will have an exception ( instead of an exploit . )
*
* @ element ANY
* @ param { expression } ngBindHtml { @ link guide / expression Expression } to evaluate .
*
* @ example
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
< example module = "bindHtmlExample" deps = "angular-sanitize.js" >
< file name = "index.html" >
< div ng - controller = "ExampleController" >
< p ng - bind - html = "myHTML" > < / p >
< / d i v >
< / f i l e >
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
< file name = "script.js" >
angular . module ( 'bindHtmlExample' , [ 'ngSanitize' ] )
. controller ( 'ExampleController' , [ '$scope' , function ( $scope ) {
$scope . myHTML =
'I am an <code>HTML</code>string with ' +
'<a href="#">links!</a> and other <em>stuff</em>' ;
} ] ) ;
< / f i l e >
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
< file name = "protractor.js" type = "protractor" >
it ( 'should check ng-bind-html' , function ( ) {
expect ( element ( by . binding ( 'myHTML' ) ) . getText ( ) ) . toBe (
'I am an HTMLstring with links! and other stuff' ) ;
} ) ;
< / f i l e >
< / e x a m p l e >
* /
var ngBindHtmlDirective = [ '$sce' , '$parse' , '$compile' , function ( $sce , $parse , $compile ) {
return {
restrict : 'A' ,
compile : function ngBindHtmlCompile ( tElement , tAttrs ) {
var ngBindHtmlGetter = $parse ( tAttrs . ngBindHtml ) ;
var ngBindHtmlWatch = $parse ( tAttrs . ngBindHtml , function getStringValue ( value ) {
return ( value || '' ) . toString ( ) ;
2016-04-18 12:34:29 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
$compile . $$addBindingClass ( tElement ) ;
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
return function ngBindHtmlLink ( scope , element , attr ) {
$compile . $$addBindingInfo ( element , attr . ngBindHtml ) ;
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
scope . $watch ( ngBindHtmlWatch , function ngBindHtmlWatchAction ( ) {
// we re-evaluate the expr because we want a TrustedValueHolderType
// for $sce, not a string
element . html ( $sce . getTrustedHtml ( ngBindHtmlGetter ( scope ) ) || '' ) ;
} ) ;
} ;
2016-04-18 12:34:29 +00:00
}
2018-05-05 12:13:16 +02:00
} ;
2016-04-18 12:34:29 +00:00
} ] ;
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngChange
2016-03-28 10:46:51 +00:00
*
* @ description
2018-05-05 12:13:16 +02:00
* Evaluate the given expression when the user changes the input .
* The expression is evaluated immediately , unlike the JavaScript onchange event
* which only triggers at the end of a change ( usually , when the user leaves the
* form element or presses the return key ) .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* The ` ngChange ` expression is only evaluated when a change in the input value causes
* a new value to be committed to the model .
2016-04-18 12:34:29 +00:00
*
2018-05-05 12:13:16 +02:00
* It will not be evaluated :
* * if the value returned from the ` $ parsers ` transformation pipeline has not changed
* * if the input has continued to be invalid since the model will stay ` null `
* * if the model is changed programmatically and not by a change to the input value
2016-03-28 10:46:51 +00:00
*
*
2016-04-18 12:34:29 +00:00
* Note , this directive requires ` ngModel ` to be present .
*
* @ element input
* @ param { expression } ngChange { @ link guide / expression Expression } to evaluate upon change
* in input value .
2016-03-28 10:46:51 +00:00
*
* @ example
2018-05-05 12:13:16 +02:00
* < example name = "ngChange-directive" module = "changeExample" >
* < file name = "index.html" >
2016-03-28 10:46:51 +00:00
* < script >
2018-05-05 12:13:16 +02:00
* angular . module ( 'changeExample' , [ ] )
* . controller ( 'ExampleController' , [ '$scope' , function ( $scope ) {
* $scope . counter = 0 ;
* $scope . change = function ( ) {
* $scope . counter ++ ;
* } ;
* } ] ) ;
2016-03-28 10:46:51 +00:00
* < / s c r i p t >
2018-05-05 12:13:16 +02:00
* < div ng - controller = "ExampleController" >
2016-03-28 10:46:51 +00:00
* < input type = "checkbox" ng - model = "confirmed" ng - change = "change()" id = "ng-change-example1" / >
* < input type = "checkbox" ng - model = "confirmed" id = "ng-change-example2" / >
* < label for = "ng-change-example2" > Confirmed < /label><br / >
2018-05-05 12:13:16 +02:00
* < tt > debug = { { confirmed } } < /tt><br/ >
* < tt > counter = { { counter } } < /tt><br/ >
2016-03-28 10:46:51 +00:00
* < / d i v >
2018-05-05 12:13:16 +02:00
* < / f i l e >
* < file name = "protractor.js" type = "protractor" >
* var counter = element ( by . binding ( 'counter' ) ) ;
* var debug = element ( by . binding ( 'confirmed' ) ) ;
*
2016-03-28 10:46:51 +00:00
* it ( 'should evaluate the expression if changing from view' , function ( ) {
2018-05-05 12:13:16 +02:00
* expect ( counter . getText ( ) ) . toContain ( '0' ) ;
*
* element ( by . id ( 'ng-change-example1' ) ) . click ( ) ;
*
* expect ( counter . getText ( ) ) . toContain ( '1' ) ;
* expect ( debug . getText ( ) ) . toContain ( 'true' ) ;
2016-03-28 10:46:51 +00:00
* } ) ;
*
* it ( 'should not evaluate the expression if changing from model' , function ( ) {
2018-05-05 12:13:16 +02:00
* element ( by . id ( 'ng-change-example2' ) ) . click ( ) ;
* expect ( counter . getText ( ) ) . toContain ( '0' ) ;
* expect ( debug . getText ( ) ) . toContain ( 'true' ) ;
2016-03-28 10:46:51 +00:00
* } ) ;
2018-05-05 12:13:16 +02:00
* < / f i l e >
* < / e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
var ngChangeDirective = valueFn ( {
2018-05-05 12:13:16 +02:00
restrict : 'A' ,
2016-03-28 10:46:51 +00:00
require : 'ngModel' ,
link : function ( scope , element , attr , ctrl ) {
ctrl . $viewChangeListeners . push ( function ( ) {
scope . $eval ( attr . ngChange ) ;
} ) ;
}
} ) ;
2018-05-05 12:13:16 +02:00
function classDirective ( name , selector ) {
name = 'ngClass' + name ;
return [ '$animate' , function ( $animate ) {
return {
restrict : 'AC' ,
link : function ( scope , element , attr ) {
var oldVal ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
scope . $watch ( attr [ name ] , ngClassWatchAction , true ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
attr . $observe ( 'class' , function ( value ) {
ngClassWatchAction ( scope . $eval ( attr [ name ] ) ) ;
2016-04-18 12:34:29 +00:00
} ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
if ( name !== 'ngClass' ) {
scope . $watch ( '$index' , function ( $index , old$index ) {
// jshint bitwise: false
var mod = $index & 1 ;
if ( mod !== ( old$index & 1 ) ) {
var classes = arrayClasses ( scope . $eval ( attr [ name ] ) ) ;
mod === selector ?
addClasses ( classes ) :
removeClasses ( classes ) ;
}
} ) ;
}
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
function addClasses ( classes ) {
var newClasses = digestClassCounts ( classes , 1 ) ;
attr . $addClass ( newClasses ) ;
}
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
function removeClasses ( classes ) {
var newClasses = digestClassCounts ( classes , - 1 ) ;
attr . $removeClass ( newClasses ) ;
}
function digestClassCounts ( classes , count ) {
var classCounts = element . data ( '$classCounts' ) || { } ;
var classesToUpdate = [ ] ;
forEach ( classes , function ( className ) {
if ( count > 0 || classCounts [ className ] ) {
classCounts [ className ] = ( classCounts [ className ] || 0 ) + count ;
if ( classCounts [ className ] === + ( count > 0 ) ) {
classesToUpdate . push ( className ) ;
}
}
2016-03-28 10:46:51 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
element . data ( '$classCounts' , classCounts ) ;
return classesToUpdate . join ( ' ' ) ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
function updateClasses ( oldClasses , newClasses ) {
var toAdd = arrayDifference ( newClasses , oldClasses ) ;
var toRemove = arrayDifference ( oldClasses , newClasses ) ;
toAdd = digestClassCounts ( toAdd , 1 ) ;
toRemove = digestClassCounts ( toRemove , - 1 ) ;
if ( toAdd && toAdd . length ) {
$animate . addClass ( element , toAdd ) ;
}
if ( toRemove && toRemove . length ) {
$animate . removeClass ( element , toRemove ) ;
}
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
function ngClassWatchAction ( newVal ) {
if ( selector === true || scope . $index % 2 === selector ) {
var newClasses = arrayClasses ( newVal || [ ] ) ;
if ( ! oldVal ) {
addClasses ( newClasses ) ;
} else if ( ! equals ( newVal , oldVal ) ) {
var oldClasses = arrayClasses ( oldVal ) ;
updateClasses ( oldClasses , newClasses ) ;
}
}
oldVal = shallowCopy ( newVal ) ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
}
} ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
function arrayDifference ( tokens1 , tokens2 ) {
var values = [ ] ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
outer :
for ( var i = 0 ; i < tokens1 . length ; i ++ ) {
var token = tokens1 [ i ] ;
for ( var j = 0 ; j < tokens2 . length ; j ++ ) {
if ( token == tokens2 [ j ] ) continue outer ;
}
values . push ( token ) ;
}
return values ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
function arrayClasses ( classVal ) {
if ( isArray ( classVal ) ) {
return classVal ;
} else if ( isString ( classVal ) ) {
return classVal . split ( ' ' ) ;
} else if ( isObject ( classVal ) ) {
var classes = [ ] ;
forEach ( classVal , function ( v , k ) {
if ( v ) {
classes = classes . concat ( k . split ( ' ' ) ) ;
}
} ) ;
return classes ;
}
return classVal ;
}
} ] ;
}
2016-04-18 12:34:29 +00:00
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngClass
* @ restrict AC
2016-04-18 12:34:29 +00:00
*
* @ description
2018-05-05 12:13:16 +02:00
* The ` ngClass ` directive allows you to dynamically set CSS classes on an HTML element by databinding
* an expression that represents all classes to be added .
2016-04-18 12:34:29 +00:00
*
2018-05-05 12:13:16 +02:00
* The directive operates in three different ways , depending on which of three types the expression
* evaluates to :
2016-04-18 12:34:29 +00:00
*
2018-05-05 12:13:16 +02:00
* 1. If the expression evaluates to a string , the string should be one or more space - delimited class
* names .
2016-04-18 12:34:29 +00:00
*
2018-05-05 12:13:16 +02:00
* 2. If the expression evaluates to an array , each element of the array should be a string that is
* one or more space - delimited class names .
2016-04-18 12:34:29 +00:00
*
2018-05-05 12:13:16 +02:00
* 3. If the expression evaluates to an object , then for each key - value pair of the
* object with a truthy value the corresponding key is used as a class name .
2016-04-18 12:34:29 +00:00
*
* The directive won ' t add duplicate classes if a particular class was already set .
*
* When the expression changes , the previously added classes are removed and only then the
* new classes are added .
*
* @ animations
2018-05-05 12:13:16 +02:00
* * * add * * - happens just before the class is applied to the elements
*
* * * remove * * - happens just before the class is removed from the element
2016-04-18 12:34:29 +00:00
*
* @ element ANY
* @ param { expression } ngClass { @ link guide / expression Expression } to eval . The result
* of the evaluation can be a string representing space delimited class
* names , an array , or a map of class names to boolean values . In the case of a map , the
* names of the properties whose values are truthy will be added as css classes to the
* element .
*
* @ example Example that demonstrates basic bindings via ngClass directive .
< example >
< file name = "index.html" >
< p ng - class = "{strike: deleted, bold: important, red: error}" > Map Syntax Example < / p >
< input type = "checkbox" ng - model = "deleted" > deleted ( apply "strike" class ) < br >
< input type = "checkbox" ng - model = "important" > important ( apply "bold" class ) < br >
< input type = "checkbox" ng - model = "error" > error ( apply "red" class )
< hr >
< p ng - class = "style" > Using String Syntax < / p >
< input type = "text" ng - model = "style" placeholder = "Type: bold strike red" >
< hr >
< p ng - class = "[style1, style2, style3]" > Using Array Syntax < / p >
< input ng - model = "style1" placeholder = "Type: bold, strike or red" > < br >
< input ng - model = "style2" placeholder = "Type: bold, strike or red" > < br >
< input ng - model = "style3" placeholder = "Type: bold, strike or red" > < br >
< / f i l e >
< file name = "style.css" >
2016-03-28 10:46:51 +00:00
. strike {
2016-04-18 12:34:29 +00:00
text - decoration : line - through ;
2016-03-28 10:46:51 +00:00
}
. bold {
font - weight : bold ;
}
. red {
color : red ;
}
< / f i l e >
2018-05-05 12:13:16 +02:00
< file name = "protractor.js" type = "protractor" >
var ps = element . all ( by . css ( 'p' ) ) ;
2016-03-28 10:46:51 +00:00
it ( 'should let you toggle the class' , function ( ) {
2018-05-05 12:13:16 +02:00
expect ( ps . first ( ) . getAttribute ( 'class' ) ) . not . toMatch ( /bold/ ) ;
expect ( ps . first ( ) . getAttribute ( 'class' ) ) . not . toMatch ( /red/ ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
element ( by . model ( 'important' ) ) . click ( ) ;
expect ( ps . first ( ) . getAttribute ( 'class' ) ) . toMatch ( /bold/ ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
element ( by . model ( 'error' ) ) . click ( ) ;
expect ( ps . first ( ) . getAttribute ( 'class' ) ) . toMatch ( /red/ ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should let you toggle string example' , function ( ) {
2018-05-05 12:13:16 +02:00
expect ( ps . get ( 1 ) . getAttribute ( 'class' ) ) . toBe ( '' ) ;
element ( by . model ( 'style' ) ) . clear ( ) ;
element ( by . model ( 'style' ) ) . sendKeys ( 'red' ) ;
expect ( ps . get ( 1 ) . getAttribute ( 'class' ) ) . toBe ( 'red' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'array example should have 3 classes' , function ( ) {
2018-05-05 12:13:16 +02:00
expect ( ps . last ( ) . getAttribute ( 'class' ) ) . toBe ( '' ) ;
element ( by . model ( 'style1' ) ) . sendKeys ( 'bold' ) ;
element ( by . model ( 'style2' ) ) . sendKeys ( 'strike' ) ;
element ( by . model ( 'style3' ) ) . sendKeys ( 'red' ) ;
expect ( ps . last ( ) . getAttribute ( 'class' ) ) . toBe ( 'bold strike red' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
< / f i l e >
< / e x a m p l e >
# # Animations
The example below demonstrates how to perform animations using ngClass .
2018-05-05 12:13:16 +02:00
< example module = "ngAnimate" deps = "angular-animate.js" animations = "true" >
2016-03-28 10:46:51 +00:00
< file name = "index.html" >
2018-05-05 12:13:16 +02:00
< input id = "setbtn" type = "button" value = "set" ng - click = "myVar='my-class'" >
< input id = "clearbtn" type = "button" value = "clear" ng - click = "myVar=''" >
2016-03-28 10:46:51 +00:00
< br >
< span class = "base-class" ng - class = "myVar" > Sample Text < / s p a n >
< / f i l e >
< file name = "style.css" >
. base - class {
2016-04-18 12:34:29 +00:00
- webkit - transition : all cubic - bezier ( 0.250 , 0.460 , 0.450 , 0.940 ) 0.5 s ;
2016-03-28 10:46:51 +00:00
transition : all cubic - bezier ( 0.250 , 0.460 , 0.450 , 0.940 ) 0.5 s ;
}
. base - class . my - class {
color : red ;
font - size : 3 em ;
}
< / f i l e >
2018-05-05 12:13:16 +02:00
< file name = "protractor.js" type = "protractor" >
2016-03-28 10:46:51 +00:00
it ( 'should check ng-class' , function ( ) {
2018-05-05 12:13:16 +02:00
expect ( element ( by . css ( '.base-class' ) ) . getAttribute ( 'class' ) ) . not .
2016-03-28 10:46:51 +00:00
toMatch ( /my-class/ ) ;
2018-05-05 12:13:16 +02:00
element ( by . id ( 'setbtn' ) ) . click ( ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
expect ( element ( by . css ( '.base-class' ) ) . getAttribute ( 'class' ) ) .
2016-03-28 10:46:51 +00:00
toMatch ( /my-class/ ) ;
2018-05-05 12:13:16 +02:00
element ( by . id ( 'clearbtn' ) ) . click ( ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
expect ( element ( by . css ( '.base-class' ) ) . getAttribute ( 'class' ) ) . not .
2016-03-28 10:46:51 +00:00
toMatch ( /my-class/ ) ;
} ) ;
< / f i l e >
< / e x a m p l e >
# # ngClass and pre - existing CSS3 Transitions / Animations
The ngClass directive still supports CSS3 Transitions / Animations even if they do not follow the ngAnimate CSS naming structure .
Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation , but this will not hinder
any pre - existing CSS transitions already on the element . To get an idea of what happens during a class - based animation , be sure
2018-05-05 12:13:16 +02:00
to view the step by step details of { @ link ng . $animate # addClass $animate . addClass } and
{ @ link ng . $animate # removeClass $animate . removeClass } .
2016-03-28 10:46:51 +00:00
* /
var ngClassDirective = classDirective ( '' , true ) ;
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngClassOdd
2016-03-28 10:46:51 +00:00
* @ restrict AC
*
* @ description
* The ` ngClassOdd ` and ` ngClassEven ` directives work exactly as
* { @ link ng . directive : ngClass ngClass } , except they work in
* conjunction with ` ngRepeat ` and take effect only on odd ( even ) rows .
*
* This directive can be applied only within the scope of an
* { @ link ng . directive : ngRepeat ngRepeat } .
*
* @ element ANY
* @ param { expression } ngClassOdd { @ link guide / expression Expression } to eval . The result
* of the evaluation can be a string representing space delimited class names or an array .
*
* @ example
< example >
< file name = "index.html" >
< ol ng - init = "names=['John', 'Mary', 'Cate', 'Suz']" >
< li ng - repeat = "name in names" >
< span ng - class - odd = "'odd'" ng - class - even = "'even'" >
{ { name } }
< / s p a n >
< / l i >
< / o l >
< / f i l e >
< file name = "style.css" >
. odd {
color : red ;
}
. even {
color : blue ;
}
< / f i l e >
2018-05-05 12:13:16 +02:00
< file name = "protractor.js" type = "protractor" >
2016-03-28 10:46:51 +00:00
it ( 'should check ng-class-odd and ng-class-even' , function ( ) {
2018-05-05 12:13:16 +02:00
expect ( element ( by . repeater ( 'name in names' ) . row ( 0 ) . column ( 'name' ) ) . getAttribute ( 'class' ) ) .
2016-03-28 10:46:51 +00:00
toMatch ( /odd/ ) ;
2018-05-05 12:13:16 +02:00
expect ( element ( by . repeater ( 'name in names' ) . row ( 1 ) . column ( 'name' ) ) . getAttribute ( 'class' ) ) .
2016-03-28 10:46:51 +00:00
toMatch ( /even/ ) ;
} ) ;
< / f i l e >
< / e x a m p l e >
* /
var ngClassOddDirective = classDirective ( 'Odd' , 0 ) ;
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngClassEven
2016-03-28 10:46:51 +00:00
* @ restrict AC
*
* @ description
* The ` ngClassOdd ` and ` ngClassEven ` directives work exactly as
* { @ link ng . directive : ngClass ngClass } , except they work in
* conjunction with ` ngRepeat ` and take effect only on odd ( even ) rows .
*
* This directive can be applied only within the scope of an
* { @ link ng . directive : ngRepeat ngRepeat } .
*
* @ element ANY
* @ param { expression } ngClassEven { @ link guide / expression Expression } to eval . The
* result of the evaluation can be a string representing space delimited class names or an array .
*
* @ example
< example >
< file name = "index.html" >
< ol ng - init = "names=['John', 'Mary', 'Cate', 'Suz']" >
< li ng - repeat = "name in names" >
< span ng - class - odd = "'odd'" ng - class - even = "'even'" >
{ { name } } & nbsp ; & nbsp ; & nbsp ;
< / s p a n >
< / l i >
< / o l >
< / f i l e >
< file name = "style.css" >
. odd {
color : red ;
}
. even {
color : blue ;
}
< / f i l e >
2018-05-05 12:13:16 +02:00
< file name = "protractor.js" type = "protractor" >
2016-03-28 10:46:51 +00:00
it ( 'should check ng-class-odd and ng-class-even' , function ( ) {
2018-05-05 12:13:16 +02:00
expect ( element ( by . repeater ( 'name in names' ) . row ( 0 ) . column ( 'name' ) ) . getAttribute ( 'class' ) ) .
2016-03-28 10:46:51 +00:00
toMatch ( /odd/ ) ;
2018-05-05 12:13:16 +02:00
expect ( element ( by . repeater ( 'name in names' ) . row ( 1 ) . column ( 'name' ) ) . getAttribute ( 'class' ) ) .
2016-03-28 10:46:51 +00:00
toMatch ( /even/ ) ;
} ) ;
< / f i l e >
< / e x a m p l e >
* /
var ngClassEvenDirective = classDirective ( 'Even' , 1 ) ;
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngCloak
2016-03-28 10:46:51 +00:00
* @ restrict AC
*
* @ description
* The ` ngCloak ` directive is used to prevent the Angular html template from being briefly
* displayed by the browser in its raw ( uncompiled ) form while your application is loading . Use this
* directive to avoid the undesirable flicker effect caused by the html template display .
*
* The directive can be applied to the ` <body> ` element , but the preferred usage is to apply
* multiple ` ngCloak ` directives to small portions of the page to permit progressive rendering
* of the browser view .
*
* ` ngCloak ` works in cooperation with the following css rule embedded within ` angular.js ` and
* ` angular.min.js ` .
* For CSP mode please add ` angular-csp.css ` to your html file ( see { @ link ng . directive : ngCsp ngCsp } ) .
*
2018-05-05 12:13:16 +02:00
* ` ` ` css
2016-03-28 10:46:51 +00:00
* [ ng \ : cloak ] , [ ng - cloak ] , [ data - ng - cloak ] , [ x - ng - cloak ] , . ng - cloak , . x - ng - cloak {
* display : none ! important ;
* }
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
* When this css rule is loaded by the browser , all html elements ( including their children ) that
* are tagged with the ` ngCloak ` directive are hidden . When Angular encounters this directive
* during the compilation of the template it deletes the ` ngCloak ` element attribute , making
* the compiled element visible .
*
* For the best result , the ` angular.js ` script must be loaded in the head section of the html
* document ; alternatively , the css rule above must be included in the external stylesheet of the
* application .
*
* @ element ANY
*
* @ example
2018-05-05 12:13:16 +02:00
< example >
< file name = "index.html" >
2016-03-28 10:46:51 +00:00
< div id = "template1" ng - cloak > { { 'hello' } } < / d i v >
2018-05-05 12:13:16 +02:00
< div id = "template2" class = "ng-cloak" > { { 'world' } } < / d i v >
< / f i l e >
< file name = "protractor.js" type = "protractor" >
2016-03-28 10:46:51 +00:00
it ( 'should remove the template directive and css class' , function ( ) {
2018-05-05 12:13:16 +02:00
expect ( $ ( '#template1' ) . getAttribute ( 'ng-cloak' ) ) .
toBeNull ( ) ;
expect ( $ ( '#template2' ) . getAttribute ( 'ng-cloak' ) ) .
toBeNull ( ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-03-28 10:46:51 +00:00
*
* /
var ngCloakDirective = ngDirective ( {
compile : function ( element , attr ) {
attr . $set ( 'ngCloak' , undefined ) ;
element . removeClass ( 'ng-cloak' ) ;
}
} ) ;
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngController
2016-03-28 10:46:51 +00:00
*
* @ description
* The ` ngController ` directive attaches a controller class to the view . This is a key aspect of how angular
* supports the principles behind the Model - View - Controller design pattern .
*
* MVC components in angular :
*
2018-05-05 12:13:16 +02:00
* * Model — Models are the properties of a scope ; scopes are attached to the DOM where scope properties
2016-03-28 10:46:51 +00:00
* are accessed through bindings .
* * View — The template ( HTML with data bindings ) that is rendered into the View .
* * Controller — The ` ngController ` directive specifies a Controller class ; the class contains business
* logic behind the application to decorate the scope with functions and values
*
* Note that you can also attach controllers to the DOM by declaring it in a route definition
* via the { @ link ngRoute . $route $route } service . A common mistake is to declare the controller
* again using ` ng-controller ` in the template itself . This will cause the controller to be attached
* and executed twice .
*
* @ element ANY
* @ scope
2018-05-05 12:13:16 +02:00
* @ priority 500
* @ param { expression } ngController Name of a constructor function registered with the current
* { @ link ng . $controllerProvider $controllerProvider } or an { @ link guide / expression expression }
* that on the current scope evaluates to a constructor function .
*
* The controller instance can be published into a scope property by specifying
* ` ng-controller="as propertyName" ` .
*
* If the current ` $ controllerProvider ` is configured to use globals ( via
* { @ link ng . $controllerProvider # allowGlobals ` $ controllerProvider.allowGlobals() ` } ) , this may
* also be the name of a globally accessible constructor function ( not recommended ) .
2016-03-28 10:46:51 +00:00
*
* @ example
* Here is a simple form for editing user contact information . Adding , removing , clearing , and
* greeting are methods declared on the controller ( see source tab ) . These methods can
2018-05-05 12:13:16 +02:00
* easily be called from the angular markup . Any changes to the data are automatically reflected
* in the View without the need for a manual update .
*
* Two different declaration styles are included below :
*
* * one binds methods and properties directly onto the controller using ` this ` :
* ` ng-controller="SettingsController1 as settings" `
* * one injects ` $ scope ` into the controller :
* ` ng-controller="SettingsController2" `
*
* The second option is more common in the Angular community , and is generally used in boilerplates
* and in this guide . However , there are advantages to binding properties directly to the controller
* and avoiding scope .
*
* * Using ` controller as ` makes it obvious which controller you are accessing in the template when
* multiple controllers apply to an element .
* * If you are writing your controllers as classes you have easier access to the properties and
* methods , which will appear on the scope , from inside the controller code .
* * Since there is always a ` . ` in the bindings , you don ' t have to worry about prototypal
* inheritance masking primitives .
*
* This example demonstrates the ` controller as ` syntax .
*
* < example name = "ngControllerAs" module = "controllerAsExample" >
* < file name = "index.html" >
* < div id = "ctrl-as-exmpl" ng - controller = "SettingsController1 as settings" >
* Name : < input type = "text" ng - model = "settings.name" / >
* [ < a href = "" ng - click = "settings.greet()" > greet < /a> ]<br/ >
* Contact :
* < ul >
* < li ng - repeat = "contact in settings.contacts" >
* < select ng - model = "contact.type" >
* < option > phone < / o p t i o n >
* < option > email < / o p t i o n >
* < / s e l e c t >
* < input type = "text" ng - model = "contact.value" / >
* [ < a href = "" ng - click = "settings.clearContact(contact)" > clear < / a >
* | < a href = "" ng - click = "settings.removeContact(contact)" > X < / a > ]
* < / l i >
* < li > [ < a href = "" ng - click = "settings.addContact()" > add < / a > ] < / l i >
* < / u l >
* < / d i v >
* < / f i l e >
* < file name = "app.js" >
* angular . module ( 'controllerAsExample' , [ ] )
* . controller ( 'SettingsController1' , SettingsController1 ) ;
*
* function SettingsController1 ( ) {
* this . name = "John Smith" ;
* this . contacts = [
* { type : 'phone' , value : '408 555 1212' } ,
* { type : 'email' , value : 'john.smith@example.org' } ] ;
* }
*
* SettingsController1 . prototype . greet = function ( ) {
* alert ( this . name ) ;
* } ;
*
* SettingsController1 . prototype . addContact = function ( ) {
* this . contacts . push ( { type : 'email' , value : 'yourname@example.org' } ) ;
* } ;
*
* SettingsController1 . prototype . removeContact = function ( contactToRemove ) {
* var index = this . contacts . indexOf ( contactToRemove ) ;
* this . contacts . splice ( index , 1 ) ;
* } ;
*
* SettingsController1 . prototype . clearContact = function ( contact ) {
* contact . type = 'phone' ;
* contact . value = '' ;
* } ;
* < / f i l e >
* < file name = "protractor.js" type = "protractor" >
* it ( 'should check controller as' , function ( ) {
* var container = element ( by . id ( 'ctrl-as-exmpl' ) ) ;
* expect ( container . element ( by . model ( 'settings.name' ) )
* . getAttribute ( 'value' ) ) . toBe ( 'John Smith' ) ;
*
* var firstRepeat =
* container . element ( by . repeater ( 'contact in settings.contacts' ) . row ( 0 ) ) ;
* var secondRepeat =
* container . element ( by . repeater ( 'contact in settings.contacts' ) . row ( 1 ) ) ;
*
* expect ( firstRepeat . element ( by . model ( 'contact.value' ) ) . getAttribute ( 'value' ) )
* . toBe ( '408 555 1212' ) ;
*
* expect ( secondRepeat . element ( by . model ( 'contact.value' ) ) . getAttribute ( 'value' ) )
* . toBe ( 'john.smith@example.org' ) ;
*
* firstRepeat . element ( by . linkText ( 'clear' ) ) . click ( ) ;
*
* expect ( firstRepeat . element ( by . model ( 'contact.value' ) ) . getAttribute ( 'value' ) )
* . toBe ( '' ) ;
*
* container . element ( by . linkText ( 'add' ) ) . click ( ) ;
*
* expect ( container . element ( by . repeater ( 'contact in settings.contacts' ) . row ( 2 ) )
* . element ( by . model ( 'contact.value' ) )
* . getAttribute ( 'value' ) )
* . toBe ( 'yourname@example.org' ) ;
* } ) ;
* < / f i l e >
* < / e x a m p l e >
*
* This example demonstrates the "attach to `$scope`" style of controller .
*
* < example name = "ngController" module = "controllerExample" >
* < file name = "index.html" >
* < div id = "ctrl-exmpl" ng - controller = "SettingsController2" >
* Name : < input type = "text" ng - model = "name" / >
* [ < a href = "" ng - click = "greet()" > greet < /a> ]<br/ >
* Contact :
* < ul >
* < li ng - repeat = "contact in contacts" >
* < select ng - model = "contact.type" >
* < option > phone < / o p t i o n >
* < option > email < / o p t i o n >
* < / s e l e c t >
* < input type = "text" ng - model = "contact.value" / >
* [ < a href = "" ng - click = "clearContact(contact)" > clear < / a >
* | < a href = "" ng - click = "removeContact(contact)" > X < / a > ]
* < / l i >
* < li > [ < a href = "" ng - click = "addContact()" > add < / a > ] < / l i >
* < / u l >
* < / d i v >
* < / f i l e >
* < file name = "app.js" >
* angular . module ( 'controllerExample' , [ ] )
* . controller ( 'SettingsController2' , [ '$scope' , SettingsController2 ] ) ;
*
* function SettingsController2 ( $scope ) {
* $scope . name = "John Smith" ;
* $scope . contacts = [
* { type : 'phone' , value : '408 555 1212' } ,
* { type : 'email' , value : 'john.smith@example.org' } ] ;
*
* $scope . greet = function ( ) {
* alert ( $scope . name ) ;
* } ;
*
* $scope . addContact = function ( ) {
* $scope . contacts . push ( { type : 'email' , value : 'yourname@example.org' } ) ;
* } ;
*
* $scope . removeContact = function ( contactToRemove ) {
* var index = $scope . contacts . indexOf ( contactToRemove ) ;
* $scope . contacts . splice ( index , 1 ) ;
* } ;
*
* $scope . clearContact = function ( contact ) {
* contact . type = 'phone' ;
* contact . value = '' ;
* } ;
* }
* < / f i l e >
* < file name = "protractor.js" type = "protractor" >
* it ( 'should check controller' , function ( ) {
* var container = element ( by . id ( 'ctrl-exmpl' ) ) ;
*
* expect ( container . element ( by . model ( 'name' ) )
* . getAttribute ( 'value' ) ) . toBe ( 'John Smith' ) ;
*
* var firstRepeat =
* container . element ( by . repeater ( 'contact in contacts' ) . row ( 0 ) ) ;
* var secondRepeat =
* container . element ( by . repeater ( 'contact in contacts' ) . row ( 1 ) ) ;
*
* expect ( firstRepeat . element ( by . model ( 'contact.value' ) ) . getAttribute ( 'value' ) )
* . toBe ( '408 555 1212' ) ;
* expect ( secondRepeat . element ( by . model ( 'contact.value' ) ) . getAttribute ( 'value' ) )
* . toBe ( 'john.smith@example.org' ) ;
*
* firstRepeat . element ( by . linkText ( 'clear' ) ) . click ( ) ;
*
* expect ( firstRepeat . element ( by . model ( 'contact.value' ) ) . getAttribute ( 'value' ) )
* . toBe ( '' ) ;
*
* container . element ( by . linkText ( 'add' ) ) . click ( ) ;
*
* expect ( container . element ( by . repeater ( 'contact in contacts' ) . row ( 2 ) )
* . element ( by . model ( 'contact.value' ) )
* . getAttribute ( 'value' ) )
* . toBe ( 'yourname@example.org' ) ;
* } ) ;
* < / f i l e >
* < / e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
var ngControllerDirective = [ function ( ) {
return {
2018-05-05 12:13:16 +02:00
restrict : 'A' ,
2016-03-28 10:46:51 +00:00
scope : true ,
controller : '@' ,
priority : 500
} ;
} ] ;
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngCsp
2016-03-28 10:46:51 +00:00
*
* @ element html
* @ description
2016-04-18 12:34:29 +00:00
* Enables [ CSP ( Content Security Policy ) ] ( https : //developer.mozilla.org/en/Security/CSP) support.
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* This is necessary when developing things like Google Chrome Extensions or Universal Windows Apps .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* CSP forbids apps to use ` eval ` or ` Function(string) ` generated functions ( among other things ) .
2018-05-05 12:13:16 +02:00
* For Angular to be CSP compatible there are only two things that we need to do differently :
*
* - don ' t use ` Function ` constructor to generate optimized value getters
* - don ' t inject custom stylesheet into the document
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* AngularJS uses ` Function(string) ` generated functions as a speed optimization . Applying the ` ngCsp `
* directive will cause Angular to use CSP compatibility mode . When this mode is on AngularJS will
* evaluate all expressions up to 30 % slower than in non - CSP mode , but no security violations will
* be raised .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* CSP forbids JavaScript to inline stylesheet rules . In non CSP mode Angular automatically
* includes some CSS rules ( e . g . { @ link ng . directive : ngCloak ngCloak } ) .
* To make those directives work in CSP mode , include the ` angular-csp.css ` manually .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* Angular tries to autodetect if CSP is active and automatically turn on the CSP - safe mode . This
* autodetection however triggers a CSP error to be logged in the console :
*
* ` ` `
* Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of
* script in the following Content Security Policy directive : "default-src 'self'" . Note that
* 'script-src' was not explicitly set , so 'default-src' is used as a fallback .
* ` ` `
*
* This error is harmless but annoying . To prevent the error from showing up , put the ` ngCsp `
* directive on the root element of the application or on the ` angular.js ` script tag , whichever
* appears first in the html document .
2016-03-28 10:46:51 +00:00
*
* * Note : This directive is only available in the ` ng-csp ` and ` data-ng-csp ` attribute form . *
*
* @ example
* This example shows how to apply the ` ngCsp ` directive to the ` html ` tag .
2018-05-05 12:13:16 +02:00
` ` ` html
2016-03-28 10:46:51 +00:00
< ! doctype html >
< html ng - app ng - csp >
...
...
< / h t m l >
2018-05-05 12:13:16 +02:00
` ` `
* @ example
// Note: the suffix `.csp` in the example name triggers
// csp mode in our http server!
< example name = "example.csp" module = "cspExample" ng - csp = "true" >
< file name = "index.html" >
< div ng - controller = "MainController as ctrl" >
< div >
< button ng - click = "ctrl.inc()" id = "inc" > Increment < / b u t t o n >
< span id = "counter" >
{ { ctrl . counter } }
< / s p a n >
< / d i v >
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
< div >
< button ng - click = "ctrl.evil()" id = "evil" > Evil < / b u t t o n >
< span id = "evilError" >
{ { ctrl . evilError } }
< / s p a n >
< / d i v >
< / d i v >
< / f i l e >
< file name = "script.js" >
angular . module ( 'cspExample' , [ ] )
. controller ( 'MainController' , function ( ) {
this . counter = 0 ;
this . inc = function ( ) {
this . counter ++ ;
} ;
this . evil = function ( ) {
// jshint evil:true
try {
eval ( '1+2' ) ;
} catch ( e ) {
this . evilError = e . message ;
}
} ;
} ) ;
< / f i l e >
< file name = "protractor.js" type = "protractor" >
var util , webdriver ;
var incBtn = element ( by . id ( 'inc' ) ) ;
var counter = element ( by . id ( 'counter' ) ) ;
var evilBtn = element ( by . id ( 'evil' ) ) ;
var evilError = element ( by . id ( 'evilError' ) ) ;
function getAndClearSevereErrors ( ) {
return browser . manage ( ) . logs ( ) . get ( 'browser' ) . then ( function ( browserLog ) {
return browserLog . filter ( function ( logEntry ) {
return logEntry . level . value > webdriver . logging . Level . WARNING . value ;
} ) ;
} ) ;
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
function clearErrors ( ) {
getAndClearSevereErrors ( ) ;
}
function expectNoErrors ( ) {
getAndClearSevereErrors ( ) . then ( function ( filteredLog ) {
expect ( filteredLog . length ) . toEqual ( 0 ) ;
if ( filteredLog . length ) {
console . log ( 'browser console errors: ' + util . inspect ( filteredLog ) ) ;
}
} ) ;
}
function expectError ( regex ) {
getAndClearSevereErrors ( ) . then ( function ( filteredLog ) {
var found = false ;
filteredLog . forEach ( function ( log ) {
if ( log . message . match ( regex ) ) {
found = true ;
}
} ) ;
if ( ! found ) {
throw new Error ( 'expected an error that matches ' + regex ) ;
}
} ) ;
}
beforeEach ( function ( ) {
util = require ( 'util' ) ;
webdriver = require ( 'protractor/node_modules/selenium-webdriver' ) ;
} ) ;
// For now, we only test on Chrome,
// as Safari does not load the page with Protractor's injected scripts,
// and Firefox webdriver always disables content security policy (#6358)
if ( browser . params . browser !== 'chrome' ) {
return ;
}
it ( 'should not report errors when the page is loaded' , function ( ) {
// clear errors so we are not dependent on previous tests
clearErrors ( ) ;
// Need to reload the page as the page is already loaded when
// we come here
browser . driver . getCurrentUrl ( ) . then ( function ( url ) {
browser . get ( url ) ;
} ) ;
expectNoErrors ( ) ;
} ) ;
it ( 'should evaluate expressions' , function ( ) {
expect ( counter . getText ( ) ) . toEqual ( '0' ) ;
incBtn . click ( ) ;
expect ( counter . getText ( ) ) . toEqual ( '1' ) ;
expectNoErrors ( ) ;
} ) ;
it ( 'should throw and report an error when using "eval"' , function ( ) {
evilBtn . click ( ) ;
expect ( evilError . getText ( ) ) . toMatch ( /Content Security Policy/ ) ;
expectError ( /Content Security Policy/ ) ;
} ) ;
< / f i l e >
< / e x a m p l e >
* /
// ngCsp is not implemented as a proper directive any more, because we need it be processed while we
// bootstrap the system (before $parse is instantiated), for this reason we just have
// the csp.isActive() fn that looks for ng-csp attribute anywhere in the current doc
/ * *
* @ ngdoc directive
* @ name ngClick
2016-03-28 10:46:51 +00:00
*
* @ description
* The ngClick directive allows you to specify custom behavior when
* an element is clicked .
*
* @ element ANY
2018-05-05 12:13:16 +02:00
* @ priority 0
2016-03-28 10:46:51 +00:00
* @ param { expression } ngClick { @ link guide / expression Expression } to evaluate upon
2018-05-05 12:13:16 +02:00
* click . ( { @ link guide / expression # - event - Event object is available as ` $ event ` } )
2016-03-28 10:46:51 +00:00
*
* @ example
2018-05-05 12:13:16 +02:00
< example >
< file name = "index.html" >
2016-03-28 10:46:51 +00:00
< button ng - click = "count = count + 1" ng - init = "count=0" >
Increment
< / b u t t o n >
2018-05-05 12:13:16 +02:00
< span >
count : { { count } }
< / s p a n >
< / f i l e >
< file name = "protractor.js" type = "protractor" >
2016-03-28 10:46:51 +00:00
it ( 'should check ng-click' , function ( ) {
expect ( element ( by . binding ( 'count' ) ) . getText ( ) ) . toMatch ( '0' ) ;
2018-05-05 12:13:16 +02:00
element ( by . css ( 'button' ) ) . click ( ) ;
2016-03-28 10:46:51 +00:00
expect ( element ( by . binding ( 'count' ) ) . getText ( ) ) . toMatch ( '1' ) ;
} ) ;
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
/ *
2018-05-05 12:13:16 +02:00
* A collection of directives that allows creation of custom event handlers that are defined as
* angular expressions and are compiled and executed within the current scope .
2016-03-28 10:46:51 +00:00
* /
var ngEventDirectives = { } ;
2018-05-05 12:13:16 +02:00
// For events that might fire synchronously during DOM manipulation
// we need to execute their event handlers asynchronously using $evalAsync,
// so that they are not executed in an inconsistent state.
var forceAsyncEvents = {
'blur' : true ,
'focus' : true
} ;
2016-03-28 10:46:51 +00:00
forEach (
'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste' . split ( ' ' ) ,
2018-05-05 12:13:16 +02:00
function ( eventName ) {
var directiveName = directiveNormalize ( 'ng-' + eventName ) ;
ngEventDirectives [ directiveName ] = [ '$parse' , '$rootScope' , function ( $parse , $rootScope ) {
2016-03-28 10:46:51 +00:00
return {
2018-05-05 12:13:16 +02:00
restrict : 'A' ,
2016-03-28 10:46:51 +00:00
compile : function ( $element , attr ) {
2018-05-05 12:13:16 +02:00
// We expose the powerful $event object on the scope that provides access to the Window,
// etc. that isn't protected by the fast paths in $parse. We explicitly request better
// checks at the cost of speed since event handler expressions are not executed as
// frequently as regular change detection.
var fn = $parse ( attr [ directiveName ] , /* interceptorFn */ null , /* expensiveChecks */ true ) ;
return function ngEventHandler ( scope , element ) {
element . on ( eventName , function ( event ) {
var callback = function ( ) {
2016-03-28 10:46:51 +00:00
fn ( scope , { $event : event } ) ;
2018-05-05 12:13:16 +02:00
} ;
if ( forceAsyncEvents [ eventName ] && $rootScope . $$phase ) {
scope . $evalAsync ( callback ) ;
} else {
scope . $apply ( callback ) ;
}
2016-03-28 10:46:51 +00:00
} ) ;
} ;
}
} ;
} ] ;
}
) ;
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngDblclick
2016-03-28 10:46:51 +00:00
*
* @ description
* The ` ngDblclick ` directive allows you to specify custom behavior on a dblclick event .
*
* @ element ANY
2018-05-05 12:13:16 +02:00
* @ priority 0
2016-03-28 10:46:51 +00:00
* @ param { expression } ngDblclick { @ link guide / expression Expression } to evaluate upon
* a dblclick . ( The Event object is available as ` $ event ` )
2016-04-18 12:34:29 +00:00
*
* @ example
2018-05-05 12:13:16 +02:00
< example >
< file name = "index.html" >
2016-04-18 12:34:29 +00:00
< button ng - dblclick = "count = count + 1" ng - init = "count=0" >
Increment ( on double click )
< / b u t t o n >
count : { { count } }
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-04-18 12:34:29 +00:00
* /
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngMousedown
2016-03-28 10:46:51 +00:00
*
* @ description
2016-04-18 12:34:29 +00:00
* The ngMousedown directive allows you to specify custom behavior on mousedown event .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ element ANY
2018-05-05 12:13:16 +02:00
* @ priority 0
2016-04-18 12:34:29 +00:00
* @ param { expression } ngMousedown { @ link guide / expression Expression } to evaluate upon
2018-05-05 12:13:16 +02:00
* mousedown . ( { @ link guide / expression # - event - Event object is available as ` $ event ` } )
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ example
2018-05-05 12:13:16 +02:00
< example >
< file name = "index.html" >
2016-04-18 12:34:29 +00:00
< button ng - mousedown = "count = count + 1" ng - init = "count=0" >
Increment ( on mouse down )
< / b u t t o n >
count : { { count } }
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-04-18 12:34:29 +00:00
* /
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngMouseup
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ description
* Specify custom behavior on mouseup event .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ element ANY
2018-05-05 12:13:16 +02:00
* @ priority 0
2016-04-18 12:34:29 +00:00
* @ param { expression } ngMouseup { @ link guide / expression Expression } to evaluate upon
2018-05-05 12:13:16 +02:00
* mouseup . ( { @ link guide / expression # - event - Event object is available as ` $ event ` } )
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ example
2018-05-05 12:13:16 +02:00
< example >
< file name = "index.html" >
2016-04-18 12:34:29 +00:00
< button ng - mouseup = "count = count + 1" ng - init = "count=0" >
Increment ( on mouse up )
< / b u t t o n >
count : { { count } }
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-04-18 12:34:29 +00:00
* /
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngMouseover
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ description
* Specify custom behavior on mouseover event .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ element ANY
2018-05-05 12:13:16 +02:00
* @ priority 0
2016-04-18 12:34:29 +00:00
* @ param { expression } ngMouseover { @ link guide / expression Expression } to evaluate upon
2018-05-05 12:13:16 +02:00
* mouseover . ( { @ link guide / expression # - event - Event object is available as ` $ event ` } )
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ example
2018-05-05 12:13:16 +02:00
< example >
< file name = "index.html" >
2016-04-18 12:34:29 +00:00
< button ng - mouseover = "count = count + 1" ng - init = "count=0" >
Increment ( when mouse is over )
< / b u t t o n >
count : { { count } }
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-04-18 12:34:29 +00:00
* /
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngMouseenter
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ description
* Specify custom behavior on mouseenter event .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ element ANY
2018-05-05 12:13:16 +02:00
* @ priority 0
2016-04-18 12:34:29 +00:00
* @ param { expression } ngMouseenter { @ link guide / expression Expression } to evaluate upon
2018-05-05 12:13:16 +02:00
* mouseenter . ( { @ link guide / expression # - event - Event object is available as ` $ event ` } )
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ example
2018-05-05 12:13:16 +02:00
< example >
< file name = "index.html" >
2016-04-18 12:34:29 +00:00
< button ng - mouseenter = "count = count + 1" ng - init = "count=0" >
Increment ( when mouse enters )
< / b u t t o n >
count : { { count } }
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-04-18 12:34:29 +00:00
* /
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngMouseleave
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ description
* Specify custom behavior on mouseleave event .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ element ANY
2018-05-05 12:13:16 +02:00
* @ priority 0
2016-04-18 12:34:29 +00:00
* @ param { expression } ngMouseleave { @ link guide / expression Expression } to evaluate upon
2018-05-05 12:13:16 +02:00
* mouseleave . ( { @ link guide / expression # - event - Event object is available as ` $ event ` } )
2016-03-28 10:46:51 +00:00
*
* @ example
2018-05-05 12:13:16 +02:00
< example >
< file name = "index.html" >
2016-04-18 12:34:29 +00:00
< button ng - mouseleave = "count = count + 1" ng - init = "count=0" >
Increment ( when mouse leaves )
< / b u t t o n >
count : { { count } }
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-04-18 12:34:29 +00:00
* /
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngMousemove
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ description
* Specify custom behavior on mousemove event .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ element ANY
2018-05-05 12:13:16 +02:00
* @ priority 0
2016-04-18 12:34:29 +00:00
* @ param { expression } ngMousemove { @ link guide / expression Expression } to evaluate upon
2018-05-05 12:13:16 +02:00
* mousemove . ( { @ link guide / expression # - event - Event object is available as ` $ event ` } )
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ example
2018-05-05 12:13:16 +02:00
< example >
< file name = "index.html" >
2016-04-18 12:34:29 +00:00
< button ng - mousemove = "count = count + 1" ng - init = "count=0" >
Increment ( when mouse moves )
< / b u t t o n >
count : { { count } }
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-04-18 12:34:29 +00:00
* /
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngKeydown
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ description
* Specify custom behavior on keydown event .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ element ANY
2018-05-05 12:13:16 +02:00
* @ priority 0
2016-04-18 12:34:29 +00:00
* @ param { expression } ngKeydown { @ link guide / expression Expression } to evaluate upon
* keydown . ( Event object is available as ` $ event ` and can be interrogated for keyCode , altKey , etc . )
2016-03-28 10:46:51 +00:00
*
* @ example
2018-05-05 12:13:16 +02:00
< example >
< file name = "index.html" >
2016-04-18 12:34:29 +00:00
< input ng - keydown = "count = count + 1" ng - init = "count=0" >
key down count : { { count } }
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngKeyup
2016-03-28 10:46:51 +00:00
*
* @ description
2016-04-18 12:34:29 +00:00
* Specify custom behavior on keyup event .
*
* @ element ANY
2018-05-05 12:13:16 +02:00
* @ priority 0
2016-04-18 12:34:29 +00:00
* @ param { expression } ngKeyup { @ link guide / expression Expression } to evaluate upon
* keyup . ( Event object is available as ` $ event ` and can be interrogated for keyCode , altKey , etc . )
2016-03-28 10:46:51 +00:00
*
* @ example
2018-05-05 12:13:16 +02:00
< example >
< file name = "index.html" >
< p > Typing in the input box below updates the key count < / p >
< input ng - keyup = "count = count + 1" ng - init = "count=0" > key up count : { { count } }
< p > Typing in the input box below updates the keycode < / p >
< input ng - keyup = "event=$event" >
< p > event keyCode : { { event . keyCode } } < / p >
< p > event altKey : { { event . altKey } } < / p >
< / f i l e >
< / e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
2016-04-18 12:34:29 +00:00
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngKeypress
2016-04-18 12:34:29 +00:00
*
* @ description
* Specify custom behavior on keypress event .
*
* @ element ANY
* @ param { expression } ngKeypress { @ link guide / expression Expression } to evaluate upon
2018-05-05 12:13:16 +02:00
* keypress . ( { @ link guide / expression # - event - Event object is available as ` $ event ` }
* and can be interrogated for keyCode , altKey , etc . )
2016-04-18 12:34:29 +00:00
*
* @ example
2018-05-05 12:13:16 +02:00
< example >
< file name = "index.html" >
2016-04-18 12:34:29 +00:00
< input ng - keypress = "count = count + 1" ng - init = "count=0" >
key press count : { { count } }
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-04-18 12:34:29 +00:00
* /
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngSubmit
2016-03-28 10:46:51 +00:00
*
* @ description
2016-04-18 12:34:29 +00:00
* Enables binding angular expressions to onsubmit events .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* Additionally it prevents the default action ( which for form means sending the request to the
2018-05-05 12:13:16 +02:00
* server and reloading the current page ) , but only if the form does not contain ` action ` ,
* ` data-action ` , or ` x-action ` attributes .
*
* < div class = "alert alert-warning" >
* * * Warning : * * Be careful not to cause "double-submission" by using both the ` ngClick ` and
* ` ngSubmit ` handlers together . See the
* { @ link form # submitting - a - form - and - preventing - the - default - action ` form ` directive documentation }
* for a detailed discussion of when ` ngSubmit ` may be triggered .
* < / d i v >
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ element form
2018-05-05 12:13:16 +02:00
* @ priority 0
* @ param { expression } ngSubmit { @ link guide / expression Expression } to eval .
* ( { @ link guide / expression # - event - Event object is available as ` $ event ` } )
2016-03-28 10:46:51 +00:00
*
* @ example
2018-05-05 12:13:16 +02:00
< example module = "submitExample" >
< file name = "index.html" >
2016-04-18 12:34:29 +00:00
< script >
2018-05-05 12:13:16 +02:00
angular . module ( 'submitExample' , [ ] )
. controller ( 'ExampleController' , [ '$scope' , function ( $scope ) {
$scope . list = [ ] ;
$scope . text = 'hello' ;
$scope . submit = function ( ) {
if ( $scope . text ) {
$scope . list . push ( this . text ) ;
$scope . text = '' ;
}
} ;
} ] ) ;
2016-04-18 12:34:29 +00:00
< / s c r i p t >
2018-05-05 12:13:16 +02:00
< form ng - submit = "submit()" ng - controller = "ExampleController" >
2016-04-18 12:34:29 +00:00
Enter text and hit enter :
< input type = "text" ng - model = "text" name = "text" / >
< input type = "submit" id = "submit" value = "Submit" / >
< pre > list = { { list } } < / p r e >
< / f o r m >
2018-05-05 12:13:16 +02:00
< / f i l e >
< file name = "protractor.js" type = "protractor" >
2016-04-18 12:34:29 +00:00
it ( 'should check ng-submit' , function ( ) {
2018-05-05 12:13:16 +02:00
expect ( element ( by . binding ( 'list' ) ) . getText ( ) ) . toBe ( 'list=[]' ) ;
element ( by . css ( '#submit' ) ) . click ( ) ;
expect ( element ( by . binding ( 'list' ) ) . getText ( ) ) . toContain ( 'hello' ) ;
expect ( element ( by . model ( 'text' ) ) . getAttribute ( 'value' ) ) . toBe ( '' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2016-04-18 12:34:29 +00:00
it ( 'should ignore empty strings' , function ( ) {
2018-05-05 12:13:16 +02:00
expect ( element ( by . binding ( 'list' ) ) . getText ( ) ) . toBe ( 'list=[]' ) ;
element ( by . css ( '#submit' ) ) . click ( ) ;
element ( by . css ( '#submit' ) ) . click ( ) ;
expect ( element ( by . binding ( 'list' ) ) . getText ( ) ) . toContain ( 'hello' ) ;
} ) ;
< / f i l e >
< / e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngFocus
2016-03-28 10:46:51 +00:00
*
* @ description
2016-04-18 12:34:29 +00:00
* Specify custom behavior on focus event .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* Note : As the ` focus ` event is executed synchronously when calling ` input.focus() `
* AngularJS executes the expression using ` scope. $ evalAsync ` if the event is fired
* during an ` $ apply ` to ensure a consistent state .
*
2016-04-18 12:34:29 +00:00
* @ element window , input , select , textarea , a
2018-05-05 12:13:16 +02:00
* @ priority 0
2016-04-18 12:34:29 +00:00
* @ param { expression } ngFocus { @ link guide / expression Expression } to evaluate upon
2018-05-05 12:13:16 +02:00
* focus . ( { @ link guide / expression # - event - Event object is available as ` $ event ` } )
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ example
* See { @ link ng . directive : ngClick ngClick }
* /
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngBlur
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ description
* Specify custom behavior on blur event .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* A [ blur event ] ( https : //developer.mozilla.org/en-US/docs/Web/Events/blur) fires when
* an element has lost focus .
*
* Note : As the ` blur ` event is executed synchronously also during DOM manipulations
* ( e . g . removing a focussed input ) ,
* AngularJS executes the expression using ` scope. $ evalAsync ` if the event is fired
* during an ` $ apply ` to ensure a consistent state .
*
2016-04-18 12:34:29 +00:00
* @ element window , input , select , textarea , a
2018-05-05 12:13:16 +02:00
* @ priority 0
2016-04-18 12:34:29 +00:00
* @ param { expression } ngBlur { @ link guide / expression Expression } to evaluate upon
2018-05-05 12:13:16 +02:00
* blur . ( { @ link guide / expression # - event - Event object is available as ` $ event ` } )
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ example
* See { @ link ng . directive : ngClick ngClick }
* /
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngCopy
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ description
* Specify custom behavior on copy event .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ element window , input , select , textarea , a
2018-05-05 12:13:16 +02:00
* @ priority 0
2016-04-18 12:34:29 +00:00
* @ param { expression } ngCopy { @ link guide / expression Expression } to evaluate upon
2018-05-05 12:13:16 +02:00
* copy . ( { @ link guide / expression # - event - Event object is available as ` $ event ` } )
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ example
2018-05-05 12:13:16 +02:00
< example >
< file name = "index.html" >
2016-04-18 12:34:29 +00:00
< input ng - copy = "copied=true" ng - init = "copied=false; value='copy me'" ng - model = "value" >
copied : { { copied } }
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-04-18 12:34:29 +00:00
* /
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngCut
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ description
* Specify custom behavior on cut event .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ element window , input , select , textarea , a
2018-05-05 12:13:16 +02:00
* @ priority 0
2016-04-18 12:34:29 +00:00
* @ param { expression } ngCut { @ link guide / expression Expression } to evaluate upon
2018-05-05 12:13:16 +02:00
* cut . ( { @ link guide / expression # - event - Event object is available as ` $ event ` } )
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ example
2018-05-05 12:13:16 +02:00
< example >
< file name = "index.html" >
2016-04-18 12:34:29 +00:00
< input ng - cut = "cut=true" ng - init = "cut=false; value='cut me'" ng - model = "value" >
cut : { { cut } }
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-04-18 12:34:29 +00:00
* /
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngPaste
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ description
* Specify custom behavior on paste event .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ element window , input , select , textarea , a
2018-05-05 12:13:16 +02:00
* @ priority 0
2016-04-18 12:34:29 +00:00
* @ param { expression } ngPaste { @ link guide / expression Expression } to evaluate upon
2018-05-05 12:13:16 +02:00
* paste . ( { @ link guide / expression # - event - Event object is available as ` $ event ` } )
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ example
2018-05-05 12:13:16 +02:00
< example >
< file name = "index.html" >
2016-04-18 12:34:29 +00:00
< input ng - paste = "paste=true" ng - init = "paste=false" placeholder = 'paste here' >
pasted : { { paste } }
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-04-18 12:34:29 +00:00
* /
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngIf
2016-04-18 12:34:29 +00:00
* @ restrict A
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ description
* The ` ngIf ` directive removes or recreates a portion of the DOM tree based on an
* { expression } . If the expression assigned to ` ngIf ` evaluates to a false
* value then the element is removed from the DOM , otherwise a clone of the
* element is reinserted into the DOM .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* ` ngIf ` differs from ` ngShow ` and ` ngHide ` in that ` ngIf ` completely removes and recreates the
* element in the DOM rather than changing its visibility via the ` display ` css property . A common
* case when this difference is significant is when using css selectors that rely on an element ' s
* position within the DOM , such as the ` :first-child ` or ` :last-child ` pseudo - classes .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* Note that when an element is removed using ` ngIf ` its scope is destroyed and a new scope
* is created when the element is restored . The scope created within ` ngIf ` inherits from
* its parent scope using
2018-05-05 12:13:16 +02:00
* [ prototypal inheritance ] ( https : //github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance).
2016-04-18 12:34:29 +00:00
* An important implication of this is if ` ngModel ` is used within ` ngIf ` to bind to
* a javascript primitive defined in the parent scope . In this case any modifications made to the
* variable within the child scope will override ( hide ) the value in the parent scope .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* Also , ` ngIf ` recreates elements using their compiled state . An example of this behavior
* is if an element 's class attribute is directly modified after it' s compiled , using something like
* jQuery ' s ` .addClass() ` method , and the element is later removed . When ` ngIf ` recreates the element
* the added class will be lost because the original compiled state is used to regenerate the element .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* Additionally , you can provide animations via the ` ngAnimate ` module to animate the ` enter `
* and ` leave ` effects .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ animations
2018-05-05 12:13:16 +02:00
* enter - happens just after the ` ngIf ` contents change and a new DOM element is created and injected into the ` ngIf ` container
* leave - happens just before the ` ngIf ` contents are removed from the DOM
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ element ANY
* @ scope
* @ priority 600
* @ param { expression } ngIf If the { @ link guide / expression expression } is falsy then
* the element is removed from the DOM tree . If it is truthy a copy of the compiled
* element is added to the DOM tree .
2016-03-28 10:46:51 +00:00
*
* @ example
2018-05-05 12:13:16 +02:00
< example module = "ngAnimate" deps = "angular-animate.js" animations = "true" >
2016-04-18 12:34:29 +00:00
< file name = "index.html" >
Click me : < input type = "checkbox" ng - model = "checked" ng - init = "checked=true" / > < br / >
Show when checked :
< span ng - if = "checked" class = "animate-if" >
2018-05-05 12:13:16 +02:00
This is removed when the checkbox is unchecked .
2016-04-18 12:34:29 +00:00
< / s p a n >
< / f i l e >
< file name = "animations.css" >
. animate - if {
background : white ;
border : 1 px solid black ;
padding : 10 px ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
. animate - if . ng - enter , . animate - if . ng - leave {
- webkit - transition : all cubic - bezier ( 0.250 , 0.460 , 0.450 , 0.940 ) 0.5 s ;
transition : all cubic - bezier ( 0.250 , 0.460 , 0.450 , 0.940 ) 0.5 s ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
. animate - if . ng - enter ,
. animate - if . ng - leave . ng - leave - active {
opacity : 0 ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
. animate - if . ng - leave ,
. animate - if . ng - enter . ng - enter - active {
opacity : 1 ;
}
< / f i l e >
< / e x a m p l e >
* /
var ngIfDirective = [ '$animate' , function ( $animate ) {
return {
2018-05-05 12:13:16 +02:00
multiElement : true ,
2016-04-18 12:34:29 +00:00
transclude : 'element' ,
priority : 600 ,
terminal : true ,
restrict : 'A' ,
$$tlb : true ,
2018-05-05 12:13:16 +02:00
link : function ( $scope , $element , $attr , ctrl , $transclude ) {
var block , childScope , previousElements ;
2016-04-18 12:34:29 +00:00
$scope . $watch ( $attr . ngIf , function ngIfWatchAction ( value ) {
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
if ( value ) {
2016-04-18 12:34:29 +00:00
if ( ! childScope ) {
2018-05-05 12:13:16 +02:00
$transclude ( function ( clone , newScope ) {
childScope = newScope ;
2016-04-18 12:34:29 +00:00
clone [ clone . length ++ ] = document . createComment ( ' end ngIf: ' + $attr . ngIf + ' ' ) ;
// Note: We only need the first/last node of the cloned nodes.
// However, we need to keep the reference to the jqlite wrapper as it might be changed later
2018-05-05 12:13:16 +02:00
// by a directive with templateUrl when its template arrives.
2016-04-18 12:34:29 +00:00
block = {
clone : clone
} ;
$animate . enter ( clone , $element . parent ( ) , $element ) ;
} ) ;
2016-03-28 10:46:51 +00:00
}
} else {
2018-05-05 12:13:16 +02:00
if ( previousElements ) {
previousElements . remove ( ) ;
previousElements = null ;
}
2016-04-18 12:34:29 +00:00
if ( childScope ) {
childScope . $destroy ( ) ;
childScope = null ;
}
if ( block ) {
2018-05-05 12:13:16 +02:00
previousElements = getBlockNodes ( block . clone ) ;
$animate . leave ( previousElements ) . then ( function ( ) {
previousElements = null ;
} ) ;
2016-04-18 12:34:29 +00:00
block = null ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
}
} ) ;
}
} ;
} ] ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngInclude
2016-04-18 12:34:29 +00:00
* @ restrict ECA
*
* @ description
* Fetches , compiles and includes an external HTML fragment .
*
* By default , the template URL is restricted to the same domain and protocol as the
2018-05-05 12:13:16 +02:00
* application document . This is done by calling { @ link $sce # getTrustedResourceUrl
2016-04-18 12:34:29 +00:00
* $sce . getTrustedResourceUrl } on it . To load templates from other domains or protocols
2018-05-05 12:13:16 +02:00
* you may either { @ link ng . $sceDelegateProvider # resourceUrlWhitelist whitelist them } or
* { @ link $sce # trustAsResourceUrl wrap them } as trusted values . Refer to Angular ' s { @ link
2016-04-18 12:34:29 +00:00
* ng . $sce Strict Contextual Escaping } .
*
* In addition , the browser ' s
2018-05-05 12:13:16 +02:00
* [ Same Origin Policy ] ( https : //code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest)
* and [ Cross - Origin Resource Sharing ( CORS ) ] ( http : //www.w3.org/TR/cors/)
* policy may further restrict whether the template is successfully loaded .
2016-04-18 12:34:29 +00:00
* For example , ` ngInclude ` won ' t work for cross - domain requests on all browsers and for ` file:// `
* access on some browsers .
*
* @ animations
* enter - animation is used to bring new content into the browser .
* leave - animation is used to animate existing content away .
*
* The enter and leave animation occur concurrently .
*
* @ scope
* @ priority 400
*
* @ param { string } ngInclude | src angular expression evaluating to URL . If the source is a string constant ,
2018-05-05 12:13:16 +02:00
* make sure you wrap it in * * single * * quotes , e . g . ` src="'myPartialTemplate.html'" ` .
2016-04-18 12:34:29 +00:00
* @ param { string = } onload Expression to evaluate when a new partial is loaded .
*
* @ param { string = } autoscroll Whether ` ngInclude ` should call { @ link ng . $anchorScroll
* $anchorScroll } to scroll the viewport after the content is loaded .
*
* - If the attribute is not set , disable scrolling .
* - If the attribute is set without value , enable scrolling .
* - Otherwise enable scrolling only if the expression evaluates to truthy value .
*
* @ example
2018-05-05 12:13:16 +02:00
< example module = "includeExample" deps = "angular-animate.js" animations = "true" >
2016-04-18 12:34:29 +00:00
< file name = "index.html" >
2018-05-05 12:13:16 +02:00
< div ng - controller = "ExampleController" >
2016-04-18 12:34:29 +00:00
< select ng - model = "template" ng - options = "t.name for t in templates" >
< option value = "" > ( blank ) < / o p t i o n >
< / s e l e c t >
2018-05-05 12:13:16 +02:00
url of the template : < code > { { template . url } } < / c o d e >
2016-04-18 12:34:29 +00:00
< hr / >
< div class = "slide-animate-container" >
< div class = "slide-animate" ng - include = "template.url" > < / d i v >
< / d i v >
< / d i v >
< / f i l e >
< file name = "script.js" >
2018-05-05 12:13:16 +02:00
angular . module ( 'includeExample' , [ 'ngAnimate' ] )
. controller ( 'ExampleController' , [ '$scope' , function ( $scope ) {
$scope . templates =
[ { name : 'template1.html' , url : 'template1.html' } ,
{ name : 'template2.html' , url : 'template2.html' } ] ;
$scope . template = $scope . templates [ 0 ] ;
} ] ) ;
2016-04-18 12:34:29 +00:00
< / f i l e >
< file name = "template1.html" >
Content of template1 . html
< / f i l e >
< file name = "template2.html" >
Content of template2 . html
< / f i l e >
< file name = "animations.css" >
. slide - animate - container {
position : relative ;
background : white ;
border : 1 px solid black ;
height : 40 px ;
overflow : hidden ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
. slide - animate {
padding : 10 px ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
. slide - animate . ng - enter , . slide - animate . ng - leave {
- webkit - transition : all cubic - bezier ( 0.250 , 0.460 , 0.450 , 0.940 ) 0.5 s ;
transition : all cubic - bezier ( 0.250 , 0.460 , 0.450 , 0.940 ) 0.5 s ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
position : absolute ;
top : 0 ;
left : 0 ;
right : 0 ;
bottom : 0 ;
display : block ;
padding : 10 px ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
. slide - animate . ng - enter {
top : - 50 px ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
. slide - animate . ng - enter . ng - enter - active {
top : 0 ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
. slide - animate . ng - leave {
top : 0 ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
. slide - animate . ng - leave . ng - leave - active {
top : 50 px ;
}
< / f i l e >
2018-05-05 12:13:16 +02:00
< file name = "protractor.js" type = "protractor" >
var templateSelect = element ( by . model ( 'template' ) ) ;
var includeElem = element ( by . css ( '[ng-include]' ) ) ;
2016-04-18 12:34:29 +00:00
it ( 'should load template1.html' , function ( ) {
2018-05-05 12:13:16 +02:00
expect ( includeElem . getText ( ) ) . toMatch ( /Content of template1.html/ ) ;
2016-04-18 12:34:29 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
2016-04-18 12:34:29 +00:00
it ( 'should load template2.html' , function ( ) {
2018-05-05 12:13:16 +02:00
if ( browser . params . browser == 'firefox' ) {
// Firefox can't handle using selects
// See https://github.com/angular/protractor/issues/480
return ;
}
templateSelect . click ( ) ;
templateSelect . all ( by . css ( 'option' ) ) . get ( 2 ) . click ( ) ;
expect ( includeElem . getText ( ) ) . toMatch ( /Content of template2.html/ ) ;
2016-04-18 12:34:29 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
2016-04-18 12:34:29 +00:00
it ( 'should change to blank' , function ( ) {
2018-05-05 12:13:16 +02:00
if ( browser . params . browser == 'firefox' ) {
// Firefox can't handle using selects
return ;
}
templateSelect . click ( ) ;
templateSelect . all ( by . css ( 'option' ) ) . get ( 0 ) . click ( ) ;
expect ( includeElem . isPresent ( ) ) . toBe ( false ) ;
2016-04-18 12:34:29 +00:00
} ) ;
< / f i l e >
< / e x a m p l e >
* /
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
/ * *
* @ ngdoc event
2018-05-05 12:13:16 +02:00
* @ name ngInclude # $includeContentRequested
2016-04-18 12:34:29 +00:00
* @ eventType emit on the scope ngInclude was declared in
* @ description
* Emitted every time the ngInclude content is requested .
2018-05-05 12:13:16 +02:00
*
* @ param { Object } angularEvent Synthetic event object .
* @ param { String } src URL of content to load .
2016-04-18 12:34:29 +00:00
* /
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
/ * *
* @ ngdoc event
2018-05-05 12:13:16 +02:00
* @ name ngInclude # $includeContentLoaded
2016-04-18 12:34:29 +00:00
* @ eventType emit on the current ngInclude scope
* @ description
* Emitted every time the ngInclude content is reloaded .
2018-05-05 12:13:16 +02:00
*
* @ param { Object } angularEvent Synthetic event object .
* @ param { String } src URL of content to load .
* /
/ * *
* @ ngdoc event
* @ name ngInclude # $includeContentError
* @ eventType emit on the scope ngInclude was declared in
* @ description
* Emitted when a template HTTP request yields an erroneous response ( status < 200 || status > 299 )
*
* @ param { Object } angularEvent Synthetic event object .
* @ param { String } src URL of content to load .
2016-04-18 12:34:29 +00:00
* /
2018-05-05 12:13:16 +02:00
var ngIncludeDirective = [ '$templateRequest' , '$anchorScroll' , '$animate' ,
function ( $templateRequest , $anchorScroll , $animate ) {
2016-04-18 12:34:29 +00:00
return {
restrict : 'ECA' ,
priority : 400 ,
terminal : true ,
transclude : 'element' ,
controller : angular . noop ,
compile : function ( element , attr ) {
var srcExp = attr . ngInclude || attr . src ,
onloadExp = attr . onload || '' ,
autoScrollExp = attr . autoscroll ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
return function ( scope , $element , $attr , ctrl , $transclude ) {
var changeCounter = 0 ,
currentScope ,
2018-05-05 12:13:16 +02:00
previousElement ,
2016-04-18 12:34:29 +00:00
currentElement ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
var cleanupLastIncludeContent = function ( ) {
2018-05-05 12:13:16 +02:00
if ( previousElement ) {
previousElement . remove ( ) ;
previousElement = null ;
}
2016-04-18 12:34:29 +00:00
if ( currentScope ) {
currentScope . $destroy ( ) ;
currentScope = null ;
}
2018-05-05 12:13:16 +02:00
if ( currentElement ) {
$animate . leave ( currentElement ) . then ( function ( ) {
previousElement = null ;
} ) ;
previousElement = currentElement ;
2016-04-18 12:34:29 +00:00
currentElement = null ;
}
} ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
scope . $watch ( srcExp , function ngIncludeWatchAction ( src ) {
2016-04-18 12:34:29 +00:00
var afterAnimation = function ( ) {
if ( isDefined ( autoScrollExp ) && ( ! autoScrollExp || scope . $eval ( autoScrollExp ) ) ) {
$anchorScroll ( ) ;
}
} ;
var thisChangeId = ++ changeCounter ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( src ) {
2018-05-05 12:13:16 +02:00
//set the 2nd param to true to ignore the template request error so that the inner
//contents and scope can be cleaned up.
$templateRequest ( src , true ) . then ( function ( response ) {
2016-04-18 12:34:29 +00:00
if ( thisChangeId !== changeCounter ) return ;
var newScope = scope . $new ( ) ;
ctrl . template = response ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
// Note: This will also link all children of ng-include that were contained in the original
// html. If that content contains controllers, ... they could pollute/change the scope.
// However, using ng-include on an element with additional content does not make sense...
// Note: We can't remove them in the cloneAttchFn of $transclude as that
// function is called before linking the content, which would apply child
// directives to non existing elements.
var clone = $transclude ( newScope , function ( clone ) {
cleanupLastIncludeContent ( ) ;
2018-05-05 12:13:16 +02:00
$animate . enter ( clone , null , $element ) . then ( afterAnimation ) ;
2016-04-18 12:34:29 +00:00
} ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
currentScope = newScope ;
currentElement = clone ;
currentScope . $emit ( '$includeContentLoaded' , src ) ;
scope . $eval ( onloadExp ) ;
} , function ( ) {
if ( thisChangeId === changeCounter ) {
cleanupLastIncludeContent ( ) ;
scope . $emit ( '$includeContentError' , src ) ;
}
} ) ;
scope . $emit ( '$includeContentRequested' , src ) ;
} else {
cleanupLastIncludeContent ( ) ;
ctrl . template = null ;
}
} ) ;
} ;
}
} ;
} ] ;
// This directive is called during the $transclude call of the first `ngInclude` directive.
// It will replace and compile the content of the element with the loaded template.
// We need this directive so that the element content is already filled when
// the link function of another directive on the same element as ngInclude
// is called.
var ngIncludeFillContentDirective = [ '$compile' ,
function ( $compile ) {
return {
restrict : 'ECA' ,
priority : - 400 ,
require : 'ngInclude' ,
link : function ( scope , $element , $attr , ctrl ) {
if ( /SVG/ . test ( $element [ 0 ] . toString ( ) ) ) {
// WebKit: https://bugs.webkit.org/show_bug.cgi?id=135698 --- SVG elements do not
// support innerHTML, so detect this here and try to generate the contents
// specially.
$element . empty ( ) ;
$compile ( jqLiteBuildFragment ( ctrl . template , document ) . childNodes ) ( scope ,
function namespaceAdaptedClone ( clone ) {
$element . append ( clone ) ;
} , { futureParentElement : $element } ) ;
return ;
}
$element . html ( ctrl . template ) ;
$compile ( $element . contents ( ) ) ( scope ) ;
}
} ;
} ] ;
/ * *
* @ ngdoc directive
* @ name ngInit
* @ restrict AC
*
* @ description
* The ` ngInit ` directive allows you to evaluate an expression in the
* current scope .
*
* < div class = "alert alert-error" >
* The only appropriate use of ` ngInit ` is for aliasing special properties of
* { @ link ng . directive : ngRepeat ` ngRepeat ` } , as seen in the demo below . Besides this case , you
* should use { @ link guide / controller controllers } rather than ` ngInit `
* to initialize values on a scope .
* < / d i v >
* < div class = "alert alert-warning" >
* * * Note * * : If you have assignment in ` ngInit ` along with { @ link ng . $filter ` $ filter ` } , make
* sure you have parenthesis for correct precedence :
* < pre class = "prettyprint" >
* ` <div ng-init="test1 = (data | orderBy:'name')"></div> `
* < / p r e >
* < / d i v >
*
* @ priority 450
*
* @ element ANY
* @ param { expression } ngInit { @ link guide / expression Expression } to eval .
*
* @ example
< example module = "initExample" >
< file name = "index.html" >
< script >
angular . module ( 'initExample' , [ ] )
. controller ( 'ExampleController' , [ '$scope' , function ( $scope ) {
$scope . list = [ [ 'a' , 'b' ] , [ 'c' , 'd' ] ] ;
} ] ) ;
< / s c r i p t >
< div ng - controller = "ExampleController" >
< div ng - repeat = "innerList in list" ng - init = "outerIndex = $index" >
< div ng - repeat = "value in innerList" ng - init = "innerIndex = $index" >
< span class = "example-init" > list [ { { outerIndex } } ] [ { { innerIndex } } ] = { { value } } ; < / s p a n >
< / d i v >
< / d i v >
< / d i v >
< / f i l e >
< file name = "protractor.js" type = "protractor" >
it ( 'should alias index positions' , function ( ) {
var elements = element . all ( by . css ( '.example-init' ) ) ;
expect ( elements . get ( 0 ) . getText ( ) ) . toBe ( 'list[ 0 ][ 0 ] = a;' ) ;
expect ( elements . get ( 1 ) . getText ( ) ) . toBe ( 'list[ 0 ][ 1 ] = b;' ) ;
expect ( elements . get ( 2 ) . getText ( ) ) . toBe ( 'list[ 1 ][ 0 ] = c;' ) ;
expect ( elements . get ( 3 ) . getText ( ) ) . toBe ( 'list[ 1 ][ 1 ] = d;' ) ;
} ) ;
< / f i l e >
< / e x a m p l e >
* /
var ngInitDirective = ngDirective ( {
priority : 450 ,
compile : function ( ) {
return {
pre : function ( scope , element , attrs ) {
scope . $eval ( attrs . ngInit ) ;
}
} ;
}
} ) ;
/ * *
* @ ngdoc directive
* @ name ngList
*
* @ description
* Text input that converts between a delimited string and an array of strings . The default
* delimiter is a comma followed by a space - equivalent to ` ng-list=", " ` . You can specify a custom
* delimiter as the value of the ` ngList ` attribute - for example , ` ng-list=" | " ` .
*
* The behaviour of the directive is affected by the use of the ` ngTrim ` attribute .
* * If ` ngTrim ` is set to ` "false" ` then whitespace around both the separator and each
* list item is respected . This implies that the user of the directive is responsible for
* dealing with whitespace but also allows you to use whitespace as a delimiter , such as a
* tab or newline character .
* * Otherwise whitespace around the delimiter is ignored when splitting ( although it is respected
* when joining the list items back together ) and whitespace around each list item is stripped
* before it is added to the model .
*
* # # # Example with Validation
*
* < example name = "ngList-directive" module = "listExample" >
* < file name = "app.js" >
* angular . module ( 'listExample' , [ ] )
* . controller ( 'ExampleController' , [ '$scope' , function ( $scope ) {
* $scope . names = [ 'morpheus' , 'neo' , 'trinity' ] ;
* } ] ) ;
* < / f i l e >
* < file name = "index.html" >
* < form name = "myForm" ng - controller = "ExampleController" >
* List : < input name = "namesInput" ng - model = "names" ng - list required >
* < span class = "error" ng - show = "myForm.namesInput.$error.required" >
* Required ! < / s p a n >
* < br >
* < tt > names = { { names } } < /tt><br/ >
* < tt > myForm . namesInput . $valid = { { myForm . namesInput . $valid } } < /tt><br/ >
* < tt > myForm . namesInput . $error = { { myForm . namesInput . $error } } < /tt><br/ >
* < tt > myForm . $valid = { { myForm . $valid } } < /tt><br/ >
* < tt > myForm . $error . required = { { ! ! myForm . $error . required } } < /tt><br/ >
* < / f o r m >
* < / f i l e >
* < file name = "protractor.js" type = "protractor" >
* var listInput = element ( by . model ( 'names' ) ) ;
* var names = element ( by . exactBinding ( 'names' ) ) ;
* var valid = element ( by . binding ( 'myForm.namesInput.$valid' ) ) ;
* var error = element ( by . css ( 'span.error' ) ) ;
*
* it ( 'should initialize to model' , function ( ) {
* expect ( names . getText ( ) ) . toContain ( '["morpheus","neo","trinity"]' ) ;
* expect ( valid . getText ( ) ) . toContain ( 'true' ) ;
* expect ( error . getCssValue ( 'display' ) ) . toBe ( 'none' ) ;
* } ) ;
*
* it ( 'should be invalid if empty' , function ( ) {
* listInput . clear ( ) ;
* listInput . sendKeys ( '' ) ;
*
* expect ( names . getText ( ) ) . toContain ( '' ) ;
* expect ( valid . getText ( ) ) . toContain ( 'false' ) ;
* expect ( error . getCssValue ( 'display' ) ) . not . toBe ( 'none' ) ;
* } ) ;
* < / f i l e >
* < / e x a m p l e >
*
* # # # Example - splitting on whitespace
* < example name = "ngList-directive-newlines" >
* < file name = "index.html" >
* < textarea ng - model = "list" ng - list = " " ng - trim = "false" > < / t e x t a r e a >
* < pre > { { list | json } } < / p r e >
* < / f i l e >
* < file name = "protractor.js" type = "protractor" >
* it ( "should split the text by newlines" , function ( ) {
* var listInput = element ( by . model ( 'list' ) ) ;
* var output = element ( by . binding ( 'list | json' ) ) ;
* listInput . sendKeys ( 'abc\ndef\nghi' ) ;
* expect ( output . getText ( ) ) . toContain ( '[\n "abc",\n "def",\n "ghi"\n]' ) ;
* } ) ;
* < / f i l e >
* < / e x a m p l e >
*
* @ element input
* @ param { string = } ngList optional delimiter that should be used to split the value .
* /
var ngListDirective = function ( ) {
return {
restrict : 'A' ,
priority : 100 ,
require : 'ngModel' ,
link : function ( scope , element , attr , ctrl ) {
// We want to control whitespace trimming so we use this convoluted approach
// to access the ngList attribute, which doesn't pre-trim the attribute
var ngList = element . attr ( attr . $attr . ngList ) || ', ' ;
var trimValues = attr . ngTrim !== 'false' ;
var separator = trimValues ? trim ( ngList ) : ngList ;
var parse = function ( viewValue ) {
// If the viewValue is invalid (say required but empty) it will be `undefined`
if ( isUndefined ( viewValue ) ) return ;
var list = [ ] ;
if ( viewValue ) {
forEach ( viewValue . split ( separator ) , function ( value ) {
if ( value ) list . push ( trimValues ? trim ( value ) : value ) ;
} ) ;
}
return list ;
} ;
ctrl . $parsers . push ( parse ) ;
ctrl . $formatters . push ( function ( value ) {
if ( isArray ( value ) ) {
return value . join ( ngList ) ;
}
return undefined ;
} ) ;
// Override the standard $isEmpty because an empty array means the input is empty.
ctrl . $isEmpty = function ( value ) {
return ! value || ! value . length ;
} ;
}
} ;
} ;
/ * g l o b a l V A L I D _ C L A S S : t r u e ,
INVALID _CLASS : true ,
PRISTINE _CLASS : true ,
DIRTY _CLASS : true ,
UNTOUCHED _CLASS : true ,
TOUCHED _CLASS : true ,
* /
var VALID _CLASS = 'ng-valid' ,
INVALID _CLASS = 'ng-invalid' ,
PRISTINE _CLASS = 'ng-pristine' ,
DIRTY _CLASS = 'ng-dirty' ,
UNTOUCHED _CLASS = 'ng-untouched' ,
TOUCHED _CLASS = 'ng-touched' ,
PENDING _CLASS = 'ng-pending' ;
var ngModelMinErr = minErr ( 'ngModel' ) ;
/ * *
* @ ngdoc type
* @ name ngModel . NgModelController
*
* @ property { string } $viewValue Actual string value in the view .
* @ property { * } $modelValue The value in the model that the control is bound to .
* @ property { Array . < Function > } $parsers Array of functions to execute , as a pipeline , whenever
the control reads value from the DOM . The functions are called in array order , each passing
its return value through to the next . The last return value is forwarded to the
{ @ link ngModel . NgModelController # $validators ` $ validators ` } collection .
Parsers are used to sanitize / convert the { @ link ngModel . NgModelController # $viewValue
` $ viewValue ` } .
Returning ` undefined ` from a parser means a parse error occurred . In that case ,
no { @ link ngModel . NgModelController # $validators ` $ validators ` } will run and the ` ngModel `
will be set to ` undefined ` unless { @ link ngModelOptions ` ngModelOptions.allowInvalid ` }
is set to ` true ` . The parse error is stored in ` ngModel. $ error.parse ` .
*
* @ property { Array . < Function > } $formatters Array of functions to execute , as a pipeline , whenever
the model value changes . The functions are called in reverse array order , each passing the value through to the
next . The last return value is used as the actual DOM value .
Used to format / convert values for display in the control .
* ` ` ` js
* function formatter ( value ) {
* if ( value ) {
* return value . toUpperCase ( ) ;
* }
* }
* ngModel . $formatters . push ( formatter ) ;
* ` ` `
*
* @ property { Object . < string , function > } $validators A collection of validators that are applied
* whenever the model value changes . The key value within the object refers to the name of the
* validator while the function refers to the validation operation . The validation operation is
* provided with the model value as an argument and must return a true or false value depending
* on the response of that validation .
*
* ` ` ` js
* ngModel . $validators . validCharacters = function ( modelValue , viewValue ) {
* var value = modelValue || viewValue ;
* return /[0-9]+/ . test ( value ) &&
* /[a-z]+/ . test ( value ) &&
* /[A-Z]+/ . test ( value ) &&
* /\W+/ . test ( value ) ;
* } ;
* ` ` `
*
* @ property { Object . < string , function > } $asyncValidators A collection of validations that are expected to
* perform an asynchronous validation ( e . g . a HTTP request ) . The validation function that is provided
* is expected to return a promise when it is run during the model validation process . Once the promise
* is delivered then the validation status will be set to true when fulfilled and false when rejected .
* When the asynchronous validators are triggered , each of the validators will run in parallel and the model
* value will only be updated once all validators have been fulfilled . As long as an asynchronous validator
* is unfulfilled , its key will be added to the controllers ` $ pending ` property . Also , all asynchronous validators
* will only run once all synchronous validators have passed .
*
* Please note that if $http is used then it is important that the server returns a success HTTP response code
* in order to fulfill the validation and a status level of ` 4xx ` in order to reject the validation .
*
* ` ` ` js
* ngModel . $asyncValidators . uniqueUsername = function ( modelValue , viewValue ) {
* var value = modelValue || viewValue ;
*
* // Lookup user by username
* return $http . get ( '/api/users/' + value ) .
* then ( function resolved ( ) {
* //username exists, this means validation fails
* return $q . reject ( 'exists' ) ;
* } , function rejected ( ) {
* //username does not exist, therefore this validation passes
* return true ;
* } ) ;
* } ;
* ` ` `
*
* @ property { Array . < Function > } $viewChangeListeners Array of functions to execute whenever the
* view value has changed . It is called with no arguments , and its return value is ignored .
* This can be used in place of additional $watches against the model value .
*
* @ property { Object } $error An object hash with all failing validator ids as keys .
* @ property { Object } $pending An object hash with all pending validator ids as keys .
*
* @ property { boolean } $untouched True if control has not lost focus yet .
* @ property { boolean } $touched True if control has lost focus .
* @ property { boolean } $pristine True if user has not interacted with the control yet .
* @ property { boolean } $dirty True if user has already interacted with the control .
* @ property { boolean } $valid True if there is no error .
* @ property { boolean } $invalid True if at least one error on the control .
* @ property { string } $name The name attribute of the control .
*
* @ description
*
* ` NgModelController ` provides API for the { @ link ngModel ` ngModel ` } directive .
* The controller contains services for data - binding , validation , CSS updates , and value formatting
* and parsing . It purposefully does not contain any logic which deals with DOM rendering or
* listening to DOM events .
* Such DOM related logic should be provided by other directives which make use of
* ` NgModelController ` for data - binding to control elements .
* Angular provides this DOM logic for most { @ link input ` input ` } elements .
* At the end of this page you can find a { @ link ngModel . NgModelController # custom - control - example
* custom control example } that uses ` ngModelController ` to bind to ` contenteditable ` elements .
*
* @ example
* # # # Custom Control Example
* This example shows how to use ` NgModelController ` with a custom control to achieve
* data - binding . Notice how different directives ( ` contenteditable ` , ` ng-model ` , and ` required ` )
* collaborate together to achieve the desired result .
*
* ` contenteditable ` is an HTML5 attribute , which tells the browser to let the element
* contents be edited in place by the user .
*
* We are using the { @ link ng . service : $sce $sce } service here and include the { @ link ngSanitize $sanitize }
* module to automatically remove "bad" content like inline event listener ( e . g . ` <span onclick="..."> ` ) .
* However , as we are using ` $ sce ` the model can still decide to provide unsafe content if it marks
* that content using the ` $ sce ` service .
*
* < example name = "NgModelController" module = "customControl" deps = "angular-sanitize.js" >
< file name = "style.css" >
[ contenteditable ] {
border : 1 px solid black ;
background - color : white ;
min - height : 20 px ;
}
. ng - invalid {
border : 1 px solid red ;
}
< / f i l e >
< file name = "script.js" >
angular . module ( 'customControl' , [ 'ngSanitize' ] ) .
directive ( 'contenteditable' , [ '$sce' , function ( $sce ) {
return {
restrict : 'A' , // only activate on element attribute
require : '?ngModel' , // get a hold of NgModelController
link : function ( scope , element , attrs , ngModel ) {
if ( ! ngModel ) return ; // do nothing if no ng-model
// Specify how UI should be updated
ngModel . $render = function ( ) {
element . html ( $sce . getTrustedHtml ( ngModel . $viewValue || '' ) ) ;
} ;
// Listen for change events to enable binding
element . on ( 'blur keyup change' , function ( ) {
scope . $evalAsync ( read ) ;
} ) ;
read ( ) ; // initialize
// Write data to the model
function read ( ) {
var html = element . html ( ) ;
// When we clear the content editable the browser leaves a <br> behind
// If strip-br attribute is provided then we strip this out
if ( attrs . stripBr && html == '<br>' ) {
html = '' ;
}
ngModel . $setViewValue ( html ) ;
}
}
} ;
} ] ) ;
< / f i l e >
< file name = "index.html" >
< form name = "myForm" >
< div contenteditable
name = "myWidget" ng - model = "userContent"
strip - br = "true"
required > Change me ! < / d i v >
< span ng - show = "myForm.myWidget.$error.required" > Required ! < / s p a n >
< hr >
< textarea ng - model = "userContent" > < / t e x t a r e a >
< / f o r m >
< / f i l e >
< file name = "protractor.js" type = "protractor" >
it ( 'should data-bind and become invalid' , function ( ) {
if ( browser . params . browser == 'safari' || browser . params . browser == 'firefox' ) {
// SafariDriver can't handle contenteditable
// and Firefox driver can't clear contenteditables very well
return ;
}
var contentEditable = element ( by . css ( '[contenteditable]' ) ) ;
var content = 'Change me!' ;
expect ( contentEditable . getText ( ) ) . toEqual ( content ) ;
contentEditable . clear ( ) ;
contentEditable . sendKeys ( protractor . Key . BACK _SPACE ) ;
expect ( contentEditable . getText ( ) ) . toEqual ( '' ) ;
expect ( contentEditable . getAttribute ( 'class' ) ) . toMatch ( /ng-invalid-required/ ) ;
} ) ;
< / f i l e >
* < / e x a m p l e >
*
*
* /
var NgModelController = [ '$scope' , '$exceptionHandler' , '$attrs' , '$element' , '$parse' , '$animate' , '$timeout' , '$rootScope' , '$q' , '$interpolate' ,
function ( $scope , $exceptionHandler , $attr , $element , $parse , $animate , $timeout , $rootScope , $q , $interpolate ) {
this . $viewValue = Number . NaN ;
this . $modelValue = Number . NaN ;
this . $$rawModelValue = undefined ; // stores the parsed modelValue / model set from scope regardless of validity.
this . $validators = { } ;
this . $asyncValidators = { } ;
this . $parsers = [ ] ;
this . $formatters = [ ] ;
this . $viewChangeListeners = [ ] ;
this . $untouched = true ;
this . $touched = false ;
this . $pristine = true ;
this . $dirty = false ;
this . $valid = true ;
this . $invalid = false ;
this . $error = { } ; // keep invalid keys here
this . $$success = { } ; // keep valid keys here
this . $pending = undefined ; // keep pending keys here
this . $name = $interpolate ( $attr . name || '' , false ) ( $scope ) ;
var parsedNgModel = $parse ( $attr . ngModel ) ,
parsedNgModelAssign = parsedNgModel . assign ,
ngModelGet = parsedNgModel ,
ngModelSet = parsedNgModelAssign ,
pendingDebounce = null ,
parserValid ,
ctrl = this ;
this . $$setOptions = function ( options ) {
ctrl . $options = options ;
if ( options && options . getterSetter ) {
var invokeModelGetter = $parse ( $attr . ngModel + '()' ) ,
invokeModelSetter = $parse ( $attr . ngModel + '($$$p)' ) ;
ngModelGet = function ( $scope ) {
var modelValue = parsedNgModel ( $scope ) ;
if ( isFunction ( modelValue ) ) {
modelValue = invokeModelGetter ( $scope ) ;
}
return modelValue ;
} ;
ngModelSet = function ( $scope , newValue ) {
if ( isFunction ( parsedNgModel ( $scope ) ) ) {
invokeModelSetter ( $scope , { $$$p : ctrl . $modelValue } ) ;
} else {
parsedNgModelAssign ( $scope , ctrl . $modelValue ) ;
}
} ;
} else if ( ! parsedNgModel . assign ) {
throw ngModelMinErr ( 'nonassign' , "Expression '{0}' is non-assignable. Element: {1}" ,
$attr . ngModel , startingTag ( $element ) ) ;
}
} ;
/ * *
* @ ngdoc method
* @ name ngModel . NgModelController # $render
*
* @ description
* Called when the view needs to be updated . It is expected that the user of the ng - model
* directive will implement this method .
*
* The ` $ render() ` method is invoked in the following situations :
*
* * ` $ rollbackViewValue() ` is called . If we are rolling back the view value to the last
* committed value then ` $ render() ` is called to update the input control .
* * The value referenced by ` ng-model ` is changed programmatically and both the ` $ modelValue ` and
* the ` $ viewValue ` are different to last time .
*
* Since ` ng-model ` does not do a deep watch , ` $ render() ` is only invoked if the values of
* ` $ modelValue ` and ` $ viewValue ` are actually different to their previous value . If ` $ modelValue `
* or ` $ viewValue ` are objects ( rather than a string or number ) then ` $ render() ` will not be
* invoked if you only change a property on the objects .
* /
this . $render = noop ;
/ * *
* @ ngdoc method
* @ name ngModel . NgModelController # $isEmpty
*
* @ description
* This is called when we need to determine if the value of an input is empty .
*
* For instance , the required directive does this to work out if the input has data or not .
*
* The default ` $ isEmpty ` function checks whether the value is ` undefined ` , ` '' ` , ` null ` or ` NaN ` .
*
* You can override this for input directives whose concept of being empty is different to the
* default . The ` checkboxInputType ` directive does this because in its case a value of ` false `
* implies empty .
*
* @ param { * } value The value of the input to check for emptiness .
* @ returns { boolean } True if ` value ` is "empty" .
* /
this . $isEmpty = function ( value ) {
return isUndefined ( value ) || value === '' || value === null || value !== value ;
} ;
var parentForm = $element . inheritedData ( '$formController' ) || nullFormCtrl ,
currentValidationRunId = 0 ;
/ * *
* @ ngdoc method
* @ name ngModel . NgModelController # $setValidity
*
* @ description
* Change the validity state , and notify the form .
*
* This method can be called within $parsers / $formatters or a custom validation implementation .
* However , in most cases it should be sufficient to use the ` ngModel. $ validators ` and
* ` ngModel. $ asyncValidators ` collections which will call ` $ setValidity ` automatically .
*
* @ param { string } validationErrorKey Name of the validator . The ` validationErrorKey ` will be assigned
* to either ` $ error[validationErrorKey] ` or ` $ pending[validationErrorKey] `
* ( for unfulfilled ` $ asyncValidators ` ) , so that it is available for data - binding .
* The ` validationErrorKey ` should be in camelCase and will get converted into dash - case
* for class name . Example : ` myError ` will result in ` ng-valid-my-error ` and ` ng-invalid-my-error `
* class and can be bound to as ` {{someForm.someControl. $ error.myError}} ` .
* @ param { boolean } isValid Whether the current state is valid ( true ) , invalid ( false ) , pending ( undefined ) ,
* or skipped ( null ) . Pending is used for unfulfilled ` $ asyncValidators ` .
* Skipped is used by Angular when validators do not run because of parse errors and
* when ` $ asyncValidators ` do not run because any of the ` $ validators ` failed .
* /
addSetValidityMethod ( {
ctrl : this ,
$element : $element ,
set : function ( object , property ) {
object [ property ] = true ;
} ,
unset : function ( object , property ) {
delete object [ property ] ;
} ,
parentForm : parentForm ,
$animate : $animate
} ) ;
/ * *
* @ ngdoc method
* @ name ngModel . NgModelController # $setPristine
*
* @ description
* Sets the control to its pristine state .
*
* This method can be called to remove the ` ng-dirty ` class and set the control to its pristine
* state ( ` ng-pristine ` class ) . A model is considered to be pristine when the control
* has not been changed from when first compiled .
* /
this . $setPristine = function ( ) {
ctrl . $dirty = false ;
ctrl . $pristine = true ;
$animate . removeClass ( $element , DIRTY _CLASS ) ;
$animate . addClass ( $element , PRISTINE _CLASS ) ;
} ;
/ * *
* @ ngdoc method
* @ name ngModel . NgModelController # $setDirty
*
* @ description
* Sets the control to its dirty state .
*
* This method can be called to remove the ` ng-pristine ` class and set the control to its dirty
* state ( ` ng-dirty ` class ) . A model is considered to be dirty when the control has been changed
* from when first compiled .
* /
this . $setDirty = function ( ) {
ctrl . $dirty = true ;
ctrl . $pristine = false ;
$animate . removeClass ( $element , PRISTINE _CLASS ) ;
$animate . addClass ( $element , DIRTY _CLASS ) ;
parentForm . $setDirty ( ) ;
} ;
/ * *
* @ ngdoc method
* @ name ngModel . NgModelController # $setUntouched
*
* @ description
* Sets the control to its untouched state .
*
* This method can be called to remove the ` ng-touched ` class and set the control to its
* untouched state ( ` ng-untouched ` class ) . Upon compilation , a model is set as untouched
* by default , however this function can be used to restore that state if the model has
* already been touched by the user .
* /
this . $setUntouched = function ( ) {
ctrl . $touched = false ;
ctrl . $untouched = true ;
$animate . setClass ( $element , UNTOUCHED _CLASS , TOUCHED _CLASS ) ;
} ;
/ * *
* @ ngdoc method
* @ name ngModel . NgModelController # $setTouched
*
* @ description
* Sets the control to its touched state .
*
* This method can be called to remove the ` ng-untouched ` class and set the control to its
* touched state ( ` ng-touched ` class ) . A model is considered to be touched when the user has
* first focused the control element and then shifted focus away from the control ( blur event ) .
* /
this . $setTouched = function ( ) {
ctrl . $touched = true ;
ctrl . $untouched = false ;
$animate . setClass ( $element , TOUCHED _CLASS , UNTOUCHED _CLASS ) ;
} ;
/ * *
* @ ngdoc method
* @ name ngModel . NgModelController # $rollbackViewValue
*
* @ description
* Cancel an update and reset the input element ' s value to prevent an update to the ` $ modelValue ` ,
* which may be caused by a pending debounced event or because the input is waiting for a some
* future event .
*
* If you have an input that uses ` ng-model-options ` to set up debounced events or events such
* as blur you can have a situation where there is a period when the ` $ viewValue `
* is out of synch with the ngModel ' s ` $ modelValue ` .
*
* In this case , you can run into difficulties if you try to update the ngModel ' s ` $ modelValue `
* programmatically before these debounced / future events have resolved / occurred , because Angular ' s
* dirty checking mechanism is not able to tell whether the model has actually changed or not .
*
* The ` $ rollbackViewValue() ` method should be called before programmatically changing the model of an
* input which may have such events pending . This is important in order to make sure that the
* input field will be updated with the new model value and any pending operations are cancelled .
*
* < example name = "ng-model-cancel-update" module = "cancel-update-example" >
* < file name = "app.js" >
* angular . module ( 'cancel-update-example' , [ ] )
*
* . controller ( 'CancelUpdateController' , [ '$scope' , function ( $scope ) {
* $scope . resetWithCancel = function ( e ) {
* if ( e . keyCode == 27 ) {
* $scope . myForm . myInput1 . $rollbackViewValue ( ) ;
* $scope . myValue = '' ;
* }
* } ;
* $scope . resetWithoutCancel = function ( e ) {
* if ( e . keyCode == 27 ) {
* $scope . myValue = '' ;
* }
* } ;
* } ] ) ;
* < / f i l e >
* < file name = "index.html" >
* < div ng - controller = "CancelUpdateController" >
* < p > Try typing something in each input . See that the model only updates when you
* blur off the input .
* < / p >
* < p > Now see what happens if you start typing then press the Escape key < / p >
*
* < form name = "myForm" ng - model - options = "{ updateOn: 'blur' }" >
* < p > With $rollbackViewValue ( ) < / p >
* < input name = "myInput1" ng - model = "myValue" ng - keydown = "resetWithCancel($event)" > < br / >
* myValue : "{{ myValue }}"
*
* < p > Without $rollbackViewValue ( ) < / p >
* < input name = "myInput2" ng - model = "myValue" ng - keydown = "resetWithoutCancel($event)" > < br / >
* myValue : "{{ myValue }}"
* < / f o r m >
* < / d i v >
* < / f i l e >
* < / e x a m p l e >
* /
this . $rollbackViewValue = function ( ) {
$timeout . cancel ( pendingDebounce ) ;
ctrl . $viewValue = ctrl . $$lastCommittedViewValue ;
ctrl . $render ( ) ;
} ;
/ * *
* @ ngdoc method
* @ name ngModel . NgModelController # $validate
*
* @ description
* Runs each of the registered validators ( first synchronous validators and then
* asynchronous validators ) .
* If the validity changes to invalid , the model will be set to ` undefined ` ,
* unless { @ link ngModelOptions ` ngModelOptions.allowInvalid ` } is ` true ` .
* If the validity changes to valid , it will set the model to the last available valid
* ` $ modelValue ` , i . e . either the last parsed value or the last value set from the scope .
* /
this . $validate = function ( ) {
// ignore $validate before model is initialized
if ( isNumber ( ctrl . $modelValue ) && isNaN ( ctrl . $modelValue ) ) {
return ;
}
var viewValue = ctrl . $$lastCommittedViewValue ;
// Note: we use the $$rawModelValue as $modelValue might have been
// set to undefined during a view -> model update that found validation
// errors. We can't parse the view here, since that could change
// the model although neither viewValue nor the model on the scope changed
var modelValue = ctrl . $$rawModelValue ;
var prevValid = ctrl . $valid ;
var prevModelValue = ctrl . $modelValue ;
var allowInvalid = ctrl . $options && ctrl . $options . allowInvalid ;
ctrl . $$runValidators ( modelValue , viewValue , function ( allValid ) {
// If there was no change in validity, don't update the model
// This prevents changing an invalid modelValue to undefined
if ( ! allowInvalid && prevValid !== allValid ) {
// Note: Don't check ctrl.$valid here, as we could have
// external validators (e.g. calculated on the server),
// that just call $setValidity and need the model value
// to calculate their validity.
ctrl . $modelValue = allValid ? modelValue : undefined ;
if ( ctrl . $modelValue !== prevModelValue ) {
ctrl . $$writeModelToScope ( ) ;
}
}
} ) ;
} ;
this . $$runValidators = function ( modelValue , viewValue , doneCallback ) {
currentValidationRunId ++ ;
var localValidationRunId = currentValidationRunId ;
// check parser error
if ( ! processParseErrors ( ) ) {
validationDone ( false ) ;
return ;
}
if ( ! processSyncValidators ( ) ) {
validationDone ( false ) ;
return ;
}
processAsyncValidators ( ) ;
function processParseErrors ( ) {
var errorKey = ctrl . $$parserName || 'parse' ;
if ( parserValid === undefined ) {
setValidity ( errorKey , null ) ;
} else {
if ( ! parserValid ) {
forEach ( ctrl . $validators , function ( v , name ) {
setValidity ( name , null ) ;
} ) ;
forEach ( ctrl . $asyncValidators , function ( v , name ) {
setValidity ( name , null ) ;
} ) ;
}
// Set the parse error last, to prevent unsetting it, should a $validators key == parserName
setValidity ( errorKey , parserValid ) ;
return parserValid ;
}
return true ;
}
function processSyncValidators ( ) {
var syncValidatorsValid = true ;
forEach ( ctrl . $validators , function ( validator , name ) {
var result = validator ( modelValue , viewValue ) ;
syncValidatorsValid = syncValidatorsValid && result ;
setValidity ( name , result ) ;
} ) ;
if ( ! syncValidatorsValid ) {
forEach ( ctrl . $asyncValidators , function ( v , name ) {
setValidity ( name , null ) ;
} ) ;
return false ;
}
return true ;
}
function processAsyncValidators ( ) {
var validatorPromises = [ ] ;
var allValid = true ;
forEach ( ctrl . $asyncValidators , function ( validator , name ) {
var promise = validator ( modelValue , viewValue ) ;
if ( ! isPromiseLike ( promise ) ) {
throw ngModelMinErr ( "$asyncValidators" ,
"Expected asynchronous validator to return a promise but got '{0}' instead." , promise ) ;
}
setValidity ( name , undefined ) ;
validatorPromises . push ( promise . then ( function ( ) {
setValidity ( name , true ) ;
} , function ( error ) {
allValid = false ;
setValidity ( name , false ) ;
} ) ) ;
} ) ;
if ( ! validatorPromises . length ) {
validationDone ( true ) ;
} else {
$q . all ( validatorPromises ) . then ( function ( ) {
validationDone ( allValid ) ;
} , noop ) ;
}
}
function setValidity ( name , isValid ) {
if ( localValidationRunId === currentValidationRunId ) {
ctrl . $setValidity ( name , isValid ) ;
}
}
function validationDone ( allValid ) {
if ( localValidationRunId === currentValidationRunId ) {
doneCallback ( allValid ) ;
}
}
} ;
/ * *
* @ ngdoc method
* @ name ngModel . NgModelController # $commitViewValue
*
* @ description
* Commit a pending update to the ` $ modelValue ` .
*
* Updates may be pending by a debounced event or because the input is waiting for a some future
* event defined in ` ng-model-options ` . this method is rarely needed as ` NgModelController `
* usually handles calling this in response to input events .
* /
this . $commitViewValue = function ( ) {
var viewValue = ctrl . $viewValue ;
$timeout . cancel ( pendingDebounce ) ;
// If the view value has not changed then we should just exit, except in the case where there is
// a native validator on the element. In this case the validation state may have changed even though
// the viewValue has stayed empty.
if ( ctrl . $$lastCommittedViewValue === viewValue && ( viewValue !== '' || ! ctrl . $$hasNativeValidators ) ) {
return ;
}
ctrl . $$lastCommittedViewValue = viewValue ;
// change to dirty
if ( ctrl . $pristine ) {
this . $setDirty ( ) ;
}
this . $$parseAndValidate ( ) ;
} ;
this . $$parseAndValidate = function ( ) {
var viewValue = ctrl . $$lastCommittedViewValue ;
var modelValue = viewValue ;
parserValid = isUndefined ( modelValue ) ? undefined : true ;
if ( parserValid ) {
for ( var i = 0 ; i < ctrl . $parsers . length ; i ++ ) {
modelValue = ctrl . $parsers [ i ] ( modelValue ) ;
if ( isUndefined ( modelValue ) ) {
parserValid = false ;
break ;
}
}
}
if ( isNumber ( ctrl . $modelValue ) && isNaN ( ctrl . $modelValue ) ) {
// ctrl.$modelValue has not been touched yet...
ctrl . $modelValue = ngModelGet ( $scope ) ;
}
var prevModelValue = ctrl . $modelValue ;
var allowInvalid = ctrl . $options && ctrl . $options . allowInvalid ;
ctrl . $$rawModelValue = modelValue ;
if ( allowInvalid ) {
ctrl . $modelValue = modelValue ;
writeToModelIfNeeded ( ) ;
}
// Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date.
// This can happen if e.g. $setViewValue is called from inside a parser
ctrl . $$runValidators ( modelValue , ctrl . $$lastCommittedViewValue , function ( allValid ) {
if ( ! allowInvalid ) {
// Note: Don't check ctrl.$valid here, as we could have
// external validators (e.g. calculated on the server),
// that just call $setValidity and need the model value
// to calculate their validity.
ctrl . $modelValue = allValid ? modelValue : undefined ;
writeToModelIfNeeded ( ) ;
}
} ) ;
function writeToModelIfNeeded ( ) {
if ( ctrl . $modelValue !== prevModelValue ) {
ctrl . $$writeModelToScope ( ) ;
}
}
} ;
this . $$writeModelToScope = function ( ) {
ngModelSet ( $scope , ctrl . $modelValue ) ;
forEach ( ctrl . $viewChangeListeners , function ( listener ) {
try {
listener ( ) ;
} catch ( e ) {
$exceptionHandler ( e ) ;
}
} ) ;
} ;
/ * *
* @ ngdoc method
* @ name ngModel . NgModelController # $setViewValue
*
* @ description
* Update the view value .
*
* This method should be called when an input directive want to change the view value ; typically ,
* this is done from within a DOM event handler .
*
* For example { @ link ng . directive : input input } calls it when the value of the input changes and
* { @ link ng . directive : select select } calls it when an option is selected .
*
* If the new ` value ` is an object ( rather than a string or a number ) , we should make a copy of the
* object before passing it to ` $ setViewValue ` . This is because ` ngModel ` does not perform a deep
* watch of objects , it only looks for a change of identity . If you only change the property of
* the object then ngModel will not realise that the object has changed and will not invoke the
* ` $ parsers ` and ` $ validators ` pipelines .
*
* For this reason , you should not change properties of the copy once it has been passed to
* ` $ setViewValue ` . Otherwise you may cause the model value on the scope to change incorrectly .
*
* When this method is called , the new ` value ` will be staged for committing through the ` $ parsers `
* and ` $ validators ` pipelines . If there are no special { @ link ngModelOptions } specified then the staged
* value sent directly for processing , finally to be applied to ` $ modelValue ` and then the
* * * expression * * specified in the ` ng-model ` attribute .
*
* Lastly , all the registered change listeners , in the ` $ viewChangeListeners ` list , are called .
*
* In case the { @ link ng . directive : ngModelOptions ngModelOptions } directive is used with ` updateOn `
* and the ` default ` trigger is not listed , all those actions will remain pending until one of the
* ` updateOn ` events is triggered on the DOM element .
* All these actions will be debounced if the { @ link ng . directive : ngModelOptions ngModelOptions }
* directive is used with a custom debounce for this particular event .
*
* Note that calling this function does not trigger a ` $ digest ` .
*
* @ param { string } value Value from the view .
* @ param { string } trigger Event that triggered the update .
* /
this . $setViewValue = function ( value , trigger ) {
ctrl . $viewValue = value ;
if ( ! ctrl . $options || ctrl . $options . updateOnDefault ) {
ctrl . $$debounceViewValueCommit ( trigger ) ;
}
} ;
this . $$debounceViewValueCommit = function ( trigger ) {
var debounceDelay = 0 ,
options = ctrl . $options ,
debounce ;
if ( options && isDefined ( options . debounce ) ) {
debounce = options . debounce ;
if ( isNumber ( debounce ) ) {
debounceDelay = debounce ;
} else if ( isNumber ( debounce [ trigger ] ) ) {
debounceDelay = debounce [ trigger ] ;
} else if ( isNumber ( debounce [ 'default' ] ) ) {
debounceDelay = debounce [ 'default' ] ;
}
}
$timeout . cancel ( pendingDebounce ) ;
if ( debounceDelay ) {
pendingDebounce = $timeout ( function ( ) {
ctrl . $commitViewValue ( ) ;
} , debounceDelay ) ;
} else if ( $rootScope . $$phase ) {
ctrl . $commitViewValue ( ) ;
} else {
$scope . $apply ( function ( ) {
ctrl . $commitViewValue ( ) ;
} ) ;
}
} ;
// model -> value
// Note: we cannot use a normal scope.$watch as we want to detect the following:
// 1. scope value is 'a'
// 2. user enters 'b'
// 3. ng-change kicks in and reverts scope value to 'a'
// -> scope value did not change since the last digest as
// ng-change executes in apply phase
// 4. view should be changed back to 'a'
$scope . $watch ( function ngModelWatch ( ) {
var modelValue = ngModelGet ( $scope ) ;
// if scope model value and ngModel value are out of sync
// TODO(perf): why not move this to the action fn?
if ( modelValue !== ctrl . $modelValue &&
// checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator
( ctrl . $modelValue === ctrl . $modelValue || modelValue === modelValue )
) {
ctrl . $modelValue = ctrl . $$rawModelValue = modelValue ;
parserValid = undefined ;
var formatters = ctrl . $formatters ,
idx = formatters . length ;
var viewValue = modelValue ;
while ( idx -- ) {
viewValue = formatters [ idx ] ( viewValue ) ;
}
if ( ctrl . $viewValue !== viewValue ) {
ctrl . $viewValue = ctrl . $$lastCommittedViewValue = viewValue ;
ctrl . $render ( ) ;
ctrl . $$runValidators ( modelValue , viewValue , noop ) ;
}
}
return modelValue ;
} ) ;
} ] ;
/ * *
* @ ngdoc directive
* @ name ngModel
*
* @ element input
* @ priority 1
*
* @ description
* The ` ngModel ` directive binds an ` input ` , ` select ` , ` textarea ` ( or custom form control ) to a
* property on the scope using { @ link ngModel . NgModelController NgModelController } ,
* which is created and exposed by this directive .
*
* ` ngModel ` is responsible for :
*
* - Binding the view into the model , which other directives such as ` input ` , ` textarea ` or ` select `
* require .
* - Providing validation behavior ( i . e . required , number , email , url ) .
* - Keeping the state of the control ( valid / invalid , dirty / pristine , touched / untouched , validation errors ) .
* - Setting related css classes on the element ( ` ng-valid ` , ` ng-invalid ` , ` ng-dirty ` , ` ng-pristine ` , ` ng-touched ` , ` ng-untouched ` ) including animations .
* - Registering the control with its parent { @ link ng . directive : form form } .
*
* Note : ` ngModel ` will try to bind to the property given by evaluating the expression on the
* current scope . If the property doesn ' t already exist on this scope , it will be created
* implicitly and added to the scope .
*
* For best practices on using ` ngModel ` , see :
*
* - [ Understanding Scopes ] ( https : //github.com/angular/angular.js/wiki/Understanding-Scopes)
*
* For basic examples , how to use ` ngModel ` , see :
*
* - { @ link ng . directive : input input }
* - { @ link input [ text ] text }
* - { @ link input [ checkbox ] checkbox }
* - { @ link input [ radio ] radio }
* - { @ link input [ number ] number }
* - { @ link input [ email ] email }
* - { @ link input [ url ] url }
* - { @ link input [ date ] date }
* - { @ link input [ datetime - local ] datetime - local }
* - { @ link input [ time ] time }
* - { @ link input [ month ] month }
* - { @ link input [ week ] week }
* - { @ link ng . directive : select select }
* - { @ link ng . directive : textarea textarea }
*
* # CSS classes
* The following CSS classes are added and removed on the associated input / select / textarea element
* depending on the validity of the model .
*
* - ` ng-valid ` : the model is valid
* - ` ng-invalid ` : the model is invalid
* - ` ng-valid-[key] ` : for each valid key added by ` $ setValidity `
* - ` ng-invalid-[key] ` : for each invalid key added by ` $ setValidity `
* - ` ng-pristine ` : the control hasn ' t been interacted with yet
* - ` ng-dirty ` : the control has been interacted with
* - ` ng-touched ` : the control has been blurred
* - ` ng-untouched ` : the control hasn ' t been blurred
* - ` ng-pending ` : any ` $ asyncValidators ` are unfulfilled
*
* Keep in mind that ngAnimate can detect each of these classes when added and removed .
*
* # # Animation Hooks
*
* Animations within models are triggered when any of the associated CSS classes are added and removed
* on the input element which is attached to the model . These classes are : ` .ng-pristine ` , ` .ng-dirty ` ,
* ` .ng-invalid ` and ` .ng-valid ` as well as any other validations that are performed on the model itself .
* The animations that are triggered within ngModel are similar to how they work in ngClass and
* animations can be hooked into using CSS transitions , keyframes as well as JS animations .
*
* The following example shows a simple way to utilize CSS transitions to style an input element
* that has been rendered as invalid after it has been validated :
*
* < pre >
* //be sure to include ngAnimate as a module to hook into more
* //advanced animations
* . my - input {
* transition : 0.5 s linear all ;
* background : white ;
* }
* . my - input . ng - invalid {
* background : red ;
* color : white ;
* }
* < / p r e >
*
* @ example
* < example deps = "angular-animate.js" animations = "true" fixBase = "true" module = "inputExample" >
< file name = "index.html" >
< script >
angular . module ( 'inputExample' , [ ] )
. controller ( 'ExampleController' , [ '$scope' , function ( $scope ) {
$scope . val = '1' ;
} ] ) ;
< / s c r i p t >
< style >
. my - input {
- webkit - transition : all linear 0.5 s ;
transition : all linear 0.5 s ;
background : transparent ;
}
. my - input . ng - invalid {
color : white ;
background : red ;
}
< / s t y l e >
Update input to see transitions when valid / invalid .
Integer is a valid value .
< form name = "testForm" ng - controller = "ExampleController" >
< input ng - model = "val" ng - pattern = "/^\d+$/" name = "anim" class = "my-input" / >
< / f o r m >
< / f i l e >
* < / e x a m p l e >
*
* # # Binding to a getter / setter
*
* Sometimes it ' s helpful to bind ` ngModel ` to a getter / setter function . A getter / setter is a
* function that returns a representation of the model when called with zero arguments , and sets
* the internal state of a model when called with an argument . It ' s sometimes useful to use this
* for models that have an internal representation that ' s different than what the model exposes
* to the view .
*
* < div class = "alert alert-success" >
* * * Best Practice : * * It ' s best to keep getters fast because Angular is likely to call them more
* frequently than other parts of your code .
* < / d i v >
*
* You use this behavior by adding ` ng-model-options="{ getterSetter: true }" ` to an element that
* has ` ng-model ` attached to it . You can also add ` ng-model-options="{ getterSetter: true }" ` to
* a ` <form> ` , which will enable this behavior for all ` <input> ` s within it . See
* { @ link ng . directive : ngModelOptions ` ngModelOptions ` } for more .
*
* The following example shows how to use ` ngModel ` with a getter / setter :
*
* @ example
* < example name = "ngModel-getter-setter" module = "getterSetterExample" >
< file name = "index.html" >
< div ng - controller = "ExampleController" >
< form name = "userForm" >
Name :
< input type = "text" name = "userName"
ng - model = "user.name"
ng - model - options = "{ getterSetter: true }" / >
< / f o r m >
< pre > user . name = < span ng - bind = "user.name()" > < / s p a n > < / p r e >
< / d i v >
< / f i l e >
< file name = "app.js" >
angular . module ( 'getterSetterExample' , [ ] )
. controller ( 'ExampleController' , [ '$scope' , function ( $scope ) {
var _name = 'Brian' ;
$scope . user = {
name : function ( newName ) {
// Note that newName can be undefined for two reasons:
// 1. Because it is called as a getter and thus called with no arguments
// 2. Because the property should actually be set to undefined. This happens e.g. if the
// input is invalid
return arguments . length ? ( _name = newName ) : _name ;
}
} ;
} ] ) ;
< / f i l e >
* < / e x a m p l e >
* /
var ngModelDirective = [ '$rootScope' , function ( $rootScope ) {
return {
restrict : 'A' ,
require : [ 'ngModel' , '^?form' , '^?ngModelOptions' ] ,
controller : NgModelController ,
// Prelink needs to run before any input directive
// so that we can set the NgModelOptions in NgModelController
// before anyone else uses it.
priority : 1 ,
compile : function ngModelCompile ( element ) {
// Setup initial state of the control
element . addClass ( PRISTINE _CLASS ) . addClass ( UNTOUCHED _CLASS ) . addClass ( VALID _CLASS ) ;
return {
pre : function ngModelPreLink ( scope , element , attr , ctrls ) {
var modelCtrl = ctrls [ 0 ] ,
formCtrl = ctrls [ 1 ] || nullFormCtrl ;
modelCtrl . $$setOptions ( ctrls [ 2 ] && ctrls [ 2 ] . $options ) ;
// notify others, especially parent forms
formCtrl . $addControl ( modelCtrl ) ;
attr . $observe ( 'name' , function ( newValue ) {
if ( modelCtrl . $name !== newValue ) {
formCtrl . $$renameControl ( modelCtrl , newValue ) ;
}
} ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
scope . $on ( '$destroy' , function ( ) {
formCtrl . $removeControl ( modelCtrl ) ;
} ) ;
} ,
post : function ngModelPostLink ( scope , element , attr , ctrls ) {
var modelCtrl = ctrls [ 0 ] ;
if ( modelCtrl . $options && modelCtrl . $options . updateOn ) {
element . on ( modelCtrl . $options . updateOn , function ( ev ) {
modelCtrl . $$debounceViewValueCommit ( ev && ev . type ) ;
2016-04-18 12:34:29 +00:00
} ) ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
element . on ( 'blur' , function ( ev ) {
if ( modelCtrl . $touched ) return ;
if ( $rootScope . $$phase ) {
scope . $evalAsync ( modelCtrl . $setTouched ) ;
} else {
scope . $apply ( modelCtrl . $setTouched ) ;
}
} ) ;
}
2016-04-18 12:34:29 +00:00
} ;
}
} ;
} ] ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
var DEFAULT _REGEXP = /(\s+|^)default(\s+|$)/ ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngModelOptions
2016-04-18 12:34:29 +00:00
*
* @ description
2018-05-05 12:13:16 +02:00
* Allows tuning how model updates are done . Using ` ngModelOptions ` you can specify a custom list of
* events that will trigger a model update and / or a debouncing delay so that the actual update only
* takes place when a timer expires ; this timer will be reset after another change takes place .
*
* Given the nature of ` ngModelOptions ` , the value displayed inside input fields in the view might
* be different than the value in the actual model . This means that if you update the model you
* should also invoke { @ link ngModel . NgModelController ` $ rollbackViewValue ` } on the relevant input field in
* order to make sure it is synchronized with the model and that any debounced action is canceled .
*
* The easiest way to reference the control ' s { @ link ngModel . NgModelController ` $ rollbackViewValue ` }
* method is by making sure the input is placed inside a form that has a ` name ` attribute . This is
* important because ` form ` controllers are published to the related scope under the name in their
* ` name ` attribute .
*
* Any pending changes will take place immediately when an enclosing form is submitted via the
* ` submit ` event . Note that ` ngClick ` events will occur before the model is updated . Use ` ngSubmit `
* to have access to the updated model .
*
* ` ngModelOptions ` has an effect on the element it ' s declared on and its descendants .
*
* @ param { Object } ngModelOptions options to apply to the current model . Valid keys are :
* - ` updateOn ` : string specifying which event should the input be bound to . You can set several
* events using an space delimited list . There is a special event called ` default ` that
* matches the default events belonging of the control .
* - ` debounce ` : integer value which contains the debounce model update value in milliseconds . A
* value of 0 triggers an immediate update . If an object is supplied instead , you can specify a
* custom value for each event . For example :
* ` ng-model-options="{ updateOn: 'default blur', debounce: {'default': 500, 'blur': 0} }" `
* - ` allowInvalid ` : boolean value which indicates that the model can be set with values that did
* not validate correctly instead of the default behavior of setting the model to undefined .
* - ` getterSetter ` : boolean value which determines whether or not to treat functions bound to
` ngModel ` as getters / setters .
* - ` timezone ` : Defines the timezone to be used to read / write the ` Date ` instance in the model for
* ` <input type="date"> ` , ` <input type="time"> ` , ... . Right now , the only supported value is ` 'UTC' ` ,
* otherwise the default timezone of the browser will be used .
2016-04-18 12:34:29 +00:00
*
* @ example
2018-05-05 12:13:16 +02:00
The following example shows how to override immediate updates . Changes on the inputs within the
form will update the model only when the control loses focus ( blur event ) . If ` escape ` key is
pressed while the input field is focused , the value is reset to the value in the current model .
< example name = "ngModelOptions-directive-blur" module = "optionsExample" >
< file name = "index.html" >
< div ng - controller = "ExampleController" >
< form name = "userForm" >
Name :
< input type = "text" name = "userName"
ng - model = "user.name"
ng - model - options = "{ updateOn: 'blur' }"
ng - keyup = "cancel($event)" / > < br / >
Other data :
< input type = "text" ng - model = "user.data" / > < br / >
< / f o r m >
< pre > user . name = < span ng - bind = "user.name" > < / s p a n > < / p r e >
< / d i v >
< / f i l e >
< file name = "app.js" >
angular . module ( 'optionsExample' , [ ] )
. controller ( 'ExampleController' , [ '$scope' , function ( $scope ) {
$scope . user = { name : 'say' , data : '' } ;
$scope . cancel = function ( e ) {
if ( e . keyCode == 27 ) {
$scope . userForm . userName . $rollbackViewValue ( ) ;
}
} ;
} ] ) ;
< / f i l e >
< file name = "protractor.js" type = "protractor" >
var model = element ( by . binding ( 'user.name' ) ) ;
var input = element ( by . model ( 'user.name' ) ) ;
var other = element ( by . model ( 'user.data' ) ) ;
it ( 'should allow custom events' , function ( ) {
input . sendKeys ( ' hello' ) ;
input . click ( ) ;
expect ( model . getText ( ) ) . toEqual ( 'say' ) ;
other . click ( ) ;
expect ( model . getText ( ) ) . toEqual ( 'say hello' ) ;
} ) ;
it ( 'should $rollbackViewValue when model changes' , function ( ) {
input . sendKeys ( ' hello' ) ;
expect ( input . getAttribute ( 'value' ) ) . toEqual ( 'say hello' ) ;
input . sendKeys ( protractor . Key . ESCAPE ) ;
expect ( input . getAttribute ( 'value' ) ) . toEqual ( 'say' ) ;
other . click ( ) ;
expect ( model . getText ( ) ) . toEqual ( 'say' ) ;
} ) ;
< / f i l e >
< / e x a m p l e >
This one shows how to debounce model changes . Model will be updated only 1 sec after last change .
If the ` Clear ` button is pressed , any debounced action is canceled and the value becomes empty .
< example name = "ngModelOptions-directive-debounce" module = "optionsExample" >
< file name = "index.html" >
< div ng - controller = "ExampleController" >
< form name = "userForm" >
Name :
< input type = "text" name = "userName"
ng - model = "user.name"
ng - model - options = "{ debounce: 1000 }" / >
< button ng - click = "userForm.userName.$rollbackViewValue(); user.name=''" > Clear < /button><br / >
< / f o r m >
< pre > user . name = < span ng - bind = "user.name" > < / s p a n > < / p r e >
< / d i v >
< / f i l e >
< file name = "app.js" >
angular . module ( 'optionsExample' , [ ] )
. controller ( 'ExampleController' , [ '$scope' , function ( $scope ) {
$scope . user = { name : 'say' } ;
} ] ) ;
< / f i l e >
< / e x a m p l e >
This one shows how to bind to getter / setters :
< example name = "ngModelOptions-directive-getter-setter" module = "getterSetterExample" >
< file name = "index.html" >
< div ng - controller = "ExampleController" >
< form name = "userForm" >
Name :
< input type = "text" name = "userName"
ng - model = "user.name"
ng - model - options = "{ getterSetter: true }" / >
< / f o r m >
< pre > user . name = < span ng - bind = "user.name()" > < / s p a n > < / p r e >
< / d i v >
< / f i l e >
< file name = "app.js" >
angular . module ( 'getterSetterExample' , [ ] )
. controller ( 'ExampleController' , [ '$scope' , function ( $scope ) {
var _name = 'Brian' ;
$scope . user = {
name : function ( newName ) {
// Note that newName can be undefined for two reasons:
// 1. Because it is called as a getter and thus called with no arguments
// 2. Because the property should actually be set to undefined. This happens e.g. if the
// input is invalid
return arguments . length ? ( _name = newName ) : _name ;
}
} ;
} ] ) ;
< / f i l e >
< / e x a m p l e >
2016-04-18 12:34:29 +00:00
* /
2018-05-05 12:13:16 +02:00
var ngModelOptionsDirective = function ( ) {
return {
restrict : 'A' ,
controller : [ '$scope' , '$attrs' , function ( $scope , $attrs ) {
var that = this ;
this . $options = $scope . $eval ( $attrs . ngModelOptions ) ;
// Allow adding/overriding bound events
if ( this . $options . updateOn !== undefined ) {
this . $options . updateOnDefault = false ;
// extract "default" pseudo-event from list of events that can trigger a model update
this . $options . updateOn = trim ( this . $options . updateOn . replace ( DEFAULT _REGEXP , function ( ) {
that . $options . updateOnDefault = true ;
return ' ' ;
} ) ) ;
} else {
this . $options . updateOnDefault = true ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
} ]
} ;
} ;
// helper methods
function addSetValidityMethod ( context ) {
var ctrl = context . ctrl ,
$element = context . $element ,
classCache = { } ,
set = context . set ,
unset = context . unset ,
parentForm = context . parentForm ,
$animate = context . $animate ;
classCache [ INVALID _CLASS ] = ! ( classCache [ VALID _CLASS ] = $element . hasClass ( VALID _CLASS ) ) ;
ctrl . $setValidity = setValidity ;
function setValidity ( validationErrorKey , state , controller ) {
if ( state === undefined ) {
createAndSet ( '$pending' , validationErrorKey , controller ) ;
} else {
unsetAndCleanup ( '$pending' , validationErrorKey , controller ) ;
}
if ( ! isBoolean ( state ) ) {
unset ( ctrl . $error , validationErrorKey , controller ) ;
unset ( ctrl . $$success , validationErrorKey , controller ) ;
} else {
if ( state ) {
unset ( ctrl . $error , validationErrorKey , controller ) ;
set ( ctrl . $$success , validationErrorKey , controller ) ;
} else {
set ( ctrl . $error , validationErrorKey , controller ) ;
unset ( ctrl . $$success , validationErrorKey , controller ) ;
}
}
if ( ctrl . $pending ) {
cachedToggleClass ( PENDING _CLASS , true ) ;
ctrl . $valid = ctrl . $invalid = undefined ;
toggleValidationCss ( '' , null ) ;
} else {
cachedToggleClass ( PENDING _CLASS , false ) ;
ctrl . $valid = isObjectEmpty ( ctrl . $error ) ;
ctrl . $invalid = ! ctrl . $valid ;
toggleValidationCss ( '' , ctrl . $valid ) ;
}
// re-read the state as the set/unset methods could have
// combined state in ctrl.$error[validationError] (used for forms),
// where setting/unsetting only increments/decrements the value,
// and does not replace it.
var combinedState ;
if ( ctrl . $pending && ctrl . $pending [ validationErrorKey ] ) {
combinedState = undefined ;
} else if ( ctrl . $error [ validationErrorKey ] ) {
combinedState = false ;
} else if ( ctrl . $$success [ validationErrorKey ] ) {
combinedState = true ;
} else {
combinedState = null ;
}
toggleValidationCss ( validationErrorKey , combinedState ) ;
parentForm . $setValidity ( validationErrorKey , combinedState , ctrl ) ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
function createAndSet ( name , value , controller ) {
if ( ! ctrl [ name ] ) {
ctrl [ name ] = { } ;
}
set ( ctrl [ name ] , value , controller ) ;
}
function unsetAndCleanup ( name , value , controller ) {
if ( ctrl [ name ] ) {
unset ( ctrl [ name ] , value , controller ) ;
}
if ( isObjectEmpty ( ctrl [ name ] ) ) {
ctrl [ name ] = undefined ;
}
}
function cachedToggleClass ( className , switchValue ) {
if ( switchValue && ! classCache [ className ] ) {
$animate . addClass ( $element , className ) ;
classCache [ className ] = true ;
} else if ( ! switchValue && classCache [ className ] ) {
$animate . removeClass ( $element , className ) ;
classCache [ className ] = false ;
}
}
function toggleValidationCss ( validationErrorKey , isValid ) {
validationErrorKey = validationErrorKey ? '-' + snake _case ( validationErrorKey , '-' ) : '' ;
cachedToggleClass ( VALID _CLASS + validationErrorKey , isValid === true ) ;
cachedToggleClass ( INVALID _CLASS + validationErrorKey , isValid === false ) ;
}
}
function isObjectEmpty ( obj ) {
if ( obj ) {
for ( var prop in obj ) {
return false ;
}
}
return true ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngNonBindable
2016-04-18 12:34:29 +00:00
* @ restrict AC
* @ priority 1000
*
* @ description
* The ` ngNonBindable ` directive tells Angular not to compile or bind the contents of the current
* DOM element . This is useful if the element contains what appears to be Angular directives and
* bindings but which should be ignored by Angular . This could be the case if you have a site that
* displays snippets of code , for instance .
*
* @ element ANY
*
* @ example
* In this example there are two locations where a simple interpolation binding ( ` {{}} ` ) is present ,
* but the one wrapped in ` ngNonBindable ` is left alone .
*
* @ example
2018-05-05 12:13:16 +02:00
< example >
< file name = "index.html" >
2016-04-18 12:34:29 +00:00
< div > Normal : { { 1 + 2 } } < / d i v >
< div ng - non - bindable > Ignored : { { 1 + 2 } } < / d i v >
2018-05-05 12:13:16 +02:00
< / f i l e >
< file name = "protractor.js" type = "protractor" >
2016-04-18 12:34:29 +00:00
it ( 'should check ng-non-bindable' , function ( ) {
2018-05-05 12:13:16 +02:00
expect ( element ( by . binding ( '1 + 2' ) ) . getText ( ) ) . toContain ( '3' ) ;
expect ( element . all ( by . css ( 'div' ) ) . last ( ) . getText ( ) ) . toMatch ( /1 \+ 2/ ) ;
2016-04-18 12:34:29 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-04-18 12:34:29 +00:00
* /
var ngNonBindableDirective = ngDirective ( { terminal : true , priority : 1000 } ) ;
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngPluralize
2016-03-28 10:46:51 +00:00
* @ restrict EA
*
* @ description
* ` ngPluralize ` is a directive that displays messages according to en - US localization rules .
* These rules are bundled with angular . js , but can be overridden
* ( see { @ link guide / i18n Angular i18n } dev guide ) . You configure ngPluralize directive
* by specifying the mappings between
2018-05-05 12:13:16 +02:00
* [ plural categories ] ( http : //unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html)
* and the strings to be displayed .
2016-03-28 10:46:51 +00:00
*
* # Plural categories and explicit number rules
* There are two
2018-05-05 12:13:16 +02:00
* [ plural categories ] ( http : //unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html)
* in Angular ' s default en - US locale : "one" and "other" .
2016-03-28 10:46:51 +00:00
*
* While a plural category may match many numbers ( for example , in en - US locale , "other" can match
* any number that is not 1 ) , an explicit number rule can only match one number . For example , the
* explicit number rule for "3" matches the number 3. There are examples of plural categories
* and explicit number rules throughout the rest of this documentation .
*
* # Configuring ngPluralize
* You configure ngPluralize by providing 2 attributes : ` count ` and ` when ` .
* You can also provide an optional attribute , ` offset ` .
*
* The value of the ` count ` attribute can be either a string or an { @ link guide / expression
* Angular expression } ; these are evaluated on the current scope for its bound value .
*
* The ` when ` attribute specifies the mappings between plural categories and the actual
* string to be displayed . The value of the attribute should be a JSON object .
*
* The following example shows how to configure ngPluralize :
*
2018-05-05 12:13:16 +02:00
* ` ` ` html
2016-03-28 10:46:51 +00:00
* < ng - pluralize count = "personCount"
when = " { '0' : 'Nobody is viewing.' ,
* 'one' : '1 person is viewing.' ,
* 'other' : '{} people are viewing.' } " >
* < / n g - p l u r a l i z e >
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
* In the example , ` "0: Nobody is viewing." ` is an explicit number rule . If you did not
* specify this rule , 0 would be matched to the "other" category and "0 people are viewing"
* would be shown instead of "Nobody is viewing" . You can specify an explicit number rule for
* other numbers , for example 12 , so that instead of showing "12 people are viewing" , you can
* show "a dozen people are viewing" .
*
* You can use a set of closed braces ( ` {} ` ) as a placeholder for the number that you want substituted
* into pluralized strings . In the previous example , Angular will replace ` {} ` with
* < span ng - non - bindable > ` {{personCount}} ` < / s p a n > . T h e c l o s e d b r a c e s ` { } ` i s a p l a c e h o l d e r
* for < span ng - non - bindable > { { numberExpression } } < / s p a n > .
*
* # Configuring ngPluralize with offset
* The ` offset ` attribute allows further customization of pluralized text , which can result in
* a better user experience . For example , instead of the message "4 people are viewing this document" ,
* you might display "John, Kate and 2 others are viewing this document" .
* The offset attribute allows you to offset a number by any desired value .
* Let ' s take a look at an example :
*
2018-05-05 12:13:16 +02:00
* ` ` ` html
2016-03-28 10:46:51 +00:00
* < ng - pluralize count = "personCount" offset = 2
* when = " { '0' : 'Nobody is viewing.' ,
* '1' : '{{person1}} is viewing.' ,
* '2' : '{{person1}} and {{person2}} are viewing.' ,
* 'one' : '{{person1}}, {{person2}} and one other person are viewing.' ,
* 'other' : '{{person1}}, {{person2}} and {} other people are viewing.' } " >
* < / n g - p l u r a l i z e >
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
* Notice that we are still using two plural categories ( one , other ) , but we added
* three explicit number rules 0 , 1 and 2.
* When one person , perhaps John , views the document , "John is viewing" will be shown .
* When three people view the document , no explicit number rule is found , so
* an offset of 2 is taken off 3 , and Angular uses 1 to decide the plural category .
2018-05-05 12:13:16 +02:00
* In this case , plural category 'one' is matched and "John, Mary and one other person are viewing"
2016-03-28 10:46:51 +00:00
* is shown .
*
* Note that when you specify offsets , you must provide explicit number rules for
* numbers from 0 up to and including the offset . If you use an offset of 3 , for example ,
* you must provide explicit number rules for 0 , 1 , 2 and 3. You must also provide plural strings for
* plural categories "one" and "other" .
*
2018-05-05 12:13:16 +02:00
* @ param { string | expression } count The variable to be bound to .
2016-03-28 10:46:51 +00:00
* @ param { string } when The mapping between plural category to its corresponding strings .
* @ param { number = } offset Offset to deduct from the total number .
*
* @ example
2018-05-05 12:13:16 +02:00
< example module = "pluralizeExample" >
< file name = "index.html" >
2016-03-28 10:46:51 +00:00
< script >
2018-05-05 12:13:16 +02:00
angular . module ( 'pluralizeExample' , [ ] )
. controller ( 'ExampleController' , [ '$scope' , function ( $scope ) {
$scope . person1 = 'Igor' ;
$scope . person2 = 'Misko' ;
$scope . personCount = 1 ;
} ] ) ;
2016-03-28 10:46:51 +00:00
< / s c r i p t >
2018-05-05 12:13:16 +02:00
< div ng - controller = "ExampleController" >
2016-04-18 12:34:29 +00:00
Person 1 : < input type = "text" ng - model = "person1" value = "Igor" / > < br / >
Person 2 : < input type = "text" ng - model = "person2" value = "Misko" / > < br / >
Number of People : < input type = "text" ng - model = "personCount" value = "1" / > < br / >
2016-03-28 10:46:51 +00:00
<!-- - Example with simple pluralization rules for en locale -- - >
Without Offset :
< ng - pluralize count = "personCount"
when = " { '0' : 'Nobody is viewing.' ,
'one' : '1 person is viewing.' ,
'other' : '{} people are viewing.' } " >
< / n g - p l u r a l i z e > < b r >
<!-- - Example with offset -- - >
With Offset ( 2 ) :
< ng - pluralize count = "personCount" offset = 2
when = " { '0' : 'Nobody is viewing.' ,
'1' : '{{person1}} is viewing.' ,
'2' : '{{person1}} and {{person2}} are viewing.' ,
'one' : '{{person1}}, {{person2}} and one other person are viewing.' ,
'other' : '{{person1}}, {{person2}} and {} other people are viewing.' } " >
< / n g - p l u r a l i z e >
< / d i v >
2018-05-05 12:13:16 +02:00
< / f i l e >
< file name = "protractor.js" type = "protractor" >
2016-03-28 10:46:51 +00:00
it ( 'should show correct pluralized string' , function ( ) {
2018-05-05 12:13:16 +02:00
var withoutOffset = element . all ( by . css ( 'ng-pluralize' ) ) . get ( 0 ) ;
var withOffset = element . all ( by . css ( 'ng-pluralize' ) ) . get ( 1 ) ;
var countInput = element ( by . model ( 'personCount' ) ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
expect ( withoutOffset . getText ( ) ) . toEqual ( '1 person is viewing.' ) ;
expect ( withOffset . getText ( ) ) . toEqual ( 'Igor is viewing.' ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
countInput . clear ( ) ;
countInput . sendKeys ( '0' ) ;
expect ( withoutOffset . getText ( ) ) . toEqual ( 'Nobody is viewing.' ) ;
expect ( withOffset . getText ( ) ) . toEqual ( 'Nobody is viewing.' ) ;
countInput . clear ( ) ;
countInput . sendKeys ( '2' ) ;
expect ( withoutOffset . getText ( ) ) . toEqual ( '2 people are viewing.' ) ;
expect ( withOffset . getText ( ) ) . toEqual ( 'Igor and Misko are viewing.' ) ;
countInput . clear ( ) ;
countInput . sendKeys ( '3' ) ;
expect ( withoutOffset . getText ( ) ) . toEqual ( '3 people are viewing.' ) ;
expect ( withOffset . getText ( ) ) . toEqual ( 'Igor, Misko and one other person are viewing.' ) ;
countInput . clear ( ) ;
countInput . sendKeys ( '4' ) ;
expect ( withoutOffset . getText ( ) ) . toEqual ( '4 people are viewing.' ) ;
expect ( withOffset . getText ( ) ) . toEqual ( 'Igor, Misko and 2 other people are viewing.' ) ;
} ) ;
it ( 'should show data-bound names' , function ( ) {
var withOffset = element . all ( by . css ( 'ng-pluralize' ) ) . get ( 1 ) ;
var personCount = element ( by . model ( 'personCount' ) ) ;
var person1 = element ( by . model ( 'person1' ) ) ;
var person2 = element ( by . model ( 'person2' ) ) ;
personCount . clear ( ) ;
personCount . sendKeys ( '4' ) ;
person1 . clear ( ) ;
person1 . sendKeys ( 'Di' ) ;
person2 . clear ( ) ;
person2 . sendKeys ( 'Vojta' ) ;
expect ( withOffset . getText ( ) ) . toEqual ( 'Di, Vojta and 2 other people are viewing.' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
2016-04-18 12:34:29 +00:00
var ngPluralizeDirective = [ '$locale' , '$interpolate' , function ( $locale , $interpolate ) {
2018-05-05 12:13:16 +02:00
var BRACE = /{}/g ,
IS _WHEN = /^when(Minus)?(.+)$/ ;
2016-03-28 10:46:51 +00:00
return {
2016-04-18 12:34:29 +00:00
restrict : 'EA' ,
2016-03-28 10:46:51 +00:00
link : function ( scope , element , attr ) {
var numberExp = attr . count ,
whenExp = attr . $attr . when && element . attr ( attr . $attr . when ) , // we have {{}} in attrs
offset = attr . offset || 0 ,
whens = scope . $eval ( whenExp ) || { } ,
whensExpFns = { } ,
startSymbol = $interpolate . startSymbol ( ) ,
endSymbol = $interpolate . endSymbol ( ) ,
2018-05-05 12:13:16 +02:00
braceReplacement = startSymbol + numberExp + '-' + offset + endSymbol ,
watchRemover = angular . noop ,
lastCount ;
2016-03-28 10:46:51 +00:00
forEach ( attr , function ( expression , attributeName ) {
2018-05-05 12:13:16 +02:00
var tmpMatch = IS _WHEN . exec ( attributeName ) ;
if ( tmpMatch ) {
var whenKey = ( tmpMatch [ 1 ] ? '-' : '' ) + lowercase ( tmpMatch [ 2 ] ) ;
whens [ whenKey ] = element . attr ( attr . $attr [ attributeName ] ) ;
2016-03-28 10:46:51 +00:00
}
} ) ;
forEach ( whens , function ( expression , key ) {
2018-05-05 12:13:16 +02:00
whensExpFns [ key ] = $interpolate ( expression . replace ( BRACE , braceReplacement ) ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
scope . $watch ( numberExp , function ngPluralizeWatchAction ( newVal ) {
var count = parseFloat ( newVal ) ;
var countIsNaN = isNaN ( count ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
if ( ! countIsNaN && ! ( count in whens ) ) {
// If an explicit number rule such as 1, 2, 3... is defined, just use it.
// Otherwise, check it against pluralization rules in $locale service.
count = $locale . pluralCat ( count - offset ) ;
}
// If both `count` and `lastCount` are NaN, we don't need to re-register a watch.
// In JS `NaN !== NaN`, so we have to exlicitly check.
if ( ( count !== lastCount ) && ! ( countIsNaN && isNaN ( lastCount ) ) ) {
watchRemover ( ) ;
watchRemover = scope . $watch ( whensExpFns [ count ] , updateElementText ) ;
lastCount = count ;
2016-03-28 10:46:51 +00:00
}
} ) ;
2018-05-05 12:13:16 +02:00
function updateElementText ( newText ) {
element . text ( newText || '' ) ;
}
2016-03-28 10:46:51 +00:00
}
} ;
} ] ;
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngRepeat
2016-03-28 10:46:51 +00:00
*
* @ description
* The ` ngRepeat ` directive instantiates a template once per item from a collection . Each template
* instance gets its own scope , where the given loop variable is set to the current collection item ,
* and ` $ index ` is set to the item index or key .
*
* Special properties are exposed on the local scope of each template instance , including :
*
* | Variable | Type | Details |
* | -- -- -- -- -- - | -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - |
* | ` $ index ` | { @ type number } | iterator offset of the repeated element ( 0. . length - 1 ) |
* | ` $ first ` | { @ type boolean } | true if the repeated element is first in the iterator . |
* | ` $ middle ` | { @ type boolean } | true if the repeated element is between the first and last in the iterator . |
* | ` $ last ` | { @ type boolean } | true if the repeated element is last in the iterator . |
* | ` $ even ` | { @ type boolean } | true if the iterator position ` $ index ` is even ( otherwise false ) . |
* | ` $ odd ` | { @ type boolean } | true if the iterator position ` $ index ` is odd ( otherwise false ) . |
*
2018-05-05 12:13:16 +02:00
* Creating aliases for these properties is possible with { @ link ng . directive : ngInit ` ngInit ` } .
2016-04-18 12:34:29 +00:00
* This may be useful when , for instance , nesting ngRepeats .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* # Iterating over object properties
*
* It is possible to get ` ngRepeat ` to iterate over the properties of an object using the following
* syntax :
*
* ` ` ` js
* < div ng - repeat = "(key, value) in myObj" > ... < / d i v >
* ` ` `
*
* You need to be aware that the JavaScript specification does not define what order
* it will return the keys for an object . In order to have a guaranteed deterministic order
* for the keys , Angular versions up to and including 1.3 * * sort the keys alphabetically * * .
*
* If this is not desired , the recommended workaround is to convert your object into an array
* that is sorted into the order that you prefer before providing it to ` ngRepeat ` . You could
* do this with a filter such as [ toArrayFilter ] ( http : //ngmodules.org/modules/angular-toArrayFilter)
* or implement a ` $ watch ` on the object yourself .
*
* In version 1.4 we will remove the sorting , since it seems that browsers generally follow the
* strategy of providing keys in the order in which they were defined , although there are exceptions
* when keys are deleted and reinstated .
*
*
* # Tracking and Duplicates
*
* When the contents of the collection change , ` ngRepeat ` makes the corresponding changes to the DOM :
*
* * When an item is added , a new instance of the template is added to the DOM .
* * When an item is removed , its template instance is removed from the DOM .
* * When items are reordered , their respective templates are reordered in the DOM .
*
* By default , ` ngRepeat ` does not allow duplicate items in arrays . This is because when
* there are duplicates , it is not possible to maintain a one - to - one mapping between collection
* items and DOM elements .
*
* If you do need to repeat duplicate items , you can substitute the default tracking behavior
* with your own using the ` track by ` expression .
*
* For example , you may track items by the index of each item in the collection , using the
* special scope property ` $ index ` :
* ` ` ` html
* < div ng - repeat = "n in [42, 42, 43, 43] track by $index" >
* { { n } }
* < / d i v >
* ` ` `
*
* You may use arbitrary expressions in ` track by ` , including references to custom functions
* on the scope :
* ` ` ` html
* < div ng - repeat = "n in [42, 42, 43, 43] track by myTrackingFunction(n)" >
* { { n } }
* < / d i v >
* ` ` `
*
* If you are working with objects that have an identifier property , you can track
* by the identifier instead of the whole object . Should you reload your data later , ` ngRepeat `
* will not have to rebuild the DOM elements for items it has already rendered , even if the
* JavaScript objects in the collection have been substituted for new ones :
* ` ` ` html
* < div ng - repeat = "model in collection track by model.id" >
* { { model . name } }
* < / d i v >
* ` ` `
*
* When no ` track by ` expression is provided , it is equivalent to tracking by the built - in
* ` $ id ` function , which tracks items by their identity :
* ` ` ` html
* < div ng - repeat = "obj in collection track by $id(obj)" >
* { { obj . prop } }
* < / d i v >
* ` ` `
*
2016-03-28 10:46:51 +00:00
* # Special repeat start and end points
* To repeat a series of elements instead of just one parent element , ngRepeat ( as well as other ng directives ) supports extending
* the range of the repeater by defining explicit start and end points by using * * ng - repeat - start * * and * * ng - repeat - end * * respectively .
* 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 )
* up to and including the ending HTML tag where * * ng - repeat - end * * is placed .
*
* The example below makes use of this feature :
2018-05-05 12:13:16 +02:00
* ` ` ` html
2016-03-28 10:46:51 +00:00
* < header ng - repeat - start = "item in items" >
* Header { { item } }
* < / h e a d e r >
* < div class = "body" >
* Body { { item } }
* < / d i v >
* < footer ng - repeat - end >
* Footer { { item } }
* < / f o o t e r >
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
* And with an input of { @ type [ 'A' , 'B' ] } for the items variable in the example above , the output will evaluate to :
2018-05-05 12:13:16 +02:00
* ` ` ` html
2016-03-28 10:46:51 +00:00
* < header >
* Header A
* < / h e a d e r >
* < div class = "body" >
* Body A
* < / d i v >
* < footer >
* Footer A
* < / f o o t e r >
* < header >
* Header B
* < / h e a d e r >
* < div class = "body" >
* Body B
* < / d i v >
* < footer >
* Footer B
* < / f o o t e r >
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
* The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS ( such
* as * * data - ng - repeat - start * * , * * x - ng - repeat - start * * and * * ng : repeat - start * * ) .
*
* @ animations
2018-05-05 12:13:16 +02:00
* * * . enter * * - when a new item is added to the list or when an item is revealed after a filter
*
* * * . leave * * - when an item is removed from the list or when an item is filtered out
*
* * * . move * * - when an adjacent item is filtered out causing a reorder or when the item contents are reordered
2016-03-28 10:46:51 +00:00
*
* @ element ANY
* @ scope
* @ priority 1000
* @ param { repeat _expression } ngRepeat The expression indicating how to enumerate a collection . These
* formats are currently supported :
*
* * ` variable in expression ` – where variable is the user defined loop variable and ` expression `
* is a scope expression giving the collection to enumerate .
*
* For example : ` album in artist.albums ` .
*
* * ` (key, value) in expression ` – where ` key ` and ` value ` can be any user defined identifiers ,
* and ` expression ` is the scope expression giving the collection to enumerate .
*
* For example : ` (name, age) in {'adam':10, 'amalie':12} ` .
*
2018-05-05 12:13:16 +02:00
* * ` variable in expression track by tracking_expression ` – You can also provide an optional tracking expression
* which can be used to associate the objects in the collection with the DOM elements . If no tracking expression
* is specified , ng - repeat associates elements by identity . It is an error to have
* more than one tracking expression value resolve to the same key . ( This would mean that two distinct objects are
* mapped to the same DOM element , which is not possible . ) If filters are used in the expression , they should be
* applied before the tracking expression .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* For example : ` item in items ` is equivalent to ` item in items track by $ id(item) ` . This implies that the DOM elements
2016-03-28 10:46:51 +00:00
* will be associated by item identity in the array .
*
* For example : ` item in items track by $ id(item) ` . A built in ` $ id() ` function can be used to assign a unique
* ` $ $ hashKey ` property to each item in the array . This property is then used as a key to associated DOM elements
* with the corresponding item in the array by identity . Moving the same object in array would move the DOM
* element in the same way in the DOM .
*
* For example : ` item in items track by item.id ` is a typical pattern when the items come from the database . In this
* case the object identity does not matter . Two objects are considered equivalent as long as their ` id `
* property is same .
*
* For example : ` item in items | filter:searchText track by item.id ` is a pattern that might be used to apply a filter
* to items in conjunction with a tracking expression .
*
2018-05-05 12:13:16 +02:00
* * ` variable in expression as alias_expression ` – You can also provide an optional alias expression which will then store the
* intermediate results of the repeater after the filters have been applied . Typically this is used to render a special message
* when a filter is active on the repeater , but the filtered result set is empty .
*
* For example : ` item in items | filter:x as results ` will store the fragment of the repeated items as ` results ` , but only after
* the items have been processed through the filter .
*
2016-03-28 10:46:51 +00:00
* @ example
2016-04-18 12:34:29 +00:00
* This example initializes the scope to a list of names and
* then uses ` ngRepeat ` to display every person :
2018-05-05 12:13:16 +02:00
< example module = "ngAnimate" deps = "angular-animate.js" animations = "true" >
2016-03-28 10:46:51 +00:00
< file name = "index.html" >
2016-04-18 12:34:29 +00:00
< div ng - init = " friends = [
{ name : 'John' , age : 25 , gender : 'boy' } ,
{ name : 'Jessie' , age : 30 , gender : 'girl' } ,
{ name : 'Johanna' , age : 28 , gender : 'girl' } ,
{ name : 'Joy' , age : 15 , gender : 'girl' } ,
{ name : 'Mary' , age : 28 , gender : 'girl' } ,
{ name : 'Peter' , age : 95 , gender : 'boy' } ,
{ name : 'Sebastian' , age : 50 , gender : 'boy' } ,
{ name : 'Erika' , age : 27 , gender : 'girl' } ,
{ name : 'Patrick' , age : 40 , gender : 'boy' } ,
{ name : 'Samantha' , age : 60 , gender : 'girl' }
] " >
2016-03-28 10:46:51 +00:00
I have { { friends . length } } friends . They are :
2016-04-18 12:34:29 +00:00
< input type = "search" ng - model = "q" placeholder = "filter friends..." / >
2016-03-28 10:46:51 +00:00
< ul class = "example-animate-container" >
2018-05-05 12:13:16 +02:00
< li class = "animate-repeat" ng - repeat = "friend in friends | filter:q as results" >
2016-03-28 10:46:51 +00:00
[ { { $index + 1 } } ] { { friend . name } } who is { { friend . age } } years old .
< / l i >
2018-05-05 12:13:16 +02:00
< li class = "animate-repeat" ng - if = "results.length == 0" >
< strong > No results found ... < / s t r o n g >
< / l i >
2016-03-28 10:46:51 +00:00
< / u l >
< / d i v >
< / f i l e >
< file name = "animations.css" >
. example - animate - container {
background : white ;
border : 1 px solid black ;
list - style : none ;
margin : 0 ;
padding : 0 10 px ;
}
. animate - repeat {
2016-04-18 12:34:29 +00:00
line - height : 40 px ;
2016-03-28 10:46:51 +00:00
list - style : none ;
box - sizing : border - box ;
}
. animate - repeat . ng - move ,
. animate - repeat . ng - enter ,
. animate - repeat . ng - leave {
2016-04-18 12:34:29 +00:00
- webkit - transition : all linear 0.5 s ;
2016-03-28 10:46:51 +00:00
transition : all linear 0.5 s ;
}
. animate - repeat . ng - leave . ng - leave - active ,
. animate - repeat . ng - move ,
. animate - repeat . ng - enter {
opacity : 0 ;
max - height : 0 ;
}
. animate - repeat . ng - leave ,
. animate - repeat . ng - move . ng - move - active ,
. animate - repeat . ng - enter . ng - enter - active {
opacity : 1 ;
2016-04-18 12:34:29 +00:00
max - height : 40 px ;
2016-03-28 10:46:51 +00:00
}
< / f i l e >
2018-05-05 12:13:16 +02:00
< file name = "protractor.js" type = "protractor" >
var friends = element . all ( by . repeater ( 'friend in friends' ) ) ;
it ( 'should render initial data set' , function ( ) {
expect ( friends . count ( ) ) . toBe ( 10 ) ;
expect ( friends . get ( 0 ) . getText ( ) ) . toEqual ( '[1] John who is 25 years old.' ) ;
expect ( friends . get ( 1 ) . getText ( ) ) . toEqual ( '[2] Jessie who is 30 years old.' ) ;
expect ( friends . last ( ) . getText ( ) ) . toEqual ( '[10] Samantha who is 60 years old.' ) ;
expect ( element ( by . binding ( 'friends.length' ) ) . getText ( ) )
. toMatch ( "I have 10 friends. They are:" ) ;
} ) ;
2016-03-28 10:46:51 +00:00
it ( 'should update repeater when filter predicate changes' , function ( ) {
2018-05-05 12:13:16 +02:00
expect ( friends . count ( ) ) . toBe ( 10 ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
element ( by . model ( 'q' ) ) . sendKeys ( 'ma' ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
expect ( friends . count ( ) ) . toBe ( 2 ) ;
expect ( friends . get ( 0 ) . getText ( ) ) . toEqual ( '[1] Mary who is 28 years old.' ) ;
expect ( friends . last ( ) . getText ( ) ) . toEqual ( '[2] Samantha who is 60 years old.' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
< / f i l e >
< / e x a m p l e >
* /
2016-04-18 12:34:29 +00:00
var ngRepeatDirective = [ '$parse' , '$animate' , function ( $parse , $animate ) {
2016-03-28 10:46:51 +00:00
var NG _REMOVED = '$$NG_REMOVED' ;
var ngRepeatMinErr = minErr ( 'ngRepeat' ) ;
2018-05-05 12:13:16 +02:00
var updateScope = function ( scope , index , valueIdentifier , value , keyIdentifier , key , arrayLength ) {
// TODO(perf): generate setters to shave off ~40ms or 1-1.5%
scope [ valueIdentifier ] = value ;
if ( keyIdentifier ) scope [ keyIdentifier ] = key ;
scope . $index = index ;
scope . $first = ( index === 0 ) ;
scope . $last = ( index === ( arrayLength - 1 ) ) ;
scope . $middle = ! ( scope . $first || scope . $last ) ;
// jshint bitwise: false
scope . $odd = ! ( scope . $even = ( index & 1 ) === 0 ) ;
// jshint bitwise: true
} ;
var getBlockStart = function ( block ) {
return block . clone [ 0 ] ;
} ;
var getBlockEnd = function ( block ) {
return block . clone [ block . clone . length - 1 ] ;
} ;
2016-03-28 10:46:51 +00:00
return {
2018-05-05 12:13:16 +02:00
restrict : 'A' ,
multiElement : true ,
2016-03-28 10:46:51 +00:00
transclude : 'element' ,
priority : 1000 ,
terminal : true ,
$$tlb : true ,
2018-05-05 12:13:16 +02:00
compile : function ngRepeatCompile ( $element , $attr ) {
var expression = $attr . ngRepeat ;
var ngRepeatEndComment = document . createComment ( ' end ngRepeat: ' + expression + ' ' ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
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*$/ ) ;
if ( ! match ) {
throw ngRepeatMinErr ( 'iexp' , "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'." ,
2016-03-28 10:46:51 +00:00
expression ) ;
2018-05-05 12:13:16 +02:00
}
var lhs = match [ 1 ] ;
var rhs = match [ 2 ] ;
var aliasAs = match [ 3 ] ;
var trackByExp = match [ 4 ] ;
match = lhs . match ( /^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/ ) ;
if ( ! match ) {
throw ngRepeatMinErr ( 'iidexp' , "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'." ,
lhs ) ;
}
var valueIdentifier = match [ 3 ] || match [ 1 ] ;
var keyIdentifier = match [ 2 ] ;
if ( aliasAs && ( ! /^[$a-zA-Z_][$a-zA-Z0-9_]*$/ . test ( aliasAs ) ||
/^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/ . test ( aliasAs ) ) ) {
throw ngRepeatMinErr ( 'badident' , "alias '{0}' is invalid --- must be a valid JS identifier which is not a reserved name." ,
aliasAs ) ;
}
var trackByExpGetter , trackByIdExpFn , trackByIdArrayFn , trackByIdObjFn ;
var hashFnLocals = { $id : hashKey } ;
if ( trackByExp ) {
trackByExpGetter = $parse ( trackByExp ) ;
} else {
trackByIdArrayFn = function ( key , value ) {
return hashKey ( value ) ;
} ;
trackByIdObjFn = function ( key ) {
return key ;
} ;
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
return function ngRepeatLink ( $scope , $element , $attr , ctrl , $transclude ) {
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
if ( trackByExpGetter ) {
2016-03-28 10:46:51 +00:00
trackByIdExpFn = function ( key , value , index ) {
// assign key, value, and $index to the locals so that they can be used in hash functions
if ( keyIdentifier ) hashFnLocals [ keyIdentifier ] = key ;
hashFnLocals [ valueIdentifier ] = value ;
hashFnLocals . $index = index ;
return trackByExpGetter ( $scope , hashFnLocals ) ;
} ;
}
// Store a list of elements from previous run. This is a hash where key is the item from the
// iterator, and the value is objects with following properties.
// - scope: bound scope
// - element: previous element.
// - index: position
2018-05-05 12:13:16 +02:00
//
// We are using no-proto object so that we don't need to guard against inherited props via
// hasOwnProperty.
var lastBlockMap = createMap ( ) ;
2016-03-28 10:46:51 +00:00
//watch props
2018-05-05 12:13:16 +02:00
$scope . $watchCollection ( rhs , function ngRepeatAction ( collection ) {
2016-03-28 10:46:51 +00:00
var index , length ,
2018-05-05 12:13:16 +02:00
previousNode = $element [ 0 ] , // node that cloned nodes should be inserted after
// initialized to the comment node anchor
2016-03-28 10:46:51 +00:00
nextNode ,
// Same as lastBlockMap but it has the current state. It will become the
// lastBlockMap on the next iteration.
2018-05-05 12:13:16 +02:00
nextBlockMap = createMap ( ) ,
collectionLength ,
2016-03-28 10:46:51 +00:00
key , value , // key/value of iteration
trackById ,
trackByIdFn ,
collectionKeys ,
block , // last object information {scope, element, id}
2018-05-05 12:13:16 +02:00
nextBlockOrder ,
2016-03-28 10:46:51 +00:00
elementsToRemove ;
2018-05-05 12:13:16 +02:00
if ( aliasAs ) {
$scope [ aliasAs ] = collection ;
}
2016-03-28 10:46:51 +00:00
if ( isArrayLike ( collection ) ) {
collectionKeys = collection ;
trackByIdFn = trackByIdExpFn || trackByIdArrayFn ;
} else {
trackByIdFn = trackByIdExpFn || trackByIdObjFn ;
2016-04-18 12:34:29 +00:00
// if object, extract keys, sort them and use to determine order of iteration over obj props
2016-03-28 10:46:51 +00:00
collectionKeys = [ ] ;
2018-05-05 12:13:16 +02:00
for ( var itemKey in collection ) {
if ( collection . hasOwnProperty ( itemKey ) && itemKey . charAt ( 0 ) != '$' ) {
collectionKeys . push ( itemKey ) ;
2016-03-28 10:46:51 +00:00
}
}
2016-04-18 12:34:29 +00:00
collectionKeys . sort ( ) ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
collectionLength = collectionKeys . length ;
nextBlockOrder = new Array ( collectionLength ) ;
2016-03-28 10:46:51 +00:00
// locate existing items
2018-05-05 12:13:16 +02:00
for ( index = 0 ; index < collectionLength ; index ++ ) {
key = ( collection === collectionKeys ) ? index : collectionKeys [ index ] ;
value = collection [ key ] ;
trackById = trackByIdFn ( key , value , index ) ;
if ( lastBlockMap [ trackById ] ) {
// found previously seen block
block = lastBlockMap [ trackById ] ;
delete lastBlockMap [ trackById ] ;
nextBlockMap [ trackById ] = block ;
nextBlockOrder [ index ] = block ;
} else if ( nextBlockMap [ trackById ] ) {
// if collision detected. restore lastBlockMap and throw an error
forEach ( nextBlockOrder , function ( block ) {
if ( block && block . scope ) lastBlockMap [ block . id ] = block ;
} ) ;
throw ngRepeatMinErr ( 'dupes' ,
"Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}" ,
expression , trackById , value ) ;
} else {
// new never before seen block
nextBlockOrder [ index ] = { id : trackById , scope : undefined , clone : undefined } ;
nextBlockMap [ trackById ] = true ;
}
}
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
// remove leftover items
for ( var blockKey in lastBlockMap ) {
block = lastBlockMap [ blockKey ] ;
elementsToRemove = getBlockNodes ( block . clone ) ;
$animate . leave ( elementsToRemove ) ;
if ( elementsToRemove [ 0 ] . parentNode ) {
// if the element was not removed yet because of pending animation, mark it as deleted
// so that we can ignore it later
for ( index = 0 , length = elementsToRemove . length ; index < length ; index ++ ) {
elementsToRemove [ index ] [ NG _REMOVED ] = true ;
}
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
block . scope . $destroy ( ) ;
2016-03-28 10:46:51 +00:00
}
// we are not using forEach for perf reasons (trying to avoid #call)
2018-05-05 12:13:16 +02:00
for ( index = 0 ; index < collectionLength ; index ++ ) {
2016-03-28 10:46:51 +00:00
key = ( collection === collectionKeys ) ? index : collectionKeys [ index ] ;
value = collection [ key ] ;
block = nextBlockOrder [ index ] ;
if ( block . scope ) {
// if we have already seen this object, then we need to reuse the
// associated scope/element
nextNode = previousNode ;
2018-05-05 12:13:16 +02:00
// skip nodes that are already pending removal via leave animation
2016-03-28 10:46:51 +00:00
do {
nextNode = nextNode . nextSibling ;
2018-05-05 12:13:16 +02:00
} while ( nextNode && nextNode [ NG _REMOVED ] ) ;
2016-03-28 10:46:51 +00:00
if ( getBlockStart ( block ) != nextNode ) {
// existing item which got moved
2018-05-05 12:13:16 +02:00
$animate . move ( getBlockNodes ( block . clone ) , null , jqLite ( previousNode ) ) ;
2016-03-28 10:46:51 +00:00
}
previousNode = getBlockEnd ( block ) ;
2018-05-05 12:13:16 +02:00
updateScope ( block . scope , index , valueIdentifier , value , keyIdentifier , key , collectionLength ) ;
2016-03-28 10:46:51 +00:00
} else {
// new item which we don't know about
2018-05-05 12:13:16 +02:00
$transclude ( function ngRepeatTransclude ( clone , scope ) {
block . scope = scope ;
// http://jsperf.com/clone-vs-createcomment
var endNode = ngRepeatEndComment . cloneNode ( false ) ;
clone [ clone . length ++ ] = endNode ;
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
// TODO(perf): support naked previousNode in `enter` to avoid creation of jqLite wrapper?
2016-04-18 12:34:29 +00:00
$animate . enter ( clone , null , jqLite ( previousNode ) ) ;
2018-05-05 12:13:16 +02:00
previousNode = endNode ;
2016-03-28 10:46:51 +00:00
// Note: We only need the first/last node of the cloned nodes.
// However, we need to keep the reference to the jqlite wrapper as it might be changed later
2018-05-05 12:13:16 +02:00
// by a directive with templateUrl when its template arrives.
2016-03-28 10:46:51 +00:00
block . clone = clone ;
nextBlockMap [ block . id ] = block ;
2018-05-05 12:13:16 +02:00
updateScope ( block . scope , index , valueIdentifier , value , keyIdentifier , key , collectionLength ) ;
2016-03-28 10:46:51 +00:00
} ) ;
}
}
lastBlockMap = nextBlockMap ;
} ) ;
2018-05-05 12:13:16 +02:00
} ;
2016-03-28 10:46:51 +00:00
}
} ;
} ] ;
2018-05-05 12:13:16 +02:00
var NG _HIDE _CLASS = 'ng-hide' ;
var NG _HIDE _IN _PROGRESS _CLASS = 'ng-hide-animate' ;
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngShow
2016-03-28 10:46:51 +00:00
*
* @ description
* The ` ngShow ` directive shows or hides the given HTML element based on the expression
2018-05-05 12:13:16 +02:00
* provided to the ` ngShow ` attribute . The element is shown or hidden by removing or adding
* the ` .ng-hide ` CSS class onto the element . The ` .ng-hide ` CSS class is predefined
2016-03-28 10:46:51 +00:00
* in AngularJS and sets the display style to none ( using an ! important flag ) .
* For CSP mode please add ` angular-csp.css ` to your html file ( see { @ link ng . directive : ngCsp ngCsp } ) .
*
2018-05-05 12:13:16 +02:00
* ` ` ` html
2016-03-28 10:46:51 +00:00
* <!-- when $scope . myValue is truthy ( element is visible ) -- >
* < div ng - show = "myValue" > < / d i v >
*
* <!-- when $scope . myValue is falsy ( element is hidden ) -- >
* < div ng - show = "myValue" class = "ng-hide" > < / d i v >
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* When the ` ngShow ` expression evaluates to a falsy value then the ` .ng-hide ` CSS class is added to the class
* attribute on the element causing it to become hidden . When truthy , the ` .ng-hide ` CSS class is removed
2016-03-28 10:46:51 +00:00
* from the element causing the element not to appear hidden .
*
* # # Why is ! important used ?
*
2018-05-05 12:13:16 +02:00
* You may be wondering why ! important is used for the ` .ng-hide ` CSS class . This is because the ` .ng-hide ` selector
2016-03-28 10:46:51 +00:00
* can be easily overridden by heavier selectors . For example , something as simple
* as changing the display style on a HTML list item would make hidden elements appear visible .
* This also becomes a bigger issue when dealing with CSS frameworks .
*
* By using ! important , the show and hide behavior will work as expected despite any clash between CSS selector
* specificity ( when ! important isn ' t used with any conflicting styles ) . If a developer chooses to override the
* styling to change how to hide an element then it is just a matter of using ! important in their own CSS code .
*
2018-05-05 12:13:16 +02:00
* # # # Overriding ` .ng-hide `
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* By default , the ` .ng-hide ` class will style the element with ` display: none!important ` . If you wish to change
* the hide behavior with ngShow / ngHide then this can be achieved by restating the styles for the ` .ng-hide `
* class CSS . Note that the selector that needs to be used is actually ` .ng-hide:not(.ng-hide-animate) ` to cope
* with extra animation classes that can be added .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* ` ` ` css
* . ng - hide : not ( . ng - hide - animate ) {
* /* this is just another form of hiding an element */
* display : block ! important ;
* position : absolute ;
* top : - 9999 px ;
* left : - 9999 px ;
2016-03-28 10:46:51 +00:00
* }
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* By default you don ' t need to override in CSS anything and the animations will work around the display style .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* # # A note about animations with ` ngShow `
2016-03-28 10:46:51 +00:00
*
* Animations in ngShow / ngHide work with the show and hide events that are triggered when the directive expression
* is true and false . This system works like the animation system present with ngClass except that
* you must also include the ! important flag to override the display property
* so that you can perform an animation when the element is hidden during the time of the animation .
*
2018-05-05 12:13:16 +02:00
* ` ` ` css
2016-03-28 10:46:51 +00:00
* //
* //a working example can be found at the bottom of this page
* //
* . my - element . ng - hide - add , . my - element . ng - hide - remove {
2018-05-05 12:13:16 +02:00
* / & # 4 2 ; t h i s i s r e q u i r e d a s o f 1 . 3 x t o p r o p e r l y
* apply all styling in a show / hide animation & # 42 ; /
* transition : 0 s linear all ;
* }
*
* . my - element . ng - hide - add - active ,
* . my - element . ng - hide - remove - active {
* /* the transition is defined in the active class */
* transition : 1 s linear all ;
2016-03-28 10:46:51 +00:00
* }
*
* . my - element . ng - hide - add { ... }
* . my - element . ng - hide - add . ng - hide - add - active { ... }
* . my - element . ng - hide - remove { ... }
* . my - element . ng - hide - remove . ng - hide - remove - active { ... }
2018-05-05 12:13:16 +02:00
* ` ` `
*
* Keep in mind that , as of AngularJS version 1.3 . 0 - beta . 11 , there is no need to change the display
* property to block during animation states -- ngAnimate will handle the style toggling automatically for you .
2016-03-28 10:46:51 +00:00
*
* @ animations
2018-05-05 12:13:16 +02:00
* addClass : ` .ng-hide ` - happens after the ` ngShow ` expression evaluates to a truthy value and the just before contents are set to visible
* removeClass : ` .ng-hide ` - happens after the ` ngShow ` expression evaluates to a non truthy value and just before the contents are set to hidden
2016-03-28 10:46:51 +00:00
*
* @ element ANY
* @ param { expression } ngShow If the { @ link guide / expression expression } is truthy
* then the element is shown or hidden respectively .
*
* @ example
2018-05-05 12:13:16 +02:00
< example module = "ngAnimate" deps = "angular-animate.js" animations = "true" >
2016-03-28 10:46:51 +00:00
< file name = "index.html" >
2016-04-18 12:34:29 +00:00
Click me : < input type = "checkbox" ng - model = "checked" > < br / >
2016-03-28 10:46:51 +00:00
< div >
Show :
< div class = "check-element animate-show" ng - show = "checked" >
2018-05-05 12:13:16 +02:00
< span class = "glyphicon glyphicon-thumbs-up" > < / s p a n > I s h o w u p w h e n y o u r c h e c k b o x i s c h e c k e d .
2016-03-28 10:46:51 +00:00
< / d i v >
< / d i v >
< div >
Hide :
< div class = "check-element animate-show" ng - hide = "checked" >
2018-05-05 12:13:16 +02:00
< span class = "glyphicon glyphicon-thumbs-down" > < / s p a n > I h i d e w h e n y o u r c h e c k b o x i s c h e c k e d .
2016-03-28 10:46:51 +00:00
< / d i v >
< / d i v >
< / f i l e >
2018-05-05 12:13:16 +02:00
< file name = "glyphicons.css" >
@ import url ( . . / . . / components / bootstrap - 3.1 . 1 / css / bootstrap . css ) ;
< / f i l e >
2016-03-28 10:46:51 +00:00
< file name = "animations.css" >
. animate - show {
2018-05-05 12:13:16 +02:00
line - height : 20 px ;
opacity : 1 ;
padding : 10 px ;
border : 1 px solid black ;
background : white ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
. animate - show . ng - hide - add . ng - hide - add - active ,
. animate - show . ng - hide - remove . ng - hide - remove - active {
- webkit - transition : all linear 0.5 s ;
transition : all linear 0.5 s ;
2016-03-28 10:46:51 +00:00
}
. animate - show . ng - hide {
2018-05-05 12:13:16 +02:00
line - height : 0 ;
opacity : 0 ;
padding : 0 10 px ;
2016-03-28 10:46:51 +00:00
}
. check - element {
2018-05-05 12:13:16 +02:00
padding : 10 px ;
border : 1 px solid black ;
background : white ;
2016-03-28 10:46:51 +00:00
}
< / f i l e >
2018-05-05 12:13:16 +02:00
< file name = "protractor.js" type = "protractor" >
var thumbsUp = element ( by . css ( 'span.glyphicon-thumbs-up' ) ) ;
var thumbsDown = element ( by . css ( 'span.glyphicon-thumbs-down' ) ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
it ( 'should check ng-show / ng-hide' , function ( ) {
expect ( thumbsUp . isDisplayed ( ) ) . toBeFalsy ( ) ;
expect ( thumbsDown . isDisplayed ( ) ) . toBeTruthy ( ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
element ( by . model ( 'checked' ) ) . click ( ) ;
expect ( thumbsUp . isDisplayed ( ) ) . toBeTruthy ( ) ;
expect ( thumbsDown . isDisplayed ( ) ) . toBeFalsy ( ) ;
} ) ;
2016-03-28 10:46:51 +00:00
< / f i l e >
< / e x a m p l e >
* /
var ngShowDirective = [ '$animate' , function ( $animate ) {
2018-05-05 12:13:16 +02:00
return {
restrict : 'A' ,
multiElement : true ,
link : function ( scope , element , attr ) {
scope . $watch ( attr . ngShow , function ngShowWatchAction ( value ) {
// we're adding a temporary, animation-specific class for ng-hide since this way
// we can control when the element is actually displayed on screen without having
// to have a global/greedy CSS selector that breaks when other animations are run.
// Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845
$animate [ value ? 'removeClass' : 'addClass' ] ( element , NG _HIDE _CLASS , {
tempClasses : NG _HIDE _IN _PROGRESS _CLASS
} ) ;
} ) ;
}
2016-03-28 10:46:51 +00:00
} ;
} ] ;
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngHide
2016-03-28 10:46:51 +00:00
*
* @ description
* The ` ngHide ` directive shows or hides the given HTML element based on the expression
2018-05-05 12:13:16 +02:00
* provided to the ` ngHide ` attribute . The element is shown or hidden by removing or adding
2016-03-28 10:46:51 +00:00
* the ` ng-hide ` CSS class onto the element . The ` .ng-hide ` CSS class is predefined
* in AngularJS and sets the display style to none ( using an ! important flag ) .
* For CSP mode please add ` angular-csp.css ` to your html file ( see { @ link ng . directive : ngCsp ngCsp } ) .
*
2018-05-05 12:13:16 +02:00
* ` ` ` html
2016-03-28 10:46:51 +00:00
* <!-- when $scope . myValue is truthy ( element is hidden ) -- >
2018-05-05 12:13:16 +02:00
* < div ng - hide = "myValue" class = "ng-hide" > < / d i v >
2016-03-28 10:46:51 +00:00
*
* <!-- when $scope . myValue is falsy ( element is visible ) -- >
2018-05-05 12:13:16 +02:00
* < div ng - hide = "myValue" > < / d i v >
* ` ` `
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* When the ` ngHide ` expression evaluates to a truthy value then the ` .ng-hide ` CSS class is added to the class
* attribute on the element causing it to become hidden . When falsy , the ` .ng-hide ` CSS class is removed
2016-03-28 10:46:51 +00:00
* from the element causing the element not to appear hidden .
*
* # # Why is ! important used ?
*
2018-05-05 12:13:16 +02:00
* You may be wondering why ! important is used for the ` .ng-hide ` CSS class . This is because the ` .ng-hide ` selector
2016-03-28 10:46:51 +00:00
* can be easily overridden by heavier selectors . For example , something as simple
* as changing the display style on a HTML list item would make hidden elements appear visible .
* This also becomes a bigger issue when dealing with CSS frameworks .
*
* By using ! important , the show and hide behavior will work as expected despite any clash between CSS selector
* specificity ( when ! important isn ' t used with any conflicting styles ) . If a developer chooses to override the
* styling to change how to hide an element then it is just a matter of using ! important in their own CSS code .
*
2018-05-05 12:13:16 +02:00
* # # # Overriding ` .ng-hide `
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* By default , the ` .ng-hide ` class will style the element with ` display: none!important ` . If you wish to change
* the hide behavior with ngShow / ngHide then this can be achieved by restating the styles for the ` .ng-hide `
* class in CSS :
2016-04-18 12:34:29 +00:00
*
2018-05-05 12:13:16 +02:00
* ` ` ` css
* . ng - hide {
* /* this is just another form of hiding an element */
* display : block ! important ;
* position : absolute ;
* top : - 9999 px ;
* left : - 9999 px ;
2016-03-28 10:46:51 +00:00
* }
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* By default you don ' t need to override in CSS anything and the animations will work around the display style .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* # # A note about animations with ` ngHide `
2016-03-28 10:46:51 +00:00
*
* Animations in ngShow / ngHide work with the show and hide events that are triggered when the directive expression
2018-05-05 12:13:16 +02:00
* is true and false . This system works like the animation system present with ngClass , except that the ` .ng-hide `
* CSS class is added and removed for you instead of your own CSS class .
2016-03-28 10:46:51 +00:00
*
2018-05-05 12:13:16 +02:00
* ` ` ` css
2016-03-28 10:46:51 +00:00
* //
* //a working example can be found at the bottom of this page
* //
* . my - element . ng - hide - add , . my - element . ng - hide - remove {
2018-05-05 12:13:16 +02:00
* transition : 0.5 s linear all ;
2016-03-28 10:46:51 +00:00
* }
*
* . my - element . ng - hide - add { ... }
* . my - element . ng - hide - add . ng - hide - add - active { ... }
* . my - element . ng - hide - remove { ... }
* . my - element . ng - hide - remove . ng - hide - remove - active { ... }
2018-05-05 12:13:16 +02:00
* ` ` `
*
* Keep in mind that , as of AngularJS version 1.3 . 0 - beta . 11 , there is no need to change the display
* property to block during animation states -- ngAnimate will handle the style toggling automatically for you .
2016-03-28 10:46:51 +00:00
*
* @ animations
2018-05-05 12:13:16 +02:00
* removeClass : ` .ng-hide ` - happens after the ` ngHide ` expression evaluates to a truthy value and just before the contents are set to hidden
* addClass : ` .ng-hide ` - happens after the ` ngHide ` expression evaluates to a non truthy value and just before the contents are set to visible
2016-03-28 10:46:51 +00:00
*
* @ element ANY
* @ param { expression } ngHide If the { @ link guide / expression expression } is truthy then
* the element is shown or hidden respectively .
*
* @ example
2018-05-05 12:13:16 +02:00
< example module = "ngAnimate" deps = "angular-animate.js" animations = "true" >
2016-03-28 10:46:51 +00:00
< file name = "index.html" >
2016-04-18 12:34:29 +00:00
Click me : < input type = "checkbox" ng - model = "checked" > < br / >
2016-03-28 10:46:51 +00:00
< div >
Show :
< div class = "check-element animate-hide" ng - show = "checked" >
2018-05-05 12:13:16 +02:00
< span class = "glyphicon glyphicon-thumbs-up" > < / s p a n > I s h o w u p w h e n y o u r c h e c k b o x i s c h e c k e d .
2016-03-28 10:46:51 +00:00
< / d i v >
< / d i v >
< div >
Hide :
< div class = "check-element animate-hide" ng - hide = "checked" >
2018-05-05 12:13:16 +02:00
< span class = "glyphicon glyphicon-thumbs-down" > < / s p a n > I h i d e w h e n y o u r c h e c k b o x i s c h e c k e d .
2016-03-28 10:46:51 +00:00
< / d i v >
< / d i v >
< / f i l e >
2018-05-05 12:13:16 +02:00
< file name = "glyphicons.css" >
@ import url ( . . / . . / components / bootstrap - 3.1 . 1 / css / bootstrap . css ) ;
< / f i l e >
2016-03-28 10:46:51 +00:00
< file name = "animations.css" >
. animate - hide {
2018-05-05 12:13:16 +02:00
- webkit - transition : all linear 0.5 s ;
transition : all linear 0.5 s ;
line - height : 20 px ;
opacity : 1 ;
padding : 10 px ;
border : 1 px solid black ;
background : white ;
2016-03-28 10:46:51 +00:00
}
. animate - hide . ng - hide {
2018-05-05 12:13:16 +02:00
line - height : 0 ;
opacity : 0 ;
padding : 0 10 px ;
2016-03-28 10:46:51 +00:00
}
. check - element {
2018-05-05 12:13:16 +02:00
padding : 10 px ;
border : 1 px solid black ;
background : white ;
2016-03-28 10:46:51 +00:00
}
< / f i l e >
2018-05-05 12:13:16 +02:00
< file name = "protractor.js" type = "protractor" >
var thumbsUp = element ( by . css ( 'span.glyphicon-thumbs-up' ) ) ;
var thumbsDown = element ( by . css ( 'span.glyphicon-thumbs-down' ) ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
it ( 'should check ng-show / ng-hide' , function ( ) {
expect ( thumbsUp . isDisplayed ( ) ) . toBeFalsy ( ) ;
expect ( thumbsDown . isDisplayed ( ) ) . toBeTruthy ( ) ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
element ( by . model ( 'checked' ) ) . click ( ) ;
expect ( thumbsUp . isDisplayed ( ) ) . toBeTruthy ( ) ;
expect ( thumbsDown . isDisplayed ( ) ) . toBeFalsy ( ) ;
} ) ;
2016-03-28 10:46:51 +00:00
< / f i l e >
< / e x a m p l e >
* /
var ngHideDirective = [ '$animate' , function ( $animate ) {
2018-05-05 12:13:16 +02:00
return {
restrict : 'A' ,
multiElement : true ,
link : function ( scope , element , attr ) {
scope . $watch ( attr . ngHide , function ngHideWatchAction ( value ) {
// The comment inside of the ngShowDirective explains why we add and
// remove a temporary class for the show/hide animation
$animate [ value ? 'addClass' : 'removeClass' ] ( element , NG _HIDE _CLASS , {
tempClasses : NG _HIDE _IN _PROGRESS _CLASS
} ) ;
} ) ;
}
2016-03-28 10:46:51 +00:00
} ;
} ] ;
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngStyle
2016-03-28 10:46:51 +00:00
* @ restrict AC
*
* @ description
* The ` ngStyle ` directive allows you to set CSS style on an HTML element conditionally .
*
* @ element ANY
2018-05-05 12:13:16 +02:00
* @ param { expression } ngStyle
*
* { @ link guide / expression Expression } which evals to an
* object whose keys are CSS style names and values are corresponding values for those CSS
* keys .
*
* Since some CSS style names are not valid keys for an object , they must be quoted .
* See the 'background-color' style in the example below .
2016-03-28 10:46:51 +00:00
*
* @ example
< example >
< file name = "index.html" >
2018-05-05 12:13:16 +02:00
< input type = "button" value = "set color" ng - click = "myStyle={color:'red'}" >
< input type = "button" value = "set background" ng - click = "myStyle={'background-color':'blue'}" >
2016-03-28 10:46:51 +00:00
< input type = "button" value = "clear" ng - click = "myStyle={}" >
< br / >
< span ng - style = "myStyle" > Sample Text < / s p a n >
< pre > myStyle = { { myStyle } } < / p r e >
< / f i l e >
< file name = "style.css" >
span {
color : black ;
}
< / f i l e >
2018-05-05 12:13:16 +02:00
< file name = "protractor.js" type = "protractor" >
var colorSpan = element ( by . css ( 'span' ) ) ;
2016-03-28 10:46:51 +00:00
it ( 'should check ng-style' , function ( ) {
2018-05-05 12:13:16 +02:00
expect ( colorSpan . getCssValue ( 'color' ) ) . toBe ( 'rgba(0, 0, 0, 1)' ) ;
element ( by . css ( 'input[value=\'set color\']' ) ) . click ( ) ;
expect ( colorSpan . getCssValue ( 'color' ) ) . toBe ( 'rgba(255, 0, 0, 1)' ) ;
element ( by . css ( 'input[value=clear]' ) ) . click ( ) ;
expect ( colorSpan . getCssValue ( 'color' ) ) . toBe ( 'rgba(0, 0, 0, 1)' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
< / f i l e >
< / e x a m p l e >
* /
var ngStyleDirective = ngDirective ( function ( scope , element , attr ) {
scope . $watch ( attr . ngStyle , function ngStyleWatchAction ( newStyles , oldStyles ) {
if ( oldStyles && ( newStyles !== oldStyles ) ) {
forEach ( oldStyles , function ( val , style ) { element . css ( style , '' ) ; } ) ;
}
if ( newStyles ) element . css ( newStyles ) ;
} , true ) ;
} ) ;
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngSwitch
2016-03-28 10:46:51 +00:00
* @ restrict EA
*
* @ description
* The ` ngSwitch ` directive is used to conditionally swap DOM structure on your template based on a scope expression .
* Elements within ` ngSwitch ` but without ` ngSwitchWhen ` or ` ngSwitchDefault ` directives will be preserved at the location
* as specified in the template .
*
* The directive itself works similar to ngInclude , however , instead of downloading template code ( or loading it
2018-05-05 12:13:16 +02:00
* from the template cache ) , ` ngSwitch ` simply chooses one of the nested elements and makes it visible based on which element
2016-03-28 10:46:51 +00:00
* matches the value obtained from the evaluated expression . In other words , you define a container element
* ( where you place the directive ) , place an expression on the * * ` on="..." ` attribute * *
* ( or the * * ` ng-switch="..." ` attribute * * ) , define any inner elements inside of the directive and place
* a when attribute per element . The when attribute is used to inform ngSwitch which element to display when the on
* expression is evaluated . If a matching expression is not found via a when attribute then an element with the default
* attribute is displayed .
*
* < div class = "alert alert-info" >
* Be aware that the attribute values to match against cannot be expressions . They are interpreted
* as literal string values to match against .
* For example , * * ` ng-switch-when="someVal" ` * * will match against the string ` "someVal" ` not against the
* value of the expression ` $ scope.someVal ` .
* < / d i v >
* @ animations
2016-04-18 12:34:29 +00:00
* enter - happens after the ngSwitch contents change and the matched child element is placed inside the container
* leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM
2016-03-28 10:46:51 +00:00
*
* @ usage
2018-05-05 12:13:16 +02:00
*
* ` ` `
2016-03-28 10:46:51 +00:00
* < ANY ng - switch = "expression" >
* < ANY ng - switch - when = "matchValue1" > ... < / A N Y >
* < ANY ng - switch - when = "matchValue2" > ... < / A N Y >
* < ANY ng - switch - default > ... < / A N Y >
* < / A N Y >
2018-05-05 12:13:16 +02:00
* ` ` `
2016-03-28 10:46:51 +00:00
*
*
* @ scope
2018-05-05 12:13:16 +02:00
* @ priority 1200
* @ param { * } ngSwitch | on expression to match against < code > ng - switch - when < / c o d e > .
2016-03-28 10:46:51 +00:00
* On child elements add :
*
* * ` ngSwitchWhen ` : the case statement to match against . If match then this
* case will be displayed . If the same match appears multiple times , all the
* elements will be displayed .
* * ` ngSwitchDefault ` : the default case when no other case match . If there
* are multiple default cases , all of them will be displayed when no other
* case match .
*
*
* @ example
2018-05-05 12:13:16 +02:00
< example module = "switchExample" deps = "angular-animate.js" animations = "true" >
2016-03-28 10:46:51 +00:00
< file name = "index.html" >
2018-05-05 12:13:16 +02:00
< div ng - controller = "ExampleController" >
2016-03-28 10:46:51 +00:00
< select ng - model = "selection" ng - options = "item for item in items" >
< / s e l e c t >
2018-05-05 12:13:16 +02:00
< code > selection = { { selection } } < / c o d e >
2016-03-28 10:46:51 +00:00
< hr / >
< div class = "animate-switch-container"
ng - switch on = "selection" >
< div class = "animate-switch" ng - switch - when = "settings" > Settings Div < / d i v >
< div class = "animate-switch" ng - switch - when = "home" > Home Span < / d i v >
< div class = "animate-switch" ng - switch - default > default < / d i v >
< / d i v >
< / d i v >
< / f i l e >
< file name = "script.js" >
2018-05-05 12:13:16 +02:00
angular . module ( 'switchExample' , [ 'ngAnimate' ] )
. controller ( 'ExampleController' , [ '$scope' , function ( $scope ) {
$scope . items = [ 'settings' , 'home' , 'other' ] ;
$scope . selection = $scope . items [ 0 ] ;
} ] ) ;
2016-03-28 10:46:51 +00:00
< / f i l e >
< file name = "animations.css" >
. animate - switch - container {
position : relative ;
background : white ;
border : 1 px solid black ;
height : 40 px ;
overflow : hidden ;
}
. animate - switch {
padding : 10 px ;
}
. animate - switch . ng - animate {
2016-04-18 12:34:29 +00:00
- webkit - transition : all cubic - bezier ( 0.250 , 0.460 , 0.450 , 0.940 ) 0.5 s ;
2016-03-28 10:46:51 +00:00
transition : all cubic - bezier ( 0.250 , 0.460 , 0.450 , 0.940 ) 0.5 s ;
position : absolute ;
top : 0 ;
left : 0 ;
right : 0 ;
bottom : 0 ;
}
. animate - switch . ng - leave . ng - leave - active ,
. animate - switch . ng - enter {
top : - 50 px ;
}
. animate - switch . ng - leave ,
. animate - switch . ng - enter . ng - enter - active {
top : 0 ;
}
< / f i l e >
2018-05-05 12:13:16 +02:00
< file name = "protractor.js" type = "protractor" >
var switchElem = element ( by . css ( '[ng-switch]' ) ) ;
var select = element ( by . model ( 'selection' ) ) ;
2016-03-28 10:46:51 +00:00
it ( 'should start in settings' , function ( ) {
2018-05-05 12:13:16 +02:00
expect ( switchElem . getText ( ) ) . toMatch ( /Settings Div/ ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should change to home' , function ( ) {
2018-05-05 12:13:16 +02:00
select . all ( by . css ( 'option' ) ) . get ( 1 ) . click ( ) ;
expect ( switchElem . getText ( ) ) . toMatch ( /Home Span/ ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should select default' , function ( ) {
2018-05-05 12:13:16 +02:00
select . all ( by . css ( 'option' ) ) . get ( 2 ) . click ( ) ;
expect ( switchElem . getText ( ) ) . toMatch ( /default/ ) ;
2016-03-28 10:46:51 +00:00
} ) ;
< / f i l e >
< / e x a m p l e >
* /
2016-04-18 12:34:29 +00:00
var ngSwitchDirective = [ '$animate' , function ( $animate ) {
2016-03-28 10:46:51 +00:00
return {
2016-04-18 12:34:29 +00:00
restrict : 'EA' ,
2016-03-28 10:46:51 +00:00
require : 'ngSwitch' ,
// asks for $scope to fool the BC controller module
controller : [ '$scope' , function ngSwitchController ( ) {
this . cases = { } ;
} ] ,
link : function ( scope , element , attr , ngSwitchController ) {
var watchExpr = attr . ngSwitch || attr . on ,
2018-05-05 12:13:16 +02:00
selectedTranscludes = [ ] ,
selectedElements = [ ] ,
previousLeaveAnimations = [ ] ,
2016-03-28 10:46:51 +00:00
selectedScopes = [ ] ;
2018-05-05 12:13:16 +02:00
var spliceFactory = function ( array , index ) {
return function ( ) { array . splice ( index , 1 ) ; } ;
} ;
2016-03-28 10:46:51 +00:00
scope . $watch ( watchExpr , function ngSwitchWatchAction ( value ) {
2018-05-05 12:13:16 +02:00
var i , ii ;
for ( i = 0 , ii = previousLeaveAnimations . length ; i < ii ; ++ i ) {
$animate . cancel ( previousLeaveAnimations [ i ] ) ;
}
previousLeaveAnimations . length = 0 ;
for ( i = 0 , ii = selectedScopes . length ; i < ii ; ++ i ) {
var selected = getBlockNodes ( selectedElements [ i ] . clone ) ;
2016-03-28 10:46:51 +00:00
selectedScopes [ i ] . $destroy ( ) ;
2018-05-05 12:13:16 +02:00
var promise = previousLeaveAnimations [ i ] = $animate . leave ( selected ) ;
promise . then ( spliceFactory ( previousLeaveAnimations , i ) ) ;
2016-03-28 10:46:51 +00:00
}
2018-05-05 12:13:16 +02:00
selectedElements . length = 0 ;
selectedScopes . length = 0 ;
2016-03-28 10:46:51 +00:00
if ( ( selectedTranscludes = ngSwitchController . cases [ '!' + value ] || ngSwitchController . cases [ '?' ] ) ) {
forEach ( selectedTranscludes , function ( selectedTransclude ) {
2018-05-05 12:13:16 +02:00
selectedTransclude . transclude ( function ( caseElement , selectedScope ) {
selectedScopes . push ( selectedScope ) ;
2016-03-28 10:46:51 +00:00
var anchor = selectedTransclude . element ;
2018-05-05 12:13:16 +02:00
caseElement [ caseElement . length ++ ] = document . createComment ( ' end ngSwitchWhen: ' ) ;
var block = { clone : caseElement } ;
2016-03-28 10:46:51 +00:00
2018-05-05 12:13:16 +02:00
selectedElements . push ( block ) ;
2016-03-28 10:46:51 +00:00
$animate . enter ( caseElement , anchor . parent ( ) , anchor ) ;
} ) ;
} ) ;
}
} ) ;
}
} ;
} ] ;
var ngSwitchWhenDirective = ngDirective ( {
transclude : 'element' ,
2018-05-05 12:13:16 +02:00
priority : 1200 ,
2016-03-28 10:46:51 +00:00
require : '^ngSwitch' ,
2018-05-05 12:13:16 +02:00
multiElement : true ,
2016-03-28 10:46:51 +00:00
link : function ( scope , element , attrs , ctrl , $transclude ) {
ctrl . cases [ '!' + attrs . ngSwitchWhen ] = ( ctrl . cases [ '!' + attrs . ngSwitchWhen ] || [ ] ) ;
ctrl . cases [ '!' + attrs . ngSwitchWhen ] . push ( { transclude : $transclude , element : element } ) ;
}
} ) ;
var ngSwitchDefaultDirective = ngDirective ( {
transclude : 'element' ,
2018-05-05 12:13:16 +02:00
priority : 1200 ,
2016-03-28 10:46:51 +00:00
require : '^ngSwitch' ,
2018-05-05 12:13:16 +02:00
multiElement : true ,
2016-03-28 10:46:51 +00:00
link : function ( scope , element , attr , ctrl , $transclude ) {
ctrl . cases [ '?' ] = ( ctrl . cases [ '?' ] || [ ] ) ;
ctrl . cases [ '?' ] . push ( { transclude : $transclude , element : element } ) ;
}
} ) ;
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name ngTransclude
* @ restrict EAC
2016-03-28 10:46:51 +00:00
*
* @ description
* Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion .
*
2016-04-18 12:34:29 +00:00
* Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted .
2016-03-28 10:46:51 +00:00
*
* @ element ANY
*
* @ example
2018-05-05 12:13:16 +02:00
< example module = "transcludeExample" >
< file name = "index.html" >
2016-04-18 12:34:29 +00:00
< script >
2018-05-05 12:13:16 +02:00
angular . module ( 'transcludeExample' , [ ] )
2016-04-18 12:34:29 +00:00
. directive ( 'pane' , function ( ) {
return {
restrict : 'E' ,
transclude : true ,
scope : { title : '@' } ,
template : '<div style="border: 1px solid black;">' +
'<div style="background-color: gray">{{title}}</div>' +
2018-05-05 12:13:16 +02:00
'<ng-transclude></ng-transclude>' +
2016-04-18 12:34:29 +00:00
'</div>'
} ;
2018-05-05 12:13:16 +02:00
} )
. controller ( 'ExampleController' , [ '$scope' , function ( $scope ) {
$scope . title = 'Lorem Ipsum' ;
$scope . text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...' ;
} ] ) ;
2016-04-18 12:34:29 +00:00
< / s c r i p t >
2018-05-05 12:13:16 +02:00
< div ng - controller = "ExampleController" >
< input ng - model = "title" > < br / >
2016-04-18 12:34:29 +00:00
< textarea ng - model = "text" > < /textarea> <br/ >
< pane title = "{{title}}" > { { text } } < / p a n e >
< / d i v >
2018-05-05 12:13:16 +02:00
< / f i l e >
< file name = "protractor.js" type = "protractor" >
2016-04-18 12:34:29 +00:00
it ( 'should have transcluded' , function ( ) {
2018-05-05 12:13:16 +02:00
var titleElement = element ( by . model ( 'title' ) ) ;
titleElement . clear ( ) ;
titleElement . sendKeys ( 'TITLE' ) ;
var textElement = element ( by . model ( 'text' ) ) ;
textElement . clear ( ) ;
textElement . sendKeys ( 'TEXT' ) ;
expect ( element ( by . binding ( 'title' ) ) . getText ( ) ) . toEqual ( 'TITLE' ) ;
expect ( element ( by . binding ( 'text' ) ) . getText ( ) ) . toEqual ( 'TEXT' ) ;
2016-04-18 12:34:29 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-03-28 10:46:51 +00:00
*
* /
var ngTranscludeDirective = ngDirective ( {
2018-05-05 12:13:16 +02:00
restrict : 'EAC' ,
link : function ( $scope , $element , $attrs , controller , $transclude ) {
2016-03-28 10:46:51 +00:00
if ( ! $transclude ) {
2016-04-18 12:34:29 +00:00
throw minErr ( 'ngTransclude' ) ( 'orphan' ,
2018-05-05 12:13:16 +02:00
'Illegal use of ngTransclude directive in the template! ' +
'No parent directive that requires a transclusion found. ' +
'Element: {0}' ,
startingTag ( $element ) ) ;
2016-04-18 12:34:29 +00:00
}
2018-05-05 12:13:16 +02:00
$transclude ( function ( clone ) {
2016-04-18 12:34:29 +00:00
$element . empty ( ) ;
$element . append ( clone ) ;
} ) ;
2016-03-28 10:46:51 +00:00
}
} ) ;
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name script
2016-03-28 10:46:51 +00:00
* @ restrict E
*
* @ description
2018-05-05 12:13:16 +02:00
* Load the content of a ` <script> ` element into { @ link ng . $templateCache ` $ templateCache ` } , so that the
* template can be used by { @ link ng . directive : ngInclude ` ngInclude ` } ,
* { @ link ngRoute . directive : ngView ` ngView ` } , or { @ link guide / directive directives } . The type of the
2016-03-28 10:46:51 +00:00
* ` <script> ` element must be specified as ` text/ng-template ` , and a cache name for the template must be
* assigned through the element 's `id`, which can then be used as a directive' s ` templateUrl ` .
*
2018-05-05 12:13:16 +02:00
* @ param { string } type Must be set to ` 'text/ng-template' ` .
2016-03-28 10:46:51 +00:00
* @ param { string } id Cache name of the template .
*
* @ example
2018-05-05 12:13:16 +02:00
< example >
< file name = "index.html" >
2016-03-28 10:46:51 +00:00
< script type = "text/ng-template" id = "/tpl.html" >
Content of the template .
< / s c r i p t >
< a ng - click = "currentTpl='/tpl.html'" id = "tpl-link" > Load inlined template < / a >
< div id = "tpl-content" ng - include src = "currentTpl" > < / d i v >
2018-05-05 12:13:16 +02:00
< / f i l e >
< file name = "protractor.js" type = "protractor" >
2016-03-28 10:46:51 +00:00
it ( 'should load template defined inside script tag' , function ( ) {
2018-05-05 12:13:16 +02:00
element ( by . css ( '#tpl-link' ) ) . click ( ) ;
expect ( element ( by . css ( '#tpl-content' ) ) . getText ( ) ) . toMatch ( /Content of the template/ ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
var scriptDirective = [ '$templateCache' , function ( $templateCache ) {
return {
restrict : 'E' ,
terminal : true ,
compile : function ( element , attr ) {
if ( attr . type == 'text/ng-template' ) {
var templateUrl = attr . id ,
text = element [ 0 ] . text ;
$templateCache . put ( templateUrl , text ) ;
}
}
} ;
} ] ;
2016-04-18 12:34:29 +00:00
var ngOptionsMinErr = minErr ( 'ngOptions' ) ;
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc directive
2018-05-05 12:13:16 +02:00
* @ name select
2016-03-28 10:46:51 +00:00
* @ restrict E
*
* @ description
* HTML ` SELECT ` element with angular data - binding .
*
2016-04-18 12:34:29 +00:00
* # ` ngOptions `
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* The ` ngOptions ` attribute can be used to dynamically generate a list of ` <option> `
* elements for the ` <select> ` element using the array or object obtained by evaluating the
2018-05-05 12:13:16 +02:00
* ` ngOptions ` comprehension expression .
*
* In many cases , ` ngRepeat ` can be used on ` <option> ` elements instead of ` ngOptions ` to achieve a
* similar result . However , ` ngOptions ` provides some benefits such as reducing memory and
* increasing speed by not creating a new scope for each repeated instance , as well as providing
* more flexibility in how the ` <select> ` ' s model is assigned via the ` select ` * * ` as ` * * part of the
* comprehension expression . ` ngOptions ` should be used when the ` <select> ` model needs to be bound
* to a non - string value . This is because an option element can only be bound to string values at
* present .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* When an item in the ` <select> ` menu is selected , the array element or object property
* represented by the selected option will be bound to the model identified by the ` ngModel `
* directive .
2016-03-28 10:46:51 +00:00
*
* Optionally , a single hard - coded ` <option> ` element , with the value set to an empty string , can
* be nested into the ` <select> ` element . This element will then represent the ` null ` or "not selected"
* option . See example below for demonstration .
*
2018-05-05 12:13:16 +02:00
* < div class = "alert alert-warning" >
* * * Note : * * ` ngModel ` compares by reference , not value . This is important when binding to an
* array of objects . See an example [ in this jsfiddle ] ( http : //jsfiddle.net/qWzTb/).
* < / d i v >
*
* # # ` select ` * * ` as ` * *
*
* Using ` select ` * * ` as ` * * will bind the result of the ` select ` expression to the model , but
* the value of the ` <select> ` and ` <option> ` html elements will be either the index ( for array data sources )
* or property name ( for object data sources ) of the value within the collection . If a * * ` track by ` * * expression
* is used , the result of that expression will be set as the value of the ` option ` and ` select ` elements .
*
*
* # # # ` select ` * * ` as ` * * and * * ` track by ` * *
*
* < div class = "alert alert-warning" >
* Do not use ` select ` * * ` as ` * * and * * ` track by ` * * in the same expression . They are not designed to work together .
* < / d i v >
*
* Consider the following example :
*
* ` ` ` html
* < select ng - options = "item.subItem as item.label for item in values track by item.id" ng - model = "selected" >
* ` ` `
*
* ` ` ` js
* $scope . values = [ {
* id : 1 ,
* label : 'aLabel' ,
* subItem : { name : 'aSubItem' }
* } , {
* id : 2 ,
* label : 'bLabel' ,
* subItem : { name : 'bSubItem' }
* } ] ;
*
* $scope . selected = { name : 'aSubItem' } ;
* ` ` `
*
* With the purpose of preserving the selection , the * * ` track by ` * * expression is always applied to the element
* of the data source ( to ` item ` in this example ) . To calculate whether an element is selected , we do the
* following :
*
* 1. Apply * * ` track by ` * * to the elements in the array . In the example : ` [1, 2] `
* 2. Apply * * ` track by ` * * to the already selected value in ` ngModel ` .
* In the example : this is not possible as * * ` track by ` * * refers to ` item.id ` , but the selected
* value from ` ngModel ` is ` {name: 'aSubItem'} ` , so the * * ` track by ` * * expression is applied to
* a wrong object , the selected element can ' t be found , ` <select> ` is always reset to the " not
* selected " option .
*
2016-03-28 10:46:51 +00:00
*
* @ param { string } ngModel Assignable angular expression to data - bind to .
* @ param { string = } name Property name of the form under which the control is published .
2016-04-18 12:34:29 +00:00
* @ param { string = } required The control is considered valid only if value is entered .
* @ param { string = } ngRequired Adds ` required ` attribute and ` required ` validation constraint to
* the element when the ngRequired expression evaluates to true . Use ` ngRequired ` instead of
* ` required ` when you want to data - bind to the ` required ` attribute .
* @ param { comprehension _expression = } ngOptions in one of the following forms :
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* * for array data sources :
* * ` label ` * * ` for ` * * ` value ` * * ` in ` * * ` array `
* * ` select ` * * ` as ` * * ` label ` * * ` for ` * * ` value ` * * ` in ` * * ` array `
2018-05-05 12:13:16 +02:00
* * ` label ` * * ` group by ` * * ` group ` * * ` for ` * * ` value ` * * ` in ` * * ` array `
* * ` label ` * * ` group by ` * * ` group ` * * ` for ` * * ` value ` * * ` in ` * * ` array ` * * ` track by ` * * ` trackexpr `
* * ` label ` * * ` for ` * * ` value ` * * ` in ` * * ` array ` | orderBy : ` orderexpr ` * * ` track by ` * * ` trackexpr `
* ( for including a filter with ` track by ` )
2016-04-18 12:34:29 +00:00
* * for object data sources :
* * ` label ` * * ` for ( ` * * ` key ` * * ` , ` * * ` value ` * * ` ) in ` * * ` object `
* * ` select ` * * ` as ` * * ` label ` * * ` for ( ` * * ` key ` * * ` , ` * * ` value ` * * ` ) in ` * * ` object `
* * ` label ` * * ` group by ` * * ` group ` * * ` for ( ` * * ` key ` * * ` , ` * * ` value ` * * ` ) in ` * * ` object `
* * ` select ` * * ` as ` * * ` label ` * * ` group by ` * * ` group `
* * * ` for ` ` ( ` * * ` key ` * * ` , ` * * ` value ` * * ` ) in ` * * ` object `
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* Where :
*
* * ` array ` / ` object ` : an expression which evaluates to an array / object to iterate over .
* * ` value ` : local variable which will refer to each item in the ` array ` or each property value
* of ` object ` during iteration .
* * ` key ` : local variable which will refer to a property name in ` object ` during iteration .
* * ` label ` : The result of this expression will be the label for ` <option> ` element . The
* ` expression ` will most likely refer to the ` value ` variable ( e . g . ` value.propertyName ` ) .
* * ` select ` : The result of this expression will be bound to the model of the parent ` <select> `
* element . If not specified , ` select ` expression will default to ` value ` .
* * ` group ` : The result of this expression will be used to group options using the ` <optgroup> `
* DOM element .
* * ` trackexpr ` : Used when working with an array of objects . The result of this expression will be
* used to identify the objects in the array . The ` trackexpr ` will most likely refer to the
2018-05-05 12:13:16 +02:00
* ` value ` variable ( e . g . ` value.propertyName ` ) . With this the selection is preserved
* even when the options are recreated ( e . g . reloaded from the server ) .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ example
2018-05-05 12:13:16 +02:00
< example module = "selectExample" >
< file name = "index.html" >
2016-04-18 12:34:29 +00:00
< script >
2018-05-05 12:13:16 +02:00
angular . module ( 'selectExample' , [ ] )
. controller ( 'ExampleController' , [ '$scope' , function ( $scope ) {
$scope . colors = [
{ name : 'black' , shade : 'dark' } ,
{ name : 'white' , shade : 'light' } ,
{ name : 'red' , shade : 'dark' } ,
{ name : 'blue' , shade : 'dark' } ,
{ name : 'yellow' , shade : 'light' }
] ;
$scope . myColor = $scope . colors [ 2 ] ; // red
} ] ) ;
2016-04-18 12:34:29 +00:00
< / s c r i p t >
2018-05-05 12:13:16 +02:00
< div ng - controller = "ExampleController" >
2016-04-18 12:34:29 +00:00
< ul >
< li ng - repeat = "color in colors" >
Name : < input ng - model = "color.name" >
[ < a href ng - click = "colors.splice($index, 1)" > X < / a > ]
< / l i >
< li >
[ < a href ng - click = "colors.push({})" > add < / a > ]
< / l i >
< / u l >
< hr / >
Color ( null not allowed ) :
2018-05-05 12:13:16 +02:00
< select ng - model = "myColor" ng - options = "color.name for color in colors" > < / s e l e c t > < b r >
2016-04-18 12:34:29 +00:00
Color ( null allowed ) :
< span class = "nullable" >
2018-05-05 12:13:16 +02:00
< select ng - model = "myColor" ng - options = "color.name for color in colors" >
2016-04-18 12:34:29 +00:00
< option value = "" > -- choose color -- < / o p t i o n >
< / s e l e c t >
< /span><br/ >
Color grouped by shade :
2018-05-05 12:13:16 +02:00
< select ng - model = "myColor" ng - options = "color.name group by color.shade for color in colors" >
2016-04-18 12:34:29 +00:00
< /select><br/ >
2018-05-05 12:13:16 +02:00
Select < a href ng - click = "myColor = { name:'not in list', shade: 'other' }" > bogus < / a > . < b r >
2016-04-18 12:34:29 +00:00
< hr / >
2018-05-05 12:13:16 +02:00
Currently selected : { { { selected _color : myColor } } }
2016-04-18 12:34:29 +00:00
< div style = "border:solid 1px black; height:20px"
2018-05-05 12:13:16 +02:00
ng - style = "{'background-color':myColor.name}" >
2016-04-18 12:34:29 +00:00
< / d i v >
< / d i v >
2018-05-05 12:13:16 +02:00
< / f i l e >
< file name = "protractor.js" type = "protractor" >
2016-04-18 12:34:29 +00:00
it ( 'should check ng-options' , function ( ) {
2018-05-05 12:13:16 +02:00
expect ( element ( by . binding ( '{selected_color:myColor}' ) ) . getText ( ) ) . toMatch ( 'red' ) ;
element . all ( by . model ( 'myColor' ) ) . first ( ) . click ( ) ;
element . all ( by . css ( 'select[ng-model="myColor"] option' ) ) . first ( ) . click ( ) ;
expect ( element ( by . binding ( '{selected_color:myColor}' ) ) . getText ( ) ) . toMatch ( 'black' ) ;
element ( by . css ( '.nullable select[ng-model="myColor"]' ) ) . click ( ) ;
element . all ( by . css ( '.nullable select[ng-model="myColor"] option' ) ) . first ( ) . click ( ) ;
expect ( element ( by . binding ( '{selected_color:myColor}' ) ) . getText ( ) ) . toMatch ( 'null' ) ;
2016-04-18 12:34:29 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
< / f i l e >
< / e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
2018-05-05 12:13:16 +02:00
var ngOptionsDirective = valueFn ( {
restrict : 'A' ,
terminal : true
} ) ;
2016-04-18 12:34:29 +00:00
// jshint maxlen: false
var selectDirective = [ '$compile' , '$parse' , function ( $compile , $parse ) {
//000011111111110000000000022222222220000000000000000000003333333333000000000000004444444444444440000000005555555555555550000000666666666666666000000000000000777777777700000000000000000008888888888
var NG _OPTIONS _REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\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]+?))?$/ ,
nullModelCtrl = { $setViewValue : noop } ;
// jshint maxlen: 100
return {
restrict : 'E' ,
require : [ 'select' , '?ngModel' ] ,
controller : [ '$element' , '$scope' , '$attrs' , function ( $element , $scope , $attrs ) {
var self = this ,
optionsMap = { } ,
ngModelCtrl = nullModelCtrl ,
nullOption ,
unknownOption ;
self . databound = $attrs . ngModel ;
self . init = function ( ngModelCtrl _ , nullOption _ , unknownOption _ ) {
ngModelCtrl = ngModelCtrl _ ;
nullOption = nullOption _ ;
unknownOption = unknownOption _ ;
} ;
2018-05-05 12:13:16 +02:00
self . addOption = function ( value , element ) {
2016-04-18 12:34:29 +00:00
assertNotHasOwnProperty ( value , '"option value"' ) ;
optionsMap [ value ] = true ;
if ( ngModelCtrl . $viewValue == value ) {
$element . val ( value ) ;
if ( unknownOption . parent ( ) ) unknownOption . remove ( ) ;
}
2018-05-05 12:13:16 +02:00
// Workaround for https://code.google.com/p/chromium/issues/detail?id=381459
// Adding an <option selected="selected"> element to a <select required="required"> should
// automatically select the new element
if ( element && element [ 0 ] . hasAttribute ( 'selected' ) ) {
element [ 0 ] . selected = true ;
}
2016-04-18 12:34:29 +00:00
} ;
self . removeOption = function ( value ) {
if ( this . hasOption ( value ) ) {
delete optionsMap [ value ] ;
2018-05-05 12:13:16 +02:00
if ( ngModelCtrl . $viewValue === value ) {
2016-04-18 12:34:29 +00:00
this . renderUnknownOption ( value ) ;
}
}
} ;
self . renderUnknownOption = function ( val ) {
var unknownVal = '? ' + hashKey ( val ) + ' ?' ;
unknownOption . val ( unknownVal ) ;
$element . prepend ( unknownOption ) ;
$element . val ( unknownVal ) ;
unknownOption . prop ( 'selected' , true ) ; // needed for IE
} ;
self . hasOption = function ( value ) {
return optionsMap . hasOwnProperty ( value ) ;
} ;
$scope . $on ( '$destroy' , function ( ) {
// disable unknown option so that we don't do work when the whole select is being destroyed
self . renderUnknownOption = noop ;
} ) ;
} ] ,
link : function ( scope , element , attr , ctrls ) {
// if ngModel is not defined, we don't need to do anything
if ( ! ctrls [ 1 ] ) return ;
var selectCtrl = ctrls [ 0 ] ,
ngModelCtrl = ctrls [ 1 ] ,
multiple = attr . multiple ,
optionsExp = attr . ngOptions ,
nullOption = false , // if false, user will not be able to select it (used by ngOptions)
emptyOption ,
2018-05-05 12:13:16 +02:00
renderScheduled = false ,
2016-04-18 12:34:29 +00:00
// we can't just jqLite('<option>') since jqLite is not smart enough
// to create it in <select> and IE barfs otherwise.
optionTemplate = jqLite ( document . createElement ( 'option' ) ) ,
optGroupTemplate = jqLite ( document . createElement ( 'optgroup' ) ) ,
unknownOption = optionTemplate . clone ( ) ;
// find "null" option
2018-05-05 12:13:16 +02:00
for ( var i = 0 , children = element . children ( ) , ii = children . length ; i < ii ; i ++ ) {
2016-04-18 12:34:29 +00:00
if ( children [ i ] . value === '' ) {
emptyOption = nullOption = children . eq ( i ) ;
break ;
}
}
selectCtrl . init ( ngModelCtrl , nullOption , unknownOption ) ;
// required validator
if ( multiple ) {
ngModelCtrl . $isEmpty = function ( value ) {
return ! value || value . length === 0 ;
} ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( optionsExp ) setupAsOptions ( scope , element , ngModelCtrl ) ;
else if ( multiple ) setupAsMultiple ( scope , element , ngModelCtrl ) ;
else setupAsSingle ( scope , element , ngModelCtrl , selectCtrl ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
////////////////////////////
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
function setupAsSingle ( scope , selectElement , ngModelCtrl , selectCtrl ) {
ngModelCtrl . $render = function ( ) {
var viewValue = ngModelCtrl . $viewValue ;
if ( selectCtrl . hasOption ( viewValue ) ) {
if ( unknownOption . parent ( ) ) unknownOption . remove ( ) ;
selectElement . val ( viewValue ) ;
if ( viewValue === '' ) emptyOption . prop ( 'selected' , true ) ; // to make IE9 happy
} else {
2018-05-05 12:13:16 +02:00
if ( viewValue == null && emptyOption ) {
2016-04-18 12:34:29 +00:00
selectElement . val ( '' ) ;
} else {
selectCtrl . renderUnknownOption ( viewValue ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
}
2016-03-28 10:46:51 +00:00
} ;
2016-04-18 12:34:29 +00:00
selectElement . on ( 'change' , function ( ) {
scope . $apply ( function ( ) {
if ( unknownOption . parent ( ) ) unknownOption . remove ( ) ;
ngModelCtrl . $setViewValue ( selectElement . val ( ) ) ;
} ) ;
} ) ;
}
function setupAsMultiple ( scope , selectElement , ctrl ) {
var lastView ;
ctrl . $render = function ( ) {
var items = new HashMap ( ctrl . $viewValue ) ;
forEach ( selectElement . find ( 'option' ) , function ( option ) {
2016-03-28 10:46:51 +00:00
option . selected = isDefined ( items . get ( option . value ) ) ;
} ) ;
} ;
// we have to do it on each watch since ngModel watches reference, but
// we need to work of an array, so we need to see if anything was inserted/removed
scope . $watch ( function selectMultipleWatch ( ) {
2016-04-18 12:34:29 +00:00
if ( ! equals ( lastView , ctrl . $viewValue ) ) {
2018-05-05 12:13:16 +02:00
lastView = shallowCopy ( ctrl . $viewValue ) ;
2016-04-18 12:34:29 +00:00
ctrl . $render ( ) ;
2016-03-28 10:46:51 +00:00
}
} ) ;
2016-04-18 12:34:29 +00:00
selectElement . on ( 'change' , function ( ) {
scope . $apply ( function ( ) {
var array = [ ] ;
forEach ( selectElement . find ( 'option' ) , function ( option ) {
if ( option . selected ) {
array . push ( option . value ) ;
}
} ) ;
ctrl . $setViewValue ( array ) ;
} ) ;
} ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
function setupAsOptions ( scope , selectElement , ctrl ) {
var match ;
2018-05-05 12:13:16 +02:00
if ( ! ( match = optionsExp . match ( NG _OPTIONS _REGEXP ) ) ) {
2016-04-18 12:34:29 +00:00
throw ngOptionsMinErr ( 'iexp' ,
"Expected expression in form of " +
"'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" +
" but got '{0}'. Element: {1}" ,
optionsExp , startingTag ( selectElement ) ) ;
}
var displayFn = $parse ( match [ 2 ] || match [ 1 ] ) ,
valueName = match [ 4 ] || match [ 6 ] ,
2018-05-05 12:13:16 +02:00
selectAs = / as / . test ( match [ 0 ] ) && match [ 1 ] ,
selectAsFn = selectAs ? $parse ( selectAs ) : null ,
2016-04-18 12:34:29 +00:00
keyName = match [ 5 ] ,
groupByFn = $parse ( match [ 3 ] || '' ) ,
valueFn = $parse ( match [ 2 ] ? match [ 1 ] : valueName ) ,
valuesFn = $parse ( match [ 7 ] ) ,
track = match [ 8 ] ,
trackFn = track ? $parse ( match [ 8 ] ) : null ,
2018-05-05 12:13:16 +02:00
trackKeysCache = { } ,
2016-04-18 12:34:29 +00:00
// This is an array of array of existing option groups in DOM.
// We try to reuse these if possible
// - optionGroupsCache[0] is the options with no option group
// - optionGroupsCache[?][0] is the parent: either the SELECT or OPTGROUP element
2018-05-05 12:13:16 +02:00
optionGroupsCache = [ [ { element : selectElement , label : '' } ] ] ,
//re-usable object to represent option's locals
locals = { } ;
2016-04-18 12:34:29 +00:00
if ( nullOption ) {
// compile the element since there might be bindings in it
$compile ( nullOption ) ( scope ) ;
// remove the class, which is added automatically because we recompile the element and it
// becomes the compilation root
nullOption . removeClass ( 'ng-scope' ) ;
// we need to remove it before calling selectElement.empty() because otherwise IE will
// remove the label from the element. wtf?
nullOption . remove ( ) ;
}
// clear contents, we'll add what's needed based on the model
selectElement . empty ( ) ;
2018-05-05 12:13:16 +02:00
selectElement . on ( 'change' , selectionChanged ) ;
ctrl . $render = render ;
scope . $watchCollection ( valuesFn , scheduleRendering ) ;
scope . $watchCollection ( getLabels , scheduleRendering ) ;
if ( multiple ) {
scope . $watchCollection ( function ( ) { return ctrl . $modelValue ; } , scheduleRendering ) ;
}
// ------------------------------------------------------------------ //
function callExpression ( exprFn , key , value ) {
locals [ valueName ] = value ;
if ( keyName ) locals [ keyName ] = key ;
return exprFn ( scope , locals ) ;
}
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
function selectionChanged ( ) {
scope . $apply ( function ( ) {
var collection = valuesFn ( scope ) || [ ] ;
var viewValue ;
2016-04-18 12:34:29 +00:00
if ( multiple ) {
2018-05-05 12:13:16 +02:00
viewValue = [ ] ;
forEach ( selectElement . val ( ) , function ( selectedKey ) {
selectedKey = trackFn ? trackKeysCache [ selectedKey ] : selectedKey ;
viewValue . push ( getViewValue ( selectedKey , collection [ selectedKey ] ) ) ;
} ) ;
2016-04-18 12:34:29 +00:00
} else {
2018-05-05 12:13:16 +02:00
var selectedKey = trackFn ? trackKeysCache [ selectElement . val ( ) ] : selectElement . val ( ) ;
viewValue = getViewValue ( selectedKey , collection [ selectedKey ] ) ;
2016-04-18 12:34:29 +00:00
}
2018-05-05 12:13:16 +02:00
ctrl . $setViewValue ( viewValue ) ;
render ( ) ;
2016-04-18 12:34:29 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
}
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
function getViewValue ( key , value ) {
if ( key === '?' ) {
return undefined ;
} else if ( key === '' ) {
return null ;
} else {
var viewValueFn = selectAsFn ? selectAsFn : valueFn ;
return callExpression ( viewValueFn , key , value ) ;
}
}
function getLabels ( ) {
var values = valuesFn ( scope ) ;
var toDisplay ;
if ( values && isArray ( values ) ) {
toDisplay = new Array ( values . length ) ;
for ( var i = 0 , ii = values . length ; i < ii ; i ++ ) {
toDisplay [ i ] = callExpression ( displayFn , i , values [ i ] ) ;
}
return toDisplay ;
} else if ( values ) {
// TODO: Add a test for this case
toDisplay = { } ;
for ( var prop in values ) {
if ( values . hasOwnProperty ( prop ) ) {
toDisplay [ prop ] = callExpression ( displayFn , prop , values [ prop ] ) ;
}
}
}
return toDisplay ;
}
function createIsSelectedFn ( viewValue ) {
var selectedSet ;
if ( multiple ) {
if ( trackFn && isArray ( viewValue ) ) {
selectedSet = new HashMap ( [ ] ) ;
for ( var trackIndex = 0 ; trackIndex < viewValue . length ; trackIndex ++ ) {
// tracking by key
selectedSet . put ( callExpression ( trackFn , null , viewValue [ trackIndex ] ) , true ) ;
}
} else {
selectedSet = new HashMap ( viewValue ) ;
}
} else if ( trackFn ) {
viewValue = callExpression ( trackFn , null , viewValue ) ;
}
return function isSelected ( key , value ) {
var compareValueFn ;
if ( trackFn ) {
compareValueFn = trackFn ;
} else if ( selectAsFn ) {
compareValueFn = selectAsFn ;
} else {
compareValueFn = valueFn ;
}
if ( multiple ) {
return isDefined ( selectedSet . remove ( callExpression ( compareValueFn , key , value ) ) ) ;
} else {
return viewValue === callExpression ( compareValueFn , key , value ) ;
}
} ;
}
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
function scheduleRendering ( ) {
if ( ! renderScheduled ) {
scope . $$postDigest ( render ) ;
renderScheduled = true ;
}
}
/ * *
* A new labelMap is created with each render .
* This function is called for each existing option with added = false ,
* and each new option with added = true .
* - Labels that are passed to this method twice ,
* ( once with added = true and once with added = false ) will end up with a value of 0 , and
* will cause no change to happen to the corresponding option .
* - Labels that are passed to this method only once with added = false will end up with a
* value of - 1 and will eventually be passed to selectCtrl . removeOption ( )
* - Labels that are passed to this method only once with added = true will end up with a
* value of 1 and will eventually be passed to selectCtrl . addOption ( )
* /
function updateLabelMap ( labelMap , label , added ) {
labelMap [ label ] = labelMap [ label ] || 0 ;
labelMap [ label ] += ( added ? 1 : - 1 ) ;
}
2016-04-18 12:34:29 +00:00
function render ( ) {
2018-05-05 12:13:16 +02:00
renderScheduled = false ;
// Temporary location for the option groups before we render them
2016-04-18 12:34:29 +00:00
var optionGroups = { '' : [ ] } ,
optionGroupNames = [ '' ] ,
optionGroupName ,
optionGroup ,
option ,
existingParent , existingOptions , existingOption ,
2018-05-05 12:13:16 +02:00
viewValue = ctrl . $viewValue ,
2016-04-18 12:34:29 +00:00
values = valuesFn ( scope ) || [ ] ,
keys = keyName ? sortedKeys ( values ) : values ,
key ,
2018-05-05 12:13:16 +02:00
value ,
2016-04-18 12:34:29 +00:00
groupLength , length ,
groupIndex , index ,
2018-05-05 12:13:16 +02:00
labelMap = { } ,
2016-04-18 12:34:29 +00:00
selected ,
2018-05-05 12:13:16 +02:00
isSelected = createIsSelectedFn ( viewValue ) ,
anySelected = false ,
2016-04-18 12:34:29 +00:00
lastElement ,
element ,
2018-05-05 12:13:16 +02:00
label ,
optionId ;
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
trackKeysCache = { } ;
2016-04-18 12:34:29 +00:00
// We now build up the list of options we need (we merge later)
for ( index = 0 ; length = keys . length , index < length ; index ++ ) {
key = index ;
if ( keyName ) {
key = keys [ index ] ;
2018-05-05 12:13:16 +02:00
if ( key . charAt ( 0 ) === '$' ) continue ;
2016-04-18 12:34:29 +00:00
}
2018-05-05 12:13:16 +02:00
value = values [ key ] ;
2016-04-18 12:34:29 +00:00
2018-05-05 12:13:16 +02:00
optionGroupName = callExpression ( groupByFn , key , value ) || '' ;
2016-04-18 12:34:29 +00:00
if ( ! ( optionGroup = optionGroups [ optionGroupName ] ) ) {
optionGroup = optionGroups [ optionGroupName ] = [ ] ;
optionGroupNames . push ( optionGroupName ) ;
}
2018-05-05 12:13:16 +02:00
selected = isSelected ( key , value ) ;
anySelected = anySelected || selected ;
label = callExpression ( displayFn , key , value ) ; // what will be seen by the user
2016-04-18 12:34:29 +00:00
// doing displayFn(scope, locals) || '' overwrites zero values
label = isDefined ( label ) ? label : '' ;
2018-05-05 12:13:16 +02:00
optionId = trackFn ? trackFn ( scope , locals ) : ( keyName ? keys [ index ] : index ) ;
if ( trackFn ) {
trackKeysCache [ optionId ] = key ;
}
2016-04-18 12:34:29 +00:00
optionGroup . push ( {
// either the index into array or key from object
2018-05-05 12:13:16 +02:00
id : optionId ,
2016-04-18 12:34:29 +00:00
label : label ,
selected : selected // determine if we should be selected
} ) ;
}
if ( ! multiple ) {
2018-05-05 12:13:16 +02:00
if ( nullOption || viewValue === null ) {
2016-04-18 12:34:29 +00:00
// insert null option if we have a placeholder, or the model is null
2018-05-05 12:13:16 +02:00
optionGroups [ '' ] . unshift ( { id : '' , label : '' , selected : ! anySelected } ) ;
} else if ( ! anySelected ) {
2016-04-18 12:34:29 +00:00
// option could not be found, we have to insert the undefined item
optionGroups [ '' ] . unshift ( { id : '?' , label : '' , selected : true } ) ;
}
}
// Now we need to update the list of DOM nodes to match the optionGroups we computed above
for ( groupIndex = 0 , groupLength = optionGroupNames . length ;
groupIndex < groupLength ;
groupIndex ++ ) {
// current option group name or '' if no group
optionGroupName = optionGroupNames [ groupIndex ] ;
// list of options for that group. (first item has the parent)
optionGroup = optionGroups [ optionGroupName ] ;
if ( optionGroupsCache . length <= groupIndex ) {
// we need to grow the optionGroups
existingParent = {
element : optGroupTemplate . clone ( ) . attr ( 'label' , optionGroupName ) ,
label : optionGroup . label
} ;
existingOptions = [ existingParent ] ;
optionGroupsCache . push ( existingOptions ) ;
selectElement . append ( existingParent . element ) ;
} else {
existingOptions = optionGroupsCache [ groupIndex ] ;
existingParent = existingOptions [ 0 ] ; // either SELECT (no group) or OPTGROUP element
// update the OPTGROUP label if not the same.
if ( existingParent . label != optionGroupName ) {
existingParent . element . attr ( 'label' , existingParent . label = optionGroupName ) ;
}
}
lastElement = null ; // start at the beginning
2018-05-05 12:13:16 +02:00
for ( index = 0 , length = optionGroup . length ; index < length ; index ++ ) {
2016-04-18 12:34:29 +00:00
option = optionGroup [ index ] ;
2018-05-05 12:13:16 +02:00
if ( ( existingOption = existingOptions [ index + 1 ] ) ) {
2016-04-18 12:34:29 +00:00
// reuse elements
lastElement = existingOption . element ;
if ( existingOption . label !== option . label ) {
2018-05-05 12:13:16 +02:00
updateLabelMap ( labelMap , existingOption . label , false ) ;
updateLabelMap ( labelMap , option . label , true ) ;
2016-04-18 12:34:29 +00:00
lastElement . text ( existingOption . label = option . label ) ;
2018-05-05 12:13:16 +02:00
lastElement . prop ( 'label' , existingOption . label ) ;
2016-04-18 12:34:29 +00:00
}
if ( existingOption . id !== option . id ) {
lastElement . val ( existingOption . id = option . id ) ;
}
// lastElement.prop('selected') provided by jQuery has side-effects
if ( lastElement [ 0 ] . selected !== option . selected ) {
lastElement . prop ( 'selected' , ( existingOption . selected = option . selected ) ) ;
2018-05-05 12:13:16 +02:00
if ( msie ) {
// See #7692
// The selected item wouldn't visually update on IE without this.
// Tested on Win7: IE9, IE10 and IE11. Future IEs should be tested as well
lastElement . prop ( 'selected' , existingOption . selected ) ;
}
2016-04-18 12:34:29 +00:00
}
} else {
// grow elements
// if it's a null option
if ( option . id === '' && nullOption ) {
// put back the pre-compiled element
element = nullOption ;
} else {
// jQuery(v1.4.2) Bug: We should be able to chain the method calls, but
// in this version of jQuery on some browser the .text() returns a string
// rather then the element.
( element = optionTemplate . clone ( ) )
. val ( option . id )
2018-05-05 12:13:16 +02:00
. prop ( 'selected' , option . selected )
2016-04-18 12:34:29 +00:00
. attr ( 'selected' , option . selected )
2018-05-05 12:13:16 +02:00
. prop ( 'label' , option . label )
2016-04-18 12:34:29 +00:00
. text ( option . label ) ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
existingOptions . push ( existingOption = {
element : element ,
label : option . label ,
id : option . id ,
selected : option . selected
} ) ;
2018-05-05 12:13:16 +02:00
updateLabelMap ( labelMap , option . label , true ) ;
2016-04-18 12:34:29 +00:00
if ( lastElement ) {
lastElement . after ( element ) ;
} else {
existingParent . element . append ( element ) ;
}
lastElement = element ;
}
}
// remove any excessive OPTIONs in a group
index ++ ; // increment since the existingOptions[0] is parent element not OPTION
2018-05-05 12:13:16 +02:00
while ( existingOptions . length > index ) {
option = existingOptions . pop ( ) ;
updateLabelMap ( labelMap , option . label , false ) ;
option . element . remove ( ) ;
2016-04-18 12:34:29 +00:00
}
}
// remove any excessive OPTGROUPs from select
2018-05-05 12:13:16 +02:00
while ( optionGroupsCache . length > groupIndex ) {
// remove all the labels in the option group
optionGroup = optionGroupsCache . pop ( ) ;
for ( index = 1 ; index < optionGroup . length ; ++ index ) {
updateLabelMap ( labelMap , optionGroup [ index ] . label , false ) ;
}
optionGroup [ 0 ] . element . remove ( ) ;
2016-04-18 12:34:29 +00:00
}
2018-05-05 12:13:16 +02:00
forEach ( labelMap , function ( count , label ) {
if ( count > 0 ) {
selectCtrl . addOption ( label ) ;
} else if ( count < 0 ) {
selectCtrl . removeOption ( label ) ;
}
} ) ;
2016-04-18 12:34:29 +00:00
}
}
}
} ;
} ] ;
2016-03-28 10:46:51 +00:00
var optionDirective = [ '$interpolate' , function ( $interpolate ) {
2016-04-18 12:34:29 +00:00
var nullSelectCtrl = {
addOption : noop ,
removeOption : noop
} ;
2016-03-28 10:46:51 +00:00
return {
restrict : 'E' ,
priority : 100 ,
compile : function ( element , attr ) {
2016-04-18 12:34:29 +00:00
if ( isUndefined ( attr . value ) ) {
var interpolateFn = $interpolate ( element . text ( ) , true ) ;
if ( ! interpolateFn ) {
2016-03-28 10:46:51 +00:00
attr . $set ( 'value' , element . text ( ) ) ;
}
}
2018-05-05 12:13:16 +02:00
return function ( scope , element , attr ) {
2016-03-28 10:46:51 +00:00
var selectCtrlName = '$selectController' ,
parent = element . parent ( ) ,
selectCtrl = parent . data ( selectCtrlName ) ||
parent . parent ( ) . data ( selectCtrlName ) ; // in case we are in optgroup
2018-05-05 12:13:16 +02:00
if ( ! selectCtrl || ! selectCtrl . databound ) {
2016-04-18 12:34:29 +00:00
selectCtrl = nullSelectCtrl ;
}
if ( interpolateFn ) {
scope . $watch ( interpolateFn , function interpolateWatchAction ( newVal , oldVal ) {
attr . $set ( 'value' , newVal ) ;
2018-05-05 12:13:16 +02:00
if ( oldVal !== newVal ) {
selectCtrl . removeOption ( oldVal ) ;
}
selectCtrl . addOption ( newVal , element ) ;
2016-04-18 12:34:29 +00:00
} ) ;
} else {
2018-05-05 12:13:16 +02:00
selectCtrl . addOption ( attr . value , element ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
element . on ( '$destroy' , function ( ) {
selectCtrl . removeOption ( attr . value ) ;
} ) ;
2016-03-28 10:46:51 +00:00
} ;
}
} ;
} ] ;
var styleDirective = valueFn ( {
restrict : 'E' ,
2018-05-05 12:13:16 +02:00
terminal : false
2016-03-28 10:46:51 +00:00
} ) ;
2018-05-05 12:13:16 +02:00
var requiredDirective = function ( ) {
return {
restrict : 'A' ,
require : '?ngModel' ,
link : function ( scope , elm , attr , ctrl ) {
if ( ! ctrl ) return ;
attr . required = true ; // force truthy in case we are on non input element
ctrl . $validators . required = function ( modelValue , viewValue ) {
return ! attr . required || ! ctrl . $isEmpty ( viewValue ) ;
} ;
attr . $observe ( 'required' , function ( ) {
ctrl . $validate ( ) ;
} ) ;
}
} ;
} ;
var patternDirective = function ( ) {
return {
restrict : 'A' ,
require : '?ngModel' ,
link : function ( scope , elm , attr , ctrl ) {
if ( ! ctrl ) return ;
var regexp , patternExp = attr . ngPattern || attr . pattern ;
attr . $observe ( 'pattern' , function ( regex ) {
if ( isString ( regex ) && regex . length > 0 ) {
regex = new RegExp ( '^' + regex + '$' ) ;
}
if ( regex && ! regex . test ) {
throw minErr ( 'ngPattern' ) ( 'noregexp' ,
'Expected {0} to be a RegExp but was {1}. Element: {2}' , patternExp ,
regex , startingTag ( elm ) ) ;
}
regexp = regex || undefined ;
ctrl . $validate ( ) ;
} ) ;
ctrl . $validators . pattern = function ( modelValue , viewValue ) {
// HTML5 pattern constraint validates the input value, so we validate the viewValue
return ctrl . $isEmpty ( viewValue ) || isUndefined ( regexp ) || regexp . test ( viewValue ) ;
} ;
}
} ;
} ;
var maxlengthDirective = function ( ) {
return {
restrict : 'A' ,
require : '?ngModel' ,
link : function ( scope , elm , attr , ctrl ) {
if ( ! ctrl ) return ;
var maxlength = - 1 ;
attr . $observe ( 'maxlength' , function ( value ) {
var intVal = int ( value ) ;
maxlength = isNaN ( intVal ) ? - 1 : intVal ;
ctrl . $validate ( ) ;
} ) ;
ctrl . $validators . maxlength = function ( modelValue , viewValue ) {
return ( maxlength < 0 ) || ctrl . $isEmpty ( viewValue ) || ( viewValue . length <= maxlength ) ;
} ;
}
} ;
} ;
var minlengthDirective = function ( ) {
return {
restrict : 'A' ,
require : '?ngModel' ,
link : function ( scope , elm , attr , ctrl ) {
if ( ! ctrl ) return ;
var minlength = 0 ;
attr . $observe ( 'minlength' , function ( value ) {
minlength = int ( value ) || 0 ;
ctrl . $validate ( ) ;
} ) ;
ctrl . $validators . minlength = function ( modelValue , viewValue ) {
return ctrl . $isEmpty ( viewValue ) || viewValue . length >= minlength ;
} ;
}
} ;
} ;
if ( window . angular . bootstrap ) {
//AngularJS is already loaded, so we can return here...
console . log ( 'WARNING: Tried to load angular more than once.' ) ;
return ;
}
//try to bind to jquery now so that one can write jqLite(document).ready()
2016-04-18 12:34:29 +00:00
//but we will rebind on bootstrap again.
bindJQuery ( ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
publishExternalAPI ( angular ) ;
2016-03-28 10:46:51 +00:00
jqLite ( document ) . ready ( function ( ) {
angularInit ( document , bootstrap ) ;
} ) ;
} ) ( window , document ) ;
2018-05-05 12:13:16 +02:00
! window . angular . $$csp ( ) && window . angular . element ( document . head ) . prepend ( '<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate){display:none !important;}ng\\:form{display:block;}</style>' ) ;